From 1e00353f297e50d41b81ab0bec3d7bb0ab844ed4 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 27 May 2026 17:25:51 +0200 Subject: [PATCH] Avoid SQLCipher key derivation (#6774) * Avoid SQLCipher key derivation by manually providing the generated as a raw key: this needs a series of DB re-key operations, performed as part of a 'dummy' DB migration * Set key generation length to 32 bytes in `RandomSecretPassphraseProvider`. * Use `ClientSecret` for handling the keys for SQLCipher. Move `ClientSecret` to `:libraries:androidutils` so it's shared. --- .../androidutils/crypto}/ClientSecret.kt | 16 ++++--- .../cachestore/impl/di/CacheStoreModule.kt | 19 ++++++-- .../impl/src/main/sqldelight/migrations/1.sqm | 8 ++++ .../impl/src/main/sqldelight/migrations/2.sqm | 2 + .../encrypteddb/SqlCipherDriverFactory.kt | 40 +++++++++++++---- ...eProvider.kt => DatabaseSecretProvider.kt} | 13 ++++-- ...der.kt => RandomDatabaseSecretProvider.kt} | 26 ++++++----- .../encrypteddb/utils/ReplaceDatabaseKey.kt | 45 +++++++++++++++++++ .../matrix/impl/RustMatrixClientFactory.kt | 1 + .../auth/RustMatrixAuthenticationService.kt | 2 +- .../impl/keys/DefaultSecretGenerator.kt | 2 +- .../matrix/impl/keys/SecretGenerator.kt | 2 +- .../matrix/impl/storage/SqliteStoreBuilder.kt | 2 +- .../matrix/impl/auth/FakeSecretGenerator.kt | 2 +- .../impl/keys/DefaultSecretGeneratorTest.kt | 2 +- .../impl/storage/FakeSqliteStoreBuilder.kt | 2 +- .../push/impl/history/di/PushHistoryModule.kt | 19 ++++++-- .../impl/src/main/sqldelight/migrations/2.sqm | 2 + .../impl/di/SessionStorageModule.kt | 19 ++++++-- .../src/main/sqldelight/migrations/11.sqm | 2 + .../DefaultAnalyticsColdStartWatcher.kt | 2 +- 21 files changed, 185 insertions(+), 43 deletions(-) rename libraries/{matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl => androidutils/src/main/kotlin/io/element/android/libraries/androidutils/crypto}/ClientSecret.kt (81%) create mode 100644 libraries/cachestore/impl/src/main/sqldelight/migrations/1.sqm create mode 100644 libraries/cachestore/impl/src/main/sqldelight/migrations/2.sqm rename libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/{PassphraseProvider.kt => DatabaseSecretProvider.kt} (51%) rename libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/{RandomSecretPassphraseProvider.kt => RandomDatabaseSecretProvider.kt} (56%) create mode 100644 libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/utils/ReplaceDatabaseKey.kt create mode 100644 libraries/push/impl/src/main/sqldelight/migrations/2.sqm create mode 100644 libraries/session-storage/impl/src/main/sqldelight/migrations/11.sqm diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/crypto/ClientSecret.kt similarity index 81% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/crypto/ClientSecret.kt index 25d040e15b..d07d302f39 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientSecret.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/crypto/ClientSecret.kt @@ -5,9 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.matrix.impl - -import okio.ByteString.Companion.decodeHex +package io.element.android.libraries.androidutils.crypto /** * Represents a client secret used to encrypt/decrypt data from databases, which can be either a passphrase or a raw key. @@ -17,7 +15,7 @@ sealed interface ClientSecret { * A passphrase that can be used to derive a key for encryption/decryption. */ data class Passphrase(val value: String) : ClientSecret { - override fun formattedAsString(): String = toString() + override fun formattedAsString(): String = value } /** @@ -57,10 +55,18 @@ sealed interface ClientSecret { val regex = Regex("^x'([0-9a-fA-F]+)'$") val rawKeyMatch = regex.matchEntire(secret) return if (rawKeyMatch != null) { - RawKey(rawKeyMatch.groupValues[1].decodeHex().toByteArray()) + RawKey(rawKeyMatch.groupValues[1].hexToByteArray()) } else { Passphrase(secret) } } + + /** + * Create a [ClientSecret] from raw bytes, which will be treated as a raw key. + */ + fun fromRawBytes(bytes: ByteArray): ClientSecret = when (bytes.size) { + 32 -> RawKey(bytes) + else -> Passphrase(bytes.toHexString()) + } } } diff --git a/libraries/cachestore/impl/src/main/kotlin/io/element/android/libraries/cachestore/impl/di/CacheStoreModule.kt b/libraries/cachestore/impl/src/main/kotlin/io/element/android/libraries/cachestore/impl/di/CacheStoreModule.kt index 05fa3d9d97..89de82a08f 100644 --- a/libraries/cachestore/impl/src/main/kotlin/io/element/android/libraries/cachestore/impl/di/CacheStoreModule.kt +++ b/libraries/cachestore/impl/src/main/kotlin/io/element/android/libraries/cachestore/impl/di/CacheStoreModule.kt @@ -16,7 +16,9 @@ import dev.zacsweers.metro.SingleIn import io.element.android.libraries.cachestore.impl.CacheDatabase import io.element.android.libraries.di.annotations.ApplicationContext import io.element.encrypteddb.SqlCipherDriverFactory -import io.element.encrypteddb.passphrase.RandomSecretPassphraseProvider +import io.element.encrypteddb.passphrase.RandomDatabaseSecretProvider +import io.element.encrypteddb.utils.ReplaceDatabaseKey +import timber.log.Timber @BindingContainer @ContributesTo(AppScope::class) @@ -35,9 +37,20 @@ object CacheStoreModule { parentDir.mkdirs() } - val passphraseProvider = RandomSecretPassphraseProvider(context, secretFile) + val rekeyMigrationVersion = 2L + val passphraseProvider = RandomDatabaseSecretProvider(context, secretFile) val driver = SqlCipherDriverFactory(passphraseProvider) - .create(CacheDatabase.Schema, "$name.db", context) + .create( + schema = CacheDatabase.Schema, + name = "$name.db", + context = context + ) { db, oldVersion, newVersion -> + Timber.d("Migrating $name database from version $oldVersion to $newVersion") + if (rekeyMigrationVersion in oldVersion..newVersion) { + ReplaceDatabaseKey(passphraseProvider).replaceKey(name, db) + } + } + return CacheDatabase(driver) } } diff --git a/libraries/cachestore/impl/src/main/sqldelight/migrations/1.sqm b/libraries/cachestore/impl/src/main/sqldelight/migrations/1.sqm new file mode 100644 index 0000000000..f038b85649 --- /dev/null +++ b/libraries/cachestore/impl/src/main/sqldelight/migrations/1.sqm @@ -0,0 +1,8 @@ +-- Migrate DB from version 1 +-- Initial schema for CacheData table + +CREATE TABLE IF NOT EXISTS CacheData ( + key TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL, + updatedAt INTEGER NOT NULL +); diff --git a/libraries/cachestore/impl/src/main/sqldelight/migrations/2.sqm b/libraries/cachestore/impl/src/main/sqldelight/migrations/2.sqm new file mode 100644 index 0000000000..e7adfb091a --- /dev/null +++ b/libraries/cachestore/impl/src/main/sqldelight/migrations/2.sqm @@ -0,0 +1,2 @@ +-- Migrate DB from version 2 +-- Dummy migration to trigger a re-key diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt index e4ac1a949f..84ab2d2a18 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt @@ -9,30 +9,54 @@ package io.element.encrypteddb import android.content.Context +import androidx.sqlite.db.SupportSQLiteDatabase import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlSchema import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import io.element.encrypteddb.passphrase.PassphraseProvider +import io.element.android.libraries.androidutils.crypto.ClientSecret +import io.element.encrypteddb.passphrase.DatabaseSecretProvider import net.zetetic.database.sqlcipher.SupportOpenHelperFactory /** * Creates an encrypted version of the [SqlDriver] using SQLCipher's [SupportOpenHelperFactory]. - * @param passphraseProvider Provides the passphrase needed to use the SQLite database with SQLCipher. + * @param databaseSecretProvider Provides the passphrase needed to use the SQLite database with SQLCipher. */ class SqlCipherDriverFactory( - private val passphraseProvider: PassphraseProvider, + private val databaseSecretProvider: DatabaseSecretProvider, ) { + companion object { + init { + System.loadLibrary("sqlcipher") + } + } + /** * Returns a valid [SqlDriver] with SQLCipher support. * @param schema The SQLite DB schema. * @param name The name of the database to create. * @param context Android [Context], used to instantiate the driver. + * @param onUpgradeCallback Optional callback to handle database upgrades, which will be called in the [AndroidSqliteDriver.Callback.onUpgrade] method. */ - fun create(schema: SqlSchema>, name: String, context: Context): SqlDriver { - System.loadLibrary("sqlcipher") - val passphrase = passphraseProvider.getPassphrase() - val factory = SupportOpenHelperFactory(passphrase) - return AndroidSqliteDriver(schema = schema, context = context, name = name, factory = factory) + fun create( + schema: SqlSchema>, + name: String, + context: Context, + onUpgradeCallback: ((driver: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) -> Unit)? = null, + ): SqlDriver { + val key = when (val secret = databaseSecretProvider.getSecret()) { + // For a raw key, we need the blob representation (`x'...'`) as bytes + is ClientSecret.RawKey -> secret.formattedAsString().toByteArray() + // For a passphrase, we need the bytes for the hex string representation + is ClientSecret.Passphrase -> secret.formattedAsString().hexToByteArray() + } + val factory = SupportOpenHelperFactory(key) + return AndroidSqliteDriver(schema = schema, context = context, name = name, factory = factory, callback = object : AndroidSqliteDriver.Callback( + schema + ) { + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + onUpgradeCallback?.invoke(db, oldVersion, newVersion) + } + }) } } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/PassphraseProvider.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/DatabaseSecretProvider.kt similarity index 51% rename from libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/PassphraseProvider.kt rename to libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/DatabaseSecretProvider.kt index a72dc73596..140a5d8de2 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/PassphraseProvider.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/DatabaseSecretProvider.kt @@ -8,12 +8,19 @@ package io.element.encrypteddb.passphrase +import io.element.android.libraries.androidutils.crypto.ClientSecret + /** * An abstraction to implement secure providers for SQLCipher passphrases. */ -interface PassphraseProvider { +interface DatabaseSecretProvider { /** - * Returns a passphrase for SQLCipher in [ByteArray] format. + * Returns a secret for SQLCipher. */ - fun getPassphrase(): ByteArray + fun getSecret(): ClientSecret + + /** + * Resets the passphrase, for example by deleting the persisted secret. Returns `true` if the reset was successful, `false` otherwise. + */ + fun reset(): Boolean } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomDatabaseSecretProvider.kt similarity index 56% rename from libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt rename to libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomDatabaseSecretProvider.kt index fe78a4a8da..246a65da07 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomDatabaseSecretProvider.kt @@ -9,35 +9,41 @@ package io.element.encrypteddb.passphrase import android.content.Context +import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.encrypteddb.crypto.EncryptedFile import java.io.File import java.security.SecureRandom /** - * Provides a secure passphrase for SQLCipher by generating a random secret and storing it into an [EncryptedFile]. + * Provides a secure secret for SQLCipher by generating a random secret and storing it into an [EncryptedFile]. * @param context Android [Context], used by [EncryptedFile] for cryptographic operations. * @param file Destination file where the key will be stored. - * @param secretSize Length of the generated secret. + * @param secretSizeBytes Length of the generated secret. */ -class RandomSecretPassphraseProvider( +class RandomDatabaseSecretProvider( private val context: Context, private val file: File, - private val secretSize: Int = 256, -) : PassphraseProvider { - override fun getPassphrase(): ByteArray { + private val secretSizeBytes: Int = 32, +) : DatabaseSecretProvider { + override fun getSecret(): ClientSecret { val encryptedFile = EncryptedFile(context, file) - return if (!file.exists()) { + val bytes = if (!file.exists()) { val secret = generateSecret() encryptedFile.openFileOutput().use { it.write(secret) } secret } else { encryptedFile.openFileInput().use { it.readBytes() } } + return ClientSecret.fromRawBytes(bytes) + } + + override fun reset(): Boolean { + return file.delete() } private fun generateSecret(): ByteArray { - val buffer = ByteArray(size = secretSize) - SecureRandom().nextBytes(buffer) - return buffer + // Generate a random secret of the specified size using a secure random generator. + return ByteArray(size = secretSizeBytes) + .also { SecureRandom().nextBytes(it) } } } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/utils/ReplaceDatabaseKey.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/utils/ReplaceDatabaseKey.kt new file mode 100644 index 0000000000..fafb18794b --- /dev/null +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/utils/ReplaceDatabaseKey.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.encrypteddb.utils + +import androidx.sqlite.db.SupportSQLiteDatabase +import io.element.encrypteddb.passphrase.RandomDatabaseSecretProvider +import timber.log.Timber + +/** + * A utility class to replace the encryption key of an existing SQLCipher database. + * This is used during database migrations when we want to change the encryption key. + * + * @param databaseSecretProvider The provider for generating new secrets. + */ +class ReplaceDatabaseKey( + private val databaseSecretProvider: RandomDatabaseSecretProvider +) { + fun replaceKey(name: String, database: SupportSQLiteDatabase) { + Timber.d("Re-keying database $name") + // Reset the passphrase provider to generate a new passphrase + databaseSecretProvider.reset() + + // Get the new secret and convert it to the format expected by SQLCipher + val newSecret = databaseSecretProvider.getSecret() + val key = newSecret.formattedAsString() + + // Use the PRAGMA rekey command to change the encryption key of the database + database.query("PRAGMA rekey = \"$key\";").close() + + // Verify that the database can be accessed with the new key by running a simple query + val result = database.query("select count(*) from sqlite_master").use { cursor -> + if (cursor.moveToNext()) cursor.getLong(0) else -1L + } + if (result >= 0) { + Timber.d("Re-keying database $name completed") + } else { + Timber.e("Re-keying database $name didn't work as expected.") + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 9a7452aa5f..4a91462399 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.data.megaBytes diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 812a0dd6fe..c44f7ffae7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -12,6 +12,7 @@ import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -30,7 +31,6 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync -import io.element.android.libraries.matrix.impl.ClientSecret import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt index 042dae36d9..bad4ecb169 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGenerator.kt @@ -11,7 +11,7 @@ package io.element.android.libraries.matrix.impl.keys import android.util.Base64 import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.androidutils.crypto.ClientSecret import java.security.SecureRandom @ContributesBinding(AppScope::class) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt index 4699f2a720..6dfade9b7c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/SecretGenerator.kt @@ -8,7 +8,7 @@ package io.element.android.libraries.matrix.impl.keys -import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.androidutils.crypto.ClientSecret private const val PASSPHRASE_SIZE = 256 private const val KEY_SIZE = 32 diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt index 1e5b8e037c..3fdfb7a179 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt @@ -7,9 +7,9 @@ package io.element.android.libraries.matrix.impl.storage +import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.data.megaBytes -import io.element.android.libraries.matrix.impl.ClientSecret import io.element.android.libraries.matrix.impl.paths.SessionPaths import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt index 6bd995348a..12c227946a 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeSecretGenerator.kt @@ -8,7 +8,7 @@ package io.element.android.libraries.matrix.impl.auth -import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.android.libraries.matrix.impl.keys.SecretGenerator import io.element.android.libraries.matrix.test.A_PASSPHRASE diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt index 5fa2b263a1..4bda511d42 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultSecretGeneratorTest.kt @@ -9,7 +9,7 @@ package io.element.android.libraries.matrix.impl.keys import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.androidutils.crypto.ClientSecret import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt index c33a67790b..7eeff7db33 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt @@ -7,7 +7,7 @@ package io.element.android.libraries.matrix.impl.storage -import io.element.android.libraries.matrix.impl.ClientSecret +import io.element.android.libraries.androidutils.crypto.ClientSecret import org.matrix.rustcomponents.sdk.ClientBuilder class FakeSqliteStoreBuilder : SqliteStoreBuilder { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt index 6472883d4d..93194b2738 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt @@ -17,7 +17,9 @@ import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.push.impl.PushDatabase import io.element.encrypteddb.SqlCipherDriverFactory -import io.element.encrypteddb.passphrase.RandomSecretPassphraseProvider +import io.element.encrypteddb.passphrase.RandomDatabaseSecretProvider +import io.element.encrypteddb.utils.ReplaceDatabaseKey +import timber.log.Timber @BindingContainer @ContributesTo(AppScope::class) @@ -36,9 +38,20 @@ object PushHistoryModule { parentDir.mkdirs() } - val passphraseProvider = RandomSecretPassphraseProvider(context, secretFile) + val rekeyMigrationVersion = 2L + val passphraseProvider = RandomDatabaseSecretProvider(context, secretFile) val driver = SqlCipherDriverFactory(passphraseProvider) - .create(PushDatabase.Schema, "$name.db", context) + .create( + schema = PushDatabase.Schema, + name = "$name.db", + context = context + ) { db, oldVersion, newVersion -> + Timber.d("Migrating $name database from version $oldVersion to $newVersion") + if (rekeyMigrationVersion in oldVersion..newVersion) { + ReplaceDatabaseKey(passphraseProvider).replaceKey(name, db) + } + } + return PushDatabase(driver) } } diff --git a/libraries/push/impl/src/main/sqldelight/migrations/2.sqm b/libraries/push/impl/src/main/sqldelight/migrations/2.sqm new file mode 100644 index 0000000000..e7adfb091a --- /dev/null +++ b/libraries/push/impl/src/main/sqldelight/migrations/2.sqm @@ -0,0 +1,2 @@ +-- Migrate DB from version 2 +-- Dummy migration to trigger a re-key diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt index fb2c9a2c78..8955aa4a27 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt @@ -17,7 +17,9 @@ import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.sessionstorage.impl.SessionDatabase import io.element.encrypteddb.SqlCipherDriverFactory -import io.element.encrypteddb.passphrase.RandomSecretPassphraseProvider +import io.element.encrypteddb.passphrase.RandomDatabaseSecretProvider +import io.element.encrypteddb.utils.ReplaceDatabaseKey +import timber.log.Timber @BindingContainer @ContributesTo(AppScope::class) @@ -36,9 +38,20 @@ object SessionStorageModule { parentDir.mkdirs() } - val passphraseProvider = RandomSecretPassphraseProvider(context, secretFile) + val rekeyMigrationVersion = 11L + val passphraseProvider = RandomDatabaseSecretProvider(context, secretFile) val driver = SqlCipherDriverFactory(passphraseProvider) - .create(SessionDatabase.Schema, "$name.db", context) + .create( + schema = SessionDatabase.Schema, + name = "$name.db", + context = context, + ) { db, oldVersion, newVersion -> + Timber.d("Migrating $name database from version $oldVersion to $newVersion") + if (rekeyMigrationVersion in oldVersion..newVersion) { + ReplaceDatabaseKey(passphraseProvider).replaceKey(name, db) + } + } + return SessionDatabase(driver) } } diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/11.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/11.sqm new file mode 100644 index 0000000000..3d61c92c07 --- /dev/null +++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/11.sqm @@ -0,0 +1,2 @@ +-- Migrate DB from version 11 +-- Dummy migration to trigger a re-key diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt index a9647ba3a1..8659fc9634 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt @@ -57,7 +57,7 @@ class DefaultAnalyticsColdStartWatcher( override fun onRoomListVisible() { if (isColdStart.getAndSet(false)) { analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStart) { - Timber.d("Room list is visible, finishing cold start check. Elapsed: ${it.duration}") + Timber.d("Room list is visible, finishing cold start check after ${it.duration}") } } }