mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
4 Commits
v1.6.46
...
feature/bc
Author | SHA1 | Date | |
---|---|---|---|
|
3fa1560839 | ||
|
1fc410a442 | ||
|
3951499530 | ||
|
240fe6d43e |
2
changelog.d/4459.misc
Normal file
2
changelog.d/4459.misc
Normal file
@@ -0,0 +1,2 @@
|
||||
Improve group key sharing speed for huge groups
|
||||
Improve e2e logging
|
@@ -1315,7 +1315,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}.fold(
|
||||
{ callback.onSuccess(Unit) },
|
||||
{
|
||||
Timber.e("## CRYPTO | prepareToEncrypt() failed.")
|
||||
Timber.e(it, "## CRYPTO | prepareToEncrypt() failed.")
|
||||
callback.onFailure(it)
|
||||
}
|
||||
)
|
||||
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.toDebugString
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SimpleSendToDeviceWorker(context: Context,
|
||||
params: WorkerParameters) :
|
||||
SessionSafeCoroutineWorker<SimpleSendToDeviceWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val eventType: String,
|
||||
val contentMap: Map<String /* userId */, Map<String /* deviceId */, Any>>,
|
||||
val txnId: String? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
// params.txnId should be provided in all cases now. But Params can be deserialized by
|
||||
// the WorkManager from data serialized in a previous version of the application, so without the txnId field.
|
||||
// So if not present, we create a txnId
|
||||
val txnId = params.txnId ?: createUniqueTxnId()
|
||||
val eventType: String = params.eventType
|
||||
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>().apply { addEntriesFromRawMap(params.contentMap) }
|
||||
|
||||
try {
|
||||
Timber.d("## CRYPTO | Worker shareUserDevicesKey() $txnId: Sharing megolm session with ${sendToDeviceMap.toDebugString()} ")
|
||||
sendToDeviceTask.execute(
|
||||
SendToDeviceTask.Params(
|
||||
eventType = eventType,
|
||||
contentMap = sendToDeviceMap,
|
||||
transactionId = txnId
|
||||
)
|
||||
)
|
||||
Timber.d("## CRYPTO | Worker shareUserDevicesKey() $txnId ... success")
|
||||
return Result.success()
|
||||
} catch (throwable: Throwable) {
|
||||
return if (throwable.shouldBeRetried()) {
|
||||
Timber.d("## CRYPTO | Worker shareUserDevicesKey() $txnId ... schedule retry")
|
||||
Result.retry()
|
||||
} else {
|
||||
Timber.d("## CRYPTO | Worker shareUserDevicesKey() $txnId ... failure")
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
@@ -21,6 +21,8 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXKey
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.forEach
|
||||
import org.matrix.android.sdk.internal.crypto.model.toDebugString
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -33,29 +35,37 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
||||
|
||||
suspend fun handle(devicesByUser: Map<String, List<CryptoDeviceInfo>>, force: Boolean = false): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||
val devicesWithoutSession = ArrayList<CryptoDeviceInfo>()
|
||||
|
||||
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||
val devicesWithSession = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||
|
||||
for ((userId, deviceInfos) in devicesByUser) {
|
||||
for (deviceInfo in deviceInfos) {
|
||||
val deviceId = deviceInfo.deviceId
|
||||
val key = deviceInfo.identityKey()
|
||||
|
||||
val sessionId = olmDevice.getSessionId(key!!)
|
||||
|
||||
if (sessionId.isNullOrEmpty() || force) {
|
||||
devicesWithoutSession.add(deviceInfo)
|
||||
if (key == null) {
|
||||
Timber.w("## CRYPTO | Ignoring device (${deviceInfo.userId}|$deviceId) without identity key")
|
||||
continue
|
||||
}
|
||||
|
||||
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
|
||||
results.setObject(userId, deviceId, olmSessionResult)
|
||||
val sessionId = olmDevice.getSessionId(key)
|
||||
|
||||
if (sessionId.isNullOrEmpty() || force) {
|
||||
Timber.d("## CRYPTO | Found no existing olm session (${deviceInfo.userId}|$deviceId) (force=$force)")
|
||||
devicesWithoutSession.add(deviceInfo)
|
||||
} else {
|
||||
Timber.d("## CRYPTO | using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
|
||||
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
|
||||
devicesWithSession.setObject(userId, deviceId, olmSessionResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timber.i("## CRYPTO | Devices without olm session (count:${devicesWithoutSession.size}) :" +
|
||||
" ${devicesWithoutSession.joinToString { "${it.userId}|${it.deviceId}" }}")
|
||||
if (devicesWithoutSession.size == 0) {
|
||||
return results
|
||||
return devicesWithSession
|
||||
}
|
||||
|
||||
// Let's try to setup olm sessions with the other devices
|
||||
// Prepare the request for claiming one-time keys
|
||||
val usersDevicesToClaim = MXUsersDevicesMap<String>()
|
||||
|
||||
@@ -68,41 +78,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
||||
// TODO: this has a race condition - if we try to send another message
|
||||
// while we are claiming a key, we will end up claiming two and setting up
|
||||
// two sessions.
|
||||
//
|
||||
// That should eventually resolve itself, but it's poor form.
|
||||
|
||||
Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
||||
Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : ${usersDevicesToClaim.toDebugString()}")
|
||||
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT)
|
||||
Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||
for ((userId, deviceInfos) in devicesByUser) {
|
||||
for (deviceInfo in deviceInfos) {
|
||||
var oneTimeKey: MXKey? = null
|
||||
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
|
||||
if (null != deviceIds) {
|
||||
for (deviceId in deviceIds) {
|
||||
val olmSessionResult = results.getObject(userId, deviceId)
|
||||
if (olmSessionResult!!.sessionId != null && !force) {
|
||||
// We already have a result for this device
|
||||
continue
|
||||
}
|
||||
val key = oneTimeKeys.getObject(userId, deviceId)
|
||||
if (key?.type == oneTimeKeyAlgorithm) {
|
||||
oneTimeKey = key
|
||||
}
|
||||
if (oneTimeKey == null) {
|
||||
Timber.w("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm +
|
||||
" for device " + userId + " : " + deviceId)
|
||||
continue
|
||||
}
|
||||
// Update the result for this device in results
|
||||
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
|
||||
|
||||
// We iterate through claimed info to log missing otks
|
||||
usersDevicesToClaim.forEach { userId, deviceId, _ ->
|
||||
val foundOtk = oneTimeKeys.getObject(userId, deviceId)
|
||||
if (foundOtk == null) {
|
||||
Timber.d("## CRYPTO: No one time key for $userId|$deviceId")
|
||||
} else if (foundOtk.type == oneTimeKeyAlgorithm) {
|
||||
devicesByUser[userId]?.firstOrNull { it.deviceId == deviceId }?.let { cryptoDeviceInfo ->
|
||||
Timber.d("## CRYPTO | creating outbound session for $userId|$deviceId ...")
|
||||
val createdSession = verifyKeyAndStartSession(foundOtk, userId, cryptoDeviceInfo)
|
||||
if (createdSession != null) {
|
||||
Timber.d("## CRYPTO | ... created outbound session $createdSession for $userId|$deviceId")
|
||||
devicesWithSession.setObject(userId, deviceId, MXOlmSessionResult(cryptoDeviceInfo, createdSession))
|
||||
} else {
|
||||
Timber.d("## CRYPTO | ... Failed to create outbound session for $userId|$deviceId")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Skipping this key
|
||||
Timber.i("## CRYPTO | skipping otk for $userId|$deviceId because unsupported algorithm")
|
||||
}
|
||||
}
|
||||
return results
|
||||
|
||||
return devicesWithSession
|
||||
}
|
||||
|
||||
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {
|
||||
@@ -112,27 +117,34 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
||||
val signKeyId = "ed25519:$deviceId"
|
||||
val signature = oneTimeKey.signatureForUserId(userId, signKeyId)
|
||||
|
||||
if (!signature.isNullOrEmpty() && !deviceInfo.fingerprint().isNullOrEmpty()) {
|
||||
val fingerprint = deviceInfo.fingerprint()
|
||||
if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) {
|
||||
var isVerified = false
|
||||
var errorMessage: String? = null
|
||||
|
||||
try {
|
||||
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
|
||||
olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
|
||||
isVerified = true
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e, "## CRYPTO | verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
|
||||
" signature:$signature fingerprint:$fingerprint")
|
||||
Timber.e("## CRYPTO | verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
|
||||
" - signable json ${oneTimeKey.signalableJSONDictionary()}")
|
||||
errorMessage = e.message
|
||||
}
|
||||
|
||||
// Check one-time key signature
|
||||
if (isVerified) {
|
||||
sessionId = olmDevice.createOutboundSession(deviceInfo.identityKey()!!, oneTimeKey.value)
|
||||
sessionId = deviceInfo.identityKey()?.let { identityKey ->
|
||||
olmDevice.createOutboundSession(identityKey, oneTimeKey.value)
|
||||
}
|
||||
|
||||
if (!sessionId.isNullOrEmpty()) {
|
||||
Timber.v("## CRYPTO | verifyKeyAndStartSession() : Started new sessionid " + sessionId +
|
||||
" for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
|
||||
} else {
|
||||
if (sessionId.isNullOrEmpty()) {
|
||||
// Possibly a bad key
|
||||
Timber.e("## CRYPTO | verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
|
||||
} else {
|
||||
Timber.v("## CRYPTO | verifyKeyAndStartSession() : Started new sessionid " + sessionId +
|
||||
" for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
|
||||
}
|
||||
} else {
|
||||
Timber.e("## CRYPTO | verifyKeyAndStartSession() : Unable to verify signature on one-time key for device " + userId +
|
||||
|
@@ -335,7 +335,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||
.mapCatching {
|
||||
val deviceId = request.deviceId
|
||||
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "")
|
||||
val deviceInfo = it.getObject(userId, deviceId) // cryptoStore.getUserDevice(userId, deviceId ?: "")
|
||||
if (deviceInfo == null) {
|
||||
throw RuntimeException()
|
||||
} else {
|
||||
|
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||
|
||||
import androidx.work.BackoffPolicy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
@@ -26,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||
import org.matrix.android.sdk.internal.crypto.SimpleSendToDeviceWorker
|
||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
||||
@@ -36,28 +40,38 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||
import org.matrix.android.sdk.internal.crypto.model.forEach
|
||||
import org.matrix.android.sdk.internal.crypto.model.toDebugCount
|
||||
import org.matrix.android.sdk.internal.crypto.model.toDebugString
|
||||
import org.matrix.android.sdk.internal.crypto.model.toShortDebugString
|
||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||
import org.matrix.android.sdk.internal.util.convertToUTF8
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.startChain
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
internal class MXMegolmEncryption(
|
||||
// The id of the room we will be sending to.
|
||||
private val roomId: String,
|
||||
private val matrixSessionId: String,
|
||||
private val olmDevice: MXOlmDevice,
|
||||
private val defaultKeysBackupService: DefaultKeysBackupService,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val deviceListManager: DeviceListManager,
|
||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||
private val userId: String,
|
||||
private val deviceId: String,
|
||||
private val myUserId: String,
|
||||
private val myDeviceId: String,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val messageEncrypter: MessageEncrypter,
|
||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val workManagerProvider: WorkManagerProvider
|
||||
) : IMXEncrypting, IMXGroupEncryption {
|
||||
|
||||
// OutboundSessionInfo. Null if we haven't yet started setting one up. Note
|
||||
@@ -80,9 +94,8 @@ internal class MXMegolmEncryption(
|
||||
eventType: String,
|
||||
userIds: List<String>): Content {
|
||||
val ts = System.currentTimeMillis()
|
||||
Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
|
||||
val devices = getDevicesInRoom(userIds)
|
||||
Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
|
||||
Timber.d("## CRYPTO | encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
|
||||
val outboundSession = ensureOutboundSession(devices.allowedDevices)
|
||||
|
||||
return encryptContent(outboundSession, eventType, eventContent)
|
||||
@@ -91,7 +104,7 @@ internal class MXMegolmEncryption(
|
||||
// annoyingly we have to serialize again the saved outbound session to store message index :/
|
||||
// if not we would see duplicate message index errors
|
||||
olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
|
||||
Timber.v("## CRYPTO | encryptEventContent: Finished in ${System.currentTimeMillis() - ts} millis")
|
||||
Timber.v("## CRYPTO | encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +166,14 @@ internal class MXMegolmEncryption(
|
||||
* @param devicesInRoom the devices list
|
||||
*/
|
||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
|
||||
Timber.v("## CRYPTO | ensureOutboundSession start")
|
||||
Timber.v("## CRYPTO | ensureOutboundSession roomId:$roomId")
|
||||
var session = outboundSession
|
||||
if (session == null ||
|
||||
// Need to make a brand new session?
|
||||
session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
|
||||
// Determine if we have shared with anyone we shouldn't have
|
||||
session.sharedWithTooManyDevices(devicesInRoom)) {
|
||||
Timber.d("## CRYPTO | roomId:$roomId Starting new megolm session because we need to rotate.")
|
||||
session = prepareNewSessionInRoom()
|
||||
outboundSession = session
|
||||
}
|
||||
@@ -176,6 +190,8 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
}
|
||||
}
|
||||
val devicesCount = shareMap.entries.fold(0) { acc, new -> acc + new.value.size }
|
||||
Timber.d("## CRYPTO | roomId:$roomId found $devicesCount devices without megolm session(${session.sessionId})")
|
||||
shareKey(safeSession, shareMap)
|
||||
return safeSession
|
||||
}
|
||||
@@ -193,20 +209,15 @@ internal class MXMegolmEncryption(
|
||||
Timber.v("## CRYPTO | shareKey() : nothing more to do")
|
||||
return
|
||||
}
|
||||
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
||||
val subMap = HashMap<String, List<CryptoDeviceInfo>>()
|
||||
var devicesCount = 0
|
||||
for ((userId, devices) in devicesByUsers) {
|
||||
subMap[userId] = devices
|
||||
devicesCount += devices.size
|
||||
if (devicesCount > 100) {
|
||||
break
|
||||
}
|
||||
}
|
||||
Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
|
||||
shareUserDevicesKey(session, subMap)
|
||||
val remainingDevices = devicesByUsers - subMap.keys
|
||||
shareKey(session, remainingDevices)
|
||||
|
||||
devicesByUsers
|
||||
.flatMap { it.value }
|
||||
.chunked(100)
|
||||
.forEach { devices ->
|
||||
Timber.d("## CRYPTO | share-megolm-key slice(${devices.size}) for room:$roomId session:${session.sessionId} " +
|
||||
"for ${devices.joinToString { "${it.userId}|${it.deviceId}" }}")
|
||||
shareUserDevicesKey(session, devices.groupBy { it.userId })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,25 +252,22 @@ internal class MXMegolmEncryption(
|
||||
)
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
var haveTargets = false
|
||||
val userIds = results.userIds
|
||||
val noOlmToNotify = mutableListOf<UserDevice>()
|
||||
for (userId in userIds) {
|
||||
val devicesToShareWith = devicesByUser[userId]
|
||||
for ((deviceID) in devicesToShareWith!!) {
|
||||
val sessionResult = results.getObject(userId, deviceID)
|
||||
if (sessionResult?.sessionId == null) {
|
||||
// no session with this device, probably because there
|
||||
// were no one-time keys.
|
||||
|
||||
devicesByUser.forEach { entry ->
|
||||
val userId = entry.key
|
||||
entry.value.forEach { deviceInfo ->
|
||||
val olmSession = results.getObject(userId, deviceInfo.deviceId)
|
||||
if (olmSession?.sessionId == null) {
|
||||
Timber.i("## CRYPTO | can't share megolm ${session.sessionId} with ${deviceInfo.toShortDebugString()}, no olm session")
|
||||
// MSC 2399
|
||||
// send withheld m.no_olm: an olm session could not be established.
|
||||
// This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.
|
||||
noOlmToNotify.add(UserDevice(userId, deviceID))
|
||||
continue
|
||||
noOlmToNotify.add(UserDevice(userId, deviceInfo.deviceId))
|
||||
} else {
|
||||
contentMap.setObject(userId, deviceInfo.deviceId, messageEncrypter.encryptMessage(payload, listOf(deviceInfo)))
|
||||
haveTargets = true
|
||||
}
|
||||
Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
|
||||
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
|
||||
haveTargets = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +283,7 @@ internal class MXMegolmEncryption(
|
||||
gossipingEventBuffer.add(
|
||||
Event(
|
||||
type = EventType.ROOM_KEY,
|
||||
senderId = this.userId,
|
||||
senderId = this.myUserId,
|
||||
content = submap.apply {
|
||||
this["session_key"] = ""
|
||||
// we add a fake key for trail
|
||||
@@ -290,13 +298,36 @@ internal class MXMegolmEncryption(
|
||||
if (haveTargets) {
|
||||
t0 = System.currentTimeMillis()
|
||||
Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||
try {
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
} catch (failure: Throwable) {
|
||||
// What to do here...
|
||||
Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
|
||||
Timber.d("## CRYPTO | sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
|
||||
withContext(Dispatchers.IO) {
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||
try {
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
|
||||
Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session: Scheduling worker for later delivery")
|
||||
// As we mark those session as shared, to avoid retry for every next message, it could be nice here to create a worker
|
||||
// fallback to ensure later delivery in case of any network issue?
|
||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<SimpleSendToDeviceWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.startChain(true)
|
||||
.setInputData(
|
||||
WorkerParamsFactory.toData(
|
||||
SimpleSendToDeviceWorker.Params(
|
||||
sessionId = matrixSessionId,
|
||||
eventType = EventType.ENCRYPTED,
|
||||
contentMap = contentMap.map,
|
||||
txnId = UUID.randomUUID().toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
.let {
|
||||
workManagerProvider.workManager.enqueue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
|
||||
@@ -317,7 +348,8 @@ internal class MXMegolmEncryption(
|
||||
sessionId: String,
|
||||
senderKey: String?,
|
||||
code: WithHeldCode) {
|
||||
Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
|
||||
Timber.d("## CRYPTO | notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
|
||||
" ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
|
||||
val withHeldContent = RoomKeyWithHeldContent(
|
||||
roomId = roomId,
|
||||
senderKey = senderKey,
|
||||
@@ -363,7 +395,7 @@ internal class MXMegolmEncryption(
|
||||
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
map["device_id"] = deviceId
|
||||
map["device_id"] = myDeviceId
|
||||
session.useCount++
|
||||
return map
|
||||
}
|
||||
@@ -447,7 +479,7 @@ internal class MXMegolmEncryption(
|
||||
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
|
||||
olmSessionResult?.sessionId // no session with this device, probably because there were no one-time keys.
|
||||
// ensureOlmSessionsForDevicesAction has already done the logging, so just skip it.
|
||||
// ensureOlmSessionsForDevicesAction has already done the logging, so just skip it.
|
||||
?: return false.also {
|
||||
Timber.w("## Crypto reshareKey: no session with this device, probably because there were no one-time keys")
|
||||
}
|
||||
|
@@ -27,7 +27,9 @@ import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepo
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MXMegolmEncryptionFactory @Inject constructor(
|
||||
@@ -38,27 +40,31 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
|
||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||
@UserId private val userId: String,
|
||||
@DeviceId private val deviceId: String?,
|
||||
@SessionId private val sessionId: String,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val messageEncrypter: MessageEncrypter,
|
||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val cryptoCoroutineScope: CoroutineScope) {
|
||||
|
||||
fun create(roomId: String): MXMegolmEncryption {
|
||||
return MXMegolmEncryption(
|
||||
roomId = roomId,
|
||||
matrixSessionId = sessionId,
|
||||
olmDevice = olmDevice,
|
||||
defaultKeysBackupService = defaultKeysBackupService,
|
||||
cryptoStore = cryptoStore,
|
||||
deviceListManager = deviceListManager,
|
||||
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
|
||||
userId = userId,
|
||||
deviceId = deviceId!!,
|
||||
myUserId = userId,
|
||||
myDeviceId = deviceId!!,
|
||||
sendToDeviceTask = sendToDeviceTask,
|
||||
messageEncrypter = messageEncrypter,
|
||||
warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
cryptoCoroutineScope = cryptoCoroutineScope
|
||||
cryptoCoroutineScope = cryptoCoroutineScope,
|
||||
workManagerProvider = workManagerProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -75,3 +75,5 @@ data class CryptoDeviceInfo(
|
||||
internal fun CryptoDeviceInfo.toRest(): DeviceKeys {
|
||||
return CryptoInfoMapper.map(this)
|
||||
}
|
||||
|
||||
internal fun CryptoDeviceInfo.toShortDebugString() = "$userId|$deviceId"
|
||||
|
@@ -115,6 +115,12 @@ class MXUsersDevicesMap<E> {
|
||||
}
|
||||
}
|
||||
|
||||
fun addEntriesFromRawMap(other: Map<String /* userId */, Map<String /* deviceId */, E>>) {
|
||||
other.forEach { (userID, deviceList) ->
|
||||
setObjects(userID, deviceList)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MXUsersDevicesMap $map"
|
||||
}
|
||||
@@ -129,3 +135,11 @@ inline fun <T> MXUsersDevicesMap<T>.forEach(action: (String, String, T) -> Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker
|
||||
import org.matrix.android.sdk.internal.crypto.CryptoModule
|
||||
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
|
||||
import org.matrix.android.sdk.internal.crypto.SendGossipWorker
|
||||
import org.matrix.android.sdk.internal.crypto.SimpleSendToDeviceWorker
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
|
||||
import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
|
||||
import org.matrix.android.sdk.internal.di.MatrixComponent
|
||||
@@ -142,6 +143,8 @@ internal interface SessionComponent {
|
||||
|
||||
fun inject(worker: UpdateTrustWorker)
|
||||
|
||||
fun inject(worker: SimpleSendToDeviceWorker)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
|
@@ -46,6 +46,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
|
||||
event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
|
||||
Timber.e("## CRYPTO | handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
|
||||
} else {
|
||||
Timber.i("## CRYPTO | Decrypted to device event from ${event.senderId} of type:${event.type}")
|
||||
verificationService.onToDeviceEvent(event)
|
||||
cryptoService.onToDeviceEvent(event)
|
||||
}
|
||||
|
Reference in New Issue
Block a user