CryptoStore migration: step3: copy DB and encrypt DB

This commit is contained in:
Benoit Marty 2020-06-19 15:16:41 +02:00
parent a2be821d2f
commit 89a20eafd8
3 changed files with 102 additions and 17 deletions

View file

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

View file

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

View file

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