crypto: Refactor and document the QR code verification class

This commit is contained in:
Damir Jelić 2021-07-20 14:30:34 +02:00
parent b33537fd6e
commit eae2a51a2d
2 changed files with 198 additions and 150 deletions

View file

@ -16,8 +16,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import java.io.File import java.io.File
@ -28,16 +26,11 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.listeners.ProgressListener 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.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.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.Content
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.JsonDict 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.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.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap 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.DeviceLists
import uniffi.olm.KeyRequestPair import uniffi.olm.KeyRequestPair
import uniffi.olm.Logger import uniffi.olm.Logger
import uniffi.olm.OutgoingVerificationRequest
import uniffi.olm.QrCode
import uniffi.olm.OlmMachine as InnerMachine import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.Request 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<VerificationService.Listener>,
) : 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( internal class OlmMachine(
user_id: String, user_id: String,
device_id: String, device_id: String,

View file

@ -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<VerificationService.Listener>,
) : 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
}
}