Skip to content

Commit

Permalink
Fix Element FS
Browse files Browse the repository at this point in the history
  • Loading branch information
yostyle committed Sep 27, 2024
1 parent 1b813f5 commit 0724f27
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 0 deletions.
3 changes: 3 additions & 0 deletions matrix-sdk-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ dependencies {
// Work
implementation libs.androidx.work

// olm lib is now hosted in MavenCentral
implementation 'org.matrix.android:olm-sdk:3.2.12'

// DI
implementation libs.dagger.dagger
kapt libs.dagger.daggerCompiler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.StreamEventsManager
Expand Down Expand Up @@ -693,6 +694,29 @@ internal class RustCryptoService @Inject constructor(
return olmMachine.exportKeys(password, iterationCount)
}

/**
* Migrate the crypto keys from realm to rust crypto database.
*
* @param password the password
* @param anIterationCount the encryption iteration count (0 means no encryption)
*/
private suspend fun migrateRoomKeys(password: String, anIterationCount: Int) {
return withContext(coroutineDispatchers.crypto) {
val iterationCount = max(0, anIterationCount)

val exportedSessions = cryptoStore.getInboundGroupSessions().mapNotNull { it.exportKeys() }

val adapter = MoshiProvider.providesMoshi()
.adapter(List::class.java)

// Export keys from realm
val roomKeys = MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)

// Import keys to rust crypto database
importRoomKeys(roomKeysAsArray = roomKeys, password = password, progressListener = null)
}
}

override fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean) {
cryptoStore.blockUnverifiedDevicesInRoom(roomId, block)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 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.events.model.content.EncryptionEventContent
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

/**
Expand All @@ -34,6 +35,13 @@ import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
*/
interface IMXCommonCryptoStore {

/**
* Retrieve the known inbound group sessions.
*
* @return the list of all known group sessions, to export them.
*/
fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper>

/**
* Provides the algorithm used in a dedicated room.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ 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.toOptional
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.doRealmTransaction
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync
Expand All @@ -46,6 +47,7 @@ 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.MyDeviceLastSeenInfoEntity
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.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.di.CryptoDatabase
Expand Down Expand Up @@ -150,6 +152,18 @@ internal class RustCryptoStore @Inject constructor(
}
}

/**
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management.
*/
override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> {
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.findAll()
.mapNotNull { it.toModel() }
}
}

override fun getRoomAlgorithm(roomId: String): String? {
return doWithRealm(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)?.algorithm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package org.matrix.android.sdk.internal.crypto.store.db

import android.util.Base64
import io.realm.Realm
import io.realm.RealmConfiguration
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import kotlin.system.measureTimeMillis

/**
Expand Down Expand Up @@ -46,3 +51,38 @@ internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, act
realm.executeTransactionAsync { action.invoke(it) }
}
}

/**
* Serialize any Serializable object, zip it and convert to Base64 String.
*/
internal fun serializeForRealm(o: Any?): String? {
if (o == null) {
return null
}

val baos = ByteArrayOutputStream()
val gzis = GZIPOutputStream(baos)
val out = ObjectOutputStream(gzis)
out.use {
it.writeObject(o)
}
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}

/**
* Do the opposite of serializeForRealm.
*/
@Suppress("UNCHECKED_CAST")
internal fun <T> deserializeFromRealm(string: String?): T? {
if (string == null) {
return null
}
val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT)

val bais = decodedB64.inputStream()
val gzis = GZIPInputStream(bais)
val ois = SafeObjectInputStream(gzis)
return ois.use {
it.readObject() as T
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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

import java.io.IOException
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectStreamClass

/**
* Package has been renamed from `im.vector.matrix.android` to `org.matrix.android.sdk`
* so ensure deserialization of previously stored objects still works
*
* Ref: https://stackoverflow.com/questions/3884492/how-can-i-change-package-for-a-bunch-of-java-serializable-classes
*/
internal class SafeObjectInputStream(inputStream: InputStream) : ObjectInputStream(inputStream) {

init {
enableResolveObject(true)
}

@Throws(IOException::class, ClassNotFoundException::class)
override fun readClassDescriptor(): ObjectStreamClass {
val read = super.readClassDescriptor()
if (read.name.startsWith("im.vector.matrix.android.")) {
return ObjectStreamClass.lookup(Class.forName(read.name.replace("im.vector.matrix.android.", "org.matrix.android.sdk.")))
}
return read
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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.model

import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
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.olm.OlmInboundGroupSession
import timber.log.Timber

internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"

internal open class OlmInboundGroupSessionEntity(
// Combined value to build a primary key
@PrimaryKey var primaryKey: String? = null,

// denormalization for faster querying (these fields are in the inboundGroupSessionDataJson)
var sessionId: String? = null,
var senderKey: String? = null,
var roomId: String? = null,

// Deprecated, used for migration / olmInboundGroupSessionData contains Json
// keep it in case of problem to have a chance to recover
var olmInboundGroupSessionData: String? = null,

// Stores the session data in an extensible format
// to allow to store data not yet supported for later use
var inboundGroupSessionDataJson: String? = null,

// The pickled session
var serializedOlmInboundGroupSession: String? = null,

// Flag that indicates whether or not the current inboundSession will be shared to
// invited users to decrypt past messages
var sharedHistory: Boolean = false,
// Indicate if the key has been backed up to the homeserver
var backedUp: Boolean = false
) :
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? {
// return try {
// deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
// } catch (failure: Throwable) {
// Timber.e(failure, "## Deserialization failure")
// return null
// }
// }
//
// fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
// 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 {
private val adapter = MoshiProvider.providesMoshi()
.adapter(InboundGroupSessionData::class.java)
}
}

0 comments on commit 0724f27

Please sign in to comment.