Merge pull request #309 from vector-im/feature/crypto_cleanup

Rework Crypto using Try
This commit is contained in:
Benoit Marty 2019-07-05 19:03:59 +02:00 committed by GitHub
commit 8c86a653b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 346 additions and 434 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 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() abstract class FeatureFailure : Failure()



View File

@ -98,9 +98,10 @@ interface CryptoService {
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) callback: MatrixCallback<MXEncryptEventContentResult>)


fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? @Throws(MXCryptoError::class)
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? fun getEncryptionAlgorithm(roomId: String): String?



View File

@ -18,108 +18,58 @@


package im.vector.matrix.android.api.session.crypto 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
import org.matrix.olm.OlmException


/** /**
* Represents a crypto error response. * Represents a crypto error response.
*/ */
class MXCryptoError(var code: String, sealed class MXCryptoError : Throwable() {
var message: String) {


/** data class Base(val errorType: ErrorType,
* Describe the error with more details val technicalMessage: String,
*/ /**
private var mDetailedErrorDescription: String? = null * Describe the error with more details
*/
val detailedErrorDescription: String? = null) : MXCryptoError()


/** data class OlmError(val olmException: OlmException) : MXCryptoError()
* Data exception.
* Some exceptions provide some data to describe the exception
*/
var mExceptionData: Any? = null


/** data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
* @return true if the current error is an olm one.
*/
val isOlmError: Boolean
get() = OLM_ERROR_CODE == code



enum class ErrorType {
/** ENCRYPTING_NOT_ENABLED,
* @return the detailed error description UNABLE_TO_ENCRYPT,
*/ UNABLE_TO_DECRYPT,
val detailedErrorDescription: String? UNKNOWN_INBOUND_SESSION_ID,
get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) { INBOUND_SESSION_MISMATCH_ROOM_ID,
message MISSING_FIELDS,
} else mDetailedErrorDescription BAD_EVENT_FORMAT,

MISSING_SENDER_KEY,
/** MISSING_CIPHER_TEXT,
* Create a crypto error BAD_DECRYPTED_FORMAT,
* NOT_INCLUDE_IN_RECIPIENTS,
* @param code the error code (see XX_ERROR_CODE) BAD_RECIPIENT,
* @param shortErrorDescription the short error description BAD_RECIPIENT_KEY,
* @param detailedErrorDescription the detailed error description FORWARDED_MESSAGE,
*/ BAD_ROOM,
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) { BAD_ENCRYPTED_MESSAGE,
mDetailedErrorDescription = detailedErrorDescription DUPLICATED_MESSAGE_INDEX,
} MISSING_PROPERTY,

OLM,
/** UNKNOWN_DEVICES,
* Create a crypto error UNKNOWN_MESSAGE_INDEX
*
* @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
} }


companion object { companion object {

// TODO Create sealed class

/** /**
* Error codes * Resource for technicalMessage
*/ */
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"
const val UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE = "UNKNOWN_INBOUND_SESSION_ID"
const val INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE = "INBOUND_SESSION_MISMATCH_ROOM_ID"
const val MISSING_FIELDS_ERROR_CODE = "MISSING_FIELDS"
const val BAD_EVENT_FORMAT_ERROR_CODE = "BAD_EVENT_FORMAT_ERROR_CODE"
const val MISSING_SENDER_KEY_ERROR_CODE = "MISSING_SENDER_KEY_ERROR_CODE"
const val MISSING_CIPHER_TEXT_ERROR_CODE = "MISSING_CIPHER_TEXT"
const val BAD_DECRYPTED_FORMAT_ERROR_CODE = "BAD_DECRYPTED_FORMAT_ERROR_CODE"
const val NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE = "NOT_INCLUDE_IN_RECIPIENTS"
const val BAD_RECIPIENT_ERROR_CODE = "BAD_RECIPIENT"
const val BAD_RECIPIENT_KEY_ERROR_CODE = "BAD_RECIPIENT_KEY"
const val FORWARDED_MESSAGE_ERROR_CODE = "FORWARDED_MESSAGE"
const val BAD_ROOM_ERROR_CODE = "BAD_ROOM"
const val BAD_ENCRYPTED_MESSAGE_ERROR_CODE = "BAD_ENCRYPTED_MESSAGE"
const val DUPLICATED_MESSAGE_INDEX_ERROR_CODE = "DUPLICATED_MESSAGE_INDEX"
const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY"
const val OLM_ERROR_CODE = "OLM_ERROR_CODE"
const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE"
const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX"

/**
* short error reasons
*/
const val UNABLE_TO_DECRYPT = "Unable to decrypt"
const val UNABLE_TO_ENCRYPT = "Unable to encrypt"

/**
* Detailed error reasons
*/
const val ENCRYPTING_NOT_ENABLED_REASON = "Encryption not enabled"
const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s" const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s"
const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s" const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s"
const val OLM_REASON = "OLM error: %1\$s" const val OLM_REASON = "OLM error: %1\$s"
const val DETAILLED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s" const val DETAILED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id" const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id"
const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)" const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)"
const val MISSING_FIELDS_REASON = "Missing fields in input" const val MISSING_FIELDS_REASON = "Missing fields in input"
@ -138,4 +88,4 @@ class MXCryptoError(var code: String,
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing." const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing."
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event." const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event."
} }
} }

View File

@ -140,37 +140,32 @@ data class Event(
* *
* @param decryptionResult the decryption result, including the plaintext and some key info. * @param decryptionResult the decryption result, including the plaintext and some key info.
*/ */
internal fun setClearData(decryptionResult: MXEventDecryptionResult?) { internal fun setClearData(decryptionResult: MXEventDecryptionResult) {
mClearEvent = null mClearEvent = null
if (decryptionResult != null) { mCryptoError = null
if (decryptionResult.clearEvent != null) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)


if (mClearEvent != null) { val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
mSenderCurve25519Key = decryptionResult.senderCurve25519Key mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
mClaimedEd25519Key = decryptionResult.claimedEd25519Key
mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain


// For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back if (mClearEvent != null) {
// in the clear event mSenderCurve25519Key = decryptionResult.senderCurve25519Key
try { mClaimedEd25519Key = decryptionResult.claimedEd25519Key
content?.get("m.relates_to")?.let { clearRelates -> mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
mClearEvent = mClearEvent?.copy(
content = HashMap(mClearEvent!!.content).apply { // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
this["m.relates_to"] = clearRelates // in the clear event
} try {
) content?.get("m.relates_to")?.let { clearRelates ->
} mClearEvent = mClearEvent?.copy(
} catch (e: Exception) { content = HashMap(mClearEvent!!.content).apply {
Timber.e(e, "Unable to restore 'm.relates_to' the clear event") this["m.relates_to"] = clearRelates
} }
)
} }

} catch (e: Exception) {

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, val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## encryptEventContent() : $reason") Timber.e("## encryptEventContent() : $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
} }
} }
} }
@ -580,10 +579,10 @@ internal class CryptoManager @Inject constructor(
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @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) @Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? { override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking { return runBlocking {
internalDecryptEvent(event, timeline).fold( internalDecryptEvent(event, timeline).fold(
{ throw it }, { 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 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 * @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) { GlobalScope.launch(EmptyCoroutineContext) {
val result = withContext(coroutineDispatchers.crypto) { val result = withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline) 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] * @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
*/ */
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
return Try { val eventContent = event.content
val eventContent = event.content return if (eventContent == null) {
if (eventContent == null) { Timber.e("## decryptEvent : empty event content")
Timber.e("## decryptEvent : empty event content") Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE)) } else {
}
val algorithm = eventContent["algorithm"]?.toString() val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason)) Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
} else { } else {
alg.decryptEvent(event, timeline) alg.decryptEvent(event, timeline)
} }
@ -872,7 +870,7 @@ internal class CryptoManager @Inject constructor(
/** /**
* Check if the user ids list have some unknown devices. * Check if the user ids list have some unknown devices.
* A success means there is no 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 userIds the user ids list
* @param callback the asynchronous callback. * @param callback the asynchronous callback.
@ -890,9 +888,7 @@ internal class CryptoManager @Inject constructor(
callback.onSuccess(Unit) callback.onSuccess(Unit)
} else { } else {
// trigger an an unknown devices exception // trigger an an unknown devices exception
callback.onFailure( callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, 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 2016 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 package im.vector.matrix.android.internal.crypto


import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import java.util.*


/** /**
* The result of a (successful) call to decryptEvent. * 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). * 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. * Key owned by the sender of this event.
* See MXEvent.senderKey. * See MXEvent.senderKey.
*/ */
var senderCurve25519Key: String? = null, val senderCurve25519Key: String? = null,


/** /**
* Ed25519 key claimed by the sender of this event. * Ed25519 key claimed by the sender of this event.
* See MXEvent.claimedEd25519Key. * See MXEvent.claimedEd25519Key.
*/ */
var claimedEd25519Key: String? = null, val claimedEd25519Key: String? = null,


/** /**
* List of curve25519 keys involved in telling us about the senderCurve25519Key and * List of curve25519 keys involved in telling us about the senderCurve25519Key and
* claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain. * 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 package im.vector.matrix.android.internal.crypto


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.crypto.MXCryptoError 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.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict 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.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -81,11 +82,6 @@ internal class MXOlmDevice @Inject constructor(
// Values are true. // Values are true.
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap() private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()


/**
* inboundGroupSessionWithId error
*/
private var inboundGroupSessionWithIdError: MXCryptoError? = null

init { init {
// Retrieve the account from the store // Retrieve the account from the store
olmAccount = store.getAccount() olmAccount = store.getAccount()
@ -508,22 +504,26 @@ internal class MXOlmDevice @Inject constructor(
forwardingCurve25519KeyChain: List<String>, forwardingCurve25519KeyChain: List<String>,
keysClaimed: Map<String, String>, keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean { exportFormat: Boolean): Boolean {
val existingInboundSession = getInboundGroupSession(sessionId, senderKey, roomId)
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat) val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)


if (null != existingInboundSession) { getInboundGroupSession(sessionId, senderKey, roomId).fold(
// If we already have this session, consider updating it {
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId") // 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 existingFirstKnown = it.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex!! val newKnownFirstIndex = session.firstKnownIndex


//If our existing session is better we keep it //If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession() session.olmInboundGroupSession?.releaseSession()
return false return false
} }
} }
)


// sanity check // sanity check
if (null == session.olmInboundGroupSession) { if (null == session.olmInboundGroupSession) {
@ -594,20 +594,26 @@ internal class MXOlmDevice @Inject constructor(
continue continue
} }


val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId) getInboundGroupSession(sessionId, senderKey, roomId)
if (null != existingOlmSession) { .fold(
// If we already have this session, consider updating it {
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") // 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 // For now we just ignore updates. TODO: implement something here
if (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) { if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
//Ignore this, keep existing //Ignore this, keep existing
session.olmInboundGroupSession!!.releaseSession() session.olmInboundGroupSession!!.releaseSession()
continue } else {
} sessions.add(session)
} }

Unit
sessions.add(session) }
)
} }


store.storeInboundGroupSessions(sessions) store.storeInboundGroupSessions(sessions)
@ -637,81 +643,64 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the decrypting result. Nil if the sessionId is unknown. * @return the decrypting result. Nil if the sessionId is unknown.
*/ */
@Throws(MXDecryptionException::class)
fun decryptGroupMessage(body: String, fun decryptGroupMessage(body: String,
roomId: String, roomId: String,
timeline: String?, timeline: String?,
sessionId: String, sessionId: String,
senderKey: String): MXDecryptionResult? { senderKey: String): Try<OlmDecryptionResult> {
val result = MXDecryptionResult() return getInboundGroupSession(sessionId, senderKey, roomId)
val session = getInboundGroupSession(sessionId, senderKey, roomId) .flatMap { session ->

// Check that the room id matches the original one for the session. This stops
if (null != session) { // the HS pretending a message was targeting a different room.
// Check that the room id matches the original one for the session. This stops if (roomId == session.roomId) {
// the HS pretending a message was targeting a different room. var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
if (TextUtils.equals(roomId, session.roomId)) { try {
var errorMessage = "" decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null } catch (e: OlmException) {
try { Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) return@flatMap Try.Failure(MXCryptoError.OlmError(e))
} catch (e: Exception) {
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
errorMessage = e.message ?: ""
}

if (null != decryptResult) {
if (null != timeline) {
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap()
} }


val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex if (null != timeline) {
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap()
}


if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) { val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason") if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE, val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
MXCryptoError.UNABLE_TO_DECRYPT, reason)) Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
}

inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
} }


inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true) store.storeInboundGroupSessions(listOf(session))
} val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}


store.storeInboundGroupSessions(listOf(session)) return@flatMap Try.just(
try { OlmDecryptionResult(
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) payload,
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) session.keysClaimed,
val payload = adapter.fromJson(payloadString) senderKey,
result.payload = payload session.forwardingCurve25519KeyChain
} catch (e: Exception) { )
Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message) )
return null } else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
} }

if (null == result.payload) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return null
}

result.keysClaimed = session.keysClaimed
result.senderKey = senderKey
result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain
} else {
Timber.e("## decryptGroupMessage() : failed to decode the message")
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
} }
} 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))
}
} else {
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
throw MXDecryptionException(inboundGroupSessionWithIdError)
}

return result
} }


/** /**
@ -725,7 +714,7 @@ internal class MXOlmDevice @Inject constructor(
} }
} }


// Utilities // Utilities


/** /**
* Verify an ed25519 signature on a JSON object. * Verify an ed25519 signature on a JSON object.
@ -775,27 +764,27 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session. * @return the inbound group session.
*/ */
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper? { fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
inboundGroupSessionWithIdError = null if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
}


if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) return null
val session = store.getInboundGroupSession(sessionId, senderKey) 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 // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) { if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription") Timber.e("## getInboundGroupSession() : $errorDescription")
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription))
MXCryptoError.UNABLE_TO_DECRYPT, errorDescription) } else {
Try.just(session)
} }
} else { } else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE, Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
} }
return session
} }


/** /**
@ -807,6 +796,6 @@ internal class MXOlmDevice @Inject constructor(
* @return true if the unbound session keys are known. * @return true if the unbound session keys are known.
*/ */
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { 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 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.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest 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.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup


@ -33,11 +33,9 @@ internal interface IMXDecrypting {
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @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 * @return the decryption information, or an error
* @throws MXDecryptionException the decryption failure reason
*/ */
@Throws(MXDecryptionException::class) suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult


/** /**
* Handle a key event. * Handle a key event.

View File

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms package im.vector.matrix.android.internal.crypto.algorithms


import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content 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.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter 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.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.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -64,66 +63,71 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/ */
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap() private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()


@Throws(MXDecryptionException::class) override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return decryptEvent(event, timeline, true) return decryptEvent(event, timeline, true)
} }


@Throws(MXDecryptionException::class) private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult { if (event.roomId.isNullOrBlank()) {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!! return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
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))
} }


var cryptoError: MXCryptoError? = null val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
var decryptGroupMessageResult: MXDecryptionResult? = null ?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))


try { if (encryptedEventContent.senderKey.isNullOrBlank()
decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!) || encryptedEventContent.sessionId.isNullOrBlank()
} catch (e: MXDecryptionException) { || encryptedEventContent.ciphertext.isNullOrBlank()) {
cryptoError = e.cryptoError return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
} }
// the decryption succeeds
if (decryptGroupMessageResult?.payload != null) {
val eventDecryptionResult = MXEventDecryptionResult()


eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, event.roomId, timeline, encryptedEventContent.sessionId, encryptedEventContent.senderKey)
eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey .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)
}
}


if (null != decryptGroupMessageResult.keysClaimed) { val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed?.get("ed25519") val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
}


eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain Try.Failure(MXCryptoError.Base(
?: emptyList() MXCryptoError.ErrorType.OLM,
return eventDecryptionResult reason,
} else if (cryptoError != null) { detailedReason))
if (cryptoError.isOlmError) { }
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) { if (throwable is MXCryptoError.Base) {
addEventToPendingList(event, timeline) if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
if (requestKeysOnFail) { addEventToPendingList(event, timeline)
requestKeysForEvent(event) if (requestKeysOnFail) {
} requestKeysForEvent(event)
} }
}
}


val reason = String.format(MXCryptoError.OLM_REASON, cryptoError.message) Try.Failure(throwable)
val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message) },

{ olmDecryptionResult ->
throw MXDecryptionException(MXCryptoError( // the decryption succeeds
MXCryptoError.OLM_ERROR_CODE, if (olmDecryptionResult.payload != null) {
reason, Try.just(
detailedReason)) MXEventDecryptionResult(
} else if (TextUtils.equals(cryptoError.code, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) { clearEvent = olmDecryptionResult.payload,
addEventToPendingList(event, timeline) senderCurve25519Key = olmDecryptionResult.senderKey,
if (requestKeysOnFail) { claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
requestKeysForEvent(event) forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
} )
throw MXDecryptionException(cryptoError) )
} } else {
} Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE)) }
}
)
} }




@ -319,11 +323,20 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
Try.just(Unit) Try.just(Unit)
} }
Timber.v("""## shareKeysWithDevice() : sharing keys for session ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId""") 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>() val payloadJson = HashMap<String, Any>()
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY 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 encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>() val sendToDeviceMap = MXUsersDevicesMap<Any>()
@ -332,8 +345,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams) sendToDeviceTask.execute(sendToDeviceParams)
} }


} }
} }
} }

View File

@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials 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.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
@ -254,29 +253,31 @@ internal class MXMegolmEncryption(
/** /**
* process the pending encryptions * 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> {
// Everything is in place, encrypt all pending events return Try<Content> {
val payloadJson = HashMap<String, Any>() // Everything is in place, encrypt all pending events
payloadJson["room_id"] = roomId val payloadJson = HashMap<String, Any>()
payloadJson["type"] = eventType payloadJson["room_id"] = roomId
payloadJson["content"] = eventContent payloadJson["type"] = eventType
payloadJson["content"] = eventContent


// Get canonical Json from // Get canonical Json from


val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson)) val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)


val map = HashMap<String, Any>() val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!! map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!! map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId map["session_id"] = session.sessionId


// Include our device ID so that recipients can send us a // Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key. // m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!! map["device_id"] = credentials.deviceId!!
session.useCount++ session.useCount++
map map
}
} }


/** /**
@ -328,9 +329,7 @@ internal class MXMegolmEncryption(
if (unknownDevices.isEmpty) { if (unknownDevices.isEmpty) {
Try.just(devicesInRoom) Try.just(devicesInRoom)
} else { } else {
val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
Try.Failure(Failure.CryptoError(cryptoError))
} }
} }
} }

View File

@ -17,13 +17,13 @@


package im.vector.matrix.android.internal.crypto.algorithms.olm 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.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError 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.Event
import im.vector.matrix.android.api.session.events.model.toModel 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.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict 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.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
@ -41,30 +41,29 @@ internal class MXOlmDecryption(
private val credentials: Credentials) private val credentials: Credentials)
: IMXDecrypting { : IMXDecrypting {


@Throws(MXDecryptionException::class) override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run { val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
Timber.e("## decryptEvent() : bad event format") Timber.e("## decryptEvent() : bad event format")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_EVENT_FORMAT_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)) MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
} }


val cipherText = olmEventContent.ciphertext ?: run { val cipherText = olmEventContent.ciphertext ?: run {
Timber.e("## decryptEvent() : missing cipher text") Timber.e("## decryptEvent() : missing cipher text")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }


val senderKey = olmEventContent.senderKey ?: run { val senderKey = olmEventContent.senderKey ?: run {
Timber.e("## decryptEvent() : missing sender key") Timber.e("## decryptEvent() : missing sender key")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
} }


val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run { val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients") Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)) MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
} }


// The message for myUser // The message for myUser
@ -74,14 +73,14 @@ internal class MXOlmDecryption(


if (decryptedPayload == null) { if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val payloadString = convertFromUTF8(decryptedPayload) val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) { if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }


val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@ -89,73 +88,72 @@ internal class MXOlmDecryption(


if (payload == null) { if (payload == null) {
Timber.e("## decryptEvent failed : null payload") Timber.e("## decryptEvent failed : null payload")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }


val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
Timber.e("## decryptEvent() : bad olmPayloadContent format") Timber.e("## decryptEvent() : bad olmPayloadContent format")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)) MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
} }


if (olmPayloadContent.recipient.isNullOrBlank()) { if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
MXCryptoError.UNABLE_TO_DECRYPT, reason)) reason))
} }


if (olmPayloadContent.recipient != credentials.userId) { if (olmPayloadContent.recipient != credentials.userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ${olmPayloadContent.recipient} does not match our id ${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, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
} }


val recipientKeys = olmPayloadContent.recipient_keys ?: run { val recipientKeys = olmPayloadContent.recipient_keys ?: run {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack") 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, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
} }


val ed25519 = recipientKeys["ed25519"] val ed25519 = recipientKeys["ed25519"]


if (ed25519 != olmDevice.deviceEd25519Key) { if (ed25519 != olmDevice.deviceEd25519Key) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON)) MXCryptoError.BAD_RECIPIENT_KEY_REASON))
} }


if (olmPayloadContent.sender.isNullOrBlank()) { if (olmPayloadContent.sender.isNullOrBlank()) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") 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, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
} }


if (olmPayloadContent.sender != event.senderId) { if (olmPayloadContent.sender != event.senderId) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported 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, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
} }


if (olmPayloadContent.room_id != event.roomId) { if (olmPayloadContent.room_id != event.roomId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${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, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
} }


val keys = olmPayloadContent.keys ?: run { val keys = olmPayloadContent.keys ?: run {
Timber.e("## decryptEvent failed : null keys") Timber.e("## decryptEvent failed : null keys")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }


val result = MXEventDecryptionResult() return Try.just(MXEventDecryptionResult(
result.clearEvent = payload clearEvent = payload,
result.senderCurve25519Key = senderKey senderCurve25519Key = senderKey,
result.claimedEd25519Key = keys["ed25519"] claimedEd25519Key = keys["ed25519"]

))
return result
} }


/** /**

View File

@ -14,32 +14,32 @@
* limitations under the License. * 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 import im.vector.matrix.android.api.util.JsonDict


/** /**
* This class represents the decryption result. * This class represents the decryption result.
*/ */
data class MXDecryptionResult( data class OlmDecryptionResult(
/** /**
* The decrypted payload (with properties 'type', 'content') * 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: * keys that the sender of the event claims ownership of:
* map from key type to base64-encoded key. * 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. * 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). * 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( data class MXEncryptEventContentResult(
/** /**
* The event content * The encrypted event content
*/ */
val eventContent: Content, val eventContent: Content,
/** /**

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getById
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
@ -47,6 +48,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
* Memory cache, to correctly release JNI objects * Memory cache, to correctly release JNI objects
* ========================================================================================== */ * ========================================================================================== */


// A realm instance, for faster future getInstance. Do not use it
private var realmLocker: Realm? = null

// The olm account // The olm account
private var olmAccount: OlmAccount? = null private var olmAccount: OlmAccount? = null


@ -86,6 +90,8 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
} }


override fun open() { override fun open() {
realmLocker = Realm.getInstance(realmConfiguration)

// Ensure CryptoMetadataEntity is inserted in DB // Ensure CryptoMetadataEntity is inserted in DB
doWithRealm(realmConfiguration) { realm -> doWithRealm(realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst() var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
@ -131,6 +137,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
inboundGroupSessionToRelease.clear() inboundGroupSessionToRelease.clear()


olmAccount?.releaseAccount() olmAccount?.releaseAccount()

realmLocker?.close()
realmLocker = null
} }


override fun storeDeviceId(deviceId: String) { override fun storeDeviceId(deviceId: String) {

View File

@ -17,11 +17,11 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline


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

View File

@ -17,15 +17,14 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync


import android.text.TextUtils 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.Event
import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent 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.CryptoManager
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService 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.SyncResponse
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse
import timber.log.Timber import timber.log.Timber
@ -40,7 +39,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
// Decrypt event if necessary // Decrypt event if necessary
decryptEvent(event, null) decryptEvent(event, null)
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE) if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") { && event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
} else { } else {
sasVerificationService.onToDeviceEvent(event) sasVerificationService.onToDeviceEvent(event)
@ -66,8 +65,8 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
var result: MXEventDecryptionResult? = null var result: MXEventDecryptionResult? = null
try { try {
result = cryptoManager.decryptEvent(event, timelineId ?: "") result = cryptoManager.decryptEvent(event, timelineId ?: "")
} catch (exception: MXDecryptionException) { } catch (exception: MXCryptoError) {
event.setCryptoError(exception.cryptoError) event.setCryptoError(exception)
} }


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


val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
@ -77,7 +82,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, null, view) return@longClickListener callback?.onEventLongClicked(informationData, null, view)
?: false ?: false
} }
} }
else -> null else -> null

View File

@ -83,7 +83,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
NoticeItem_() NoticeItem_()
.avatarRenderer(avatarRenderer) .avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.noticeText("{ \"type\": ${event.root.type} }") .noticeText("{ \"type\": ${event.root.getClearType()} }")
.highlighted(highlight) .highlighted(highlight)
.baseCallback(callback) .baseCallback(callback)
} }

View File

@ -28,6 +28,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.utils.DimensionUtils.dpToPx import im.vector.riotx.core.utils.DimensionUtils.dpToPx
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject


@ -94,6 +95,11 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
val fullSize = contentUrlResolver.resolveFullSize(data.url) val fullSize = contentUrlResolver.resolveFullSize(data.url)
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)


if (fullSize.isNullOrBlank() || thumbnail.isNullOrBlank()) {
Timber.w("Invalid urls")
return
}

// TODO DECRYPT_FILE Decrypt file // TODO DECRYPT_FILE Decrypt file
imageView.showImage( imageView.showImage(
Uri.parse(thumbnail), Uri.parse(thumbnail),