configure encryption settings from state

This commit is contained in:
valere 2023-01-10 10:56:11 +01:00
parent 13d3f4f1a7
commit 56b1b9dec1
10 changed files with 177 additions and 14 deletions

View file

@ -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

View file

@ -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,
)

View file

@ -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).

View file

@ -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

View file

@ -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()
}
}

View file

@ -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
)
}
}

View file

@ -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)
}
}
}

View file

@ -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() {

View file

@ -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

View file

@ -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<String>
): 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<EncryptionEventContent>(), userIds)
// }
// }
}