mirror of
https://github.com/element-hq/element-android
synced 2024-12-21 00:42:04 +03:00
crypto: Refactor and document the QR code verification class
This commit is contained in:
parent
b33537fd6e
commit
eae2a51a2d
2 changed files with 198 additions and 150 deletions
|
@ -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<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(
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue