From 56b1b9dec18b51be0cafe152831c7cb3ee44580d Mon Sep 17 00:00:00 2001 From: valere Date: Tue, 10 Jan 2023 10:56:11 +0100 Subject: [PATCH] configure encryption settings from state --- .../android/sdk/api/crypto/CryptoConstants.kt | 3 ++ .../session/crypto/model/CryptoRoomInfo.kt | 33 ++++++++++++++ .../internal/crypto/store/IMXCryptoStore.kt | 5 +++ .../crypto/store/db/RealmCryptoStore.kt | 27 ++++++++++++ .../store/db/RealmCryptoStoreMigration.kt | 4 +- .../store/db/mapper/CryptoRoomInfoMapper.kt | 38 ++++++++++++++++ .../store/db/migration/MigrateCryptoTo021.kt | 43 +++++++++++++++++++ .../crypto/store/db/model/CryptoRoomEntity.kt | 7 ++- .../crypto/PrepareToEncryptUseCase.kt | 23 ++++++---- .../sdk/internal/crypto/RustCryptoService.kt | 8 ++-- 10 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoRoomInfo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CryptoRoomInfoMapper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt index 37b9ac379e..aced0ca3a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt @@ -42,3 +42,6 @@ const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2" // TODO Refacto: use this constants everywhere const val ed25519 = "ed25519" const val curve25519 = "curve25519" + +const val MEGOLM_DEFAULT_ROTATION_MSGS = 100L +const val MEGOLM_DEFAULT_ROTATION_PERIOD_MS = 7 * 24 * 3600 * 1000L diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoRoomInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoRoomInfo.kt new file mode 100644 index 0000000000..51cd811150 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoRoomInfo.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.crypto.model + +data class CryptoRoomInfo( + val algorithm: String, + val shouldEncryptForInvitedMembers: Boolean, + val blacklistUnverifiedDevices: Boolean, + // Determines whether or not room history should be shared on new member invites + val shouldShareHistory: Boolean, + // This is specific to megolm but not sure how to model it better + // a security to ensure that a room will never revert to not encrypted + // even if a new state event with empty encryption, or state is reset somehow + val wasEncryptedOnce: Boolean, + // How long the session should be used before changing it. 604800000 (a week) is the recommended default. + val rotationPeriodMs: Long, + // How many messages should be sent before changing the session. 100 is the recommended default. + val rotationPeriodMsgs: Long, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 881b456c48..a13c15bd1a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -28,11 +28,13 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +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.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.crypto.model.TrailType import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional @@ -284,6 +286,9 @@ internal interface IMXCryptoStore { */ fun getRoomAlgorithm(roomId: String): String? + fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? + fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) + /** * This is a bit different than isRoomEncrypted. * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not). diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index c3dfffb181..30a33c8810 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +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.ForwardInfo import org.matrix.android.sdk.api.session.crypto.model.IncomingKeyRequestInfo @@ -48,6 +49,7 @@ import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.crypto.model.TrailType import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional @@ -58,6 +60,7 @@ import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import org.matrix.android.sdk.internal.crypto.store.db.mapper.CryptoRoomInfoMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields @@ -714,6 +717,30 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? { + return doWithRealm(realmConfiguration) { realm -> + CryptoRoomEntity.getById(realm, roomId)?.let { + CryptoRoomInfoMapper.map(it) + } + } + } + + override fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) { + doRealmTransaction("setAlgorithmInfo", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> + entity.algorithm = encryption?.algorithm + // store anyway the new algorithm, but mark the room + // as having been encrypted once whatever, this can never + // go back to false + if (encryption?.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { + entity.wasEncryptedOnce = true + entity.rotationPeriodMs = encryption.rotationPeriodMs + entity.rotationPeriodMsgs = encryption.rotationPeriodMsgs + } + } + } + } + override fun roomWasOnceEncrypted(roomId: String): Boolean { return doWithRealm(realmConfiguration) { CryptoRoomEntity.getById(it, roomId)?.wasEncryptedOnce ?: false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 9129453c8a..c1aeff368f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo 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.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject @@ -51,7 +52,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( private val clock: Clock, ) : MatrixRealmMigration( dbName = "Crypto", - schemaVersion = 20L, + schemaVersion = 21L, ) { /** * Forces all RealmCryptoStoreMigration instances to be equal. @@ -81,5 +82,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 18) MigrateCryptoTo018(realm).perform() if (oldVersion < 19) MigrateCryptoTo019(realm).perform() if (oldVersion < 20) MigrateCryptoTo020(realm).perform() + if (oldVersion < 21) MigrateCryptoTo021(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CryptoRoomInfoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CryptoRoomInfoMapper.kt new file mode 100644 index 0000000000..ef4d30ad42 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CryptoRoomInfoMapper.kt @@ -0,0 +1,38 @@ +/* + * 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.mapper + +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.api.session.crypto.model.CryptoRoomInfo +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity + +internal object CryptoRoomInfoMapper { + + fun map(entity: CryptoRoomEntity): CryptoRoomInfo? { + val algorithm = entity.algorithm ?: return null + return CryptoRoomInfo( + algorithm = algorithm, + shouldEncryptForInvitedMembers = entity.shouldEncryptForInvitedMembers ?: false, + blacklistUnverifiedDevices = entity.blacklistUnverifiedDevices, + shouldShareHistory = entity.shouldShareHistory, + wasEncryptedOnce = entity.wasEncryptedOnce ?: false, + rotationPeriodMsgs = entity.rotationPeriodMsgs ?: MEGOLM_DEFAULT_ROTATION_MSGS, + rotationPeriodMs = entity.rotationPeriodMs ?: MEGOLM_DEFAULT_ROTATION_PERIOD_MS + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt new file mode 100644 index 0000000000..7284d9df50 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo021.kt @@ -0,0 +1,43 @@ +/* + * 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) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt index be57586163..dce47860c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt @@ -32,7 +32,12 @@ internal open class CryptoRoomEntity( var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null, // a security to ensure that a room will never revert to not encrypted // even if a new state event with empty encryption, or state is reset somehow - var wasEncryptedOnce: Boolean? = false + var wasEncryptedOnce: Boolean? = false, + + // How long the session should be used before changing it. 604800000 (a week) is the recommended default. + var rotationPeriodMs: Long? = null, + // How many messages should be sent before changing the session. 100 is the recommended default. + var rotationPeriodMsgs: Long? = null, ) : RealmObject() { diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt index 92fdebf5bd..bd55257a44 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService @@ -49,7 +50,8 @@ internal class PrepareToEncryptUseCase @Inject constructor( private val getRoomUserIds: GetRoomUserIdsUseCase, private val requestSender: RequestSender, private val loadRoomMembersTask: LoadRoomMembersTask, - private val keysBackupService: RustKeyBackupService + private val keysBackupService: RustKeyBackupService, + private val shouldEncryptForInvitedMembers: ShouldEncryptForInvitedMembersUseCase, ) { private val keyClaimLock: Mutex = Mutex() @@ -87,17 +89,20 @@ internal class PrepareToEncryptUseCase @Inject constructor( val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() } var sharedKey = false - cryptoStore.getBlockUnverifiedDevices(roomId) - cryptoStore.shouldShareHistory(roomId) + val info = cryptoStore.getRoomCryptoInfo(roomId) + ?: throw java.lang.IllegalArgumentException("Encryption not configured in this room") + // how to react if this is null?? + if (info.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) { + throw java.lang.IllegalArgumentException("Unsupported algorithm ${info.algorithm}") + } val settings = EncryptionSettings( algorithm = EventEncryptionAlgorithm.MEGOLM_V1_AES_SHA2, - onlyAllowTrustedDevices = cryptoStore.getBlockUnverifiedDevices(roomId), - // TODO should take that from m.room.encryption event - rotationPeriod = (7 * 24 * 3600 * 1000).toULong(), - rotationPeriodMsgs = 100UL, - historyVisibility = if (cryptoStore.shouldShareHistory(roomId)) { + onlyAllowTrustedDevices = info.blacklistUnverifiedDevices, + rotationPeriod = info.rotationPeriodMs.toULong(), + rotationPeriodMsgs = info.rotationPeriodMsgs.toULong(), + historyVisibility = if (info.shouldShareHistory) { HistoryVisibility.SHARED - } else if (cryptoStore.shouldEncryptForInvitedMembers(roomId)) { + } else if (shouldEncryptForInvitedMembers.invoke(roomId)) { HistoryVisibility.INVITED } else { HistoryVisibility.JOINED diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index c5cf779f93..693ea14ba2 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -57,6 +57,7 @@ import org.matrix.android.sdk.api.session.crypto.model.TrailType import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent @@ -384,12 +385,13 @@ internal class RustCryptoService @Inject constructor( */ private suspend fun setEncryptionInRoom( roomId: String, - algorithm: String?, + info: EncryptionEventContent?, membersId: List ): Boolean { // If we already have encryption in this room, we should ignore this event // (for now at least. Maybe we should alert the user somehow?) val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId) + val algorithm = info?.algorithm if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) { Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId") @@ -397,7 +399,7 @@ internal class RustCryptoService @Inject constructor( } // TODO CHECK WITH VALERE - cryptoStore.storeRoomAlgorithm(roomId, algorithm) + cryptoStore.setAlgorithmInfo(roomId, info) if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM) { Timber.tag(loggerTag.value).e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm") @@ -508,7 +510,7 @@ internal class RustCryptoService @Inject constructor( // Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") // } finally { val userIds = getRoomUserIds(roomId) - setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds) + setEncryptionInRoom(roomId, event.content?.toModel(), userIds) // } // } }