Crypto: continue threading rework. WIP to shash

This commit is contained in:
ganfra 2019-06-05 22:18:16 +02:00
parent e125862794
commit 6b0ab10231
27 changed files with 472 additions and 800 deletions

View File

@ -41,7 +41,7 @@ class MXCryptoError(var code: String,
* @return true if the current error is an olm one. * @return true if the current error is an olm one.
*/ */
val isOlmError: Boolean val isOlmError: Boolean
get() = TextUtils.equals(OLM_ERROR_CODE, code) get() = OLM_ERROR_CODE == code




/** /**
@ -98,6 +98,7 @@ class MXCryptoError(var code: String,
const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY" const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY"
const val OLM_ERROR_CODE = "OLM_ERROR_CODE" const val OLM_ERROR_CODE = "OLM_ERROR_CODE"
const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE" const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE"
const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX"


/** /**
* short error reasons * short error reasons

View File

@ -19,10 +19,8 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto


import android.content.Context import android.content.Context
import android.os.Handler
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
@ -70,10 +68,7 @@ import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -245,40 +240,26 @@ internal class CryptoManager(
* @param isInitialSync true if it starts from an initial sync * @param isInitialSync true if it starts from an initial sync
*/ */
fun start(isInitialSync: Boolean) { fun start(isInitialSync: Boolean) {
if (isStarting.get()) { CoroutineScope(coroutineDispatchers.crypto).launch {
return internalStart(isInitialSync)
}
} }


// do not start if there is not network connection private suspend fun internalStart(isInitialSync: Boolean) {
// TODO if (isStarted.get() || isStarting.get()) {
//if (null != mNetworkConnectivityReceiver && !mNetworkConnectivityReceiver!!.isConnected()) { return
// // wait that a valid network connection is retrieved }
// mNetworkConnectivityReceiver!!.removeEventListener(mNetworkListener)
// mNetworkConnectivityReceiver!!.addEventListener(mNetworkListener)
// return
//}

isStarting.set(true) isStarting.set(true)
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
CoroutineScope(coroutineDispatchers.crypto).launch {
uploadDeviceKeys() uploadDeviceKeys()
.flatMap { .flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
oneTimeKeysUploader.maybeUploadOneTimeKeys()
}
.handleError {
Handler().postDelayed(
{
if (!isStarted()) {
isStarting.set(false)
start(isInitialSync)
}
}, 1000
)
}
.fold( .fold(
{ {
Timber.e("Start failed: $it") Timber.e("Start failed: $it")
delay(1000)
isStarting.set(false)
internalStart(isInitialSync)
}, },
{ {
isStarting.set(false) isStarting.set(false)
@ -295,7 +276,6 @@ internal class CryptoManager(
} }
) )
} }
}


/** /**
* Close the crypto * Close the crypto
@ -447,7 +427,10 @@ internal class CryptoManager(
* @param membersId list of members to start tracking their devices * @param membersId list of members to start tracking their devices
* @return true if the operation succeeds. * @return true if the operation succeeds.
*/ */
private suspend fun setEncryptionInRoom(roomId: String, algorithm: String?, inhibitDeviceQuery: Boolean, membersId: List<String>): Boolean { private suspend fun setEncryptionInRoom(roomId: String,
algorithm: String?,
inhibitDeviceQuery: Boolean,
membersId: List<String>): Boolean {
// If we already have encryption in this room, we should ignore this event // If we already have encryption in this room, we should ignore this event
// (for now at least. Maybe we should alert the user somehow?) // (for now at least. Maybe we should alert the user somehow?)
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId) val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
@ -555,13 +538,11 @@ internal class CryptoManager(
eventType: String, eventType: String,
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) { callback: MatrixCallback<MXEncryptEventContentResult>) {
// wait that the crypto is really started CoroutineScope(coroutineDispatchers.crypto).launch {
if (!isStarted()) { if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init") Timber.v("## encryptEventContent() : wait after e2e init")
start(false) internalStart(false)
return
} }
CoroutineScope(coroutineDispatchers.crypto).launch {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
var alg = synchronized(roomEncryptors) { var alg = synchronized(roomEncryptors) {
roomEncryptors[roomId] roomEncryptors[roomId]
@ -580,22 +561,19 @@ internal class CryptoManager(
if (safeAlgorithm != null) { if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts") Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds, object : MatrixCallback<Content> { safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
override fun onSuccess(data: Content) { .fold(
{ callback.onFailure(it) },
{
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms") Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED)) callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
} }

)
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
} else { } else {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
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(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, reason))) MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
} }
@ -616,14 +594,14 @@ internal class CryptoManager(
Timber.e("## decryptEvent : empty event content") Timber.e("## decryptEvent : empty event content")
return null return null
} }
return runBlocking {
withContext(coroutineDispatchers.crypto) {
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason)) throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else { } else {
return runBlocking {
withContext(coroutineDispatchers.crypto) {
alg.decryptEvent(event, timeline) alg.decryptEvent(event, timeline)
} }
} }
@ -661,19 +639,15 @@ internal class CryptoManager(
*/ */
private fun onRoomKeyEvent(event: Event) { private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>()!! val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>()!!

if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) { if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
Timber.e("## onRoomKeyEvent() : missing fields") Timber.e("## onRoomKeyEvent() : missing fields")
return return
} }

val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)

if (alg == null) {
if (null == alg) {
Timber.e("## onRoomKeyEvent() : Unable to handle keys for " + roomKeyContent.algorithm) Timber.e("## onRoomKeyEvent() : Unable to handle keys for " + roomKeyContent.algorithm)
return return
} }

alg.onRoomKeyEvent(event, keysBackup) alg.onRoomKeyEvent(event, keysBackup)
} }


@ -936,7 +910,7 @@ internal class CryptoManager(
* @param roomId the room id * @param roomId the room id
* @return true if the client should encrypt messages only for the verified devices. * @return true if the client should encrypt messages only for the verified devices.
*/ */
// TODO add this info in CryptoRoomEntity? // TODO add this info in CryptoRoomEntity?
override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean { override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean {
return if (null != roomId) { return if (null != roomId) {
cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
@ -984,7 +958,7 @@ internal class CryptoManager(
setRoomBlacklistUnverifiedDevices(roomId, false) setRoomBlacklistUnverifiedDevices(roomId, false)
} }


// TODO Check if this method is still necessary // TODO Check if this method is still necessary
/** /**
* Cancel any earlier room key request * Cancel any earlier room key request
* *
@ -1057,7 +1031,7 @@ internal class CryptoManager(
return unknownDevices return unknownDevices
} }


/* ========================================================================================== /* ==========================================================================================
* DEBUG INFO * DEBUG INFO
* ========================================================================================== */ * ========================================================================================== */



View File

@ -169,7 +169,7 @@ internal class CryptoModule {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
EnsureOlmSessionsForDevicesAction(get(), get(), get(), get()) EnsureOlmSessionsForDevicesAction(get(), get())
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
@ -192,7 +192,7 @@ internal class CryptoModule {
// Factories // Factories
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
MXMegolmDecryptionFactory( MXMegolmDecryptionFactory(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get() get(), get(), get(), get(), get(), get(), get(), get(), get()
) )
} }



View File

@ -27,21 +27,21 @@ import timber.log.Timber
import java.util.* import java.util.*


internal class IncomingRoomKeyRequestManager( internal class IncomingRoomKeyRequestManager(
private val mCredentials: Credentials, private val credentials: Credentials,
private val mCryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val mRoomDecryptorProvider: RoomDecryptorProvider) { private val roomDecryptorProvider: RoomDecryptorProvider) {




// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync. // we received in the current sync.
private val mReceivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>() private val receivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
private val mReceivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>() private val receivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()


// the listeners // the listeners
private val mRoomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet() private val roomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()


init { init {
mReceivedRoomKeyRequests.addAll(mCryptoStore.getPendingIncomingRoomKeyRequests()) receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
} }


/** /**
@ -52,13 +52,12 @@ internal class IncomingRoomKeyRequestManager(
*/ */
fun onRoomKeyRequestEvent(event: Event) { fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>() val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()

when (roomKeyShare?.action) { when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> synchronized(mReceivedRoomKeyRequests) { RoomKeyShare.ACTION_SHARE_REQUEST -> synchronized(receivedRoomKeyRequests) {
mReceivedRoomKeyRequests.add(IncomingRoomKeyRequest(event)) receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
} }
RoomKeyShare.ACTION_SHARE_CANCELLATION -> synchronized(mReceivedRoomKeyRequestCancellations) { RoomKeyShare.ACTION_SHARE_CANCELLATION -> synchronized(receivedRoomKeyRequestCancellations) {
mReceivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event)) receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
} }
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action) else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action)
} }
@ -71,10 +70,10 @@ internal class IncomingRoomKeyRequestManager(
fun processReceivedRoomKeyRequests() { fun processReceivedRoomKeyRequests() {
var receivedRoomKeyRequests: List<IncomingRoomKeyRequest>? = null var receivedRoomKeyRequests: List<IncomingRoomKeyRequest>? = null


synchronized(mReceivedRoomKeyRequests) { synchronized(this.receivedRoomKeyRequests) {
if (!mReceivedRoomKeyRequests.isEmpty()) { if (this.receivedRoomKeyRequests.isNotEmpty()) {
receivedRoomKeyRequests = ArrayList(mReceivedRoomKeyRequests) receivedRoomKeyRequests = ArrayList(this.receivedRoomKeyRequests)
mReceivedRoomKeyRequests.clear() this.receivedRoomKeyRequests.clear()
} }
} }


@ -88,7 +87,7 @@ internal class IncomingRoomKeyRequestManager(


Timber.v("m.room_key_request from " + userId + ":" + deviceId + " for " + roomId + " / " + body.sessionId + " id " + request.requestId) Timber.v("m.room_key_request from " + userId + ":" + deviceId + " for " + roomId + " / " + body.sessionId + " id " + request.requestId)


if (!TextUtils.equals(mCredentials.userId, userId)) { if (!TextUtils.equals(credentials.userId, userId)) {
// TODO: determine if we sent this device the keys already: in // TODO: determine if we sent this device the keys already: in
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now") Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
return return
@ -100,7 +99,7 @@ internal class IncomingRoomKeyRequestManager(
// if we don't have a decryptor for this room/alg, we don't have // if we don't have a decryptor for this room/alg, we don't have
// the keys for the requested events, and can drop the requests. // the keys for the requested events, and can drop the requests.


val decryptor = mRoomDecryptorProvider.getRoomDecryptor(roomId, alg) val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)


if (null == decryptor) { if (null == decryptor) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId") Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
@ -109,52 +108,52 @@ internal class IncomingRoomKeyRequestManager(


if (!decryptor.hasKeysForKeyRequest(request)) { if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session " + body.sessionId!!) Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session " + body.sessionId!!)
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
continue continue
} }


if (TextUtils.equals(deviceId, mCredentials.deviceId) && TextUtils.equals(mCredentials.userId, userId)) { if (TextUtils.equals(deviceId, credentials.deviceId) && TextUtils.equals(credentials.userId, userId)) {
Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored") Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored")
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
continue continue
} }


request.share = Runnable { request.share = Runnable {
decryptor.shareKeysWithDevice(request) decryptor.shareKeysWithDevice(request)
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
} }


request.ignore = Runnable { mCryptoStore.deleteIncomingRoomKeyRequest(request) } request.ignore = Runnable { cryptoStore.deleteIncomingRoomKeyRequest(request) }


// if the device is verified already, share the keys // if the device is verified already, share the keys
val device = mCryptoStore.getUserDevice(deviceId!!, userId) val device = cryptoStore.getUserDevice(deviceId!!, userId)


if (null != device) { if (null != device) {
if (device.isVerified) { if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys") Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
request.share?.run() request.share?.run()
continue continue
} }


if (device.isBlocked) { if (device.isBlocked) {
Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored") Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored")
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
continue continue
} }
} }


mCryptoStore.storeIncomingRoomKeyRequest(request) cryptoStore.storeIncomingRoomKeyRequest(request)
onRoomKeyRequest(request) onRoomKeyRequest(request)
} }
} }


var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null


synchronized(mReceivedRoomKeyRequestCancellations) { synchronized(this.receivedRoomKeyRequestCancellations) {
if (!mReceivedRoomKeyRequestCancellations.isEmpty()) { if (!this.receivedRoomKeyRequestCancellations.isEmpty()) {
receivedRoomKeyRequestCancellations = mReceivedRoomKeyRequestCancellations.toList() receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
mReceivedRoomKeyRequestCancellations.clear() this.receivedRoomKeyRequestCancellations.clear()
} }
} }


@ -167,7 +166,7 @@ internal class IncomingRoomKeyRequestManager(
// about, but we don't currently have a record of that, so we just pass // about, but we don't currently have a record of that, so we just pass
// everything through. // everything through.
onRoomKeyRequestCancellation(request) onRoomKeyRequestCancellation(request)
mCryptoStore.deleteIncomingRoomKeyRequest(request) cryptoStore.deleteIncomingRoomKeyRequest(request)
} }
} }
} }
@ -178,8 +177,8 @@ internal class IncomingRoomKeyRequestManager(
* @param request the request * @param request the request
*/ */
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) { private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
synchronized(mRoomKeysRequestListeners) { synchronized(roomKeysRequestListeners) {
for (listener in mRoomKeysRequestListeners) { for (listener in roomKeysRequestListeners) {
try { try {
listener.onRoomKeyRequest(request) listener.onRoomKeyRequest(request)
} catch (e: Exception) { } catch (e: Exception) {
@ -197,8 +196,8 @@ internal class IncomingRoomKeyRequestManager(
* @param request the cancellation request * @param request the cancellation request
*/ */
private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) { private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
synchronized(mRoomKeysRequestListeners) { synchronized(roomKeysRequestListeners) {
for (listener in mRoomKeysRequestListeners) { for (listener in roomKeysRequestListeners) {
try { try {
listener.onRoomKeyRequestCancellation(request) listener.onRoomKeyRequestCancellation(request)
} catch (e: Exception) { } catch (e: Exception) {
@ -210,14 +209,14 @@ internal class IncomingRoomKeyRequestManager(
} }


fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) { fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(mRoomKeysRequestListeners) { synchronized(roomKeysRequestListeners) {
mRoomKeysRequestListeners.add(listener) roomKeysRequestListeners.add(listener)
} }
} }


fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) { fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(mRoomKeysRequestListeners) { synchronized(roomKeysRequestListeners) {
mRoomKeysRequestListeners.remove(listener) roomKeysRequestListeners.remove(listener)
} }
} }



View File

@ -28,20 +28,19 @@ import timber.log.Timber
import java.util.* import java.util.*


internal class OneTimeKeysUploader( internal class OneTimeKeysUploader(
private val mCredentials: Credentials, private val credentials: Credentials,
private val mOlmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val mObjectSigner: ObjectSigner, private val objectSigner: ObjectSigner,
private val mUploadKeysTask: UploadKeysTask private val uploadKeysTask: UploadKeysTask
) { ) {
// tell if there is a OTK check in progress // tell if there is a OTK check in progress
private var mOneTimeKeyCheckInProgress = false private var oneTimeKeyCheckInProgress = false


// last OTK check timestamp // last OTK check timestamp
private var mLastOneTimeKeyCheck: Long = 0 private var lastOneTimeKeyCheck: Long = 0
private var mOneTimeKeyCount: Int? = null private var oneTimeKeyCount: Int? = null


var mLastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null private var lastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
private set


/** /**
* Stores the current one_time_key count which will be handled later (in a call of * Stores the current one_time_key count which will be handled later (in a call of
@ -50,7 +49,7 @@ internal class OneTimeKeysUploader(
* @param currentCount the new count * @param currentCount the new count
*/ */
fun updateOneTimeKeyCount(currentCount: Int) { fun updateOneTimeKeyCount(currentCount: Int) {
mOneTimeKeyCount = currentCount oneTimeKeyCount = currentCount
} }




@ -58,19 +57,19 @@ internal class OneTimeKeysUploader(
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*/ */
suspend fun maybeUploadOneTimeKeys(): Try<Unit> { suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
if (mOneTimeKeyCheckInProgress) { if (oneTimeKeyCheckInProgress) {
return Try.just(Unit) return Try.just(Unit)
} }
if (System.currentTimeMillis() - mLastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently. // we've done a key upload recently.
return Try.just(Unit) return Try.just(Unit)
} }


mLastOneTimeKeyCheck = System.currentTimeMillis() lastOneTimeKeyCheck = System.currentTimeMillis()
mOneTimeKeyCheckInProgress = true oneTimeKeyCheckInProgress = true


// We then check how many keys we can store in the Account object. // We then check how many keys we can store in the Account object.
val maxOneTimeKeys = mOlmDevice.getMaxNumberOfOneTimeKeys() val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()


// Try to keep at most half that number on the server. This leaves the // Try to keep at most half that number on the server. This leaves the
// rest of the slots free to hold keys that have been claimed from the // rest of the slots free to hold keys that have been claimed from the
@ -79,12 +78,12 @@ internal class OneTimeKeysUploader(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
if (mOneTimeKeyCount != null) { val result = if (oneTimeKeyCount != null) {
return uploadOTK(mOneTimeKeyCount!!, keyLimit) uploadOTK(oneTimeKeyCount!!, keyLimit)
} else { } else {
// ask the server how many keys we have // ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, mCredentials.deviceId!!) val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
return mUploadKeysTask.execute(uploadKeysParams) uploadKeysTask.execute(uploadKeysParams)
.flatMap { .flatMap {
// We need to keep a pool of one time public keys on the server so that // We need to keep a pool of one time public keys on the server so that
// other devices can start conversations with us. But we can only store // other devices can start conversations with us. But we can only store
@ -97,16 +96,22 @@ internal class OneTimeKeysUploader(
// private keys clogging up our local storage. // private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of // So we need some kind of engineering compromise to balance all of
// these factors. // these factors.
// TODO Why we do not set mOneTimeKeyCount here? // TODO Why we do not set oneTimeKeyCount here?
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also) // TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit) uploadOTK(keyCount, keyLimit)
} }
}
return result
.map {
Timber.v("## uploadKeys() : success")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
.handleError { .handleError {
Timber.e(it, "## uploadKeys() : failed") Timber.e(it, "## uploadKeys() : failed")
mOneTimeKeyCount = null oneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false oneTimeKeyCheckInProgress = false
}
} }
} }


@ -117,27 +122,17 @@ internal class OneTimeKeysUploader(
* @param keyLimit the limit * @param keyLimit the limit
*/ */
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> {
return uploadLoop(keyCount, keyLimit)
}

/**
* OTK upload loop
*
* @param keyCount the number of key to generate
* @param keyLimit the limit
*/
private suspend fun uploadLoop(keyCount: Int, keyLimit: Int): Try<Unit> {
if (keyLimit <= keyCount) { if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done. // If we don't need to generate any more keys then we are done.
return Try.just(Unit) return Try.just(Unit)
} }


val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
mOlmDevice.generateOneTimeKeys(keysThisLoop) olmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys() return uploadOneTimeKeys()
.flatMap { .flatMap {
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadLoop(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
} else { } else {
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")) Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519"))
@ -149,7 +144,7 @@ internal class OneTimeKeysUploader(
* Upload my user's one time keys. * Upload my user's one time keys.
*/ */
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> { private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
val oneTimeKeys = mOlmDevice.getOneTimeKeys() val oneTimeKeys = olmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>() val oneTimeJson = HashMap<String, Any>()


val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY] val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
@ -162,7 +157,7 @@ internal class OneTimeKeysUploader(
// the key is also signed // the key is also signed
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k)


k["signatures"] = mObjectSigner.signObject(canonicalJson) k["signatures"] = objectSigner.signObject(canonicalJson)


oneTimeJson["signed_curve25519:$key_id"] = k oneTimeJson["signed_curve25519:$key_id"] = k
} }
@ -170,12 +165,12 @@ internal class OneTimeKeysUploader(


// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!) val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
return mUploadKeysTask return uploadKeysTask
.execute(uploadParams) .execute(uploadParams)
.map { .map {
mLastPublishedOneTimeKeys = oneTimeKeys lastPublishedOneTimeKeys = oneTimeKeys
mOlmDevice.markKeysAsPublished() olmDevice.markKeysAsPublished()
it it
} }
} }

View File

@ -24,15 +24,15 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
*/ */
class OutgoingRoomKeyRequest( class OutgoingRoomKeyRequest(
// RequestBody // RequestBody
var mRequestBody: RoomKeyRequestBody?, // list of recipients for the request var requestBody: RoomKeyRequestBody?, // list of recipients for the request
var mRecipients: List<Map<String, String>>, // Unique id for this request. Used for both var recipients: List<Map<String, String>>, // Unique id for this request. Used for both
// an id within the request for later pairing with a cancellation, and for // an id within the request for later pairing with a cancellation, and for
// the transaction id when sending the to_device messages to our local // the transaction id when sending the to_device messages to our local
var mRequestId: String, // current state of this request var requestId: String, // current state of this request
var mState: RequestState) { var state: RequestState) {


// transaction id for the cancellation, if any // transaction id for the cancellation, if any
var mCancellationTxnId: String? = null var cancellationTxnId: String? = null


/** /**
* Used only for log. * Used only for log.
@ -40,8 +40,8 @@ class OutgoingRoomKeyRequest(
* @return the room id. * @return the room id.
*/ */
val roomId: String? val roomId: String?
get() = if (null != mRequestBody) { get() = if (null != requestBody) {
mRequestBody!!.roomId requestBody!!.roomId
} else null } else null


/** /**
@ -50,8 +50,8 @@ class OutgoingRoomKeyRequest(
* @return the session id * @return the session id
*/ */
val sessionId: String? val sessionId: String?
get() = if (null != mRequestBody) { get() = if (null != requestBody) {
mRequestBody!!.sessionId requestBody!!.sessionId
} else null } else null


/** /**

View File

@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -88,7 +87,7 @@ internal class OutgoingRoomKeyRequestManager(
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT)) OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))




if (req?.mState == OutgoingRoomKeyRequest.RequestState.UNSENT) { if (req?.state == OutgoingRoomKeyRequest.RequestState.UNSENT) {
startTimer() startTimer()
} }
} }
@ -122,20 +121,20 @@ internal class OutgoingRoomKeyRequestManager(
?: // no request was made for this key ?: // no request was made for this key
return return


Timber.v("cancelRoomKeyRequest: requestId: " + req.mRequestId + " state: " + req.mState + " andResend: " + andResend) Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend)


if (req.mState === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING || req.mState === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND) { if (req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING || req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND) {
// nothing to do here // nothing to do here
} else if (req.mState === OutgoingRoomKeyRequest.RequestState.UNSENT || req.mState === OutgoingRoomKeyRequest.RequestState.FAILED) { } else if (req.state === OutgoingRoomKeyRequest.RequestState.UNSENT || req.state === OutgoingRoomKeyRequest.RequestState.FAILED) {
Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody") Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody")
cryptoStore.deleteOutgoingRoomKeyRequest(req.mRequestId) cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
} else if (req.mState === OutgoingRoomKeyRequest.RequestState.SENT) { } else if (req.state === OutgoingRoomKeyRequest.RequestState.SENT) {
if (andResend) { if (andResend) {
req.mState = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
} else { } else {
req.mState = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
} }
req.mCancellationTxnId = makeTxnId() req.cancellationTxnId = makeTxnId()
cryptoStore.updateOutgoingRoomKeyRequest(req) cryptoStore.updateOutgoingRoomKeyRequest(req)
sendOutgoingRoomKeyRequestCancellation(req) sendOutgoingRoomKeyRequestCancellation(req)
} }
@ -149,7 +148,6 @@ internal class OutgoingRoomKeyRequestManager(
if (sendOutgoingRoomKeyRequestsRunning) { if (sendOutgoingRoomKeyRequestsRunning) {
return return
} }

Handler().postDelayed(Runnable { Handler().postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning) { if (sendOutgoingRoomKeyRequestsRunning) {
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!") Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
@ -182,7 +180,7 @@ internal class OutgoingRoomKeyRequestManager(
return return
} }


if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.mState) { if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.state) {
sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest) sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest)
} else { } else {
sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest) sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest)
@ -195,20 +193,20 @@ internal class OutgoingRoomKeyRequestManager(
* @param request the request * @param request the request
*/ */
private fun sendOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) { private fun sendOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.mRequestBody Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody
+ " from " + request.mRecipients + " id " + request.mRequestId) + " from " + request.recipients + " id " + request.requestId)


val requestMessage = RoomKeyShareRequest() val requestMessage = RoomKeyShareRequest()
requestMessage.requestingDeviceId = cryptoStore.getDeviceId() requestMessage.requestingDeviceId = cryptoStore.getDeviceId()
requestMessage.requestId = request.mRequestId requestMessage.requestId = request.requestId
requestMessage.body = request.mRequestBody requestMessage.body = request.requestBody


sendMessageToDevices(requestMessage, request.mRecipients, request.mRequestId, object : MatrixCallback<Unit> { sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) { private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
if (request.mState !== OutgoingRoomKeyRequest.RequestState.UNSENT) { if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.mState) Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.state)
} else { } else {
request.mState = state request.state = state
cryptoStore.updateOutgoingRoomKeyRequest(request) cryptoStore.updateOutgoingRoomKeyRequest(request)
} }


@ -234,17 +232,17 @@ internal class OutgoingRoomKeyRequestManager(
* @param request the request * @param request the request
*/ */
private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest) { private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.mRequestBody Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.requestBody
+ " to " + request.mRecipients + " to " + request.recipients
+ " cancellation id " + request.mCancellationTxnId) + " cancellation id " + request.cancellationTxnId)


val roomKeyShareCancellation = RoomKeyShareCancellation() val roomKeyShareCancellation = RoomKeyShareCancellation()
roomKeyShareCancellation.requestingDeviceId = cryptoStore.getDeviceId() roomKeyShareCancellation.requestingDeviceId = cryptoStore.getDeviceId()
roomKeyShareCancellation.requestId = request.mCancellationTxnId roomKeyShareCancellation.requestId = request.cancellationTxnId


sendMessageToDevices(roomKeyShareCancellation, request.mRecipients, request.mCancellationTxnId, object : MatrixCallback<Unit> { sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
private fun onDone() { private fun onDone() {
cryptoStore.deleteOutgoingRoomKeyRequest(request.mRequestId) cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
sendOutgoingRoomKeyRequestsRunning = false sendOutgoingRoomKeyRequestsRunning = false
startTimer() startTimer()
} }
@ -252,13 +250,13 @@ internal class OutgoingRoomKeyRequestManager(


override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done") Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done")
val resend = request.mState === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND val resend = request.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND


onDone() onDone()


// Resend the request with a new ID // Resend the request with a new ID
if (resend) { if (resend) {
sendRoomKeyRequest(request.mRequestBody, request.mRecipients) sendRoomKeyRequest(request.requestBody, request.recipients)
} }
} }



View File

@ -43,38 +43,36 @@ internal class RoomDecryptorProvider(
*/ */
fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? { fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
// sanity check // sanity check
if (algorithm.isNullOrEmpty() || roomId.isNullOrEmpty()) { if (algorithm.isNullOrEmpty()) {
Timber.e("## getRoomDecryptor() : null algorithm") Timber.e("## getRoomDecryptor() : null algorithm")
return null return null
} }

if(roomId != null && roomId.isNotEmpty()) {
var alg: IMXDecrypting?
synchronized(roomDecryptors) { synchronized(roomDecryptors) {
if (!roomDecryptors.containsKey(roomId)) { if (!roomDecryptors.containsKey(roomId)) {
roomDecryptors[roomId!!] = HashMap() roomDecryptors[roomId] = HashMap()
}

alg = roomDecryptors[roomId]!![algorithm]
} }
val alg = roomDecryptors[roomId]?.get(algorithm)
if (alg != null) { if (alg != null) {
return alg return alg
} }
}
}
val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm) val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)
if (decryptingClass) { if (decryptingClass) {
alg = when (algorithm) { val alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create() MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create()
else -> olmDecryptionFactory.create() else -> olmDecryptionFactory.create()
} }
if (null != alg) { if (roomId != null && !TextUtils.isEmpty(roomId)) {
if (!TextUtils.isEmpty(roomId)) {
synchronized(roomDecryptors) { synchronized(roomDecryptors) {
roomDecryptors[roomId]!!.put(algorithm!!, alg!!) roomDecryptors[roomId]?.put(algorithm, alg)
}
}
} }
} }
return alg return alg
} }
return null
}


fun getRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? { fun getRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
if (roomId == null || algorithm == null) { if (roomId == null || algorithm == null) {

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
@ -31,12 +32,10 @@ import timber.log.Timber
import java.util.* import java.util.*


internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDevice, internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDevice,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask, private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor) {




fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>, callback: MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>>?) { suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): Try<MXUsersDevicesMap<MXOlmSessionResult>> {
val devicesWithoutSession = ArrayList<MXDeviceInfo>() val devicesWithoutSession = ArrayList<MXDeviceInfo>()


val results = MXUsersDevicesMap<MXOlmSessionResult>() val results = MXUsersDevicesMap<MXOlmSessionResult>()
@ -62,8 +61,7 @@ internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDev
} }


if (devicesWithoutSession.size == 0) { if (devicesWithoutSession.size == 0) {
callback?.onSuccess(results) return Try.just(results)
return
} }


// Prepare the request for claiming one-time keys // Prepare the request for claiming one-time keys
@ -83,67 +81,42 @@ internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDev


Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")



val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
oneTimeKeysForUsersDeviceTask return oneTimeKeysForUsersDeviceTask
.configureWith(ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)) .execute(claimParams)
.dispatchTo(object : MatrixCallback<MXUsersDevicesMap<MXKey>> { .map {
override fun onSuccess(data: MXUsersDevicesMap<MXKey>) { Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it")
try {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $data")

for (userId in userIds) { for (userId in userIds) {
val deviceInfos = devicesByUser[userId] val deviceInfos = devicesByUser[userId]

for (deviceInfo in deviceInfos!!) { for (deviceInfo in deviceInfos!!) {

var oneTimeKey: MXKey? = null var oneTimeKey: MXKey? = null

val deviceIds = it.getUserDeviceIds(userId)
val deviceIds = data.getUserDeviceIds(userId)

if (null != deviceIds) { if (null != deviceIds) {
for (deviceId in deviceIds) { for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(deviceId, userId) val olmSessionResult = results.getObject(deviceId, userId)

if (olmSessionResult!!.mSessionId != null) {
if (null != olmSessionResult!!.mSessionId) {
// We already have a result for this device // We already have a result for this device
continue continue
} }

val key = it.getObject(deviceId, userId)
val key = data.getObject(deviceId, userId) if (key?.type == oneTimeKeyAlgorithm) {

if (TextUtils.equals(key!!.type, oneTimeKeyAlgorithm)) {
oneTimeKey = key oneTimeKey = key
} }

if (oneTimeKey == null) {
if (null == oneTimeKey) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId) + " for device " + userId + " : " + deviceId)
continue continue
} }

// Update the result for this device in results // Update the result for this device in results
olmSessionResult.mSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo) olmSessionResult.mSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
} }
} }
} }
} }
} catch (e: Exception) { results
Timber.e(e, "## ensureOlmSessionsForDevices() " + e.message)
} }

callback?.onSuccess(results)
} }


override fun onFailure(failure: Throwable) {
Timber.e(failure, "## ensureOlmSessionsForUsers(): claimOneTimeKeysForUsersDevices request failed")

callback?.onFailure(failure)
}
})
.executeBy(taskExecutor)
}


private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? { private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
var sessionId: String? = null var sessionId: String? = null



View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions


import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
@ -32,15 +33,10 @@ internal class EnsureOlmSessionsForUsersAction(private val olmDevice: MXOlmDevic


/** /**
* Try to make sure we have established olm sessions for the given users. * Try to make sure we have established olm sessions for the given users.
* It must be called in getEncryptingThreadHandler() thread.
* The callback is called in the UI thread.
*
* @param users a list of user ids. * @param users a list of user ids.
* @param callback the asynchronous callback
*/ */
fun handle(users: List<String>, callback: MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>>) { suspend fun handle(users: List<String>) : Try<MXUsersDevicesMap<MXOlmSessionResult>> {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")

val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>() val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()


for (userId in users) { for (userId in users) {
@ -64,7 +60,6 @@ internal class EnsureOlmSessionsForUsersAction(private val olmDevice: MXOlmDevic
devicesByUser[userId]!!.add(device) devicesByUser[userId]!!.add(device)
} }
} }

return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
ensureOlmSessionsForDevicesAction.handle(devicesByUser, callback)
} }
} }

View File

@ -37,7 +37,7 @@ internal interface IMXDecrypting {
* @throws MXDecryptionException the decryption failure reason * @throws MXDecryptionException the decryption failure reason
*/ */
@Throws(MXDecryptionException::class) @Throws(MXDecryptionException::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?


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

View File

@ -17,6 +17,7 @@


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.MatrixCallback 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


@ -31,7 +32,7 @@ internal interface IMXEncrypting {
* @param eventContent the content of the event. * @param eventContent the content of the event.
* @param eventType the type of the event. * @param eventType the type of the event.
* @param userIds the room members the event will be sent to. * @param userIds the room members the event will be sent to.
* @param callback the asynchronous callback * @return the encrypted content wrapped by [Try]
*/ */
fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>, callback: MatrixCallback<Content>) suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content>
} }

View File

@ -18,26 +18,19 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm


import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.MatrixCallback 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.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.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.*
import im.vector.matrix.android.internal.crypto.DeviceListManager
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.MXOlmDevice
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
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.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.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
@ -69,7 +62,7 @@ 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) @Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
return decryptEvent(event, timeline, true) return decryptEvent(event, timeline, true)
} }


@ -90,7 +83,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} catch (e: MXDecryptionException) { } catch (e: MXDecryptionException) {
cryptoError = e.cryptoError cryptoError = e.cryptoError
} }

// the decryption succeeds // the decryption succeeds
if (decryptGroupMessageResult?.payload != null && cryptoError == null) { if (decryptGroupMessageResult?.payload != null && cryptoError == null) {
eventDecryptionResult = MXEventDecryptionResult() eventDecryptionResult = MXEventDecryptionResult()
@ -105,9 +97,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!! eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!!
} else if (cryptoError != null) { } else if (cryptoError != null) {
if (cryptoError.isOlmError) { if (cryptoError.isOlmError) {
if (TextUtils.equals("UNKNOWN_MESSAGE_INDEX", cryptoError.message)) { if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
addEventToPendingList(event, timeline) addEventToPendingList(event, timeline)

if (requestKeysOnFail) { if (requestKeysOnFail) {
requestKeysForEvent(event) requestKeysForEvent(event)
} }
@ -176,20 +167,19 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/ */
private fun addEventToPendingList(event: Event, timelineId: String) { private fun addEventToPendingList(event: Event, timelineId: String) {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!! val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"


val k = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}" if (!pendingEvents.containsKey(pendingEventsKey)) {

pendingEvents[pendingEventsKey] = HashMap()
if (!pendingEvents.containsKey(k)) {
pendingEvents[k] = HashMap()
} }


if (!pendingEvents[k]!!.containsKey(timelineId)) { if (!pendingEvents[pendingEventsKey]!!.containsKey(timelineId)) {
pendingEvents[k]!!.put(timelineId, ArrayList<Event>()) pendingEvents[pendingEventsKey]!![timelineId] = ArrayList()
} }


if (pendingEvents[k]!![timelineId]!!.indexOf(event) < 0) { if (pendingEvents[pendingEventsKey]!![timelineId]!!.indexOf(event) < 0) {
Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId) Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId)
pendingEvents[k]!![timelineId]!!.add(event) pendingEvents[pendingEventsKey]!![timelineId]!!.add(event)
} }
} }


@ -275,7 +265,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
* @param sessionId the session id * @param sessionId the session id
*/ */
override fun onNewSession(senderKey: String, sessionId: String) { override fun onNewSession(senderKey: String, sessionId: String) {
val k = "$senderKey|$sessionId" //TODO see how to handle this
/*val k = "$senderKey|$sessionId"


val pending = pendingEvents[k] val pending = pendingEvents[k]


@ -309,6 +300,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
} }
} }
*/
} }


override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
@ -323,28 +315,25 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
val userId = request.userId!! val userId = request.userId!!
CoroutineScope(coroutineDispatchers.crypto).launch { CoroutineScope(coroutineDispatchers.crypto).launch {
deviceListManager.downloadKeys(listOf(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { deviceListManager
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) { .downloadKeys(listOf(userId), false)
.flatMap {
val deviceId = request.deviceId val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId) val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)

if (deviceInfo == null) {
if (null != deviceInfo) { throw RuntimeException()
val body = request.requestBody } else {

val devicesByUser = HashMap<String, List<MXDeviceInfo>>() val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo)) devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))

ensureOlmSessionsForDevicesAction
ensureOlmSessionsForDevicesAction.handle(devicesByUser, object : MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>> { .handle(devicesByUser)
override fun onSuccess(data: MXUsersDevicesMap<MXOlmSessionResult>) { .flatMap {
val olmSessionResult = data.getObject(deviceId, userId) val body = request.requestBody

val olmSessionResult = it.getObject(deviceId, userId)
if (olmSessionResult?.mSessionId == null) { if (olmSessionResult?.mSessionId == null) {
// no session with this device, probably because there // no session with this device, probably because there
// were no one-time keys. // were no one-time keys.
// Try.just(Unit)
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
return
} }
Timber.v("## shareKeysWithDevice() : sharing keys for session " + body!!.senderKey + "|" + body.sessionId Timber.v("## shareKeysWithDevice() : sharing keys for session " + body!!.senderKey + "|" + body.sessionId
+ " with device " + userId + ":" + deviceId) + " with device " + userId + ":" + deviceId)
@ -358,35 +347,14 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val sendToDeviceMap = MXUsersDevicesMap<Any>() val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(encodedPayload, userId, deviceId) sendToDeviceMap.setObject(encodedPayload, userId, deviceId)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId") Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")

val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask sendToDeviceTask.execute(sendToDeviceParams)
.execute(sendToDeviceParams)
.fold(
{
Timber.e(it, "## shareKeysWithDevice() : sendToDevice $userId:$deviceId failed")
}
, {
Timber.v("## shareKeysWithDevice() : sent to $userId:$deviceId")
}
)
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareKeysWithDevice() : ensureOlmSessionsForDevices $userId:$deviceId failed")
}
})
} else {
Timber.e("## shareKeysWithDevice() : ensureOlmSessionsForDevices $userId:$deviceId not found")
}
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareKeysWithDevice() : downloadKeys $userId failed")
}
})
} }




} }
}
}
}

} }

View File

@ -24,7 +24,6 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
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.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers


internal class MXMegolmDecryptionFactory(private val mCredentials: Credentials, internal class MXMegolmDecryptionFactory(private val mCredentials: Credentials,
@ -35,8 +34,7 @@ internal class MXMegolmDecryptionFactory(private val mCredentials: Credentials,
private val mEnsureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val mEnsureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val mCryptoStore: IMXCryptoStore, private val mCryptoStore: IMXCryptoStore,
private val mSendToDeviceTask: SendToDeviceTask, private val mSendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers) {
private val mTaskExecutor: TaskExecutor) {


fun create(): MXMegolmDecryption { fun create(): MXMegolmDecryption {
return MXMegolmDecryption( return MXMegolmDecryption(

View File

@ -19,13 +19,10 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm


import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.MatrixCallback 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.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
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
@ -34,15 +31,11 @@ import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
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.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXQueuedEncryption
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -50,7 +43,6 @@ import java.util.*
internal class MXMegolmEncryption( internal class MXMegolmEncryption(
// The id of the room we will be sending to. // The id of the room we will be sending to.
private var roomId: String, private var roomId: String,

private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val keysBackup: KeysBackup, private val keysBackup: KeysBackup,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
@ -58,7 +50,6 @@ internal class MXMegolmEncryption(
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials, private val credentials: Credentials,
private val sendToDeviceTask: SendToDeviceTask, private val sendToDeviceTask: SendToDeviceTask,
private val taskExecutor: TaskExecutor,
private val messageEncrypter: MessageEncrypter, private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository
) : IMXEncrypting { ) : IMXEncrypting {
@ -69,84 +60,20 @@ internal class MXMegolmEncryption(
// case outboundSession.shareOperation will be non-null.) // case outboundSession.shareOperation will be non-null.)
private var outboundSession: MXOutboundSessionInfo? = null private var outboundSession: MXOutboundSessionInfo? = null


// true when there is an HTTP operation in progress
private var shareOperationIsProgress: Boolean = false

private val _pendingEncryptions = ArrayList<MXQueuedEncryption>()

// Default rotation periods // Default rotation periods
// TODO: Make it configurable via parameters // TODO: Make it configurable via parameters
// Session rotation periods // Session rotation periods
private var sessionRotationPeriodMsgs: Int = 100 private var sessionRotationPeriodMsgs: Int = 100
private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000 private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000


/** override suspend fun encryptEventContent(eventContent: Content,
* @return a snapshot of the pending encryptions
*/
private val pendingEncryptions: List<MXQueuedEncryption>
get() {
val list = ArrayList<MXQueuedEncryption>()
synchronized(_pendingEncryptions) {
list.addAll(_pendingEncryptions)
}
return list
}

override fun encryptEventContent(eventContent: Content,
eventType: String, eventType: String,
userIds: List<String>, userIds: List<String>): Try<Content> {
callback: MatrixCallback<Content>) { return getDevicesInRoom(userIds)
// Queue the encryption request .flatMap { ensureOutboundSession(it) }
// It will be processed when everything is set up .flatMap {
val queuedEncryption = MXQueuedEncryption() encryptContent(it, eventType, eventContent)

queuedEncryption.eventContent = eventContent
queuedEncryption.eventType = eventType
queuedEncryption.apiCallback = callback

synchronized(_pendingEncryptions) {
_pendingEncryptions.add(queuedEncryption)
} }

val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent () starts")

getDevicesInRoom(userIds, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {

/**
* A network error has been received while encrypting
* @param failure the exception
*/
private fun dispatchFailure(failure: Throwable) {
Timber.e(failure, "## encryptEventContent() : failure")
val queuedEncryptions = pendingEncryptions

for (queuedEncryption in queuedEncryptions) {
queuedEncryption.apiCallback?.onFailure(failure)
}

synchronized(_pendingEncryptions) {
_pendingEncryptions.removeAll(queuedEncryptions)
}
}

override fun onSuccess(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>) {
ensureOutboundSession(devicesInRoom, object : MatrixCallback<MXOutboundSessionInfo> {
override fun onSuccess(data: MXOutboundSessionInfo) {
Timber.v("## encryptEventContent () processPendingEncryptions after " + (System.currentTimeMillis() - t0) + "ms")
processPendingEncryptions(data)
}

override fun onFailure(failure: Throwable) {
dispatchFailure(failure)
}
})
}

override fun onFailure(failure: Throwable) {
dispatchFailure(failure)
}
})
} }


/** /**
@ -172,12 +99,10 @@ internal class MXMegolmEncryption(
* Ensure the outbound session * Ensure the outbound session
* *
* @param devicesInRoom the devices list * @param devicesInRoom the devices list
* @param callback the asynchronous callback.
*/ */
private fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>, callback: MatrixCallback<MXOutboundSessionInfo>?) { private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> {
var session = outboundSession var session = outboundSession

if (session == null
if (null == session
// Need to make a brand new session? // Need to make a brand new session?
|| session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs)
// Determine if we have shared with anyone we shouldn't have // Determine if we have shared with anyone we shouldn't have
@ -185,49 +110,22 @@ internal class MXMegolmEncryption(
session = prepareNewSessionInRoom() session = prepareNewSessionInRoom()
outboundSession = session outboundSession = session
} }

val safeSession = session
if (shareOperationIsProgress) {
Timber.v("## ensureOutboundSessionInRoom() : already in progress")
// Key share already in progress
return
}

val fSession = session

val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */ val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */

val userIds = devicesInRoom.userIds val userIds = devicesInRoom.userIds

for (userId in userIds) { for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId) val deviceIds = devicesInRoom.getUserDeviceIds(userId)

for (deviceId in deviceIds!!) { for (deviceId in deviceIds!!) {
val deviceInfo = devicesInRoom.getObject(deviceId, userId) val deviceInfo = devicesInRoom.getObject(deviceId, userId)

if (null == safeSession.sharedWithDevices.getObject(deviceId, userId)) {
if (null == fSession.mSharedWithDevices.getObject(deviceId, userId)) {
if (!shareMap.containsKey(userId)) { if (!shareMap.containsKey(userId)) {
shareMap[userId] = ArrayList() shareMap[userId] = ArrayList()
} }

shareMap[userId]!!.add(deviceInfo) shareMap[userId]!!.add(deviceInfo)
} }
} }
} }

return shareKey(safeSession, shareMap).map { safeSession!! }
shareKey(fSession, shareMap, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
shareOperationIsProgress = false
callback?.onSuccess(fSession)
}

override fun onFailure(failure: Throwable) {
Timber.e("## ensureOutboundSessionInRoom() : shareKey onFailure")

callback?.onFailure(failure)
shareOperationIsProgress = false
}
})

} }


/** /**
@ -235,55 +133,33 @@ internal class MXMegolmEncryption(
* *
* @param session the session info * @param session the session info
* @param devicesByUsers the devices map * @param devicesByUsers the devices map
* @param callback the asynchronous callback
*/ */
private fun shareKey(session: MXOutboundSessionInfo, private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: MutableMap<String, MutableList<MXDeviceInfo>>, devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> {
callback: MatrixCallback<Unit>?) {
// nothing to send, the task is done // nothing to send, the task is done
if (0 == devicesByUsers.size) { if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do") Timber.v("## shareKey() : nothing more to do")

return Try.just(Unit)
if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(Unit) }
} }

return
}

// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user) // reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
val subMap = HashMap<String, List<MXDeviceInfo>>() val subMap = HashMap<String, List<MXDeviceInfo>>()

val userIds = ArrayList<String>() val userIds = ArrayList<String>()
var devicesCount = 0 var devicesCount = 0

for (userId in devicesByUsers.keys) { for (userId in devicesByUsers.keys) {
val devicesList = devicesByUsers[userId] val devicesList = devicesByUsers[userId]

userIds.add(userId) userIds.add(userId)
subMap[userId] = devicesList!! subMap[userId] = devicesList!!

devicesCount += devicesList.size devicesCount += devicesList.size

if (devicesCount > 100) { if (devicesCount > 100) {
break break
} }
} }

Timber.v("## shareKey() ; userId $userIds") Timber.v("## shareKey() ; userId $userIds")
shareUserDevicesKey(session, subMap, object : MatrixCallback<Unit> { return shareUserDevicesKey(session, subMap)
override fun onSuccess(data: Unit) { .flatMap {
for (userId in userIds) { val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it) }
devicesByUsers.remove(userId) shareKey(session, remainingDevices)
} }
shareKey(session, devicesByUsers, callback)
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareKey() ; userIds " + userIds + " failed")
callback?.onFailure(failure)
}
})
} }


/** /**
@ -293,16 +169,15 @@ internal class MXMegolmEncryption(
* @param devicesByUser the devices map * @param devicesByUser the devices map
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private fun shareUserDevicesKey(session: MXOutboundSessionInfo, private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>, devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> {
callback: MatrixCallback<Unit>?) { val sessionKey = olmDevice.getSessionKey(session.sessionId)
val sessionKey = olmDevice.getSessionKey(session.mSessionId) val chainIndex = olmDevice.getMessageIndex(session.sessionId)
val chainIndex = olmDevice.getMessageIndex(session.mSessionId)


val submap = HashMap<String, Any>() val submap = HashMap<String, Any>()
submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
submap["room_id"] = roomId submap["room_id"] = roomId
submap["session_id"] = session.mSessionId submap["session_id"] = session.sessionId
submap["session_key"] = sessionKey!! submap["session_key"] = sessionKey!!
submap["chain_index"] = chainIndex submap["chain_index"] = chainIndex


@ -310,26 +185,21 @@ internal class MXMegolmEncryption(
payload["type"] = EventType.ROOM_KEY payload["type"] = EventType.ROOM_KEY
payload["content"] = submap payload["content"] = submap


val t0 = System.currentTimeMillis() var t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : starts") Timber.v("## shareUserDevicesKey() : starts")


ensureOlmSessionsForDevicesAction.handle(devicesByUser, object : MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>> { return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
override fun onSuccess(data: MXUsersDevicesMap<MXOlmSessionResult>) { .flatMap {
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms") + (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()

var haveTargets = false var haveTargets = false
val userIds = data.userIds val userIds = it.userIds

for (userId in userIds) { for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId] val devicesToShareWith = devicesByUser[userId]

for ((deviceID) in devicesToShareWith!!) { for ((deviceID) in devicesToShareWith!!) {

val sessionResult = it.getObject(deviceID, userId)
val sessionResult = data.getObject(deviceID, userId) if (sessionResult?.mSessionId == null) {

if (null == sessionResult || null == sessionResult.mSessionId) {
// no session with this device, probably because there // no session with this device, probably because there
// were no one-time keys. // were no one-time keys.
// //
@ -343,21 +213,18 @@ internal class MXMegolmEncryption(
// so just skip it. // so just skip it.
continue continue
} }

Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument //noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.mDevice)), userId, deviceID) contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.mDevice)), userId, deviceID)
haveTargets = true haveTargets = true
} }
} }

if (haveTargets) { if (haveTargets) {
val t0 = System.currentTimeMillis() t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target") Timber.v("## shareUserDevicesKey() : has target")

val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)) sendToDeviceTask.execute(sendToDeviceParams)
.dispatchTo(object : MatrixCallback<Unit> { .map {
override fun onSuccess(data: Unit) {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after " Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms") + (System.currentTimeMillis() - t0) + " ms")


@ -368,80 +235,45 @@ internal class MXMegolmEncryption(
// for dead devices on every message. // for dead devices on every message.
for (userId in devicesByUser.keys) { for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId] val devicesToShareWith = devicesByUser[userId]

for ((deviceId) in devicesToShareWith!!) { for ((deviceId) in devicesToShareWith!!) {
session.mSharedWithDevices.setObject(chainIndex, userId, deviceId) session.sharedWithDevices.setObject(chainIndex, userId, deviceId)
} }
} }

Unit
CryptoAsyncHelper.getUiHandler().post {
callback?.onSuccess(Unit)
} }
}

override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareUserDevicesKey() : sendToDevice")

callback?.onFailure(failure)
}
})
.executeBy(taskExecutor)
} else { } else {
Timber.v("## shareUserDevicesKey() : no need to sharekey") Timber.v("## shareUserDevicesKey() : no need to sharekey")

Try.just(Unit)
if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(Unit) }
} }
} }
} }


override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareUserDevicesKey() : ensureOlmSessionsForDevices failed")

callback?.onFailure(failure)
}
})
}

/** /**
* process the pending encryptions * process the pending encryptions
*/ */
private fun processPendingEncryptions(session: MXOutboundSessionInfo?) { private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
if (null != session) {
val queuedEncryptions = pendingEncryptions

// Everything is in place, encrypt all pending events // Everything is in place, encrypt all pending events
for (queuedEncryption in queuedEncryptions) {
val payloadJson = HashMap<String, Any>() val payloadJson = HashMap<String, Any>()

payloadJson["room_id"] = roomId payloadJson["room_id"] = roomId
payloadJson["type"] = queuedEncryption.eventType!! payloadJson["type"] = eventType
payloadJson["content"] = queuedEncryption.eventContent!! 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.mSessionId, 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.mSessionId 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++
CryptoAsyncHelper.getUiHandler().post { queuedEncryption.apiCallback?.onSuccess(map) } map

session.mUseCount++
}

synchronized(_pendingEncryptions) {
_pendingEncryptions.removeAll(queuedEncryptions)
}
}
} }


/** /**
@ -451,31 +283,30 @@ internal class MXMegolmEncryption(
* @param userIds the user ids whose devices must be checked. * @param userIds the user ids whose devices must be checked.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private fun getDevicesInRoom(userIds: List<String>, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) { private suspend fun getDevicesInRoom(userIds: List<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
// We are happy to use a cached version here: we assume that if we already // We are happy to use a cached version here: we assume that if we already
// have a list of the user's devices, then we already share an e2e room // have a list of the user's devices, then we already share an e2e room
// with them, which means that they will have announced any new devices via // with them, which means that they will have announced any new devices via
// an m.new_device. // an m.new_device.
deviceListManager.downloadKeys(userIds, false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { return deviceListManager
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) { .downloadKeys(userIds, false)
.map {
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) || cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)


val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>() val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>() val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()


for (userId in data.userIds) { for (userId in it.userIds) {
val deviceIds = data.getUserDeviceIds(userId) val deviceIds = it.getUserDeviceIds(userId)


for (deviceId in deviceIds!!) { for (deviceId in deviceIds!!) {
val deviceInfo = data.getObject(deviceId, userId) val deviceInfo = it.getObject(deviceId, userId)

if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo!!.isUnknown) { if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo!!.isUnknown) {
// The device is not yet known by the user // The device is not yet known by the user
unknownDevices.setObject(deviceInfo, userId, deviceId) unknownDevices.setObject(deviceInfo, userId, deviceId)
continue continue
} }

if (deviceInfo!!.isBlocked) { if (deviceInfo!!.isBlocked) {
// Remove any blocked devices // Remove any blocked devices
continue continue
@ -489,27 +320,10 @@ internal class MXMegolmEncryption(
// Don't bother sending to ourself // Don't bother sending to ourself
continue continue
} }

devicesInRoom.setObject(deviceInfo, userId, deviceId) devicesInRoom.setObject(deviceInfo, userId, deviceId)
} }
} }

devicesInRoom
CryptoAsyncHelper.getUiHandler().post {
// Check if any of these devices are not yet known to the user.
// if so, warn the user so they can verify or ignore.
if (unknownDevices.map.isNotEmpty()) {
callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
} else {
callback.onSuccess(devicesInRoom)
} }

}
}

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
} }
} }

View File

@ -50,7 +50,6 @@ internal class MXMegolmEncryptionFactory(
mEnsureOlmSessionsForDevicesAction, mEnsureOlmSessionsForDevicesAction,
mCredentials, mCredentials,
mSendToDeviceTask, mSendToDeviceTask,
mTaskExecutor,
mMessageEncrypter, mMessageEncrypter,
mWarnOnUnknownDevicesRepository) mWarnOnUnknownDevicesRepository)
} }

View File

@ -23,23 +23,23 @@ import timber.log.Timber


internal class MXOutboundSessionInfo( internal class MXOutboundSessionInfo(
// The id of the session // The id of the session
val mSessionId: String) { val sessionId: String) {
// When the session was created // When the session was created
private val mCreationTime = System.currentTimeMillis() private val creationTime = System.currentTimeMillis()


// Number of times this session has been used // Number of times this session has been used
var mUseCount: Int = 0 var useCount: Int = 0


// Devices with which we have shared the session key // Devices with which we have shared the session key
// userId -> {deviceId -> msgindex} // userId -> {deviceId -> msgindex}
val mSharedWithDevices: MXUsersDevicesMap<Int> = MXUsersDevicesMap() val sharedWithDevices: MXUsersDevicesMap<Int> = MXUsersDevicesMap()


fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean { fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean {
var needsRotation = false var needsRotation = false
val sessionLifetime = System.currentTimeMillis() - mCreationTime val sessionLifetime = System.currentTimeMillis() - creationTime


if (mUseCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) { if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
Timber.v("## needsRotation() : Rotating megolm session after " + mUseCount + ", " + sessionLifetime + "ms") Timber.v("## needsRotation() : Rotating megolm session after " + useCount + ", " + sessionLifetime + "ms")
needsRotation = true needsRotation = true
} }


@ -53,7 +53,7 @@ internal class MXOutboundSessionInfo(
* @return true if we have shared the session with devices which aren't in devicesInRoom. * @return true if we have shared the session with devices which aren't in devicesInRoom.
*/ */
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Boolean { fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Boolean {
val userIds = mSharedWithDevices.userIds val userIds = sharedWithDevices.userIds


for (userId in userIds) { for (userId in userIds) {
if (null == devicesInRoom.getUserDeviceIds(userId)) { if (null == devicesInRoom.getUserDeviceIds(userId)) {
@ -61,7 +61,7 @@ internal class MXOutboundSessionInfo(
return true return true
} }


val deviceIds = mSharedWithDevices.getUserDeviceIds(userId) val deviceIds = sharedWithDevices.getUserDeviceIds(userId)


for (deviceId in deviceIds!!) { for (deviceId in deviceIds!!) {
if (null == devicesInRoom.getObject(deviceId, userId)) { if (null == devicesInRoom.getObject(deviceId, userId)) {

View File

@ -44,7 +44,7 @@ internal class MXOlmDecryption(
: IMXDecrypting { : IMXDecrypting {


@Throws(MXDecryptionException::class) @Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
val olmEventContent = event.content.toModel<OlmEventContent>()!! val olmEventContent = event.content.toModel<OlmEventContent>()!!


if (null == olmEventContent.ciphertext) { if (null == olmEventContent.ciphertext) {

View File

@ -19,7 +19,7 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm


import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.MatrixCallback import arrow.core.Try
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.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
@ -28,12 +28,7 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUser
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.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
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.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.* import java.util.*


internal class MXOlmEncryption( internal class MXOlmEncryption(
@ -42,38 +37,28 @@ internal class MXOlmEncryption(
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val messageEncrypter: MessageEncrypter, private val messageEncrypter: MessageEncrypter,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting { : IMXEncrypting {


override fun encryptEventContent(eventContent: Content, override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> {
eventType: String,
userIds: List<String>,
callback: MatrixCallback<Content>) {
// pick the list of recipients based on the membership list. // pick the list of recipients based on the membership list.
// //
// TODO: there is a race condition here! What if a new user turns up // TODO: there is a race condition here! What if a new user turns up
CoroutineScope(coroutineDispatchers.crypto).launch { return ensureSession(userIds)
ensureSession(userIds, object : MatrixCallback<Unit> { .map {
override fun onSuccess(data: Unit) {
val deviceInfos = ArrayList<MXDeviceInfo>() val deviceInfos = ArrayList<MXDeviceInfo>()

for (userId in userIds) { for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()

for (device in devices) { for (device in devices) {
val key = device.identityKey() val key = device.identityKey()

if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) { if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
// Don't bother setting up session to ourself // Don't bother setting up session to ourself
continue continue
} }

if (device.isBlocked) { if (device.isBlocked) {
// Don't bother setting up sessions with blocked users // Don't bother setting up sessions with blocked users
continue continue
} }

deviceInfos.add(device) deviceInfos.add(device)
} }
} }
@ -84,12 +69,10 @@ internal class MXOlmEncryption(
messageMap["content"] = eventContent messageMap["content"] = eventContent


messageEncrypter.encryptMessage(messageMap, deviceInfos) messageEncrypter.encryptMessage(messageMap, deviceInfos)
messageMap.toContent()!!
}
}


callback.onSuccess(messageMap.toContent()!!)
}
})
}
}


/** /**
* Ensure that the session * Ensure that the session
@ -97,24 +80,11 @@ internal class MXOlmEncryption(
* @param users the user ids list * @param users the user ids list
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private fun ensureSession(users: List<String>, callback: MatrixCallback<Unit>?) { private suspend fun ensureSession(users: List<String>): Try<Unit> {
deviceListManager.downloadKeys(users, false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> { return deviceListManager
.downloadKeys(users, false)
.flatMap { ensureOlmSessionsForUsersAction.handle(users) }
.map { Unit }


override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
ensureOlmSessionsForUsersAction.handle(users, object : MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>> {
override fun onSuccess(data: MXUsersDevicesMap<MXOlmSessionResult>) {
callback?.onSuccess(Unit)
}

override fun onFailure(failure: Throwable) {
callback?.onFailure(failure)
}
})
}

override fun onFailure(failure: Throwable) {
callback?.onFailure(failure)
}
})
} }
} }

View File

@ -37,7 +37,6 @@ internal class MXOlmEncryptionFactory(private val mOlmDevice: MXOlmDevice,
mCryptoStore, mCryptoStore,
mMessageEncrypter, mMessageEncrypter,
mDeviceListManager, mDeviceListManager,
coroutineDispatchers,
mEnsureOlmSessionsForUsersAction) mEnsureOlmSessionsForUsersAction)
} }
} }

View File

@ -603,11 +603,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
} }


override fun getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): OutgoingRoomKeyRequest? { override fun getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): OutgoingRoomKeyRequest? {
if (request.mRequestBody == null) { if (request.requestBody == null) {
return null return null
} }


val existingOne = getOutgoingRoomKeyRequest(request.mRequestBody!!) val existingOne = getOutgoingRoomKeyRequest(request.requestBody!!)


if (existingOne != null) { if (existingOne != null) {
return existingOne return existingOne
@ -615,11 +615,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals


// Insert the request and return the one passed in parameter // Insert the request and return the one passed in parameter
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) {
it.createObject(OutgoingRoomKeyRequestEntity::class.java, request.mRequestId).apply { it.createObject(OutgoingRoomKeyRequestEntity::class.java, request.requestId).apply {
putRequestBody(request.mRequestBody) putRequestBody(request.requestBody)
putRecipients(request.mRecipients) putRecipients(request.recipients)
cancellationTxnId = request.mCancellationTxnId cancellationTxnId = request.cancellationTxnId
state = request.mState.ordinal state = request.state.ordinal
} }
} }


@ -638,11 +638,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) { override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) {
val obj = OutgoingRoomKeyRequestEntity().apply { val obj = OutgoingRoomKeyRequestEntity().apply {
requestId = request.mRequestId requestId = request.requestId
cancellationTxnId = request.mCancellationTxnId cancellationTxnId = request.cancellationTxnId
state = request.mState.ordinal state = request.state.ordinal
putRecipients(request.mRecipients) putRecipients(request.recipients)
putRequestBody(request.mRequestBody) putRequestBody(request.requestBody)
} }


it.insertOrUpdate(obj) it.insertOrUpdate(obj)

View File

@ -52,7 +52,7 @@ internal open class OutgoingRoomKeyRequestEntity(
requestId!!, requestId!!,
OutgoingRoomKeyRequest.RequestState.from(state) OutgoingRoomKeyRequest.RequestState.from(state)
).apply { ).apply {
this.mCancellationTxnId = cancellationTxnId this.cancellationTxnId = cancellationTxnId
} }
} }



View File

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di


import android.content.Context import android.content.Context
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.android.asCoroutineDispatcher
import org.koin.dsl.module.module import org.koin.dsl.module.module
import java.util.concurrent.Executors




class MatrixModule(private val context: Context) { class MatrixModule(private val context: Context) {
@ -35,10 +35,11 @@ class MatrixModule(private val context: Context) {
} }


single { single {
val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler()
MatrixCoroutineDispatchers(io = Dispatchers.IO, MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO, computation = Dispatchers.IO,
main = Dispatchers.Main, main = Dispatchers.Main,
crypto = Executors.newSingleThreadExecutor().asCoroutineDispatcher() crypto = cryptoHandler.asCoroutineDispatcher("crypto")
) )
} }



View File

@ -16,9 +16,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.failure.Failure
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.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.internal.crypto.MXDecryptionException
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
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
@ -44,6 +46,9 @@ internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomM
event.setClearData(result) event.setClearData(result)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
if (e is MXDecryptionException) {
event.setCryptoError(e.cryptoError)
}
} }
} }
return TimelineEvent( return TimelineEvent(

View File

@ -97,7 +97,6 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex) roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex)


// Give info to crypto module // Give info to crypto module
// TODO Remove
roomSync.state.events.forEach { roomSync.state.events.forEach {
mCrypto.onStateEvent(roomId, it) mCrypto.onStateEvent(roomId, it)
} }

View File

@ -49,7 +49,9 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler,
cryptoSyncHandler.onSyncCompleted(syncResponse, fromToken, isCatchingUp) cryptoSyncHandler.onSyncCompleted(syncResponse, fromToken, isCatchingUp)
} }
val isInitialSync = fromToken == null val isInitialSync = fromToken == null
if (!cryptoManager.isStarted()) {
cryptoManager.start(isInitialSync) cryptoManager.start(isInitialSync)
}
Timber.v("Finish handling sync in $measure ms") Timber.v("Finish handling sync in $measure ms")
syncResponse syncResponse
} }

View File

@ -45,15 +45,12 @@ class EncryptedItemFactory(


return when { return when {
EventType.ENCRYPTED == timelineEvent.root.getClearType() -> { EventType.ENCRYPTED == timelineEvent.root.getClearType() -> {
val decrypted: MXEventDecryptionResult? val cryptoError = timelineEvent.root.mCryptoError
try {
decrypted = session.decryptEvent(timelineEvent.root, "TODO")
} catch (e: MXDecryptionException) {
val errorDescription = val errorDescription =
if (e.cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
} else { } else {
e.localizedMessage cryptoError?.message
} }


val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
@ -66,20 +63,6 @@ class EncryptedItemFactory(
.avatarUrl(timelineEvent.senderAvatar) .avatarUrl(timelineEvent.senderAvatar)
.memberName(timelineEvent.senderName) .memberName(timelineEvent.senderName)
} }

if (decrypted == null) {
return null
}
if (decrypted.clearEvent == null) {
return null
}
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
val clearEvent = adapter.fromJsonValue(decrypted.clearEvent) ?: return null
val decryptedTimelineEvent = timelineEvent.copy(root = clearEvent)

// Success
return messageItemFactory.create(decryptedTimelineEvent, nextEvent, callback)
}
else -> null else -> null
} }
} }