diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 45ba7d3344..39f37e8653 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -83,6 +83,11 @@ android { test { java.srcDirs += "src/sharedTest/java" } + main { + assets { + srcDirs 'src/main/assets', 'src/androidTest/assets' + } + } } } diff --git a/matrix-sdk-android/src/androidTest/assets/crypto_store_rust_migration.realm b/matrix-sdk-android/src/androidTest/assets/crypto_store_rust_migration.realm new file mode 100644 index 0000000000..5d6d58956e Binary files /dev/null and b/matrix-sdk-android/src/androidTest/assets/crypto_store_rust_migration.realm differ diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCaseTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCaseTest.kt new file mode 100644 index 0000000000..12268da4fe --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCaseTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2020 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import org.amshove.kluent.internal.assertFails +import org.junit.Assert.assertNotNull +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.common.TemporaryRealmConfigurationFactory +import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule +import org.matrix.android.sdk.internal.crypto.store.migration.fixtures.rustCryptoStoreMigrationConfiguration +import org.matrix.olm.OlmManager + +@RunWith(AndroidJUnit4::class) +class ExtractMigrationDataUseCaseTest : InstrumentedTest { + + @Rule + @JvmField + val realmConfigurationFactory = TemporaryRealmConfigurationFactory() + + private val extractMigrationData = ExtractMigrationDataUseCase() + + @Before + fun setup() { + // Ensure Olm is initialized + OlmManager() + } + + @Test + fun given_a_valid_crypto_store_realm_file_then_extraction_should_be_successful() { + val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = true) + val migrationData = Realm.getInstance(realmConfiguration).use { + extractMigrationData(it) + } + assertNotNull(migrationData) + } + + @Test + fun given_an_empty_crypto_store_realm_file_then_extraction_should_throw() { + val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = false) + assertFails { + Realm.getInstance(realmConfiguration).use { + extractMigrationData(it) + } + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCaseTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCaseTest.kt new file mode 100644 index 0000000000..8e9538a1c0 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCaseTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertEquals +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.common.TemporaryRealmConfigurationFactory +import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule +import org.matrix.android.sdk.internal.crypto.store.migration.fixtures.rustCryptoStoreMigrationConfiguration +import org.matrix.olm.OlmManager +import uniffi.olm.OlmMachine +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +class RustCryptoStoreMigrateUseCaseTest : InstrumentedTest { + + @Rule + @JvmField + val realmConfigurationFactory = TemporaryRealmConfigurationFactory() + + private val extractMigrationData = ExtractMigrationDataUseCase() + + @Before + fun setup() { + // Ensure Olm is initialized + OlmManager() + } + + @Test + fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() = runBlocking { + val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = true) + val cryptoStoreMigrate = RustCryptoStoreMigrateUseCase(realmConfiguration, realmConfigurationFactory.root, extractMigrationData) + val latch = CountDownLatch(1) + val progressListener = ProgressListener(latch) + val result = cryptoStoreMigrate(progressListener) + latch.await() + assert(result.isSuccess) + + val machine = OlmMachine("@ganfra146:matrix.org", "UTDQCHKKNS",realmConfigurationFactory.root.path, null) + assertEquals("mW7LWO4zmhH8Ttuvmzn27vm/USXSKBPgmg7FKQITLiU", machine.identityKeys()["ed25519"]) + assertNotNull(machine.getBackupKeys()) + val crossSigningStatus = machine.crossSigningStatus() + assertTrue(crossSigningStatus.hasMaster) + assertTrue(crossSigningStatus.hasSelfSigning) + assertTrue(crossSigningStatus.hasUserSigning) + } + + @Test + fun given_an_empty_crypto_store_realm_file_then_migration_should_fail() = runBlocking { + val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = false) + val cryptoStoreMigrate = RustCryptoStoreMigrateUseCase(realmConfiguration, realmConfigurationFactory.root, extractMigrationData) + val progressListener = ProgressListener() + val result = cryptoStoreMigrate(progressListener) + assert(result.isFailure) + } + + private class ProgressListener(val latch: CountDownLatch? = null) : uniffi.olm.ProgressListener { + override fun onProgress(progress: Int, total: Int) { + if (progress == total) { + latch?.countDown() + } + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/fixtures/CryptoStoreRealmConfiguration.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/fixtures/CryptoStoreRealmConfiguration.kt new file mode 100644 index 0000000000..d6c61b52e5 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/store/migration/fixtures/CryptoStoreRealmConfiguration.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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.fixtures + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.common.TemporaryRealmConfigurationFactory +import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule + +fun TemporaryRealmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore: Boolean): RealmConfiguration { + return create( + realmFilename = "crypto_store_rust_migration.realm", + assetFilename = "crypto_store_rust_migration.realm".takeIf { populateCryptoStore }, + schemaVersion = 15L, + module = RealmCryptoStoreModule() + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/CleanUpCryptoStoreUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/CleanUpCryptoStoreUseCase.kt new file mode 100644 index 0000000000..1804f887b1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/CleanUpCryptoStoreUseCase.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 + +class CleanUpCryptoStoreUseCase { +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCase.kt index bf3aea2168..dacec3150f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCase.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/ExtractMigrationDataUseCase.kt @@ -35,7 +35,15 @@ private val charset = Charset.forName("UTF-8") internal class ExtractMigrationDataUseCase @Inject constructor() { - operator fun invoke(realm: Realm): MigrationData? { + operator fun invoke(realm: Realm): MigrationData { + return try { + extract(realm) ?: throw ExtractMigrationDataFailure + } catch (failure: Throwable) { + throw ExtractMigrationDataFailure + } + } + + private fun extract(realm: Realm): MigrationData? { val metadataEntity = realm.where().findFirst() ?: return null val pickleKey = OlmUtility.getRandomKey() @@ -61,7 +69,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() { val isOlmAccountShared = metadataEntity.deviceKeysSentToServer val olmAccount = metadataEntity.getOlmAccount()!! - val pickledOlmAccount = olmAccount.pickle(pickleKey) + val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString() val pickledAccount = PickledAccount( userId = userId, deviceId = deviceId, @@ -88,7 +96,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() { private fun OlmInboundGroupSessionEntity.toPickledInboundGroupSession(pickleKey: ByteArray): PickledInboundGroupSession { val senderKey = this.senderKey ?: "" val olmInboundGroupSession = getInboundGroupSession()!! - val pickledInboundGroupSession = olmInboundGroupSession.olmInboundGroupSession!!.pickle(pickleKey) + val pickledInboundGroupSession = olmInboundGroupSession.olmInboundGroupSession!!.pickle(pickleKey, StringBuffer()).asString() return PickledInboundGroupSession( pickle = pickledInboundGroupSession, senderKey = senderKey, @@ -104,7 +112,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() { val deviceKey = this.deviceKey ?: "" val lastReceivedMessageTs = this.lastReceivedMessageTs val olmSession = getOlmSession()!! - val pickledOlmSession = olmSession.pickle(pickleKey) + val pickledOlmSession = olmSession.pickle(pickleKey, StringBuffer()).asString() return PickledSession( pickle = pickledOlmSession, senderKey = deviceKey, @@ -114,14 +122,5 @@ internal class ExtractMigrationDataUseCase @Inject constructor() { ) } - private fun Any.pickle(pickleKey: ByteArray): String { - return try { - val pickleMethod = this.javaClass.getDeclaredMethod("serialize", ByteArray::class.java, StringBuffer::class.java) - pickleMethod.isAccessible = true - val pickled = pickleMethod.invoke(this, pickleKey, StringBuffer())!! - String(pickled as ByteArray, charset) - } catch (throwable: Throwable) { - "" - } - } + private fun ByteArray.asString() = String(this, charset) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/MigrationFailures.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/MigrationFailures.kt new file mode 100644 index 0000000000..1f38de3592 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/MigrationFailures.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 + +object ExtractMigrationDataFailure : java.lang.RuntimeException("Can't proceed with migration, crypto store is empty or some necessary data is missing.") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCase.kt index 31e7a0ccdd..8f6607e36b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCase.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/migration/RustCryptoStoreMigrateUseCase.kt @@ -21,7 +21,6 @@ import io.realm.RealmConfiguration import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.SessionFilesDirectory -import timber.log.Timber import uniffi.olm.ProgressListener import java.io.File import javax.inject.Inject @@ -38,10 +37,6 @@ internal class RustCryptoStoreMigrateUseCase @Inject constructor( private suspend fun migrate(progressListener: ProgressListener) { awaitTransaction(realmConfiguration) { realm: Realm -> val migrationData = extractMigrationData(realm) - if (migrationData == null) { - Timber.v("No migration to do, return") - return@awaitTransaction - } uniffi.olm.migrate(migrationData, dataDir.path, null, progressListener) } }