forked from GitHub-Mirror/riotX-android
Crypto: continue threading rework. WIP to shash
This commit is contained in:
parent
e125862794
commit
6b0ab10231
@ -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
|
||||||
|
@ -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,56 +240,41 @@ 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 {
|
||||||
|
internalStart(isInitialSync)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun internalStart(isInitialSync: Boolean) {
|
||||||
|
if (isStarted.get() || isStarting.get()) {
|
||||||
return
|
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)
|
isStarting.set(true)
|
||||||
// Open the store
|
// Open the store
|
||||||
cryptoStore.open()
|
cryptoStore.open()
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
uploadDeviceKeys()
|
||||||
uploadDeviceKeys()
|
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() }
|
||||||
.flatMap {
|
.fold(
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
{
|
||||||
}
|
Timber.e("Start failed: $it")
|
||||||
.handleError {
|
delay(1000)
|
||||||
Handler().postDelayed(
|
isStarting.set(false)
|
||||||
{
|
internalStart(isInitialSync)
|
||||||
if (!isStarted()) {
|
},
|
||||||
isStarting.set(false)
|
{
|
||||||
start(isInitialSync)
|
isStarting.set(false)
|
||||||
}
|
isStarted.set(true)
|
||||||
}, 1000
|
outgoingRoomKeyRequestManager.start()
|
||||||
)
|
keysBackup.checkAndStartKeysBackup()
|
||||||
}
|
if (isInitialSync) {
|
||||||
.fold(
|
// refresh the devices list for each known room members
|
||||||
{
|
deviceListManager.invalidateAllDeviceLists()
|
||||||
Timber.e("Start failed: $it")
|
deviceListManager.refreshOutdatedDeviceLists()
|
||||||
},
|
} else {
|
||||||
{
|
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||||
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
|
* @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
|
|
||||||
if (!isStarted()) {
|
|
||||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
|
||||||
start(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||||
|
if (!isStarted()) {
|
||||||
|
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||||
|
internalStart(false)
|
||||||
|
}
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
var alg = synchronized(roomEncryptors) {
|
var alg = synchronized(roomEncryptors) {
|
||||||
roomEncryptors[roomId]
|
roomEncryptors[roomId]
|
||||||
@ -580,24 +561,21 @@ 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(
|
||||||
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
{ callback.onFailure(it) },
|
||||||
callback.onSuccess(MXEncryptEventContentResult(data, EventType.ENCRYPTED))
|
{
|
||||||
}
|
Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
||||||
|
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
|
||||||
}
|
}
|
||||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
|
return runBlocking {
|
||||||
if (alg == null) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
|
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
|
||||||
Timber.e("## decryptEvent() : $reason")
|
if (alg == null) {
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
|
||||||
} else {
|
Timber.e("## decryptEvent() : $reason")
|
||||||
return runBlocking {
|
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
||||||
withContext(coroutineDispatchers.crypto) {
|
} else {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,7 +673,7 @@ internal class CryptoManager(
|
|||||||
monarchy.doWithRealm { realm ->
|
monarchy.doWithRealm { realm ->
|
||||||
// Check whether the event content must be encrypted for the invited members.
|
// Check whether the event content must be encrypted for the invited members.
|
||||||
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
|
|
||||||
userIds = if (encryptForInvitedMembers) {
|
userIds = if (encryptForInvitedMembers) {
|
||||||
RoomMembers(realm, roomId).getActiveRoomMemberIds()
|
RoomMembers(realm, roomId).getActiveRoomMemberIds()
|
||||||
@ -734,8 +708,8 @@ internal class CryptoManager(
|
|||||||
// make sure we are tracking the deviceList for this user.
|
// make sure we are tracking the deviceList for this user.
|
||||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
||||||
} else if (membership == Membership.INVITE
|
} else if (membership == Membership.INVITE
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
&& cryptoConfig.mEnableEncryptionForInvitedMembers) {
|
&& cryptoConfig.mEnableEncryptionForInvitedMembers) {
|
||||||
// track the deviceList for this invited user.
|
// track the deviceList for this invited user.
|
||||||
// Caution: there's a big edge case here in that federated servers do not
|
// 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.
|
// 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
|
// trigger an an unknown devices exception
|
||||||
callback.onFailure(
|
callback.onFailure(
|
||||||
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
|
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
|
* @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,9 +1031,9 @@ internal class CryptoManager(
|
|||||||
return unknownDevices
|
return unknownDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* DEBUG INFO
|
* DEBUG INFO
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,17 +96,23 @@ 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)
|
||||||
}
|
}
|
||||||
.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
|
* @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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,37 +43,35 @@ 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()
|
}
|
||||||
}
|
val alg = roomDecryptors[roomId]?.get(algorithm)
|
||||||
|
if (alg != null) {
|
||||||
alg = roomDecryptors[roomId]!![algorithm]
|
return alg
|
||||||
}
|
|
||||||
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!!)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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? {
|
fun getRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? {
|
||||||
|
@ -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 {
|
for (userId in userIds) {
|
||||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $data")
|
val deviceInfos = devicesByUser[userId]
|
||||||
|
for (deviceInfo in deviceInfos!!) {
|
||||||
for (userId in userIds) {
|
var oneTimeKey: MXKey? = null
|
||||||
val deviceInfos = devicesByUser[userId]
|
val deviceIds = it.getUserDeviceIds(userId)
|
||||||
|
if (null != deviceIds) {
|
||||||
for (deviceInfo in deviceInfos!!) {
|
for (deviceId in deviceIds) {
|
||||||
|
val olmSessionResult = results.getObject(deviceId, userId)
|
||||||
var oneTimeKey: MXKey? = null
|
if (olmSessionResult!!.mSessionId != null) {
|
||||||
|
// We already have a result for this device
|
||||||
val deviceIds = data.getUserDeviceIds(userId)
|
continue
|
||||||
|
|
||||||
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 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)
|
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +71,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
||||||
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
|
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE,
|
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
|
var eventDecryptionResult: MXEventDecryptionResult? = null
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +203,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
|
|
||||||
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
|
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
|
||||||
Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
|
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>()!!
|
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()!!
|
||||||
|
|
||||||
if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
|
if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
|
||||||
@ -239,7 +229,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key!!
|
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key!!
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
|
Timber.v("## onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
|
||||||
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
|
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
|
||||||
|
|
||||||
if (null == senderKey) {
|
if (null == senderKey) {
|
||||||
Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
|
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
|
* @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,70 +315,46 @@ 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)
|
||||||
val deviceId = request.deviceId
|
.flatMap {
|
||||||
val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)
|
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 payloadJson = HashMap<String, Any>()
|
||||||
val body = request.requestBody
|
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
|
||||||
|
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
|
||||||
|
|
||||||
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
|
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
|
||||||
devicesByUser[userId] = ArrayList(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")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
eventType: String,
|
||||||
*/
|
userIds: List<String>): Try<Content> {
|
||||||
private val pendingEncryptions: List<MXQueuedEncryption>
|
return getDevicesInRoom(userIds)
|
||||||
get() {
|
.flatMap { ensureOutboundSession(it) }
|
||||||
val list = ArrayList<MXQueuedEncryption>()
|
.flatMap {
|
||||||
synchronized(_pendingEncryptions) {
|
encryptContent(it, eventType, eventContent)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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!!
|
keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
|
||||||
|
|
||||||
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
||||||
ArrayList(), keysClaimedMap, false)
|
ArrayList(), keysClaimedMap, false)
|
||||||
|
|
||||||
keysBackup.maybeBackupKeys()
|
keysBackup.maybeBackupKeys()
|
||||||
|
|
||||||
@ -172,62 +99,33 @@ 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
|
|| session.sharedWithTooManyDevices(devicesInRoom)) {
|
||||||
|| session.sharedWithTooManyDevices(devicesInRoom)) {
|
|
||||||
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,56 +185,48 @@ 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 = it.userIds
|
||||||
val userIds = data.userIds
|
for (userId in userIds) {
|
||||||
|
val devicesToShareWith = devicesByUser[userId]
|
||||||
for (userId in userIds) {
|
for ((deviceID) in devicesToShareWith!!) {
|
||||||
val devicesToShareWith = devicesByUser[userId]
|
val sessionResult = it.getObject(deviceID, userId)
|
||||||
|
if (sessionResult?.mSessionId == null) {
|
||||||
for ((deviceID) in devicesToShareWith!!) {
|
// no session with this device, probably because there
|
||||||
|
// were no one-time keys.
|
||||||
val sessionResult = data.getObject(deviceID, userId)
|
//
|
||||||
|
// we could send them a to_device message anyway, as a
|
||||||
if (null == sessionResult || null == sessionResult.mSessionId) {
|
// signal that they have missed out on the key sharing
|
||||||
// no session with this device, probably because there
|
// message because of the lack of keys, but there's not
|
||||||
// were no one-time keys.
|
// much point in that really; it will mostly serve to clog
|
||||||
//
|
// up to_device inboxes.
|
||||||
// we could send them a to_device message anyway, as a
|
//
|
||||||
// signal that they have missed out on the key sharing
|
// ensureOlmSessionsForUsers has already done the logging,
|
||||||
// message because of the lack of keys, but there's not
|
// so just skip it.
|
||||||
// much point in that really; it will mostly serve to clog
|
continue
|
||||||
// up to_device inboxes.
|
}
|
||||||
//
|
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
|
||||||
// ensureOlmSessionsForUsers has already done the logging,
|
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
|
||||||
// so just skip it.
|
contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.mDevice)), userId, deviceID)
|
||||||
continue
|
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) {
|
||||||
|
t0 = System.currentTimeMillis()
|
||||||
if (haveTargets) {
|
Timber.v("## shareUserDevicesKey() : has target")
|
||||||
val t0 = System.currentTimeMillis()
|
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||||
Timber.v("## shareUserDevicesKey() : has target")
|
sendToDeviceTask.execute(sendToDeviceParams)
|
||||||
|
.map {
|
||||||
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap))
|
|
||||||
.dispatchTo(object : MatrixCallback<Unit> {
|
|
||||||
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")
|
||||||
|
|
||||||
// Add the devices we have shared with to session.sharedWithDevices.
|
// Add the devices we have shared with to session.sharedWithDevices.
|
||||||
// we deliberately iterate over devicesByUser (ie, the devices we
|
// we deliberately iterate over devicesByUser (ie, the devices we
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
override fun onFailure(failure: Throwable) {
|
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
||||||
Timber.e(failure, "## shareUserDevicesKey() : sendToDevice")
|
Try.just(Unit)
|
||||||
|
|
||||||
callback?.onFailure(failure)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
} else {
|
|
||||||
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
|
||||||
|
|
||||||
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) {
|
// Everything is in place, encrypt all pending events
|
||||||
val queuedEncryptions = pendingEncryptions
|
val payloadJson = HashMap<String, Any>()
|
||||||
|
payloadJson["room_id"] = roomId
|
||||||
|
payloadJson["type"] = eventType
|
||||||
|
payloadJson["content"] = eventContent
|
||||||
|
|
||||||
// Everything is in place, encrypt all pending events
|
// Get canonical Json from
|
||||||
for (queuedEncryption in queuedEncryptions) {
|
|
||||||
val payloadJson = HashMap<String, Any>()
|
|
||||||
|
|
||||||
payloadJson["room_id"] = roomId
|
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
|
||||||
payloadJson["type"] = queuedEncryption.eventType!!
|
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
|
||||||
payloadJson["content"] = queuedEncryption.eventContent!!
|
|
||||||
|
|
||||||
// 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))
|
// Include our device ID so that recipients can send us a
|
||||||
val ciphertext = olmDevice.encryptGroupMessage(session.mSessionId, payloadString!!)
|
// m.new_device message if they don't have our session key.
|
||||||
|
map["device_id"] = credentials.deviceId!!
|
||||||
val map = HashMap<String, Any>()
|
session.useCount++
|
||||||
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
|
map
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -451,65 +283,47 @@ 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)
|
||||||
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
.map {
|
||||||
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
||||||
|
|| 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) {
|
||||||
|
// 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) {
|
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
|
||||||
// The device is not yet known by the user
|
continue
|
||||||
unknownDevices.setObject(deviceInfo, userId, deviceId)
|
}
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,6 @@ internal class MXMegolmEncryptionFactory(
|
|||||||
mEnsureOlmSessionsForDevicesAction,
|
mEnsureOlmSessionsForDevicesAction,
|
||||||
mCredentials,
|
mCredentials,
|
||||||
mSendToDeviceTask,
|
mSendToDeviceTask,
|
||||||
mTaskExecutor,
|
|
||||||
mMessageEncrypter,
|
mMessageEncrypter,
|
||||||
mWarnOnUnknownDevicesRepository)
|
mWarnOnUnknownDevicesRepository)
|
||||||
}
|
}
|
||||||
|
@ -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)) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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,37 +69,22 @@ 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
|
||||||
*
|
*
|
||||||
* @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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ internal class MXOlmEncryptionFactory(private val mOlmDevice: MXOlmDevice,
|
|||||||
mCryptoStore,
|
mCryptoStore,
|
||||||
mMessageEncrypter,
|
mMessageEncrypter,
|
||||||
mDeviceListManager,
|
mDeviceListManager,
|
||||||
coroutineDispatchers,
|
|
||||||
mEnsureOlmSessionsForUsersAction)
|
mEnsureOlmSessionsForUsersAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
cryptoManager.start(isInitialSync)
|
if (!cryptoManager.isStarted()) {
|
||||||
|
cryptoManager.start(isInitialSync)
|
||||||
|
}
|
||||||
Timber.v("Finish handling sync in $measure ms")
|
Timber.v("Finish handling sync in $measure ms")
|
||||||
syncResponse
|
syncResponse
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
@ -65,20 +62,6 @@ class EncryptedItemFactory(
|
|||||||
.noticeText(spannableStr)
|
.noticeText(spannableStr)
|
||||||
.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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user