diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index b7d44aace2..b076ec6b7f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.sas.VerificationService import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction @@ -669,59 +670,14 @@ internal class DefaultVerificationService @Inject constructor( return } - var myGeneratedSharedSecret: String? = null - val qrCodeText = readyReq.methods + val qrCodeData = readyReq.methods // Check if other user is able to scan QR code ?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) } ?.let { - // Build the QR code URL - val requestEventId = existingRequest.transactionId ?: run { - Timber.w("Unknown requestEventId") - return@let null - } - - val myMasterKey = crossSigningService.getMyCrossSigningKeys() - ?.masterKey() - ?.unpaddedBase64PublicKey - ?: run { - Timber.w("Unable to get my master key") - return@let null - } - - // TODO Force download? - val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(existingRequest.otherUserId) - ?.masterKey() - ?.unpaddedBase64PublicKey - ?: run { - Timber.w("Unable to get other user master key") - return@let null - } - - val myDeviceId = deviceId - ?: run { - Timber.w("Unable to get my deviceId") - return@let null - } - - // TODO I'm pretty sure it's the correct key to put here - val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!! - - val generatedSharedSecret = generateSharedSecret() - .also { myGeneratedSharedSecret = it } - QrCodeData( - userId = userId, - requestEventId = requestEventId, - action = QrCodeData.ACTION_VERIFY, - keys = hashMapOf( - myMasterKey to myMasterKey, - myDeviceId to myDeviceKey - ), - sharedSecret = generatedSharedSecret, - otherUserKey = otherUserMasterKey - ).toUrl() + createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId) } - if (qrCodeText != null) { + if (qrCodeData != null) { // Create the pending transaction val tx = DefaultQrCodeVerificationTransaction( readyReq.transactionID!!, @@ -729,8 +685,8 @@ internal class DefaultVerificationService @Inject constructor( readyReq.fromDevice, crossSigningService, cryptoStore, - myGeneratedSharedSecret!!, - qrCodeText, + qrCodeData.sharedSecret, + qrCodeData.toUrl(), deviceId ?: "", false) @@ -744,6 +700,51 @@ internal class DefaultVerificationService @Inject constructor( )) } + private fun createQrCodeData(transactionId: String?, otherUserId: String): QrCodeData? { + // Build the QR code URL + val requestEventId = transactionId ?: run { + Timber.w("## Unknown requestEventId") + return null + } + + val myMasterKey = crossSigningService.getMyCrossSigningKeys() + ?.masterKey() + ?.unpaddedBase64PublicKey + ?: run { + Timber.w("## Unable to get my master key") + return null + } + + val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(otherUserId) + ?.masterKey() + ?.unpaddedBase64PublicKey + ?: run { + Timber.w("## Unable to get other user master key") + return null + } + + val myDeviceId = deviceId + ?: run { + Timber.w("## Unable to get my deviceId") + return null + } + + val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!! + + val generatedSharedSecret = generateSharedSecret() + return QrCodeData( + userId = userId, + requestEventId = requestEventId, + action = QrCodeData.ACTION_VERIFY, + keys = hashMapOf( + myMasterKey to myMasterKey, + myDeviceId to myDeviceKey + ), + sharedSecret = generatedSharedSecret, + otherUserKey = otherUserMasterKey + ) + } + private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) { val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID } if (existingRequest == null) { @@ -953,7 +954,13 @@ internal class DefaultVerificationService @Inject constructor( if (existingRequest != null) { // we need to send a ready event, with matching methods val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null) - val computedMethods = computeReadyMethods(existingRequest.requestInfo?.methods, methods) + val computedMethods = computeReadyMethods( + transactionId, + otherUserId, + existingRequest.requestInfo?.fromDevice ?: "", + roomId, + existingRequest.requestInfo?.methods, + methods) if (methods.isNullOrEmpty()) { Timber.i("Cannot ready this request, no common methods found txId:$transactionId") // TODO buttons should not be shown in this case? @@ -976,7 +983,13 @@ internal class DefaultVerificationService @Inject constructor( } } - private fun computeReadyMethods(otherUserMethods: List?, methods: List): List { + private fun computeReadyMethods( + transactionId: String, + otherUserId: String, + otherDeviceId: String, + roomId: String, + otherUserMethods: List?, + methods: List): List { if (otherUserMethods.isNullOrEmpty()) { return emptyList() } @@ -985,17 +998,42 @@ internal class DefaultVerificationService @Inject constructor( if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in methods) { // Other can do SAS and so do I - result + VERIFICATION_METHOD_SAS + result.add(VERIFICATION_METHOD_SAS) } - if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) { - // Other can Scan and I can show QR code - result + VERIFICATION_METHOD_QR_CODE_SHOW - result + VERIFICATION_METHOD_RECIPROCATE - } - if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) { - // Other can show and I can scan QR code - result + VERIFICATION_METHOD_QR_CODE_SCAN - result + VERIFICATION_METHOD_RECIPROCATE + + if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) { + // Other user want to verify using QR code. Cross-signing has to be setup + val qrCodeData = createQrCodeData(transactionId, otherUserId) + + if (qrCodeData != null) { + if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) { + // Other can Scan and I can show QR code + result.add(VERIFICATION_METHOD_QR_CODE_SHOW) + result.add(VERIFICATION_METHOD_RECIPROCATE) + + // Create the pending request, to display the QR code + // Create the pending transaction + val tx = DefaultQrCodeVerificationTransaction( + transactionId, + otherUserId, + otherDeviceId, + crossSigningService, + cryptoStore, + qrCodeData.sharedSecret, + qrCodeData.toUrl(), + deviceId ?: "", + false) + + tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) + + addTransaction(tx) + } + if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) { + // Other can show and I can scan QR code + result.add(VERIFICATION_METHOD_QR_CODE_SCAN) + result.add(VERIFICATION_METHOD_RECIPROCATE) + } + } } return result.toList() @@ -1024,5 +1062,13 @@ internal class DefaultVerificationService @Inject constructor( // remove this.removeTransaction(tx.otherUserId, tx.transactionId) } + if (tx is QrCodeVerificationTransaction + && (tx.state == VerificationTxState.Cancelled + || tx.state == VerificationTxState.OnCancelled + || tx.state == VerificationTxState.Verified) + ) { + // remove + this.removeTransaction(tx.otherUserId, tx.transactionId) + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index a85c68ca83..a38ec10afb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -152,5 +152,10 @@ internal class DefaultQrCodeVerificationTransaction( Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") } }) + + // TODO Sign devices + } + + // TODO Send the done event } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 4e91a93da3..b83e9b696d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -98,7 +98,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { it.otherUserMxItem?.let { matrixItem -> avatarRenderer.render(matrixItem, otherUserAvatarImageView) - if (it.transactionState == VerificationTxState.Verified) { + if (it.sasTransactionState == VerificationTxState.Verified || it.qrTransactionState == VerificationTxState.Verified) { otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName()) otherUserShield.isVisible = true } else { @@ -108,8 +108,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } // Did the request result in a SAS transaction? - if (it.transactionState != null) { - when (it.transactionState) { + if (it.sasTransactionState != null) { + when (it.sasTransactionState) { VerificationTxState.None, VerificationTxState.SendingStart, VerificationTxState.Started, @@ -138,7 +138,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { VerificationTxState.OnCancelled -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args( - it.transactionState == VerificationTxState.Verified, + it.sasTransactionState == VerificationTxState.Verified, it.cancelCode?.value)) }) } @@ -147,6 +147,19 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { return@withState } + when (it.qrTransactionState) { + VerificationTxState.Verified, + VerificationTxState.Cancelled, + VerificationTxState.OnCancelled -> { + showFragment(VerificationConclusionFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args( + it.qrTransactionState == VerificationTxState.Verified, + it.cancelCode?.value)) + }) + } + else -> Unit + } + // At this point there is no transaction for this request // Transaction has not yet started diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 6b67e8f477..d29b7b3774 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -52,7 +52,8 @@ data class VerificationBottomSheetViewState( val roomId: String? = null, val pendingRequest: Async = Uninitialized, val pendingLocalId: String? = null, - val transactionState: VerificationTxState? = null, + val sasTransactionState: VerificationTxState? = null, + val qrTransactionState: VerificationTxState? = null, val transactionId: String? = null, val cancelCode: CancelCode? = null ) : MvRxState @@ -94,12 +95,17 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val pr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) val sasTx = (pr?.transactionId ?: args.verificationId)?.let { - session.getVerificationService().getExistingTransaction(args.otherUserId, it) + session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? SasVerificationTransaction + } + + val qrTx = (pr?.transactionId ?: args.verificationId)?.let { + session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction } return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState( otherUserMxItem = userItem?.toMatrixItem(), - transactionState = sasTx?.state, + sasTransactionState = sasTx?.state, + qrTransactionState = qrTx?.state, transactionId = args.verificationId, pendingRequest = if (pr != null) Success(pr) else Uninitialized, roomId = args.roomId) @@ -192,13 +198,28 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> - if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) { - // A SAS tx has been started following this request - setState { - copy( - transactionState = tx.state, - cancelCode = tx.cancelledReason - ) + when (tx) { + is SasVerificationTransaction -> { + if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) { + // A SAS tx has been started following this request + setState { + copy( + sasTransactionState = tx.state, + cancelCode = tx.cancelledReason + ) + } + } + } + is QrCodeVerificationTransaction -> { + if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) { + // A SAS tx has been started following this request + setState { + copy( + qrTransactionState = tx.state, + cancelCode = tx.cancelledReason + ) + } + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index 852488a3ed..75c1b69058 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -98,10 +98,14 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() val pvr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) + // Get the QR code now, because transaction is already created, so transactionCreated() will not be called + val qrCodeVerificationTransaction = session.getVerificationService().getExistingTransaction(args.otherUserId, args.verificationId ?: "") + return VerificationChooseMethodViewState(otherUserId = args.otherUserId, transactionId = args.verificationId ?: "", otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false, otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false, + qrCodeText = (qrCodeVerificationTransaction as? QrCodeVerificationTransaction)?.qrCodeText, SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false ) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index b3b31aa00e..f6a4717c78 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -815,6 +815,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro room.roomId, action.transactionId)) { _requestLiveData.postValue(LiveEvent(Success(action))) + } else { + // TODO } }