Write metadata with new version 1
Reading still supports version 0
diff --git a/app/build.gradle b/app/build.gradle
index 2f5208c..8509fc4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -105,6 +105,8 @@
implementation rootProject.ext.std_libs.androidx_documentfile
implementation rootProject.ext.std_libs.com_google_android_material
+ implementation rootProject.ext.storage_libs.com_google_crypto_tink_android
+
/**
* Storage Dependencies
*/
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 78c8e6e..0ab1e79 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt
@@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.crypto
+import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
import com.stevesoltys.seedvault.header.HeaderReader
import com.stevesoltys.seedvault.header.HeaderWriter
import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH
@@ -7,10 +8,13 @@
import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE
import com.stevesoltys.seedvault.header.SegmentHeader
import com.stevesoltys.seedvault.header.VersionHeader
+import org.calyxos.backup.storage.crypto.StreamCrypto
+import org.calyxos.backup.storage.crypto.StreamCrypto.deriveStreamKey
import java.io.EOFException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
+import java.security.GeneralSecurityException
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import kotlin.math.min
@@ -34,6 +38,22 @@
interface Crypto {
/**
+ * Returns a [AesGcmHkdfStreaming] encrypting stream
+ * that gets encrypted with the given secret.
+ */
+ @Throws(IOException::class, GeneralSecurityException::class)
+ fun newEncryptingStream(
+ outputStream: OutputStream,
+ associatedData: ByteArray = ByteArray(0)
+ ): OutputStream
+
+ @Throws(IOException::class, GeneralSecurityException::class)
+ fun newDecryptingStream(
+ inputStream: InputStream,
+ associatedData: ByteArray = ByteArray(0)
+ ): InputStream
+
+ /**
* Encrypts a backup stream header ([VersionHeader]) and writes it to the given [OutputStream].
*
* The header using a small segment containing only
@@ -105,12 +125,35 @@
fun verifyBackupKey(seed: ByteArray): Boolean
}
+internal const val TYPE_METADATA: Byte = 0x00
+internal const val TYPE_BACKUP_KV: Byte = 0x01
+internal const val TYPE_BACKUP_FULL: Byte = 0x02
+
internal class CryptoImpl(
+ private val keyManager: KeyManager,
private val cipherFactory: CipherFactory,
private val headerWriter: HeaderWriter,
private val headerReader: HeaderReader
) : Crypto {
+ private val key: ByteArray by lazy {
+ deriveStreamKey(keyManager.getMainKey(), "app data key".toByteArray())
+ }
+
+ override fun newEncryptingStream(
+ outputStream: OutputStream,
+ associatedData: ByteArray
+ ): OutputStream {
+ return StreamCrypto.newEncryptingStream(key, outputStream, associatedData)
+ }
+
+ override fun newDecryptingStream(
+ inputStream: InputStream,
+ associatedData: ByteArray
+ ): InputStream {
+ return StreamCrypto.newDecryptingStream(key, inputStream, associatedData)
+ }
+
@Throws(IOException::class)
override fun encryptHeader(outputStream: OutputStream, versionHeader: VersionHeader) {
val bytes = headerWriter.getEncodedVersionHeader(versionHeader)
diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt
index d15c960..41b0dbb 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt
@@ -15,5 +15,5 @@
}
KeyManagerImpl(keyStore)
}
- single<Crypto> { CryptoImpl(get(), get(), get()) }
+ single<Crypto> { CryptoImpl(get(), get(), get(), get()) }
}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt b/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt
index 248cd8c..f6a6dc2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt
@@ -2,7 +2,7 @@
import com.stevesoltys.seedvault.crypto.GCM_AUTHENTICATION_TAG_LENGTH
-internal const val VERSION: Byte = 0
+internal const val VERSION: Byte = 1
internal const val MAX_PACKAGE_LENGTH_SIZE = 255
internal const val MAX_KEY_LENGTH_SIZE = MAX_PACKAGE_LENGTH_SIZE
internal const val MAX_VERSION_HEADER_SIZE =
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
index 8400cfc..025b82b 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
@@ -2,9 +2,12 @@
import android.content.pm.ApplicationInfo.FLAG_STOPPED
import android.os.Build
+import com.stevesoltys.seedvault.crypto.TYPE_METADATA
import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
+import org.calyxos.backup.storage.crypto.StreamCrypto.toByteArray
import java.io.InputStream
+import java.nio.ByteBuffer
typealias PackageMetadataMap = HashMap<String, PackageMetadata>
@@ -110,3 +113,9 @@
*/
constructor(token: Long) : this(token, null, true)
}
+
+internal fun getAD(version: Byte, token: Long) = ByteBuffer.allocate(2 + 8)
+ .put(version)
+ .put(TYPE_METADATA)
+ .put(token.toByteArray())
+ .array()
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
index 938cfc0..a006a30 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -10,6 +10,7 @@
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import com.stevesoltys.seedvault.Clock
+import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
@@ -205,6 +206,9 @@
private val mLastBackupTime = MutableLiveData<Long>()
internal val lastBackupTime: LiveData<Long> = mLastBackupTime.distinctUntilChanged()
+ internal val isLegacyFormat: Boolean
+ @Synchronized get() = metadata.version < VERSION
+
@Synchronized
fun getPackageMetadata(packageName: String): PackageMetadata? {
return metadata.packageMetadataMap[packageName]?.copy()
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
index a6e2dc7..d1e30ac 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
@@ -14,6 +14,7 @@
import org.json.JSONObject
import java.io.IOException
import java.io.InputStream
+import java.security.GeneralSecurityException
import javax.crypto.AEADBadTagException
interface MetadataReader {
@@ -47,12 +48,29 @@
val version = inputStream.read().toByte()
if (version < 0) throw IOException()
if (version > VERSION) throw UnsupportedVersionException(version)
+ if (version == 0.toByte()) return readMetadataV0(inputStream, expectedToken)
+
+ val metadataBytes = try {
+ crypto.newDecryptingStream(inputStream, getAD(version, expectedToken)).readBytes()
+ } catch (e: GeneralSecurityException) {
+ throw DecryptionFailedException(e)
+ }
+ return decode(metadataBytes, version, expectedToken)
+ }
+
+ @Throws(
+ SecurityException::class,
+ DecryptionFailedException::class,
+ UnsupportedVersionException::class,
+ IOException::class
+ )
+ private fun readMetadataV0(inputStream: InputStream, expectedToken: Long): BackupMetadata {
val metadataBytes = try {
crypto.decryptMultipleSegments(inputStream)
} catch (e: AEADBadTagException) {
throw DecryptionFailedException(e)
}
- return decode(metadataBytes, version, expectedToken)
+ return decode(metadataBytes, 0.toByte(), expectedToken)
}
@Throws(SecurityException::class)
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
index 7afcd5b..9ace924 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
@@ -20,7 +20,9 @@
@Throws(IOException::class)
override fun write(metadata: BackupMetadata, outputStream: OutputStream) {
outputStream.write(ByteArray(1).apply { this[0] = metadata.version })
- crypto.encryptMultipleSegments(outputStream, encode(metadata))
+ crypto.newEncryptingStream(outputStream, getAD(metadata.version, metadata.token)).use {
+ it.write(encode(metadata))
+ }
}
override fun encode(metadata: BackupMetadata): ByteArray {
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 69c4cd8..543bc47 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
@@ -238,6 +238,7 @@
// We need to reject them manually when we can not do a backup now.
// What else we tried can be found in: https://github.com/seedvault-app/seedvault/issues/102
if (packageName == MAGIC_PACKAGE_MANAGER) {
+ val isIncremental = flags and FLAG_INCREMENTAL != 0
if (!settingsManager.canDoBackupNow()) {
// Returning anything else here (except non-incremental-required which re-tries)
// will make the system consider the backup state compromised
@@ -248,9 +249,17 @@
settingsManager.pmBackupNextTimeNonIncremental = true
data.close()
return TRANSPORT_OK
- } else if (flags and FLAG_INCREMENTAL != 0 &&
- settingsManager.pmBackupNextTimeNonIncremental
- ) {
+ } else if (metadataManager.isLegacyFormat) {
+ // start a new restore set to upgrade from legacy format
+ // by starting a clean backup with all files using the new version
+ try {
+ startNewRestoreSet()
+ } catch (e: IOException) {
+ Log.e(TAG, "Error starting new restore set", e)
+ }
+ // this causes a backup error, but things should go back to normal afterwards
+ return TRANSPORT_NOT_INITIALIZED
+ } else if (isIncremental && settingsManager.pmBackupNextTimeNonIncremental) {
settingsManager.pmBackupNextTimeNonIncremental = false
data.close()
return TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED
diff --git a/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt b/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt
index 6d4a126..1bb0c7c 100644
--- a/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt
+++ b/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt
@@ -29,12 +29,8 @@
throw NotImplementedError("not implemented")
}
- override fun getBackupKey(): SecretKey {
- return key
- }
+ override fun getBackupKey(): SecretKey = key
- override fun getMainKey(): SecretKey {
- throw NotImplementedError("not implemented")
- }
+ override fun getMainKey(): SecretKey = key
}
diff --git a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt
index b1ad45a..539fb20 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt
@@ -21,7 +21,7 @@
private val testCryptoModule = module {
factory<CipherFactory> { CipherFactoryImpl(get()) }
single<KeyManager> { KeyManagerTestImpl() }
- single<Crypto> { CryptoImpl(get(), get(), get()) }
+ single<Crypto> { CryptoImpl(get(), get(), get(), get()) }
}
private val appModule = module {
single { Clock() }
diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt
index 26a3f19..e0f914b 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt
@@ -20,11 +20,12 @@
@TestInstance(PER_METHOD)
class CryptoImplTest {
+ private val keyManager = mockk<KeyManager>()
private val cipherFactory = mockk<CipherFactory>()
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val crypto = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val cipher = mockk<Cipher>()
diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt
index 46b68f2..3d4966c 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt
@@ -21,7 +21,7 @@
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val crypto = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val cleartext = byteArrayOf(0x01, 0x02, 0x03)
diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
index 643344f..c8cece9 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
@@ -36,11 +36,12 @@
@TestInstance(PER_METHOD)
class CryptoTest {
+ private val keyManager = mockk<KeyManager>()
private val cipherFactory = mockk<CipherFactory>()
private val headerWriter = mockk<HeaderWriter>()
private val headerReader = mockk<HeaderReader>()
- private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val crypto = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val cipher = mockk<Cipher>()
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
index 1930ac1..eac322d 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
@@ -29,7 +29,7 @@
private val cipherFactory = CipherFactoryImpl(keyManager)
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val cryptoImpl = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val writer = MetadataWriterImpl(cryptoImpl)
private val reader = MetadataReaderImpl(cryptoImpl)
@@ -48,7 +48,6 @@
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
-
assertEquals(metadata, reader.readMetadata(inputStream, metadata.token))
}
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
index b4d1fb9..bd9fc7f 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
@@ -6,7 +6,6 @@
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
import com.stevesoltys.seedvault.header.HeaderReaderImpl
import com.stevesoltys.seedvault.header.HeaderWriterImpl
-import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.toByteArrayFromHex
@@ -30,7 +29,7 @@
private val cipherFactory = CipherFactoryImpl(keyManager)
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val cryptoImpl = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val reader = MetadataReaderImpl(cryptoImpl)
@@ -55,7 +54,7 @@
private fun getMetadata(
packageMetadata: HashMap<String, PackageMetadata> = HashMap()
) = BackupMetadata(
- version = VERSION,
+ version = 0x00,
token = 1337L,
time = 2342L,
androidVersion = 30,
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
index e7e07b4..7bc3320 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
@@ -59,7 +59,7 @@
private val cipherFactory = CipherFactoryImpl(keyManager)
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val cryptoImpl = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val metadataReader = MetadataReaderImpl(cryptoImpl)
private val notificationManager = mockk<BackupNotificationManager>()
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 d8375e6..24c9373 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
@@ -3,6 +3,7 @@
import android.app.backup.BackupTransport.FLAG_INCREMENTAL
import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED
+import android.app.backup.BackupTransport.TRANSPORT_NOT_INITIALIZED
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
@@ -152,6 +153,7 @@
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, data, 0))
every { settingsManager.canDoBackupNow() } returns true
+ every { metadataManager.isLegacyFormat } returns false
every { settingsManager.pmBackupNextTimeNonIncremental } returns true
every { settingsManager.pmBackupNextTimeNonIncremental = false } just Runs
@@ -163,6 +165,27 @@
}
@Test
+ fun `performIncrementalBackup of @pm@ causes re-init when legacy format`() = runBlocking {
+ val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER }
+
+ every { settingsManager.canDoBackupNow() } returns true
+ every { metadataManager.isLegacyFormat } returns true
+
+ // start new restore set
+ every { clock.time() } returns token + 1
+ every { settingsManager.setNewToken(token + 1) } just Runs
+ coEvery { plugin.startNewRestoreSet(token + 1) } just Runs
+
+ every { data.close() } just Runs
+
+ // returns TRANSPORT_NOT_INITIALIZED to re-init next time
+ assertEquals(
+ TRANSPORT_NOT_INITIALIZED,
+ backup.performIncrementalBackup(packageInfo, data, 0)
+ )
+ }
+
+ @Test
fun `getBackupQuota() delegates to right plugin`() = runBlocking {
val isFullBackup = Random.nextBoolean()
val quota = Random.nextLong()
@@ -354,6 +377,7 @@
val packageMetadata: PackageMetadata = mockk()
every { settingsManager.canDoBackupNow() } returns true
+ every { metadataManager.isLegacyFormat } returns false
// do actual @pm@ backup
coEvery { kv.performBackup(packageInfo, fileDescriptor, 0) } returns TRANSPORT_OK
// now check if we have opt-out apps that we need to back up APKs for
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt
index 9a60cb4..355a0a3 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt
@@ -44,7 +44,7 @@
private val cipherFactory = CipherFactoryImpl(keyManager)
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
- private val cryptoImpl = CryptoImpl(cipherFactory, headerWriter, headerReader)
+ private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerWriter, headerReader)
private val metadataReader = MetadataReaderImpl(cryptoImpl)
private val notificationManager = mockk<BackupNotificationManager>()