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 00ac4a6986..8771c7a303 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 @@ -65,8 +65,9 @@ import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_S 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.QrCodeDataV2 import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret +import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecretV2 import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.SessionScope @@ -788,7 +789,7 @@ internal class DefaultVerificationService @Inject constructor( )) } - private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? { + private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeDataV2? { requestId ?: run { Timber.w("## Unknown requestId") return null @@ -796,17 +797,17 @@ internal class DefaultVerificationService @Inject constructor( return when { userId != otherUserId -> - createQrCodeDataForDistinctUser(requestId, otherUserId, otherDeviceId) + createQrCodeDataForDistinctUser(requestId, otherUserId /*, otherDeviceId*/) crossSigningService.isCrossSigningVerified() -> // This is a self verification and I am the old device (Osborne2) createQrCodeDataForVerifiedDevice(requestId, otherDeviceId) else -> // This is a self verification and I am the new device (Dynabook) - createQrCodeDataForUnVerifiedDevice(requestId, otherDeviceId) + createQrCodeDataForUnVerifiedDevice(requestId/*, otherDeviceId*/) } } - private fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData? { + private fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String /*, otherDeviceId: String?*/): QrCodeDataV2.VerifyingAnotherUser? { val myMasterKey = crossSigningService.getMyCrossSigningKeys() ?.masterKey() ?.unpaddedBase64PublicKey @@ -823,6 +824,7 @@ internal class DefaultVerificationService @Inject constructor( return null } + /* TODO Cleanup val myDeviceId = deviceId ?: run { Timber.w("## Unable to get my deviceId") @@ -839,23 +841,18 @@ internal class DefaultVerificationService @Inject constructor( ?.let { cryptoStore.getUserDevice(userId, otherDeviceId)?.fingerprint() } + */ - return QrCodeData( - userId = userId, - requestId = requestId, - action = QrCodeData.ACTION_VERIFY, - keys = hashMapOf( - myMasterKey to myMasterKey, - myDeviceId to myDeviceKey - ), - sharedSecret = generateSharedSecret(), - otherUserKey = otherUserMasterKey, - otherDeviceKey = otherDeviceKey + return QrCodeDataV2.VerifyingAnotherUser( + transactionId = requestId, + userMasterCrossSigningPublicKey = myMasterKey, + otherUserMasterCrossSigningPublicKey = otherUserMasterKey, + sharedSecret = generateSharedSecretV2() ) } // Create a QR code to display on the old device (Osborne2) - private fun createQrCodeDataForVerifiedDevice(requestId: String, otherDeviceId: String?): QrCodeData? { + private fun createQrCodeDataForVerifiedDevice(requestId: String, otherDeviceId: String?): QrCodeDataV2.SelfVerifyingMasterKeyTrusted? { val myMasterKey = crossSigningService.getMyCrossSigningKeys() ?.masterKey() ?.unpaddedBase64PublicKey @@ -873,6 +870,7 @@ internal class DefaultVerificationService @Inject constructor( return null } + /* TODO Cleanup val myDeviceId = deviceId ?: run { Timber.w("## Unable to get my deviceId") @@ -884,23 +882,18 @@ internal class DefaultVerificationService @Inject constructor( Timber.w("## Unable to get my fingerprint") return null } + */ - return QrCodeData( - userId = userId, - requestId = requestId, - action = QrCodeData.ACTION_VERIFY, - keys = hashMapOf( - myMasterKey to myMasterKey, - myDeviceId to myDeviceKey - ), - sharedSecret = generateSharedSecret(), - otherUserKey = null, - otherDeviceKey = otherDeviceKey + return QrCodeDataV2.SelfVerifyingMasterKeyTrusted( + transactionId = requestId, + userMasterCrossSigningPublicKey = myMasterKey, + otherDeviceKey = otherDeviceKey, + sharedSecret = generateSharedSecretV2() ) } // Create a QR code to display on the new device (Dynabook) - private fun createQrCodeDataForUnVerifiedDevice(requestId: String, otherDeviceId: String?): QrCodeData? { + private fun createQrCodeDataForUnVerifiedDevice(requestId: String/*, otherDeviceId: String?*/): QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted? { val myMasterKey = crossSigningService.getMyCrossSigningKeys() ?.masterKey() ?.unpaddedBase64PublicKey @@ -909,11 +902,13 @@ internal class DefaultVerificationService @Inject constructor( return null } + /* TODO Cleanup val myDeviceId = deviceId ?: run { Timber.w("## Unable to get my deviceId") return null } + */ val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint() ?: run { @@ -921,22 +916,18 @@ internal class DefaultVerificationService @Inject constructor( return null } + /* TODO Cleanup val otherDeviceKey = otherDeviceId ?.let { cryptoStore.getUserDevice(userId, otherDeviceId)?.fingerprint() } + */ - return QrCodeData( - userId = userId, - requestId = requestId, - action = QrCodeData.ACTION_VERIFY, - keys = hashMapOf( - // Note: no master key here - myDeviceId to myDeviceKey - ), - sharedSecret = generateSharedSecret(), - otherUserKey = myMasterKey, - otherDeviceKey = otherDeviceKey + return QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted( + transactionId = requestId, + deviceKey = myDeviceKey, + userMasterCrossSigningPublicKey = myMasterKey, + sharedSecret = generateSharedSecretV2() ) } 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 d1b72f54c6..c6b84c60b0 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 @@ -28,7 +28,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 im.vector.matrix.android.internal.util.exhaustive import timber.log.Timber internal class DefaultQrCodeVerificationTransaction( @@ -39,14 +39,14 @@ internal class DefaultQrCodeVerificationTransaction( private val crossSigningService: CrossSigningService, private val cryptoStore: IMXCryptoStore, // Not null only if other user is able to scan QR code - private val qrCodeData: QrCodeData?, + private val qrCodeData: QrCodeDataV2?, val userId: String, val deviceId: String, override val isIncoming: Boolean ) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction { override val qrCodeText: String? - get() = qrCodeData?.toUrl() + get() = qrCodeData?.toEncodedString() override var state: VerificationTxState = VerificationTxState.None set(newState) { @@ -62,96 +62,77 @@ internal class DefaultQrCodeVerificationTransaction( } override fun userHasScannedOtherQrCode(otherQrCodeText: String) { - val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run { + val otherQrCodeData = otherQrCodeText.toQrCodeDataV2() ?: 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.requestId != transactionId) { - Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.requestId} expected:$transactionId") + if (otherQrCodeData.transactionId != transactionId) { + Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId") cancel(CancelCode.QrCodeInvalid) return } // check master key - if (otherQrCodeData.userId != userId - && otherQrCodeData.otherUserKey == null) { - // Verification with other user, other_user_key is mandatory in this case - Timber.d("## Verification QR: Invalid, missing other_user_key") - cancel(CancelCode.QrCodeInvalid) - return - } - - if (otherQrCodeData.otherUserKey != null - && otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { - Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserKey}") - cancel(CancelCode.MismatchedKeys) - return - } - - // Check device key if available - if (otherQrCodeData.otherDeviceKey != null - && otherQrCodeData.otherDeviceKey != cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) { - Timber.d("## Verification QR: Invalid other device key") - cancel(CancelCode.MismatchedKeys) - return - } + when (otherQrCodeData) { + is QrCodeDataV2.VerifyingAnotherUser -> { + if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { + Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}") + cancel(CancelCode.MismatchedKeys) + return + } else Unit + } + is QrCodeDataV2.SelfVerifyingMasterKeyTrusted -> { + if (otherQrCodeData.userMasterCrossSigningPublicKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { + Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") + cancel(CancelCode.MismatchedKeys) + return + } else Unit + } + is QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted -> { + if (otherQrCodeData.userMasterCrossSigningPublicKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) { + Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") + cancel(CancelCode.MismatchedKeys) + return + } else Unit + } + }.exhaustive val toVerifyDeviceIds = mutableListOf() var canTrustOtherUserMasterKey = false - val otherDevices = cryptoStore.getUserDevices(otherUserId) - otherQrCodeData.keys.keys.forEach { key -> - Timber.w("## Verification QR: Checking key $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 - } - } - } - } + // Check device key if available + when (otherQrCodeData) { + is QrCodeDataV2.VerifyingAnotherUser -> { + if (otherQrCodeData.userMasterCrossSigningPublicKey != crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) { + Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") + cancel(CancelCode.MismatchedKeys) + return + } else { + canTrustOtherUserMasterKey = true + Unit } } - } + is QrCodeDataV2.SelfVerifyingMasterKeyTrusted -> { + if (otherQrCodeData.otherDeviceKey != cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) { + Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}") + cancel(CancelCode.MismatchedKeys) + return + } else Unit + } + is QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted -> { + if (otherQrCodeData.deviceKey != cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) { + Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}") + cancel(CancelCode.MismatchedKeys) + return + } else { + toVerifyDeviceIds.add(otherQrCodeData.deviceKey) + Unit + } + } + }.exhaustive if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { // Nothing to verify @@ -164,13 +145,6 @@ internal class DefaultQrCodeVerificationTransaction( // qrCodeData.sharedSecret will be used to send the start request start(otherQrCodeData.sharedSecret) - val safeOtherDeviceId = otherDeviceId - if (!otherQrCodeData.otherDeviceKey.isNullOrBlank() - && safeOtherDeviceId != null) { - // Locally verify the device - toVerifyDeviceIds.add(safeOtherDeviceId) - } - // Trust the other user trust(canTrustOtherUserMasterKey, toVerifyDeviceIds.distinct()) } @@ -264,8 +238,8 @@ internal class DefaultQrCodeVerificationTransaction( // TODO what if the otherDevice is not in this list? and should we toVerifyDeviceIds.forEach { - setDeviceVerified(otherUserId, it) - } + setDeviceVerified(otherUserId, it) + } transport.done(transactionId) state = VerificationTxState.Verified } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Exhaustive.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Exhaustive.kt new file mode 100644 index 0000000000..8f6beea92d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Exhaustive.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 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.util + +// Trick to ensure that when block is exhaustive +internal val T.exhaustive: T get() = this