Rework Crypto using Try

This commit is contained in:
Benoit Marty 2019-07-05 14:28:28 +02:00 committed by Benoit Marty
parent 25b0cd0e4b
commit 87dec337d8
18 changed files with 308 additions and 406 deletions

View file

@ -38,7 +38,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
data class CryptoError(val error: MXCryptoError) : Failure(error)
abstract class FeatureFailure : Failure()

View file

@ -98,9 +98,9 @@ interface CryptoService {
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>)
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
fun getEncryptionAlgorithm(roomId: String): String?

View file

@ -18,72 +18,28 @@
package im.vector.matrix.android.api.session.crypto
import android.text.TextUtils
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
/**
* Represents a crypto error response.
*/
class MXCryptoError(var code: String,
var message: String) {
sealed class MXCryptoError : Throwable() {
// TODO Create data class for all cases, and remove error code
data class Base(val code: String,
val _message: String,
/**
* Describe the error with more details
*/
private var mDetailedErrorDescription: String? = null
val detailedErrorDescription: String? = null) : MXCryptoError()
/**
* Data exception.
* Some exceptions provide some data to describe the exception
*/
var mExceptionData: Any? = null
/**
* @return true if the current error is an olm one.
*/
val isOlmError: Boolean
get() = OLM_ERROR_CODE == code
/**
* @return the detailed error description
*/
val detailedErrorDescription: String?
get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) {
message
} else mDetailedErrorDescription
/**
* Create a crypto error
*
* @param code the error code (see XX_ERROR_CODE)
* @param shortErrorDescription the short error description
* @param detailedErrorDescription the detailed error description
*/
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) {
mDetailedErrorDescription = detailedErrorDescription
}
/**
* Create a crypto error
*
* @param code the error code (see XX_ERROR_CODE)
* @param shortErrorDescription the short error description
* @param detailedErrorDescription the detailed error description
* @param exceptionData the exception data
*/
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?, exceptionData: Any) : this(code, shortErrorDescription) {
mDetailedErrorDescription = detailedErrorDescription
mExceptionData = exceptionData
}
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
companion object {
// TODO Create sealed class
/**
* Error codes
*/
const val UNKNOWN_ERROR_CODE = "UNKNOWN_ERROR_CODE"
const val ENCRYPTING_NOT_ENABLED_ERROR_CODE = "ENCRYPTING_NOT_ENABLED"
const val UNABLE_TO_ENCRYPT_ERROR_CODE = "UNABLE_TO_ENCRYPT"
const val UNABLE_TO_DECRYPT_ERROR_CODE = "UNABLE_TO_DECRYPT"

View file

@ -140,10 +140,10 @@ data class Event(
*
* @param decryptionResult the decryption result, including the plaintext and some key info.
*/
internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
internal fun setClearData(decryptionResult: MXEventDecryptionResult) {
mClearEvent = null
if (decryptionResult != null) {
if (decryptionResult.clearEvent != null) {
mCryptoError = null
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
@ -166,11 +166,6 @@ data class Event(
Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
}
}
}
}
mCryptoError = null
}
/**

View file

@ -569,8 +569,7 @@ internal class CryptoManager @Inject constructor(
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## encryptEventContent() : $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, reason)))
}
}
}
@ -580,10 +579,10 @@ internal class CryptoManager @Inject constructor(
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking {
internalDecryptEvent(event, timeline).fold(
{ throw it },
@ -599,7 +598,7 @@ internal class CryptoManager @Inject constructor(
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>) {
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
GlobalScope.launch(EmptyCoroutineContext) {
val result = withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
@ -616,18 +615,17 @@ internal class CryptoManager @Inject constructor(
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
*/
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
return Try {
val eventContent = event.content
if (eventContent == null) {
return if (eventContent == null) {
Timber.e("## decryptEvent : empty event content")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE))
}
Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} else {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
Try.Failure(MXCryptoError.Base(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, reason))
} else {
alg.decryptEvent(event, timeline)
}
@ -872,7 +870,7 @@ internal class CryptoManager @Inject constructor(
/**
* Check if the user ids list have some unknown devices.
* A success means there is no unknown devices.
* If there are some unknown devices, a MXCryptoError.UNKNOWN_DEVICES_CODE exception is triggered.
* If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered.
*
* @param userIds the user ids list
* @param callback the asynchronous callback.
@ -890,9 +888,7 @@ internal class CryptoManager @Inject constructor(
callback.onSuccess(Unit)
} else {
// trigger an an unknown devices exception
callback.onFailure(
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
}
}
)

View file

@ -1,39 +0,0 @@
/*
* Copyright 2016 OpenMarket Ltd
* Copyright 2017 Vector Creations 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
import im.vector.matrix.android.api.session.crypto.MXCryptoError
/**
* This class represents a decryption exception
*/
class MXDecryptionException
(
/**
* the linked crypto error
*/
val cryptoError: MXCryptoError?
) : Exception() {
override val message: String?
get() = cryptoError?.message ?: super.message
override fun getLocalizedMessage(): String {
return cryptoError?.message ?: super.getLocalizedMessage()
}
}

View file

@ -1,6 +1,5 @@
/*
* Copyright 2016 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +17,6 @@
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.util.JsonDict
import java.util.*
/**
* The result of a (successful) call to decryptEvent.
@ -28,23 +26,23 @@ data class MXEventDecryptionResult(
/**
* The plaintext payload for the event (typically containing "type" and "content" fields).
*/
var clearEvent: JsonDict? = null,
val clearEvent: JsonDict,
/**
* Key owned by the sender of this event.
* See MXEvent.senderKey.
*/
var senderCurve25519Key: String? = null,
val senderCurve25519Key: String? = null,
/**
* Ed25519 key claimed by the sender of this event.
* See MXEvent.claimedEd25519Key.
*/
var claimedEd25519Key: String? = null,
val claimedEd25519Key: String? = null,
/**
* List of curve25519 keys involved in telling us about the senderCurve25519Key and
* claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain.
*/
var forwardingCurve25519KeyChain: List<String> = ArrayList()
val forwardingCurve25519KeyChain: List<String> = emptyList()
)

View file

@ -18,10 +18,11 @@
package im.vector.matrix.android.internal.crypto
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -81,11 +82,6 @@ internal class MXOlmDevice @Inject constructor(
// Values are true.
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
/**
* inboundGroupSessionWithId error
*/
private var inboundGroupSessionWithIdError: MXCryptoError? = null
init {
// Retrieve the account from the store
olmAccount = store.getAccount()
@ -508,15 +504,18 @@ internal class MXOlmDevice @Inject constructor(
forwardingCurve25519KeyChain: List<String>,
keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean {
val existingInboundSession = getInboundGroupSession(sessionId, senderKey, roomId)
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
if (null != existingInboundSession) {
getInboundGroupSession(sessionId, senderKey, roomId).fold(
{
// Nothing to do in case of error
},
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingFirstKnown = existingInboundSession.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex!!
val existingFirstKnown = it.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex
//If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
@ -524,6 +523,7 @@ internal class MXOlmDevice @Inject constructor(
return false
}
}
)
// sanity check
if (null == session.olmInboundGroupSession) {
@ -594,21 +594,27 @@ internal class MXOlmDevice @Inject constructor(
continue
}
val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId)
if (null != existingOlmSession) {
getInboundGroupSession(sessionId, senderKey, roomId)
.fold(
{
// Session does not already exist, add it
sessions.add(session)
},
{
// If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
// For now we just ignore updates. TODO: implement something here
if (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) {
if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
//Ignore this, keep existing
session.olmInboundGroupSession!!.releaseSession()
continue
}
}
} else {
sessions.add(session)
}
Unit
}
)
}
store.storeInboundGroupSessions(sessions)
@ -637,19 +643,16 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return the decrypting result. Nil if the sessionId is unknown.
*/
@Throws(MXDecryptionException::class)
fun decryptGroupMessage(body: String,
roomId: String,
timeline: String?,
sessionId: String,
senderKey: String): MXDecryptionResult? {
val result = MXDecryptionResult()
val session = getInboundGroupSession(sessionId, senderKey, roomId)
if (null != session) {
senderKey: String): Try<OlmDecryptionResult> {
return getInboundGroupSession(sessionId, senderKey, roomId)
.flatMap { session ->
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (TextUtils.equals(roomId, session.roomId)) {
if (roomId == session.roomId) {
var errorMessage = ""
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
try {
@ -670,48 +673,40 @@ internal class MXOlmDevice @Inject constructor(
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE, reason))
}
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
}
store.storeInboundGroupSessions(listOf(session))
try {
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
val payload = adapter.fromJson(payloadString)
result.payload = payload
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message)
return null
}
if (null == result.payload) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return null
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}
result.keysClaimed = session.keysClaimed
result.senderKey = senderKey
result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain
return@flatMap Try.just(
OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
)
} else {
Timber.e("## decryptGroupMessage() : failed to decode the message")
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.OLM_ERROR_CODE, errorMessage))
}
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, reason))
}
} else {
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
throw MXDecryptionException(inboundGroupSessionWithIdError)
}
return result
}
/**
@ -725,7 +720,7 @@ internal class MXOlmDevice @Inject constructor(
}
}
// Utilities
// Utilities
/**
* Verify an ed25519 signature on a JSON object.
@ -775,27 +770,27 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session.
*/
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper? {
inboundGroupSessionWithIdError = null
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
}
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) return null
val session = store.getInboundGroupSession(sessionId, senderKey)
if (null != session) {
return if (null != session) {
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription")
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, errorDescription)
Try.Failure(MXCryptoError.Base(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, errorDescription))
} else {
Try.just(session)
}
} else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE,
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
Try.Failure(MXCryptoError.Base(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
}
return session
}
/**
@ -807,6 +802,6 @@ internal class MXOlmDevice @Inject constructor(
* @return true if the unbound session keys are known.
*/
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return null != getInboundGroupSession(sessionId, senderKey, roomId)
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
}
}

View file

@ -17,9 +17,9 @@
package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
@ -33,11 +33,9 @@ internal interface IMXDecrypting {
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the decryption information, or null in case of error
* @throws MXDecryptionException the decryption failure reason
* @return the decryption information, or an error
*/
@Throws(MXDecryptionException::class)
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
/**
* Handle a key event.

View file

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content
/**

View file

@ -28,7 +28,6 @@ import im.vector.matrix.android.internal.crypto.*
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -64,66 +63,65 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
@Throws(MXDecryptionException::class)
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
return decryptEvent(event, timeline, true)
}
@Throws(MXDecryptionException::class)
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON))
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
?: throw MXCryptoError.Base(MXCryptoError.MISSING_FIELDS_ERROR_CODE, MXCryptoError.MISSING_FIELDS_REASON)
if (TextUtils.isEmpty(encryptedEventContent.senderKey)
|| TextUtils.isEmpty(encryptedEventContent.sessionId)
|| TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
throw MXCryptoError.Base(MXCryptoError.MISSING_FIELDS_ERROR_CODE, MXCryptoError.MISSING_FIELDS_REASON)
}
var cryptoError: MXCryptoError? = null
var decryptGroupMessageResult: MXDecryptionResult? = null
try {
decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!)
} catch (e: MXDecryptionException) {
cryptoError = e.cryptoError
}
// the decryption succeeds
if (decryptGroupMessageResult?.payload != null) {
val eventDecryptionResult = MXEventDecryptionResult()
eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload
eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey
if (null != decryptGroupMessageResult.keysClaimed) {
eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed?.get("ed25519")
}
eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain
?: emptyList()
return eventDecryptionResult
} else if (cryptoError != null) {
if (cryptoError.isOlmError) {
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!)
.fold(
{ throwable ->
if (throwable is MXCryptoError.Base) {
if (throwable.code == MXCryptoError.OLM_ERROR_CODE) {
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == throwable._message) {
addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
}
val reason = String.format(MXCryptoError.OLM_REASON, cryptoError.message)
val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message)
val reason = String.format(MXCryptoError.OLM_REASON, throwable._message)
val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, throwable._message)
throw MXDecryptionException(MXCryptoError(
throw MXCryptoError.Base(
MXCryptoError.OLM_ERROR_CODE,
reason,
detailedReason))
} else if (TextUtils.equals(cryptoError.code, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) {
detailedReason)
} else if (throwable.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
throw MXDecryptionException(cryptoError)
}
}
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE))
throw throwable
},
{ decryptionResult ->
// the decryption succeeds
if (decryptionResult.payload != null) {
return Try.just(
MXEventDecryptionResult(
clearEvent = decryptionResult.payload,
senderCurve25519Key = decryptionResult.senderKey,
claimedEd25519Key = decryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain ?: emptyList()
)
)
} else {
throw MXCryptoError.Base(MXCryptoError.MISSING_FIELDS_ERROR_CODE, MXCryptoError.MISSING_FIELDS_REASON)
}
}
)
}
@ -319,11 +317,20 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
Try.just(Unit)
}
Timber.v("""## shareKeysWithDevice() : sharing keys for session ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId""")
val inboundGroupSession = olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
val payloadJson = HashMap<String, Any>()
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
@ -332,8 +339,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams)
}
}
}
}

View file

@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.EventType
@ -254,7 +253,8 @@ internal class MXMegolmEncryption(
/**
* process the pending encryptions
*/
private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
return Try<Content> {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
@ -278,6 +278,7 @@ internal class MXMegolmEncryption(
session.useCount++
map
}
}
/**
* Get the list of devices which can encrypt data to.
@ -328,9 +329,7 @@ internal class MXMegolmEncryption(
if (unknownDevices.isEmpty) {
Try.just(devicesInRoom)
} else {
val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
Try.Failure(Failure.CryptoError(cryptoError))
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
}
}
}

View file

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm
import arrow.core.Try
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.toModel
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
@ -41,30 +41,29 @@ internal class MXOlmDecryption(
private val credentials: Credentials)
: IMXDecrypting {
@Throws(MXDecryptionException::class)
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
Timber.e("## decryptEvent() : bad event format")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_EVENT_FORMAT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_EVENT_FORMAT_ERROR_CODE,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
}
val cipherText = olmEventContent.ciphertext ?: run {
Timber.e("## decryptEvent() : missing cipher text")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
}
val senderKey = olmEventContent.senderKey ?: run {
Timber.e("## decryptEvent() : missing sender key")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
}
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE,
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
}
// The message for myUser
@ -74,14 +73,14 @@ internal class MXOlmDecryption(
if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
}
val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
}
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@ -89,73 +88,72 @@ internal class MXOlmDecryption(
if (payload == null) {
Timber.e("## decryptEvent failed : null payload")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
}
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
Timber.e("## decryptEvent() : bad olmPayloadContent format")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE,
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}
if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
reason))
}
if (olmPayloadContent.recipient != credentials.userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_RECIPIENT_ERROR_CODE,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
}
val recipientKeys = olmPayloadContent.recipient_keys ?: run {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
}
val ed25519 = recipientKeys["ed25519"]
if (ed25519 != olmDevice.deviceEd25519Key) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE,
MXCryptoError.BAD_RECIPIENT_KEY_REASON))
}
if (olmPayloadContent.sender.isNullOrBlank()) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
return Try.Failure(MXCryptoError.Base(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
}
if (olmPayloadContent.sender != event.senderId) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
return Try.Failure(MXCryptoError.Base(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
}
if (olmPayloadContent.room_id != event.roomId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ROOM_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
return Try.Failure(MXCryptoError.Base(MXCryptoError.BAD_ROOM_ERROR_CODE,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
}
val keys = olmPayloadContent.keys ?: run {
Timber.e("## decryptEvent failed : null keys")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
return Try.Failure(MXCryptoError.Base(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
}
val result = MXEventDecryptionResult()
result.clearEvent = payload
result.senderCurve25519Key = senderKey
result.claimedEd25519Key = keys["ed25519"]
return result
return Try.just(MXEventDecryptionResult(
clearEvent = payload,
senderCurve25519Key = senderKey,
claimedEd25519Key = keys["ed25519"]
))
}
/**

View file

@ -14,32 +14,32 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.algorithms
package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.util.JsonDict
/**
* This class represents the decryption result.
*/
data class MXDecryptionResult(
data class OlmDecryptionResult(
/**
* The decrypted payload (with properties 'type', 'content')
*/
var payload: JsonDict? = null,
val payload: JsonDict? = null,
/**
* keys that the sender of the event claims ownership of:
* map from key type to base64-encoded key.
*/
var keysClaimed: Map<String, String>? = null,
val keysClaimed: Map<String, String>? = null,
/**
* The curve25519 key that the sender of the event is known to have ownership of.
*/
var senderKey: String? = null,
val senderKey: String? = null,
/**
* Devices which forwarded this session to us (normally empty).
*/
var forwardingCurve25519KeyChain: List<String>? = null
val forwardingCurve25519KeyChain: List<String>? = null
)

View file

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
data class MXEncryptEventContentResult(
/**
* The event content
* The encrypted event content
*/
val eventContent: Content,
/**

View file

@ -16,12 +16,12 @@
package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.CryptoService
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.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
@ -81,8 +81,8 @@ internal class SimpleTimelineEventFactory @Inject constructor(private val roomMe
event.setClearData(result)
} catch (failure: Throwable) {
Timber.e("Encrypted event: decryption failed")
if (failure is MXDecryptionException) {
event.setCryptoError(failure.cryptoError)
if (failure is Failure.CryptoError) {
event.setCryptoError(failure.error)
}
}
}
@ -135,14 +135,12 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room
} else {
try {
val result = cryptoService.decryptEvent(event, timelineId)
if (result != null) {
decryptionCache[cacheKey] = result
}
event.setClearData(result)
} catch (failure: Throwable) {
Timber.e("Encrypted event: decryption failed ${failure.localizedMessage}")
if (failure is MXDecryptionException) {
event.setCryptoError(failure.cryptoError)
if (failure is Failure.CryptoError) {
event.setCryptoError(failure.error)
} else {
// Other error
Timber.e("Other error, should be handled")

View file

@ -17,15 +17,14 @@
package im.vector.matrix.android.internal.session.sync
import android.text.TextUtils
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.api.session.room.model.message.MessageContent
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse
import timber.log.Timber
@ -66,8 +65,8 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
var result: MXEventDecryptionResult? = null
try {
result = cryptoManager.decryptEvent(event, timelineId ?: "")
} catch (exception: MXDecryptionException) {
event.setCryptoError(exception.cryptoError)
} catch (exception: MXCryptoError) {
event.setCryptoError(exception)
}
if (null != result) {

View file

@ -28,7 +28,6 @@ import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import me.gujun.android.span.span
import javax.inject.Inject
@ -49,10 +48,16 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
EventType.ENCRYPTED == event.root.getClearType() -> {
val cryptoError = event.root.mCryptoError
val errorDescription =
if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
if (cryptoError is MXCryptoError.Base) {
if (cryptoError.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
} else {
cryptoError?.message
// TODO i18n
cryptoError._message
}
} else {
// Cannot happen (for now)
"Other error"
}
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)