Split key concern out of storage plugin

This creates a KeyManager interface in the new core module which the storage module can use to get the key from.
This commit is contained in:
Torsten Grote 2024-08-23 17:35:40 -03:00
parent 9e56384cb2
commit 27eb95f768
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
21 changed files with 64 additions and 72 deletions

View file

@ -24,7 +24,7 @@ internal const val KEY_ALIAS_MAIN = "com.stevesoltys.seedvault.main"
private const val KEY_ALGORITHM_BACKUP = "AES" private const val KEY_ALGORITHM_BACKUP = "AES"
private const val KEY_ALGORITHM_MAIN = "HmacSHA256" private const val KEY_ALGORITHM_MAIN = "HmacSHA256"
interface KeyManager { interface KeyManager : org.calyxos.seedvault.core.crypto.KeyManager {
/** /**
* Store a new backup key derived from the given [seed]. * Store a new backup key derived from the given [seed].
* *
@ -57,14 +57,6 @@ interface KeyManager {
* because the key can not leave the [KeyStore]'s hardware security module. * because the key can not leave the [KeyStore]'s hardware security module.
*/ */
fun getBackupKey(): SecretKey fun getBackupKey(): SecretKey
/**
* Returns the main key, so it can be used for deriving sub-keys.
*
* Note that any attempt to export the key will return null or an empty [ByteArray],
* because the key can not leave the [KeyStore]'s hardware security module.
*/
fun getMainKey(): SecretKey
} }
internal class KeyManagerImpl( internal class KeyManagerImpl(

View file

@ -8,12 +8,10 @@ package com.stevesoltys.seedvault.plugins.webdav
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.provider.Settings import android.provider.Settings
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
class WebDavFactory( class WebDavFactory(
private val context: Context, private val context: Context,
private val keyManager: KeyManager,
) { ) {
fun createAppStoragePlugin(config: WebDavConfig): StoragePlugin<WebDavConfig> { fun createAppStoragePlugin(config: WebDavConfig): StoragePlugin<WebDavConfig> {
@ -27,7 +25,6 @@ class WebDavFactory(
val androidId = val androidId =
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
return com.stevesoltys.seedvault.storage.WebDavStoragePlugin( return com.stevesoltys.seedvault.storage.WebDavStoragePlugin(
keyManager = keyManager,
androidId = androidId, androidId = androidId,
webDavConfig = config, webDavConfig = config,
) )

View file

@ -9,6 +9,6 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val storagePluginModuleWebDav = module { val storagePluginModuleWebDav = module {
single { WebDavFactory(androidContext(), get()) } single { WebDavFactory(androidContext()) }
single { WebDavHandler(androidContext(), get(), get(), get()) } single { WebDavHandler(androidContext(), get(), get(), get()) }
} }

View file

@ -11,7 +11,6 @@ import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage
import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin
import javax.crypto.SecretKey
internal class SeedvaultSafStoragePlugin( internal class SeedvaultSafStoragePlugin(
private val appContext: Context, private val appContext: Context,
@ -24,6 +23,4 @@ internal class SeedvaultSafStoragePlugin(
override val context: Context get() = appContext.getStorageContext { storage.safStorage.isUsb } override val context: Context get() = appContext.getStorageContext { storage.safStorage.isUsb }
override val root: DocumentFile get() = storage.rootBackupDir ?: error("No storage set") override val root: DocumentFile get() = storage.rootBackupDir ?: error("No storage set")
override fun getMasterKey(): SecretKey = keyManager.getMainKey()
override fun hasMasterKey(): Boolean = keyManager.hasMainKey()
} }

View file

@ -5,10 +5,11 @@
package com.stevesoltys.seedvault.storage package com.stevesoltys.seedvault.storage
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.koin.dsl.module import org.koin.dsl.module
val storageModule = module { val storageModule = module {
single { StorageBackup(get(), { get<StoragePluginManager>().filesPlugin }) } single { StorageBackup(get(), { get<StoragePluginManager>().filesPlugin }, get<KeyManager>()) }
} }

View file

@ -11,7 +11,6 @@ import at.bitfire.dav4jvm.Response.HrefRelation.SELF
import at.bitfire.dav4jvm.exception.NotFoundException import at.bitfire.dav4jvm.exception.NotFoundException
import at.bitfire.dav4jvm.property.webdav.DisplayName import at.bitfire.dav4jvm.property.webdav.DisplayName
import at.bitfire.dav4jvm.property.webdav.ResourceType import at.bitfire.dav4jvm.property.webdav.ResourceType
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.plugins.chunkFolderRegex import com.stevesoltys.seedvault.plugins.chunkFolderRegex
import com.stevesoltys.seedvault.plugins.webdav.DIRECTORY_ROOT import com.stevesoltys.seedvault.plugins.webdav.DIRECTORY_ROOT
import com.stevesoltys.seedvault.plugins.webdav.WebDavConfig import com.stevesoltys.seedvault.plugins.webdav.WebDavConfig
@ -26,12 +25,10 @@ import org.koin.core.time.measureDuration
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import javax.crypto.SecretKey
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
internal class WebDavStoragePlugin( internal class WebDavStoragePlugin(
private val keyManager: KeyManager,
/** /**
* The result of Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) * The result of Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
*/ */
@ -121,9 +118,6 @@ internal class WebDavStoragePlugin(
} }
} }
override fun getMasterKey(): SecretKey = keyManager.getMainKey()
override fun hasMasterKey(): Boolean = keyManager.hasMainKey()
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getChunkOutputStream(chunkId: String): OutputStream { override suspend fun getChunkOutputStream(chunkId: String): OutputStream {
val chunkFolderName = chunkId.substring(0, 2) val chunkFolderName = chunkId.substring(0, 2)

View file

@ -5,12 +5,10 @@
package com.stevesoltys.seedvault.storage package com.stevesoltys.seedvault.storage
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.getRandomByteArray import com.stevesoltys.seedvault.getRandomByteArray
import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.plugins.webdav.WebDavTestConfig import com.stevesoltys.seedvault.plugins.webdav.WebDavTestConfig
import com.stevesoltys.seedvault.transport.backup.BackupTest import com.stevesoltys.seedvault.transport.backup.BackupTest
import io.mockk.mockk
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.backup.storage.api.StoredSnapshot import org.calyxos.backup.storage.api.StoredSnapshot
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
@ -21,8 +19,7 @@ import java.io.IOException
internal class WebDavStoragePluginTest : BackupTest() { internal class WebDavStoragePluginTest : BackupTest() {
private val keyManager: KeyManager = mockk() private val plugin = WebDavStoragePlugin("foo", WebDavTestConfig.getConfig())
private val plugin = WebDavStoragePlugin(keyManager, "foo", WebDavTestConfig.getConfig())
private val snapshot = StoredSnapshot("foo.sv", System.currentTimeMillis()) private val snapshot = StoredSnapshot("foo.sv", System.currentTimeMillis())
@ -85,7 +82,7 @@ internal class WebDavStoragePluginTest : BackupTest() {
) )
// other device writes another snapshot // other device writes another snapshot
val otherPlugin = WebDavStoragePlugin(keyManager, "bar", WebDavTestConfig.getConfig()) val otherPlugin = WebDavStoragePlugin("bar", WebDavTestConfig.getConfig())
val otherSnapshot = StoredSnapshot("bar.sv", System.currentTimeMillis()) val otherSnapshot = StoredSnapshot("bar.sv", System.currentTimeMillis())
val otherSnapshotBytes = getRandomByteArray() val otherSnapshotBytes = getRandomByteArray()
assertEquals(emptyList<String>(), otherPlugin.getAvailableChunkIds()) assertEquals(emptyList<String>(), otherPlugin.getAvailableChunkIds())
@ -110,7 +107,6 @@ internal class WebDavStoragePluginTest : BackupTest() {
@Test @Test
fun `test missing root dir`() = runBlocking { fun `test missing root dir`() = runBlocking {
val plugin = WebDavStoragePlugin( val plugin = WebDavStoragePlugin(
keyManager = keyManager,
androidId = "foo", androidId = "foo",
webDavConfig = WebDavTestConfig.getConfig(), webDavConfig = WebDavTestConfig.getConfig(),
root = getRandomString(), root = getRandomString(),

View file

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package org.calyxos.seedvault.core.crypto
import java.security.KeyStore
import javax.crypto.SecretKey
public interface KeyManager {
/**
* Returns the main key, so it can be used for deriving sub-keys.
*
* Note that any attempt to export the key will return null or an empty [ByteArray],
* because the key can not leave the [KeyStore]'s hardware security module.
*/
public fun getMainKey(): SecretKey
}

View file

@ -69,6 +69,7 @@ android {
} }
dependencies { dependencies {
implementation(project(":core"))
implementation(project(":storage:lib")) implementation(project(":storage:lib"))
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)

View file

@ -9,6 +9,7 @@ import android.app.Application
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.VmPolicy import android.os.StrictMode.VmPolicy
import android.util.Log import android.util.Log
import de.grobox.storagebackuptester.crypto.KeyManager
import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin
import de.grobox.storagebackuptester.settings.SettingsManager import de.grobox.storagebackuptester.settings.SettingsManager
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
@ -19,7 +20,7 @@ class App : Application() {
val settingsManager: SettingsManager by lazy { SettingsManager(applicationContext) } val settingsManager: SettingsManager by lazy { SettingsManager(applicationContext) }
val storageBackup: StorageBackup by lazy { val storageBackup: StorageBackup by lazy {
val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() } val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() }
StorageBackup(this, { plugin }) StorageBackup(this, { plugin }, KeyManager)
} }
val fileSelectionManager: FileSelectionManager get() = FileSelectionManager() val fileSelectionManager: FileSelectionManager get() = FileSelectionManager()

View file

@ -24,7 +24,7 @@ class MainActivity : AppCompatActivity() {
KeyManager.storeMasterKey() KeyManager.storeMasterKey()
if (!KeyManager.hasMasterKey()) { if (!KeyManager.hasMainKey()) {
Log.e("TEST", "storing new key") Log.e("TEST", "storing new key")
KeyManager.storeMasterKey() KeyManager.storeMasterKey()
} else { } else {

View file

@ -14,7 +14,7 @@ import java.security.KeyStore
import javax.crypto.SecretKey import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
object KeyManager { object KeyManager: org.calyxos.seedvault.core.crypto.KeyManager {
private const val KEY_SIZE = 256 private const val KEY_SIZE = 256
internal const val KEY_SIZE_BYTES = KEY_SIZE / 8 internal const val KEY_SIZE_BYTES = KEY_SIZE / 8
@ -42,9 +42,9 @@ object KeyManager {
keyStore.setEntry(KEY_ALIAS_MASTER, ksEntry, getKeyProtection()) keyStore.setEntry(KEY_ALIAS_MASTER, ksEntry, getKeyProtection())
} }
fun hasMasterKey(): Boolean = keyStore.containsAlias(KEY_ALIAS_MASTER) fun hasMainKey(): Boolean = keyStore.containsAlias(KEY_ALIAS_MASTER)
fun getMasterKey(): SecretKey { override fun getMainKey(): SecretKey {
val ksEntry = keyStore.getEntry(KEY_ALIAS_MASTER, null) as KeyStore.SecretKeyEntry val ksEntry = keyStore.getEntry(KEY_ALIAS_MASTER, null) as KeyStore.SecretKeyEntry
return ksEntry.secretKey return ksEntry.secretKey
} }

View file

@ -8,13 +8,10 @@ package de.grobox.storagebackuptester.plugin
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import de.grobox.storagebackuptester.crypto.KeyManager
import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import javax.crypto.SecretKey
@Suppress("BlockingMethodInNonBlockingContext")
class TestSafStoragePlugin( class TestSafStoragePlugin(
appContext: Context, appContext: Context,
private val getLocationUri: () -> Uri?, private val getLocationUri: () -> Uri?,
@ -33,14 +30,6 @@ class TestSafStoragePlugin(
} }
} }
override fun getMasterKey(): SecretKey {
return KeyManager.getMasterKey()
}
override fun hasMasterKey(): Boolean {
return KeyManager.hasMasterKey()
}
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getChunkOutputStream(chunkId: String): OutputStream { override suspend fun getChunkOutputStream(chunkId: String): OutputStream {
if (getLocationUri() == null) return nullStream if (getLocationUri() == null) return nullStream

View file

@ -81,6 +81,7 @@ android {
} }
dependencies { dependencies {
implementation(project(":core"))
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.fragment) implementation(libs.androidx.fragment)

View file

@ -31,6 +31,7 @@ import org.calyxos.backup.storage.scanner.DocumentScanner
import org.calyxos.backup.storage.scanner.FileScanner import org.calyxos.backup.storage.scanner.FileScanner
import org.calyxos.backup.storage.scanner.MediaScanner import org.calyxos.backup.storage.scanner.MediaScanner
import org.calyxos.backup.storage.toStoredUri import org.calyxos.backup.storage.toStoredUri
import org.calyxos.seedvault.core.crypto.KeyManager
import java.io.IOException import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -39,6 +40,7 @@ private const val TAG = "StorageBackup"
public class StorageBackup( public class StorageBackup(
private val context: Context, private val context: Context,
private val pluginGetter: () -> StoragePlugin, private val pluginGetter: () -> StoragePlugin,
private val keyManager: KeyManager,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO, private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) { ) {
@ -54,13 +56,16 @@ public class StorageBackup(
private val backup by lazy { private val backup by lazy {
val documentScanner = DocumentScanner(context) val documentScanner = DocumentScanner(context)
val fileScanner = FileScanner(uriStore, mediaScanner, documentScanner) val fileScanner = FileScanner(uriStore, mediaScanner, documentScanner)
Backup(context, db, fileScanner, pluginGetter, chunksCacheRepopulater) Backup(context, db, fileScanner, pluginGetter, keyManager, chunksCacheRepopulater)
} }
private val restore by lazy { private val restore by lazy {
Restore(context, pluginGetter, snapshotRetriever, FileRestore(context, mediaScanner)) val fileRestore = FileRestore(context, mediaScanner)
Restore(context, pluginGetter, keyManager, snapshotRetriever, fileRestore)
} }
private val retention = RetentionManager(context) private val retention = RetentionManager(context)
private val pruner by lazy { Pruner(db, retention, pluginGetter, snapshotRetriever) } private val pruner by lazy {
Pruner(db, retention, pluginGetter, keyManager, snapshotRetriever)
}
private val backupRunning = AtomicBoolean(false) private val backupRunning = AtomicBoolean(false)
private val restoreRunning = AtomicBoolean(false) private val restoreRunning = AtomicBoolean(false)

View file

@ -8,8 +8,6 @@ package org.calyxos.backup.storage.api
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.security.KeyStore
import javax.crypto.SecretKey
public interface StoragePlugin { public interface StoragePlugin {
@ -28,16 +26,6 @@ public interface StoragePlugin {
@Throws(IOException::class) @Throws(IOException::class)
public suspend fun getAvailableChunkIds(): List<String> public suspend fun getAvailableChunkIds(): List<String>
/**
* Returns a [SecretKey] for HmacSHA256, ideally stored in the [KeyStore].
*/
public fun getMasterKey(): SecretKey
/**
* Returns true if the key for [getMasterKey] exists, false otherwise.
*/
public fun hasMasterKey(): Boolean
@Throws(IOException::class) @Throws(IOException::class)
public suspend fun getChunkOutputStream(chunkId: String): OutputStream public suspend fun getChunkOutputStream(chunkId: String): OutputStream
@ -48,8 +36,7 @@ public interface StoragePlugin {
/** /**
* Returns *all* [StoredSnapshot]s that are available on storage * Returns *all* [StoredSnapshot]s that are available on storage
* independent of user ID and whether they can be decrypted * independent of user ID and whether they can be decrypted with the main key.
* with the key returned by [getMasterKey].
*/ */
@Throws(IOException::class) @Throws(IOException::class)
public suspend fun getBackupSnapshotsForRestore(): List<StoredSnapshot> public suspend fun getBackupSnapshotsForRestore(): List<StoredSnapshot>

View file

@ -19,6 +19,7 @@ import org.calyxos.backup.storage.db.Db
import org.calyxos.backup.storage.measure import org.calyxos.backup.storage.measure
import org.calyxos.backup.storage.scanner.FileScanner import org.calyxos.backup.storage.scanner.FileScanner
import org.calyxos.backup.storage.scanner.FileScannerResult import org.calyxos.backup.storage.scanner.FileScannerResult
import org.calyxos.seedvault.core.crypto.KeyManager
import java.io.IOException import java.io.IOException
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import kotlin.time.Duration import kotlin.time.Duration
@ -42,6 +43,7 @@ internal class Backup(
private val db: Db, private val db: Db,
private val fileScanner: FileScanner, private val fileScanner: FileScanner,
private val storagePluginGetter: () -> StoragePlugin, private val storagePluginGetter: () -> StoragePlugin,
keyManager: KeyManager,
private val cacheRepopulater: ChunksCacheRepopulater, private val cacheRepopulater: ChunksCacheRepopulater,
chunkSizeMax: Int = CHUNK_SIZE_MAX, chunkSizeMax: Int = CHUNK_SIZE_MAX,
private val streamCrypto: StreamCrypto = StreamCrypto, private val streamCrypto: StreamCrypto = StreamCrypto,
@ -60,12 +62,12 @@ internal class Backup(
private val chunksCache = db.getChunksCache() private val chunksCache = db.getChunksCache()
private val mac = try { private val mac = try {
ChunkCrypto.getMac(ChunkCrypto.deriveChunkIdKey(storagePlugin.getMasterKey())) ChunkCrypto.getMac(ChunkCrypto.deriveChunkIdKey(keyManager.getMainKey()))
} catch (e: GeneralSecurityException) { } catch (e: GeneralSecurityException) {
throw AssertionError(e) throw AssertionError(e)
} }
private val streamKey = try { private val streamKey = try {
streamCrypto.deriveStreamKey(storagePlugin.getMasterKey()) streamCrypto.deriveStreamKey(keyManager.getMainKey())
} catch (e: GeneralSecurityException) { } catch (e: GeneralSecurityException) {
throw AssertionError(e) throw AssertionError(e)
} }

View file

@ -13,6 +13,7 @@ import org.calyxos.backup.storage.crypto.StreamCrypto
import org.calyxos.backup.storage.db.Db import org.calyxos.backup.storage.db.Db
import org.calyxos.backup.storage.measure import org.calyxos.backup.storage.measure
import org.calyxos.backup.storage.plugin.SnapshotRetriever import org.calyxos.backup.storage.plugin.SnapshotRetriever
import org.calyxos.seedvault.core.crypto.KeyManager
import java.io.IOException import java.io.IOException
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ -23,6 +24,7 @@ internal class Pruner(
private val db: Db, private val db: Db,
private val retentionManager: RetentionManager, private val retentionManager: RetentionManager,
private val storagePluginGetter: () -> StoragePlugin, private val storagePluginGetter: () -> StoragePlugin,
keyManager: KeyManager,
private val snapshotRetriever: SnapshotRetriever, private val snapshotRetriever: SnapshotRetriever,
streamCrypto: StreamCrypto = StreamCrypto, streamCrypto: StreamCrypto = StreamCrypto,
) { ) {
@ -30,7 +32,7 @@ internal class Pruner(
private val storagePlugin get() = storagePluginGetter() private val storagePlugin get() = storagePluginGetter()
private val chunksCache = db.getChunksCache() private val chunksCache = db.getChunksCache()
private val streamKey = try { private val streamKey = try {
streamCrypto.deriveStreamKey(storagePlugin.getMasterKey()) streamCrypto.deriveStreamKey(keyManager.getMainKey())
} catch (e: GeneralSecurityException) { } catch (e: GeneralSecurityException) {
throw AssertionError(e) throw AssertionError(e)
} }

View file

@ -19,6 +19,7 @@ import org.calyxos.backup.storage.backup.BackupSnapshot
import org.calyxos.backup.storage.crypto.StreamCrypto import org.calyxos.backup.storage.crypto.StreamCrypto
import org.calyxos.backup.storage.measure import org.calyxos.backup.storage.measure
import org.calyxos.backup.storage.plugin.SnapshotRetriever import org.calyxos.backup.storage.plugin.SnapshotRetriever
import org.calyxos.seedvault.core.crypto.KeyManager
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
@ -28,6 +29,7 @@ private const val TAG = "Restore"
internal class Restore( internal class Restore(
context: Context, context: Context,
private val storagePluginGetter: () -> StoragePlugin, private val storagePluginGetter: () -> StoragePlugin,
private val keyManager: KeyManager,
private val snapshotRetriever: SnapshotRetriever, private val snapshotRetriever: SnapshotRetriever,
fileRestore: FileRestore, fileRestore: FileRestore,
streamCrypto: StreamCrypto = StreamCrypto, streamCrypto: StreamCrypto = StreamCrypto,
@ -39,7 +41,7 @@ internal class Restore(
// so we need to get it lazily here to prevent crashes. We can still crash later, // so we need to get it lazily here to prevent crashes. We can still crash later,
// if the plugin is not providing a key as it should when performing calls into this class. // if the plugin is not providing a key as it should when performing calls into this class.
try { try {
streamCrypto.deriveStreamKey(storagePlugin.getMasterKey()) streamCrypto.deriveStreamKey(keyManager.getMainKey())
} catch (e: GeneralSecurityException) { } catch (e: GeneralSecurityException) {
throw AssertionError(e) throw AssertionError(e)
} }

View file

@ -46,6 +46,7 @@ import org.calyxos.backup.storage.restore.RestorableFile
import org.calyxos.backup.storage.restore.Restore import org.calyxos.backup.storage.restore.Restore
import org.calyxos.backup.storage.scanner.FileScanner import org.calyxos.backup.storage.scanner.FileScanner
import org.calyxos.backup.storage.scanner.FileScannerResult import org.calyxos.backup.storage.scanner.FileScannerResult
import org.calyxos.seedvault.core.crypto.KeyManager
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -71,6 +72,7 @@ internal class BackupRestoreTest {
private val fileScanner: FileScanner = mockk() private val fileScanner: FileScanner = mockk()
private val pluginGetter: () -> StoragePlugin = mockk() private val pluginGetter: () -> StoragePlugin = mockk()
private val keyManager: KeyManager = mockk()
private val plugin: StoragePlugin = mockk() private val plugin: StoragePlugin = mockk()
private val fileRestore: FileRestore = mockk() private val fileRestore: FileRestore = mockk()
private val snapshotRetriever = SnapshotRetriever(pluginGetter) private val snapshotRetriever = SnapshotRetriever(pluginGetter)
@ -87,7 +89,7 @@ internal class BackupRestoreTest {
every { pluginGetter() } returns plugin every { pluginGetter() } returns plugin
every { db.getFilesCache() } returns filesCache every { db.getFilesCache() } returns filesCache
every { db.getChunksCache() } returns chunksCache every { db.getChunksCache() } returns chunksCache
every { plugin.getMasterKey() } returns SecretKeySpec( every { keyManager.getMainKey() } returns SecretKeySpec(
"This is a backup key for testing".toByteArray(), "This is a backup key for testing".toByteArray(),
0, KEY_SIZE_BYTES, ALGORITHM_HMAC 0, KEY_SIZE_BYTES, ALGORITHM_HMAC
) )
@ -95,11 +97,11 @@ internal class BackupRestoreTest {
every { context.contentResolver } returns contentResolver every { context.contentResolver } returns contentResolver
} }
private val restore = Restore(context, pluginGetter, snapshotRetriever, fileRestore) private val restore = Restore(context, pluginGetter, keyManager, snapshotRetriever, fileRestore)
@Test @Test
fun testZipAndSingleRandom(): Unit = runBlocking { fun testZipAndSingleRandom(): Unit = runBlocking {
val backup = Backup(context, db, fileScanner, pluginGetter, cacheRepopulater) val backup = Backup(context, db, fileScanner, pluginGetter, keyManager, cacheRepopulater)
val smallFileMBytes = Random.nextBytes(Random.nextInt(SMALL_FILE_SIZE_MAX)) val smallFileMBytes = Random.nextBytes(Random.nextInt(SMALL_FILE_SIZE_MAX))
val smallFileM = getRandomMediaFile(smallFileMBytes.size) val smallFileM = getRandomMediaFile(smallFileMBytes.size)
@ -236,7 +238,8 @@ internal class BackupRestoreTest {
@Test @Test
fun testMultiChunks(): Unit = runBlocking { fun testMultiChunks(): Unit = runBlocking {
val backup = Backup(context, db, fileScanner, pluginGetter, cacheRepopulater, 4) val backup =
Backup(context, db, fileScanner, pluginGetter, keyManager, cacheRepopulater, 4)
val chunk1 = byteArrayOf(0x00, 0x01, 0x02, 0x03) val chunk1 = byteArrayOf(0x00, 0x01, 0x02, 0x03)
val chunk2 = byteArrayOf(0x04, 0x05, 0x06, 0x07) val chunk2 = byteArrayOf(0x04, 0x05, 0x06, 0x07)

View file

@ -26,6 +26,7 @@ import org.calyxos.backup.storage.db.Db
import org.calyxos.backup.storage.getRandomString import org.calyxos.backup.storage.getRandomString
import org.calyxos.backup.storage.mockLog import org.calyxos.backup.storage.mockLog
import org.calyxos.backup.storage.plugin.SnapshotRetriever import org.calyxos.backup.storage.plugin.SnapshotRetriever
import org.calyxos.seedvault.core.crypto.KeyManager
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
@ -37,6 +38,7 @@ internal class PrunerTest {
private val db: Db = mockk() private val db: Db = mockk()
private val chunksCache: ChunksCache = mockk() private val chunksCache: ChunksCache = mockk()
private val pluginGetter: () -> StoragePlugin = mockk() private val pluginGetter: () -> StoragePlugin = mockk()
private val keyManager: KeyManager = mockk()
private val plugin: StoragePlugin = mockk() private val plugin: StoragePlugin = mockk()
private val snapshotRetriever: SnapshotRetriever = mockk() private val snapshotRetriever: SnapshotRetriever = mockk()
private val retentionManager: RetentionManager = mockk() private val retentionManager: RetentionManager = mockk()
@ -48,11 +50,12 @@ internal class PrunerTest {
mockLog(false) mockLog(false)
every { pluginGetter() } returns plugin every { pluginGetter() } returns plugin
every { db.getChunksCache() } returns chunksCache every { db.getChunksCache() } returns chunksCache
every { plugin.getMasterKey() } returns masterKey every { keyManager.getMainKey() } returns masterKey
every { streamCrypto.deriveStreamKey(masterKey) } returns streamKey every { streamCrypto.deriveStreamKey(masterKey) } returns streamKey
} }
private val pruner = Pruner(db, retentionManager, pluginGetter, snapshotRetriever, streamCrypto) private val pruner =
Pruner(db, retentionManager, pluginGetter, keyManager, snapshotRetriever, streamCrypto)
@Test @Test
fun test() = runBlocking { fun test() = runBlocking {