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.
*/
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 OLM_ERROR_CODE = "OLM_ERROR_CODE"
const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE"
const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX"
/**
* short error reasons

View file

@ -19,10 +19,8 @@
package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.os.Handler
import android.text.TextUtils
import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
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.configureWith
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.*
@ -245,56 +240,41 @@ internal class CryptoManager(
* @param isInitialSync true if it starts from an initial sync
*/
fun start(isInitialSync: Boolean) {
if (isStarting.get()) {
CoroutineScope(coroutineDispatchers.crypto).launch {
internalStart(isInitialSync)
}
}
private suspend fun internalStart(isInitialSync: Boolean) {
if (isStarted.get() || isStarting.get()) {
return
}
// do not start if there is not network connection
// TODO
//if (null != mNetworkConnectivityReceiver && !mNetworkConnectivityReceiver!!.isConnected()) {
// // wait that a valid network connection is retrieved
// mNetworkConnectivityReceiver!!.removeEventListener(mNetworkListener)
// mNetworkConnectivityReceiver!!.addEventListener(mNetworkListener)
// return
//}
isStarting.set(true)
// Open the store
cryptoStore.open()
CoroutineScope(coroutineDispatchers.crypto).launch {
uploadDeviceKeys()
.flatMap {
oneTimeKeysUploader.maybeUploadOneTimeKeys()
}
.handleError {
Handler().postDelayed(
{
if (!isStarted()) {
isStarting.set(false)
start(isInitialSync)
}
}, 1000
)
}
.fold(
{
Timber.e("Start failed: $it")
},
{
isStarting.set(false)
isStarted.set(true)
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
uploadDeviceKeys()
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
.fold(
{
Timber.e("Start failed: $it")
delay(1000)
isStarting.set(false)
internalStart(isInitialSync)
},
{
isStarting.set(false)
isStarted.set(true)
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
)
}
}
)
}
/**
@ -447,7 +427,10 @@ internal class CryptoManager(
* @param membersId list of members to start tracking their devices
* @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
// (for now at least. Maybe we should alert the user somehow?)
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
@ -555,13 +538,11 @@ internal class CryptoManager(
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) {
// wait that the crypto is really started
if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init")
start(false)
return
}
CoroutineScope(coroutineDispatchers.crypto).launch {
if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init")
internalStart(false)
}
val userIds = getRoomUserIds(roomId)
var alg = synchronized(roomEncryptors) {
roomEncryptors[roomId]
@ -580,24 +561,21 @@ internal class CryptoManager(
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds, object : MatrixCallback<Content> {
override fun onSuccess(data: Content) {
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED))
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
.fold(
{ callback.onFailure(it) },
{
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
}
)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
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")
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")
return null
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else {
return runBlocking {
withContext(coroutineDispatchers.crypto) {
return runBlocking {
withContext(coroutineDispatchers.crypto) {
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else {
alg.decryptEvent(event, timeline)
}
}
@ -661,19 +639,15 @@ internal class CryptoManager(
*/
private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>()!!
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
Timber.e("## onRoomKeyEvent() : missing fields")
return
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (null == alg) {
if (alg == null) {
Timber.e("## onRoomKeyEvent() : Unable to handle keys for " + roomKeyContent.algorithm)
return
}
alg.onRoomKeyEvent(event, keysBackup)
}
@ -699,7 +673,7 @@ internal class CryptoManager(
monarchy.doWithRealm { realm ->
// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
&& shouldEncryptForInvitedMembers(roomId)
&& shouldEncryptForInvitedMembers(roomId)
userIds = if (encryptForInvitedMembers) {
RoomMembers(realm, roomId).getActiveRoomMemberIds()
@ -734,8 +708,8 @@ internal class CryptoManager(
// make sure we are tracking the deviceList for this user.
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
} else if (membership == Membership.INVITE
&& shouldEncryptForInvitedMembers(roomId)
&& cryptoConfig.mEnableEncryptionForInvitedMembers) {
&& shouldEncryptForInvitedMembers(roomId)
&& cryptoConfig.mEnableEncryptionForInvitedMembers) {
// track the deviceList for this invited user.
// Caution: there's a big edge case here in that federated servers do not
// know what other servers are in the room at the time they've been invited.
@ -898,7 +872,7 @@ internal class CryptoManager(
// trigger an an unknown devices exception
callback.onFailure(
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
}
}
)
@ -936,7 +910,7 @@ internal class CryptoManager(
* @param roomId the room id
* @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 {
return if (null != roomId) {
cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
@ -984,7 +958,7 @@ internal class CryptoManager(
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
*
@ -1057,9 +1031,9 @@ internal class CryptoManager(
return unknownDevices
}
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
override fun toString(): String {
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"

View file

@ -169,7 +169,7 @@ internal class CryptoModule {
}
scope(DefaultSession.SCOPE) {
EnsureOlmSessionsForDevicesAction(get(), get(), get(), get())
EnsureOlmSessionsForDevicesAction(get(), get())
}
scope(DefaultSession.SCOPE) {
@ -192,7 +192,7 @@ internal class CryptoModule {
// Factories
scope(DefaultSession.SCOPE) {
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.*
internal class IncomingRoomKeyRequestManager(
private val mCredentials: Credentials,
private val mCryptoStore: IMXCryptoStore,
private val mRoomDecryptorProvider: RoomDecryptorProvider) {
private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val roomDecryptorProvider: RoomDecryptorProvider) {
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync.
private val mReceivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
private val mReceivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()
private val receivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
private val receivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()
// the listeners
private val mRoomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()
private val roomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()
init {
mReceivedRoomKeyRequests.addAll(mCryptoStore.getPendingIncomingRoomKeyRequests())
receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
}
/**
@ -52,13 +52,12 @@ internal class IncomingRoomKeyRequestManager(
*/
fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> synchronized(mReceivedRoomKeyRequests) {
mReceivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
RoomKeyShare.ACTION_SHARE_REQUEST -> synchronized(receivedRoomKeyRequests) {
receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
}
RoomKeyShare.ACTION_SHARE_CANCELLATION -> synchronized(mReceivedRoomKeyRequestCancellations) {
mReceivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
RoomKeyShare.ACTION_SHARE_CANCELLATION -> synchronized(receivedRoomKeyRequestCancellations) {
receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
}
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action)
}
@ -71,10 +70,10 @@ internal class IncomingRoomKeyRequestManager(
fun processReceivedRoomKeyRequests() {
var receivedRoomKeyRequests: List<IncomingRoomKeyRequest>? = null
synchronized(mReceivedRoomKeyRequests) {
if (!mReceivedRoomKeyRequests.isEmpty()) {
receivedRoomKeyRequests = ArrayList(mReceivedRoomKeyRequests)
mReceivedRoomKeyRequests.clear()
synchronized(this.receivedRoomKeyRequests) {
if (this.receivedRoomKeyRequests.isNotEmpty()) {
receivedRoomKeyRequests = ArrayList(this.receivedRoomKeyRequests)
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)
if (!TextUtils.equals(mCredentials.userId, userId)) {
if (!TextUtils.equals(credentials.userId, userId)) {
// TODO: determine if we sent this device the keys already: in
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
return
@ -100,7 +99,7 @@ internal class IncomingRoomKeyRequestManager(
// 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.
val decryptor = mRoomDecryptorProvider.getRoomDecryptor(roomId, alg)
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
if (null == decryptor) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
@ -109,52 +108,52 @@ internal class IncomingRoomKeyRequestManager(
if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session " + body.sessionId!!)
mCryptoStore.deleteIncomingRoomKeyRequest(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
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")
mCryptoStore.deleteIncomingRoomKeyRequest(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
request.share = Runnable {
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
val device = mCryptoStore.getUserDevice(deviceId!!, userId)
val device = cryptoStore.getUserDevice(deviceId!!, userId)
if (null != device) {
if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
mCryptoStore.deleteIncomingRoomKeyRequest(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
request.share?.run()
continue
}
if (device.isBlocked) {
Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored")
mCryptoStore.deleteIncomingRoomKeyRequest(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
}
mCryptoStore.storeIncomingRoomKeyRequest(request)
cryptoStore.storeIncomingRoomKeyRequest(request)
onRoomKeyRequest(request)
}
}
var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null
synchronized(mReceivedRoomKeyRequestCancellations) {
if (!mReceivedRoomKeyRequestCancellations.isEmpty()) {
receivedRoomKeyRequestCancellations = mReceivedRoomKeyRequestCancellations.toList()
mReceivedRoomKeyRequestCancellations.clear()
synchronized(this.receivedRoomKeyRequestCancellations) {
if (!this.receivedRoomKeyRequestCancellations.isEmpty()) {
receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
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
// everything through.
onRoomKeyRequestCancellation(request)
mCryptoStore.deleteIncomingRoomKeyRequest(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
}
}
@ -178,8 +177,8 @@ internal class IncomingRoomKeyRequestManager(
* @param request the request
*/
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
synchronized(mRoomKeysRequestListeners) {
for (listener in mRoomKeysRequestListeners) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequest(request)
} catch (e: Exception) {
@ -197,8 +196,8 @@ internal class IncomingRoomKeyRequestManager(
* @param request the cancellation request
*/
private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
synchronized(mRoomKeysRequestListeners) {
for (listener in mRoomKeysRequestListeners) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequestCancellation(request)
} catch (e: Exception) {
@ -210,14 +209,14 @@ internal class IncomingRoomKeyRequestManager(
}
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(mRoomKeysRequestListeners) {
mRoomKeysRequestListeners.add(listener)
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(mRoomKeysRequestListeners) {
mRoomKeysRequestListeners.remove(listener)
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.remove(listener)
}
}

View file

@ -28,20 +28,19 @@ import timber.log.Timber
import java.util.*
internal class OneTimeKeysUploader(
private val mCredentials: Credentials,
private val mOlmDevice: MXOlmDevice,
private val mObjectSigner: ObjectSigner,
private val mUploadKeysTask: UploadKeysTask
private val credentials: Credentials,
private val olmDevice: MXOlmDevice,
private val objectSigner: ObjectSigner,
private val uploadKeysTask: UploadKeysTask
) {
// tell if there is a OTK check in progress
private var mOneTimeKeyCheckInProgress = false
private var oneTimeKeyCheckInProgress = false
// last OTK check timestamp
private var mLastOneTimeKeyCheck: Long = 0
private var mOneTimeKeyCount: Int? = null
private var lastOneTimeKeyCheck: Long = 0
private var oneTimeKeyCount: Int? = null
var mLastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
private set
private var lastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
/**
* 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
*/
fun updateOneTimeKeyCount(currentCount: Int) {
mOneTimeKeyCount = currentCount
oneTimeKeyCount = currentCount
}
@ -58,19 +57,19 @@ internal class OneTimeKeysUploader(
* Check if the OTK must be uploaded.
*/
suspend fun maybeUploadOneTimeKeys(): Try<Unit> {
if (mOneTimeKeyCheckInProgress) {
if (oneTimeKeyCheckInProgress) {
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.
return Try.just(Unit)
}
mLastOneTimeKeyCheck = System.currentTimeMillis()
mOneTimeKeyCheckInProgress = true
lastOneTimeKeyCheck = System.currentTimeMillis()
oneTimeKeyCheckInProgress = true
// 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
// 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
// out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
if (mOneTimeKeyCount != null) {
return uploadOTK(mOneTimeKeyCount!!, keyLimit)
val result = if (oneTimeKeyCount != null) {
uploadOTK(oneTimeKeyCount!!, keyLimit)
} else {
// ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, mCredentials.deviceId!!)
return mUploadKeysTask.execute(uploadKeysParams)
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
uploadKeysTask.execute(uploadKeysParams)
.flatMap {
// 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
@ -97,17 +96,23 @@ internal class OneTimeKeysUploader(
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// 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)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit)
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
mOneTimeKeyCount = null
mOneTimeKeyCheckInProgress = false
}
}
return result
.map {
Timber.v("## uploadKeys() : success")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
}
/**
@ -117,27 +122,17 @@ internal class OneTimeKeysUploader(
* @param keyLimit the limit
*/
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 we don't need to generate any more keys then we are done.
return Try.just(Unit)
}
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
mOlmDevice.generateOneTimeKeys(keysThisLoop)
olmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys()
.flatMap {
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 {
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"))
@ -149,7 +144,7 @@ internal class OneTimeKeysUploader(
* Upload my user's one time keys.
*/
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> {
val oneTimeKeys = mOlmDevice.getOneTimeKeys()
val oneTimeKeys = olmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>()
val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
@ -162,7 +157,7 @@ internal class OneTimeKeysUploader(
// the key is also signed
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k)
k["signatures"] = mObjectSigner.signObject(canonicalJson)
k["signatures"] = objectSigner.signObject(canonicalJson)
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
// same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)
return mUploadKeysTask
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
return uploadKeysTask
.execute(uploadParams)
.map {
mLastPublishedOneTimeKeys = oneTimeKeys
mOlmDevice.markKeysAsPublished()
lastPublishedOneTimeKeys = oneTimeKeys
olmDevice.markKeysAsPublished()
it
}
}

View file

@ -24,15 +24,15 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
*/
class OutgoingRoomKeyRequest(
// RequestBody
var mRequestBody: RoomKeyRequestBody?, // list of recipients for the request
var mRecipients: List<Map<String, String>>, // Unique id for this request. Used for both
var requestBody: RoomKeyRequestBody?, // list of recipients for the request
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
// the transaction id when sending the to_device messages to our local
var mRequestId: String, // current state of this request
var mState: RequestState) {
var requestId: String, // current state of this request
var state: RequestState) {
// transaction id for the cancellation, if any
var mCancellationTxnId: String? = null
var cancellationTxnId: String? = null
/**
* Used only for log.
@ -40,8 +40,8 @@ class OutgoingRoomKeyRequest(
* @return the room id.
*/
val roomId: String?
get() = if (null != mRequestBody) {
mRequestBody!!.roomId
get() = if (null != requestBody) {
requestBody!!.roomId
} else null
/**
@ -50,8 +50,8 @@ class OutgoingRoomKeyRequest(
* @return the session id
*/
val sessionId: String?
get() = if (null != mRequestBody) {
mRequestBody!!.sessionId
get() = if (null != requestBody) {
requestBody!!.sessionId
} 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.tasks.SendToDeviceTask
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 timber.log.Timber
import java.util.*
@ -88,7 +87,7 @@ internal class OutgoingRoomKeyRequestManager(
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))
if (req?.mState == OutgoingRoomKeyRequest.RequestState.UNSENT) {
if (req?.state == OutgoingRoomKeyRequest.RequestState.UNSENT) {
startTimer()
}
}
@ -122,20 +121,20 @@ internal class OutgoingRoomKeyRequestManager(
?: // no request was made for this key
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
} 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")
cryptoStore.deleteOutgoingRoomKeyRequest(req.mRequestId)
} else if (req.mState === OutgoingRoomKeyRequest.RequestState.SENT) {
cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
} else if (req.state === OutgoingRoomKeyRequest.RequestState.SENT) {
if (andResend) {
req.mState = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
} else {
req.mState = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
}
req.mCancellationTxnId = makeTxnId()
req.cancellationTxnId = makeTxnId()
cryptoStore.updateOutgoingRoomKeyRequest(req)
sendOutgoingRoomKeyRequestCancellation(req)
}
@ -149,7 +148,6 @@ internal class OutgoingRoomKeyRequestManager(
if (sendOutgoingRoomKeyRequestsRunning) {
return
}
Handler().postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning) {
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
@ -182,7 +180,7 @@ internal class OutgoingRoomKeyRequestManager(
return
}
if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.mState) {
if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.state) {
sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest)
} else {
sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest)
@ -195,20 +193,20 @@ internal class OutgoingRoomKeyRequestManager(
* @param request the request
*/
private fun sendOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.mRequestBody
+ " from " + request.mRecipients + " id " + request.mRequestId)
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody
+ " from " + request.recipients + " id " + request.requestId)
val requestMessage = RoomKeyShareRequest()
requestMessage.requestingDeviceId = cryptoStore.getDeviceId()
requestMessage.requestId = request.mRequestId
requestMessage.body = request.mRequestBody
requestMessage.requestId = request.requestId
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) {
if (request.mState !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.mState)
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.state)
} else {
request.mState = state
request.state = state
cryptoStore.updateOutgoingRoomKeyRequest(request)
}
@ -234,17 +232,17 @@ internal class OutgoingRoomKeyRequestManager(
* @param request the request
*/
private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.mRequestBody
+ " to " + request.mRecipients
+ " cancellation id " + request.mCancellationTxnId)
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.requestBody
+ " to " + request.recipients
+ " cancellation id " + request.cancellationTxnId)
val roomKeyShareCancellation = RoomKeyShareCancellation()
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() {
cryptoStore.deleteOutgoingRoomKeyRequest(request.mRequestId)
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
sendOutgoingRoomKeyRequestsRunning = false
startTimer()
}
@ -252,13 +250,13 @@ internal class OutgoingRoomKeyRequestManager(
override fun onSuccess(data: Unit) {
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()
// Resend the request with a new ID
if (resend) {
sendRoomKeyRequest(request.mRequestBody, request.mRecipients)
sendRoomKeyRequest(request.requestBody, request.recipients)
}
}

View file

@ -43,37 +43,35 @@ internal class RoomDecryptorProvider(
*/
fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
// sanity check
if (algorithm.isNullOrEmpty() || roomId.isNullOrEmpty()) {
if (algorithm.isNullOrEmpty()) {
Timber.e("## getRoomDecryptor() : null algorithm")
return null
}
var alg: IMXDecrypting?
synchronized(roomDecryptors) {
if (!roomDecryptors.containsKey(roomId)) {
roomDecryptors[roomId!!] = HashMap()
}
alg = roomDecryptors[roomId]!![algorithm]
}
if (alg != null) {
return alg
}
val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)
if (decryptingClass) {
alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create()
else -> olmDecryptionFactory.create()
}
if (null != alg) {
if (!TextUtils.isEmpty(roomId)) {
synchronized(roomDecryptors) {
roomDecryptors[roomId]!!.put(algorithm!!, alg!!)
}
if(roomId != null && roomId.isNotEmpty()) {
synchronized(roomDecryptors) {
if (!roomDecryptors.containsKey(roomId)) {
roomDecryptors[roomId] = HashMap()
}
val alg = roomDecryptors[roomId]?.get(algorithm)
if (alg != null) {
return alg
}
}
}
return alg
val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm)
if (decryptingClass) {
val alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create()
else -> olmDecryptionFactory.create()
}
if (roomId != null && !TextUtils.isEmpty(roomId)) {
synchronized(roomDecryptors) {
roomDecryptors[roomId]?.put(algorithm, alg)
}
}
return alg
}
return null
}
fun getRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
@ -31,12 +32,10 @@ import timber.log.Timber
import java.util.*
internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDevice,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor) {
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
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 results = MXUsersDevicesMap<MXOlmSessionResult>()
@ -62,8 +61,7 @@ internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDev
}
if (devicesWithoutSession.size == 0) {
callback?.onSuccess(results)
return
return Try.just(results)
}
// Prepare the request for claiming one-time keys
@ -83,67 +81,42 @@ internal class EnsureOlmSessionsForDevicesAction(private val olmDevice: MXOlmDev
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
oneTimeKeysForUsersDeviceTask
.configureWith(ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim))
.dispatchTo(object : MatrixCallback<MXUsersDevicesMap<MXKey>> {
override fun onSuccess(data: MXUsersDevicesMap<MXKey>) {
try {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $data")
for (userId in userIds) {
val deviceInfos = devicesByUser[userId]
for (deviceInfo in deviceInfos!!) {
var oneTimeKey: MXKey? = null
val deviceIds = data.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(deviceId, userId)
if (null != olmSessionResult!!.mSessionId) {
// We already have a result for this device
continue
}
val key = data.getObject(deviceId, userId)
if (TextUtils.equals(key!!.type, oneTimeKeyAlgorithm)) {
oneTimeKey = key
}
if (null == oneTimeKey) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.mSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
return oneTimeKeysForUsersDeviceTask
.execute(claimParams)
.map {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it")
for (userId in userIds) {
val deviceInfos = devicesByUser[userId]
for (deviceInfo in deviceInfos!!) {
var oneTimeKey: MXKey? = null
val deviceIds = it.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(deviceId, userId)
if (olmSessionResult!!.mSessionId != null) {
// We already have a result for this device
continue
}
val key = it.getObject(deviceId, userId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.mSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
} catch (e: Exception) {
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)
results
}
}
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
var sessionId: String? = null

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions
import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.MXOlmDevice
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.
* It must be called in getEncryptingThreadHandler() thread.
* The callback is called in the UI thread.
*
* @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")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()
for (userId in users) {
@ -64,7 +60,6 @@ internal class EnsureOlmSessionsForUsersAction(private val olmDevice: MXOlmDevic
devicesByUser[userId]!!.add(device)
}
}
ensureOlmSessionsForDevicesAction.handle(devicesByUser, callback)
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
}
}

View file

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

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content
@ -31,7 +32,7 @@ internal interface IMXEncrypting {
* @param eventContent the content of the event.
* @param eventType the type of the event.
* @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
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.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
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.*
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
@ -69,7 +62,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
@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)
}
@ -78,7 +71,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON))
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON))
}
var eventDecryptionResult: MXEventDecryptionResult? = null
@ -90,7 +83,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} catch (e: MXDecryptionException) {
cryptoError = e.cryptoError
}
// the decryption succeeds
if (decryptGroupMessageResult?.payload != null && cryptoError == null) {
eventDecryptionResult = MXEventDecryptionResult()
@ -105,9 +97,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!!
} else if (cryptoError != null) {
if (cryptoError.isOlmError) {
if (TextUtils.equals("UNKNOWN_MESSAGE_INDEX", cryptoError.message)) {
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
@ -176,20 +167,19 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/
private fun addEventToPendingList(event: Event, timelineId: String) {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
val k = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
if (!pendingEvents.containsKey(k)) {
pendingEvents[k] = HashMap()
if (!pendingEvents.containsKey(pendingEventsKey)) {
pendingEvents[pendingEventsKey] = HashMap()
}
if (!pendingEvents[k]!!.containsKey(timelineId)) {
pendingEvents[k]!!.put(timelineId, ArrayList<Event>())
if (!pendingEvents[pendingEventsKey]!!.containsKey(timelineId)) {
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)
pendingEvents[k]!![timelineId]!!.add(event)
pendingEvents[pendingEventsKey]!![timelineId]!!.add(event)
}
}
@ -213,7 +203,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()!!
if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
@ -239,7 +229,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key!!
} else {
Timber.v("## onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
if (null == senderKey) {
Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
@ -275,7 +265,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
* @param sessionId the session id
*/
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]
@ -309,6 +300,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
}
}
}
*/
}
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
@ -323,70 +315,46 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
}
val userId = request.userId!!
CoroutineScope(coroutineDispatchers.crypto).launch {
deviceListManager.downloadKeys(listOf(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)
deviceListManager
.downloadKeys(listOf(userId), false)
.flatMap {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)
if (deviceInfo == null) {
throw RuntimeException()
} else {
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
ensureOlmSessionsForDevicesAction
.handle(devicesByUser)
.flatMap {
val body = request.requestBody
val olmSessionResult = it.getObject(deviceId, userId)
if (olmSessionResult?.mSessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
Try.just(Unit)
}
Timber.v("## shareKeysWithDevice() : sharing keys for session " + body!!.senderKey + "|" + body.sessionId
+ " with device " + userId + ":" + deviceId)
val inboundGroupSession = olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
if (null != deviceInfo) {
val body = request.requestBody
val payloadJson = HashMap<String, Any>()
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(encodedPayload, userId, deviceId)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams)
}
ensureOlmSessionsForDevicesAction.handle(devicesByUser, object : MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>> {
override fun onSuccess(data: MXUsersDevicesMap<MXOlmSessionResult>) {
val olmSessionResult = data.getObject(deviceId, userId)
if (olmSessionResult?.mSessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
return
}
Timber.v("## shareKeysWithDevice() : sharing keys for session " + body!!.senderKey + "|" + body.sessionId
+ " with device " + userId + ":" + deviceId)
val inboundGroupSession = olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
val payloadJson = HashMap<String, Any>()
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(encodedPayload, userId, deviceId)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask
.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.store.IMXCryptoStore
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
internal class MXMegolmDecryptionFactory(private val mCredentials: Credentials,
@ -35,8 +34,7 @@ internal class MXMegolmDecryptionFactory(private val mCredentials: Credentials,
private val mEnsureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val mCryptoStore: IMXCryptoStore,
private val mSendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val mTaskExecutor: TaskExecutor) {
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
fun create(): MXMegolmDecryption {
return MXMegolmDecryption(

View file

@ -19,13 +19,10 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm
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.failure.Failure
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
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.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.keysbackup.KeysBackup
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.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
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 timber.log.Timber
import java.util.*
@ -50,7 +43,6 @@ import java.util.*
internal class MXMegolmEncryption(
// The id of the room we will be sending to.
private var roomId: String,
private val olmDevice: MXOlmDevice,
private val keysBackup: KeysBackup,
private val cryptoStore: IMXCryptoStore,
@ -58,7 +50,6 @@ internal class MXMegolmEncryption(
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials,
private val sendToDeviceTask: SendToDeviceTask,
private val taskExecutor: TaskExecutor,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository
) : IMXEncrypting {
@ -69,84 +60,20 @@ internal class MXMegolmEncryption(
// case outboundSession.shareOperation will be non-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
// TODO: Make it configurable via parameters
// Session rotation periods
private var sessionRotationPeriodMsgs: Int = 100
private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000
/**
* @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,
userIds: List<String>,
callback: MatrixCallback<Content>) {
// Queue the encryption request
// It will be processed when everything is set up
val queuedEncryption = MXQueuedEncryption()
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)
override suspend fun encryptEventContent(eventContent: Content,
eventType: String,
userIds: List<String>): Try<Content> {
return getDevicesInRoom(userIds)
.flatMap { ensureOutboundSession(it) }
.flatMap {
encryptContent(it, eventType, eventContent)
}
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)
}
})
}
/**
@ -161,7 +88,7 @@ internal class MXMegolmEncryption(
keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
ArrayList(), keysClaimedMap, false)
ArrayList(), keysClaimedMap, false)
keysBackup.maybeBackupKeys()
@ -172,62 +99,33 @@ internal class MXMegolmEncryption(
* Ensure the outbound session
*
* @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
if (null == session
// Need to make a brand new session?
|| session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs)
// Determine if we have shared with anyone we shouldn't have
|| session.sharedWithTooManyDevices(devicesInRoom)) {
if (session == null
// Need to make a brand new session?
|| session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs)
// Determine if we have shared with anyone we shouldn't have
|| session.sharedWithTooManyDevices(devicesInRoom)) {
session = prepareNewSessionInRoom()
outboundSession = session
}
if (shareOperationIsProgress) {
Timber.v("## ensureOutboundSessionInRoom() : already in progress")
// Key share already in progress
return
}
val fSession = session
val safeSession = session
val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */
val userIds = devicesInRoom.userIds
for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) {
val deviceInfo = devicesInRoom.getObject(deviceId, userId)
if (null == fSession.mSharedWithDevices.getObject(deviceId, userId)) {
if (null == safeSession.sharedWithDevices.getObject(deviceId, userId)) {
if (!shareMap.containsKey(userId)) {
shareMap[userId] = ArrayList()
}
shareMap[userId]!!.add(deviceInfo)
}
}
}
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
}
})
return shareKey(safeSession, shareMap).map { safeSession!! }
}
/**
@ -235,55 +133,33 @@ internal class MXMegolmEncryption(
*
* @param session the session info
* @param devicesByUsers the devices map
* @param callback the asynchronous callback
*/
private fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: MutableMap<String, MutableList<MXDeviceInfo>>,
callback: MatrixCallback<Unit>?) {
private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> {
// nothing to send, the task is done
if (0 == devicesByUsers.size) {
if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do")
if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(Unit) }
}
return
return Try.just(Unit)
}
// 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 userIds = ArrayList<String>()
var devicesCount = 0
for (userId in devicesByUsers.keys) {
val devicesList = devicesByUsers[userId]
userIds.add(userId)
subMap[userId] = devicesList!!
devicesCount += devicesList.size
if (devicesCount > 100) {
break
}
}
Timber.v("## shareKey() ; userId $userIds")
shareUserDevicesKey(session, subMap, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
for (userId in userIds) {
devicesByUsers.remove(userId)
return shareUserDevicesKey(session, subMap)
.flatMap {
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it) }
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 callback the asynchronous callback
*/
private fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>,
callback: MatrixCallback<Unit>?) {
val sessionKey = olmDevice.getSessionKey(session.mSessionId)
val chainIndex = olmDevice.getMessageIndex(session.mSessionId)
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> {
val sessionKey = olmDevice.getSessionKey(session.sessionId)
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
val submap = HashMap<String, Any>()
submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
submap["room_id"] = roomId
submap["session_id"] = session.mSessionId
submap["session_id"] = session.sessionId
submap["session_key"] = sessionKey!!
submap["chain_index"] = chainIndex
@ -310,56 +185,48 @@ internal class MXMegolmEncryption(
payload["type"] = EventType.ROOM_KEY
payload["content"] = submap
val t0 = System.currentTimeMillis()
var t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : starts")
ensureOlmSessionsForDevicesAction.handle(devicesByUser, object : MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>> {
override fun onSuccess(data: MXUsersDevicesMap<MXOlmSessionResult>) {
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>()
var haveTargets = false
val userIds = data.userIds
for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) {
val sessionResult = data.getObject(deviceID, userId)
if (null == sessionResult || null == sessionResult.mSessionId) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.flatMap {
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>()
var haveTargets = false
val userIds = it.userIds
for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) {
val sessionResult = it.getObject(deviceID, userId)
if (sessionResult?.mSessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.mDevice)), userId, deviceID)
haveTargets = true
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.mDevice)), userId, deviceID)
haveTargets = true
}
}
if (haveTargets) {
val t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap))
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
.map {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
+ (System.currentTimeMillis() - t0) + " ms")
// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
@ -368,80 +235,45 @@ internal class MXMegolmEncryption(
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.mSharedWithDevices.setObject(chainIndex, userId, deviceId)
session.sharedWithDevices.setObject(chainIndex, userId, deviceId)
}
}
CryptoAsyncHelper.getUiHandler().post {
callback?.onSuccess(Unit)
}
Unit
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareUserDevicesKey() : sendToDevice")
callback?.onFailure(failure)
}
})
.executeBy(taskExecutor)
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
if (null != callback) {
CryptoAsyncHelper.getUiHandler().post { callback.onSuccess(Unit) }
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
Try.just(Unit)
}
}
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## shareUserDevicesKey() : ensureOlmSessionsForDevices failed")
callback?.onFailure(failure)
}
})
}
/**
* process the pending encryptions
*/
private fun processPendingEncryptions(session: MXOutboundSessionInfo?) {
if (null != session) {
val queuedEncryptions = pendingEncryptions
private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
payloadJson["type"] = eventType
payloadJson["content"] = eventContent
// Everything is in place, encrypt all pending events
for (queuedEncryption in queuedEncryptions) {
val payloadJson = HashMap<String, Any>()
// Get canonical Json from
payloadJson["room_id"] = roomId
payloadJson["type"] = queuedEncryption.eventType!!
payloadJson["content"] = queuedEncryption.eventContent!!
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
// Get canonical Json from
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.mSessionId, payloadString!!)
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.mSessionId
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
CryptoAsyncHelper.getUiHandler().post { queuedEncryption.apiCallback?.onSuccess(map) }
session.mUseCount++
}
synchronized(_pendingEncryptions) {
_pendingEncryptions.removeAll(queuedEncryptions)
}
}
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
session.useCount++
map
}
/**
@ -451,65 +283,47 @@ internal class MXMegolmEncryption(
* @param userIds the user ids whose devices must be checked.
* @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
// 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
// an m.new_device.
deviceListManager.downloadKeys(userIds, false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
return deviceListManager
.downloadKeys(userIds, false)
.map {
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
for (userId in data.userIds) {
val deviceIds = data.getUserDeviceIds(userId)
for (userId in it.userIds) {
val deviceIds = it.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) {
val deviceInfo = data.getObject(deviceId, userId)
for (deviceId in deviceIds!!) {
val deviceInfo = it.getObject(deviceId, userId)
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo!!.isUnknown) {
// The device is not yet known by the user
unknownDevices.setObject(deviceInfo, userId, deviceId)
continue
}
if (deviceInfo!!.isBlocked) {
// Remove any blocked devices
continue
}
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo!!.isUnknown) {
// The device is not yet known by the user
unknownDevices.setObject(deviceInfo, userId, deviceId)
continue
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(deviceInfo, userId, deviceId)
}
if (deviceInfo!!.isBlocked) {
// Remove any blocked devices
continue
}
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
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,
mCredentials,
mSendToDeviceTask,
mTaskExecutor,
mMessageEncrypter,
mWarnOnUnknownDevicesRepository)
}

View file

@ -23,23 +23,23 @@ import timber.log.Timber
internal class MXOutboundSessionInfo(
// The id of the session
val mSessionId: String) {
val sessionId: String) {
// When the session was created
private val mCreationTime = System.currentTimeMillis()
private val creationTime = System.currentTimeMillis()
// 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
// userId -> {deviceId -> msgindex}
val mSharedWithDevices: MXUsersDevicesMap<Int> = MXUsersDevicesMap()
val sharedWithDevices: MXUsersDevicesMap<Int> = MXUsersDevicesMap()
fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean {
var needsRotation = false
val sessionLifetime = System.currentTimeMillis() - mCreationTime
val sessionLifetime = System.currentTimeMillis() - creationTime
if (mUseCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
Timber.v("## needsRotation() : Rotating megolm session after " + mUseCount + ", " + sessionLifetime + "ms")
if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
Timber.v("## needsRotation() : Rotating megolm session after " + useCount + ", " + sessionLifetime + "ms")
needsRotation = true
}
@ -53,7 +53,7 @@ internal class MXOutboundSessionInfo(
* @return true if we have shared the session with devices which aren't in devicesInRoom.
*/
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Boolean {
val userIds = mSharedWithDevices.userIds
val userIds = sharedWithDevices.userIds
for (userId in userIds) {
if (null == devicesInRoom.getUserDeviceIds(userId)) {
@ -61,7 +61,7 @@ internal class MXOutboundSessionInfo(
return true
}
val deviceIds = mSharedWithDevices.getUserDeviceIds(userId)
val deviceIds = sharedWithDevices.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) {
if (null == devicesInRoom.getObject(deviceId, userId)) {

View file

@ -44,7 +44,7 @@ internal class MXOlmDecryption(
: IMXDecrypting {
@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>()!!
if (null == olmEventContent.ciphertext) {

View file

@ -19,7 +19,7 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm
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.toContent
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.algorithms.IMXEncrypting
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.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.*
internal class MXOlmEncryption(
@ -42,38 +37,28 @@ internal class MXOlmEncryption(
private val cryptoStore: IMXCryptoStore,
private val messageEncrypter: MessageEncrypter,
private val deviceListManager: DeviceListManager,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting {
override fun encryptEventContent(eventContent: Content,
eventType: String,
userIds: List<String>,
callback: MatrixCallback<Content>) {
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> {
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up
CoroutineScope(coroutineDispatchers.crypto).launch {
ensureSession(userIds, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
return ensureSession(userIds)
.map {
val deviceInfos = ArrayList<MXDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
for (device in devices) {
val key = device.identityKey()
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
// Don't bother setting up session to ourself
continue
}
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}
@ -84,37 +69,22 @@ internal class MXOlmEncryption(
messageMap["content"] = eventContent
messageEncrypter.encryptMessage(messageMap, deviceInfos)
callback.onSuccess(messageMap.toContent()!!)
messageMap.toContent()!!
}
})
}
}
/**
* Ensure that the session
*
* @param users the user ids list
* @param callback the asynchronous callback
*/
private fun ensureSession(users: List<String>, callback: MatrixCallback<Unit>?) {
deviceListManager.downloadKeys(users, false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
private suspend fun ensureSession(users: List<String>): Try<Unit> {
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,
mMessageEncrypter,
mDeviceListManager,
coroutineDispatchers,
mEnsureOlmSessionsForUsersAction)
}
}

View file

@ -603,11 +603,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
}
override fun getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): OutgoingRoomKeyRequest? {
if (request.mRequestBody == null) {
if (request.requestBody == null) {
return null
}
val existingOne = getOutgoingRoomKeyRequest(request.mRequestBody!!)
val existingOne = getOutgoingRoomKeyRequest(request.requestBody!!)
if (existingOne != null) {
return existingOne
@ -615,11 +615,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
// Insert the request and return the one passed in parameter
doRealmTransaction(realmConfiguration) {
it.createObject(OutgoingRoomKeyRequestEntity::class.java, request.mRequestId).apply {
putRequestBody(request.mRequestBody)
putRecipients(request.mRecipients)
cancellationTxnId = request.mCancellationTxnId
state = request.mState.ordinal
it.createObject(OutgoingRoomKeyRequestEntity::class.java, request.requestId).apply {
putRequestBody(request.requestBody)
putRecipients(request.recipients)
cancellationTxnId = request.cancellationTxnId
state = request.state.ordinal
}
}
@ -638,11 +638,11 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
doRealmTransaction(realmConfiguration) {
val obj = OutgoingRoomKeyRequestEntity().apply {
requestId = request.mRequestId
cancellationTxnId = request.mCancellationTxnId
state = request.mState.ordinal
putRecipients(request.mRecipients)
putRequestBody(request.mRequestBody)
requestId = request.requestId
cancellationTxnId = request.cancellationTxnId
state = request.state.ordinal
putRecipients(request.recipients)
putRequestBody(request.requestBody)
}
it.insertOrUpdate(obj)

View file

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

View file

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.di
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.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.android.asCoroutineDispatcher
import org.koin.dsl.module.module
import java.util.concurrent.Executors
class MatrixModule(private val context: Context) {
@ -35,10 +35,11 @@ class MatrixModule(private val context: Context) {
}
single {
val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler()
MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO,
main = Dispatchers.Main,
crypto = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
computation = Dispatchers.IO,
main = Dispatchers.Main,
crypto = cryptoHandler.asCoroutineDispatcher("crypto")
)
}

View file

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

View file

@ -97,7 +97,6 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex)
// Give info to crypto module
// TODO Remove
roomSync.state.events.forEach {
mCrypto.onStateEvent(roomId, it)
}

View file

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

View file

@ -45,15 +45,12 @@ class EncryptedItemFactory(
return when {
EventType.ENCRYPTED == timelineEvent.root.getClearType() -> {
val decrypted: MXEventDecryptionResult?
try {
decrypted = session.decryptEvent(timelineEvent.root, "TODO")
} catch (e: MXDecryptionException) {
val cryptoError = timelineEvent.root.mCryptoError
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)
} else {
e.localizedMessage
cryptoError?.message
}
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
@ -65,20 +62,6 @@ class EncryptedItemFactory(
.noticeText(spannableStr)
.avatarUrl(timelineEvent.senderAvatar)
.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
}