BayernMessenger/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt

362 lines
17 KiB
Kotlin
Raw Normal View History

2019-05-16 08:23:57 +00:00
/*
* Copyright 2016 OpenMarket Ltd
* Copyright 2018 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils
import arrow.core.Try
2019-05-16 08:23:57 +00:00
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.*
2019-05-20 08:30:33 +00:00
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
2019-05-16 08:23:57 +00:00
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
2019-05-20 08:30:33 +00:00
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
2019-05-16 08:23:57 +00:00
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
2019-05-16 12:18:56 +00:00
import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent
2019-05-16 08:23:57 +00:00
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
2019-05-16 12:18:56 +00:00
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
2019-05-16 08:23:57 +00:00
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
2019-05-16 08:23:57 +00:00
import timber.log.Timber
import kotlin.collections.HashMap
2019-05-16 08:23:57 +00:00
internal class MXMegolmDecryption(private val credentials: Credentials,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers)
2019-05-20 08:30:33 +00:00
: IMXDecrypting {
2019-05-16 08:23:57 +00:00
2019-07-02 14:03:32 +00:00
var newSessionListener: NewSessionListener? = null
2019-05-16 08:23:57 +00:00
/**
* Events which we couldn't decrypt due to unknown sessions / indexes: map from
* senderKey|sessionId to timelines to list of MatrixEvents.
*/
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
2019-05-16 08:23:57 +00:00
2019-07-05 12:28:28 +00:00
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
2019-05-16 08:23:57 +00:00
return decryptEvent(event, timeline, true)
}
2019-07-05 12:28:28 +00:00
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
2019-07-05 14:11:54 +00:00
if (event.roomId.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
}
2019-07-05 12:28:28 +00:00
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
2019-07-05 13:52:37 +00:00
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
2019-05-16 08:23:57 +00:00
2019-07-05 14:11:54 +00:00
if (encryptedEventContent.senderKey.isNullOrBlank()
|| encryptedEventContent.sessionId.isNullOrBlank()
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
2019-07-05 13:52:37 +00:00
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
2019-05-16 08:23:57 +00:00
}
2019-07-09 13:26:32 +00:00
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
event.roomId,
timeline,
encryptedEventContent.sessionId,
encryptedEventContent.senderKey)
2019-07-05 12:28:28 +00:00
.fold(
{ throwable ->
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event)
2019-07-05 12:28:28 +00:00
}
}
2019-05-16 08:23:57 +00:00
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
2019-07-05 12:28:28 +00:00
2019-07-05 14:11:54 +00:00
Try.Failure(MXCryptoError.Base(
2019-07-05 13:52:37 +00:00
MXCryptoError.ErrorType.OLM,
reason,
detailedReason))
}
if (throwable is MXCryptoError.Base) {
2019-07-05 13:52:37 +00:00
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
2019-07-05 12:28:28 +00:00
addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
}
}
2019-07-05 14:11:54 +00:00
Try.Failure(throwable)
2019-07-05 12:28:28 +00:00
},
2019-07-05 14:11:54 +00:00
{ olmDecryptionResult ->
2019-07-05 12:28:28 +00:00
// the decryption succeeds
2019-07-05 14:11:54 +00:00
if (olmDecryptionResult.payload != null) {
Try.just(
2019-07-05 12:28:28 +00:00
MXEventDecryptionResult(
2019-07-05 14:11:54 +00:00
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
2019-07-05 12:28:28 +00:00
)
)
} else {
2019-07-05 13:52:37 +00:00
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
2019-07-05 12:28:28 +00:00
}
}
)
2019-05-16 08:23:57 +00:00
}
2019-05-16 08:23:57 +00:00
/**
* Helper for the real decryptEvent and for _retryDecryption. If
* requestKeysOnFail is true, we'll send an m.room_key_request when we fail
* to decrypt the event due to missing megolm keys.
*
* @param event the event
*/
private fun requestKeysForEvent(event: Event) {
val sender = event.senderId!!
2019-05-16 08:23:57 +00:00
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
val recipients = ArrayList<Map<String, String>>()
val selfMap = HashMap<String, String>()
2019-07-03 09:58:50 +00:00
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
selfMap["userId"] = credentials.userId
2019-05-16 08:23:57 +00:00
selfMap["deviceId"] = "*"
recipients.add(selfMap)
if (!TextUtils.equals(sender, credentials.userId)) {
2019-05-16 08:23:57 +00:00
val senderMap = HashMap<String, String>()
senderMap["userId"] = sender
senderMap["deviceId"] = encryptedEventContent.deviceId!!
recipients.add(senderMap)
}
val requestBody = RoomKeyRequestBody()
requestBody.roomId = event.roomId
requestBody.algorithm = encryptedEventContent.algorithm
requestBody.senderKey = encryptedEventContent.senderKey
requestBody.sessionId = encryptedEventContent.sessionId
outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients)
2019-05-16 08:23:57 +00:00
}
/**
* Add an event to the list of those we couldn't decrypt the first time we
* saw them.
*
* @param event the event to try to decrypt later
* @param timelineId the timeline identifier
*/
private fun addEventToPendingList(event: Event, timelineId: String) {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
2019-05-16 08:23:57 +00:00
if (!pendingEvents.containsKey(pendingEventsKey)) {
pendingEvents[pendingEventsKey] = HashMap()
2019-05-16 08:23:57 +00:00
}
2019-07-04 09:38:56 +00:00
if (pendingEvents[pendingEventsKey]?.containsKey(timelineId) == false) {
pendingEvents[pendingEventsKey]?.put(timelineId, ArrayList())
2019-05-16 08:23:57 +00:00
}
2019-07-04 09:38:56 +00:00
if (pendingEvents[pendingEventsKey]?.get(timelineId)?.contains(event) == false) {
2019-05-20 15:13:08 +00:00
Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId)
2019-07-04 09:38:56 +00:00
pendingEvents[pendingEventsKey]?.get(timelineId)?.add(event)
2019-05-16 08:23:57 +00:00
}
}
/**
* Handle a key event.
*
2019-05-20 08:30:33 +00:00
* @param event the key event.
2019-05-16 08:23:57 +00:00
*/
2019-05-20 08:30:33 +00:00
override fun onRoomKeyEvent(event: Event, keysBackup: KeysBackup) {
2019-05-16 08:23:57 +00:00
var exportFormat = false
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
2019-05-16 08:23:57 +00:00
var senderKey: String? = event.getSenderKey()
var keysClaimed: MutableMap<String, String> = HashMap()
val forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
2019-05-16 08:23:57 +00:00
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.sessionId) || TextUtils.isEmpty(roomKeyContent.sessionKey)) {
Timber.e("## onRoomKeyEvent() : Key event is missing fields")
return
}
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
2019-07-09 13:26:32 +00:00
Timber.v("## onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" +
" sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}")
2019-07-02 14:03:32 +00:00
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
?: return
forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let {
forwardingCurve25519KeyChain.addAll(it)
2019-05-16 08:23:57 +00:00
}
2019-07-01 09:08:05 +00:00
if (senderKey == null) {
Timber.e("## onRoomKeyEvent() : event is missing sender_key field")
return
}
forwardingCurve25519KeyChain.add(senderKey)
2019-05-16 08:23:57 +00:00
exportFormat = true
senderKey = forwardedRoomKeyContent.senderKey
if (null == senderKey) {
Timber.e("## onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
return
}
if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
Timber.e("## forwarded_room_key_event is missing sender_claimed_ed25519_key field")
return
}
2019-07-01 09:08:05 +00:00
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
2019-05-16 08:23:57 +00:00
} else {
2019-05-20 15:13:08 +00:00
Timber.v("## onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
2019-05-16 08:23:57 +00:00
if (null == senderKey) {
Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
return
}
// inherit the claimed ed25519 key from the setup message
keysClaimed = event.getKeysClaimed().toMutableMap()
}
2019-07-01 09:08:05 +00:00
if (roomKeyContent.sessionId == null
|| roomKeyContent.sessionKey == null
|| roomKeyContent.roomId == null) {
Timber.e("## invalid roomKeyContent")
return
}
2019-07-09 13:26:32 +00:00
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
roomKeyContent.sessionKey,
roomKeyContent.roomId,
senderKey,
forwardingCurve25519KeyChain,
keysClaimed,
exportFormat)
2019-05-16 08:23:57 +00:00
if (added) {
2019-05-20 08:30:33 +00:00
keysBackup.maybeBackupKeys()
2019-05-16 08:23:57 +00:00
val content = RoomKeyRequestBody()
content.algorithm = roomKeyContent.algorithm
content.roomId = roomKeyContent.roomId
content.sessionId = roomKeyContent.sessionId
content.senderKey = senderKey
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content)
2019-05-16 08:23:57 +00:00
2019-07-01 09:08:05 +00:00
onNewSession(senderKey, roomKeyContent.sessionId)
2019-05-16 08:23:57 +00:00
}
}
/**
* Check if the some messages can be decrypted with a new session
*
* @param senderKey the session sender key
* @param sessionId the session id
*/
override fun onNewSession(senderKey: String, sessionId: String) {
Timber.v("ON NEW SESSION $sessionId - $senderKey")
2019-07-02 14:03:32 +00:00
newSessionListener?.onNewSession(null, senderKey, sessionId)
2019-05-16 08:23:57 +00:00
}
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
val roomId = request.requestBody?.roomId ?: return false
val senderKey = request.requestBody?.senderKey ?: return false
val sessionId = request.requestBody?.sessionId ?: return false
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
2019-05-16 08:23:57 +00:00
}
override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
// sanity checks
if (request.requestBody == null) {
2019-05-16 08:23:57 +00:00
return
}
val userId = request.userId ?: return
GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager
.downloadKeys(listOf(userId), false)
.flatMap {
2019-07-01 09:08:05 +00:00
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
2019-07-01 09:08:05 +00:00
if (deviceInfo == null) {
throw RuntimeException()
} else {
val devicesByUser = mapOf(userId to listOf(deviceInfo))
2019-07-01 09:08:05 +00:00
ensureOlmSessionsForDevicesAction
.handle(devicesByUser)
.flatMap {
val body = request.requestBody
2019-07-03 15:34:08 +00:00
val olmSessionResult = it.getObject(userId, deviceId)
2019-07-01 09:08:05 +00:00
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
Try.just(Unit)
}
2019-07-09 13:26:32 +00:00
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
2019-07-01 09:08:05 +00:00
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
2019-07-05 12:28:28 +00:00
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
2019-07-01 09:08:05 +00:00
val sendToDeviceMap = MXUsersDevicesMap<Any>()
2019-07-03 15:34:08 +00:00
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
2019-07-01 09:08:05 +00:00
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams)
}
}
}
}
2019-05-16 08:23:57 +00:00
}
2019-05-16 08:23:57 +00:00
}