mirror of
https://github.com/element-hq/element-android
synced 2024-12-21 08:54:12 +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
|
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,
|
||||||
|
|
|
@ -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