From eae2a51a2d6fea1b2478d7654a7492ad6fe0ea5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 20 Jul 2021 14:30:34 +0200 Subject: [PATCH] crypto: Refactor and document the QR code verification class --- .../android/sdk/internal/crypto/OlmMachine.kt | 150 ------------- .../sdk/internal/crypto/QrCodeVerification.kt | 198 ++++++++++++++++++ 2 files changed, 198 insertions(+), 150 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 365f058c00..91a0e35ac0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.internal.crypto -import android.os.Handler -import android.os.Looper import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import java.io.File @@ -28,16 +26,11 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.verification.CancelCode -import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationService -import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState -import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap @@ -53,8 +46,6 @@ import uniffi.olm.Device as InnerDevice import uniffi.olm.DeviceLists import uniffi.olm.KeyRequestPair import uniffi.olm.Logger -import uniffi.olm.OutgoingVerificationRequest -import uniffi.olm.QrCode import uniffi.olm.OlmMachine as InnerMachine import uniffi.olm.ProgressListener as RustProgressListener import uniffi.olm.Request @@ -158,147 +149,6 @@ internal class Device( } } -internal class QrCodeVerification( - private val machine: uniffi.olm.OlmMachine, - private var request: org.matrix.android.sdk.internal.crypto.VerificationRequest, - private var inner: QrCode?, - private val sender: RequestSender, - private val listeners: ArrayList, -) : QrCodeVerificationTransaction { - private val uiHandler = Handler(Looper.getMainLooper()) - - private fun dispatchTxUpdated() { - uiHandler.post { - listeners.forEach { - try { - it.transactionUpdated(this) - } catch (e: Throwable) { - Timber.e(e, "## Error while notifying listeners") - } - } - } - } - - override val qrCodeText: String? - get() { - val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) } - - // TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64? - return data?.fromBase64()?.toString(Charsets.ISO_8859_1) - } - - override fun userHasScannedOtherQrCode(otherQrCodeText: String) { - runBlocking { - request.scanQrCode(otherQrCodeText) - } - dispatchTxUpdated() - } - - override fun otherUserScannedMyQrCode() { - val request = runBlocking { confirm() } ?: return - sendRequest(request) - } - - override fun otherUserDidNotScannedMyQrCode() { - // TODO Is this code correct here? The old code seems to do this - cancelHelper(CancelCode.MismatchedKeys) - } - - override var state: VerificationTxState - get() { - refreshData() - val inner = this.inner - val cancelInfo = inner?.cancelInfo - - return if (inner != null) { - when { - inner.isDone -> VerificationTxState.Verified - inner.reciprocated -> VerificationTxState.Started - inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm - inner.otherSideScanned -> VerificationTxState.QrScannedByOther - cancelInfo != null -> { - val cancelCode = safeValueOf(cancelInfo.cancelCode) - val byMe = cancelInfo.cancelledByUs - VerificationTxState.Cancelled(cancelCode, byMe) - } - else -> { - VerificationTxState.None - } - } - } else { - VerificationTxState.None - } - } - @Suppress("UNUSED_PARAMETER") - set(value) {} - - override val transactionId: String - get() = this.request.flowId() - - override val otherUserId: String - get() = this.request.otherUser() - - override var otherDeviceId: String? - get() = this.request.otherDeviceId() - @Suppress("UNUSED_PARAMETER") - set(value) {} - - override val isIncoming: Boolean - get() = !this.request.weStarted() - - override fun cancel() { - cancelHelper(CancelCode.User) - } - - override fun cancel(code: CancelCode) { - cancelHelper(code) - } - - override fun isToDeviceTransport(): Boolean { - return this.request.roomId() == null - } - - @Throws(CryptoStoreErrorException::class) - suspend fun confirm(): OutgoingVerificationRequest? = - withContext(Dispatchers.IO) { - machine.confirmVerification(request.otherUser(), request.flowId()) - } - - private fun sendRequest(request: OutgoingVerificationRequest) { - runBlocking { - when (request) { - is OutgoingVerificationRequest.ToDevice -> { - sender.sendToDevice(request) - } - is OutgoingVerificationRequest.InRoom -> TODO() - } - } - - refreshData() - dispatchTxUpdated() - } - - private fun cancelHelper(code: CancelCode) { - val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value) - - if (request != null) { - sendRequest(request) - } - } - - private fun refreshData() { - when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) { - is Verification.QrCodeV1 -> { - this.inner = verification.qrcode - } - else -> { - } - } - - return - } -} - internal class OlmMachine( user_id: String, device_id: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt new file mode 100644 index 0000000000..209f060e0a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.internal.crypto + +import android.os.Handler +import android.os.Looper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.session.crypto.verification.CancelCode +import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction +import org.matrix.android.sdk.api.session.crypto.verification.VerificationService +import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState +import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf +import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 +import timber.log.Timber +import uniffi.olm.CryptoStoreErrorException +import uniffi.olm.OlmMachine +import uniffi.olm.OutgoingVerificationRequest +import uniffi.olm.QrCode +import uniffi.olm.Verification + +internal class QrCodeVerification( + private val machine: OlmMachine, + private var request: VerificationRequest, + private var inner: QrCode?, + private val sender: RequestSender, + private val listeners: ArrayList, +) : QrCodeVerificationTransaction { + private val uiHandler = Handler(Looper.getMainLooper()) + + private fun dispatchTxUpdated() { + uiHandler.post { + listeners.forEach { + try { + it.transactionUpdated(this) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } + + /** Generate, if possible, data that should be encoded as a QR code for QR code verification. + * + * QR code verification can't verify devices between two users, so in the case that + * we're verifying another user and we don't have or trust our cross signing identity + * no QR code will be generated. + */ + override val qrCodeText: String? + get() { + val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) } + + // TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64? + return data?.fromBase64()?.toString(Charsets.ISO_8859_1) + } + + /** Pass the data from a scanned QR code into the QR code verification object */ + override fun userHasScannedOtherQrCode(otherQrCodeText: String) { + runBlocking { + request.scanQrCode(otherQrCodeText) + } + dispatchTxUpdated() + } + + /** Confirm that the other side has indeed scanned the QR code we presented */ + override fun otherUserScannedMyQrCode() { + runBlocking { confirm() } + } + + /** Cancel the QR code verification, denying that the other side has scanned the QR code */ + override fun otherUserDidNotScannedMyQrCode() { + // TODO Is this code correct here? The old code seems to do this + cancelHelper(CancelCode.MismatchedKeys) + } + + override var state: VerificationTxState + get() { + refreshData() + val inner = this.inner + val cancelInfo = inner?.cancelInfo + + return if (inner != null) { + when { + cancelInfo != null -> { + val cancelCode = safeValueOf(cancelInfo.cancelCode) + val byMe = cancelInfo.cancelledByUs + VerificationTxState.Cancelled(cancelCode, byMe) + } + inner.isDone -> VerificationTxState.Verified + inner.reciprocated -> VerificationTxState.Started + inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm + inner.otherSideScanned -> VerificationTxState.QrScannedByOther + else -> { + VerificationTxState.None + } + } + } else { + VerificationTxState.None + } + } + @Suppress("UNUSED_PARAMETER") + set(value) { + } + + /** Get the unique id of this verification */ + override val transactionId: String + get() = this.request.flowId() + + /** Get the user id of the other user participating in this verification flow */ + override val otherUserId: String + get() = this.request.otherUser() + + /** Get the device id of the other user's device participating in this verification flow */ + override var otherDeviceId: String? + get() = this.request.otherDeviceId() + @Suppress("UNUSED_PARAMETER") + set(value) { + } + + /** Did the other side initiate this verification flow */ + override val isIncoming: Boolean + get() = !this.request.weStarted() + + /** Cancel the verification flow */ + override fun cancel() { + cancelHelper(CancelCode.User) + } + + /** Cancel the verification with the given cancel code */ + override fun cancel(code: CancelCode) { + cancelHelper(code) + } + + /** Is this verification happening over to-device messages */ + override fun isToDeviceTransport(): Boolean { + return this.request.roomId() == null + } + + /** Confirm the QR code verification + * + * This confirms that the other side has scanned our QR code. + */ + @Throws(CryptoStoreErrorException::class) + suspend fun confirm() { + val request = withContext(Dispatchers.IO) + { + machine.confirmVerification(request.otherUser(), request.flowId()) + } + + if (request != null) { + this.sender.sendVerificationRequest(request) + } + } + + /** Send out a verification request in a blocking manner*/ + private fun sendRequest(request: OutgoingVerificationRequest) { + runBlocking { sender.sendVerificationRequest(request) } + + refreshData() + dispatchTxUpdated() + } + + private fun cancelHelper(code: CancelCode) { + val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value) + + if (request != null) { + sendRequest(request) + } + } + + /** Fetch fetch data from the Rust side for our verification flow */ + private fun refreshData() { + when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) { + is Verification.QrCodeV1 -> { + this.inner = verification.qrcode + } + else -> { + } + } + + return + } +}