diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt index 9785857e..4ecb459e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt @@ -1,6 +1,5 @@ package com.stevesoltys.seedvault.crypto -import android.os.Build.VERSION.SDK_INT import android.security.keystore.KeyProperties.BLOCK_MODE_GCM import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE import android.security.keystore.KeyProperties.PURPOSE_DECRYPT @@ -48,7 +47,7 @@ internal class KeyManagerImpl : KeyManager { override fun storeBackupKey(seed: ByteArray) { if (seed.size < KEY_SIZE_BYTES) throw IllegalArgumentException() - // TODO check if using first 256 of 512 bytes produced by PBKDF2WithHmacSHA512 is safe! + // TODO check if using first 256 of 512 bits produced by PBKDF2WithHmacSHA512 is safe! val secretKeySpec = SecretKeySpec(seed, 0, KEY_SIZE_BYTES, "AES") val ksEntry = SecretKeyEntry(secretKeySpec) keyStore.setEntry(KEY_ALIAS, ksEntry, getKeyProtection()) @@ -68,7 +67,7 @@ internal class KeyManagerImpl : KeyManager { .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) .setRandomizedEncryptionRequired(true) // unlocking is required only for decryption, so when restoring from backup - if (SDK_INT >= 28) builder.setUnlockedDeviceRequired(true) + builder.setUnlockedDeviceRequired(true) return builder.build() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 03f5f3d5..5b673ccd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -6,6 +6,7 @@ import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import android.content.Context import android.content.pm.PackageInfo +import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.os.ParcelFileDescriptor import android.util.Log import com.stevesoltys.seedvault.BackupNotificationManager @@ -94,7 +95,20 @@ internal class BackupCoordinator( return targetPackage.packageName != plugin.providerPackageName } + /** + * Ask the transport about current quota for backup size of the package. + * + * @param packageName ID of package to provide the quota. + * @param isFullBackup If set, transport should return limit for full data backup, + * otherwise for key-value backup. + * @return Current limit on backup size in bytes. + */ fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { + // try to back up APK here as later methods are sometimes not called called + val pm = context.packageManager + backUpApk(pm.getPackageInfo(packageName, GET_SIGNING_CERTIFICATES)) + + // report back quota Log.i(TAG, "Get backup quota for $packageName. Is full backup: $isFullBackup.") val quota = if (isFullBackup) full.getQuota() else kv.getQuota() Log.i(TAG, "Reported quota of $quota bytes.") @@ -132,8 +146,7 @@ internal class BackupCoordinator( // hook in here to back up APKs of apps that are otherwise not allowed for backup backUpNotAllowedPackages() } - val result = kv.performBackup(packageInfo, data, flags) - return backUpApk(result, packageInfo) + return kv.performBackup(packageInfo, data, flags) } // ------------------------------------------------------------------------------------ @@ -166,8 +179,7 @@ internal class BackupCoordinator( fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int): Int { cancelReason = UNKNOWN_ERROR - val result = full.performFullBackup(targetPackage, fileDescriptor, flags) - return backUpApk(result, targetPackage) + return full.performFullBackup(targetPackage, fileDescriptor, flags) } fun sendBackupData(numBytes: Int) = full.sendBackupData(numBytes) @@ -254,27 +266,24 @@ internal class BackupCoordinator( Log.d(TAG, "Checking if APKs of opt-out apps need backup...") packageService.notAllowedPackages.forEach { optOutPackageInfo -> try { - backUpApk(0, optOutPackageInfo, NOT_ALLOWED) + backUpApk(optOutPackageInfo, NOT_ALLOWED) } catch (e: IOException) { Log.e(TAG, "Error backing up opt-out APK of ${optOutPackageInfo.packageName}", e) } } } - private fun backUpApk(result: Int, packageInfo: PackageInfo, packageState: PackageState = UNKNOWN_ERROR): Int { + private fun backUpApk(packageInfo: PackageInfo, packageState: PackageState = UNKNOWN_ERROR) { val packageName = packageInfo.packageName - if (packageName == MAGIC_PACKAGE_MANAGER) return result - return try { + try { apkBackup.backupApkIfNecessary(packageInfo, packageState) { plugin.getApkOutputStream(packageInfo) }?.let { packageMetadata -> val outputStream = plugin.getMetadataOutputStream() metadataManager.onApkBackedUp(packageInfo, packageMetadata, outputStream) } - result } catch (e: IOException) { Log.e(TAG, "Error while writing APK or metadata for $packageName", e) - TRANSPORT_PACKAGE_REJECTED } } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt index 2bb51378..63c0e741 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt @@ -113,6 +113,7 @@ internal class BackupCoordinatorTest : BackupTest() { val isFullBackup = Random.nextBoolean() val quota = Random.nextLong() + expectApkBackupAndMetadataWrite() if (isFullBackup) { every { full.getQuota() } returns quota } else { @@ -264,9 +265,9 @@ internal class BackupCoordinatorTest : BackupTest() { } private fun expectApkBackupAndMetadataWrite() { - every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata + every { apkBackup.backupApkIfNecessary(any(), UNKNOWN_ERROR, any()) } returns packageMetadata every { plugin.getMetadataOutputStream() } returns metadataOutputStream - every { metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) } just Runs + every { metadataManager.onApkBackedUp(any(), packageMetadata, metadataOutputStream) } just Runs } }