From 050eb0af9df02902250a705deb41cf4ca2b62aeb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Jan 2020 14:29:03 +0100 Subject: [PATCH 01/22] Create dedicated View and Epoxy item for QrCode --- .../riotx/features/debug/DebugMenuActivity.kt | 4 +- .../debug/res/layout/activity_debug_menu.xml | 2 +- .../im/vector/riotx/core/qrcode/QrCode.kt | 11 +-- .../riotx/core/ui/views/QrCodeImageView.kt | 94 +++++++++++++++++++ .../VerificationChooseMethodController.kt | 16 +--- .../BottomSheetVerificationBigImageItem.kt | 10 +- .../BottomSheetVerificationQrCodeItem.kt | 56 +++++++++++ .../res/layout/item_verification_qr_code.xml | 8 ++ 8 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/ui/views/QrCodeImageView.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt create mode 100644 vector/src/main/res/layout/item_verification_qr_code.xml diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt index 31998e9b99..81708182b7 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt @@ -29,7 +29,6 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseActivity -import im.vector.riotx.core.qrcode.toQrCode import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA import im.vector.riotx.core.utils.allGranted @@ -56,8 +55,7 @@ class DebugMenuActivity : VectorBaseActivity() { } private fun renderQrCode(text: String) { - val qrBitmap = text.toQrCode(200, 200) - debug_qr_code.setImageBitmap(qrBitmap) + debug_qr_code.setData(text, true) } @OnClick(R.id.debug_test_text_view_link) diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 272432526a..6578258e70 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -68,7 +68,7 @@ android:layout_height="wrap_content" android:text="Scan QR-code" /> - 0 } + ?.let { + if (animate) { + // NOT SUPPORTED YET val anim = createAnimation(it) + // NOT SUPPORTED YET setImageDrawable(anim) + // NOT SUPPORTED YET anim.start() + // NOT SUPPORTED YET setImageDrawable(BitmapDrawable(resources, it.toBitMatrix(width).toBitmap())) + val bitmap = it.toBitMatrix(width).toBitmap() + post { setImageBitmap(bitmap) } + } else { + val bitmap = it.toBitMatrix(width).toBitmap() + post { setImageBitmap(bitmap) } + } + } + } + + private fun createAnimation(data: String): AnimationDrawable { + val finalQr = data.toBitMatrix(width) + + val list = mutableListOf(finalQr) + + val random = Random(System.currentTimeMillis()) + val repeatTime = 8 + repeat(repeatTime) { index -> + val alteredQr = finalQr.clone() + for (x in 0 until alteredQr.width) { + for (y in 0 until alteredQr.height) { + if (random.nextInt(repeatTime - index) == 0) { + // Pb is that it does not toggle a whole black square, but only a pixel + alteredQr.unset(x, y) + } + } + } + list.add(alteredQr) + } + + val animDrawable = AnimationDrawable() + + list.asReversed() + .forEach { + animDrawable.addFrame(BitmapDrawable(resources, it.toBitmap()), 150) + } + + return animDrawable + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt index 0bf38979da..f5568331a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -19,19 +19,16 @@ package im.vector.riotx.features.crypto.verification.choose import com.airbnb.epoxy.EpoxyController import im.vector.riotx.R import im.vector.riotx.core.epoxy.dividerItem -import im.vector.riotx.core.qrcode.toQrCode import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem -import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem import javax.inject.Inject class VerificationChooseMethodController @Inject constructor( private val stringProvider: StringProvider, - private val colorProvider: ColorProvider, - private val dimensionConverter: DimensionConverter + private val colorProvider: ColorProvider ) : EpoxyController() { var listener: Listener? = null @@ -53,13 +50,10 @@ class VerificationChooseMethodController @Inject constructor( } if (state.otherCanScanQrCode && !state.QRtext.isNullOrBlank()) { - // Generate the QR code - val size = dimensionConverter.dpToPx(180) - val qrCodeBitmap = state.QRtext.toQrCode(size, size) - - bottomSheetVerificationBigImageItem { + bottomSheetVerificationQrCodeItem { id("qr") - imageBitmap(qrCodeBitmap) + data(state.QRtext) + animate(false) } dividerItem { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt index 4973f425cd..5163f5e8a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt @@ -16,7 +16,6 @@ */ package im.vector.riotx.features.crypto.verification.epoxy -import android.graphics.Bitmap import android.widget.ImageView import androidx.core.view.ViewCompat import com.airbnb.epoxy.EpoxyAttribute @@ -34,18 +33,11 @@ abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var data: String + + @EpoxyAttribute + var animate = false + + @EpoxyAttribute + var contentDescription: String? = null + + override fun bind(holder: Holder) { + holder.qsrCodeImage.setData(data, animate) + + if (contentDescription == null) { + ViewCompat.setImportantForAccessibility(holder.qsrCodeImage, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) + } else { + ViewCompat.setImportantForAccessibility(holder.qsrCodeImage, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + holder.qsrCodeImage.contentDescription = contentDescription + } + } + + class Holder : VectorEpoxyHolder() { + val qsrCodeImage by bind(R.id.itemVerificationBigImage) + } +} diff --git a/vector/src/main/res/layout/item_verification_qr_code.xml b/vector/src/main/res/layout/item_verification_qr_code.xml new file mode 100644 index 0000000000..2407d2754d --- /dev/null +++ b/vector/src/main/res/layout/item_verification_qr_code.xml @@ -0,0 +1,8 @@ + + From 345824daa29a4610fb65b2d735b1056dead17c1f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Jan 2020 15:09:33 +0100 Subject: [PATCH 02/22] Keep on renaming --- .../internal/crypto/verification/SASTest.kt | 66 +++++++++---------- .../api/session/crypto/CryptoService.kt | 7 +- .../session/crypto/sas/VerificationService.kt | 4 +- .../crypto/sas/VerificationTransaction.kt | 18 ++++- .../internal/crypto/DefaultCryptoService.kt | 8 +-- .../DefaultVerificationService.kt | 26 ++++---- .../SASDefaultVerificationTransaction.kt | 4 +- .../verification/VerificationTransport.kt | 5 +- .../VerificationTransportRoomMessage.kt | 2 +- .../VerificationTransportToDevice.kt | 2 +- .../crypto/keysrequest/KeyRequestHandler.kt | 4 +- .../IncomingVerificationRequestHandler.kt | 6 +- .../VerificationBottomSheetViewModel.kt | 23 ++++--- .../VerificationChooseMethodViewModel.kt | 8 +-- .../emoji/VerificationEmojiCodeViewModel.kt | 6 +- .../home/room/detail/RoomDetailViewModel.kt | 8 +-- .../features/navigation/DefaultNavigator.kt | 2 +- .../settings/devices/DevicesViewModel.kt | 6 +- 18 files changed, 111 insertions(+), 94 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index 7b10f47806..178eca09b2 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -51,8 +51,8 @@ class SASTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession - val aliceSasMgr = aliceSession.getSasVerificationService() - val bobSasMgr = bobSession!!.getSasVerificationService() + val aliceVerificationService = aliceSession.getVerificationService() + val bobVerificationService = bobSession!!.getVerificationService() val bobTxCreatedLatch = CountDownLatch(1) val bobListener = object : VerificationService.VerificationListener { @@ -64,18 +64,18 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSasMgr.addListener(bobListener) + bobVerificationService.addListener(bobListener) - val txID = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, bobSession.getMyDevice().deviceId) + val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, bobSession.getMyDevice().deviceId) assertNotNull("Alice should have a started transaction", txID) - val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!) + val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!) assertNotNull("Alice should have a started transaction", aliceKeyTx) mTestHelper.await(bobTxCreatedLatch) - bobSasMgr.removeListener(bobListener) + bobVerificationService.removeListener(bobListener) - val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID) + val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID) assertNotNull("Bob should have started verif transaction", bobKeyTx) assertTrue(bobKeyTx is SASDefaultVerificationTransaction) @@ -105,7 +105,7 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSasMgr.addListener(bobListener2) + bobVerificationService.addListener(bobListener2) aliceSasTx.cancel(CancelCode.User) mTestHelper.await(cancelLatch) @@ -120,8 +120,8 @@ class SASTest : InstrumentedTest { assertEquals("Should be User cancelled on bob side", CancelCode.User, aliceSasTx.cancelledReason) - assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)) - assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID)) + assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) + assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) cryptoTestData.close() } @@ -151,7 +151,7 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSession.getSasVerificationService().addListener(bobListener) + bobSession.getVerificationService().addListener(bobListener) // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { // TODO override fun onToDeviceEvent(event: Event?) { @@ -179,7 +179,7 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - aliceSession.getSasVerificationService().addListener(aliceListener) + aliceSession.getVerificationService().addListener(aliceListener) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) @@ -303,7 +303,7 @@ class SASTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession - val aliceSasMgr = aliceSession.getSasVerificationService() + val aliceVerificationService = aliceSession.getVerificationService() val aliceCreatedLatch = CountDownLatch(2) val aliceCancelledLatch = CountDownLatch(2) @@ -322,12 +322,12 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - aliceSasMgr.addListener(aliceListener) + aliceVerificationService.addListener(aliceListener) val bobUserId = bobSession!!.myUserId val bobDeviceId = bobSession.getMyDevice().deviceId - aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) - aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) + aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) + aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) mTestHelper.await(aliceCreatedLatch) mTestHelper.await(aliceCancelledLatch) @@ -345,8 +345,8 @@ class SASTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession - val aliceSasMgr = aliceSession.getSasVerificationService() - val bobSasMgr = bobSession!!.getSasVerificationService() + val aliceVerificationService = aliceSession.getVerificationService() + val bobVerificationService = bobSession!!.getVerificationService() var accepted: KeyVerificationAccept? = null var startReq: KeyVerificationStart? = null @@ -366,7 +366,7 @@ class SASTest : InstrumentedTest { } } } - aliceSasMgr.addListener(aliceListener) + aliceVerificationService.addListener(aliceListener) val bobListener = object : VerificationService.VerificationListener { override fun transactionCreated(tx: VerificationTransaction) {} @@ -380,11 +380,11 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSasMgr.addListener(bobListener) + bobVerificationService.addListener(bobListener) val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.getMyDevice().deviceId - aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) + aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) mTestHelper.await(aliceAcceptedLatch) assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false) @@ -409,8 +409,8 @@ class SASTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession - val aliceSasMgr = aliceSession.getSasVerificationService() - val bobSasMgr = bobSession!!.getSasVerificationService() + val aliceVerificationService = aliceSession.getVerificationService() + val bobVerificationService = bobSession!!.getVerificationService() val aliceSASLatch = CountDownLatch(1) val aliceListener = object : VerificationService.VerificationListener { @@ -428,7 +428,7 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - aliceSasMgr.addListener(aliceListener) + aliceVerificationService.addListener(aliceListener) val bobSASLatch = CountDownLatch(1) val bobListener = object : VerificationService.VerificationListener { @@ -449,16 +449,16 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSasMgr.addListener(bobListener) + bobVerificationService.addListener(bobListener) val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.getMyDevice().deviceId - val verificationSAS = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) + val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) mTestHelper.await(aliceSASLatch) mTestHelper.await(bobSASLatch) - val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction - val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction + val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction + val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) @@ -473,8 +473,8 @@ class SASTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession - val aliceSasMgr = aliceSession.getSasVerificationService() - val bobSasMgr = bobSession!!.getSasVerificationService() + val aliceVerificationService = aliceSession.getVerificationService() + val bobVerificationService = bobSession!!.getVerificationService() val aliceSASLatch = CountDownLatch(1) val aliceListener = object : VerificationService.VerificationListener { @@ -495,7 +495,7 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - aliceSasMgr.addListener(aliceListener) + aliceVerificationService.addListener(aliceListener) val bobSASLatch = CountDownLatch(1) val bobListener = object : VerificationService.VerificationListener { @@ -519,11 +519,11 @@ class SASTest : InstrumentedTest { override fun markedAsManuallyVerified(userId: String, deviceId: String) {} } - bobSasMgr.addListener(bobListener) + bobVerificationService.addListener(bobListener) val bobUserId = bobSession.myUserId val bobDeviceId = bobSession.getMyDevice().deviceId - aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) + aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId) mTestHelper.await(aliceSASLatch) mTestHelper.await(bobSASLatch) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index af2285db08..594ad39063 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -50,7 +50,7 @@ interface CryptoService { fun isCryptoEnabled(): Boolean - fun getSasVerificationService(): VerificationService + fun getVerificationService(): VerificationService fun getCrossSigningService(): CrossSigningService @@ -118,8 +118,9 @@ interface CryptoService { fun downloadKeys(userIds: List, forceDownload: Boolean, callback: MatrixCallback>) - fun getCryptoDeviceInfo(userId: String) : List - fun getLiveCryptoDeviceInfo(userId: String) : LiveData> + fun getCryptoDeviceInfo(userId: String): List + + fun getLiveCryptoDeviceInfo(userId: String): LiveData> fun addNewSessionListener(newSessionListener: NewSessionListener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt index a65eba54f7..153a5cc273 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt @@ -24,8 +24,8 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification * https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework * * Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors. - * SAS verification is a user-friendly key verification process. - * SAS verification is intended to be a highly interactive process for users, + * Verification is a user-friendly key verification process. + * Verification is intended to be a highly interactive process for users, * and as such exposes verification methods which are easier for users to use. */ interface VerificationService { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt index 2415a00759..793c67ae33 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt @@ -1,8 +1,23 @@ +/* + * Copyright 2020 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.api.session.crypto.sas interface VerificationTransaction { - var state: VerificationTxState val cancelledReason: CancelCode? @@ -14,6 +29,7 @@ interface VerificationTransaction { * User wants to cancel the transaction */ fun cancel() + fun cancel(code: CancelCode) fun isToDeviceTransport(): Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 0e9f47df61..0f4358e725 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -126,8 +126,8 @@ internal class DefaultCryptoService @Inject constructor( private val oneTimeKeysUploader: OneTimeKeysUploader, // private val roomDecryptorProvider: RoomDecryptorProvider, - // The SAS verification service. - private val sasVerificationService: DefaultVerificationService, + // The verification service. + private val verificationService: DefaultVerificationService, private val crossSigningService: DefaultCrossSigningService, // @@ -157,7 +157,7 @@ internal class DefaultCryptoService @Inject constructor( init { - sasVerificationService.cryptoService = this + verificationService.cryptoService = this } private val uiHandler = Handler(Looper.getMainLooper()) @@ -343,7 +343,7 @@ internal class DefaultCryptoService @Inject constructor( /** * @return the VerificationService */ - override fun getSasVerificationService() = sasVerificationService + override fun getVerificationService() = verificationService override fun getCrossSigningService() = crossSigningService 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 4c487b5ed2..9a7cc80c06 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 @@ -57,8 +57,8 @@ internal class DefaultVerificationService @Inject constructor( private val deviceListManager: DeviceListManager, private val setDeviceVerificationAction: SetDeviceVerificationAction, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, - private val sasTransportToDeviceFactory: SasTransportToDeviceFactory, + private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory, + private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory, private val crossSigningService: CrossSigningService ) : DefaultVerificationTransaction.Listener, VerificationService { @@ -289,7 +289,7 @@ internal class DefaultVerificationService @Inject constructor( if (startReq?.isValid()?.not() == true) { Timber.e("## received invalid verification request") if (startReq.transactionID != null) { - sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) + verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) .cancelTransaction( startReq.transactionID ?: "", otherUserId!!, @@ -301,9 +301,9 @@ internal class DefaultVerificationService @Inject constructor( } handleStart(otherUserId, startReq as VerificationInfoStart) { - it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", it) + it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it) }?.let { - sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) + verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) .cancelTransaction( startReq.transactionID ?: "", otherUserId!!, @@ -322,7 +322,7 @@ internal class DefaultVerificationService @Inject constructor( if (!startReq.isValid()) { Timber.e("## SAS received invalid verification request") if (startReq.transactionID != null) { - sasTransportToDeviceFactory.createTransport(null).cancelTransaction( + verificationTransportToDeviceFactory.createTransport(null).cancelTransaction( startReq.transactionID, otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -333,9 +333,9 @@ internal class DefaultVerificationService @Inject constructor( } // Download device keys prior to everything handleStart(otherUserId, startReq) { - it.transport = sasTransportToDeviceFactory.createTransport(it) + it.transport = verificationTransportToDeviceFactory.createTransport(it) }?.let { - sasTransportToDeviceFactory.createTransport(null).cancelTransaction( + verificationTransportToDeviceFactory.createTransport(null).cancelTransaction( startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -705,7 +705,7 @@ internal class DefaultVerificationService @Inject constructor( txID, userId, deviceID) - tx.transport = sasTransportToDeviceFactory.createTransport(tx) + tx.transport = verificationTransportToDeviceFactory.createTransport(tx) addTransaction(tx) tx.start(method) @@ -724,7 +724,7 @@ internal class DefaultVerificationService @Inject constructor( pendingRequests[userId] = it } - val transport = sasTransportRoomMessageFactory.createTransport(roomId, null) + val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null) // Cancel existing pending requests? requestsForUser.toImmutableList().forEach { existingRequest -> @@ -762,7 +762,7 @@ internal class DefaultVerificationService @Inject constructor( } override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) { - sasTransportRoomMessageFactory.createTransport(roomId, null) + verificationTransportRoomMessageFactory.createTransport(roomId, null) .cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) getExistingVerificationRequest(otherUserId, transactionId)?.let { @@ -804,7 +804,7 @@ internal class DefaultVerificationService @Inject constructor( transactionId, otherUserId, otherDeviceId) - tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, tx) + tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) addTransaction(tx) tx.start(method) @@ -820,7 +820,7 @@ internal class DefaultVerificationService @Inject constructor( val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) if (existingRequest != null) { // we need to send a ready event, with matching methods - val transport = sasTransportRoomMessageFactory.createTransport(roomId, null) + val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null) // TODO We should not use supportedVerificationMethods here, because it depends on the client implementation val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList() if (methods.isNullOrEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 806bbc2703..e8d01a0b25 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -48,8 +48,8 @@ internal abstract class SASDefaultVerificationTransaction( transactionId: String, otherUserId: String, otherDevice: String?, - isIncoming: Boolean) : - DefaultVerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming), SasVerificationTransaction { + isIncoming: Boolean +) : DefaultVerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming), SasVerificationTransaction { companion object { const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt index b2740ff96b..69752f83be 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt @@ -20,8 +20,8 @@ import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent /** - * SAS verification can be performed using toDevice events or via DM. - * This class abstracts the concept of transport for SAS + * Verification can be performed using toDevice events or via DM. + * This class abstracts the concept of transport for verification */ internal interface VerificationTransport { @@ -46,6 +46,7 @@ internal interface VerificationTransport { code: CancelCode) fun done(transactionId: String) + /** * Creates an accept message suitable for this transport */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt index f36d8a2a8d..7ed86fc028 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt @@ -292,7 +292,7 @@ internal class VerificationTransportRoomMessage( } } -internal class SasTransportRoomMessageFactory @Inject constructor( +internal class VerificationTransportRoomMessageFactory @Inject constructor( private val workManagerProvider: WorkManagerProvider, private val stringProvider: StringProvider, private val monarchy: Monarchy, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt index 04c78d0154..3a132e8608 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt @@ -145,7 +145,7 @@ internal class VerificationTransportToDevice( } } -internal class SasTransportToDeviceFactory @Inject constructor( +internal class VerificationTransportToDeviceFactory @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, private val taskExecutor: TaskExecutor) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt index 24b656c810..856c71f888 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt @@ -64,12 +64,12 @@ class KeyRequestHandler @Inject constructor(private val context: Context) fun start(session: Session) { this.session = session - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) session.addRoomKeysRequestListener(this) } fun stop() { - session?.getSasVerificationService()?.removeListener(this) + session?.getVerificationService()?.removeListener(this) session?.removeRoomKeysRequestListener(this) session = null } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt index 3efac92800..234b60cba3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -40,11 +40,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context fun start(session: Session) { this.session = session - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) } fun stop() { - session?.getSasVerificationService()?.removeListener(this) + session?.getVerificationService()?.removeListener(this) this.session = null } @@ -130,7 +130,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context } } dismissedAction = Runnable { - session?.getSasVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId, + session?.getVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId, pr.requestInfo?.fromDevice ?: "", pr.transactionId ?: "", pr.roomId ?: "" 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 5b7be367e5..6b9b658b82 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 @@ -68,11 +68,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini get() = _requestLiveData init { - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) } override fun onCleared() { - session.getSasVerificationService().removeListener(this) + session.getVerificationService().removeListener(this) super.onCleared() } @@ -91,10 +91,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val userItem = session.getUser(args.otherUserId) - val pr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) + val pr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) val sasTx = (pr?.transactionId ?: args.verificationId)?.let { - session.getSasVerificationService().getExistingTransaction(args.otherUserId, it) + session.getVerificationService().getExistingTransaction(args.otherUserId, it) } return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState( @@ -132,7 +132,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini copy( roomId = data, pendingRequest = Success( - session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId) + session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId) ) ) } @@ -146,16 +146,16 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini }) } else { setState { - copy(pendingRequest = Success(session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId))) + copy(pendingRequest = Success(session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId))) } } } is VerificationAction.StartSASVerification -> { - val request = session.getSasVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId) + val request = session.getVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId) ?: return@withState if (roomId == null) return@withState val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice - session.getSasVerificationService().beginKeyVerificationInDMs( + session.getVerificationService().beginKeyVerificationInDMs( VerificationMethod.SAS, transactionId = action.pendingRequestTransactionId, roomId = roomId, @@ -165,19 +165,18 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini ) } is VerificationAction.RemoteQrCodeScanned -> { - // TODO Use session.getCrossSigningService()? - val existingTransaction = session.getSasVerificationService() + val existingTransaction = session.getVerificationService() .getExistingTransaction(action.userID, action.transactionId) as? QRVerificationTransaction existingTransaction ?.userHasScannedRemoteQrCode(action.scannedData) } is VerificationAction.SASMatchAction -> { - (session.getSasVerificationService() + (session.getVerificationService() .getExistingTransaction(action.userID, action.sasTransactionId) as? SasVerificationTransaction)?.userHasVerifiedShortCode() } is VerificationAction.SASDoNotMatchAction -> { - (session.getSasVerificationService() + (session.getVerificationService() .getExistingTransaction(action.userID, action.sasTransactionId) as? SasVerificationTransaction) ?.shortCodeDoesNotMatch() 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 cee1cba7a5..a9b24b5710 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 @@ -51,7 +51,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( override fun transactionUpdated(tx: VerificationTransaction) {} override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> - val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) + val pvr = session.getVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) setState { copy( @@ -70,12 +70,12 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( } init { - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) } override fun onCleared() { super.onCleared() - session.getSasVerificationService().removeListener(this) + session.getVerificationService().removeListener(this) } companion object : MvRxViewModelFactory { @@ -87,7 +87,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): VerificationChooseMethodViewState? { val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() - val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) + val pvr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) return VerificationChooseMethodViewState(otherUserId = args.otherUserId, transactionId = args.verificationId ?: "", diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt index 5c2caa7ef4..8824cd88e2 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt @@ -48,16 +48,16 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor( init { withState { state -> - refreshStateFromTx(session.getSasVerificationService() + refreshStateFromTx(session.getVerificationService() .getExistingTransaction(state.otherUser?.id ?: "", state.transactionId ?: "") as? SasVerificationTransaction) } - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) } override fun onCleared() { - session.getSasVerificationService().removeListener(this) + session.getVerificationService().removeListener(this) super.onCleared() } 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 055dcfa9fb..695dfa8d33 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 @@ -411,7 +411,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro popDraft() } is ParsedCommand.VerifyUser -> { - session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId) + session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } @@ -809,14 +809,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") - if (session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, + if (session.getVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, action.transactionId)) { _requestLiveData.postValue(LiveEvent(Success(action))) } } private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) { - session.getSasVerificationService().declineVerificationRequestInDMs( + session.getVerificationService().declineVerificationRequestInDMs( action.otherUserId, action.otherdDeviceId, action.transactionId, @@ -830,7 +830,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) { // Check if this request is still active and handled by me - session.getSasVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let { + session.getVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let { if (it.handledByOtherSession) return if (!it.isFinished) { _requestLiveData.postValue(LiveEvent(Success(action.copy( diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index f28bcebd0e..1a90151693 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -65,7 +65,7 @@ class DefaultNavigator @Inject constructor( override fun performDeviceVerification(context: Context, otherUserId: String, sasTransationId: String) { val session = sessionHolder.getSafeActiveSession() ?: return - val tx = session.getSasVerificationService().getExistingTransaction(otherUserId, sasTransationId) ?: return + val tx = session.getVerificationService().getExistingTransaction(otherUserId, sasTransationId) ?: return (tx as? IncomingSasVerificationTransaction)?.performAccept() if (context is VectorBaseActivity) { VerificationBottomSheet.withArgs( diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index 71bfafbc28..f9e28e35e0 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -86,11 +86,11 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic init { refreshDevicesList() - session.getSasVerificationService().addListener(this) + session.getVerificationService().addListener(this) } override fun onCleared() { - session.getSasVerificationService().removeListener(this) + session.getVerificationService().removeListener(this) super.onCleared() } @@ -169,7 +169,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic private fun handleVerify(action: DevicesAction.VerifyMyDevice) { // TODO Implement request in to DEVICE!!! - val txID = session.getSasVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId) + val txID = session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId) if (txID != null) { _requestLiveData.postValue(LiveEvent(Success( action.copy( From efc8cfb9a103f2c4778c54b58a5c75dfff4105da Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Jan 2020 17:30:10 +0100 Subject: [PATCH 03/22] QR code: modify APIs --- .../session/crypto/sas/VerificationService.kt | 1 + .../MessageVerificationStartContent.kt | 39 +++++++++--- .../crypto/model/rest/KeyVerificationStart.kt | 38 ++++++++--- ...ltOutgoingSASDefaultVerificationRequest.kt | 7 +-- .../DefaultVerificationService.kt | 44 ++++++++++--- .../verification/VerificationInfoStart.kt | 6 ++ .../verification/VerificationTransport.kt | 23 ++++--- .../VerificationTransportRoomMessage.kt | 63 +++++++++++++++---- .../VerificationTransportToDevice.kt | 41 +++++++++--- 9 files changed, 198 insertions(+), 64 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt index 153a5cc273..c3ec713344 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt @@ -56,6 +56,7 @@ interface VerificationService { fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) + // Only SAS method is supported for the moment fun beginKeyVerificationInDMs(method: VerificationMethod, transactionId: String, roomId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt index 10b3603ae4..3031b213d9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -20,7 +20,8 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.SasMode 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.model.rest.supportedVerificationMethods +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.verification.SASDefaultVerificationTransaction import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart import im.vector.matrix.android.internal.util.JsonCanonicalizer @@ -34,7 +35,8 @@ internal data class MessageVerificationStartContent( @Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List?, @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, @Json(name = "method") override val method: String?, - @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?, + @Json(name = "secret") override val sharedSecret: String? ) : VerificationInfoStart { override fun toCanonicalJson(): String? { @@ -44,22 +46,39 @@ internal data class MessageVerificationStartContent( override val transactionID: String? get() = relatesTo?.eventId + // TODO Move those method to the interface? override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() - || method !in supportedVerificationMethods - || keyAgreementProtocols.isNullOrEmpty() - || hashes.isNullOrEmpty() - || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() - || (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) - && !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) - || shortAuthenticationStrings.isNullOrEmpty() - || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + || (method == VERIFICATION_METHOD_SAS && !isValidSas()) + || (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) { Timber.e("## received invalid verification request") return false } return true } + private fun isValidSas(): Boolean { + if (keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty() + || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() + || (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) + && !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || shortAuthenticationStrings.isNullOrEmpty() + || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + return false + } + + return true + } + + private fun isValidReciprocate(): Boolean { + if (sharedSecret.isNullOrBlank()) { + return false + } + + return true + } + override fun toEventContent() = toContent() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index d4e72bb769..9e4b7b773e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -34,30 +34,48 @@ internal data class KeyVerificationStart( @Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List? = null, @Json(name = "hashes") override val hashes: List? = null, @Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List? = null, - @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List? = null + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List? = null, + // For QR code verification + @Json(name = "secret") override val sharedSecret: String? = null ) : SendToDeviceObject, VerificationInfoStart { override fun toCanonicalJson(): String? { return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) } + // TODO Move those method to the interface? override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() - || method !in supportedVerificationMethods - || keyAgreementProtocols.isNullOrEmpty() - || hashes.isNullOrEmpty() - || !hashes.contains("sha256") - || messageAuthenticationCodes.isNullOrEmpty() - || (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) - && !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) - || shortAuthenticationStrings.isNullOrEmpty() - || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + || (method == VERIFICATION_METHOD_SAS && !isValidSas()) + || (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) { Timber.e("## received invalid verification request") return false } return true } + private fun isValidSas(): Boolean { + if (keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty() + || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() + || (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) + && !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || shortAuthenticationStrings.isNullOrEmpty() + || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + return false + } + + return true + } + + private fun isValidReciprocate(): Boolean { + if (sharedSecret.isNullOrBlank()) { + return false + } + + return true + } + override fun toSendToDeviceObject() = this } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt index 35f319bfc1..d283ef3132 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt @@ -20,10 +20,8 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningServ import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest 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.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.toValue import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import timber.log.Timber @@ -75,16 +73,15 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( cancel(CancelCode.UnexpectedMessage) } - fun start(method: VerificationMethod) { + fun start() { if (state != VerificationTxState.None) { Timber.e("## SAS O: start verification from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } - val startMessage = transport.createStart( + val startMessage = transport.createStartForSas( credentials.deviceId ?: "", - method.toValue(), transactionId, KNOWN_AGREEMENT_PROTOCOLS, KNOWN_HASHES, 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 9a7cc80c06..86965760c7 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,19 +23,42 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials 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.* +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.VerificationService +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.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationAcceptContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationDoneContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationKeyContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationMacContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel 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.rest.* +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.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.model.rest.supportedVerificationMethods +import im.vector.matrix.android.internal.crypto.model.rest.toValue import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -43,10 +66,8 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import okhttp3.internal.toImmutableList import timber.log.Timber -import java.util.* +import java.util.UUID import javax.inject.Inject -import kotlin.collections.ArrayList -import kotlin.collections.HashMap import kotlin.collections.set @SessionScope @@ -708,7 +729,7 @@ internal class DefaultVerificationService @Inject constructor( tx.transport = verificationTransportToDeviceFactory.createTransport(tx) addTransaction(tx) - tx.start(method) + tx.start() return txID } else { throw IllegalArgumentException("Unknown verification method") @@ -747,7 +768,12 @@ internal class DefaultVerificationService @Inject constructor( otherUserId = userId ) - transport.sendVerificationRequest(methods.map { it.toValue() }, localID, userId, roomId) { syncedId, info -> + // 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() + val methodValues = (methods.map { it.toValue() } + reciprocateMethod).distinct() + + transport.sendVerificationRequest(methodValues, localID, userId, roomId) { syncedId, info -> // We need to update with the syncedID updatePendingRequest(verificationRequest.copy( transactionId = syncedId, @@ -807,7 +833,7 @@ internal class DefaultVerificationService @Inject constructor( tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) addTransaction(tx) - tx.start(method) + tx.start() return transactionId } else { throw IllegalArgumentException("Unknown verification method") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt index d0cfe06815..4875705580 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification internal interface VerificationInfoStart : VerificationInfo { val method: String? + /** * Alice’s device ID */ @@ -58,5 +59,10 @@ internal interface VerificationInfoStart : VerificationInfo { */ val shortAuthenticationStrings: List? + /** + * Shared secret, when starting verification with QR code + */ + val sharedSecret: String? + fun toCanonicalJson(): String? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt index 69752f83be..286b099168 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt @@ -60,13 +60,22 @@ internal interface VerificationTransport { fun createKey(tid: String, pubKey: String): VerificationInfoKey - fun createStart(fromDevice: String, - method: String, - transactionID: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart + /** + * Create start for SAS verification + */ + fun createStartForSas(fromDevice: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List): VerificationInfoStart + + /** + * Create start for QR code verification + */ + fun createStartForQrCode(fromDevice: String, + transactionID: String, + sharedSecret: String): VerificationInfoStart fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt index 7ed86fc028..e20b4ae131 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt @@ -16,14 +16,33 @@ package im.vector.matrix.android.internal.crypto.verification import androidx.lifecycle.Observer -import androidx.work.* +import androidx.work.BackoffPolicy +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.Operation +import androidx.work.WorkInfo import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R 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.* -import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationAcceptContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationDoneContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationKeyContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationMacContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +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.di.DeviceId import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.UserId @@ -35,7 +54,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber -import java.util.* +import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -245,24 +264,42 @@ internal class VerificationTransportRoomMessage( override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) - override fun createStart(fromDevice: String, - method: String, - transactionID: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart { + override fun createStartForSas(fromDevice: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List): VerificationInfoStart { return MessageVerificationStartContent( fromDevice, hashes, keyAgreementProtocols, messageAuthenticationCodes, shortAuthenticationStrings, - method, + VERIFICATION_METHOD_SAS, RelationDefaultContent( type = RelationType.REFERENCE, eventId = transactionID - ) + ), + null + ) + } + + override fun createStartForQrCode(fromDevice: String, + transactionID: String, + sharedSecret: String): VerificationInfoStart { + return MessageVerificationStartContent( + fromDevice, + null, + null, + null, + null, + VERIFICATION_METHOD_RECIPROCATE, + RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = transactionID + ), + sharedSecret ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt index 3a132e8608..59ab9bf1cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt @@ -21,7 +21,14 @@ 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.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.* +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.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.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -119,21 +126,35 @@ internal class VerificationTransportToDevice( override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) - override fun createStart(fromDevice: String, - method: String, - transactionID: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart { + override fun createStartForSas(fromDevice: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List): VerificationInfoStart { return KeyVerificationStart( fromDevice, - method, + VERIFICATION_METHOD_SAS, transactionID, keyAgreementProtocols, hashes, messageAuthenticationCodes, - shortAuthenticationStrings) + shortAuthenticationStrings, + null) + } + + override fun createStartForQrCode(fromDevice: String, + transactionID: String, + sharedSecret: String): VerificationInfoStart { + return KeyVerificationStart( + fromDevice, + VERIFICATION_METHOD_RECIPROCATE, + transactionID, + null, + null, + null, + null, + sharedSecret) } override fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady { From df49ab8362d2a417f5abce5ef7460889f72e262d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Jan 2020 18:12:47 +0100 Subject: [PATCH 04/22] QR code: update code which build URL --- .../crypto/verification/qrcode/Extensions.kt | 20 ++++++++++--------- .../crypto/verification/qrcode/QrCodeTest.kt | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt index 2f90d36140..a4efe385bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt @@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.permalinks.PermalinkFactory +import java.net.URLDecoder +import java.net.URLEncoder + +private const val ENCODING = "utf-8" /** * Generate an URL to generate a QR code of the form: @@ -34,21 +38,19 @@ fun QrCodeData.toUrl(): String { return buildString { append(PermalinkFactory.createPermalink(userId)) append("?request=") - append(PermalinkFactory.escape(requestEventId)) + append(URLEncoder.encode(requestEventId, ENCODING)) append("&action=") - append(PermalinkFactory.escape(action)) + append(URLEncoder.encode(action, ENCODING)) for ((keyId, key) in keys) { - append("&key_") - append(PermalinkFactory.escape(keyId)) - append("=") - append(PermalinkFactory.escape(key)) + append("&key_$keyId=") + append(URLEncoder.encode(key, ENCODING)) } append("&secret=") - append(PermalinkFactory.escape(sharedSecret)) + append(URLEncoder.encode(sharedSecret, ENCODING)) append("&other_user_key=") - append(PermalinkFactory.escape(otherUserKey)) + append(URLEncoder.encode(otherUserKey, ENCODING)) } } @@ -82,7 +84,7 @@ fun String.toQrCodeData(): QrCodeData? { .filter { it.isNotEmpty() } val keyValues = urlParams.map { - (it.substringBefore("=") to it.substringAfter("=").let { value -> PermalinkFactory.unescape(value) }) + (it.substringBefore("=") to it.substringAfter("=").let { value -> URLDecoder.decode(value, ENCODING) }) }.toMap() val action = keyValues["action"] ?: return null diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt index c356ee4795..3c184907a6 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt @@ -39,7 +39,7 @@ class QrCodeTest { otherUserKey = "otherUserKey" ) - private val basicUrl = "https://matrix.to/#/@benoit:matrix.org?request=\$azertyazerty&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" @Test fun testNominalCase() { @@ -101,7 +101,7 @@ class QrCodeTest { @Test fun testBadRequestEventId() { - basicUrl.replace("\$azertyazerty", "@azertyazerty") + basicUrl.replace("%24azertyazerty", "%32azertyazerty") .toQrCodeData() .shouldBeNull() } From adc2d570eb8d430ef0bc9e63f92b0ad6a22235dd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 25 Jan 2020 11:34:55 +0100 Subject: [PATCH 05/22] QR code: handle the case where other user can scan QR codes --- .../DefaultVerificationService.kt | 65 ++++++++++++++++++- .../PendingVerificationRequest.kt | 4 +- .../VerificationChooseMethodController.kt | 4 +- .../VerificationChooseMethodViewModel.kt | 8 +-- 4 files changed, 72 insertions(+), 9 deletions(-) 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 86965760c7..372064c128 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 @@ -55,11 +55,15 @@ 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.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_RECIPROCATE import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import im.vector.matrix.android.internal.crypto.model.rest.supportedVerificationMethods import im.vector.matrix.android.internal.crypto.model.rest.toValue import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.verification.qrcode.QrCodeData +import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret +import im.vector.matrix.android.internal.crypto.verification.qrcode.toUrl import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope @@ -72,6 +76,9 @@ import kotlin.collections.set @SessionScope internal class DefaultVerificationService @Inject constructor( + // TODO @UserId private val userId: String, + // TODO @DeviceId private val deviceId: String, + // TODO Do not use credential (do it in a dedicated commit) private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val myDeviceInfoHolder: Lazy, @@ -650,7 +657,63 @@ internal class DefaultVerificationService @Inject constructor( Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}") return } - updatePendingRequest(existingRequest.copy(readyInfo = readyReq)) + + var myGeneratedSharedSecret: String? = null + val qrCodeText = 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 + } + + val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(existingRequest.otherUserId) + ?.masterKey() + ?.unpaddedBase64PublicKey + ?: run { + Timber.w("Unable to get other user master key") + return@let null + } + + val myDeviceId = credentials.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 = credentials.userId, + requestEventId = requestEventId, + action = QrCodeData.ACTION_VERIFY, + keys = hashMapOf( + myMasterKey to myMasterKey, + myDeviceId to myDeviceKey + ), + sharedSecret = generatedSharedSecret, + otherUserKey = otherUserMasterKey + ).toUrl() + } + + updatePendingRequest(existingRequest.copy( + readyInfo = readyReq, + myGeneratedSecret = myGeneratedSharedSecret, + qrCodeText = qrCodeText + )) } private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index 531d2c8fa1..573da614fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -37,7 +37,9 @@ data class PendingVerificationRequest( val readyInfo: VerificationInfoReady? = null, val cancelConclusion: CancelCode? = null, val isSuccessful: Boolean = false, - val handledByOtherSession: Boolean = false + val handledByOtherSession: Boolean = false, + val myGeneratedSecret: String? = null, + val qrCodeText: String? = null ) { val isReady: Boolean = readyInfo != null diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt index f5568331a9..87bb843291 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -49,10 +49,10 @@ class VerificationChooseMethodController @Inject constructor( notice(stringProvider.getString(R.string.verification_scan_notice)) } - if (state.otherCanScanQrCode && !state.QRtext.isNullOrBlank()) { + if (state.otherCanScanQrCode && !state.qrCodeText.isNullOrBlank()) { bottomSheetVerificationQrCodeItem { id("qr") - data(state.QRtext) + data(state.qrCodeText) animate(false) } 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 a9b24b5710..e4b8f75a8c 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 @@ -37,7 +37,7 @@ data class VerificationChooseMethodViewState( val transactionId: String = "", val otherCanShowQrCode: Boolean = false, val otherCanScanQrCode: Boolean = false, - val QRtext: String? = null, + val qrCodeText: String? = null, val SASModeAvailable: Boolean = false ) : MvRxState @@ -57,8 +57,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( copy( otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false, otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false, - // TODO - QRtext = "https://www.example.org", + qrCodeText = pvr?.qrCodeText, SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false ) } @@ -93,8 +92,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( transactionId = args.verificationId ?: "", otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false, otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false, - // TODO - QRtext = "https://www.example.org", + qrCodeText = pvr?.qrCodeText, SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false ) } From 962b85b041a791c54402f79eb5e6756dd292a4a6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 25 Jan 2020 11:35:59 +0100 Subject: [PATCH 06/22] Add TODO --- .../internal/crypto/verification/PendingVerificationRequest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index 573da614fb..cca6f32067 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -25,6 +25,7 @@ import java.util.UUID /** * Stores current pending verification requests + * TODO We should not expose this whole object to the app. Create an interface */ data class PendingVerificationRequest( val ageLocalTs: Long, From be7701720944ab9f7df4da61d5da82c264cec9b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 25 Jan 2020 12:08:50 +0100 Subject: [PATCH 07/22] Avoid injecting credentials. Inject userId and deviceId instead And cleanup API --- .../session/crypto/sas/VerificationService.kt | 10 +-- ...comingSASDefaultVerificationTransaction.kt | 16 ++--- ...ltOutgoingSASDefaultVerificationRequest.kt | 18 +++-- .../DefaultVerificationService.kt | 67 ++++++++++--------- .../SASDefaultVerificationTransaction.kt | 22 +++--- 5 files changed, 64 insertions(+), 69 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt index c3ec713344..e1c0a1d455 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt @@ -39,20 +39,20 @@ interface VerificationService { */ fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) - fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? + fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? - fun getExistingVerificationRequest(otherUser: String): List? + fun getExistingVerificationRequest(otherUserId: String): List? - fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? + fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? - fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? + fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String? /** * Request a key verification from another user using toDevice events. */ - fun requestKeyVerificationInDMs(methods: List, userId: String, roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest + fun requestKeyVerificationInDMs(methods: List, otherUserId: String, roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index 0b97d50398..863a68996e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification import android.util.Base64 import im.vector.matrix.android.BuildConfig -import im.vector.matrix.android.api.auth.data.Credentials 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.IncomingSasVerificationTransaction @@ -30,16 +29,18 @@ import timber.log.Timber internal class DefaultIncomingSASDefaultVerificationTransaction( setDeviceVerificationAction: SetDeviceVerificationAction, - override val credentials: Credentials, + override val userId: String, + override val deviceId: String?, private val cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, deviceFingerprint: String, transactionId: String, otherUserID: String, - val autoAccept: Boolean = false + private val autoAccept: Boolean = false ) : SASDefaultVerificationTransaction( setDeviceVerificationAction, - credentials, + userId, + deviceId, cryptoStore, crossSigningService, deviceFingerprint, @@ -157,7 +158,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( cancel(CancelCode.UnexpectedMessage) } - override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) { + override fun onKeyVerificationKey(vKey: VerificationInfoKey) { Timber.v("## SAS received key for request id:$transactionId") if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) { Timber.e("## SAS received key from invalid state $state") @@ -194,10 +195,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( // - the Matrix ID of the user who sent the m.key.verification.accept message, // - he device ID of the device that sent the m.key.verification.accept message // - the transaction ID. - val sasInfo = "MATRIX_KEY_VERIFICATION_SAS" + - "$otherUserId$otherDeviceId" + - "${credentials.userId}${credentials.deviceId}" + - transactionId + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId" // decimal: generate five bytes by using HKDF. // emoji: generate six bytes by using HKDF. shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt index d283ef3132..a2f9ae8219 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt @@ -15,7 +15,6 @@ */ package im.vector.matrix.android.internal.crypto.verification -import im.vector.matrix.android.api.auth.data.Credentials 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.OutgoingSasVerificationRequest @@ -27,7 +26,8 @@ import timber.log.Timber internal class DefaultOutgoingSASDefaultVerificationRequest( setDeviceVerificationAction: SetDeviceVerificationAction, - credentials: Credentials, + userId: String, + deviceId: String?, cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, deviceFingerprint: String, @@ -36,7 +36,8 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( otherDeviceId: String ) : SASDefaultVerificationTransaction( setDeviceVerificationAction, - credentials, + userId, + deviceId, cryptoStore, crossSigningService, deviceFingerprint, @@ -81,7 +82,7 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( } val startMessage = transport.createStartForSas( - credentials.deviceId ?: "", + deviceId ?: "", transactionId, KNOWN_AGREEMENT_PROTOCOLS, KNOWN_HASHES, @@ -161,7 +162,7 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( } } - override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) { + override fun onKeyVerificationKey(vKey: VerificationInfoKey) { Timber.v("## SAS O: onKeyVerificationKey id:$transactionId") if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) { Timber.e("## received key from invalid state $state") @@ -189,16 +190,13 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( // - the Matrix ID of the user who sent the m.key.verification.accept message, // - he device ID of the device that sent the m.key.verification.accept message // - the transaction ID. - val sasInfo = "MATRIX_KEY_VERIFICATION_SAS" + - "${credentials.userId}${credentials.deviceId}" + - "$otherUserId$otherDeviceId" + - transactionId + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId" // decimal: generate five bytes by using HKDF. // emoji: generate six bytes by using HKDF. shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) state = VerificationTxState.ShortCodeReady } else { - // bad commitement + // bad commitment cancel(CancelCode.MismatchedCommitment) } } 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 372064c128..92b5ea6e26 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 @@ -20,7 +20,6 @@ import android.os.Handler import android.os.Looper import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.auth.data.Credentials 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 @@ -64,6 +63,8 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.verification.qrcode.QrCodeData import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret import im.vector.matrix.android.internal.crypto.verification.qrcode.toUrl +import im.vector.matrix.android.internal.di.DeviceId +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope @@ -76,10 +77,8 @@ import kotlin.collections.set @SessionScope internal class DefaultVerificationService @Inject constructor( - // TODO @UserId private val userId: String, - // TODO @DeviceId private val deviceId: String, - // TODO Do not use credential (do it in a dedicated commit) - private val credentials: Credentials, + @UserId private val userId: String, + @DeviceId private val deviceId: String?, private val cryptoStore: IMXCryptoStore, private val myDeviceInfoHolder: Lazy, private val deviceListManager: DeviceListManager, @@ -264,7 +263,7 @@ internal class DefaultVerificationService @Inject constructor( ?: return val senderId = event.senderId ?: return - if (requestInfo.toUserId != credentials.userId) { + if (requestInfo.toUserId != userId) { // I should ignore this, it's not for me Timber.w("## SAS Verification ignoring request from ${event.senderId}, not sent to me") return @@ -404,7 +403,8 @@ internal class DefaultVerificationService @Inject constructor( val tx = DefaultIncomingSASDefaultVerificationTransaction( // this, setDeviceVerificationAction, - credentials, + userId, + deviceId, cryptoStore, crossSigningService, myDeviceInfoHolder.get().myDevice.fingerprint()!!, @@ -685,7 +685,7 @@ internal class DefaultVerificationService @Inject constructor( return@let null } - val myDeviceId = credentials.deviceId + val myDeviceId = deviceId ?: run { Timber.w("Unable to get my deviceId") return@let null @@ -697,7 +697,7 @@ internal class DefaultVerificationService @Inject constructor( val generatedSharedSecret = generateSharedSecret() .also { myGeneratedSharedSecret = it } QrCodeData( - userId = credentials.userId, + userId = userId, requestEventId = requestEventId, action = QrCodeData.ACTION_VERIFY, keys = hashMapOf( @@ -725,21 +725,22 @@ internal class DefaultVerificationService @Inject constructor( updatePendingRequest(existingRequest.copy(isSuccessful = true)) } - override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? { + // TODO All this methods should be delegated to a TransactionStore + override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? { synchronized(lock = txMap) { - return txMap[otherUser]?.get(tid) + return txMap[otherUserId]?.get(tid) } } - override fun getExistingVerificationRequest(otherUser: String): List? { + override fun getExistingVerificationRequest(otherUserId: String): List? { synchronized(lock = pendingRequests) { - return pendingRequests[otherUser] + return pendingRequests[otherUserId] } } - override fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? { + override fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? { synchronized(lock = pendingRequests) { - return tid?.let { tid -> pendingRequests[otherUser]?.firstOrNull { it.transactionId == tid } } + return tid?.let { tid -> pendingRequests[otherUserId]?.firstOrNull { it.transactionId == tid } } } } @@ -776,19 +777,20 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? { - val txID = createUniqueIDForTransaction(userId, deviceID) + override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String? { + val txID = createUniqueIDForTransaction(otherUserId, otherDeviceID) // should check if already one (and cancel it) if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASDefaultVerificationRequest( setDeviceVerificationAction, - credentials, + userId, + deviceId, cryptoStore, crossSigningService, myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, - userId, - deviceID) + otherUserId, + otherDeviceID) tx.transport = verificationTransportToDeviceFactory.createTransport(tx) addTransaction(tx) @@ -799,13 +801,13 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun requestKeyVerificationInDMs(methods: List, userId: String, roomId: String, localId: String?) + override fun requestKeyVerificationInDMs(methods: List, otherUserId: String, roomId: String, localId: String?) : PendingVerificationRequest { - Timber.i("## SAS Requesting verification to user: $userId in room $roomId") + Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") - val requestsForUser = pendingRequests[userId] + val requestsForUser = pendingRequests[otherUserId] ?: ArrayList().also { - pendingRequests[userId] = it + pendingRequests[otherUserId] = it } val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null) @@ -828,7 +830,7 @@ internal class DefaultVerificationService @Inject constructor( isIncoming = false, roomId = roomId, localID = localID, - otherUserId = userId + otherUserId = otherUserId ) // Add reciprocate method if application declares it can scan or show QR codes @@ -836,7 +838,7 @@ internal class DefaultVerificationService @Inject constructor( val reciprocateMethod = methods.firstOrNull { it == VerificationMethod.QR_CODE_SCAN || it == VerificationMethod.QR_CODE_SHOW }?.let { listOf(VERIFICATION_METHOD_RECIPROCATE) }.orEmpty() val methodValues = (methods.map { it.toValue() } + reciprocateMethod).distinct() - transport.sendVerificationRequest(methodValues, localID, userId, roomId) { syncedId, info -> + transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId) { syncedId, info -> // We need to update with the syncedID updatePendingRequest(verificationRequest.copy( transactionId = syncedId, @@ -886,7 +888,8 @@ internal class DefaultVerificationService @Inject constructor( if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASDefaultVerificationRequest( setDeviceVerificationAction, - credentials, + userId, + deviceId, cryptoStore, crossSigningService, myDeviceInfoHolder.get().myDevice.fingerprint()!!, @@ -918,7 +921,7 @@ internal class DefaultVerificationService @Inject constructor( 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, credentials.deviceId ?: "", methods) + val readyMsg = transport.createReady(transactionId, deviceId ?: "", methods) transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg, VerificationTxState.None, CancelCode.User, @@ -936,12 +939,12 @@ internal class DefaultVerificationService @Inject constructor( /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ - private fun createUniqueIDForTransaction(userId: String, deviceID: String): String { + private fun createUniqueIDForTransaction(otherUserId: String, otherDeviceID: String): String { return buildString { - append(credentials.userId).append("|") - append(credentials.deviceId).append("|") append(userId).append("|") - append(deviceID).append("|") + append(deviceId).append("|") + append(otherUserId).append("|") + append(otherDeviceID).append("|") append(UUID.randomUUID().toString()) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index e8d01a0b25..a8f13d92cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification import android.os.Build import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.auth.data.Credentials 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.EmojiRepresentation @@ -41,7 +40,8 @@ import kotlin.properties.Delegates */ internal abstract class SASDefaultVerificationTransaction( private val setDeviceVerificationAction: SetDeviceVerificationAction, - open val credentials: Credentials, + open val userId: String, + open val deviceId: String?, private val cryptoStore: IMXCryptoStore, private val crossSigningService: CrossSigningService, private val deviceFingerprint: String, @@ -139,11 +139,7 @@ internal abstract class SASDefaultVerificationTransaction( // - the device ID of the device receiving the MAC, // - the transaction ID, and // - the key ID of the key being MAC-ed, or the string “KEY_IDS” if the item being MAC-ed is the list of key IDs. - - val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + - credentials.userId + credentials.deviceId + - otherUserId + otherDeviceId + - transactionId + val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$userId$deviceId$otherUserId$otherDeviceId$transactionId" // Previously, with SAS verification, the m.key.verification.mac message only contained the user's device key. // It should now contain both the device key and the MSK. @@ -151,7 +147,7 @@ internal abstract class SASDefaultVerificationTransaction( val keyMap = HashMap() - val keyId = "ed25519:${credentials.deviceId}" + val keyId = "ed25519:$deviceId" val macString = macUsingAgreedMethod(deviceFingerprint, baseInfo + keyId) if (macString.isNullOrBlank()) { @@ -211,7 +207,7 @@ internal abstract class SASDefaultVerificationTransaction( when (info) { is VerificationInfoStart -> onVerificationStart(info) is VerificationInfoAccept -> onVerificationAccept(info) - is VerificationInfoKey -> onKeyVerificationKey(senderId, info) + is VerificationInfoKey -> onKeyVerificationKey(info) is VerificationInfoMac -> onKeyVerificationMac(info) else -> { // nop @@ -223,7 +219,7 @@ internal abstract class SASDefaultVerificationTransaction( abstract fun onVerificationAccept(accept: VerificationInfoAccept) - abstract fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) + abstract fun onKeyVerificationKey(vKey: VerificationInfoKey) abstract fun onKeyVerificationMac(vKey: VerificationInfoMac) @@ -241,7 +237,7 @@ internal abstract class SASDefaultVerificationTransaction( val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + otherUserId + otherDeviceId + - credentials.userId + credentials.deviceId + + userId + deviceId + transactionId val commaSeparatedListOfKeyIds = theirMac!!.mac!!.keys.sorted().joinToString(",") @@ -305,7 +301,7 @@ internal abstract class SASDefaultVerificationTransaction( } // If not me sign his MSK and upload the signature - if (otherMasterKeyIsVerified && otherUserId != credentials.userId) { + if (otherMasterKeyIsVerified && otherUserId != userId) { // we should trust this master key // And check verification MSK -> SSK? crossSigningService.trustUser(otherUserId, object : MatrixCallback { @@ -315,7 +311,7 @@ internal abstract class SASDefaultVerificationTransaction( }) } - if (otherUserId == credentials.userId) { + if (otherUserId == userId) { // If me it's reasonable to sign and upload the device signature // Notice that i might not have the private keys, so may ot be able to do it crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { From fb5148fd436b514d4f37d669e0ead34c6910f94d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 25 Jan 2020 12:52:40 +0100 Subject: [PATCH 08/22] Avoid to inject credential (again) --- .../crosssigning/CrossSigningService.kt | 20 ++- .../DefaultCrossSigningService.kt | 132 +++++++++--------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 7cf7011672..6a0311f202 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -26,33 +26,39 @@ import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth interface CrossSigningService { - fun isUserTrusted(userId: String) : Boolean + fun isUserTrusted(otherUserId: String): Boolean /** * Will not force a download of the key, but will verify signatures trust chain. * Checks that my trusted user key has signed the other user UserKey */ - fun checkUserTrust(userId: String) : UserTrustResult + fun checkUserTrust(otherUserId: String): UserTrustResult /** * Initialize cross signing for this user. * Users needs to enter credentials */ - fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback? = null) + fun initializeCrossSigning(authParams: UserPasswordAuth?, + callback: MatrixCallback? = null) - fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? + fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? fun getLiveCrossSigningKeys(userId: String): LiveData> fun getMyCrossSigningKeys(): MXCrossSigningInfo? + fun canCrossSign(): Boolean - fun trustUser(userId: String, callback: MatrixCallback) + fun trustUser(otherUserId: String, + callback: MatrixCallback) /** * Sign one of your devices and upload the signature */ - fun signDevice(deviceId: String, callback: MatrixCallback) + fun signDevice(deviceId: String, + callback: MatrixCallback) - fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?) : DeviceTrustResult + fun checkDeviceTrust(otherUserId: String, + otherDeviceId: String, + locallyTrusted: Boolean?): DeviceTrustResult } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index dbed2dcc44..56bf79af99 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning import androidx.lifecycle.LiveData import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional @@ -50,7 +49,6 @@ import javax.inject.Inject @SessionScope internal class DefaultCrossSigningService @Inject constructor( @UserId private val userId: String, - private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val myDeviceInfoHolder: Lazy, private val olmDevice: MXOlmDevice, @@ -145,8 +143,6 @@ internal class DefaultCrossSigningService @Inject constructor( override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback?) { Timber.d("## CrossSigning initializeCrossSigning") - val myUserID = credentials.userId - // ================= // MASTER KEY // ================= @@ -166,7 +162,7 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey") // Sign userSigningKey with master - val signedUSK = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.USER_SIGNING) + val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) .key(uskPublicKey) .build() .canonicalSignable() @@ -182,23 +178,23 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") // Sign userSigningKey with master - val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING) + val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) .key(sskPublicKey) .build().signalableJSONDictionary()).let { masterPkOlm.sign(it) } // I need to upload the keys - val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.MASTER) + val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER) .key(masterPublicKey) .build() val params = UploadSigningKeysTask.Params( masterKey = mskCrossSigningKeyInfo, - userKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.USER_SIGNING) + userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) .key(uskPublicKey) - .signature(myUserID, masterPublicKey, signedUSK) + .signature(userId, masterPublicKey, signedUSK) .build(), - selfSignedKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING) + selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) .key(sskPublicKey) - .signature(myUserID, masterPublicKey, signedSSK) + .signature(userId, masterPublicKey, signedSSK) .build(), userPasswordAuth = authParams ) @@ -207,9 +203,9 @@ internal class DefaultCrossSigningService @Inject constructor( this.userPkSigning = userSigningPkOlm this.selfSigningPkSigning = selfSigningPkOlm - val crossSigningInfo = MXCrossSigningInfo(myUserID, listOf(params.masterKey, params.userKey, params.selfSignedKey)) + val crossSigningInfo = MXCrossSigningInfo(userId, listOf(params.masterKey, params.userKey, params.selfSignedKey)) cryptoStore.setMyCrossSigningInfo(crossSigningInfo) - cryptoStore.setUserKeysAsTrusted(myUserID) + cryptoStore.setUserKeysAsTrusted(userId) cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding()) uploadSigningKeysTask.configureWith(params) { @@ -225,7 +221,7 @@ internal class DefaultCrossSigningService @Inject constructor( val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) val signedDevice = selfSigningPkOlm.sign(canonicalJson) val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also { - it[myUserID] = (it[myUserID] + it[userId] = (it[userId] ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) } myDevice.copy(signatures = updateSignatures).let { @@ -236,7 +232,7 @@ internal class DefaultCrossSigningService @Inject constructor( olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign -> val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() ?: HashMap()).also { - it[myUserID] = (it[myUserID] + it[userId] = (it[userId] ?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign) } mskCrossSigningKeyInfo.copy( @@ -310,16 +306,16 @@ internal class DefaultCrossSigningService @Inject constructor( * │ BOB's Device │ * └──────────────┘ */ - override fun isUserTrusted(userId: String): Boolean { + override fun isUserTrusted(otherUserId: String): Boolean { return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true } /** * Will not force a download of the key, but will verify signatures trust chain */ - override fun checkUserTrust(userId: String): UserTrustResult { - Timber.d("## CrossSigning checkUserTrust for $userId") - if (userId == credentials.userId) { + override fun checkUserTrust(otherUserId: String): UserTrustResult { + Timber.d("## CrossSigning checkUserTrust for $otherUserId") + if (otherUserId == userId) { return checkSelfTrust() } // I trust a user if I trust his master key @@ -327,25 +323,25 @@ internal class DefaultCrossSigningService @Inject constructor( // TODO what if the master key is signed by a device key that i have verified // First let's get my user key - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(credentials.userId) + val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) val myUserKey = myCrossSigningInfo?.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(credentials.userId) + ?: return UserTrustResult.CrossSigningNotConfigured(userId) if (!myCrossSigningInfo.isTrusted()) { return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) } // Let's get the other user master key - val otherMasterKey = cryptoStore.getCrossSigningInfo(userId)?.masterKey() - ?: return UserTrustResult.UnknownCrossSignatureInfo(userId) + val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey() + ?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId) val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures - ?.get(credentials.userId) // Signatures made by me + ?.get(userId) // Signatures made by me ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $userId, not signed by my UserSigningKey") + Timber.d("## CrossSigning checkUserTrust false for $otherUserId, not signed by my UserSigningKey") return UserTrustResult.KeyNotSigned(otherMasterKey) } @@ -363,12 +359,10 @@ internal class DefaultCrossSigningService @Inject constructor( // Special case when it's me, // I have to check that MSK -> USK -> SSK // and that MSK is trusted (i know the private key, or is signed by a trusted device) - - val myUserId = credentials.userId - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(myUserId) + val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) val myMasterKey = myCrossSigningInfo?.masterKey() - ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + ?: return UserTrustResult.CrossSigningNotConfigured(userId) // Is the master key trusted // 1) check if I know the private key @@ -390,9 +384,9 @@ internal class DefaultCrossSigningService @Inject constructor( olmPkSigning?.releaseSigning() } else { // Maybe it's signed by a locally trusted device? - myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) -> + myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> val potentialDeviceId = if (key.startsWith("ed25519:")) key.substring("ed25519:".length) else key - val potentialDevice = cryptoStore.getUserDevice(myUserId, potentialDeviceId) + val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId) if (potentialDevice != null && potentialDevice.isVerified) { // Check signature validity? try { @@ -412,10 +406,10 @@ internal class DefaultCrossSigningService @Inject constructor( } val myUserKey = myCrossSigningInfo.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + ?: return UserTrustResult.CrossSigningNotConfigured(userId) val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures - ?.get(myUserId) // Signatures made by me + ?.get(userId) // Signatures made by me ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { @@ -431,29 +425,29 @@ internal class DefaultCrossSigningService @Inject constructor( } val mySSKey = myCrossSigningInfo.selfSigningKey() - ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + ?: return UserTrustResult.CrossSigningNotConfigured(userId) - val SSKeySignaturesMadeByMyMasterKey = mySSKey.signatures - ?.get(myUserId) // Signatures made by me + val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures + ?.get(userId) // Signatures made by me ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") - if (SSKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { + if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") return UserTrustResult.KeyNotSigned(mySSKey) } // Check that Alice USK signature of Alice MSK is valid try { - olmUtility!!.verifyEd25519Signature(SSKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) + olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(mySSKey, SSKeySignaturesMadeByMyMasterKey) + return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) } return UserTrustResult.Success } - override fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? { - return cryptoStore.getCrossSigningInfo(userId) + override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { + return cryptoStore.getCrossSigningInfo(otherUserId) } override fun getLiveCrossSigningKeys(userId: String): LiveData> { @@ -468,15 +462,15 @@ internal class DefaultCrossSigningService @Inject constructor( return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null } - override fun trustUser(userId: String, callback: MatrixCallback) { + override fun trustUser(otherUserId: String, callback: MatrixCallback) { Timber.d("## CrossSigning - Mark user $userId as trusted ") // We should have this user keys - val otherMasterKeys = getUserCrossSigningKeys(userId)?.masterKey() + val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() if (otherMasterKeys == null) { callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) return } - val myKeys = getUserCrossSigningKeys(credentials.userId) + val myKeys = getUserCrossSigningKeys(userId) if (myKeys == null) { callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) return @@ -487,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor( return } - // Sign the other MasterKey with our UserSiging key + // Sign the other MasterKey with our UserSigning key val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } @@ -497,12 +491,12 @@ internal class DefaultCrossSigningService @Inject constructor( return } - cryptoStore.setUserKeysAsTrusted(userId, true) + cryptoStore.setUserKeysAsTrusted(otherUserId, true) // TODO update local copy with new signature directly here? kind of local echo of trust? Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") val uploadQuery = UploadSignatureQueryBuilder() - .withSigningKeyInfo(otherMasterKeys.copyForSignature(credentials.userId, userPubKey, newSignature)) + .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) .build() uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { this.callback = callback @@ -511,13 +505,13 @@ internal class DefaultCrossSigningService @Inject constructor( override fun signDevice(deviceId: String, callback: MatrixCallback) { // This device should be yours - val device = cryptoStore.getUserDevice(credentials.userId, deviceId) + val device = cryptoStore.getUserDevice(userId, deviceId) if (device == null) { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) + callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) return } - val myKeys = getUserCrossSigningKeys(credentials.userId) + val myKeys = getUserCrossSigningKeys(userId) if (myKeys == null) { callback.onFailure(Throwable("CrossSigning is not setup for this account")) return @@ -539,7 +533,7 @@ internal class DefaultCrossSigningService @Inject constructor( } val toUpload = device.copy( signatures = mapOf( - credentials.userId + userId to mapOf( "ed25519:$ssPubKey" to newSignature @@ -555,17 +549,17 @@ internal class DefaultCrossSigningService @Inject constructor( }.executeBy(taskExecutor) } - override fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { - val otherDevice = cryptoStore.getUserDevice(userId, deviceId) - ?: return DeviceTrustResult.UnknownDevice(deviceId) + override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { + val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) + ?: return DeviceTrustResult.UnknownDevice(otherDeviceId) - val myKeys = getUserCrossSigningKeys(credentials.userId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(credentials.userId)) + val myKeys = getUserCrossSigningKeys(userId) + ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) - val otherKeys = getUserCrossSigningKeys(userId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) + val otherKeys = getUserCrossSigningKeys(otherUserId) + ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId)) // TODO should we force verification ? if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) @@ -589,15 +583,15 @@ internal class DefaultCrossSigningService @Inject constructor( * └──────────────┘ */ - val otherSSKSignature = otherDevice.signatures?.get(userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.MissingDeviceSignature(deviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey + val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") + ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey ?: "")) // Check bob's device is signed by bob's SSK try { olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) } catch (e: Throwable) { - return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(deviceId, otherSSKSignature, e)) + return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e)) } return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) @@ -614,19 +608,19 @@ internal class DefaultCrossSigningService @Inject constructor( override fun onUsersDeviceUpdate(users: List) { Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users") - users.forEach { userId -> + users.forEach { otherUserId -> - checkUserTrust(userId).let { - Timber.d("## CrossSigning - update trust for ${userId} , verified=${it.isVerified()}") - cryptoStore.setUserKeysAsTrusted(userId, it.isVerified()) + checkUserTrust(otherUserId).let { + Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}") + cryptoStore.setUserKeysAsTrusted(otherUserId, it.isVerified()) } // TODO if my keys have changes, i should recheck all devices of all users? - val devices = cryptoStore.getUserDeviceList(userId) + val devices = cryptoStore.getUserDeviceList(otherUserId) devices?.forEach { device -> - val updatedTrust = checkDeviceTrust(userId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user ${userId} , verified=$updatedTrust") - cryptoStore.setDeviceTrust(userId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) + val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) + Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") + cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) } } } From 9e796067cc55c89216f4d7944b6815647bc14273 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 10:49:49 +0100 Subject: [PATCH 09/22] Do not support SHOW or SCAN if cross-signing is not enabled --- .../crosssigning/CrossSigningService.kt | 2 ++ .../DefaultCrossSigningService.kt | 25 +++++++++---------- .../DefaultVerificationService.kt | 19 ++++++++++---- .../PendingVerificationRequest.kt | 2 ++ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 6a0311f202..99423ed094 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -26,6 +26,8 @@ import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth interface CrossSigningService { + fun isCrossSigningEnabled(): Boolean + fun isUserTrusted(otherUserId: String): Boolean /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 56bf79af99..ca281ac7ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -212,7 +212,7 @@ internal class DefaultCrossSigningService @Inject constructor( this.constraints = TaskConstraints(true) this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { - Timber.i("## CrossSigning - Keys succesfully uploaded") + Timber.i("## CrossSigning - Keys successfully uploaded") // Sign the current device with SSK val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder() @@ -248,7 +248,7 @@ internal class DefaultCrossSigningService @Inject constructor( this.constraints = TaskConstraints(true) this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { - Timber.i("## CrossSigning - signatures succesfuly uploaded") + Timber.i("## CrossSigning - signatures successfully uploaded") callback?.onSuccess(Unit) } @@ -293,23 +293,22 @@ internal class DefaultCrossSigningService @Inject constructor( * ┏━━━━━━━━┓ ┏━━━━━━━━┓ * ┃ ALICE ┃ ┃ BOB ┃ * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶MSK + * MSK ┌────────────▶ MSK * │ - * │ │ │ - * │ SSK │ └──▶ SSK ──────────────────┐ - * │ │ │ - * │ │ USK │ - * └──▶ USK ────────────┘ (not visible by │ - * Alice) │ - * ▼ - * ┌──────────────┐ - * │ BOB's Device │ - * └──────────────┘ + * │ │ + * │ SSK │ + * │ │ + * │ │ + * └──▶ USK ────────────┘ */ override fun isUserTrusted(otherUserId: String): Boolean { return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true } + override fun isCrossSigningEnabled(): Boolean { + return checkSelfTrust().isVerified() + } + /** * Will not force a download of the key, but will verify signatures trust chain */ 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 92b5ea6e26..e2ef60d990 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 @@ -601,7 +601,7 @@ internal class DefaultVerificationService @Inject constructor( return } if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) { - Timber.e("## SAS Verification device ${readyReq.fromDevice} is not knwown") + Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known") // TODO cancel? return } @@ -833,10 +833,19 @@ internal class DefaultVerificationService @Inject constructor( otherUserId = otherUserId ) - // 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() - val methodValues = (methods.map { it.toValue() } + reciprocateMethod).distinct() + // 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, roomId) { syncedId, info -> // We need to update with the syncedID diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index cca6f32067..4548313ba1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -39,7 +39,9 @@ data class PendingVerificationRequest( val cancelConclusion: CancelCode? = null, val isSuccessful: Boolean = false, val handledByOtherSession: Boolean = false, + // TODO Move to OutgoingQrCodeTransaction val myGeneratedSecret: String? = null, + // TODO Move to OutgoingQrCodeTransaction val qrCodeText: String? = null ) { From f80861bed8b87846301bfd6e21ae3c883f84cdf6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 10:50:27 +0100 Subject: [PATCH 10/22] Add TODO --- .../internal/crypto/verification/DefaultVerificationService.kt | 1 + 1 file changed, 1 insertion(+) 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 e2ef60d990..2f28e64ca8 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 @@ -677,6 +677,7 @@ internal class DefaultVerificationService @Inject constructor( return@let null } + // TODO Force download? val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(existingRequest.otherUserId) ?.masterKey() ?.unpaddedBase64PublicKey From fc04833157c0a1ba33bcedf23b8fc560b0ee928e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 11:22:30 +0100 Subject: [PATCH 11/22] Rename parameter --- .../crypto/verification/VerificationAction.kt | 10 +++++----- .../verification/VerificationBottomSheetViewModel.kt | 6 +++--- .../choose/VerificationChooseMethodFragment.kt | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt index 03658786dc..83fdc46270 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt @@ -19,10 +19,10 @@ package im.vector.riotx.features.crypto.verification import im.vector.riotx.core.platform.VectorViewModelAction sealed class VerificationAction : VectorViewModelAction { - data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction() - data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction() - data class RemoteQrCodeScanned(val userID: String, val transactionId: String, val scannedData: String) : VerificationAction() - data class SASMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction() - data class SASDoNotMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction() + data class RequestVerificationByDM(val otherUserId: String, val roomId: String?) : VerificationAction() + data class StartSASVerification(val otherUserId: String, val pendingRequestTransactionId: String) : VerificationAction() + data class RemoteQrCodeScanned(val otherUserId: String, val transactionId: String, val scannedData: String) : VerificationAction() + data class SASMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction() + data class SASDoNotMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction() object GotItConclusion : VerificationAction() } 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 6b9b658b82..5a680556ca 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 @@ -166,18 +166,18 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } is VerificationAction.RemoteQrCodeScanned -> { val existingTransaction = session.getVerificationService() - .getExistingTransaction(action.userID, action.transactionId) as? QRVerificationTransaction + .getExistingTransaction(action.otherUserId, action.transactionId) as? QRVerificationTransaction existingTransaction ?.userHasScannedRemoteQrCode(action.scannedData) } is VerificationAction.SASMatchAction -> { (session.getVerificationService() - .getExistingTransaction(action.userID, action.sasTransactionId) + .getExistingTransaction(action.otherUserId, action.sasTransactionId) as? SasVerificationTransaction)?.userHasVerifiedShortCode() } is VerificationAction.SASDoNotMatchAction -> { (session.getVerificationService() - .getExistingTransaction(action.userID, action.sasTransactionId) + .getExistingTransaction(action.otherUserId, action.sasTransactionId) as? SasVerificationTransaction) ?.shortCodeDoesNotMatch() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt index b9f543808c..e0b7f97383 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt @@ -69,10 +69,10 @@ class VerificationChooseMethodFragment @Inject constructor( controller.update(state) } - override fun doVerifyBySas() = withState(sharedViewModel) { + override fun doVerifyBySas() = withState(sharedViewModel) { state -> sharedViewModel.handle(VerificationAction.StartSASVerification( - it.otherUserMxItem?.id ?: "", - it.pendingRequest.invoke()?.transactionId ?: "")) + state.otherUserMxItem?.id ?: "", + state.pendingRequest.invoke()?.transactionId ?: "")) } override fun openCamera() { @@ -112,10 +112,10 @@ class VerificationChooseMethodFragment @Inject constructor( } } - private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) { + private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) { state -> sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned( - it.otherUserMxItem?.id ?: "", - it.pendingRequest.invoke()?.transactionId ?: "", + state.otherUserMxItem?.id ?: "", + state.pendingRequest.invoke()?.transactionId ?: "", remoteQrCode )) } From 0aaba26f17ad715d00a972cf538df429ff466c1d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 14:50:04 +0100 Subject: [PATCH 12/22] Rename classes --- .../internal/crypto/verification/SASTest.kt | 14 +++++------ .../api/session/crypto/sas/CancelCode.kt | 2 ++ ... => OutgoingSasVerificationTransaction.kt} | 2 +- ...comingSASDefaultVerificationTransaction.kt | 2 +- ...goingSASDefaultVerificationTransaction.kt} | 24 +++++++++---------- .../DefaultVerificationService.kt | 4 ++-- .../SASDefaultVerificationTransaction.kt | 4 ++-- 7 files changed, 27 insertions(+), 25 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/{OutgoingSasVerificationRequest.kt => OutgoingSasVerificationTransaction.kt} (92%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{DefaultOutgoingSASDefaultVerificationRequest.kt => DefaultOutgoingSASDefaultVerificationTransaction.kt} (94%) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index 178eca09b2..06310e7566 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -417,12 +417,12 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: VerificationTransaction) {} override fun transactionUpdated(tx: VerificationTransaction) { - val uxState = (tx as OutgoingSasVerificationRequest).uxState + val uxState = (tx as OutgoingSasVerificationTransaction).uxState when (uxState) { - OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { aliceSASLatch.countDown() } - else -> Unit + else -> Unit } } @@ -481,15 +481,15 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: VerificationTransaction) {} override fun transactionUpdated(tx: VerificationTransaction) { - val uxState = (tx as OutgoingSasVerificationRequest).uxState + val uxState = (tx as OutgoingSasVerificationTransaction).uxState when (uxState) { - OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { tx.userHasVerifiedShortCode() } - OutgoingSasVerificationRequest.UxState.VERIFIED -> { + OutgoingSasVerificationTransaction.UxState.VERIFIED -> { aliceSASLatch.countDown() } - else -> Unit + else -> Unit } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt index 92a69bcad6..149d099b66 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +// TODO Rename package package im.vector.matrix.android.api.session.crypto.sas enum class CancelCode(val value: String, val humanReadable: String) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationTransaction.kt similarity index 92% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationRequest.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationTransaction.kt index 587dac1f62..7ab386295a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/OutgoingSasVerificationTransaction.kt @@ -16,7 +16,7 @@ package im.vector.matrix.android.api.session.crypto.sas -interface OutgoingSasVerificationRequest : SasVerificationTransaction { +interface OutgoingSasVerificationTransaction : SasVerificationTransaction { val uxState: UxState enum class UxState { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index 863a68996e..13839a0499 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -47,7 +47,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( transactionId, otherUserID, null, - true), + isIncoming = true), IncomingSasVerificationTransaction { override val uxState: IncomingSasVerificationTransaction.UxState diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt similarity index 94% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt index a2f9ae8219..3e693f53ad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt @@ -17,14 +17,14 @@ package im.vector.matrix.android.internal.crypto.verification 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.OutgoingSasVerificationRequest +import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction 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.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import timber.log.Timber -internal class DefaultOutgoingSASDefaultVerificationRequest( +internal class DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction: SetDeviceVerificationAction, userId: String, deviceId: String?, @@ -45,27 +45,27 @@ internal class DefaultOutgoingSASDefaultVerificationRequest( otherUserId, otherDeviceId, isIncoming = false), - OutgoingSasVerificationRequest { + OutgoingSasVerificationTransaction { - override val uxState: OutgoingSasVerificationRequest.UxState + override val uxState: OutgoingSasVerificationTransaction.UxState get() { return when (state) { - VerificationTxState.None -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_START + VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START VerificationTxState.SendingStart, VerificationTxState.Started, VerificationTxState.OnAccepted, VerificationTxState.SendingKey, VerificationTxState.KeySent, - VerificationTxState.OnKeyReceived -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT - VerificationTxState.ShortCodeReady -> OutgoingSasVerificationRequest.UxState.SHOW_SAS + VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT + VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS VerificationTxState.ShortCodeAccepted, VerificationTxState.SendingMac, VerificationTxState.MacSent, - VerificationTxState.Verifying -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION - VerificationTxState.Verified -> OutgoingSasVerificationRequest.UxState.VERIFIED - VerificationTxState.OnCancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME - VerificationTxState.Cancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER - else -> OutgoingSasVerificationRequest.UxState.UNKNOWN + VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION + VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED + VerificationTxState.OnCancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME + VerificationTxState.Cancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER + else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN } } 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 2f28e64ca8..1bf35b2108 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 @@ -782,7 +782,7 @@ internal class DefaultVerificationService @Inject constructor( val txID = createUniqueIDForTransaction(otherUserId, otherDeviceID) // should check if already one (and cancel it) if (method == VerificationMethod.SAS) { - val tx = DefaultOutgoingSASDefaultVerificationRequest( + val tx = DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction, userId, deviceId, @@ -896,7 +896,7 @@ internal class DefaultVerificationService @Inject constructor( otherDeviceId: String, callback: MatrixCallback?): String? { if (method == VerificationMethod.SAS) { - val tx = DefaultOutgoingSASDefaultVerificationRequest( + val tx = DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction, userId, deviceId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index a8f13d92cc..d53703f73a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -47,9 +47,9 @@ internal abstract class SASDefaultVerificationTransaction( private val deviceFingerprint: String, transactionId: String, otherUserId: String, - otherDevice: String?, + otherDeviceId: String?, isIncoming: Boolean -) : DefaultVerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming), SasVerificationTransaction { +) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), SasVerificationTransaction { companion object { const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" From 39e746413a1571054300e79ebced62db541a24f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 17:07:52 +0100 Subject: [PATCH 13/22] QrCode: WIP --- .../api/session/crypto/sas/CancelCode.kt | 4 +- .../crypto/sas/QRVerificationTransaction.kt | 7 - .../sas/QrCodeVerificationTransaction.kt | 30 ++++ .../session/crypto/sas/VerificationService.kt | 5 +- .../crypto/sas/VerificationTransaction.kt | 2 + .../model/rest/VerificationMethodValues.kt | 8 - .../DefaultVerificationService.kt | 77 +++++++-- .../PendingVerificationRequest.kt | 7 +- .../DefaultQrCodeVerificationTransaction.kt | 156 ++++++++++++++++++ .../VerificationBottomSheetViewModel.kt | 4 +- .../VerificationChooseMethodViewModel.kt | 19 ++- .../home/room/detail/RoomDetailViewModel.kt | 5 +- 12 files changed, 282 insertions(+), 42 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QRVerificationTransaction.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt index 149d099b66..a8ad1de421 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt @@ -27,7 +27,9 @@ enum class CancelCode(val value: String, val humanReadable: String) { UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"), InvalidMessage("m.invalid_message", "an invalid message was received"), MismatchedKeys("m.key_mismatch", "Key mismatch"), - UserMismatchError("m.user_error", "User mismatch") + UserError("m.user_error", "User mismatch"), + UserMismatchError("m.user_mismatch", "Key mismatch"), + QrCodeInvalid("m.qr_code.invalid", "User mismatch") } fun safeValueOf(code: String?): CancelCode { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QRVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QRVerificationTransaction.kt deleted file mode 100644 index 4464e6a0e4..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QRVerificationTransaction.kt +++ /dev/null @@ -1,7 +0,0 @@ -package im.vector.matrix.android.api.session.crypto.sas - -interface QRVerificationTransaction : VerificationTransaction { - - fun userHasScannedRemoteQrCode(scannedData: String) - -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt new file mode 100644 index 0000000000..9e90681fc0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 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.api.session.crypto.sas + +interface QrCodeVerificationTransaction : VerificationTransaction { + + /** + * To use to display a qr code, for the other user to scan it + */ + val qrCodeText: String? + + /** + * Call when you have scan the other user QR code + */ + fun userHasScannedRemoteQrCode(otherQrCodeText: String): CancelCode? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt index e1c0a1d455..aafc64d267 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationService.kt @@ -67,7 +67,10 @@ interface VerificationService { /** * Returns false if the request is unknown */ - fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean + fun readyPendingVerificationInDMs(methods: List, + otherUserId: String, + roomId: String, + transactionId: String): Boolean // fun transactionUpdated(tx: SasVerificationTransaction) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt index 793c67ae33..16bb37e180 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTransaction.kt @@ -24,6 +24,8 @@ interface VerificationTransaction { val transactionId: String val otherUserId: String var otherDeviceId: String? + + // TODO Not used. Remove? val isIncoming: Boolean /** * User wants to cancel the transaction diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt index e3659bbde2..643ac5a495 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt @@ -33,11 +33,3 @@ internal fun VerificationMethod.toValue(): String { VerificationMethod.QR_CODE_SHOW -> VERIFICATION_METHOD_QR_CODE_SHOW } } - -internal val supportedVerificationMethods = - listOf( - VERIFICATION_METHOD_SAS, - VERIFICATION_METHOD_QR_CODE_SHOW, - VERIFICATION_METHOD_QR_CODE_SCAN, - VERIFICATION_METHOD_RECIPROCATE - ) 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 1bf35b2108..b7d44aace2 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 @@ -55,11 +55,12 @@ 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.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 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.model.rest.supportedVerificationMethods import im.vector.matrix.android.internal.crypto.model.rest.toValue import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.verification.qrcode.DefaultQrCodeVerificationTransaction import im.vector.matrix.android.internal.crypto.verification.qrcode.QrCodeData import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret import im.vector.matrix.android.internal.crypto.verification.qrcode.toUrl @@ -413,6 +414,16 @@ internal class DefaultVerificationService @Inject constructor( autoAccept).also { txConfigure(it) } addTransaction(tx) tx.acceptVerificationEvent(otherUserId, startReq) + } else if (startReq.method == VERIFICATION_METHOD_RECIPROCATE) { + // Other user has scanned my QR code + val pendingTransaction = getExistingTransaction(otherUserId, startReq.transactionID!!) + + if (pendingTransaction != null && pendingTransaction is DefaultQrCodeVerificationTransaction) { + pendingTransaction.onStartReceived(startReq) + } else { + Timber.w("## SAS onStartRequestReceived - unknown transaction ${startReq.transactionID}") + return CancelCode.UnknownTransaction + } } else { Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") return CancelCode.UnknownMethod @@ -606,7 +617,7 @@ internal class DefaultVerificationService @Inject constructor( return } - handleReadyReceived(event.senderId, readyReq) + handleReadyReceived(event.senderId, event.roomId!!, readyReq) } private fun onRoomDoneReceived(event: Event) { @@ -651,7 +662,7 @@ internal class DefaultVerificationService @Inject constructor( } } - private fun handleReadyReceived(senderId: String, readyReq: VerificationInfoReady) { + private fun handleReadyReceived(senderId: String, roomId: String, readyReq: VerificationInfoReady) { val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID } if (existingRequest == null) { Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}") @@ -710,10 +721,26 @@ internal class DefaultVerificationService @Inject constructor( ).toUrl() } + if (qrCodeText != null) { + // Create the pending transaction + val tx = DefaultQrCodeVerificationTransaction( + readyReq.transactionID!!, + senderId, + readyReq.fromDevice, + crossSigningService, + cryptoStore, + myGeneratedSharedSecret!!, + qrCodeText, + deviceId ?: "", + false) + + tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) + + addTransaction(tx) + } + updatePendingRequest(existingRequest.copy( - readyInfo = readyReq, - myGeneratedSecret = myGeneratedSharedSecret, - qrCodeText = qrCodeText + readyInfo = readyReq )) } @@ -916,23 +943,26 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean { + override fun readyPendingVerificationInDMs(methods: List, + otherUserId: String, + roomId: String, + transactionId: String): Boolean { Timber.v("## SAS readyPendingVerificationInDMs $otherUserId room:$roomId 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 = verificationTransportRoomMessageFactory.createTransport(roomId, null) - // TODO We should not use supportedVerificationMethods here, because it depends on the client implementation - val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList() + val computedMethods = computeReadyMethods(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? 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 ?: "", methods) - transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg, + val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods) + transport.sendToOther(EventType.KEY_VERIFICATION_READY, + readyMsg, VerificationTxState.None, CancelCode.User, null // TODO handle error? @@ -946,6 +976,31 @@ internal class DefaultVerificationService @Inject constructor( } } + private fun computeReadyMethods(otherUserMethods: List?, methods: List): List { + if (otherUserMethods.isNullOrEmpty()) { + return emptyList() + } + + val result = mutableSetOf() + + if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in methods) { + // Other can do SAS and so do I + result + 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 + } + + return result.toList() + } + /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index 4548313ba1..cc0df09adb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -38,12 +38,7 @@ data class PendingVerificationRequest( val readyInfo: VerificationInfoReady? = null, val cancelConclusion: CancelCode? = null, val isSuccessful: Boolean = false, - val handledByOtherSession: Boolean = false, - // TODO Move to OutgoingQrCodeTransaction - val myGeneratedSecret: String? = null, - // TODO Move to OutgoingQrCodeTransaction - val qrCodeText: String? = null - + val handledByOtherSession: Boolean = false ) { val isReady: Boolean = readyInfo != null val isSent: Boolean = transactionId != null 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 new file mode 100644 index 0000000000..a85c68ca83 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2020 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.verification.qrcode + +import im.vector.matrix.android.api.MatrixCallback +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.VerificationTxState +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction +import im.vector.matrix.android.internal.crypto.verification.VerificationInfo +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart +import timber.log.Timber +import kotlin.properties.Delegates + +internal class DefaultQrCodeVerificationTransaction( + override val transactionId: String, + override val otherUserId: String, + override var otherDeviceId: String?, + private val crossSigningService: CrossSigningService, + private val cryptoStore: IMXCryptoStore, + private val myGeneratedSecret: String, + override val qrCodeText: String, + val deviceId: String, + override val isIncoming: Boolean +) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction { + + override var cancelledReason: CancelCode? = null + + override var state by Delegates.observable(VerificationTxState.None) { _, _, _ -> + listeners.forEach { + try { + it.transactionUpdated(this) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + + override fun userHasScannedRemoteQrCode(otherQrCodeText: String): CancelCode? { + val qrCodeData = otherQrCodeText.toQrCodeData() ?: return CancelCode.QrCodeInvalid + + // Perform some checks + if (qrCodeData.action != QrCodeData.ACTION_VERIFY) { + return CancelCode.QrCodeInvalid + } + + if (qrCodeData.userId != otherUserId) { + return CancelCode.UserMismatchError + } + + if (qrCodeData.requestEventId != transactionId) { + return CancelCode.QrCodeInvalid + } + + // check master key + if (qrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) { + return CancelCode.MismatchedKeys + } + + val otherDevices = cryptoStore.getUserDevices(otherUserId) + qrCodeData.keys.keys.forEach { key -> + Timber.w("Checking key $key") + val fingerprint = otherDevices?.get(key)?.fingerprint() + if (fingerprint != null && fingerprint != qrCodeData.keys[key]) { + return CancelCode.MismatchedKeys + } + } + + // All checks are correct + + // Trust the other user + trust() + state = VerificationTxState.Verified + + // Send the shared secret so that sender can trust me + // qrCodeData.sharedSecret will be used to send the start request + start(qrCodeData.sharedSecret) + + return null + } + + fun start(remoteSecret: String) { + if (state != VerificationTxState.None) { + Timber.e("## SAS O: start verification from invalid state") + // should I cancel?? + throw IllegalStateException("Interactive Key verification already started") + } + + val startMessage = transport.createStartForQrCode( + deviceId, + transactionId, + remoteSecret + ) + + transport.sendToOther( + EventType.KEY_VERIFICATION_START, + startMessage, + VerificationTxState.Started, + CancelCode.User, + null + ) + } + + override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) { + } + + override fun cancel() { + cancel(CancelCode.User) + } + + override fun cancel(code: CancelCode) { + cancelledReason = code + state = VerificationTxState.Cancelled + transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code) + } + + override fun isToDeviceTransport() = false + + // Remote user has scanned our QR code. check that the secret matched, so we can trust him + fun onStartReceived(startReq: VerificationInfoStart) { + if (startReq.sharedSecret == myGeneratedSecret) { + // Ok, we can trust the other user + trust() + } else { + // Display a warning + cancelledReason = CancelCode.QrCodeInvalid + state = VerificationTxState.OnCancelled + } + } + + private fun trust() { + crossSigningService.trustUser(otherUserId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") + } + }) + } +} 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 5a680556ca..6b67e8f477 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 @@ -31,7 +31,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.sas.CancelCode -import im.vector.matrix.android.api.session.crypto.sas.QRVerificationTransaction +import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.sas.VerificationService @@ -166,7 +166,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } is VerificationAction.RemoteQrCodeScanned -> { val existingTransaction = session.getVerificationService() - .getExistingTransaction(action.otherUserId, action.transactionId) as? QRVerificationTransaction + .getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction existingTransaction ?.userHasScannedRemoteQrCode(action.scannedData) } 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 e4b8f75a8c..852488a3ed 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 @@ -22,8 +22,9 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject 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.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 import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.di.HasScreenInjector @@ -46,9 +47,19 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( private val session: Session ) : VectorViewModel(initialState), VerificationService.VerificationListener { - override fun transactionCreated(tx: VerificationTransaction) {} + override fun transactionCreated(tx: VerificationTransaction) { + transactionUpdated(tx) + } - override fun transactionUpdated(tx: VerificationTransaction) {} + override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> + if (tx.transactionId == state.transactionId && tx is QrCodeVerificationTransaction) { + setState { + copy( + qrCodeText = tx.qrCodeText + ) + } + } + } override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> val pvr = session.getVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) @@ -57,7 +68,6 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( copy( otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false, otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false, - qrCodeText = pvr?.qrCodeText, SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false ) } @@ -92,7 +102,6 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( transactionId = args.verificationId ?: "", otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false, otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false, - qrCodeText = pvr?.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 695dfa8d33..b3b31aa00e 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 @@ -809,7 +809,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") - if (session.getVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, + if (session.getVerificationService().readyPendingVerificationInDMs( + supportedVerificationMethods, + action.otherUserId, + room.roomId, action.transactionId)) { _requestLiveData.postValue(LiveEvent(Success(action))) } From 86592169551fdd85d919d38d26e80891869d6245 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 19:41:40 +0100 Subject: [PATCH 14/22] QrCode: WIP --- .../DefaultVerificationService.kt | 170 +++++++++++------- .../DefaultQrCodeVerificationTransaction.kt | 5 + .../verification/VerificationBottomSheet.kt | 21 ++- .../VerificationBottomSheetViewModel.kt | 41 +++-- .../VerificationChooseMethodViewModel.kt | 4 + .../home/room/detail/RoomDetailViewModel.kt | 2 + 6 files changed, 167 insertions(+), 76 deletions(-) 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 } } From d8d465f70bea0339bf134064fe472b8ae921c092 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Jan 2020 19:57:40 +0100 Subject: [PATCH 15/22] QrCode: WIP --- .../crypto/verification/DefaultVerificationService.kt | 3 ++- .../qrcode/DefaultQrCodeVerificationTransaction.kt | 3 ++- .../crypto/verification/VerificationBottomSheetViewModel.kt | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) 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 b076ec6b7f..0529b57173 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 @@ -687,6 +687,7 @@ internal class DefaultVerificationService @Inject constructor( cryptoStore, qrCodeData.sharedSecret, qrCodeData.toUrl(), + userId, deviceId ?: "", false) @@ -1011,7 +1012,6 @@ internal class DefaultVerificationService @Inject constructor( 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, @@ -1021,6 +1021,7 @@ internal class DefaultVerificationService @Inject constructor( cryptoStore, qrCodeData.sharedSecret, qrCodeData.toUrl(), + userId, deviceId ?: "", false) 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 a38ec10afb..27243bc0e9 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 @@ -38,6 +38,7 @@ internal class DefaultQrCodeVerificationTransaction( private val cryptoStore: IMXCryptoStore, private val myGeneratedSecret: String, override val qrCodeText: String, + val userId: String, val deviceId: String, override val isIncoming: Boolean ) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction { @@ -71,7 +72,7 @@ internal class DefaultQrCodeVerificationTransaction( } // check master key - if (qrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) { + if (qrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { return CancelCode.MismatchedKeys } 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 d29b7b3774..096cb31948 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 @@ -46,6 +46,7 @@ import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.LiveEvent +import timber.log.Timber data class VerificationBottomSheetViewState( val otherUserMxItem: MatrixItem? = null, @@ -175,6 +176,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini .getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction existingTransaction ?.userHasScannedRemoteQrCode(action.scannedData) + ?.let { cancelCode -> + // Something went wrong + Timber.w("## Something is not right: $cancelCode") + // TODO + } } is VerificationAction.SASMatchAction -> { (session.getVerificationService() From f46023e84c862af0caa885ed7c377fab51479ff8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 11:20:40 +0100 Subject: [PATCH 16/22] QrCode: WIP --- .../sas/QrCodeVerificationTransaction.kt | 2 +- .../session/crypto/sas/VerificationTxState.kt | 2 + .../DefaultVerificationService.kt | 45 +++---- .../SASDefaultVerificationTransaction.kt | 2 +- .../DefaultQrCodeVerificationTransaction.kt | 115 ++++++++++++------ .../VerificationBottomSheetViewModel.kt | 2 +- 6 files changed, 108 insertions(+), 60 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt index 9e90681fc0..b2b6cfdd39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/QrCodeVerificationTransaction.kt @@ -26,5 +26,5 @@ interface QrCodeVerificationTransaction : VerificationTransaction { /** * Call when you have scan the other user QR code */ - fun userHasScannedRemoteQrCode(otherQrCodeText: String): CancelCode? + fun userHasScannedOtherQrCode(otherQrCodeText: String) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTxState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTxState.kt index c4e85302de..8d4ebb5549 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTxState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationTxState.kt @@ -44,6 +44,8 @@ enum class VerificationTxState { Verified, // Global: The verification has been cancelled (by me or other), see cancelReason for details + // When I do the cancel Cancelled, + // When the other user do a cancel OnCancelled } 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 0529b57173..14918611ab 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 @@ -64,7 +64,6 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.verification.qrcode.DefaultQrCodeVerificationTransaction import im.vector.matrix.android.internal.crypto.verification.qrcode.QrCodeData import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret -import im.vector.matrix.android.internal.crypto.verification.qrcode.toUrl import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.SessionScope @@ -677,16 +676,16 @@ internal class DefaultVerificationService @Inject constructor( createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId) } - if (qrCodeData != null) { + if (readyReq.methods?.orEmpty().orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) { // Create the pending transaction val tx = DefaultQrCodeVerificationTransaction( + setDeviceVerificationAction, readyReq.transactionID!!, senderId, readyReq.fromDevice, crossSigningService, cryptoStore, - qrCodeData.sharedSecret, - qrCodeData.toUrl(), + qrCodeData, userId, deviceId ?: "", false) @@ -1003,7 +1002,7 @@ internal class DefaultVerificationService @Inject constructor( } 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 + // Other user wants to verify using QR code. Cross-signing has to be setup val qrCodeData = createQrCodeData(transactionId, otherUserId) if (qrCodeData != null) { @@ -1011,23 +1010,6 @@ internal class DefaultVerificationService @Inject constructor( // Other can Scan and I can show QR code result.add(VERIFICATION_METHOD_QR_CODE_SHOW) result.add(VERIFICATION_METHOD_RECIPROCATE) - - // Create the pending transaction - val tx = DefaultQrCodeVerificationTransaction( - transactionId, - otherUserId, - otherDeviceId, - crossSigningService, - cryptoStore, - qrCodeData.sharedSecret, - qrCodeData.toUrl(), - userId, - 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 @@ -1035,6 +1017,25 @@ internal class DefaultVerificationService @Inject constructor( result.add(VERIFICATION_METHOD_RECIPROCATE) } } + + if (VERIFICATION_METHOD_RECIPROCATE in result) { + // Create the pending transaction + val tx = DefaultQrCodeVerificationTransaction( + setDeviceVerificationAction, + transactionId, + otherUserId, + otherDeviceId, + crossSigningService, + cryptoStore, + qrCodeData, + userId, + deviceId ?: "", + false) + + tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) + + addTransaction(tx) + } } return result.toList() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index d53703f73a..880fef0662 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -313,7 +313,7 @@ internal abstract class SASDefaultVerificationTransaction( if (otherUserId == userId) { // If me it's reasonable to sign and upload the device signature - // Notice that i might not have the private keys, so may ot be able to do it + // Notice that i might not have the private keys, so may not be able to do it crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { override fun onFailure(failure: Throwable) { Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId") 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 27243bc0e9..d791a2c257 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 @@ -22,6 +22,8 @@ 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.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction +import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction @@ -31,13 +33,14 @@ import timber.log.Timber import kotlin.properties.Delegates internal class DefaultQrCodeVerificationTransaction( + private val setDeviceVerificationAction: SetDeviceVerificationAction, override val transactionId: String, override val otherUserId: String, override var otherDeviceId: String?, private val crossSigningService: CrossSigningService, private val cryptoStore: IMXCryptoStore, - private val myGeneratedSecret: String, - override val qrCodeText: String, + // Not null only if other user is able to scan QR code + private val qrCodeData: QrCodeData?, val userId: String, val deviceId: String, override val isIncoming: Boolean @@ -45,6 +48,9 @@ internal class DefaultQrCodeVerificationTransaction( override var cancelledReason: CancelCode? = null + override val qrCodeText: String? + get() = qrCodeData?.toUrl() + override var state by Delegates.observable(VerificationTxState.None) { _, _, _ -> listeners.forEach { try { @@ -55,47 +61,56 @@ internal class DefaultQrCodeVerificationTransaction( } } - override fun userHasScannedRemoteQrCode(otherQrCodeText: String): CancelCode? { - val qrCodeData = otherQrCodeText.toQrCodeData() ?: return CancelCode.QrCodeInvalid + override fun userHasScannedOtherQrCode(otherQrCodeText: String) { + val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run { + cancel(CancelCode.QrCodeInvalid) + return + } // Perform some checks - if (qrCodeData.action != QrCodeData.ACTION_VERIFY) { - return CancelCode.QrCodeInvalid + if (otherQrCodeData.action != QrCodeData.ACTION_VERIFY) { + cancel(CancelCode.QrCodeInvalid) + return } - if (qrCodeData.userId != otherUserId) { - return CancelCode.UserMismatchError + if (otherQrCodeData.userId != otherUserId) { + cancel(CancelCode.UserMismatchError) + return } - if (qrCodeData.requestEventId != transactionId) { - return CancelCode.QrCodeInvalid + if (otherQrCodeData.requestEventId != transactionId) { + cancel(CancelCode.QrCodeInvalid) + return } // check master key - if (qrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { - return CancelCode.MismatchedKeys + if (otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { + cancel(CancelCode.MismatchedKeys) + return } + val verifiedDeviceIds = mutableListOf() + val otherDevices = cryptoStore.getUserDevices(otherUserId) - qrCodeData.keys.keys.forEach { key -> + otherQrCodeData.keys.keys.forEach { key -> Timber.w("Checking key $key") val fingerprint = otherDevices?.get(key)?.fingerprint() - if (fingerprint != null && fingerprint != qrCodeData.keys[key]) { - return CancelCode.MismatchedKeys + if (fingerprint != null && fingerprint != otherQrCodeData.keys[key]) { + cancel(CancelCode.MismatchedKeys) + return + } else { + // Store the deviceId to verify after + verifiedDeviceIds.add(key) } } // All checks are correct - - // Trust the other user - trust() - state = VerificationTxState.Verified - // Send the shared secret so that sender can trust me // qrCodeData.sharedSecret will be used to send the start request - start(qrCodeData.sharedSecret) + start(otherQrCodeData.sharedSecret) - return null + // Trust the other user + trust(verifiedDeviceIds) } fun start(remoteSecret: String) { @@ -135,28 +150,58 @@ internal class DefaultQrCodeVerificationTransaction( override fun isToDeviceTransport() = false - // Remote user has scanned our QR code. check that the secret matched, so we can trust him + // Other user has scanned our QR code. check that the secret matched, so we can trust him fun onStartReceived(startReq: VerificationInfoStart) { - if (startReq.sharedSecret == myGeneratedSecret) { + if (qrCodeData == null) { + // Should not happen + cancel(CancelCode.UnexpectedMessage) + return + } + + if (startReq.sharedSecret == qrCodeData.sharedSecret) { // Ok, we can trust the other user - trust() + // We can only trust the master key in this case + trust(listOf(qrCodeData.otherUserKey)) } else { // Display a warning - cancelledReason = CancelCode.QrCodeInvalid - state = VerificationTxState.OnCancelled + cancel(CancelCode.QrCodeInvalid) } } - private fun trust() { - crossSigningService.trustUser(otherUserId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") - } - }) + private fun trust(verifiedDeviceIds: List) { + // If not me sign his MSK and upload the signature + if (otherUserId != userId) { + // we should trust this master key + // And check verification MSK -> SSK? + crossSigningService.trustUser(otherUserId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") + } + }) + } - // TODO Sign devices + if (otherUserId == userId) { + // If me it's reasonable to sign and upload the device signature + // Notice that i might not have the private keys, so may not be able to do it + crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId") + } + }) + } + // TODO what if the otherDevice is not in this list? and should we + verifiedDeviceIds.forEach { + setDeviceVerified(otherUserId, it) + } + transport.done(transactionId) + state = VerificationTxState.Verified } - // TODO Send the done event + private fun setDeviceVerified(userId: String, deviceId: String) { + // TODO should not override cross sign status + setDeviceVerificationAction.handle(DeviceTrustLevel(false, true), + userId, + deviceId) + } } 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 096cb31948..5472b1df65 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 @@ -175,7 +175,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val existingTransaction = session.getVerificationService() .getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction existingTransaction - ?.userHasScannedRemoteQrCode(action.scannedData) + ?.userHasScannedOtherQrCode(action.scannedData) ?.let { cancelCode -> // Something went wrong Timber.w("## Something is not right: $cancelCode") From 69ab5e43d5789b06acf9df1cd148f965e55ad893 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 11:48:36 +0100 Subject: [PATCH 17/22] QrCode: WIP --- .../matrix/android/api/session/crypto/sas/CancelCode.kt | 6 +++--- .../qrcode/DefaultQrCodeVerificationTransaction.kt | 2 +- .../features/crypto/verification/VerificationBottomSheet.kt | 3 ++- .../conclusion/VerificationConclusionViewModel.kt | 2 ++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt index a8ad1de421..79448de83f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/CancelCode.kt @@ -27,9 +27,9 @@ enum class CancelCode(val value: String, val humanReadable: String) { UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"), InvalidMessage("m.invalid_message", "an invalid message was received"), MismatchedKeys("m.key_mismatch", "Key mismatch"), - UserError("m.user_error", "User mismatch"), - UserMismatchError("m.user_mismatch", "Key mismatch"), - QrCodeInvalid("m.qr_code.invalid", "User mismatch") + UserError("m.user_error", "User error"), + MismatchedUser("m.user_mismatch", "User mismatch"), + QrCodeInvalid("m.qr_code.invalid", "Invalid QR code") } fun safeValueOf(code: String?): CancelCode { 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 d791a2c257..5ef2a787a5 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 @@ -74,7 +74,7 @@ internal class DefaultQrCodeVerificationTransaction( } if (otherQrCodeData.userId != otherUserId) { - cancel(CancelCode.UserMismatchError) + cancel(CancelCode.MismatchedUser) return } 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 b83e9b696d..702e79d451 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 @@ -156,11 +156,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { it.qrTransactionState == VerificationTxState.Verified, it.cancelCode?.value)) }) + return@withState } else -> Unit } - // At this point there is no transaction for this request + // At this point there is no SAS transaction for this request // Transaction has not yet started if (it.pendingRequest.invoke()?.cancelConclusion != null) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index 9cf5c6466b..d2239e779f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -43,6 +43,8 @@ class VerificationConclusionViewModel(initialState: VerificationConclusionViewSt val args = viewModelContext.args() return when (safeValueOf(args.cancelReason)) { + CancelCode.QrCodeInvalid, + CancelCode.MismatchedUser, CancelCode.MismatchedSas, CancelCode.MismatchedCommitment, CancelCode.MismatchedKeys -> { From 20c7e4c3ad35efd34a0488bcfcda0e6fab9ee5b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 12:05:06 +0100 Subject: [PATCH 18/22] QrCode: improve UI --- .../java/im/vector/riotx/core/qrcode/QrCode.kt | 2 -- .../riotx/core/ui/views/QrCodeImageView.kt | 15 ++++++++++----- .../epoxy/BottomSheetVerificationQrCodeItem.kt | 15 ++------------- .../res/layout/item_verification_qr_code.xml | 18 +++++++++++++----- vector/src/main/res/values/strings_riotX.xml | 3 ++- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt b/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt index 2287d4ab43..110427a3ec 100644 --- a/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt +++ b/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt @@ -34,8 +34,6 @@ fun String.toBitMatrix(size: Int): BitMatrix { fun BitMatrix.toBitmap(@ColorInt backgroundColor: Int = Color.WHITE, @ColorInt foregroundColor: Int = Color.BLACK): Bitmap { - val height: Int = height - val width: Int = width val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) for (x in 0 until width) { for (y in 0 until height) { diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/QrCodeImageView.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/QrCodeImageView.kt index e49af7f1d9..7cf54ae588 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/QrCodeImageView.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/QrCodeImageView.kt @@ -17,6 +17,7 @@ package im.vector.riotx.core.ui.views import android.content.Context +import android.graphics.Color import android.graphics.drawable.AnimationDrawable import android.graphics.drawable.BitmapDrawable import android.util.AttributeSet @@ -32,6 +33,10 @@ class QrCodeImageView @JvmOverloads constructor( private var data: String? = null private var animate = false + init { + setBackgroundColor(Color.WHITE) + } + fun setData(data: String, animate: Boolean) { this.data = data this.animate = animate @@ -46,24 +51,24 @@ class QrCodeImageView @JvmOverloads constructor( private fun render() { data - ?.takeIf { width > 0 } + ?.takeIf { height > 0 } ?.let { if (animate) { // NOT SUPPORTED YET val anim = createAnimation(it) // NOT SUPPORTED YET setImageDrawable(anim) // NOT SUPPORTED YET anim.start() - // NOT SUPPORTED YET setImageDrawable(BitmapDrawable(resources, it.toBitMatrix(width).toBitmap())) - val bitmap = it.toBitMatrix(width).toBitmap() + // NOT SUPPORTED YET setImageDrawable(BitmapDrawable(resources, it.toBitMatrix(height).toBitmap())) + val bitmap = it.toBitMatrix(height).toBitmap() post { setImageBitmap(bitmap) } } else { - val bitmap = it.toBitMatrix(width).toBitmap() + val bitmap = it.toBitMatrix(height).toBitmap() post { setImageBitmap(bitmap) } } } } private fun createAnimation(data: String): AnimationDrawable { - val finalQr = data.toBitMatrix(width) + val finalQr = data.toBitMatrix(height) val list = mutableListOf(finalQr) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt index e6f51784b1..dc126bc460 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt @@ -16,7 +16,6 @@ */ package im.vector.riotx.features.crypto.verification.epoxy -import androidx.core.view.ViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -25,7 +24,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.ui.views.QrCodeImageView /** - * A action for bottom sheet. + * An Epoxy item displaying a QR code */ @EpoxyModelClass(layout = R.layout.item_verification_qr_code) abstract class BottomSheetVerificationQrCodeItem : VectorEpoxyModel() { @@ -36,21 +35,11 @@ abstract class BottomSheetVerificationQrCodeItem : VectorEpoxyModel(R.id.itemVerificationBigImage) + val qsrCodeImage by bind(R.id.itemVerificationQrCodeImage) } } diff --git a/vector/src/main/res/layout/item_verification_qr_code.xml b/vector/src/main/res/layout/item_verification_qr_code.xml index 2407d2754d..6a16315185 100644 --- a/vector/src/main/res/layout/item_verification_qr_code.xml +++ b/vector/src/main/res/layout/item_verification_qr_code.xml @@ -1,8 +1,16 @@ - + android:layout_height="wrap_content" + android:layout_margin="8dp"> + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 3de0cdbf72..628493397f 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -100,7 +100,6 @@ Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Enable encryption - For extra security, verify %s by checking a one-time code. For maximum security, do this in person. @@ -146,4 +145,6 @@ Initialize CrossSigning Reset Keys + + QR code From 9c829e62e61479aab195c34f0f2446b444183dec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 15:10:40 +0100 Subject: [PATCH 19/22] QrCode: WIP --- .../DefaultCrossSigningService.kt | 3 +- .../DefaultVerificationService.kt | 86 +++++++++++-------- .../SASDefaultVerificationTransaction.kt | 5 +- .../DefaultQrCodeVerificationTransaction.kt | 65 ++++++++++---- .../android/internal/util/StringUtils.kt | 2 + 5 files changed, 108 insertions(+), 53 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index ca281ac7ef..9df145b0ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -40,6 +40,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import im.vector.matrix.android.internal.util.withoutPrefix import kotlinx.coroutines.CoroutineScope import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility @@ -384,7 +385,7 @@ internal class DefaultCrossSigningService @Inject constructor( } else { // Maybe it's signed by a locally trusted device? myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> - val potentialDeviceId = if (key.startsWith("ed25519:")) key.substring("ed25519:".length) else key + val potentialDeviceId = key.withoutPrefix("ed25519:") val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId) if (potentialDevice != null && potentialDevice.isVerified) { // Check signature validity? 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 14918611ab..2fa32b1b98 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 @@ -24,6 +24,7 @@ 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.SasVerificationTransaction 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 @@ -372,30 +373,46 @@ internal class DefaultVerificationService @Inject constructor( } } + /** + * Return a CancelCode to make the caller cancel the verification. Else return null + */ private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? { - Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}") + Timber.d("## SAS onStartRequestReceived ${startReq.transactionID}") if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) { - Timber.v("## SAS onStartRequestReceived $startReq") val tid = startReq.transactionID!! val existing = getExistingTransaction(otherUserId, tid) - val existingTxs = getExistingTransactionsForUser(otherUserId) - if (existing != null) { - // should cancel both! - Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}") - existing.cancel(CancelCode.UnexpectedMessage) - return CancelCode.UnexpectedMessage - // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) - } else if (existingTxs?.isEmpty() == false) { - Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}") - // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time. - existingTxs.forEach { - it.cancel(CancelCode.UnexpectedMessage) - } - return CancelCode.UnexpectedMessage - // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) - } else { - // Ok we can create - if (startReq.method == VERIFICATION_METHOD_SAS) { + + when (startReq.method) { + VERIFICATION_METHOD_SAS -> { + when (existing) { + is SasVerificationTransaction -> { + // should cancel both! + Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionID}") + existing.cancel(CancelCode.UnexpectedMessage) + // Already cancelled, so return null + return null + } + is QrCodeVerificationTransaction -> { + // Nothing to do? + } + null -> { + getExistingTransactionsForUser(otherUserId) + ?.filterIsInstance(SasVerificationTransaction::class.java) + ?.takeIf { it.isNotEmpty() } + ?.also { + // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time. + Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID}") + } + ?.forEach { + it.cancel(CancelCode.UnexpectedMessage) + } + ?.also { + return CancelCode.UnexpectedMessage + } + } + } + + // Ok we can create a SAS transaction Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") // If there is a corresponding request, we can auto accept // as we are the one requesting in first place (or we accepted the request) @@ -414,40 +431,39 @@ internal class DefaultVerificationService @Inject constructor( autoAccept).also { txConfigure(it) } addTransaction(tx) tx.acceptVerificationEvent(otherUserId, startReq) - } else if (startReq.method == VERIFICATION_METHOD_RECIPROCATE) { + return null + } + VERIFICATION_METHOD_RECIPROCATE -> { // Other user has scanned my QR code - val pendingTransaction = getExistingTransaction(otherUserId, startReq.transactionID!!) - - if (pendingTransaction != null && pendingTransaction is DefaultQrCodeVerificationTransaction) { - pendingTransaction.onStartReceived(startReq) + if (existing is DefaultQrCodeVerificationTransaction) { + existing.onStartReceived(startReq) + return null } else { - Timber.w("## SAS onStartRequestReceived - unknown transaction ${startReq.transactionID}") - return CancelCode.UnknownTransaction + Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionID}") + return CancelCode.UnexpectedMessage } - } else { + } + else ->{ Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") return CancelCode.UnknownMethod - // cancelTransaction(tid, otherUserId, startReq.fromDevice -// ?: event.getSenderKey()!!, CancelCode.UnknownMethod) } } } else { return CancelCode.UnexpectedMessage -// cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } - return null } + // TODO Refacto: It could just return a boolean private suspend fun checkKeysAreDownloaded(otherUserId: String, - fromDevice: String): MXUsersDevicesMap? { + otherDeviceId: String): MXUsersDevicesMap? { return try { var keys = deviceListManager.downloadKeys(listOf(otherUserId), false) - if (keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true) { + if (keys.getUserDeviceIds(otherUserId)?.contains(otherDeviceId) == true) { return keys } else { // force download keys = deviceListManager.downloadKeys(listOf(otherUserId), true) - return keys.takeIf { keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true } + return keys.takeIf { keys.getUserDeviceIds(otherUserId)?.contains(otherDeviceId) == true } } } catch (e: Exception) { null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 880fef0662..a879cdb797 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.extensions.toUnsignedInt +import im.vector.matrix.android.internal.util.withoutPrefix import org.matrix.olm.OlmSAS import org.matrix.olm.OlmUtility import timber.log.Timber @@ -253,7 +254,7 @@ internal abstract class SASDefaultVerificationTransaction( // cannot be empty because it has been validated theirMac!!.mac!!.keys.forEach { - val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it + val keyIDNoPrefix = it.withoutPrefix("ed25519:") val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() if (otherDeviceKey == null) { Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify") @@ -276,7 +277,7 @@ internal abstract class SASDefaultVerificationTransaction( if (otherCrossSigningMasterKeyPublic != null) { // Did the user signed his master key theirMac!!.mac!!.keys.forEach { - val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it + val keyIDNoPrefix = it.withoutPrefix("ed25519:") if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) { // Check the signature val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it) 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 5ef2a787a5..6f05571b25 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 @@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction import im.vector.matrix.android.internal.crypto.verification.VerificationInfo import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart +import im.vector.matrix.android.internal.util.withoutPrefix import timber.log.Timber import kotlin.properties.Delegates @@ -89,19 +90,53 @@ internal class DefaultQrCodeVerificationTransaction( return } - val verifiedDeviceIds = mutableListOf() + val toVerifyDeviceIds = mutableListOf() + var canTrustOtherUserMasterKey = false val otherDevices = cryptoStore.getUserDevices(otherUserId) otherQrCodeData.keys.keys.forEach { key -> Timber.w("Checking key $key") - val fingerprint = otherDevices?.get(key)?.fingerprint() - if (fingerprint != null && fingerprint != otherQrCodeData.keys[key]) { - cancel(CancelCode.MismatchedKeys) - return - } else { - // Store the deviceId to verify after - verifiedDeviceIds.add(key) + + when (val keyNoPrefix = key.withoutPrefix("ed25519:")) { + otherQrCodeData.keys[key] -> { + // Maybe master key? + if (otherQrCodeData.keys[key] == crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) { + canTrustOtherUserMasterKey = true + } else { + cancel(CancelCode.MismatchedKeys) + return + } + } + else -> { + when (val otherDevice = otherDevices?.get(keyNoPrefix)) { + null -> { + // Unknown device, ignore + } + else -> { + when (otherDevice.fingerprint()) { + null -> { + // Ignore + } + otherQrCodeData.keys[key] -> { + // Store the deviceId to verify after + toVerifyDeviceIds.add(key) + } + else -> { + cancel(CancelCode.MismatchedKeys) + return + } + } + } + } + } } + + } + + if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { + // Nothing to verify + cancel(CancelCode.MismatchedKeys) + return } // All checks are correct @@ -110,7 +145,7 @@ internal class DefaultQrCodeVerificationTransaction( start(otherQrCodeData.sharedSecret) // Trust the other user - trust(verifiedDeviceIds) + trust(canTrustOtherUserMasterKey, toVerifyDeviceIds) } fun start(remoteSecret: String) { @@ -161,16 +196,16 @@ internal class DefaultQrCodeVerificationTransaction( if (startReq.sharedSecret == qrCodeData.sharedSecret) { // Ok, we can trust the other user // We can only trust the master key in this case - trust(listOf(qrCodeData.otherUserKey)) + trust(true, emptyList()) } else { // Display a warning - cancel(CancelCode.QrCodeInvalid) + cancel(CancelCode.MismatchedKeys) } } - private fun trust(verifiedDeviceIds: List) { + private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List) { // If not me sign his MSK and upload the signature - if (otherUserId != userId) { + if (otherUserId != userId && canTrustOtherUserMasterKey) { // we should trust this master key // And check verification MSK -> SSK? crossSigningService.trustUser(otherUserId, object : MatrixCallback { @@ -191,7 +226,7 @@ internal class DefaultQrCodeVerificationTransaction( } // TODO what if the otherDevice is not in this list? and should we - verifiedDeviceIds.forEach { + toVerifyDeviceIds.forEach { setDeviceVerified(otherUserId, it) } transport.done(transactionId) @@ -200,7 +235,7 @@ internal class DefaultQrCodeVerificationTransaction( private fun setDeviceVerified(userId: String, deviceId: String) { // TODO should not override cross sign status - setDeviceVerificationAction.handle(DeviceTrustLevel(false, true), + setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), userId, deviceId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt index f19bebe482..b3f240f23d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt @@ -49,3 +49,5 @@ fun convertFromUTF8(s: String): String { s } } + +fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this From e0b3ea7e480cbc85803c99b6080df4bd5b489e71 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 15:42:26 +0100 Subject: [PATCH 20/22] QrCode: WIP --- .../matrix/android/internal/crypto/CryptoConstants.kt | 4 ++++ .../internal/crypto/verification/qrcode/Extensions.kt | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt index c045019cda..a3b0a567fe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt @@ -30,3 +30,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2" * Matrix algorithm value for megolm keys backup. */ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2" + +// TODO Refacto: use this constants everywhere +const val ed25519 = "ed25519" +const val curve25519 = "curve25519" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt index a4efe385bb..36b1a7b86b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt @@ -32,6 +32,15 @@ private const val ENCODING = "utf-8" * &key_=... * &secret= * &other_user_key= + * + * Example: + * https://matrix.to/#/@user:matrix.org? + * request=%24pBeIfm7REDACTEDSQJbgqvi-yYiwmPB8_H_W_O974 + * &action=verify + * &key_VJEDVKUYTQ=DL7LWIw7Qp%2B4AREDACTEDOwy2BjygumSWAGfzaWY + * &key_fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo=fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo + * &secret=AjQqw51Fp6UBuPolZ2FAD5WnXc22ZhJG6iGslrVvIdw%3D + * &other_user_key=WqSVLkBCS%2Fi5NqR%2F%2FymC8T7K9RPxBIuqK8Usl6Y3big * */ fun QrCodeData.toUrl(): String { From b7ecfd997dea0fae7e9e986658dd95460da1dc91 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 15:58:45 +0100 Subject: [PATCH 21/22] Fix compilation issue after rebase --- .../qrcode/DefaultQrCodeVerificationTransaction.kt | 4 ++-- .../roommemberprofile/devices/DeviceListBottomSheet.kt | 2 +- .../devices/DeviceListBottomSheetViewModel.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 6f05571b25..4f4c3e9317 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 @@ -208,7 +208,7 @@ internal class DefaultQrCodeVerificationTransaction( if (otherUserId != userId && canTrustOtherUserMasterKey) { // we should trust this master key // And check verification MSK -> SSK? - crossSigningService.trustUser(otherUserId, object : MatrixCallback { + crossSigningService.trustUser(otherUserId, object : MatrixCallback { override fun onFailure(failure: Throwable) { Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") } @@ -218,7 +218,7 @@ internal class DefaultQrCodeVerificationTransaction( if (otherUserId == userId) { // If me it's reasonable to sign and upload the device signature // Notice that i might not have the private keys, so may not be able to do it - crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { + crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { override fun onFailure(failure: Throwable) { Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId") } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt index f3d26911f4..dc8ed66fb8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -55,7 +55,7 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() { is VerificationAction.StartSASVerification -> { VerificationBottomSheet.withArgs( roomId = null, - otherUserId = action.userID, + otherUserId = action.otherUserId, transactionId = action.pendingRequestTransactionId ).show(requireActivity().supportFragmentManager, "REQPOP") } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index c6fc06ade0..48e8c38dec 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -100,7 +100,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } fun manuallyVerify(device: CryptoDeviceInfo) { - session.getSasVerificationService().beginKeyVerification(VerificationMethod.SAS, deviceID = device.deviceId, userId = userId)?.let { txID -> + session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId)?.let { txID -> _requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID)))) } } From a57393cafa59cb18545929cbe39b8e7079acee69 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 28 Jan 2020 18:09:17 +0100 Subject: [PATCH 22/22] More log + quick fix in settings --- .../qrcode/DefaultQrCodeVerificationTransaction.kt | 9 +++++++-- .../settings/VectorSettingsSecurityPrivacyFragment.kt | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) 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 4f4c3e9317..60216600f0 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 @@ -64,28 +64,33 @@ internal class DefaultQrCodeVerificationTransaction( override fun userHasScannedOtherQrCode(otherQrCodeText: String) { val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run { + Timber.d("## Verification QR: Invalid QR Code Data") cancel(CancelCode.QrCodeInvalid) return } // Perform some checks if (otherQrCodeData.action != QrCodeData.ACTION_VERIFY) { + Timber.d("## Verification QR: Invalid action ${otherQrCodeData.action}") cancel(CancelCode.QrCodeInvalid) return } if (otherQrCodeData.userId != otherUserId) { + Timber.d("## Verification QR: Mismatched user ${otherQrCodeData.userId}") cancel(CancelCode.MismatchedUser) return } if (otherQrCodeData.requestEventId != transactionId) { + Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.requestEventId} expected:$transactionId") cancel(CancelCode.QrCodeInvalid) return } // check master key if (otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { + Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserKey}") cancel(CancelCode.MismatchedKeys) return } @@ -95,7 +100,7 @@ internal class DefaultQrCodeVerificationTransaction( val otherDevices = cryptoStore.getUserDevices(otherUserId) otherQrCodeData.keys.keys.forEach { key -> - Timber.w("Checking key $key") + Timber.w("## Verification QR: Checking key $key") when (val keyNoPrefix = key.withoutPrefix("ed25519:")) { otherQrCodeData.keys[key] -> { @@ -150,7 +155,7 @@ internal class DefaultQrCodeVerificationTransaction( fun start(remoteSecret: String) { if (state != VerificationTxState.None) { - Timber.e("## SAS O: start verification from invalid state") + Timber.e("## Verification QR: start verification from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index bc56351106..e688b6b68e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -429,11 +429,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( // TODO Move to a ViewModel... session.getDevicesList(object : MatrixCallback { override fun onSuccess(data: DevicesListResponse) { - refreshCryptographyPreference(data.devices ?: emptyList()) + if (isAdded) { + refreshCryptographyPreference(data.devices ?: emptyList()) + } } override fun onFailure(failure: Throwable) { - refreshCryptographyPreference(emptyList()) + if (isAdded) { + refreshCryptographyPreference(emptyList()) + } } }) }