Back up APKs as early as possible to not miss any

Fixes #68
This commit is contained in:
Torsten Grote 2020-01-18 16:42:26 -03:00
parent fcf17fe23a
commit a98364efbe
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
3 changed files with 24 additions and 15 deletions

View file

@ -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()
}

View file

@ -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
}
}

View file

@ -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
}
}