mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge olm to Rust migration
This commit is contained in:
commit
d3ef5cc230
23 changed files with 453 additions and 67 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +1,2 @@
|
||||||
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.realm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
@ -125,25 +125,6 @@ android {
|
||||||
java.srcDirs += "src/sharedTest/java"
|
java.srcDirs += "src/sharedTest/java"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// flavorDimensions "crypto"
|
|
||||||
//
|
|
||||||
// productFlavors {
|
|
||||||
// kotlinCrypto {
|
|
||||||
// dimension "crypto"
|
|
||||||
// isDefault = true
|
|
||||||
// // versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
|
||||||
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"JC\""
|
|
||||||
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"KotlinCrypto\""
|
|
||||||
// }
|
|
||||||
// rustCrypto {
|
|
||||||
// dimension "crypto"
|
|
||||||
// // versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
|
||||||
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"RC\""
|
|
||||||
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"RustCrypto\""
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// publishNonDefault true
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:59b4957aa2f9cdc17b14ec8546e144537fac9dee050c6eb173f56fa8602c2736
|
||||||
|
size 2097152
|
Binary file not shown.
|
@ -20,6 +20,7 @@ import org.amshove.kluent.shouldBeNull
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.util.fromBase64
|
import org.matrix.android.sdk.api.util.fromBase64
|
||||||
|
import org.matrix.android.sdk.api.util.fromBase64Safe
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
class ExtensionsKtTest {
|
class ExtensionsKtTest {
|
||||||
|
|
|
@ -54,6 +54,8 @@ import java.util.concurrent.CountDownLatch
|
||||||
@Ignore
|
@Ignore
|
||||||
class SASTest : InstrumentedTest {
|
class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -614,4 +616,6 @@ class SASTest : InstrumentedTest {
|
||||||
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
|
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
}
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.store.migration
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.amshove.kluent.internal.assertEquals
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.TestRealmConfigurationFactory
|
||||||
|
import org.matrix.android.sdk.internal.session.MigrateEAtoEROperation
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import org.matrix.olm.OlmAccount
|
||||||
|
import org.matrix.olm.OlmManager
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.OlmMachine
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ElementAndroidToElementRMigrationTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
var realm: Realm? = null
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
// Ensure Olm is initialized
|
||||||
|
OlmManager()
|
||||||
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
realm?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() {
|
||||||
|
val realmName = "crypto_store_migration_16.realm"
|
||||||
|
val migration = RealmCryptoStoreMigration(object : Clock {
|
||||||
|
override fun epochMillis() = 0L
|
||||||
|
})
|
||||||
|
|
||||||
|
val realmConfiguration = configurationFactory.createConfiguration(
|
||||||
|
realmName,
|
||||||
|
null,
|
||||||
|
RealmCryptoStoreModule(),
|
||||||
|
migration.schemaVersion,
|
||||||
|
migration
|
||||||
|
)
|
||||||
|
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
|
||||||
|
|
||||||
|
realm = Realm.getInstance(realmConfiguration)
|
||||||
|
|
||||||
|
val metaData = realm!!.where<CryptoMetadataEntity>().findFirst()!!
|
||||||
|
val userId = metaData.userId!!
|
||||||
|
val deviceId = metaData.deviceId!!
|
||||||
|
val olmAccount = metaData.getOlmAccount()!!
|
||||||
|
|
||||||
|
val extractor = MigrateEAtoEROperation()
|
||||||
|
|
||||||
|
val targetFile = File(configurationFactory.root, "rust-sdk")
|
||||||
|
|
||||||
|
extractor.execute(realmConfiguration, targetFile)
|
||||||
|
|
||||||
|
val machine = OlmMachine(userId, deviceId, targetFile.path, null)
|
||||||
|
|
||||||
|
assertEquals(olmAccount.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY], machine.identityKeys()["ed25519"])
|
||||||
|
assertNotNull(machine.getBackupKeys())
|
||||||
|
val crossSigningStatus = machine.crossSigningStatus()
|
||||||
|
assertTrue(crossSigningStatus.hasMaster)
|
||||||
|
assertTrue(crossSigningStatus.hasSelfSigning)
|
||||||
|
assertTrue(crossSigningStatus.hasUserSigning)
|
||||||
|
|
||||||
|
val inboundGroupSessionEntities = realm!!.where<OlmInboundGroupSessionEntity>().findAll()
|
||||||
|
assertEquals(inboundGroupSessionEntities.size, machine.roomKeyCounts().total.toInt())
|
||||||
|
|
||||||
|
val backedUpInboundGroupSessionEntities = realm!!
|
||||||
|
.where<OlmInboundGroupSessionEntity>()
|
||||||
|
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, true)
|
||||||
|
.findAll()
|
||||||
|
assertEquals(backedUpInboundGroupSessionEntities.size, machine.roomKeyCounts().backedUp.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun given_an_empty_crypto_store_realm_file_then_migration_should_not_happen() {
|
||||||
|
// val realmConfiguration = realmConfigurationFactory.configurationForMigrationFrom15To16(populateCryptoStore = false)
|
||||||
|
// Realm.getInstance(realmConfiguration).use {
|
||||||
|
// assertTrue(it.isEmpty)
|
||||||
|
// }
|
||||||
|
// val machine = OlmMachine("@ganfra146:matrix.org", "UTDQCHKKNS", realmConfigurationFactory.root.path, null)
|
||||||
|
// assertNull(machine.getBackupKeys())
|
||||||
|
// val crossSigningStatus = machine.crossSigningStatus()
|
||||||
|
// assertFalse(crossSigningStatus.hasMaster)
|
||||||
|
// assertFalse(crossSigningStatus.hasSelfSigning)
|
||||||
|
// assertFalse(crossSigningStatus.hasUserSigning)
|
||||||
|
// }
|
||||||
|
}
|
|
@ -15,11 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.crosssigning
|
package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
|
@ -28,15 +26,3 @@ internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||||
internal fun CryptoCrossSigningKey.canonicalSignable(): String {
|
internal fun CryptoCrossSigningKey.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
|
|
||||||
*/
|
|
||||||
internal fun String.fromBase64Safe(): ByteArray? {
|
|
||||||
return try {
|
|
||||||
Base64.decode(this, Base64.DEFAULT)
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
Timber.e(throwable, "Unable to decode base64 string")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MigrateEAtoEROperation {
|
||||||
|
|
||||||
|
fun execute(cryptoRealm: RealmConfiguration, sessionFilesDir: File): File {
|
||||||
|
// no op in kotlinCrypto
|
||||||
|
return sessionFilesDir
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.util
|
package org.matrix.android.sdk.api.util
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
fun ByteArray.toBase64NoPadding(): String {
|
fun ByteArray.toBase64NoPadding(): String {
|
||||||
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
|
@ -25,3 +26,15 @@ fun ByteArray.toBase64NoPadding(): String {
|
||||||
fun String.fromBase64(): ByteArray {
|
fun String.fromBase64(): ByteArray {
|
||||||
return Base64.decode(this, Base64.DEFAULT)
|
return Base64.decode(this, Base64.DEFAULT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
|
||||||
|
*/
|
||||||
|
internal fun String.fromBase64Safe(): ByteArray? {
|
||||||
|
return try {
|
||||||
|
Base64.decode(this, Base64.DEFAULT)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable, "Unable to decode base64 string")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -146,37 +146,38 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
.setWriteAsyncExecutor(monarchyWriteAsyncExecutor)
|
.setWriteAsyncExecutor(monarchyWriteAsyncExecutor)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
init {
|
// init {
|
||||||
// Ensure CryptoMetadataEntity is inserted in DB
|
// // Ensure CryptoMetadataEntity is inserted in DB
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
// doRealmTransaction(realmConfiguration) { realm ->
|
||||||
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
// var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
||||||
|
//
|
||||||
|
// var deleteAll = false
|
||||||
|
//
|
||||||
|
// if (currentMetadata != null) {
|
||||||
|
// // Check credentials
|
||||||
|
// // The device id may not have been provided in credentials.
|
||||||
|
// // Check it only if provided, else trust the stored one.
|
||||||
|
// if (currentMetadata.userId != userId ||
|
||||||
|
// (deviceId != currentMetadata.deviceId)) {
|
||||||
|
// Timber.w("## open() : Credentials do not match, close this store and delete data")
|
||||||
|
// deleteAll = true
|
||||||
|
// currentMetadata = null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (currentMetadata == null) {
|
||||||
|
// if (deleteAll) {
|
||||||
|
// realm.deleteAll()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Metadata not found, or database cleaned, create it
|
||||||
|
// realm.createObject(CryptoMetadataEntity::class.java, userId).apply {
|
||||||
|
// deviceId = this@RealmCryptoStore.deviceId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
var deleteAll = false
|
|
||||||
|
|
||||||
if (currentMetadata != null) {
|
|
||||||
// Check credentials
|
|
||||||
// The device id may not have been provided in credentials.
|
|
||||||
// Check it only if provided, else trust the stored one.
|
|
||||||
if (currentMetadata.userId != userId ||
|
|
||||||
(deviceId != currentMetadata.deviceId)) {
|
|
||||||
Timber.w("## open() : Credentials do not match, close this store and delete data")
|
|
||||||
deleteAll = true
|
|
||||||
currentMetadata = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMetadata == null) {
|
|
||||||
if (deleteAll) {
|
|
||||||
realm.deleteAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata not found, or database cleaned, create it
|
|
||||||
realm.createObject(CryptoMetadataEntity::class.java, userId).apply {
|
|
||||||
deviceId = this@RealmCryptoStore.deviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Other data
|
* Other data
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
|
@ -33,3 +33,7 @@ internal annotation class CacheDirectory
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
internal annotation class ExternalFilesDirectory
|
internal annotation class ExternalFilesDirectory
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class SessionRustFilesDirectory
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||||
import org.matrix.android.sdk.internal.legacy.riot.LoginStorage
|
import org.matrix.android.sdk.internal.legacy.riot.LoginStorage
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -44,7 +45,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val realmKeysUtils: RealmKeysUtils,
|
private val realmKeysUtils: RealmKeysUtils,
|
||||||
private val realmCryptoStoreMigration: RealmCryptoStoreMigration
|
private val clock: Clock,
|
||||||
) : LegacySessionImporter {
|
) : LegacySessionImporter {
|
||||||
|
|
||||||
private val loginStorage = LoginStorage(context)
|
private val loginStorage = LoginStorage(context)
|
||||||
|
@ -167,6 +168,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
|
||||||
newLocation.deleteRecursively()
|
newLocation.deleteRecursively()
|
||||||
newLocation.mkdirs()
|
newLocation.mkdirs()
|
||||||
|
|
||||||
|
val realmCryptoStoreMigration = RealmCryptoStoreMigration(clock)
|
||||||
|
|
||||||
Timber.d("Migration: create legacy realm configuration")
|
Timber.d("Migration: create legacy realm configuration")
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
val realmConfiguration = RealmConfiguration.Builder()
|
||||||
|
|
|
@ -52,11 +52,13 @@ import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
||||||
import org.matrix.android.sdk.internal.di.Authenticated
|
import org.matrix.android.sdk.internal.di.Authenticated
|
||||||
import org.matrix.android.sdk.internal.di.CacheDirectory
|
import org.matrix.android.sdk.internal.di.CacheDirectory
|
||||||
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionRustFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
|
||||||
|
@ -178,6 +180,18 @@ internal abstract class SessionModule {
|
||||||
return File(context.filesDir, sessionId)
|
return File(context.filesDir, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionRustFilesDirectory
|
||||||
|
@SessionScope
|
||||||
|
fun providesRustCryptoFilesDir(
|
||||||
|
@SessionFilesDirectory parent: File,
|
||||||
|
@CryptoDatabase realmConfiguration: RealmConfiguration,
|
||||||
|
): File {
|
||||||
|
val target = File(parent, "rustFlavor")
|
||||||
|
return MigrateEAtoEROperation().execute(realmConfiguration, target)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionDownloadsDirectory
|
@SessionDownloadsDirectory
|
||||||
|
|
|
@ -60,7 +60,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationsProvider
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
|
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionRustFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
@ -121,7 +121,7 @@ fun setRustLogger() {
|
||||||
internal class OlmMachine @Inject constructor(
|
internal class OlmMachine @Inject constructor(
|
||||||
@UserId userId: String,
|
@UserId userId: String,
|
||||||
@DeviceId deviceId: String,
|
@DeviceId deviceId: String,
|
||||||
@SessionFilesDirectory path: File,
|
@SessionRustFilesDirectory path: File,
|
||||||
private val requestSender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
baseMoshi: Moshi,
|
baseMoshi: Moshi,
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.store.db.migration.rust
|
||||||
|
|
||||||
|
object ExtractMigrationDataFailure : java.lang.RuntimeException("Can't proceed with migration, crypto store is empty or some necessary data is missing.")
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.store.db.migration.rust
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
|
||||||
|
import org.matrix.olm.OlmSession
|
||||||
|
import org.matrix.olm.OlmUtility
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.CrossSigningKeyExport
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.MigrationData
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.PickledAccount
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.PickledInboundGroupSession
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.PickledSession
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
private val charset = Charset.forName("UTF-8")
|
||||||
|
|
||||||
|
internal class ExtractMigrationDataUseCase {
|
||||||
|
|
||||||
|
fun extractData(realm: Realm): MigrationData {
|
||||||
|
return try {
|
||||||
|
extract(realm) ?: throw ExtractMigrationDataFailure
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
throw ExtractMigrationDataFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasExistingData(realmConfiguration: RealmConfiguration): Boolean {
|
||||||
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
|
!realm.isEmpty &&
|
||||||
|
// Check if there is a MetaData object
|
||||||
|
realm.where<CryptoMetadataEntity>().count() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extract(realm: Realm): MigrationData? {
|
||||||
|
val metadataEntity = realm.where<CryptoMetadataEntity>().findFirst() ?: return null.also {
|
||||||
|
Timber.w("Rust db migration: No existing metadataEntity")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pickleKey = OlmUtility.getRandomKey()
|
||||||
|
val olmSessionEntities = realm.where<OlmSessionEntity>().findAll()
|
||||||
|
val pickledSessions = olmSessionEntities.map { it.toPickledSession(pickleKey) }
|
||||||
|
|
||||||
|
val inboundGroupSessionEntities = realm.where<OlmInboundGroupSessionEntity>().findAll()
|
||||||
|
val pickledInboundGroupSessions = inboundGroupSessionEntities.mapNotNull { it.toPickledInboundGroupSession(pickleKey) }
|
||||||
|
|
||||||
|
val masterKey = metadataEntity.xSignMasterPrivateKey
|
||||||
|
val userKey = metadataEntity.xSignUserPrivateKey
|
||||||
|
val selfSignedKey = metadataEntity.xSignSelfSignedPrivateKey
|
||||||
|
|
||||||
|
val userId = metadataEntity.userId ?: return null
|
||||||
|
val deviceId = metadataEntity.deviceId ?: return null
|
||||||
|
val backupVersion = metadataEntity.backupVersion
|
||||||
|
val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey
|
||||||
|
|
||||||
|
val trackedUserEntities = realm.where<UserEntity>().findAll()
|
||||||
|
val trackedUserIds = trackedUserEntities.mapNotNull {
|
||||||
|
it.userId
|
||||||
|
}
|
||||||
|
val isOlmAccountShared = metadataEntity.deviceKeysSentToServer
|
||||||
|
|
||||||
|
val olmAccount = metadataEntity.getOlmAccount() ?: return null
|
||||||
|
val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
|
||||||
|
val pickledAccount = PickledAccount(
|
||||||
|
userId = userId,
|
||||||
|
deviceId = deviceId,
|
||||||
|
pickle = pickledOlmAccount,
|
||||||
|
shared = isOlmAccountShared,
|
||||||
|
uploadedSignedKeyCount = 50
|
||||||
|
)
|
||||||
|
return MigrationData(
|
||||||
|
account = pickledAccount,
|
||||||
|
sessions = pickledSessions,
|
||||||
|
inboundGroupSessions = pickledInboundGroupSessions,
|
||||||
|
pickleKey = pickleKey.map { it.toUByte() },
|
||||||
|
backupVersion = backupVersion,
|
||||||
|
backupRecoveryKey = backupRecoveryKey,
|
||||||
|
crossSigning = CrossSigningKeyExport(
|
||||||
|
masterKey = masterKey,
|
||||||
|
selfSigningKey = selfSignedKey,
|
||||||
|
userSigningKey = userKey
|
||||||
|
),
|
||||||
|
trackedUsers = trackedUserIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OlmInboundGroupSessionEntity.toPickledInboundGroupSession(pickleKey: ByteArray): PickledInboundGroupSession? {
|
||||||
|
val senderKey = this.senderKey ?: return null
|
||||||
|
val backedUp = this.backedUp
|
||||||
|
val olmInboundGroupSession = this.getOlmGroupSession() ?: return null.also {
|
||||||
|
Timber.w("Rust db migration: Failed to migrated group session $sessionId")
|
||||||
|
}
|
||||||
|
val data = this.getData() ?: return null.also {
|
||||||
|
Timber.w("Rust db migration: Failed to migrated group session $sessionId, no meta data")
|
||||||
|
}
|
||||||
|
val roomId = data.roomId ?: return null.also {
|
||||||
|
Timber.w("Rust db migration: Failed to migrated group session $sessionId, no roomId")
|
||||||
|
}
|
||||||
|
val pickledInboundGroupSession = olmInboundGroupSession.pickle(pickleKey, StringBuffer()).asString()
|
||||||
|
return PickledInboundGroupSession(
|
||||||
|
pickle = pickledInboundGroupSession,
|
||||||
|
senderKey = senderKey,
|
||||||
|
signingKey = data.keysClaimed.orEmpty(),
|
||||||
|
roomId = roomId,
|
||||||
|
forwardingChains = data.forwardingCurve25519KeyChain.orEmpty(),
|
||||||
|
imported = true,
|
||||||
|
backedUp = backedUp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OlmSessionEntity.toPickledSession(pickleKey: ByteArray): PickledSession {
|
||||||
|
val deviceKey = this.deviceKey ?: ""
|
||||||
|
val lastReceivedMessageTs = this.lastReceivedMessageTs
|
||||||
|
val olmSessionStr = this.olmSessionData
|
||||||
|
val olmSession = deserializeFromRealm<OlmSession>(olmSessionStr)!!
|
||||||
|
val pickledOlmSession = olmSession.pickle(pickleKey, StringBuffer()).asString()
|
||||||
|
return PickledSession(
|
||||||
|
pickle = pickledOlmSession,
|
||||||
|
senderKey = deviceKey,
|
||||||
|
createdUsingFallbackKey = false,
|
||||||
|
creationTime = lastReceivedMessageTs.toString(),
|
||||||
|
lastUseTime = lastReceivedMessageTs.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.asString() = String(this, charset)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.migration.rust.ExtractMigrationDataUseCase
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.ProgressListener
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MigrateEAtoEROperation {
|
||||||
|
|
||||||
|
fun execute(cryptoRealm: RealmConfiguration, sessionFilesDir: File): File {
|
||||||
|
// Temporary code for migration
|
||||||
|
if (!sessionFilesDir.exists()) {
|
||||||
|
sessionFilesDir.mkdir()
|
||||||
|
// perform a migration?
|
||||||
|
val extractMigrationData = ExtractMigrationDataUseCase()
|
||||||
|
try {
|
||||||
|
val progressListener = object : ProgressListener {
|
||||||
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
|
Timber.v("OnProgress: $progress/$total")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Realm.getInstance(cryptoRealm).use { realm ->
|
||||||
|
val migrationData = extractMigrationData.extractData(realm)
|
||||||
|
org.matrix.rustcomponents.sdk.crypto.migrate(migrationData, sessionFilesDir.path, null, progressListener)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "Failure while calling rust migration method")
|
||||||
|
throw failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionFilesDir
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue