Crypto: finally get a working encrypt/decrypt + SAS

This commit is contained in:
ganfra 2019-06-06 19:10:04 +02:00
parent 6b0ab10231
commit c4d7711d2f
9 changed files with 141 additions and 151 deletions

View File

@ -958,7 +958,7 @@ internal class CryptoManager(
setRoomBlacklistUnverifiedDevices(roomId, false)
}

// TODO Check if this method is still necessary
// TODO Check if this method is still necessary
/**
* Cancel any earlier room key request
*
@ -974,7 +974,7 @@ internal class CryptoManager(
* @param event the event to decrypt again.
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content!! // Wireeventcontent?
val wireContent = event.content!!

val algorithm = wireContent["algorithm"].toString()
val senderKey = wireContent["sender_key"].toString()

View File

@ -280,11 +280,8 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore,

/**
* Download the devices keys for a set of users.
* It must be called in getEncryptingThreadHandler() thread.
* The callback is called in the UI thread.
*
* @param downloadUsers the user ids list
* @param callback the asynchronous callback
*/
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList

internal class IncomingRoomKeyRequestManager(
private val credentials: Credentials,
@ -46,19 +47,15 @@ internal class IncomingRoomKeyRequestManager(

/**
* Called when we get an m.room_key_request event
* This method must be called on getEncryptingThreadHandler() thread.
* It must be called on CryptoThread
*
* @param event the announcement event.
*/
fun onRoomKeyRequestEvent(event: Event) {
suspend fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> synchronized(receivedRoomKeyRequests) {
receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
}
RoomKeyShare.ACTION_SHARE_CANCELLATION -> synchronized(receivedRoomKeyRequestCancellations) {
receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
}
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action)
}
}
@ -66,86 +63,68 @@ internal class IncomingRoomKeyRequestManager(
/**
* Process any m.room_key_request events which were queued up during the
* current sync.
* It must be called on CryptoThread
*/
fun processReceivedRoomKeyRequests() {
var receivedRoomKeyRequests: List<IncomingRoomKeyRequest>? = null
val roomKeyRequestsToProcess = ArrayList(receivedRoomKeyRequests)
receivedRoomKeyRequests.clear()
for (request in roomKeyRequestsToProcess) {
val userId = request.userId
val deviceId = request.deviceId
val body = request.requestBody
val roomId = body!!.roomId
val alg = body.algorithm

synchronized(this.receivedRoomKeyRequests) {
if (this.receivedRoomKeyRequests.isNotEmpty()) {
receivedRoomKeyRequests = ArrayList(this.receivedRoomKeyRequests)
this.receivedRoomKeyRequests.clear()
Timber.v("m.room_key_request from " + userId + ":" + deviceId + " for " + roomId + " / " + body.sessionId + " id " + request.requestId)
if (userId == null || credentials.userId != userId) {
// TODO: determine if we sent this device the keys already: in
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
return
}
}

if (null != receivedRoomKeyRequests) {
for (request in receivedRoomKeyRequests!!) {
val userId = request.userId!!
val deviceId = request.deviceId
val body = request.requestBody
val roomId = body!!.roomId
val alg = body.algorithm

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

if (!TextUtils.equals(credentials.userId, userId)) {
// TODO: determine if we sent this device the keys already: in
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
return
}

// todo: should we queue up requests we don't yet have keys for,
// in case they turn up later?

// if we don't have a decryptor for this room/alg, we don't have
// the keys for the requested events, and can drop the requests.

val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)

if (null == decryptor) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
continue
}

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

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

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

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

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

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

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

cryptoStore.storeIncomingRoomKeyRequest(request)
onRoomKeyRequest(request)
// todo: should we queue up requests we don't yet have keys for, in case they turn up later?
// if we don't have a decryptor for this room/alg, we don't have
// the keys for the requested events, and can drop the requests.
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
if (null == decryptor) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
continue
}
if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session " + body.sessionId!!)
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}

if (TextUtils.equals(deviceId, credentials.deviceId) && TextUtils.equals(credentials.userId, userId)) {
Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored")
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
request.share = Runnable {
decryptor.shareKeysWithDevice(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
request.ignore = Runnable {
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
// if the device is verified already, share the keys
val device = cryptoStore.getUserDevice(deviceId!!, userId)
if (device != null) {
if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
cryptoStore.deleteIncomingRoomKeyRequest(request)
request.share?.run()
continue
}

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

var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null

View File

@ -194,25 +194,22 @@ internal class MXMegolmDecryption(private val credentials: Credentials,

var senderKey: String? = event.getSenderKey()
var keysClaimed: MutableMap<String, String> = HashMap()
var forwarding_curve25519_key_chain: MutableList<String>? = null
var forwardingCurve25519KeyChain: MutableList<String> = ArrayList()

if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.sessionId) || TextUtils.isEmpty(roomKeyContent.sessionKey)) {
Timber.e("## onRoomKeyEvent() : Key event is missing fields")
return
}

if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()!!

if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
forwarding_curve25519_key_chain = ArrayList()
forwardingCurve25519KeyChain = if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
ArrayList()
} else {
forwarding_curve25519_key_chain = ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain!!)
ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain!!)
}

forwarding_curve25519_key_chain.add(senderKey!!)
forwardingCurve25519KeyChain.add(senderKey!!)

exportFormat = true
senderKey = forwardedRoomKeyContent.senderKey
@ -239,8 +236,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
// inherit the claimed ed25519 key from the setup message
keysClaimed = event.getKeysClaimed().toMutableMap()
}

val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId!!, roomKeyContent.sessionKey!!, roomKeyContent.roomId!!, senderKey, forwarding_curve25519_key_chain!!, keysClaimed, exportFormat)
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId!!, roomKeyContent.sessionKey!!, roomKeyContent.roomId!!, senderKey, forwardingCurve25519KeyChain, keysClaimed, exportFormat)

if (added) {
keysBackup.maybeBackupKeys()
@ -318,41 +314,41 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
deviceListManager
.downloadKeys(listOf(userId), false)
.flatMap {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)
if (deviceInfo == null) {
throw RuntimeException()
} else {
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
ensureOlmSessionsForDevicesAction
.handle(devicesByUser)
.flatMap {
val body = request.requestBody
val olmSessionResult = it.getObject(deviceId, userId)
if (olmSessionResult?.mSessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
Try.just(Unit)
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)

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)
}
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)
}


}
}
}
}
}

View File

@ -88,7 +88,7 @@ internal class MXMegolmEncryption(
keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!

olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
ArrayList(), keysClaimedMap, false)
ArrayList(), keysClaimedMap, false)

keysBackup.maybeBackupKeys()

@ -103,10 +103,10 @@ internal class MXMegolmEncryption(
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> {
var session = outboundSession
if (session == null
// Need to make a brand new session?
|| session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs)
// Determine if we have shared with anyone we shouldn't have
|| session.sharedWithTooManyDevices(devicesInRoom)) {
// Need to make a brand new session?
|| session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs)
// Determine if we have shared with anyone we shouldn't have
|| session.sharedWithTooManyDevices(devicesInRoom)) {
session = prepareNewSessionInRoom()
outboundSession = session
}
@ -146,10 +146,11 @@ internal class MXMegolmEncryption(
val userIds = ArrayList<String>()
var devicesCount = 0
for (userId in devicesByUsers.keys) {
val devicesList = devicesByUsers[userId]
userIds.add(userId)
subMap[userId] = devicesList!!
devicesCount += devicesList.size
devicesByUsers[userId]?.let {
userIds.add(userId)
subMap[userId] = it
devicesCount += it.size
}
if (devicesCount > 100) {
break
}
@ -157,7 +158,7 @@ internal class MXMegolmEncryption(
Timber.v("## shareKey() ; userId $userIds")
return shareUserDevicesKey(session, subMap)
.flatMap {
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it) }
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
shareKey(session, remainingDevices)
}
}
@ -191,7 +192,7 @@ internal class MXMegolmEncryption(
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.flatMap {
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
+ (System.currentTimeMillis() - t0) + " ms")
val contentMap = MXUsersDevicesMap<Any>()
var haveTargets = false
val userIds = it.userIds
@ -226,7 +227,7 @@ internal class MXMegolmEncryption(
sendToDeviceTask.execute(sendToDeviceParams)
.map {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
+ (System.currentTimeMillis() - t0) + " ms")

// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
@ -292,7 +293,7 @@ internal class MXMegolmEncryption(
.downloadKeys(userIds, false)
.map {
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)

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

View File

@ -223,7 +223,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
.fold(
{ error() },
{
if (it != null && it.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
if (it.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
success(it)
} else {
error()

View File

@ -118,7 +118,7 @@ internal class IncomingSASVerificationTransaction(
}

//Bobs device ensures that it has a copy of Alices device key.
val mxDeviceInfo = mCryptoStore.getUserDevice(this.otherUserId, otherDeviceId!!)
val mxDeviceInfo = mCryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId)

if (mxDeviceInfo?.fingerprint() == null) {
Timber.e("## Failed to find device key ")

View File

@ -16,36 +16,47 @@

package im.vector.matrix.android.internal.session.room.timeline

import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
import io.realm.Realm
import timber.log.Timber
import java.util.*

internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomMemberExtractor,
private val cryptoService: CryptoService) {

private val cached = mutableMapOf<String, SenderData>()
private val timelineId = UUID.randomUUID().toString()
private val senderCache = mutableMapOf<String, SenderData>()
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>()

fun create(eventEntity: EventEntity, realm: Realm = eventEntity.realm): TimelineEvent {
val sender = eventEntity.sender
val cacheKey = sender + eventEntity.stateIndex
val senderData = cached.getOrPut(cacheKey) {
val senderData = senderCache.getOrPut(cacheKey) {
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm)
SenderData(senderRoomMember?.displayName, senderRoomMember?.avatarUrl)
}
val event = eventEntity.asDomain()
if (event.getClearType() == EventType.ENCRYPTED) {
try {
val result = cryptoService.decryptEvent(event, "TODO")
Timber.v("Encrypted event: try to decrypt ${event.eventId}")
val result = if (decryptionCache.containsKey(eventEntity.localId)) {
Timber.v("Encrypted event ${event.eventId} cached")
decryptionCache[eventEntity.localId]
} else {
cryptoService.decryptEvent(event, timelineId)?.also {
decryptionCache[eventEntity.localId] = it
}
}
event.setClearData(result)
} catch (e: Exception) {
Timber.e(e)
Timber.e(e, "Encrypted event: decryption failed")
if (e is MXDecryptionException) {
event.setCryptoError(e.cryptoError)
}
@ -62,7 +73,7 @@ internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomM
}

fun clear() {
cached.clear()
senderCache.clear()
}

private data class SenderData(

View File

@ -34,22 +34,28 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler,
val measure = measureTimeMillis {
// Handle the to device events before the room ones
// to ensure to decrypt them properly
Timber.v("Handle toDevice")
if (syncResponse.toDevice != null) {
cryptoSyncHandler.handleToDevice(syncResponse.toDevice)
}
Timber.v("Handle rooms")
if (syncResponse.rooms != null) {
roomSyncHandler.handle(syncResponse.rooms)
}
Timber.v("Handle groups")
if (syncResponse.groups != null) {
groupSyncHandler.handle(syncResponse.groups)
}
Timber.v("Handle accoundData")
if (syncResponse.accountData != null) {
userAccountDataSyncHandler.handle(syncResponse.accountData)
}
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse, fromToken, isCatchingUp)
}
val isInitialSync = fromToken == null
if (!cryptoManager.isStarted()) {
Timber.v("Should start cryptoManager")
cryptoManager.start(isInitialSync)
}
Timber.v("Finish handling sync in $measure ms")