BayernMessenger/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManag...

205 lines
8.4 KiB
Kotlin

/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import android.text.TextUtils
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
@SessionScope
internal class IncomingRoomKeyRequestManager @Inject constructor(
private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val roomDecryptorProvider: RoomDecryptorProvider) {
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync.
private val receivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
private val receivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()
// the listeners
private val roomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()
init {
receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
}
/**
* Called when we get an m.room_key_request event
* It must be called on CryptoThread
*
* @param event the announcement event.
*/
suspend fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action)
}
}
/**
* Process any m.room_key_request events which were queued up during the
* current sync.
* It must be called on CryptoThread
*/
fun processReceivedRoomKeyRequests() {
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
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
}
// 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
synchronized(this.receivedRoomKeyRequestCancellations) {
if (!this.receivedRoomKeyRequestCancellations.isEmpty()) {
receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
this.receivedRoomKeyRequestCancellations.clear()
}
}
if (null != receivedRoomKeyRequestCancellations) {
for (request in receivedRoomKeyRequestCancellations!!) {
Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId
+ ":" + request.deviceId + " id " + request.requestId)
// we should probably only notify the app of cancellations we told it
// about, but we don't currently have a record of that, so we just pass
// everything through.
onRoomKeyRequestCancellation(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
}
}
/**
* Dispatch onRoomKeyRequest
*
* @param request the request
*/
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequest(request)
} catch (e: Exception) {
Timber.e(e, "## onRoomKeyRequest() failed")
}
}
}
}
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequestCancellation(request)
} catch (e: Exception) {
Timber.e(e, "## onRoomKeyRequestCancellation() failed")
}
}
}
}
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.remove(listener)
}
}
}