Remove dependency on libolm - WIP

This commit is contained in:
Benoit Marty 2024-08-26 15:09:08 +02:00 committed by Benoit Marty
parent 6a8e978204
commit 5908cd54f0
64 changed files with 51 additions and 3290 deletions

View File

@ -205,9 +205,6 @@ dependencies {
// Work // Work
implementation libs.androidx.work implementation libs.androidx.work
// olm lib is now hosted in MavenCentral
implementation 'org.matrix.android:olm-sdk:3.2.12'
// DI // DI
implementation libs.dagger.dagger implementation libs.dagger.dagger
kapt libs.dagger.daggerCompiler kapt libs.dagger.daggerCompiler

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7acd69f37612bab0a1ab7f456656712d7ba19dbb679f81b97b58ef44e239f42
size 8523776

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59b4957aa2f9cdc17b14ec8546e144537fac9dee050c6eb173f56fa8602c2736
size 2097152

View File

@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
@ -49,7 +48,6 @@ internal class TestMatrix(context: Context, matrixConfiguration: MatrixConfigura
@Inject internal lateinit var rawService: RawService @Inject internal lateinit var rawService: RawService
@Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var apiInterceptor: ApiInterceptor

View File

@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
@ -29,19 +28,12 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.common.assertByteArrayNotEqual import org.matrix.android.sdk.common.assertByteArrayNotEqual
import org.matrix.olm.OlmManager
import org.matrix.olm.OlmPkDecryption
@Ignore("Ignored in order to speed up test run time") @Ignore("Ignored in order to speed up test run time")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class KeysBackupPasswordTest : InstrumentedTest { class KeysBackupPasswordTest : InstrumentedTest {
@Before
fun ensureLibLoaded() {
OlmManager()
}
/** /**
* Check KeysBackupPassword utilities * Check KeysBackupPassword utilities
*/ */
@ -51,7 +43,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length) assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations) assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation // Reverse operation
val retrievedPrivateKey = retrievePrivateKeyWithPassword( val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -60,7 +52,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations generatePrivateKeyResult.iterations
) )
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey) assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
} }
@ -101,7 +93,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length) assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations) assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad password // Reverse operation, with bad password
val retrievedPrivateKey = retrievePrivateKeyWithPassword( val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -110,7 +102,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations generatePrivateKeyResult.iterations
) )
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
} }
@ -123,7 +115,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length) assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations) assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad iteration // Reverse operation, with bad iteration
val retrievedPrivateKey = retrievePrivateKeyWithPassword( val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -132,7 +124,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
500_001 500_001
) )
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
} }
@ -145,7 +137,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length) assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations) assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad iteration // Reverse operation, with bad iteration
val retrievedPrivateKey = retrievePrivateKeyWithPassword( val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -154,7 +146,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations generatePrivateKeyResult.iterations
) )
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
} }
@ -169,7 +161,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration) val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
// Data from RiotWeb // Data from RiotWeb
val privateKeyBytes = byteArrayOf( val privateKeyBytes = byteArrayOf(
@ -187,5 +179,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
private const val BAD_PASSWORD = "passw0rd" private const val BAD_PASSWORD = "passw0rd"
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu" private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
private const val EXPECTED_PRIVATE_KEY_LENGTH = 32
} }
} }

View File

@ -1,147 +0,0 @@
/*
* 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.mockk.spyk
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.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
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.RustMigrationInfoProvider
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.database.TestRealmConfigurationFactory
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.android.sdk.test.shared.createTimberTestRule
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmManager
import org.matrix.rustcomponents.sdk.crypto.OlmMachine
import java.io.File
import java.security.KeyStore
@RunWith(AndroidJUnit4::class)
class DynamicElementAndroidToElementRMigrationTest {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
@Rule
fun timberTestRule() = createTimberTestRule()
var context: Context = InstrumentationRegistry.getInstrumentation().context
var realm: Realm? = null
@Before
fun setUp() {
// Ensure Olm is initialized
OlmManager()
}
@After
fun tearDown() {
realm?.close()
}
private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) }
private val rustEncryptionConfiguration = RustEncryptionConfiguration(
"foo",
RealmKeysUtils(
context,
SecretStoringUtils(context, keyStore, TestBuildVersionSdkIntProvider(), false)
)
)
private val fakeClock = object : Clock {
override fun epochMillis() = 0L
}
@Test
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() {
testMigrate(false)
}
@Test
@Ignore("We don't migrate group sessions for now, and it's making this test suite unstable")
fun given_a_valid_crypto_store_realm_file_no_lazy_then_migration_should_be_successful() {
testMigrate(true)
}
private fun testMigrate(migrateGroupSessions: Boolean) {
val targetFile = File(configurationFactory.root, "rust-sdk")
val realmName = "crypto_store_migration_16.realm"
val infoProvider = RustMigrationInfoProvider(
targetFile,
rustEncryptionConfiguration
).apply {
migrateMegolmGroupSessions = migrateGroupSessions
}
val migration = RealmCryptoStoreMigration(fakeClock, infoProvider)
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 machine = OlmMachine(userId, deviceId, targetFile.path, rustEncryptionConfiguration.getDatabasePassphrase())
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)
if (migrateGroupSessions) {
assertTrue("Some outbound sessions should be migrated", machine.roomKeyCounts().total.toInt() > 0)
assertTrue("There are some backed-up sessions", machine.roomKeyCounts().backedUp.toInt() > 0)
} else {
assertTrue(machine.roomKeyCounts().total.toInt() == 0)
assertTrue(machine.roomKeyCounts().backedUp.toInt() == 0)
}
// legacy olm sessions should have been deleted
val remainingOlmSessions = realm!!.where<OlmSessionEntity>().findAll().size
assertEquals("legacy olm sessions should have been removed from store", 0, remainingOlmSessions)
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright 2023 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.database
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.spyk
import io.realm.Realm
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
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.RustMigrationInfoProvider
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmManager
import java.io.File
import java.security.KeyStore
class CryptoSanityMigrationTest {
@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()
}
private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) }
@Test
fun cryptoDatabaseShouldMigrateGracefully() {
val realmName = "crypto_store_20.realm"
val rustMigrationInfo = RustMigrationInfoProvider(
File(configurationFactory.root, "test_rust"),
RustEncryptionConfiguration(
"foo",
RealmKeysUtils(
context,
SecretStoringUtils(context, keyStore, TestBuildVersionSdkIntProvider(), false)
)
),
)
val migration = RealmCryptoStoreMigration(
object : Clock {
override fun epochMillis(): Long {
return 0L
}
},
rustMigrationInfo
)
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"7b9a21a8a311e85d75b069a343c23fc952fc3fec5e0c83ecfa13f24b787479c487c3ed587db3dd1f5805d52041fc0ac246516e94b27ffa699ff928622e621aca",
RealmCryptoStoreModule(),
migration.schemaVersion,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
}
}

View File

@ -39,7 +39,6 @@ import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
@ -59,7 +58,6 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var debugService: DebugService @Inject internal lateinit var debugService: DebugService
@Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var apiInterceptor: ApiInterceptor

View File

@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.crypto
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.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.olm.OlmException
/** /**
* Represents a crypto error response. * Represents a crypto error response.
@ -34,8 +33,6 @@ sealed class MXCryptoError : Throwable() {
val detailedErrorDescription: String? = null val detailedErrorDescription: String? = null
) : MXCryptoError() ) : MXCryptoError()
data class OlmError(val olmException: OlmException) : MXCryptoError()
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError() data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
enum class ErrorType { enum class ErrorType {

View File

@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.SasVerification import org.matrix.android.sdk.internal.crypto.verification.SasVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
@ -318,22 +317,6 @@ internal class OlmMachine @Inject constructor(
inner.receiveVerificationEvent(serializedEvent, roomId) inner.receiveVerificationEvent(serializedEvent, roomId)
} }
/**
* Used for lazy migration of inboundGroupSession from EA to ER.
*/
suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result<Unit> {
Timber.v("Migration:: Tentative lazy migration")
return withContext(coroutineDispatchers.io) {
val export = inbound.exportKeys()
?: return@withContext Result.failure(Exception("Failed to export key"))
val result = importDecryptedKeys(listOf(export), null).also {
Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}")
}
if (result.totalNumberOfKeys == 1) return@withContext Result.success(Unit)
return@withContext Result.failure(Exception("Import failed"))
}
}
/** /**
* Mark the given list of users to be tracked, triggering a key query request for them. * Mark the given list of users to be tracked, triggering a key query request for them.
* *

View File

@ -504,17 +504,10 @@ internal class RustCryptoService @Inject constructor(
val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError
val roomId = event.roomId val roomId = event.roomId
val sessionId = content.sessionId val sessionId = content.sessionId
val senderKey = content.senderKey
if (roomId != null && sessionId != null) { if (roomId != null && sessionId != null) {
// try to perform a lazy migration from legacy store
val legacy = tryOrNull("Failed to access legacy crypto store") {
cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
}
if (legacy == null || olmMachine.importRoomKey(legacy).isFailure) {
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId) perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
} }
} }
}
throw mxCryptoError throw mxCryptoError
} }
} }

View File

@ -65,7 +65,6 @@ import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmException
import org.matrix.rustcomponents.sdk.crypto.Request import org.matrix.rustcomponents.sdk.crypto.Request
import org.matrix.rustcomponents.sdk.crypto.RequestType import org.matrix.rustcomponents.sdk.crypto.RequestType
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
@ -840,8 +839,8 @@ internal class RustKeyBackupService @Inject constructor(
try { try {
olmMachine.enableBackupV1(retrievedMegolmBackupAuthData.publicKey, keysVersionResult.version) olmMachine.enableBackupV1(retrievedMegolmBackupAuthData.publicKey, keysVersionResult.version)
keysBackupVersion = keysVersionResult keysBackupVersion = keysVersionResult
} catch (e: OlmException) { } catch (e: Exception) {
Timber.e(e, "OlmException") Timber.e(e, "Exception")
keysBackupStateManager.state = KeysBackupState.Disabled keysBackupStateManager.state = KeysBackupState.Disabled
return return
} }

View File

@ -1,98 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
data class MXInboundMegolmSessionWrapper(
// olm object
val session: OlmInboundGroupSession,
// data about the session
val sessionData: InboundGroupSessionData
) {
// shortcut
val roomId = sessionData.roomId
val senderKey = sessionData.senderKey
val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() }
/**
* Export the inbound group session keys.
* @param index the index to export. If null, the first known index will be used
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
internal fun exportKeys(index: Long? = null): MegolmSessionData? {
return try {
val keysClaimed = sessionData.keysClaimed ?: return null
val wantedIndex = index ?: session.firstKnownIndex
MegolmSessionData(
senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(),
sessionKey = session.export(wantedIndex),
senderClaimedKeys = keysClaimed,
roomId = sessionData.roomId,
sessionId = session.sessionIdentifier(),
senderKey = senderKey,
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
sharedHistory = sessionData.sharedHistory
)
} catch (e: Exception) {
Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed")
null
}
}
companion object {
/**
* @exportFormat true if the megolm keys are in export format
* (ie, they lack an ed25519 signature)
*/
@Throws
internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper {
val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found")
val inboundSession = if (exportFormat) {
OlmInboundGroupSession.importSession(exportedKey)
} else {
OlmInboundGroupSession(exportedKey)
}
.also {
if (it.sessionIdentifier() != megolmSessionData.sessionId) {
it.releaseSession()
throw IllegalStateException("Mismatched group session Id")
}
}
val data = InboundGroupSessionData(
roomId = megolmSessionData.roomId,
senderKey = megolmSessionData.senderKey,
keysClaimed = megolmSessionData.senderClaimedKeys,
forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
sharedHistory = megolmSessionData.sharedHistory,
trusted = false
)
return MXInboundMegolmSessionWrapper(
inboundSession,
data
)
}
}
}

View File

@ -1,132 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.util.JsonDict
import timber.log.Timber
internal data class MXKey(
/**
* The type of the key (in the example: "signed_curve25519").
*/
val type: String,
/**
* The id of the key (in the example: "AAAAFw").
*/
private val keyId: String,
/**
* The key (in the example: "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4").
*/
val value: String,
/**
* signature user Id to [deviceid][signature].
*/
private val signatures: Map<String, Map<String, String>>,
/**
* We have to store the original json because it can contain other fields
* that we don't support yet but they would be needed to check signatures.
*/
private val rawMap: JsonDict
) {
/**
* @return the signed data map
*/
fun signalableJSONDictionary(): Map<String, Any> {
return rawMap.filter {
it.key != "signatures" && it.key != "unsigned"
}
}
/**
* Returns a signature for an user Id and a signkey.
*
* @param userId the user id
* @param signkey the sign key
* @return the signature
*/
fun signatureForUserId(userId: String, signkey: String): String? {
// sanity checks
if (userId.isNotBlank() && signkey.isNotBlank()) {
return signatures[userId]?.get(signkey)
}
return null
}
companion object {
/**
* Key types.
*/
const val KEY_CURVE_25519_TYPE = "curve25519"
const val KEY_SIGNED_CURVE_25519_TYPE = "signed_curve25519"
// const val KEY_ED_25519_TYPE = "ed25519"
/**
* Convert a map to a MXKey.
*
* @param map the map to convert
*
* Json Example:
*
* <pre>
* "signed_curve25519:AAAAFw": {
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
* "fallback" : true|false
* "signatures": {
* "@userId:matrix.org": {
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
* }
* }
* }
* </pre>
*
* into several val members
*/
fun from(map: Map<String, JsonDict>?): MXKey? {
if (map?.isNotEmpty() == true) {
val firstKey = map.keys.first()
val components = firstKey.split(":").dropLastWhile { it.isEmpty() }
if (components.size == 2) {
val params = map[firstKey]
if (params != null) {
if (params["key"] is String) {
@Suppress("UNCHECKED_CAST")
return MXKey(
type = components[0],
keyId = components[1],
value = params["key"] as String,
signatures = params["signatures"] as Map<String, Map<String, String>>,
rawMap = params
)
}
}
}
}
// Error case
Timber.e("## Unable to parse map")
return null
}
}
}

View File

@ -1,32 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import java.io.Serializable
internal data class MXOlmSessionResult(
/**
* the device.
*/
val deviceInfo: CryptoDeviceInfo,
/**
* Base64 olm session id.
* null if no session could be established.
*/
var sessionId: String?
) : Serializable

View File

@ -1,27 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
internal fun <T> MXUsersDevicesMap<T>.toDebugString() =
map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" }
internal fun <T> MXUsersDevicesMap<T>.toDebugCount() =
map.entries.fold(0) { acc, new ->
acc + new.value.keys.size
}

View File

@ -1,151 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
import java.io.Serializable
/**
* This class adds more context to a OlmInboundGroupSession object.
* This allows additional checks. The class implements Serializable so that the context can be stored.
*/
internal class OlmInboundGroupSessionWrapper : Serializable {
// The associated olm inbound group session.
var olmInboundGroupSession: OlmInboundGroupSession? = null
// The room in which this session is used.
var roomId: String? = null
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
// Other keys the sender claims.
var keysClaimed: Map<String, String>? = null
// Devices which forwarded this session to us (normally empty).
var forwardingCurve25519KeyChain: List<String>? = ArrayList()
/**
* @return the first known message index
*/
val firstKnownIndex: Long?
get() {
if (null != olmInboundGroupSession) {
try {
return olmInboundGroupSession!!.firstKnownIndex
} catch (e: Exception) {
Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed")
}
}
return null
}
/**
* Constructor.
*
* @param sessionKey the session key
* @param isImported true if it is an imported session key
*/
constructor(sessionKey: String, isImported: Boolean) {
try {
if (!isImported) {
olmInboundGroupSession = OlmInboundGroupSession(sessionKey)
} else {
olmInboundGroupSession = OlmInboundGroupSession.importSession(sessionKey)
}
} catch (e: Exception) {
Timber.e(e, "Cannot create")
}
}
/**
* Create a new instance from the provided keys map.
*
* @param megolmSessionData the megolm session data
* @throws Exception if the data are invalid
*/
@Throws(Exception::class)
constructor(megolmSessionData: MegolmSessionData) {
try {
olmInboundGroupSession = OlmInboundGroupSession.importSession(megolmSessionData.sessionKey!!)
if (olmInboundGroupSession!!.sessionIdentifier() != megolmSessionData.sessionId) {
throw Exception("Mismatched group session Id")
}
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
} catch (e: Exception) {
throw Exception(e.message)
}
}
/**
* Export the inbound group session keys.
*
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
fun exportKeys(): MegolmSessionData? {
return try {
if (null == forwardingCurve25519KeyChain) {
forwardingCurve25519KeyChain = ArrayList()
}
if (keysClaimed == null) {
return null
}
MegolmSessionData(
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
senderKey = senderKey,
senderClaimedKeys = keysClaimed,
roomId = roomId,
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
null
}
}
/**
* Export the session for a message index.
*
* @param messageIndex the message index
* @return the exported data
*/
fun exportSession(messageIndex: Long): String? {
if (null != olmInboundGroupSession) {
try {
return olmInboundGroupSession!!.export(messageIndex)
} catch (e: Exception) {
Timber.e(e, "## exportSession() : export failed")
}
}
return null
}
}

View File

@ -1,158 +0,0 @@
/*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
import java.io.Serializable
/**
* This class adds more context to a OlmInboundGroupSession object.
* This allows additional checks. The class implements Serializable so that the context can be stored.
*/
// Note used anymore, just for database migration
// Deprecated("Use MXInboundMegolmSessionWrapper")
internal class OlmInboundGroupSessionWrapper2 : Serializable {
// The associated olm inbound group session.
var olmInboundGroupSession: OlmInboundGroupSession? = null
// The room in which this session is used.
var roomId: String? = null
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
// Other keys the sender claims.
var keysClaimed: Map<String, String>? = null
// Devices which forwarded this session to us (normally empty).
var forwardingCurve25519KeyChain: List<String>? = ArrayList()
/**
* @return the first known message index
*/
val firstKnownIndex: Long?
get() {
return try {
olmInboundGroupSession?.firstKnownIndex
} catch (e: Exception) {
Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed")
null
}
}
/**
* Constructor.
*
* @param sessionKey the session key
* @param isImported true if it is an imported session key
*/
constructor(sessionKey: String, isImported: Boolean) {
try {
if (!isImported) {
olmInboundGroupSession = OlmInboundGroupSession(sessionKey)
} else {
olmInboundGroupSession = OlmInboundGroupSession.importSession(sessionKey)
}
} catch (e: Exception) {
Timber.e(e, "Cannot create")
}
}
constructor() {
// empty
}
/**
* Create a new instance from the provided keys map.
*
* @param megolmSessionData the megolm session data
* @throws Exception if the data are invalid
*/
@Throws(Exception::class)
constructor(megolmSessionData: MegolmSessionData) {
try {
val safeSessionKey = megolmSessionData.sessionKey ?: throw Exception("invalid data")
olmInboundGroupSession = OlmInboundGroupSession.importSession(safeSessionKey)
.also {
if (it.sessionIdentifier() != megolmSessionData.sessionId) {
throw Exception("Mismatched group session Id")
}
}
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
} catch (e: Exception) {
throw Exception(e.message)
}
}
/**
* Export the inbound group session keys.
* @param index the index to export. If null, the first known index will be used
*
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
fun exportKeys(index: Long? = null): MegolmSessionData? {
return try {
if (null == forwardingCurve25519KeyChain) {
forwardingCurve25519KeyChain = ArrayList()
}
if (keysClaimed == null) {
return null
}
val safeOlmInboundGroupSession = olmInboundGroupSession ?: return null
val wantedIndex = index ?: safeOlmInboundGroupSession.firstKnownIndex
MegolmSessionData(
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain?.toList().orEmpty(),
senderKey = senderKey,
senderClaimedKeys = keysClaimed,
roomId = roomId,
sessionId = safeOlmInboundGroupSession.sessionIdentifier(),
sessionKey = safeOlmInboundGroupSession.export(wantedIndex),
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
null
}
}
/**
* Export the session for a message index.
*
* @param messageIndex the message index
* @return the exported data
*/
fun exportSession(messageIndex: Long): String? {
return try {
return olmInboundGroupSession?.export(messageIndex)
} catch (e: Exception) {
Timber.e(e, "## exportSession() : export failed")
null
}
}
}

View File

@ -1,40 +0,0 @@
/*
* 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.model
import kotlinx.coroutines.sync.Mutex
import org.matrix.olm.OlmSession
/**
* Encapsulate a OlmSession and a last received message Timestamp.
*/
internal data class OlmSessionWrapper(
// The associated olm session.
val olmSession: OlmSession,
// Timestamp at which the session last received a message.
var lastReceivedMessageTs: Long = 0,
val mutex: Mutex = Mutex()
) {
/**
* Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`.
*/
fun onMessageReceived(currentTimeMillis: Long) {
lastReceivedMessageTs = currentTimeMillis
}
}

View File

@ -1,28 +0,0 @@
/*
* 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.model
import org.matrix.olm.OlmOutboundGroupSession
internal data class OutboundGroupSessionWrapper(
val outboundGroupSession: OlmOutboundGroupSession,
val creationTime: Long,
/**
* As per MSC 3061, declares if this key could be shared when inviting a new user to the room.
*/
val sharedHistory: Boolean = false
)

View File

@ -44,9 +44,7 @@ import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.SecretShareManager import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword
import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.olm.OlmPkMessage
import java.security.SecureRandom import java.security.SecureRandom
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.Mac import javax.crypto.Mac
@ -322,9 +320,13 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
val algorithm = key.keyInfo.content val algorithm = key.keyInfo.content
if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat // TODO BMA
// val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) { return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
// decrypt from recovery key // decrypt from recovery key
"TODO"
// TODO BMA
/*
withOlmDecryption { olmPkDecryption -> withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(keySpec.privateKey) olmPkDecryption.setPrivateKey(keySpec.privateKey)
olmPkDecryption.decrypt(OlmPkMessage() olmPkDecryption.decrypt(OlmPkMessage()
@ -335,6 +337,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
} }
) )
} }
*/
} }
} else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) { } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat

View File

@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
/** /**
@ -143,14 +142,4 @@ interface IMXCommonCryptoStore {
* @return the device or null if not found * @return the device or null if not found
*/ */
fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo?
/**
* Retrieve an inbound group session.
* Used in rust for lazy migration
*
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return an inbound group session.
*/
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
} }

View File

@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventCo
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.OlmMachine import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync
@ -49,11 +48,8 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
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.crypto.store.db.model.OutgoingKeyRequestEntity import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
@ -70,7 +66,7 @@ private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO)
/** /**
* In the transition phase, the rust SDK is still using parts to the realm crypto store, * In the transition phase, the rust SDK is still using parts to the realm crypto store,
* this should be removed after full migration. * this should be removed after full migration. TODO BMA
*/ */
@SessionScope @SessionScope
internal class RustCryptoStore @Inject constructor( internal class RustCryptoStore @Inject constructor(
@ -86,6 +82,7 @@ internal class RustCryptoStore @Inject constructor(
// still needed on rust due to the global crypto settings // still needed on rust due to the global crypto settings
init { init {
// Ensure CryptoMetadataEntity is inserted in DB // Ensure CryptoMetadataEntity is inserted in DB
// TODO BMA
doRealmTransaction("init", realmConfiguration) { realm -> doRealmTransaction("init", realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst() var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
@ -134,20 +131,6 @@ internal class RustCryptoStore @Inject constructor(
} }
} }
/**
* Needed for lazy migration of sessions from the legacy store.
*/
override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
?.toModel()
}
}
// ================================================ // ================================================
// Things that should be migrated to another store than realm // Things that should be migrated to another store than realm
// ================================================ // ================================================

View File

@ -17,31 +17,7 @@
package org.matrix.android.sdk.internal.crypto.store.db package org.matrix.android.sdk.internal.crypto.store.db
import io.realm.DynamicRealm import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo001Legacy
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo002Legacy
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo003RiotX
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo004
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo005
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo006
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo007
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo008
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo009
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo010
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo011
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo012
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo019
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo020
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo021
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo022
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo023
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -51,11 +27,9 @@ import javax.inject.Inject
* 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6). * 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6).
*/ */
internal class RealmCryptoStoreMigration @Inject constructor( internal class RealmCryptoStoreMigration @Inject constructor(
private val clock: Clock,
private val rustMigrationInfoProvider: RustMigrationInfoProvider,
) : MatrixRealmMigration( ) : MatrixRealmMigration(
dbName = "Crypto", dbName = "Crypto",
schemaVersion = 23L, schemaVersion = 24L,
) { ) {
/** /**
* Forces all RealmCryptoStoreMigration instances to be equal. * Forces all RealmCryptoStoreMigration instances to be equal.
@ -65,33 +39,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(
override fun hashCode() = 5000 override fun hashCode() = 5000
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform() // Delete the whole DB
if (oldVersion < 2) MigrateCryptoTo002Legacy(realm).perform() // TODO BMA
if (oldVersion < 3) MigrateCryptoTo003RiotX(realm).perform() realm.deleteAll()
if (oldVersion < 4) MigrateCryptoTo004(realm).perform()
if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
if (oldVersion < 8) MigrateCryptoTo008(realm, clock).perform()
if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
if (oldVersion < 12) MigrateCryptoTo012(realm).perform()
if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
if (oldVersion < 16) MigrateCryptoTo016(realm).perform()
if (oldVersion < 17) MigrateCryptoTo017(realm).perform()
if (oldVersion < 18) MigrateCryptoTo018(realm).perform()
if (oldVersion < 19) MigrateCryptoTo019(realm).perform()
if (oldVersion < 20) MigrateCryptoTo020(realm).perform()
if (oldVersion < 21) MigrateCryptoTo021(realm).perform()
if (oldVersion < 22) MigrateCryptoTo022(
realm,
rustMigrationInfoProvider.rustDirectory,
rustMigrationInfoProvider.rustEncryptionConfiguration,
rustMigrationInfoProvider.migrateMegolmGroupSessions
).perform()
if (oldVersion < 23) MigrateCryptoTo023(realm).perform()
} }
} }

View File

@ -1,31 +0,0 @@
/*
* Copyright 2023 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
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.di.SessionRustFilesDirectory
import java.io.File
import javax.inject.Inject
internal class RustMigrationInfoProvider @Inject constructor(
@SessionRustFilesDirectory
val rustDirectory: File,
val rustEncryptionConfiguration: RustEncryptionConfiguration
) {
var migrateMegolmGroupSessions: Boolean = false
}

View File

@ -1,87 +0,0 @@
/*
* 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.db.mapper
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.realm.RealmList
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
import timber.log.Timber
import javax.inject.Inject
internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) {
private val signaturesAdapter = moshi.adapter<Map<String, Map<String, String>>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
fun update(keyInfo: KeyInfoEntity, cryptoCrossSigningKey: CryptoCrossSigningKey) {
// update signatures?
keyInfo.signatures = serializeSignatures(cryptoCrossSigningKey.signatures)
keyInfo.usages = cryptoCrossSigningKey.usages?.toTypedArray()?.let { RealmList(*it) }
?: RealmList()
}
fun map(userId: String?, keyInfo: KeyInfoEntity?): CryptoCrossSigningKey? {
val pubKey = keyInfo?.publicKeyBase64 ?: return null
return CryptoCrossSigningKey(
userId = userId ?: "",
keys = mapOf("ed25519:$pubKey" to pubKey),
usages = keyInfo.usages.toList(),
signatures = deserializeSignatures(keyInfo.signatures),
trustLevel = keyInfo.trustLevelEntity?.let {
DeviceTrustLevel(
crossSigningVerified = it.crossSignedVerified ?: false,
locallyVerified = it.locallyVerified ?: false
)
}
)
}
fun map(keyInfo: CryptoCrossSigningKey): KeyInfoEntity {
return KeyInfoEntity().apply {
publicKeyBase64 = keyInfo.unpaddedBase64PublicKey
usages = keyInfo.usages?.let { RealmList(*it.toTypedArray()) } ?: RealmList()
signatures = serializeSignatures(keyInfo.signatures)
// TODO how to handle better, check if same keys?
// reset trust
trustLevelEntity = null
}
}
fun serializeSignatures(signatures: Map<String, Map<String, String>>?): String {
return signaturesAdapter.toJson(signatures)
}
fun deserializeSignatures(signatures: String?): Map<String, Map<String, String>>? {
if (signatures == null) {
return null
}
return try {
signaturesAdapter.fromJson(signatures)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
}
}

View File

@ -1,35 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
realm.schema.get("OlmSessionEntity")
?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
?.transform {
it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
}
}
}

View File

@ -1,86 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("IncomingRoomKeyRequestEntity")
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
try {
val requestBodyString = dynamicObject.getString("requestBodyString")
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeFieldIfExists("requestBodyString")
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("OutgoingRoomKeyRequestEntity")
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
try {
val requestBodyString = dynamicObject.getString("requestBodyString")
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeFieldIfExists("requestBodyString")
Timber.d("Create KeysBackupDataEntity")
if (!realm.schema.contains("KeysBackupDataEntity")) {
realm.schema.create("KeysBackupDataEntity")
.addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
.addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
.setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
}
}
}

View File

@ -1,83 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import org.matrix.androidsdk.crypto.data.MXDeviceInfo
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber
internal class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Migrate to RiotX model")
realm.schema.get("CryptoRoomEntity")
?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
// Convert format of MXDeviceInfo, package has to be the same.
realm.schema.get("DeviceInfoEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { legacyMxDeviceInfo ->
val newMxDeviceInfo = org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo(
deviceId = legacyMxDeviceInfo.deviceId,
userId = legacyMxDeviceInfo.userId,
algorithms = legacyMxDeviceInfo.algorithms,
keys = legacyMxDeviceInfo.keys,
signatures = legacyMxDeviceInfo.signatures,
unsigned = legacyMxDeviceInfo.unsigned,
verified = legacyMxDeviceInfo.mVerified
)
obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
.apply {
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
roomId = mxOlmInboundGroupSession2.mRoomId
senderKey = mxOlmInboundGroupSession2.mSenderKey
keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
}
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
}
}

View File

@ -1,143 +0,0 @@
/*
* 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
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.di.SerializeNulls
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Version 4L added Cross Signing info persistence
internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
override fun doMigrate(realm: DynamicRealm) {
if (realm.schema.contains("TrustLevelEntity")) {
Timber.d("Skipping Step 3 -> 4 because entities already exist")
return
}
Timber.d("Create KeyInfoEntity")
val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
Timber.d("Create CrossSigningInfoEntity")
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
Timber.d("Updating UserEntity table")
realm.schema.get("UserEntity")
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
val listMigrationAdapter = moshi.adapter<List<String>>(
Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
)
)
val mapMigrationAdapter = moshi.adapter<JsonDict>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
realm.schema.get("DeviceInfoEntity")
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
?.transform { obj ->
try {
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
val trustLevel = realm.createObject("TrustLevelEntity")
when (oldDevice.verified) {
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
}
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
}
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
}
} catch (failure: Throwable) {
Timber.w(failure, "Crypto Data base migration error")
// an unfortunate refactor did modify that class, making deserialization failing
// so we just skip and ignore..
}
}
?.removeField("deviceInfoData")
}
}

View File

@ -1,62 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.remove("OutgoingRoomKeyRequestEntity")
realm.schema.remove("IncomingRoomKeyRequestEntity")
// Not need to migrate existing request, just start fresh?
realm.schema.create("GossipingEventEntity")
.addField("type", String::class.java)
.addIndex("type")
.addField("content", String::class.java)
.addField("sender", String::class.java)
.addIndex("sender")
.addField("decryptionResultJson", String::class.java)
.addField("decryptionErrorCode", String::class.java)
.addField("ageLocalTs", Long::class.java)
.setNullable("ageLocalTs", true)
.addField("sendStateStr", String::class.java)
realm.schema.create("IncomingGossipingRequestEntity")
.addField("requestId", String::class.java)
.addIndex("requestId")
.addField("typeStr", String::class.java)
.addIndex("typeStr")
.addField("otherUserId", String::class.java)
.addField("requestedInfoStr", String::class.java)
.addField("otherDeviceId", String::class.java)
.addField("requestStateStr", String::class.java)
.addField("localCreationTimestamp", Long::class.java)
.setNullable("localCreationTimestamp", true)
realm.schema.create("OutgoingGossipingRequestEntity")
.addField("requestId", String::class.java)
.addIndex("requestId")
.addField("recipientsData", String::class.java)
.addField("requestedInfoStr", String::class.java)
.addField("typeStr", String::class.java)
.addIndex("typeStr")
.addField("requestStateStr", String::class.java)
}
}

View File

@ -1,32 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
}
}

View File

@ -1,65 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Updating KeyInfoEntity table")
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try {
keyInfoEntities.forEach {
val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
val objectSignatures: Map<String, Map<String, String>>? = deserializeFromRealm(stringSignatures)
val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
}
} catch (ignore: Throwable) {
}
// Migrate frozen classes
val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
inboundGroupSessions.forEach { dynamicObject ->
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
try {
deserializeFromRealm<OlmInboundGroupSessionWrapper?>(serializedObject)?.let { oldFormat ->
val newFormat = oldFormat.exportKeys()?.let {
OlmInboundGroupSessionWrapper2(it)
}
dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
}
} catch (failure: Throwable) {
Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
}
}
}
}
}

View File

@ -1,50 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import org.matrix.android.sdk.internal.util.time.Clock
internal class MigrateCryptoTo008(
realm: DynamicRealm,
private val clock: Clock,
) : RealmMigrator(realm, 8) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("MyDeviceLastSeenInfoEntity")
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
.addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
.setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
val now = clock.epochMillis()
realm.schema.get("DeviceInfoEntity")
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
?.transform { deviceInfoEntity ->
tryOrNull {
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
}
}
}
}

View File

@ -1,44 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Fixes duplicate devices in UserEntity#devices
internal class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
override fun doMigrate(realm: DynamicRealm) {
val userEntities = realm.where("UserEntity").findAll()
userEntities.forEach {
try {
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
?: return@forEach
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
if (distinct.size != deviceList.size) {
deviceList.clear()
deviceList.addAll(distinct)
}
} catch (failure: Throwable) {
Timber.w(failure, "Crypto Data base migration error for migrateTo9")
}
}
}
}

View File

@ -1,50 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 10L added WithHeld Keys Info (MSC2399)
internal class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("WithHeldSessionEntity")
.addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
.addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
.addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
.addIndex(WithHeldSessionEntityFields.SESSION_ID)
.addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
.addIndex(WithHeldSessionEntityFields.SENDER_KEY)
.addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
.addField(WithHeldSessionEntityFields.REASON, String::class.java)
realm.schema.create("SharedSessionEntity")
.addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
.addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
.addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
.addIndex(SharedSessionEntityFields.SESSION_ID)
.addField(SharedSessionEntityFields.USER_ID, String::class.java)
.addIndex(SharedSessionEntityFields.USER_ID)
.addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
.addIndex(SharedSessionEntityFields.DEVICE_ID)
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
}
}

View File

@ -1,30 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
internal class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
}
}

View File

@ -1,36 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 12L added outbound group session persistence
internal class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
override fun doMigrate(realm: DynamicRealm) {
val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
.addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
.addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
.setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
realm.schema.get("CryptoRoomEntity")
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
}
}

View File

@ -1,78 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Version 13L delete unreferenced TrustLevelEntity
internal class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
override fun doMigrate(realm: DynamicRealm) {
// Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
/*
Creating a new temp field called isLinked which is set to true for those which are
references by other objects. Rest of them are set to false. Then removing all
those which are false and hence duplicate and unnecessary. Then removing the temp field
isLinked
*/
var mainCounter = 0
var deviceInfoCounter = 0
var keyInfoCounter = 0
val deleteCounter: Int
trustLevelEntitySchema
?.addField("isLinked", Boolean::class.java)
?.transform { obj ->
// Setting to false for all by default
obj.set("isLinked", false)
mainCounter++
}
realm.schema.get("DeviceInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in DeviceInfoEntity
deviceInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
realm.schema.get("KeyInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in KeyInfoEntity
keyInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
// Removing all those which are set as false
realm.where("TrustLevelEntity")
.equalTo("isLinked", false)
.findAll()
.also { deleteCounter = it.size }
.deleteAllFromRealm()
trustLevelEntitySchema?.removeField("isLinked")
Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
Timber.e("TrustLevelEntity cleanup: Something is not correct...")
}
}
}

View File

@ -1,41 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 14L Update the way we remember key sharing
internal class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("SharedSessionEntity")
?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
?.transform {
val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
val knownDevice = realm.where("DeviceInfoEntity")
.equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
.equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
.findFirst()
it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
}
}
}

View File

@ -1,36 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 15L adds wasEncryptedOnce field to CryptoRoomEntity
internal class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, Boolean::class.java)
?.setNullable(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, true)
?.transform {
val currentAlgorithm = it.getString(CryptoRoomEntityFields.ALGORITHM)
it.set(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, currentAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM)
}
}
}

View File

@ -1,70 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateCryptoTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.remove("OutgoingGossipingRequestEntity")
realm.schema.remove("IncomingGossipingRequestEntity")
realm.schema.remove("GossipingEventEntity")
// No need to migrate existing request, just start fresh
val replySchema = realm.schema.create("KeyRequestReplyEntity")
.addField(KeyRequestReplyEntityFields.SENDER_ID, String::class.java)
.addField(KeyRequestReplyEntityFields.FROM_DEVICE, String::class.java)
.addField(KeyRequestReplyEntityFields.EVENT_JSON, String::class.java)
realm.schema.create("OutgoingKeyRequestEntity")
.addField(OutgoingKeyRequestEntityFields.REQUEST_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.REQUEST_ID)
.addField(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID)
.addRealmListField(OutgoingKeyRequestEntityFields.REPLIES.`$`, replySchema)
.addField(OutgoingKeyRequestEntityFields.RECIPIENTS_DATA, String::class.java)
.addField(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR)
.addField(OutgoingKeyRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
.addField(OutgoingKeyRequestEntityFields.ROOM_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.ROOM_ID)
.addField(OutgoingKeyRequestEntityFields.REQUESTED_INDEX, Integer::class.java)
.addField(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, Long::class.java)
.setNullable(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, true)
realm.schema.create("AuditTrailEntity")
.addField(AuditTrailEntityFields.AGE_LOCAL_TS, Long::class.java)
.setNullable(AuditTrailEntityFields.AGE_LOCAL_TS, true)
.addField(AuditTrailEntityFields.CONTENT_JSON, String::class.java)
.addField(AuditTrailEntityFields.TYPE, String::class.java)
.addIndex(AuditTrailEntityFields.TYPE)
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, Boolean::class.java)
?.transform {
// set the default value to true
it.setBoolean(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, true)
}
}
}

View File

@ -1,101 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
/**
* Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061.
* Also migrates how megolm session are stored to avoid additional serialized frozen class.
*/
internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
// We don't have access to the session database to check for the state here and set the good value.
// But for now as it's behind a lab flag, will set to false and force initial sync when enabled
it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false)
}
realm.schema.get("OutboundGroupSessionInfoEntity")
?.addField(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
// We don't have access to the session database to check for the state here and set the good value.
// But for now as it's behind a lab flag, will set to false and force initial sync when enabled
it.setBoolean(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, false)
}
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java)
?.transform { obj ->
// default to false
obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false)
}
val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
realm.schema.get("OlmInboundGroupSessionEntity")
?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java)
?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java)
?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java)
?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java)
?.transform { dynamicObject ->
try {
// we want to convert the old wrapper frozen class into a
// map of sessionData & the pickled session herself
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData ->
val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") {
@Suppress("DEPRECATION")
deserializeFromRealm<org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2?>(oldData)
}
val groupSession = oldWrapper?.olmInboundGroupSession
?: return@transform Unit.also {
Timber.w("Failed to migrate megolm session, no olmInboundGroupSession")
}
// now convert to new data
val data = InboundGroupSessionData(
senderKey = oldWrapper.senderKey,
roomId = oldWrapper.roomId,
keysClaimed = oldWrapper.keysClaimed,
forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain,
sharedHistory = false,
)
dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data))
dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession))
// denormalized fields
dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId)
dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false)
}
} catch (failure: Throwable) {
Timber.e(failure, "Failed to migrate megolm session")
}
}
}
}

View File

@ -1,52 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
/**
* This migration is adding support for trusted flags on megolm sessions.
* We can't really assert the trust of existing keys, so for the sake of simplicity we are going to
* mark existing keys as safe.
* This migration can take long depending on the account
*/
internal class MigrateCryptoTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) {
private val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { dynamicObject ->
try {
dynamicObject.getString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON)?.let { oldData ->
moshiAdapter.fromJson(oldData)?.let { dataToMigrate ->
dataToMigrate.copy(trusted = true).let {
dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(it))
}
}
}
} catch (failure: Throwable) {
Timber.e(failure, "Failed to migrate megolm session")
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import io.realm.DynamicRealmObject
import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration is adding support for trusted flags on megolm sessions.
* We can't really assert the trust of existing keys, so for the sake of simplicity we are going to
* mark existing keys as safe.
* This migration can take long depending on the account
*/
internal class MigrateCryptoTo019(realm: DynamicRealm) : RealmMigrator(realm, 19) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CrossSigningInfoEntity")
?.addField(CrossSigningInfoEntityFields.WAS_USER_VERIFIED_ONCE, Boolean::class.java)
?.transform { dynamicObject ->
val knowKeys = dynamicObject.getList(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`)
val msk = knowKeys.firstOrNull {
it.getList(KeyInfoEntityFields.USAGES.`$`, String::class.java).orEmpty().contains(KeyUsage.MASTER.value)
}
val ssk = knowKeys.firstOrNull {
it.getList(KeyInfoEntityFields.USAGES.`$`, String::class.java).orEmpty().contains(KeyUsage.SELF_SIGNING.value)
}
val isTrusted = isDynamicKeyInfoTrusted(msk?.get<DynamicRealmObject>(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)) &&
isDynamicKeyInfoTrusted(ssk?.get<DynamicRealmObject>(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`))
dynamicObject.setBoolean(CrossSigningInfoEntityFields.WAS_USER_VERIFIED_ONCE, isTrusted)
}
}
private fun isDynamicKeyInfoTrusted(keyInfo: DynamicRealmObject?): Boolean {
if (keyInfo == null) return false
return !keyInfo.isNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) && keyInfo.getBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) &&
!keyInfo.isNull(TrustLevelEntityFields.LOCALLY_VERIFIED) && keyInfo.getBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED)
}
}

View File

@ -1,32 +0,0 @@
/*
* 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration adds a new field into MyDeviceLastSeenInfoEntity corresponding to the last seen user agent.
*/
internal class MigrateCryptoTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("MyDeviceLastSeenInfoEntity")
?.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_USER_AGENT, String::class.java)
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.crypto.MEGOLM_DEFAULT_ROTATION_MSGS
import org.matrix.android.sdk.api.crypto.MEGOLM_DEFAULT_ROTATION_PERIOD_MS
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration stores the rotation parameters for megolm oubound sessions.
*/
internal class MigrateCryptoTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.ROTATION_PERIOD_MS, Long::class.java)
?.setNullable(CryptoRoomEntityFields.ROTATION_PERIOD_MS, true)
?.addField(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, Long::class.java)
?.setNullable(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, true)
?.transform {
// As a migration we set the default (will be on par with existing code)
// A clear cache will have the correct values.
it.setLong(CryptoRoomEntityFields.ROTATION_PERIOD_MS, MEGOLM_DEFAULT_ROTATION_PERIOD_MS)
it.setLong(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, MEGOLM_DEFAULT_ROTATION_MSGS)
}
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright 2023 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.session.MigrateEAtoEROperation
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import java.io.File
/**
* This migration creates the rust database and migrates from legacy crypto.
*/
internal class MigrateCryptoTo022(
realm: DynamicRealm,
private val rustDirectory: File,
private val rustEncryptionConfiguration: RustEncryptionConfiguration,
private val migrateMegolmGroupSessions: Boolean = false
) : RealmMigrator(
realm,
22
) {
override fun doMigrate(realm: DynamicRealm) {
// Migrate to rust!
val migrateOperation = MigrateEAtoEROperation(migrateMegolmGroupSessions)
migrateOperation.dynamicExecute(realm, rustDirectory, rustEncryptionConfiguration.getDatabasePassphrase())
// wa can't delete all for now, but we can do some cleaning
realm.schema.get("OlmSessionEntity")?.transform {
it.deleteFromRealm()
}
// a future migration will clean the rest
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright (c) 2024 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
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Some fields are now required due to upgrade of Kotlin version.
// See https://github.com/realm/realm-java/issues/7810 for more details.
internal class MigrateCryptoTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("OutgoingKeyRequestEntity")
?.setRequired(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, true)
}
}

View File

@ -1,20 +0,0 @@
/*
* 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
data class ExtractMigrationDataFailure(override val cause: Throwable) :
java.lang.RuntimeException("Can't proceed with migration, crypto store is empty or some necessary data is missing.", cause)

View File

@ -1,96 +0,0 @@
/*
* 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.model.CryptoMetadataEntity
import org.matrix.olm.OlmUtility
import org.matrix.rustcomponents.sdk.crypto.MigrationData
import timber.log.Timber
import kotlin.system.measureTimeMillis
internal class ExtractMigrationDataUseCase(private val migrateGroupSessions: Boolean = false) {
fun extractData(realm: RealmToMigrate, importPartial: ((MigrationData) -> Unit)) {
return try {
extract(realm, importPartial)
} catch (failure: Throwable) {
throw ExtractMigrationDataFailure(failure)
}
}
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 &&
realm.where<CryptoMetadataEntity>().findFirst()?.olmAccountData != null
}
}
private fun extract(realm: RealmToMigrate, importPartial: ((MigrationData) -> Unit)) {
val pickleKey = OlmUtility.getRandomKey()
val baseExtract = realm.getPickledAccount(pickleKey)
// import the account asap
importPartial(baseExtract)
val chunkSize = 500
realm.trackedUsersChunk(500) {
importPartial(
baseExtract.copy(trackedUsers = it)
)
}
var migratedOlmSessionCount = 0
var writeTime = 0L
measureTimeMillis {
realm.pickledOlmSessions(pickleKey, chunkSize) { pickledSessions ->
migratedOlmSessionCount += pickledSessions.size
measureTimeMillis {
importPartial(
baseExtract.copy(sessions = pickledSessions)
)
}.also { writeTime += it }
}
}.also {
Timber.i("Migration: took $it ms to migrate $migratedOlmSessionCount olm sessions")
Timber.i("Migration: rust import time $writeTime")
}
// We don't migrate outbound session by default directly after migration
// We are going to do it lazyly when decryption fails
if (migrateGroupSessions) {
var migratedInboundGroupSessionCount = 0
measureTimeMillis {
realm.pickledOlmGroupSessions(pickleKey, chunkSize) { pickledSessions ->
migratedInboundGroupSessionCount += pickledSessions.size
measureTimeMillis {
importPartial(
baseExtract.copy(inboundGroupSessions = pickledSessions)
)
}.also { writeTime += it }
}
}.also {
Timber.i("Migration: took $it ms to migrate $migratedInboundGroupSessionCount group sessions")
Timber.i("Migration: rust import time $writeTime")
}
}
}
}

View File

@ -1,336 +0,0 @@
/*
* Copyright (c) 2023 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.kotlin.where
import okhttp3.internal.toImmutableList
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
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.CryptoMetadataEntityFields
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.crypto.store.db.model.OlmSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmInboundGroupSession
import org.matrix.olm.OlmSession
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
sealed class RealmToMigrate {
data class DynamicRealm(val realm: io.realm.DynamicRealm) : RealmToMigrate()
data class ClassicRealm(val realm: io.realm.Realm) : RealmToMigrate()
}
fun RealmToMigrate.hasExistingData(): Boolean {
return when (this) {
is RealmToMigrate.ClassicRealm -> {
!this.realm.isEmpty &&
// Check if there is a MetaData object
this.realm.where<CryptoMetadataEntity>().count() > 0 &&
this.realm.where<CryptoMetadataEntity>().findFirst()?.olmAccountData != null
}
is RealmToMigrate.DynamicRealm -> {
return true
}
}
}
@Throws
fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData {
return when (this) {
is RealmToMigrate.ClassicRealm -> {
val metadataEntity = realm.where<CryptoMetadataEntity>().findFirst()
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing metadataEntity")
val masterKey = metadataEntity.xSignMasterPrivateKey
val userKey = metadataEntity.xSignUserPrivateKey
val selfSignedKey = metadataEntity.xSignSelfSignedPrivateKey
Timber.i("## Migration: has private MSK ${masterKey.isNullOrBlank().not()}")
Timber.i("## Migration: has private USK ${userKey.isNullOrBlank().not()}")
Timber.i("## Migration: has private SSK ${selfSignedKey.isNullOrBlank().not()}")
val userId = metadataEntity.userId
?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null")
val deviceId = metadataEntity.deviceId
?: throw java.lang.IllegalArgumentException("Rust db migration: deviceID is null")
val backupVersion = metadataEntity.backupVersion
val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey
Timber.i("## Migration: has private backup key ${backupRecoveryKey != null} for version $backupVersion")
val isOlmAccountShared = metadataEntity.deviceKeysSentToServer
val olmAccount = metadataEntity.getOlmAccount()
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing account")
val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
val pickledAccount = PickledAccount(
userId = userId,
deviceId = deviceId,
pickle = pickledOlmAccount,
shared = isOlmAccountShared,
uploadedSignedKeyCount = 50
)
MigrationData(
account = pickledAccount,
pickleKey = pickleKey,
crossSigning = CrossSigningKeyExport(
masterKey = masterKey,
selfSigningKey = selfSignedKey,
userSigningKey = userKey
),
sessions = emptyList(),
backupRecoveryKey = backupRecoveryKey,
trackedUsers = emptyList(),
inboundGroupSessions = emptyList(),
backupVersion = backupVersion,
// TODO import room settings from legacy DB
roomSettings = emptyMap()
)
}
is RealmToMigrate.DynamicRealm -> {
val cryptoMetadataEntitySchema = realm.schema.get("CryptoMetadataEntity")
?: throw java.lang.IllegalStateException("Missing Metadata entity")
var migrationData: MigrationData? = null
cryptoMetadataEntitySchema.transform { dynMetaData ->
val serializedOlmAccount = dynMetaData.getString(CryptoMetadataEntityFields.OLM_ACCOUNT_DATA)
val masterKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY)
val userKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY)
val selfSignedKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY)
val userId = dynMetaData.getString(CryptoMetadataEntityFields.USER_ID)
?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null")
val deviceId = dynMetaData.getString(CryptoMetadataEntityFields.DEVICE_ID)
?: throw java.lang.IllegalArgumentException("Rust db migration: deviceID is null")
val backupVersion = dynMetaData.getString(CryptoMetadataEntityFields.BACKUP_VERSION)
val backupRecoveryKey = dynMetaData.getString(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY)
val isOlmAccountShared = dynMetaData.getBoolean(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER)
val olmAccount = deserializeFromRealm<OlmAccount>(serializedOlmAccount)
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing account")
val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
val pickledAccount = PickledAccount(
userId = userId,
deviceId = deviceId,
pickle = pickledOlmAccount,
shared = isOlmAccountShared,
uploadedSignedKeyCount = 50
)
migrationData = MigrationData(
account = pickledAccount,
pickleKey = pickleKey,
crossSigning = CrossSigningKeyExport(
masterKey = masterKey,
selfSigningKey = selfSignedKey,
userSigningKey = userKey
),
sessions = emptyList(),
backupRecoveryKey = backupRecoveryKey,
trackedUsers = emptyList(),
inboundGroupSessions = emptyList(),
backupVersion = backupVersion,
// TODO import room settings from legacy DB
roomSettings = emptyMap()
)
}
migrationData!!
}
}
}
fun RealmToMigrate.trackedUsersChunk(chunkSize: Int, onChunk: ((List<String>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<UserEntity>()
.findAll()
.chunked(chunkSize)
.onEach {
onChunk(it.mapNotNull { it.userId })
}
}
is RealmToMigrate.DynamicRealm -> {
val userList = mutableListOf<String>()
realm.schema.get("UserEntity")?.transform {
val userId = it.getString(UserEntityFields.USER_ID)
// should we check the tracking status?
userList.add(userId)
if (userList.size > chunkSize) {
onChunk(userList.toImmutableList())
userList.clear()
}
}
if (userList.isNotEmpty()) {
onChunk(userList)
}
}
}
}
fun RealmToMigrate.pickledOlmSessions(pickleKey: ByteArray, chunkSize: Int, onChunk: ((List<PickledSession>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<OlmSessionEntity>().findAll()
.chunked(chunkSize) { chunk ->
val export = chunk.map { it.toPickledSession(pickleKey) }
onChunk(export)
}
}
is RealmToMigrate.DynamicRealm -> {
val pickledSessions = mutableListOf<PickledSession>()
realm.schema.get("OlmSessionEntity")?.transform {
val sessionData = it.getString(OlmSessionEntityFields.OLM_SESSION_DATA)
val deviceKey = it.getString(OlmSessionEntityFields.DEVICE_KEY)
val lastReceivedMessageTs = it.getLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS)
val olmSession = deserializeFromRealm<OlmSession>(sessionData)!!
val pickle = olmSession.pickle(pickleKey, StringBuffer()).asString()
val pickledSession = PickledSession(
pickle = pickle,
senderKey = deviceKey,
createdUsingFallbackKey = false,
// / Unix timestamp (in seconds) when the session was created.
creationTime = (lastReceivedMessageTs / 1000).toULong(),
// / Unix timestamp (in seconds) when the session was last used.
lastUseTime = (lastReceivedMessageTs / 1000).toULong(),
)
// should we check the tracking status?
pickledSessions.add(pickledSession)
if (pickledSessions.size > chunkSize) {
onChunk(pickledSessions.toImmutableList())
pickledSessions.clear()
}
}
if (pickledSessions.isNotEmpty()) {
onChunk(pickledSessions)
}
}
}
}
private val sessionDataAdapter = MoshiProvider.providesMoshi()
.adapter(InboundGroupSessionData::class.java)
fun RealmToMigrate.pickledOlmGroupSessions(pickleKey: ByteArray, chunkSize: Int, onChunk: ((List<PickledInboundGroupSession>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<OlmInboundGroupSessionEntity>()
.findAll()
.chunked(chunkSize) { chunk ->
val export = chunk.mapNotNull { it.toPickledInboundGroupSession(pickleKey) }
onChunk(export)
}
}
is RealmToMigrate.DynamicRealm -> {
val pickledSessions = mutableListOf<PickledInboundGroupSession>()
realm.schema.get("OlmInboundGroupSessionEntity")?.transform {
val senderKey = it.getString(OlmInboundGroupSessionEntityFields.SENDER_KEY)
val roomId = it.getString(OlmInboundGroupSessionEntityFields.ROOM_ID)
val backedUp = it.getBoolean(OlmInboundGroupSessionEntityFields.BACKED_UP)
val serializedOlmInboundGroupSession = it.getString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION)
val inboundSession = deserializeFromRealm<OlmInboundGroupSession>(serializedOlmInboundGroupSession) ?: return@transform Unit.also {
Timber.w("Rust db migration: Failed to migrated group session, no meta data")
}
val sessionData = it.getString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON).let { json ->
sessionDataAdapter.fromJson(json)
} ?: return@transform Unit.also {
Timber.w("Rust db migration: Failed to migrated group session, no meta data")
}
val pickle = inboundSession.pickle(pickleKey, StringBuffer()).asString()
val pickledSession = PickledInboundGroupSession(
pickle = pickle,
senderKey = senderKey,
signingKey = sessionData.keysClaimed.orEmpty(),
roomId = roomId,
forwardingChains = sessionData.forwardingCurve25519KeyChain.orEmpty(),
imported = sessionData.trusted.orFalse().not(),
backedUp = backedUp
)
// should we check the tracking status?
pickledSessions.add(pickledSession)
if (pickledSessions.size > chunkSize) {
onChunk(pickledSessions.toImmutableList())
pickledSessions.clear()
}
}
if (pickledSessions.isNotEmpty()) {
onChunk(pickledSessions)
}
}
}
}
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 = data.trusted.orFalse().not(),
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,
// Rust expect in seconds
creationTime = (lastReceivedMessageTs / 1000).toULong(),
// Rust expect in seconds
lastUseTime = (lastReceivedMessageTs / 1000).toULong(),
)
}
private val charset = Charset.forName("UTF-8")
private fun ByteArray.asString() = String(this, charset)

View File

@ -18,9 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.olm.OlmAccount
internal open class CryptoMetadataEntity( internal open class CryptoMetadataEntity(
// The current user id. // The current user id.
@ -53,15 +50,4 @@ internal open class CryptoMetadataEntity(
var keyBackupRecoveryKeyVersion: String? = null var keyBackupRecoveryKeyVersion: String? = null
// var crossSigningInfoEntity: CrossSigningInfoEntity? = null // var crossSigningInfoEntity: CrossSigningInfoEntity? = null
) : RealmObject() { ) : RealmObject()
// Deserialize data
fun getOlmAccount(): OlmAccount? {
return deserializeFromRealm(olmAccountData)
}
// Serialize data
fun putOlmAccount(olmAccount: OlmAccount?) {
olmAccountData = serializeForRealm(olmAccount)
}
}

View File

@ -19,12 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey" internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
@ -56,14 +51,6 @@ internal open class OlmInboundGroupSessionEntity(
) : ) :
RealmObject() { RealmObject() {
fun store(wrapper: MXInboundMegolmSessionWrapper) {
this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session)
this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData)
this.roomId = wrapper.sessionData.roomId
this.senderKey = wrapper.sessionData.senderKey
this.sessionId = wrapper.session.sessionIdentifier()
this.sharedHistory = wrapper.sessionData.sharedHistory
}
// fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { // fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
// return try { // return try {
// deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData) // deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
@ -77,35 +64,6 @@ internal open class OlmInboundGroupSessionEntity(
// olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper) // olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
// } // }
fun getOlmGroupSession(): OlmInboundGroupSession? {
return try {
deserializeFromRealm(serializedOlmInboundGroupSession)
} catch (failure: Throwable) {
Timber.e(failure, "## Deserialization failure")
return null
}
}
fun getData(): InboundGroupSessionData? {
return try {
inboundGroupSessionDataJson?.let {
adapter.fromJson(it)
}
} catch (failure: Throwable) {
Timber.e(failure, "## Deserialization failure")
return null
}
}
fun toModel(): MXInboundMegolmSessionWrapper? {
val data = getData() ?: return null
val session = getOlmGroupSession() ?: return null
return MXInboundMegolmSessionWrapper(
session = session,
sessionData = data
)
}
companion object { companion object {
private val adapter = MoshiProvider.providesMoshi() private val adapter = MoshiProvider.providesMoshi()
.adapter(InboundGroupSessionData::class.java) .adapter(InboundGroupSessionData::class.java)

View File

@ -18,9 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.olm.OlmSession
internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, deviceKey: String) = "$sessionId|$deviceKey" internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, deviceKey: String) = "$sessionId|$deviceKey"
@ -34,13 +31,5 @@ internal open class OlmSessionEntity(
) : ) :
RealmObject() { RealmObject() {
fun getOlmSession(): OlmSession? {
return deserializeFromRealm(olmSessionData)
}
fun putOlmSession(olmSession: OlmSession?) {
olmSessionData = serializeForRealm(olmSession)
}
companion object companion object
} }

View File

@ -17,10 +17,6 @@
package org.matrix.android.sdk.internal.crypto.store.db.model package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject import io.realm.RealmObject
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.olm.OlmOutboundGroupSession
import timber.log.Timber
internal open class OutboundGroupSessionInfoEntity( internal open class OutboundGroupSessionInfoEntity(
var serializedOutboundSessionData: String? = null, var serializedOutboundSessionData: String? = null,
@ -28,18 +24,5 @@ internal open class OutboundGroupSessionInfoEntity(
var shouldShareHistory: Boolean = false var shouldShareHistory: Boolean = false
) : RealmObject() { ) : RealmObject() {
fun getOutboundGroupSession(): OlmOutboundGroupSession? {
return try {
deserializeFromRealm(serializedOutboundSessionData)
} catch (failure: Throwable) {
Timber.e(failure, "## getOutboundGroupSession() Deserialization failure")
return null
}
}
fun putOutboundGroupSession(olmOutboundGroupSession: OlmOutboundGroupSession?) {
serializedOutboundSessionData = serializeForRealm(olmOutboundGroupSession)
}
companion object companion object
} }

View File

@ -16,43 +16,10 @@
package org.matrix.android.sdk.internal.crypto.tools package org.matrix.android.sdk.internal.crypto.tools
import org.matrix.olm.OlmPkDecryption // TODO BMA
import org.matrix.olm.OlmPkEncryption data object OlmPkEncryption
import org.matrix.olm.OlmPkSigning
import org.matrix.olm.OlmUtility
internal fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T { internal fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
val olmPkEncryption = OlmPkEncryption() val olmPkEncryption = OlmPkEncryption
try {
return block(olmPkEncryption) return block(olmPkEncryption)
} finally {
olmPkEncryption.releaseEncryption()
}
}
internal fun <T> withOlmDecryption(block: (OlmPkDecryption) -> T): T {
val olmPkDecryption = OlmPkDecryption()
try {
return block(olmPkDecryption)
} finally {
olmPkDecryption.releaseDecryption()
}
}
internal fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
val olmPkSigning = OlmPkSigning()
try {
return block(olmPkSigning)
} finally {
olmPkSigning.releaseSigning()
}
}
internal fun <T> withOlmUtility(block: (OlmUtility) -> T): T {
val olmUtility = OlmUtility()
try {
return block(olmUtility)
} finally {
olmUtility.releaseUtility()
}
} }

View File

@ -43,7 +43,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.system.SystemModule import org.matrix.android.sdk.internal.util.system.SystemModule
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.io.File import java.io.File
@Component( @Component(
@ -89,8 +88,6 @@ internal interface MatrixComponent {
@CacheDirectory @CacheDirectory
fun cacheDir(): File fun cacheDir(): File
fun olmManager(): OlmManager
fun taskExecutor(): TaskExecutor fun taskExecutor(): TaskExecutor
fun sessionParamsStore(): SessionParamsStore fun sessionParamsStore(): SessionParamsStore

View File

@ -25,7 +25,6 @@ import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.createBackgroundHandler import org.matrix.android.sdk.internal.util.createBackgroundHandler
import org.matrix.olm.OlmManager
import java.io.File import java.io.File
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -57,11 +56,4 @@ internal object MatrixModule {
fun providesCacheDir(context: Context): File { fun providesCacheDir(context: Context): File {
return context.cacheDir return context.cacheDir
} }
@JvmStatic
@Provides
@MatrixScope
fun providesOlmManager(): OlmManager {
return OlmManager()
}
} }

View File

@ -1,78 +0,0 @@
/*
* 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.DynamicRealm
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.crypto.store.db.migration.rust.ExtractMigrationDataUseCase
import org.matrix.android.sdk.internal.crypto.store.db.migration.rust.RealmToMigrate
import org.matrix.rustcomponents.sdk.crypto.ProgressListener
import timber.log.Timber
import java.io.File
class MigrateEAtoEROperation(private val migrateGroupSessions: Boolean = false) {
fun execute(cryptoRealm: RealmConfiguration, rustFilesDir: File, passphrase: String?): File {
// Temporary code for migration
if (!rustFilesDir.exists()) {
rustFilesDir.mkdir()
// perform a migration?
val extractMigrationData = ExtractMigrationDataUseCase(migrateGroupSessions)
val hasExitingData = extractMigrationData.hasExistingData(cryptoRealm)
if (!hasExitingData) return rustFilesDir
try {
val progressListener = object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
Timber.v("OnProgress: $progress/$total")
}
}
Realm.getInstance(cryptoRealm).use { realm ->
extractMigrationData.extractData(RealmToMigrate.ClassicRealm(realm)) {
org.matrix.rustcomponents.sdk.crypto.migrate(it, rustFilesDir.path, passphrase, progressListener)
}
}
} catch (failure: Throwable) {
Timber.e(failure, "Failure while calling rust migration method")
throw failure
}
}
return rustFilesDir
}
fun dynamicExecute(dynamicRealm: DynamicRealm, rustFilesDir: File, passphrase: String?) {
if (!rustFilesDir.exists()) {
rustFilesDir.mkdir()
}
val extractMigrationData = ExtractMigrationDataUseCase(migrateGroupSessions)
try {
val progressListener = object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
Timber.v("OnProgress: $progress/$total")
}
}
extractMigrationData.extractData(RealmToMigrate.DynamicRealm(dynamicRealm)) {
org.matrix.rustcomponents.sdk.crypto.migrate(it, rustFilesDir.path, passphrase, progressListener)
}
} catch (failure: Throwable) {
Timber.e(failure, "Failure while calling rust migration method")
throw failure
}
}
}

View File

@ -44,9 +44,11 @@ internal object ScanEncryptorUtils {
) )
return if (publicServerKey != null) { return if (publicServerKey != null) {
// We should encrypt // We should encrypt
withOlmEncryption { olm -> withOlmEncryption { //olm ->
// TODO BMA
error("Not supported anymore")
/*
olm.setRecipientKey(publicServerKey) olm.setRecipientKey(publicServerKey)
val olmResult = olm.encrypt(DownloadBody(encryptedInfo).toCanonicalJson()) val olmResult = olm.encrypt(DownloadBody(encryptedInfo).toCanonicalJson())
DownloadBody( DownloadBody(
encryptedBody = EncryptedBody( encryptedBody = EncryptedBody(
@ -55,6 +57,7 @@ internal object ScanEncryptorUtils {
mac = olmResult.mMac mac = olmResult.mMac
) )
) )
*/
} }
} else { } else {
DownloadBody(encryptedInfo) DownloadBody(encryptedInfo)

View File

@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.identity.FoundThreePid
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.api.session.identity.toMedium
import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpPara
import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpResponse import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpResponse
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.base64ToBase64Url import org.matrix.android.sdk.internal.util.base64ToBase64Url
import java.security.MessageDigest
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -118,15 +118,10 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
} }
private fun getHashedAddresses(threePids: List<ThreePid>, pepper: String): List<String> { private fun getHashedAddresses(threePids: List<ThreePid>, pepper: String): List<String> {
return withOlmUtility { olmUtility -> return threePids.map { threePid ->
threePids.map { threePid ->
base64ToBase64Url( base64ToBase64Url(
olmUtility.sha256( (threePid.value.lowercase(Locale.ROOT) + " " + threePid.toMedium() + " " + pepper).toSha256()
threePid.value.lowercase(Locale.ROOT) +
" " + threePid.toMedium() + " " + pepper
) )
)
}
} }
} }
@ -144,4 +139,11 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
) )
} }
} }
private val sha256 by lazy { MessageDigest.getInstance("SHA-256") }
@OptIn(ExperimentalStdlibApi::class)
private fun String.toSha256(): String {
return sha256.digest(toByteArray()).toHexString()
}
} }

View File

@ -1,83 +0,0 @@
/*
* 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.androidsdk.crypto.data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* <b>IMPORTANT:</b> This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
public class MXDeviceInfo implements Serializable {
private static final long serialVersionUID = 20129670646382964L;
// This device is a new device and the user was not warned it has been added.
public static final int DEVICE_VERIFICATION_UNKNOWN = -1;
// The user has not yet verified this device.
public static final int DEVICE_VERIFICATION_UNVERIFIED = 0;
// The user has verified this device.
public static final int DEVICE_VERIFICATION_VERIFIED = 1;
// The user has blocked this device.
public static final int DEVICE_VERIFICATION_BLOCKED = 2;
/**
* The id of this device.
*/
public String deviceId;
/**
* the user id
*/
public String userId;
/**
* The list of algorithms supported by this device.
*/
public List<String> algorithms;
/**
* A map from <key type>:<id> to <base64-encoded key>>.
*/
public Map<String, String> keys;
/**
* The signature of this MXDeviceInfo.
* A map from <key type>:<device_id> to <base64-encoded key>>.
*/
public Map<String, Map<String, String>> signatures;
/*
* Additional data from the homeserver.
*/
public Map<String, Object> unsigned;
/**
* Verification state of this device.
*/
public int mVerified;
/**
* Constructor
*/
public MXDeviceInfo() {
mVerified = DEVICE_VERIFICATION_UNKNOWN;
}
}

View File

@ -1,50 +0,0 @@
/*
* 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.androidsdk.crypto.data;
import org.matrix.olm.OlmInboundGroupSession;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <b>IMPORTANT:</b> This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*
* This class adds more context to a OLMInboundGroupSession object.
* This allows additional checks. The class implements NSCoding so that the context can be stored.
*/
public class MXOlmInboundGroupSession2 implements Serializable {
// define a serialVersionUID to avoid having to redefine the class after updates
private static final long serialVersionUID = 201702011617L;
// The associated olm inbound group session.
public OlmInboundGroupSession mSession;
// The room in which this session is used.
public String mRoomId;
// The base64-encoded curve25519 key of the sender.
public String mSenderKey;
// Other keys the sender claims.
public Map<String, String> mKeysClaimed;
// Devices which forwarded this session to us (normally empty).
public List<String> mForwardingCurve25519KeyChain = new ArrayList<>();
}