Prepare support for toDevice .request

This commit is contained in:
Valere 2020-01-30 16:11:34 +01:00
parent ff95392e10
commit 4ddd831d7f
9 changed files with 196 additions and 35 deletions

View file

@ -58,6 +58,14 @@ interface VerificationService {
localId: String? = LocalEcho.createLocalEchoId()
): PendingVerificationRequest
/**
* Request a key verification from another user using toDevice events.
*/
fun requestKeyVerification(methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?
): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String)
// Only SAS method is supported for the moment

View file

@ -18,18 +18,33 @@ package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
@JsonClass(generateAdapter = true)
data class MessageVerificationRequestContent(
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
@Json(name = "body") override val body: String,
@Json(name = "from_device") val fromDevice: String,
@Json(name = "methods") val methods: List<String>,
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,
@Json(name = "to") val toUserId: String,
// @Json(name = "timestamp") val timestamp: Int,
@Json(name = "timestamp") override val timestamp: Long?,
@Json(name = "format") val format: String? = null,
@Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent
) : MessageContent, VerificationInfoRequest {
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
return false
}
return true
}
override val transactionID: String?
get() = relatesTo?.eventId
override fun toEventContent() = toContent()
}

View file

@ -17,32 +17,26 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
/**
* Requests a key verification with another user's devices.
*/
@JsonClass(generateAdapter = true)
internal data class KeyVerificationRequest(
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,
@Json(name = "methods") override val timestamp: Long?,
@Json(name = "transaction_id") override var transactionID: String? = null
@Json(name = "from_device")
val fromDevice: String,
/** The verification methods supported by the sender. */
val methods: List<String>,
/**
* The POSIX timestamp in milliseconds for when the request was made.
* If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
* the message should be ignored by the receiver.
*/
val timestamp: Int,
) : SendToDeviceObject, VerificationInfoRequest {
@Json(name = "transaction_id")
override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfo {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
// TODO
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
return false
}
return true
}
}

View file

@ -55,6 +55,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationRequest
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
@ -124,6 +125,9 @@ internal class DefaultVerificationService @Inject constructor(
EventType.KEY_VERIFICATION_MAC -> {
onMacReceived(event)
}
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
onRequestReceived(event)
}
else -> {
// ignore
}
@ -259,11 +263,51 @@ internal class DefaultVerificationService @Inject constructor(
}
}
private fun onRequestReceived(event: Event) {
val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!!
if (!requestInfo.isValid()) {
// ignore
Timber.e("## SAS Received invalid key request")
return
}
val senderId = event.senderId ?: return
// We don't want to block here
val otherDeviceId = requestInfo.fromDevice ?: return
GlobalScope.launch {
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
Timber.e("## Verification device $otherDeviceId is not knwon")
}
}
// Remember this request
val requestsForUser = pendingRequests[senderId]
?: ArrayList<PendingVerificationRequest>().also {
pendingRequests[event.senderId] = it
}
val pendingVerificationRequest = PendingVerificationRequest(
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
isIncoming = true,
otherUserId = senderId, // requestInfo.toUserId,
roomId = null,
transactionId = event.eventId,
requestInfo = requestInfo
)
requestsForUser.add(pendingVerificationRequest)
dispatchRequestAdded(pendingVerificationRequest)
}
suspend fun onRoomRequestReceived(event: Event) {
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
?: return
val senderId = event.senderId ?: return
val fromDevice = requestInfo.fromDevice ?: return
if (requestInfo.toUserId != userId) {
// I should ignore this, it's not for me
@ -273,8 +317,8 @@ internal class DefaultVerificationService @Inject constructor(
// We don't want to block here
GlobalScope.launch {
if (checkKeysAreDownloaded(senderId, requestInfo.fromDevice) == null) {
Timber.e("## SAS Verification device ${requestInfo.fromDevice} is not knwon")
if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
Timber.e("## SAS Verification device $fromDevice is not knwon")
}
}
@ -894,7 +938,7 @@ internal class DefaultVerificationService @Inject constructor(
}
.distinct()
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId) { syncedId, info ->
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId, null) { syncedId, info ->
// We need to update with the syncedID
updatePendingRequest(verificationRequest.copy(
transactionId = syncedId,
@ -908,6 +952,69 @@ internal class DefaultVerificationService @Inject constructor(
return verificationRequest
}
override fun requestKeyVerification(methods: List<VerificationMethod>, otherUserId: String, otherDevices: List<String>?): PendingVerificationRequest {
// TODO refactor this with the DM one
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
val requestsForUser = pendingRequests[otherUserId]
?: ArrayList<PendingVerificationRequest>().also {
pendingRequests[otherUserId] = it
}
val transport = verificationTransportToDeviceFactory.createTransport(null)
// Cancel existing pending requests?
requestsForUser.toImmutableList().forEach { existingRequest ->
existingRequest.transactionId?.let { tid ->
if (!existingRequest.isFinished) {
Timber.d("## SAS, cancelling pending requests to start a new one")
updatePendingRequest(existingRequest.copy(cancelConclusion = CancelCode.User))
existingRequest.targetDevices?.forEach {
transport.cancelTransaction(tid, existingRequest.otherUserId, it, CancelCode.User)
}
}
}
}
val localID = LocalEcho.createLocalEchoId()
val verificationRequest = PendingVerificationRequest(
transactionId = localID,
ageLocalTs = System.currentTimeMillis(),
isIncoming = false,
roomId = null,
localID = localID,
otherUserId = otherUserId,
targetDevices = targetDevices
)
// We can SCAN or SHOW QR codes only if cross-signing is enabled
val methodValues = if (crossSigningService.isCrossSigningEnabled()) {
// Add reciprocate method if application declares it can scan or show QR codes
// Not sure if it ok to do that (?)
val reciprocateMethod = methods
.firstOrNull { it == VerificationMethod.QR_CODE_SCAN || it == VerificationMethod.QR_CODE_SHOW }
?.let { listOf(VERIFICATION_METHOD_RECIPROCATE) }.orEmpty()
methods.map { it.toValue() } + reciprocateMethod
} else {
// Filter out SCAN and SHOW qr code method
methods
.filter { it != VerificationMethod.QR_CODE_SHOW && it != VerificationMethod.QR_CODE_SCAN }
.map { it.toValue() }
}
.distinct()
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, _ ->
// Nothing special to do in to device mode
}
requestsForUser.add(verificationRequest)
dispatchRequestAdded(verificationRequest)
return verificationRequest
}
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
verificationTransportRoomMessageFactory.createTransport(roomId, null)
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)

View file

@ -34,11 +34,13 @@ data class PendingVerificationRequest(
val otherUserId: String,
val roomId: String?,
val transactionId: String? = null,
val requestInfo: MessageVerificationRequestContent? = null,
val requestInfo: VerificationInfoRequest? = null,
val readyInfo: VerificationInfoReady? = null,
val cancelConclusion: CancelCode? = null,
val isSuccessful: Boolean = false,
val handledByOtherSession: Boolean = false
val handledByOtherSession: Boolean = false,
// In case of to device it is sent to a list of devices
val targetDevices: List<String> ? = null
) {
val isReady: Boolean = readyInfo != null
val isSent: Boolean = transactionId != null

View file

@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
/**
* Verification can be performed using toDevice events or via DM.
@ -37,8 +36,9 @@ internal interface VerificationTransport {
fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit)
roomId: String?,
toDevices: List<String>?,
callback: (String?, VerificationInfoRequest?) -> Unit)
fun cancelTransaction(transactionId: String,
otherUserId: String,

View file

@ -140,12 +140,17 @@ internal class VerificationTransportRoomMessage(
override fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit) {
roomId: String?,
toDevices: List<String>?,
callback: (String?, VerificationInfoRequest?) -> Unit) {
// This transport requires a room
requireNotNull(roomId)
val info = MessageVerificationRequestContent(
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
fromDevice = userDeviceId ?: "",
toUserId = otherUserId,
timestamp = System.currentTimeMillis(),
methods = supportedMethods
)
val content = info.toContent()

View file

@ -19,17 +19,19 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationReady
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationRequest
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.DeviceId
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import timber.log.Timber
@ -38,15 +40,41 @@ import javax.inject.Inject
internal class VerificationTransportToDevice(
private var tx: DefaultVerificationTransaction?,
private var sendToDeviceTask: SendToDeviceTask,
private val myDeviceId: String?,
private var taskExecutor: TaskExecutor
) : VerificationTransport {
override fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit) {
// TODO "not implemented"
roomId: String?,
toDevices: List<String>?,
callback: (String?, VerificationInfoRequest?) -> Unit) {
val contentMap = MXUsersDevicesMap<Any>()
val keyReq = KeyVerificationRequest(
fromDevice = myDeviceId,
methods = supportedMethods,
timestamp = System.currentTimeMillis(),
transactionID = localID
)
toDevices?.forEach {
contentMap.setObject(otherUserId, it, keyReq)
}
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localID)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## verification [$tx.transactionId] send toDevice request success")
callback.invoke(localID, keyReq)
}
override fun onFailure(failure: Throwable) {
Timber.e("## verification [$tx.transactionId] failed to send toDevice request")
}
}
}
.executeBy(taskExecutor)
}
override fun sendToOther(type: String,
@ -168,9 +196,10 @@ internal class VerificationTransportToDevice(
internal class VerificationTransportToDeviceFactory @Inject constructor(
private val sendToDeviceTask: SendToDeviceTask,
@DeviceId val myDeviceId: String?,
private val taskExecutor: TaskExecutor) {
fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
return VerificationTransportToDevice(tx, sendToDeviceTask, taskExecutor)
return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
}
}

View file

@ -298,6 +298,7 @@ internal class LocalEchoEventFactory @Inject constructor(
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
fromDevice = fromDevice,
toUserId = toUserId,
timestamp = System.currentTimeMillis(),
methods = methods
).toContent(),
unsignedData = UnsignedData(age = null, transactionId = localID)