Merge pull request #921 from vector-im/decorations

Add field in QR code and make some optional
This commit is contained in:
Benoit Marty 2020-01-30 18:48:30 +01:00 committed by GitHub
commit 506c2dd262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 453 additions and 136 deletions

View file

@ -47,7 +47,10 @@ interface VerificationService {
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String? fun beginKeyVerification(method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
transactionId: String?): String?
/** /**
* Request a key verification from another user using toDevice events. * Request a key verification from another user using toDevice events.
@ -62,11 +65,14 @@ interface VerificationService {
* Request a key verification from another user using toDevice events. * Request a key verification from another user using toDevice events.
*/ */
fun requestKeyVerification(methods: List<VerificationMethod>, fun requestKeyVerification(methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,
otherDevices: List<String>? otherDevices: List<String>?
): PendingVerificationRequest ): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) fun declineVerificationRequestInDMs(otherUserId: String,
otherDeviceId: String,
transactionId: String,
roomId: String)
// Only SAS method is supported for the moment // Only SAS method is supported for the moment
fun beginKeyVerificationInDMs(method: VerificationMethod, fun beginKeyVerificationInDMs(method: VerificationMethod,
@ -84,6 +90,13 @@ interface VerificationService {
roomId: String, roomId: String,
transactionId: String): Boolean transactionId: String): Boolean
/**
* Returns false if the request is unknown
*/
fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean
// fun transactionUpdated(tx: SasVerificationTransaction) // fun transactionUpdated(tx: SasVerificationTransaction)
interface VerificationListener { interface VerificationListener {

View file

@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReq
internal data class KeyVerificationRequest( internal data class KeyVerificationRequest(
@Json(name = "from_device") override val fromDevice: String?, @Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>, @Json(name = "methods") override val methods: List<String>,
@Json(name = "methods") override val timestamp: Long?, @Json(name = "timestamp") override val timestamp: Long?,
@Json(name = "transaction_id") override var transactionID: String? = null @Json(name = "transaction_id") override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfoRequest { ) : SendToDeviceObject, VerificationInfoRequest {

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.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey 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.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.KeyVerificationRequest
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart 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_SCAN
@ -110,25 +111,28 @@ internal class DefaultVerificationService @Inject constructor(
fun onToDeviceEvent(event: Event) { fun onToDeviceEvent(event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) { when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> { EventType.KEY_VERIFICATION_START -> {
onStartRequestReceived(event) onStartRequestReceived(event)
} }
EventType.KEY_VERIFICATION_CANCEL -> { EventType.KEY_VERIFICATION_CANCEL -> {
onCancelReceived(event) onCancelReceived(event)
} }
EventType.KEY_VERIFICATION_ACCEPT -> { EventType.KEY_VERIFICATION_ACCEPT -> {
onAcceptReceived(event) onAcceptReceived(event)
} }
EventType.KEY_VERIFICATION_KEY -> { EventType.KEY_VERIFICATION_KEY -> {
onKeyReceived(event) onKeyReceived(event)
} }
EventType.KEY_VERIFICATION_MAC -> { EventType.KEY_VERIFICATION_MAC -> {
onMacReceived(event) onMacReceived(event)
} }
MessageType.MSGTYPE_VERIFICATION_REQUEST -> { EventType.KEY_VERIFICATION_READY -> {
onReadyReceived(event)
}
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
onRequestReceived(event) onRequestReceived(event)
} }
else -> { else -> {
// ignore // ignore
} }
} }
@ -263,7 +267,6 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
private fun onRequestReceived(event: Event) { private fun onRequestReceived(event: Event) {
val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!! val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!!
@ -279,7 +282,7 @@ internal class DefaultVerificationService @Inject constructor(
GlobalScope.launch { GlobalScope.launch {
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) { if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
Timber.e("## Verification device $otherDeviceId is not knwon") Timber.e("## Verification device $otherDeviceId is not known")
} }
} }
@ -294,14 +297,13 @@ internal class DefaultVerificationService @Inject constructor(
isIncoming = true, isIncoming = true,
otherUserId = senderId, // requestInfo.toUserId, otherUserId = senderId, // requestInfo.toUserId,
roomId = null, roomId = null,
transactionId = event.eventId, transactionId = requestInfo.transactionID,
requestInfo = requestInfo requestInfo = requestInfo
) )
requestsForUser.add(pendingVerificationRequest) requestsForUser.add(pendingVerificationRequest)
dispatchRequestAdded(pendingVerificationRequest) dispatchRequestAdded(pendingVerificationRequest)
} }
suspend fun onRoomRequestReceived(event: Event) { suspend fun onRoomRequestReceived(event: Event) {
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
@ -318,7 +320,7 @@ internal class DefaultVerificationService @Inject constructor(
// We don't want to block here // We don't want to block here
GlobalScope.launch { GlobalScope.launch {
if (checkKeysAreDownloaded(senderId, fromDevice) == null) { if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
Timber.e("## SAS Verification device $fromDevice is not knwon") Timber.e("## SAS Verification device $fromDevice is not known")
} }
} }
@ -677,7 +679,29 @@ internal class DefaultVerificationService @Inject constructor(
return return
} }
handleReadyReceived(event.senderId, event.roomId!!, readyReq) handleReadyReceived(event.senderId, readyReq) {
verificationTransportRoomMessageFactory.createTransport(event.roomId!!, it)
}
}
private suspend fun onReadyReceived(event: Event) {
val readyReq = event.getClearContent().toModel<KeyVerificationReady>()
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
// ignore
Timber.e("## SAS Received invalid ready request")
// TODO should we cancel?
return
}
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
// TODO cancel?
return
}
handleReadyReceived(event.senderId, readyReq) {
verificationTransportToDeviceFactory.createTransport(it)
}
} }
private fun onRoomDoneReceived(event: Event) { private fun onRoomDoneReceived(event: Event) {
@ -722,7 +746,9 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
private fun handleReadyReceived(senderId: String, roomId: String, readyReq: VerificationInfoReady) { private fun handleReadyReceived(senderId: String,
readyReq: VerificationInfoReady,
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) {
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID } val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
if (existingRequest == null) { if (existingRequest == null) {
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}") Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
@ -733,10 +759,10 @@ internal class DefaultVerificationService @Inject constructor(
// Check if other user is able to scan QR code // Check if other user is able to scan QR code
?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) } ?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
?.let { ?.let {
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId) createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId, readyReq.fromDevice)
} }
if (readyReq.methods?.orEmpty().orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) { if (readyReq.methods.orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) {
// Create the pending transaction // Create the pending transaction
val tx = DefaultQrCodeVerificationTransaction( val tx = DefaultQrCodeVerificationTransaction(
setDeviceVerificationAction, setDeviceVerificationAction,
@ -750,7 +776,7 @@ internal class DefaultVerificationService @Inject constructor(
deviceId ?: "", deviceId ?: "",
false) false)
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) tx.transport = transportCreator.invoke(tx)
addTransaction(tx) addTransaction(tx)
} }
@ -760,13 +786,25 @@ internal class DefaultVerificationService @Inject constructor(
)) ))
} }
private fun createQrCodeData(transactionId: String?, otherUserId: String): QrCodeData? { private fun createQrCodeData(requestEventId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? {
// Build the QR code URL requestEventId ?: run {
val requestEventId = transactionId ?: run {
Timber.w("## Unknown requestEventId") Timber.w("## Unknown requestEventId")
return null return null
} }
return when {
userId != otherUserId ->
createQrCodeDataForDistinctUser(requestEventId, otherUserId, otherDeviceId)
crossSigningService.isCrossSigningVerified() ->
// This is a self verification and I am the old device (Osborne2)
createQrCodeDataForVerifiedDevice(requestEventId, otherDeviceId)
else ->
// This is a self verification and I am the new device (Dynabook)
createQrCodeDataForUnVerifiedDevice(requestEventId, otherDeviceId)
}
}
private fun createQrCodeDataForDistinctUser(requestEventId: String, otherUserId: String, otherDeviceId: String?): QrCodeData? {
val myMasterKey = crossSigningService.getMyCrossSigningKeys() val myMasterKey = crossSigningService.getMyCrossSigningKeys()
?.masterKey() ?.masterKey()
?.unpaddedBase64PublicKey ?.unpaddedBase64PublicKey
@ -789,9 +827,17 @@ internal class DefaultVerificationService @Inject constructor(
return null return null
} }
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!! val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()
?: run {
Timber.w("## Unable to get my fingerprint")
return null
}
val otherDeviceKey = otherDeviceId
?.let {
cryptoStore.getUserDevice(userId, otherDeviceId)?.fingerprint()
}
val generatedSharedSecret = generateSharedSecret()
return QrCodeData( return QrCodeData(
userId = userId, userId = userId,
requestEventId = requestEventId, requestEventId = requestEventId,
@ -800,8 +846,95 @@ internal class DefaultVerificationService @Inject constructor(
myMasterKey to myMasterKey, myMasterKey to myMasterKey,
myDeviceId to myDeviceKey myDeviceId to myDeviceKey
), ),
sharedSecret = generatedSharedSecret, sharedSecret = generateSharedSecret(),
otherUserKey = otherUserMasterKey otherUserKey = otherUserMasterKey,
otherDeviceKey = otherDeviceKey
)
}
// Create a QR code to display on the old device (Osborne2)
private fun createQrCodeDataForVerifiedDevice(requestEventId: String, otherDeviceId: String?): QrCodeData? {
val myMasterKey = crossSigningService.getMyCrossSigningKeys()
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("## Unable to get my master key")
return null
}
val otherDeviceKey = otherDeviceId
?.let {
cryptoStore.getUserDevice(userId, otherDeviceId)?.fingerprint()
}
?: run {
Timber.w("## Unable to get other device data")
return null
}
val myDeviceId = deviceId
?: run {
Timber.w("## Unable to get my deviceId")
return null
}
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()
?: run {
Timber.w("## Unable to get my fingerprint")
return null
}
return QrCodeData(
userId = userId,
requestEventId = requestEventId,
action = QrCodeData.ACTION_VERIFY,
keys = hashMapOf(
myMasterKey to myMasterKey,
myDeviceId to myDeviceKey
),
sharedSecret = generateSharedSecret(),
otherUserKey = null,
otherDeviceKey = otherDeviceKey
)
}
// Create a QR code to display on the new device (Dynabook)
private fun createQrCodeDataForUnVerifiedDevice(requestEventId: String, otherDeviceId: String?): QrCodeData? {
val myMasterKey = crossSigningService.getMyCrossSigningKeys()
?.masterKey()
?.unpaddedBase64PublicKey
?: run {
Timber.w("## Unable to get my master key")
return null
}
val myDeviceId = deviceId
?: run {
Timber.w("## Unable to get my deviceId")
return null
}
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()
?: run {
Timber.w("## Unable to get my fingerprint")
return null
}
val otherDeviceKey = otherDeviceId
?.let {
cryptoStore.getUserDevice(userId, otherDeviceId)?.fingerprint()
}
return QrCodeData(
userId = userId,
requestEventId = requestEventId,
action = QrCodeData.ACTION_VERIFY,
keys = hashMapOf(
// Note: no master key here
myDeviceId to myDeviceKey
),
sharedSecret = generateSharedSecret(),
otherUserKey = myMasterKey,
otherDeviceKey = otherDeviceKey
) )
} }
@ -866,8 +999,8 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String? { override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceId: String, transactionId: String?): String? {
val txID = createUniqueIDForTransaction(otherUserId, otherDeviceID) val txID = transactionId?.takeIf { it.isNotEmpty() } ?: createUniqueIDForTransaction(otherUserId, otherDeviceId)
// should check if already one (and cancel it) // should check if already one (and cancel it)
if (method == VerificationMethod.SAS) { if (method == VerificationMethod.SAS) {
val tx = DefaultOutgoingSASDefaultVerificationTransaction( val tx = DefaultOutgoingSASDefaultVerificationTransaction(
@ -879,7 +1012,7 @@ internal class DefaultVerificationService @Inject constructor(
myDeviceInfoHolder.get().myDevice.fingerprint()!!, myDeviceInfoHolder.get().myDevice.fingerprint()!!,
txID, txID,
otherUserId, otherUserId,
otherDeviceID) otherDeviceId)
tx.transport = verificationTransportToDeviceFactory.createTransport(tx) tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
addTransaction(tx) addTransaction(tx)
@ -1083,9 +1216,10 @@ internal class DefaultVerificationService @Inject constructor(
transactionId, transactionId,
otherUserId, otherUserId,
existingRequest.requestInfo?.fromDevice ?: "", existingRequest.requestInfo?.fromDevice ?: "",
roomId,
existingRequest.requestInfo?.methods, existingRequest.requestInfo?.methods,
methods) methods) {
verificationTransportRoomMessageFactory.createTransport(roomId, it)
}
if (methods.isNullOrEmpty()) { if (methods.isNullOrEmpty()) {
Timber.i("Cannot ready this request, no common methods found txId:$transactionId") Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
// TODO buttons should not be shown in this case? // TODO buttons should not be shown in this case?
@ -1108,13 +1242,52 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
override fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean {
Timber.v("## SAS readyPendingVerification $otherUserId tx:$transactionId")
// Let's find the related request
val existingRequest = getExistingVerificationRequest(otherUserId, transactionId)
if (existingRequest != null) {
// we need to send a ready event, with matching methods
val transport = verificationTransportToDeviceFactory.createTransport(null)
val computedMethods = computeReadyMethods(
transactionId,
otherUserId,
existingRequest.requestInfo?.fromDevice ?: "",
existingRequest.requestInfo?.methods,
methods) {
verificationTransportToDeviceFactory.createTransport(it)
}
if (methods.isNullOrEmpty()) {
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
// TODO buttons should not be shown in this case?
return false
}
// TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods)
transport.sendVerificationReady(
readyMsg,
otherUserId,
existingRequest.requestInfo?.fromDevice ?: "",
null // TODO handle error?
)
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
return true
} else {
Timber.e("## SAS readyPendingVerification Verification not found")
// :/ should not be possible... unless live observer very slow
return false
}
}
private fun computeReadyMethods( private fun computeReadyMethods(
transactionId: String, transactionId: String,
otherUserId: String, otherUserId: String,
otherDeviceId: String, otherDeviceId: String,
roomId: String,
otherUserMethods: List<String>?, otherUserMethods: List<String>?,
methods: List<VerificationMethod>): List<String> { methods: List<VerificationMethod>,
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport): List<String> {
if (otherUserMethods.isNullOrEmpty()) { if (otherUserMethods.isNullOrEmpty()) {
return emptyList() return emptyList()
} }
@ -1128,7 +1301,7 @@ internal class DefaultVerificationService @Inject constructor(
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) { if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) {
// Other user wants to verify using QR code. Cross-signing has to be setup // Other user wants to verify using QR code. Cross-signing has to be setup
val qrCodeData = createQrCodeData(transactionId, otherUserId) val qrCodeData = createQrCodeData(transactionId, otherUserId, otherDeviceId)
if (qrCodeData != null) { if (qrCodeData != null) {
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) { if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) {
@ -1157,7 +1330,7 @@ internal class DefaultVerificationService @Inject constructor(
deviceId ?: "", deviceId ?: "",
false) false)
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) tx.transport = transportCreator.invoke(tx)
addTransaction(tx) addTransaction(tx)
} }

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.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
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_SCAN
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
@ -40,7 +39,7 @@ data class PendingVerificationRequest(
val isSuccessful: Boolean = false, 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 // In case of to device it is sent to a list of devices
val targetDevices: List<String> ? = null val targetDevices: List<String>? = null
) { ) {
val isReady: Boolean = readyInfo != null val isReady: Boolean = readyInfo != null
val isSent: Boolean = transactionId != null val isSent: Boolean = transactionId != null

View file

@ -80,4 +80,10 @@ internal interface VerificationTransport {
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady
// TODO Refactor
fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
callback: (() -> Unit)?)
} }

View file

@ -332,6 +332,13 @@ internal class VerificationTransportRoomMessage(
localEchoEventFactory.saveLocalEcho(monarchy, it) localEchoEventFactory.saveLocalEcho(monarchy, it)
} }
} }
override fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
callback: (() -> Unit)?) {
// Not applicable
}
} }
internal class VerificationTransportRoomMessageFactory @Inject constructor( internal class VerificationTransportRoomMessageFactory @Inject constructor(

View file

@ -50,7 +50,6 @@ internal class VerificationTransportToDevice(
roomId: String?, roomId: String?,
toDevices: List<String>?, toDevices: List<String>?,
callback: (String?, VerificationInfoRequest?) -> Unit) { callback: (String?, VerificationInfoRequest?) -> Unit) {
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
val keyReq = KeyVerificationRequest( val keyReq = KeyVerificationRequest(
fromDevice = myDeviceId, fromDevice = myDeviceId,
@ -77,6 +76,30 @@ internal class VerificationTransportToDevice(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun sendVerificationReady(keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String,
callback: (() -> Unit)?) {
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherDeviceId, keyReq)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_READY, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## verification [$tx.transactionId] send toDevice request success")
callback?.invoke()
}
override fun onFailure(failure: Throwable) {
Timber.e("## verification [$tx.transactionId] failed to send toDevice request")
}
}
}
.executeBy(taskExecutor)
}
override fun sendToOther(type: String, override fun sendToOther(type: String,
verificationInfo: VerificationInfo, verificationInfo: VerificationInfo,
nextState: VerificationTxState, nextState: VerificationTxState,

View file

@ -88,12 +88,29 @@ internal class DefaultQrCodeVerificationTransaction(
} }
// check master key // check master key
if (otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { if (otherQrCodeData.userId != userId
&& otherQrCodeData.otherUserKey == null) {
// Verification with other user, other_user_key is mandatory in this case
Timber.d("## Verification QR: Invalid, missing other_user_key")
cancel(CancelCode.QrCodeInvalid)
return
}
if (otherQrCodeData.otherUserKey != null
&& otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserKey}") Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserKey}")
cancel(CancelCode.MismatchedKeys) cancel(CancelCode.MismatchedKeys)
return return
} }
// Check device key if available
if (otherQrCodeData.otherDeviceKey != null
&& otherQrCodeData.otherDeviceKey != cryptoStore.getUserDevice(otherQrCodeData.userId, otherDeviceId ?: "")?.fingerprint()) {
Timber.d("## Verification QR: Invalid other device key")
cancel(CancelCode.MismatchedKeys)
return
}
val toVerifyDeviceIds = mutableListOf<String>() val toVerifyDeviceIds = mutableListOf<String>()
var canTrustOtherUserMasterKey = false var canTrustOtherUserMasterKey = false
@ -147,8 +164,15 @@ internal class DefaultQrCodeVerificationTransaction(
// qrCodeData.sharedSecret will be used to send the start request // qrCodeData.sharedSecret will be used to send the start request
start(otherQrCodeData.sharedSecret) start(otherQrCodeData.sharedSecret)
val safeOtherDeviceId = otherDeviceId
if (!otherQrCodeData.otherDeviceKey.isNullOrBlank()
&& safeOtherDeviceId != null) {
// Locally verify the device
toVerifyDeviceIds.add(safeOtherDeviceId)
}
// Trust the other user // Trust the other user
trust(canTrustOtherUserMasterKey, toVerifyDeviceIds) trust(canTrustOtherUserMasterKey, toVerifyDeviceIds.distinct())
} }
fun start(remoteSecret: String) { fun start(remoteSecret: String) {
@ -240,8 +264,8 @@ internal class DefaultQrCodeVerificationTransaction(
// TODO what if the otherDevice is not in this list? and should we // TODO what if the otherDevice is not in this list? and should we
toVerifyDeviceIds.forEach { toVerifyDeviceIds.forEach {
setDeviceVerified(otherUserId, it) setDeviceVerified(otherUserId, it)
} }
transport.done(transactionId) transport.done(transactionId)
state = VerificationTxState.Verified state = VerificationTxState.Verified
} }

View file

@ -32,6 +32,7 @@ private const val ENCODING = "utf-8"
* &key_<keyid>=<key-in-base64>... * &key_<keyid>=<key-in-base64>...
* &secret=<shared_secret> * &secret=<shared_secret>
* &other_user_key=<master-key-in-base64> * &other_user_key=<master-key-in-base64>
* &other_device_key=<device-key-in-base64>
* *
* Example: * Example:
* https://matrix.to/#/@user:matrix.org? * https://matrix.to/#/@user:matrix.org?
@ -40,7 +41,8 @@ private const val ENCODING = "utf-8"
* &key_VJEDVKUYTQ=DL7LWIw7Qp%2B4AREDACTEDOwy2BjygumSWAGfzaWY * &key_VJEDVKUYTQ=DL7LWIw7Qp%2B4AREDACTEDOwy2BjygumSWAGfzaWY
* &key_fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo=fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo * &key_fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo=fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo
* &secret=AjQqw51Fp6UBuPolZ2FAD5WnXc22ZhJG6iGslrVvIdw%3D * &secret=AjQqw51Fp6UBuPolZ2FAD5WnXc22ZhJG6iGslrVvIdw%3D
* &other_user_key=WqSVLkBCS%2Fi5NqR%2F%2FymC8T7K9RPxBIuqK8Usl6Y3big * &other_user_key=WqSVLkBCS%2Fi5NqRREDACTEDRPxBIuqK8Usl6Y3big
* &other_device_key=WqSVLkBREDACTEDBsfszdvsdBEvefqsdcsfBvsfcsFb
* </pre> * </pre>
*/ */
fun QrCodeData.toUrl(): String { fun QrCodeData.toUrl(): String {
@ -58,8 +60,15 @@ fun QrCodeData.toUrl(): String {
append("&secret=") append("&secret=")
append(URLEncoder.encode(sharedSecret, ENCODING)) append(URLEncoder.encode(sharedSecret, ENCODING))
append("&other_user_key=")
append(URLEncoder.encode(otherUserKey, ENCODING)) if (!otherUserKey.isNullOrBlank()) {
append("&other_user_key=")
append(URLEncoder.encode(otherUserKey, ENCODING))
}
if (!otherDeviceKey.isNullOrBlank()) {
append("&other_device_key=")
append(URLEncoder.encode(otherDeviceKey, ENCODING))
}
} }
} }
@ -100,7 +109,8 @@ fun String.toQrCodeData(): QrCodeData? {
val requestEventId = keyValues["request"]?.takeIf { MatrixPatterns.isEventId(it) } ?: return null val requestEventId = keyValues["request"]?.takeIf { MatrixPatterns.isEventId(it) } ?: return null
val sharedSecret = keyValues["secret"] ?: return null val sharedSecret = keyValues["secret"] ?: return null
val otherUserKey = keyValues["other_user_key"] ?: return null val otherUserKey = keyValues["other_user_key"]
val otherDeviceKey = keyValues["other_device_key"]
val keys = keyValues.keys val keys = keyValues.keys
.filter { it.startsWith("key_") } .filter { it.startsWith("key_") }
@ -115,6 +125,7 @@ fun String.toQrCodeData(): QrCodeData? {
action, action,
keys, keys,
sharedSecret, sharedSecret,
otherUserKey otherUserKey,
otherDeviceKey
) )
} }

View file

@ -26,13 +26,18 @@ data class QrCodeData(
// The action // The action
val action: String, val action: String,
// key_<key_id>: each key that the user wants verified will have an entry of this form, where the value is the key in unpadded base64. // key_<key_id>: each key that the user wants verified will have an entry of this form, where the value is the key in unpadded base64.
// The QR code should contain at least the user's master cross-signing key. // The QR code should contain at least the user's master cross-signing key. In the case where a device does not have a cross-signing key
// (as in the case where a user logs in to a new device, and is verifying against another device), thin the QR code should contain at
// least the device's key.
val keys: Map<String, String>, val keys: Map<String, String>,
// random single-use shared secret in unpadded base64. It must be at least 256-bits long (43 characters when base64-encoded). // random single-use shared secret in unpadded base64. It must be at least 256-bits long (43 characters when base64-encoded).
val sharedSecret: String, val sharedSecret: String,
// the other user's master cross-signing key, in unpadded base64. In other words, if Alice is displaying the QR code, // the other user's master cross-signing key, in unpadded base64. In other words, if Alice is displaying the QR code,
// this would be the copy of Bob's master cross-signing key that Alice has. // this would be the copy of Bob's master cross-signing key that Alice has.
val otherUserKey: String val otherUserKey: String?,
// The other device's key, in unpadded base64
// This is only needed when a user is verifying their own devices, where the other device has not yet been signed with the cross-signing key.
val otherDeviceKey: String?
) { ) {
companion object { companion object {
const val ACTION_VERIFY = "verify" const val ACTION_VERIFY = "verify"

View file

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.verification.qrcode package im.vector.matrix.android.internal.crypto.verification.qrcode
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldBeNull
import org.amshove.kluent.shouldNotBeNull import org.amshove.kluent.shouldNotBeNull
@ -36,10 +37,18 @@ class QrCodeTest {
"2" to "ghijql" "2" to "ghijql"
), ),
sharedSecret = "sharedSecret", sharedSecret = "sharedSecret",
otherUserKey = "otherUserKey" otherUserKey = "otherUserKey",
otherDeviceKey = "otherDeviceKey"
) )
private val basicUrl = "https://matrix.to/#/@benoit:matrix.org?request=%24azertyazerty&action=verify&key_1=abcdef&key_2=ghijql&secret=sharedSecret&other_user_key=otherUserKey" private val basicUrl = "https://matrix.to/#/@benoit:matrix.org" +
"?request=%24azertyazerty" +
"&action=verify" +
"&key_1=abcdef" +
"&key_2=ghijql" +
"&secret=sharedSecret" +
"&other_user_key=otherUserKey" +
"&other_device_key=otherDeviceKey"
@Test @Test
fun testNominalCase() { fun testNominalCase() {
@ -56,7 +65,8 @@ class QrCodeTest {
decodedData.keys["1"]?.shouldBeEqualTo("abcdef") decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
decodedData.keys["2"]?.shouldBeEqualTo("ghijql") decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
decodedData.sharedSecret shouldBeEqualTo "sharedSecret" decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
decodedData.otherUserKey shouldBeEqualTo "otherUserKey" decodedData.otherUserKey?.shouldBeEqualTo("otherUserKey")
decodedData.otherDeviceKey?.shouldBeEqualTo("otherDeviceKey")
} }
@Test @Test
@ -81,7 +91,56 @@ class QrCodeTest {
decodedData.keys["1"]?.shouldBeEqualTo("abcdef") decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
decodedData.keys["2"]?.shouldBeEqualTo("ghijql") decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
decodedData.sharedSecret shouldBeEqualTo "sharedSecret" decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
decodedData.otherUserKey shouldBeEqualTo "otherUserKey" decodedData.otherUserKey!! shouldBeEqualTo "otherUserKey"
decodedData.otherDeviceKey!! shouldBeEqualTo "otherDeviceKey"
}
@Test
fun testNoOtherUserKey() {
val url = basicQrCodeData
.copy(
otherUserKey = null
)
.toUrl()
url shouldBeEqualTo basicUrl
.replace("&other_user_key=otherUserKey", "")
val decodedData = url.toQrCodeData()
decodedData.shouldNotBeNull()
decodedData.userId shouldBeEqualTo "@benoit:matrix.org"
decodedData.requestEventId shouldBeEqualTo "\$azertyazerty"
decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
decodedData.otherUserKey shouldBe null
decodedData.otherDeviceKey?.shouldBeEqualTo("otherDeviceKey")
}
@Test
fun testNoOtherDeviceKey() {
val url = basicQrCodeData
.copy(
otherDeviceKey = null
)
.toUrl()
url shouldBeEqualTo basicUrl
.replace("&other_device_key=otherDeviceKey", "")
val decodedData = url.toQrCodeData()
decodedData.shouldNotBeNull()
decodedData.userId shouldBeEqualTo "@benoit:matrix.org"
decodedData.requestEventId shouldBeEqualTo "\$azertyazerty"
decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
decodedData.otherUserKey?.shouldBeEqualTo("otherUserKey")
decodedData.otherDeviceKey shouldBe null
} }
@Test @Test
@ -149,11 +208,4 @@ class QrCodeTest {
.toQrCodeData() .toQrCodeData()
.shouldBeNull() .shouldBeNull()
} }
@Test
fun testMissingOtherUserKey() {
basicUrl.replace("&other_user_key=otherUserKey", "")
.toQrCodeData()
.shouldBeNull()
}
} }

View file

@ -135,7 +135,16 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
.apply { .apply {
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
it.navigator.openRoom(it, pr.roomId ?: "", pr.transactionId) val roomId = pr.roomId
if (roomId.isNullOrBlank()) {
session?.getVerificationService()
?.readyPendingVerification(supportedVerificationMethods,
pr.otherUserId,
pr.transactionId ?: "")
it.navigator.waitSessionVerification(it)
} else {
it.navigator.openRoom(it, roomId, pr.transactionId)
}
} }
} }
dismissedAction = Runnable { dismissedAction = Runnable {

View file

@ -97,11 +97,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}) })
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) { state ->
it.otherUserMxItem?.let { matrixItem -> state.otherUserMxItem?.let { matrixItem ->
if (state.isMe) {
if (it.waitForOtherUserMode) { if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
if (it.sasTransactionState == VerificationTxState.Verified || it.qrTransactionState == VerificationTxState.Verified) {
otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_trusted)
} else { } else {
otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_warning) otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_warning)
@ -111,7 +110,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
} else { } else {
avatarRenderer.render(matrixItem, otherUserAvatarImageView) avatarRenderer.render(matrixItem, otherUserAvatarImageView)
if (it.sasTransactionState == VerificationTxState.Verified || it.qrTransactionState == VerificationTxState.Verified) { if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName()) otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
otherUserShield.isVisible = true otherUserShield.isVisible = true
} else { } else {
@ -122,8 +121,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
// Did the request result in a SAS transaction? // Did the request result in a SAS transaction?
if (it.sasTransactionState != null) { if (state.sasTransactionState != null) {
when (it.sasTransactionState) { when (state.sasTransactionState) {
is VerificationTxState.None, is VerificationTxState.None,
is VerificationTxState.SendingStart, is VerificationTxState.SendingStart,
is VerificationTxState.Started, is VerificationTxState.Started,
@ -141,20 +140,20 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
is VerificationTxState.Verifying -> { is VerificationTxState.Verifying -> {
showFragment(VerificationEmojiCodeFragment::class, Bundle().apply { showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs( putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "", state.otherUserMxItem?.id ?: "",
// If it was outgoing it.transaction id would be null, but the pending request // If it was outgoing it.transaction id would be null, but the pending request
// would be updated (from localID to txId) // would be updated (from localID to txId)
it.pendingRequest.invoke()?.transactionId ?: it.transactionId)) state.pendingRequest.invoke()?.transactionId ?: state.transactionId))
}) })
} }
is VerificationTxState.Verified -> { is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, it.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))
}) })
} }
is VerificationTxState.Cancelled -> { is VerificationTxState.Cancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, it.sasTransactionState.cancelCode.value, it.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.sasTransactionState.cancelCode.value, state.isMe))
}) })
} }
} }
@ -162,20 +161,20 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
return@withState return@withState
} }
when (it.qrTransactionState) { when (state.qrTransactionState) {
is VerificationTxState.QrScannedByOther -> { is VerificationTxState.QrScannedByOther -> {
showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) showFragment(VerificationQrScannedByOtherFragment::class, Bundle())
return@withState return@withState
} }
is VerificationTxState.Verified -> { is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, it.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))
}) })
return@withState return@withState
} }
is VerificationTxState.Cancelled -> { is VerificationTxState.Cancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, it.qrTransactionState.cancelCode.value, it.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe))
}) })
return@withState return@withState
} }
@ -185,36 +184,36 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// At this point there is no SAS transaction for this request // At this point there is no SAS transaction for this request
// Transaction has not yet started // Transaction has not yet started
if (it.pendingRequest.invoke()?.cancelConclusion != null) { if (state.pendingRequest.invoke()?.cancelConclusion != null) {
// The request has been declined, we should dismiss // The request has been declined, we should dismiss
dismiss() dismiss()
} }
// If it's an outgoing // If it's an outgoing
if (it.pendingRequest.invoke() == null || it.pendingRequest.invoke()?.isIncoming == false || it.waitForOtherUserMode) { if (state.pendingRequest.invoke() == null || state.pendingRequest.invoke()?.isIncoming == false || state.waitForOtherUserMode) {
Timber.v("## SAS show bottom sheet for outgoing request") Timber.v("## SAS show bottom sheet for outgoing request")
if (it.pendingRequest.invoke()?.isReady == true) { if (state.pendingRequest.invoke()?.isReady == true) {
Timber.v("## SAS show bottom sheet for outgoing and ready request") Timber.v("## SAS show bottom sheet for outgoing and ready request")
// Show choose method fragment with waiting // Show choose method fragment with waiting
showFragment(VerificationChooseMethodFragment::class, Bundle().apply { showFragment(VerificationChooseMethodFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id putParcelable(MvRx.KEY_ARG, VerificationArgs(state.otherUserMxItem?.id
?: "", it.pendingRequest.invoke()?.transactionId)) ?: "", state.pendingRequest.invoke()?.transactionId))
}) })
} else { } else {
// Stay on the start fragment // Stay on the start fragment
showFragment(VerificationRequestFragment::class, Bundle().apply { showFragment(VerificationRequestFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs( putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "", state.otherUserMxItem?.id ?: "",
it.pendingRequest.invoke()?.transactionId, state.pendingRequest.invoke()?.transactionId,
it.roomId)) state.roomId))
}) })
} }
} else if (it.pendingRequest.invoke()?.isIncoming == true) { } else if (state.pendingRequest.invoke()?.isIncoming == true) {
Timber.v("## SAS show bottom sheet for Incoming request") Timber.v("## SAS show bottom sheet for Incoming request")
// For incoming we can switch to choose method because ready is being sent or already sent // For incoming we can switch to choose method because ready is being sent or already sent
showFragment(VerificationChooseMethodFragment::class, Bundle().apply { showFragment(VerificationChooseMethodFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id putParcelable(MvRx.KEY_ARG, VerificationArgs(state.otherUserMxItem?.id
?: "", it.pendingRequest.invoke()?.transactionId)) ?: "", state.pendingRequest.invoke()?.transactionId))
}) })
} }
super.invalidate() super.invalidate()

View file

@ -47,7 +47,6 @@ import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import timber.log.Timber
data class VerificationBottomSheetViewState( data class VerificationBottomSheetViewState(
val otherUserMxItem: MatrixItem? = null, val otherUserMxItem: MatrixItem? = null,
@ -95,7 +94,15 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
val userItem = session.getUser(args.otherUserId) val userItem = session.getUser(args.otherUserId)
val pr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) val isWaitingForOtherMode = args.waitForIncomingRequest
val pr = if (isWaitingForOtherMode) {
// See if active tx for this user and take it
session.getVerificationService().getExistingVerificationRequest(args.otherUserId)
?.firstOrNull { !it.isFinished }
} else {
session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
}
val sasTx = (pr?.transactionId ?: args.verificationId)?.let { val sasTx = (pr?.transactionId ?: args.verificationId)?.let {
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? SasVerificationTransaction session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? SasVerificationTransaction
@ -105,9 +112,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction
} }
val isWaitingForOtherMode = args.waitForIncomingRequest
// TODO see if active tx for this user and take it
return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState( return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState(
otherUserMxItem = userItem?.toMatrixItem(), otherUserMxItem = userItem?.toMatrixItem(),
sasTransactionState = sasTx?.state, sasTransactionState = sasTx?.state,
@ -177,16 +181,24 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
is VerificationAction.StartSASVerification -> { is VerificationAction.StartSASVerification -> {
val request = session.getVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId) val request = session.getVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
?: return@withState ?: return@withState
if (roomId == null) return@withState
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
session.getVerificationService().beginKeyVerificationInDMs( if (roomId == null) {
VerificationMethod.SAS, session.getVerificationService().beginKeyVerification(
transactionId = action.pendingRequestTransactionId, VerificationMethod.SAS,
roomId = roomId, otherUserId = request.otherUserId,
otherUserId = request.otherUserId, otherDeviceId = otherDevice ?: "",
otherDeviceId = otherDevice ?: "", transactionId = action.pendingRequestTransactionId
callback = null )
) } else {
session.getVerificationService().beginKeyVerificationInDMs(
VerificationMethod.SAS,
transactionId = action.pendingRequestTransactionId,
roomId = roomId,
otherUserId = request.otherUserId,
otherDeviceId = otherDevice ?: "",
callback = null
)
}
Unit Unit
} }
is VerificationAction.RemoteQrCodeScanned -> { is VerificationAction.RemoteQrCodeScanned -> {
@ -194,11 +206,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction .getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
existingTransaction existingTransaction
?.userHasScannedOtherQrCode(action.scannedData) ?.userHasScannedOtherQrCode(action.scannedData)
?.let { cancelCode ->
// Something went wrong
Timber.w("## Something is not right: $cancelCode")
// TODO
}
} }
is VerificationAction.OtherUserScannedSuccessfully -> { is VerificationAction.OtherUserScannedSuccessfully -> {
val transactionId = state.transactionId ?: return@withState val transactionId = state.transactionId ?: return@withState
@ -241,7 +248,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
if (state.waitForOtherUserMode && state.transactionId == null) { if (state.waitForOtherUserMode && state.transactionId == null) {
// is this an incoming with that user // is this an incoming with that user
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) { if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
// Also auto accept incoming if needed! // Also auto accept incoming if needed!
if (tx is IncomingSasVerificationTransaction) { if (tx is IncomingSasVerificationTransaction) {
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {

View file

@ -52,7 +52,6 @@ class VerificationConclusionController @Inject constructor(
notice(stringProvider.getString( notice(stringProvider.getString(
if (state.isSelfVerification) R.string.verification_conclusion_ok_self_notice if (state.isSelfVerification) R.string.verification_conclusion_ok_self_notice
else R.string.verification_conclusion_ok_notice)) else R.string.verification_conclusion_ok_notice))
} }
bottomSheetVerificationBigImageItem { bottomSheetVerificationBigImageItem {

View file

@ -53,9 +53,9 @@ class VerificationConclusionViewModel(initialState: VerificationConclusionViewSt
} }
else -> { else -> {
VerificationConclusionViewState( VerificationConclusionViewState(
if (args.isSuccessFull) ConclusionState.SUCCESS if (args.isSuccessFull) ConclusionState.SUCCESS else ConclusionState.CANCELLED,
else ConclusionState.CANCELLED args.isMe
, args.isMe) )
} }
} }
} }

View file

@ -50,8 +50,6 @@ class VerificationRequestController @Inject constructor(
val state = viewState ?: return val state = viewState ?: return
val matrixItem = viewState?.otherUserMxItem ?: return val matrixItem = viewState?.otherUserMxItem ?: return
if (state.waitForOtherUserMode) { if (state.waitForOtherUserMode) {
bottomSheetVerificationNoticeItem { bottomSheetVerificationNoticeItem {
id("notice") id("notice")
@ -62,13 +60,11 @@ class VerificationRequestController @Inject constructor(
id("sep") id("sep")
} }
bottomSheetVerificationWaitingItem { bottomSheetVerificationWaitingItem {
id("waiting") id("waiting")
title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName())) title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
} }
} else { } else {
val styledText = matrixItem.let { val styledText = matrixItem.let {
stringProvider.getString(R.string.verification_request_notice, it.id) stringProvider.getString(R.string.verification_request_notice, it.id)
.toSpannable() .toSpannable()

View file

@ -168,7 +168,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
addButton( addButton(
getString(R.string.later), getString(R.string.later),
Runnable { Runnable {
} }
) )
addButton( addButton(

View file

@ -100,7 +100,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
} }
fun manuallyVerify(device: CryptoDeviceInfo) { fun manuallyVerify(device: CryptoDeviceInfo) {
session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId)?.let { txID -> session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId, null)?.let { txID ->
_requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID)))) _requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID))))
} }
} }

View file

@ -33,9 +33,8 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.VerificationService import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -44,6 +43,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods
data class DevicesViewState( data class DevicesViewState(
val myDeviceId: String = "", val myDeviceId: String = "",
@ -96,9 +96,9 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
override fun transactionCreated(tx: VerificationTransaction) {} override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.state == VerificationTxState.Verified) { if (tx.state == VerificationTxState.Verified) {
refreshDevicesList() refreshDevicesList()
} }
} }
/** /**
@ -168,16 +168,13 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
} }
private fun handleVerify(action: DevicesAction.VerifyMyDevice) { private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
// TODO Implement request in to DEVICE!!! val txID = session.getVerificationService().requestKeyVerification(supportedVerificationMethods, session.myUserId, listOf(action.deviceId))
val txID = session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId) _requestLiveData.postValue(LiveEvent(Success(
if (txID != null) { action.copy(
_requestLiveData.postValue(LiveEvent(Success( userId = session.myUserId,
action.copy( transactionId = txID.transactionId
userId = session.myUserId, )
transactionId = txID )))
)
)))
}
} }
private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state -> private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state ->

View file

@ -21,7 +21,6 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@drawable/circle"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"