diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt index 489e6c0a..acb6902e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt @@ -120,6 +120,7 @@ internal interface Crypto { internal const val TYPE_METADATA: Byte = 0x00 internal const val TYPE_BACKUP_KV: Byte = 0x01 internal const val TYPE_BACKUP_FULL: Byte = 0x02 +internal const val TYPE_ICONS: Byte = 0x03 internal class CryptoImpl( private val keyManager: KeyManager, diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 25a17a7c..ff44615d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -221,7 +221,7 @@ internal class RestoreViewModel( val token = restorableBackup.token val packagesWithIcons = try { plugin.getInputStream(token, FILE_BACKUP_ICONS).use { - iconManager.downloadIcons(it) + iconManager.downloadIcons(restorableBackup.version, token, it) } } catch (e: Exception) { Log.e(TAG, "Error loading icons:", e) diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt index 3ebff1f3..2e0a3af4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt @@ -101,7 +101,7 @@ internal class ApkBackupManager( try { val token = settingsManager.getToken() ?: throw IOException("no current token") pluginManager.appPlugin.getOutputStream(token, FILE_BACKUP_ICONS).use { - iconManager.uploadIcons(it) + iconManager.uploadIcons(token, it) } } catch (e: IOException) { Log.e(TAG, "Error uploading icons: ", e) diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/IconManager.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/IconManager.kt index 341083ae..1fb5f90c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/IconManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/IconManager.kt @@ -14,13 +14,19 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.crypto.Crypto +import com.stevesoltys.seedvault.crypto.TYPE_ICONS +import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.transport.backup.PackageService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.calyxos.backup.storage.crypto.StreamCrypto.toByteArray import java.io.File import java.io.IOException import java.io.InputStream import java.io.OutputStream +import java.nio.ByteBuffer +import java.security.GeneralSecurityException import java.util.zip.Deflater.BEST_SPEED import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -35,33 +41,36 @@ private val TAG = IconManager::class.simpleName internal class IconManager( private val context: Context, private val packageService: PackageService, + private val crypto: Crypto, ) { - @Throws(IOException::class) - fun uploadIcons(outputStream: OutputStream) { + @Throws(IOException::class, GeneralSecurityException::class) + fun uploadIcons(token: Long, outputStream: OutputStream) { Log.d(TAG, "Start uploading icons") val packageManager = context.packageManager - ZipOutputStream(outputStream).use { zip -> - zip.setLevel(BEST_SPEED) - val entries = mutableSetOf() - packageService.allUserPackages.forEach { - val drawable = packageManager.getApplicationIcon(it.applicationInfo) - if (packageManager.isDefaultApplicationIcon(drawable)) return@forEach - val entry = ZipEntry(it.packageName) - zip.putNextEntry(entry) - drawable.toBitmap(ICON_SIZE, ICON_SIZE).compress(WEBP_LOSSY, ICON_QUALITY, zip) - entries.add(it.packageName) - zip.closeEntry() - } - packageService.launchableSystemApps.forEach { - val drawable = it.loadIcon(packageManager) - if (packageManager.isDefaultApplicationIcon(drawable)) return@forEach - // check for duplicates (e.g. updated launchable system app) - if (it.activityInfo.packageName in entries) return@forEach - val entry = ZipEntry(it.activityInfo.packageName) - zip.putNextEntry(entry) - drawable.toBitmap(ICON_SIZE, ICON_SIZE).compress(WEBP_LOSSY, ICON_QUALITY, zip) - zip.closeEntry() + crypto.newEncryptingStream(outputStream, getAD(VERSION, token)).use { cryptoStream -> + ZipOutputStream(cryptoStream).use { zip -> + zip.setLevel(BEST_SPEED) + val entries = mutableSetOf() + packageService.allUserPackages.forEach { + val drawable = packageManager.getApplicationIcon(it.applicationInfo) + if (packageManager.isDefaultApplicationIcon(drawable)) return@forEach + val entry = ZipEntry(it.packageName) + zip.putNextEntry(entry) + drawable.toBitmap(ICON_SIZE, ICON_SIZE).compress(WEBP_LOSSY, ICON_QUALITY, zip) + entries.add(it.packageName) + zip.closeEntry() + } + packageService.launchableSystemApps.forEach { + val drawable = it.loadIcon(packageManager) + if (packageManager.isDefaultApplicationIcon(drawable)) return@forEach + // check for duplicates (e.g. updated launchable system app) + if (it.activityInfo.packageName in entries) return@forEach + val entry = ZipEntry(it.activityInfo.packageName) + zip.putNextEntry(entry) + drawable.toBitmap(ICON_SIZE, ICON_SIZE).compress(WEBP_LOSSY, ICON_QUALITY, zip) + zip.closeEntry() + } } } Log.d(TAG, "Finished uploading icons") @@ -71,21 +80,23 @@ internal class IconManager( * Downloads icons file from given [inputStream]. * @return a set of package names for which icons were found */ - @Throws(IOException::class, SecurityException::class) - fun downloadIcons(inputStream: InputStream): Set { + @Throws(IOException::class, SecurityException::class, GeneralSecurityException::class) + fun downloadIcons(version: Byte, token: Long, inputStream: InputStream): Set { Log.d(TAG, "Start downloading icons") val folder = File(context.cacheDir, CACHE_FOLDER) if (!folder.isDirectory && !folder.mkdirs()) throw IOException("Can't create cache folder for icons") val set = mutableSetOf() - ZipInputStream(inputStream).use { zip -> - var entry = zip.nextEntry - while (entry != null) { - File(folder, entry.name).outputStream().use { outputStream -> - zip.copyTo(outputStream) + crypto.newDecryptingStream(inputStream, getAD(version, token)).use { cryptoStream -> + ZipInputStream(cryptoStream).use { zip -> + var entry = zip.nextEntry + while (entry != null) { + File(folder, entry.name).outputStream().use { outputStream -> + zip.copyTo(outputStream) + } + set.add(entry.name) + entry = zip.nextEntry } - set.add(entry.name) - entry = zip.nextEntry } } Log.d(TAG, "Finished downloading icons") @@ -122,4 +133,10 @@ internal class IconManager( } } + private fun getAD(version: Byte, token: Long) = ByteBuffer.allocate(2 + 8) + .put(version) + .put(TYPE_ICONS) + .put(token.toByteArray()) + .array() + } diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt index 834fcc30..8c3d0c3e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt @@ -20,6 +20,7 @@ val workerModule = module { IconManager( context = androidContext(), packageService = get(), + crypto = get(), ) } single { diff --git a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt index 1ba643b8..81a412ae 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt @@ -249,7 +249,7 @@ internal class ApkBackupManagerTest : TransportTest() { private suspend fun expectUploadIcons() { val stream = ByteArrayOutputStream() coEvery { plugin.getOutputStream(token, FILE_BACKUP_ICONS) } returns stream - every { iconManager.uploadIcons(stream) } just Runs + every { iconManager.uploadIcons(token, stream) } just Runs } private fun expectAllAppsWillGetBackedUp() {