From 89a20eafd8ca0ca7aed987dc45c5850ff0269188 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Jun 2020 15:16:41 +0200 Subject: [PATCH] CryptoStore migration: step3: copy DB and encrypt DB --- .../android/internal/crypto/CryptoModule.kt | 4 +- .../internal/database/RealmKeysUtils.kt | 9 +- .../legacy/DefaultLegacySessionImporter.kt | 106 +++++++++++++++--- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 7a0580e727..44092f4ae4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -121,10 +121,8 @@ internal abstract class CryptoModule { .apply { realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5)) } - // Add `_x` because of the name clash with legacy Riot - .name("crypto_store_x.realm") + .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) - // TODO Cleanup the migration .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .migration(realmCryptoStoreMigration) .build() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmKeysUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmKeysUtils.kt index 71973b1193..5dac5d9f86 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmKeysUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmKeysUtils.kt @@ -86,6 +86,13 @@ internal class RealmKeysUtils @Inject constructor(context: Context, } fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) { + val key = getRealmEncryptionKey(alias) + + realmConfigurationBuilder.encryptionKey(key) + } + + // Expose to handle Realm migration to riotX + fun getRealmEncryptionKey(alias: String) : ByteArray { val key = if (hasKeyForDatabase(alias)) { Timber.i("Found key for alias:$alias") extractKeyForDatabase(alias) @@ -99,7 +106,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context, Timber.w("Database key for alias `$alias`: $log") } - realmConfigurationBuilder.encryptionKey(key) + return key } // Delete elements related to the alias diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/DefaultLegacySessionImporter.kt index 050278a392..96b72546c6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/DefaultLegacySessionImporter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/legacy/DefaultLegacySessionImporter.kt @@ -22,20 +22,29 @@ import im.vector.matrix.android.api.auth.data.DiscoveryInformation import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.WellKnownBaseConfig +import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.legacy.LegacySessionImporter import im.vector.matrix.android.internal.auth.SessionParamsStore +import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration +import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule +import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.legacy.riot.LoginStorage import im.vector.matrix.android.internal.network.ssl.Fingerprint -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import im.vector.matrix.android.internal.util.md5 +import io.realm.Realm +import io.realm.RealmConfiguration +import kotlinx.coroutines.runBlocking import timber.log.Timber +import java.io.File import javax.inject.Inject import im.vector.matrix.android.internal.legacy.riot.Fingerprint as LegacyFingerprint import im.vector.matrix.android.internal.legacy.riot.HomeServerConnectionConfig as LegacyHomeServerConnectionConfig internal class DefaultLegacySessionImporter @Inject constructor( - context: Context, - private val sessionParamsStore: SessionParamsStore + private val context: Context, + private val sessionParamsStore: SessionParamsStore, + private val realmCryptoStoreMigration: RealmCryptoStoreMigration, + private val realmKeysUtils: RealmKeysUtils ) : LegacySessionImporter { private val loginStorage = LoginStorage(context) @@ -49,17 +58,30 @@ internal class DefaultLegacySessionImporter @Inject constructor( val legacyConfig = list.firstOrNull() ?: return - GlobalScope.launch { + runBlocking { Timber.d("Migration: importing a session") - importCredentials(legacyConfig) + try { + importCredentials(legacyConfig) + } catch (t: Throwable) { + // It can happen in case of partial migration. To test, do not return + Timber.e(t, "Error importing credential") + } Timber.d("Migration: importing crypto DB") - importCryptoDb(legacyConfig) + try { + importCryptoDb(legacyConfig) + } catch (t: Throwable) { + // It can happen in case of partial migration. To test, do not return + Timber.e(t, "Error importing crypto DB") + } - Timber.d("Migration: clear legacy session") - - // Delete to avoid doing this several times - loginStorage.clear() + Timber.d("Migration: clear file system") + try { + clearFileSystem(legacyConfig) + } catch (t: Throwable) { + // It can happen in case of partial migration. To test, do not return + Timber.e(t, "Error clearing filesystem") + } } } @@ -114,7 +136,65 @@ internal class DefaultLegacySessionImporter @Inject constructor( sessionParamsStore.save(sessionParams) } - private suspend fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) { - TODO("Not yet implemented") + private fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) { + // Here we migrate the DB, we copy the crypto DB to the location specific to RiotX, and we encrypt it. + val userMd5 = legacyConfig.credentials.userId.md5() + + val sessionId = legacyConfig.credentials.let { (if (it.deviceId.isNullOrBlank()) it.userId else "${it.userId}|${it.deviceId}").md5() } + val newLocation = File(context.filesDir, sessionId) + + val keyAlias = "crypto_module_$userMd5" + + // Ensure newLocation does not exist (can happen in case of partial migration) + newLocation.deleteRecursively() + newLocation.mkdirs() + + // TODO Check if file exists first? + Timber.d("Migration: create legacy realm configuration") + + val realmConfiguration = RealmConfiguration.Builder() + .directory(File(context.filesDir, userMd5)) + .name("crypto_store.realm") + .modules(RealmCryptoStoreModule()) + .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) + .migration(realmCryptoStoreMigration) + // .initialData(CryptoFileStoreImporter(enableFileEncryption, context, credentials)) + .build() + + Timber.d("Migration: copy DB to encrypted DB") + Realm.getInstance(realmConfiguration).use { + // Move the DB to the new location, handled by RiotX + it.writeEncryptedCopyTo(File(newLocation, realmConfiguration.realmFileName), realmKeysUtils.getRealmEncryptionKey(keyAlias)) + } + } + + // Delete all the files created by Riot Android which will not be used anymore by RiotX + private fun clearFileSystem(legacyConfig: LegacyHomeServerConnectionConfig) { + val cryptoFolder = legacyConfig.credentials.userId.md5() + + val sharedPrefFolder = File(context.filesDir, "shared_prefs") + + listOf( + // Where session store was saved (we do not care about migrating that, an initial sync will be performed) + File(context.filesDir, "MXFileStore"), + // Previous (and very old) file crypto store + File(context.filesDir, "MXFileCryptoStore"), + // Draft. They will be lost, this is sad TODO handle them? + File(context.filesDir, "MXLatestMessagesStore"), + // Media storage + File(context.filesDir, "MXMediaStore"), + File(context.filesDir, "MXMediaStore2"), + File(context.filesDir, "MXMediaStore3"), + // Ext folder + File(context.filesDir, "ext_share"), + // Crypto store + File(context.filesDir, cryptoFolder), + // Shared Pref. Note that we do not delete the default preferences, as it should be nearly the same (TODO check that) + File(sharedPrefFolder, "Vector.LoginStorage.xml"), + File(sharedPrefFolder, "GcmRegistrationManager"), + File(sharedPrefFolder, "IntegrationManager.Storage") + ).forEach { file -> + tryThis { file.deleteRecursively() } + } } }