mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 20:10:04 +03:00
Merge pull request #1111 from vector-im/feature/verification_code
Feature/verification code
This commit is contained in:
commit
35179509f2
46 changed files with 652 additions and 627 deletions
|
@ -33,7 +33,6 @@ import im.vector.matrix.android.common.CommonTestHelper
|
|||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||
|
@ -279,7 +278,7 @@ class SASTest : InstrumentedTest {
|
|||
val startMessage = KeyVerificationStart(
|
||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||
method = VerificationMethod.SAS.toValue(),
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
keyAgreementProtocols = protocols,
|
||||
hashes = hashes,
|
||||
messageAuthenticationCodes = mac,
|
||||
|
@ -350,16 +349,16 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: KeyVerificationStart? = null
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted as? KeyVerificationAccept
|
||||
startReq = at.startReq as? KeyVerificationStart
|
||||
accepted = at.accepted
|
||||
startReq = at.startReq
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
@ -384,13 +383,13 @@ class SASTest : InstrumentedTest {
|
|||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
||||
// check that agreement is valid
|
||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
|
||||
assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
|
||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
||||
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
||||
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||
|
||||
accepted!!.shortAuthenticationStrings?.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
||||
accepted!!.shortAuthenticationStrings.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.common.CommonTestHelper
|
|||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
package im.vector.matrix.android.api.session.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
|
@ -24,17 +23,16 @@ import java.util.UUID
|
|||
|
||||
/**
|
||||
* Stores current pending verification requests
|
||||
* TODO We should not expose this whole object to the app. Create an interface
|
||||
*/
|
||||
data class PendingVerificationRequest(
|
||||
val ageLocalTs: Long,
|
||||
val isIncoming: Boolean = false,
|
||||
val localID: String = UUID.randomUUID().toString(),
|
||||
val localId: String = UUID.randomUUID().toString(),
|
||||
val otherUserId: String,
|
||||
val roomId: String?,
|
||||
val transactionId: String? = null,
|
||||
val requestInfo: VerificationInfoRequest? = null,
|
||||
val readyInfo: VerificationInfoReady? = null,
|
||||
val requestInfo: ValidVerificationInfoRequest? = null,
|
||||
val readyInfo: ValidVerificationInfoReady? = null,
|
||||
val cancelConclusion: CancelCode? = null,
|
||||
val isSuccessful: Boolean = false,
|
||||
val handledByOtherSession: Boolean = false,
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.api.session.crypto.verification
|
||||
|
||||
data class ValidVerificationInfoReady(
|
||||
val transactionId: String,
|
||||
val fromDevice: String,
|
||||
val methods: List<String>
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.api.session.crypto.verification
|
||||
|
||||
data class ValidVerificationInfoRequest(
|
||||
val transactionId: String,
|
||||
val fromDevice: String,
|
||||
val methods: List<String>,
|
||||
val timestamp: Long?
|
||||
)
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.crypto.verification
|
|||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||
|
|
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
|||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationAcceptContent(
|
||||
|
@ -34,22 +33,9 @@ internal data class MessageVerificationAcceptContent(
|
|||
@Json(name = "commitment") override var commitment: String? = null
|
||||
) : VerificationInfoAccept {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| keyAgreementProtocol.isNullOrBlank()
|
||||
|| hash.isNullOrBlank()
|
||||
|| commitment.isNullOrBlank()
|
||||
|| messageAuthenticationCode.isNullOrBlank()
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
companion object : VerificationInfoAcceptFactory {
|
||||
|
|
|
@ -28,21 +28,13 @@ data class MessageVerificationCancelContent(
|
|||
@Json(name = "code") override val code: String? = null,
|
||||
@Json(name = "reason") override val reason: String? = null,
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
|
||||
) : VerificationInfoCancel {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
|
||||
return MessageVerificationCancelContent(
|
||||
|
|
|
@ -25,12 +25,22 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationDoneContent(
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfo {
|
||||
) : VerificationInfo<ValidVerificationDone> {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun isValid() = transactionID?.isNotEmpty() == true
|
||||
|
||||
override fun toEventContent(): Content? = toContent()
|
||||
|
||||
override fun asValidObject(): ValidVerificationDone? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationDone(
|
||||
validTransactionId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ValidVerificationDone(
|
||||
val transactionId: String
|
||||
)
|
||||
|
|
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
|||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationKeyContent(
|
||||
|
@ -33,17 +32,9 @@ internal data class MessageVerificationKeyContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoKey {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
companion object : VerificationInfoKeyFactory {
|
||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationMacContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoMac {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object : VerificationInfoMacFactory {
|
||||
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
||||
return MessageVerificationMacContent(
|
||||
|
|
|
@ -30,18 +30,11 @@ internal data class MessageVerificationReadyContent(
|
|||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||
) : VerificationInfoReady {
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object : MessageVerificationReadyFactory {
|
||||
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
||||
return MessageVerificationReadyContent(
|
||||
|
|
|
@ -33,18 +33,10 @@ data class MessageVerificationRequestContent(
|
|||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||
// Not parsed, but set after, using the eventId
|
||||
override val transactionId: String? = null
|
||||
) : MessageContent, VerificationInfoRequest {
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override val transactionID: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
||||
|
|
|
@ -17,15 +17,10 @@ package im.vector.matrix.android.api.session.room.model.message
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class MessageVerificationStartContent(
|
||||
|
@ -39,46 +34,12 @@ internal data class MessageVerificationStartContent(
|
|||
@Json(name = "secret") override val sharedSecret: String?
|
||||
) : VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
override fun toCanonicalJson(): String {
|
||||
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
|
||||
}
|
||||
|
||||
override val transactionID: String?
|
||||
override val transactionId: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
// TODO Move those method to the interface?
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| fromDevice.isNullOrBlank()
|
||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidSas(): Boolean {
|
||||
if (keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidReciprocate(): Boolean {
|
||||
if (sharedSecret.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
||||
|
|
|
@ -19,21 +19,19 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationAccept(
|
||||
|
||||
/**
|
||||
* string to identify the transaction.
|
||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
override val transactionID: String? = null,
|
||||
override val transactionId: String? = null,
|
||||
|
||||
/**
|
||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||
|
@ -67,19 +65,6 @@ internal data class KeyVerificationAccept(
|
|||
override var commitment: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoAccept {
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| keyAgreementProtocol.isNullOrBlank()
|
||||
|| hash.isNullOrBlank()
|
||||
|| commitment.isNullOrBlank()
|
||||
|| messageAuthenticationCode.isNullOrBlank()
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
companion object : VerificationInfoAcceptFactory {
|
||||
|
@ -90,7 +75,7 @@ internal data class KeyVerificationAccept(
|
|||
messageAuthenticationCode: String,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
||||
return KeyVerificationAccept(
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
keyAgreementProtocol = keyAgreementProtocol,
|
||||
hash = hash,
|
||||
commitment = commitment,
|
||||
|
|
|
@ -29,7 +29,7 @@ internal data class KeyVerificationCancel(
|
|||
* the transaction ID of the verification to cancel
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
override val transactionID: String? = null,
|
||||
override val transactionId: String? = null,
|
||||
|
||||
/**
|
||||
* machine-readable reason for cancelling, see #CancelCode
|
||||
|
@ -53,11 +53,4 @@ internal data class KeyVerificationCancel(
|
|||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,15 +24,8 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDon
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationDone(
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoDone {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ internal data class KeyVerificationKey(
|
|||
/**
|
||||
* the ID of the transaction that the message is part of
|
||||
*/
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||
|
||||
/**
|
||||
* The device’s ephemeral public key, as an unpadded base64 string
|
||||
|
@ -44,11 +44,4 @@ internal data class KeyVerificationKey(
|
|||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,19 +25,12 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationMac(
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||
@Json(name = "keys") override val keys: String? = null
|
||||
|
||||
) : SendToDeviceObject, VerificationInfoMac {
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||
|
||||
companion object : VerificationInfoMacFactory {
|
||||
|
|
|
@ -26,12 +26,8 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea
|
|||
internal data class KeyVerificationReady(
|
||||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>?,
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoReady {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,16 +27,8 @@ internal data class KeyVerificationRequest(
|
|||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>,
|
||||
@Json(name = "timestamp") override val timestamp: Long?,
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoRequest {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,8 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Sent by Alice to initiate an interactive key verification.
|
||||
|
@ -29,8 +26,8 @@ import timber.log.Timber
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationStart(
|
||||
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||
override val method: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||
@Json(name = "method") override val method: String? = null,
|
||||
@Json(name = "transaction_id") override val transactionId: String? = null,
|
||||
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
||||
@Json(name = "hashes") override val hashes: List<String>? = null,
|
||||
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
|
||||
|
@ -39,43 +36,9 @@ internal data class KeyVerificationStart(
|
|||
@Json(name = "secret") override val sharedSecret: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
override fun toCanonicalJson(): String {
|
||||
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
||||
}
|
||||
|
||||
// TODO Move those method to the interface?
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| fromDevice.isNullOrBlank()
|
||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidSas(): Boolean {
|
||||
if (keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidReciprocate(): Boolean {
|
||||
if (sharedSecret.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
}
|
||||
|
|
|
@ -41,22 +41,22 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
|
||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
||||
val event = handleEncryption(params)
|
||||
val localID = event.eventId!!
|
||||
val localId = event.eventId!!
|
||||
|
||||
try {
|
||||
localEchoUpdater.updateSendState(localID, SendState.SENDING)
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.send(
|
||||
localID,
|
||||
localId,
|
||||
roomId = event.roomId ?: "",
|
||||
content = event.content,
|
||||
eventType = event.type
|
||||
)
|
||||
}
|
||||
localEchoUpdater.updateSendState(localID, SendState.SENT)
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||
return executeRequest.eventId
|
||||
} catch (e: Throwable) {
|
||||
localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED)
|
||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationStart(startReq: VerificationInfoStart) {
|
||||
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
|
||||
Timber.v("## SAS I: received verification request from state $state")
|
||||
if (state != VerificationTxState.None) {
|
||||
Timber.e("## SAS I: received verification request from invalid state")
|
||||
|
@ -100,10 +100,10 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
|
||||
// Select a key agreement protocol, a hash algorithm, a message authentication code,
|
||||
// and short authentication string methods out of the lists given in requester's message.
|
||||
val agreedProtocol = startReq!!.keyAgreementProtocols?.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
|
||||
val agreedHash = startReq!!.hashes?.firstOrNull { KNOWN_HASHES.contains(it) }
|
||||
val agreedMac = startReq!!.messageAuthenticationCodes?.firstOrNull { KNOWN_MACS.contains(it) }
|
||||
val agreedShortCode = startReq!!.shortAuthenticationStrings?.filter { KNOWN_SHORT_CODES.contains(it) }
|
||||
val agreedProtocol = startReq!!.keyAgreementProtocols.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
|
||||
val agreedHash = startReq!!.hashes.firstOrNull { KNOWN_HASHES.contains(it) }
|
||||
val agreedMac = startReq!!.messageAuthenticationCodes.firstOrNull { KNOWN_MACS.contains(it) }
|
||||
val agreedShortCode = startReq!!.shortAuthenticationStrings.filter { KNOWN_SHORT_CODES.contains(it) }
|
||||
|
||||
// No common key sharing/hashing/hmac/SAS methods.
|
||||
// If a device is unable to complete the verification because the devices are unable to find a common key sharing,
|
||||
|
@ -141,12 +141,12 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
private fun doAccept(accept: VerificationInfoAccept) {
|
||||
this.accepted = accept
|
||||
this.accepted = accept.asValidObject()
|
||||
Timber.v("## SAS incoming accept request id:$transactionId")
|
||||
|
||||
// The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
|
||||
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message
|
||||
val concat = getSAS().publicKey + startReq!!.toCanonicalJson()
|
||||
val concat = getSAS().publicKey + startReq!!.canonicalJson
|
||||
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||
// we need to send this to other device now
|
||||
state = VerificationTxState.SendingAccept
|
||||
|
@ -158,12 +158,12 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationAccept(accept: VerificationInfoAccept) {
|
||||
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
||||
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
||||
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
|
||||
Timber.v("## SAS received key for request id:$transactionId")
|
||||
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
|
||||
Timber.e("## SAS received key from invalid state $state")
|
||||
|
@ -213,7 +213,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
state = VerificationTxState.ShortCodeReady
|
||||
}
|
||||
|
||||
override fun onKeyVerificationMac(vKey: VerificationInfoMac) {
|
||||
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||
// Check for state?
|
||||
if (state != VerificationTxState.SendingKey
|
||||
|
@ -226,12 +226,13 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
theirMac = vKey
|
||||
|
||||
theirMac = vMac
|
||||
|
||||
// Do I have my Mac?
|
||||
if (myMac != null) {
|
||||
// I can check
|
||||
verifyMacs()
|
||||
verifyMacs(vMac)
|
||||
}
|
||||
// Wait for ShortCode Accepted
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationStart(startReq: VerificationInfoStart) {
|
||||
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
|
||||
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
KNOWN_SHORT_CODES
|
||||
)
|
||||
|
||||
startReq = startMessage
|
||||
startReq = startMessage.asValidObject() as? ValidVerificationInfoStart.SasVerificationInfoStart
|
||||
state = VerificationTxState.SendingStart
|
||||
|
||||
sendToOther(
|
||||
|
@ -118,7 +118,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
// timestamp = System.currentTimeMillis().toInt(),
|
||||
// transactionID = transactionId
|
||||
// transactionId = transactionId
|
||||
// )
|
||||
//
|
||||
// sendToOther(
|
||||
|
@ -130,7 +130,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
// )
|
||||
// }
|
||||
|
||||
override fun onVerificationAccept(accept: VerificationInfoAccept) {
|
||||
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
||||
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
|
||||
if (state != VerificationTxState.Started) {
|
||||
Timber.e("## SAS O: received accept request from invalid state $state")
|
||||
|
@ -141,7 +141,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
if (!KNOWN_AGREEMENT_PROTOCOLS.contains(accept.keyAgreementProtocol)
|
||||
|| !KNOWN_HASHES.contains(accept.hash)
|
||||
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
||||
|| accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
||||
|| accept.shortAuthenticationStrings.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
||||
Timber.e("## SAS O: received accept request from invalid state")
|
||||
cancel(CancelCode.UnknownMethod)
|
||||
return
|
||||
|
@ -167,7 +167,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
||||
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
|
||||
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
||||
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
|
@ -182,7 +182,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
||||
|
||||
// check commitment
|
||||
val concat = vKey.key + startReq!!.toCanonicalJson()
|
||||
val concat = vKey.key + startReq!!.canonicalJson
|
||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||
|
||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
||||
|
@ -206,7 +206,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyVerificationMac(vKey: VerificationInfoMac) {
|
||||
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
||||
if (state != VerificationTxState.OnKeyReceived
|
||||
&& state != VerificationTxState.ShortCodeReady
|
||||
|
@ -218,12 +218,12 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
theirMac = vKey
|
||||
theirMac = vMac
|
||||
|
||||
// Do I have my Mac?
|
||||
if (myMac != null) {
|
||||
// I can check
|
||||
verifyMacs()
|
||||
verifyMacs(vMac)
|
||||
}
|
||||
// Wait for ShortCode Accepted
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoReady
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
|
@ -45,6 +47,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone
|
||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
|
@ -73,7 +76,6 @@ import im.vector.matrix.android.internal.session.SessionScope
|
|||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toImmutableList
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
@ -105,7 +107,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
* Map [sender: [PendingVerificationRequest]]
|
||||
* For now we keep all requests (even terminated ones) during the lifetime of the app.
|
||||
*/
|
||||
private val pendingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
|
||||
private val pendingRequests = HashMap<String, MutableList<PendingVerificationRequest>>()
|
||||
|
||||
// Event received from the sync
|
||||
fun onToDeviceEvent(event: Event) {
|
||||
|
@ -268,9 +270,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onRequestReceived(event: Event) {
|
||||
val requestInfo = event.getClearContent().toModel<KeyVerificationRequest>()!!
|
||||
val validRequestInfo = event.getClearContent().toModel<KeyVerificationRequest>()?.asValidObject()
|
||||
|
||||
if (!requestInfo.isValid()) {
|
||||
if (validRequestInfo == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
|
@ -278,7 +280,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
val senderId = event.senderId ?: return
|
||||
|
||||
// We don't want to block here
|
||||
val otherDeviceId = requestInfo.fromDevice ?: return
|
||||
val otherDeviceId = validRequestInfo.fromDevice
|
||||
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||
|
@ -287,19 +289,16 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
// Remember this request
|
||||
val requestsForUser = pendingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[event.senderId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||
isIncoming = true,
|
||||
otherUserId = senderId, // requestInfo.toUserId,
|
||||
roomId = null,
|
||||
transactionId = requestInfo.transactionID,
|
||||
localID = requestInfo.transactionID!!,
|
||||
requestInfo = requestInfo
|
||||
transactionId = validRequestInfo.transactionId,
|
||||
localId = validRequestInfo.transactionId,
|
||||
requestInfo = validRequestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
|
@ -307,10 +306,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
suspend fun onRoomRequestReceived(event: Event) {
|
||||
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
|
||||
?: return
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() ?: return
|
||||
val validRequestInfo = requestInfo
|
||||
// copy the EventId to the transactionId
|
||||
.copy(transactionId = event.eventId)
|
||||
.asValidObject() ?: return
|
||||
|
||||
val senderId = event.senderId ?: return
|
||||
val fromDevice = requestInfo.fromDevice ?: return
|
||||
|
||||
if (requestInfo.toUserId != userId) {
|
||||
// I should ignore this, it's not for me
|
||||
|
@ -320,16 +322,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
// We don't want to block here
|
||||
GlobalScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device $fromDevice is not known")
|
||||
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
||||
}
|
||||
}
|
||||
|
||||
// Remember this request
|
||||
val requestsForUser = pendingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[event.senderId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
|
||||
|
@ -337,8 +336,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
otherUserId = senderId, // requestInfo.toUserId,
|
||||
roomId = event.roomId,
|
||||
transactionId = event.eventId,
|
||||
localID = event.eventId!!,
|
||||
requestInfo = requestInfo
|
||||
localId = event.eventId!!,
|
||||
requestInfo = validRequestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
|
@ -362,13 +361,15 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
|
||||
val validStartReq = startReq?.asValidObject()
|
||||
|
||||
val otherUserId = event.senderId
|
||||
if (startReq?.isValid()?.not() == true) {
|
||||
if (validStartReq == null) {
|
||||
Timber.e("## received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
if (startReq?.transactionId != null) {
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
startReq.transactionId ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
CancelCode.UnknownMethod
|
||||
|
@ -377,14 +378,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
handleStart(otherUserId, startReq as VerificationInfoStart) {
|
||||
handleStart(otherUserId, validStartReq) {
|
||||
it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
||||
}?.let {
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
validStartReq.transactionId,
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
validStartReq.fromDevice,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
@ -392,16 +393,17 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
private suspend fun onStartRequestReceived(event: Event) {
|
||||
Timber.e("## SAS received Start request ${event.eventId}")
|
||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()
|
||||
val validStartReq = startReq?.asValidObject()
|
||||
Timber.v("## SAS received Start request $startReq")
|
||||
|
||||
val otherUserId = event.senderId
|
||||
if (!startReq.isValid()) {
|
||||
val otherUserId = event.senderId!!
|
||||
if (validStartReq == null) {
|
||||
Timber.e("## SAS received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
if (startReq?.transactionId != null) {
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID,
|
||||
otherUserId!!,
|
||||
startReq.transactionId,
|
||||
otherUserId,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
CancelCode.UnknownMethod
|
||||
)
|
||||
|
@ -409,13 +411,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return
|
||||
}
|
||||
// Download device keys prior to everything
|
||||
handleStart(otherUserId, startReq) {
|
||||
handleStart(otherUserId, validStartReq) {
|
||||
it.transport = verificationTransportToDeviceFactory.createTransport(it)
|
||||
}?.let {
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
validStartReq.transactionId,
|
||||
otherUserId,
|
||||
validStartReq.fromDevice,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
@ -424,18 +426,20 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
/**
|
||||
* Return a CancelCode to make the caller cancel the verification. Else return null
|
||||
*/
|
||||
private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) {
|
||||
val tid = startReq.transactionID!!
|
||||
private suspend fun handleStart(otherUserId: String?,
|
||||
startReq: ValidVerificationInfoStart,
|
||||
txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice) != null) {
|
||||
val tid = startReq.transactionId
|
||||
val existing = getExistingTransaction(otherUserId, tid)
|
||||
|
||||
when (startReq.method) {
|
||||
VERIFICATION_METHOD_SAS -> {
|
||||
when (startReq) {
|
||||
is ValidVerificationInfoStart.SasVerificationInfoStart -> {
|
||||
when (existing) {
|
||||
is SasVerificationTransaction -> {
|
||||
// should cancel both!
|
||||
Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionID}")
|
||||
Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionId}")
|
||||
existing.cancel(CancelCode.UnexpectedMessage)
|
||||
// Already cancelled, so return null
|
||||
return null
|
||||
|
@ -450,7 +454,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
?.also {
|
||||
// Multiple keyshares between two devices:
|
||||
// any two devices may only have at most one key verification in flight at a time.
|
||||
Timber.v("## SAS onStartRequestReceived - Already a transaction with this user ${startReq.transactionID}")
|
||||
Timber.v("## SAS onStartRequestReceived - Already a transaction with this user ${startReq.transactionId}")
|
||||
}
|
||||
?.forEach {
|
||||
it.cancel(CancelCode.UnexpectedMessage)
|
||||
|
@ -462,12 +466,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
// Ok we can create a SAS transaction
|
||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionId}")
|
||||
// If there is a corresponding request, we can auto accept
|
||||
// as we are the one requesting in first place (or we accepted the request)
|
||||
// I need to check if the pending request was related to this device also
|
||||
val autoAccept = getExistingVerificationRequest(otherUserId)?.any {
|
||||
it.transactionId == startReq.transactionID
|
||||
it.transactionId == startReq.transactionId
|
||||
&& (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId)
|
||||
}
|
||||
?: false
|
||||
|
@ -479,27 +483,23 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
cryptoStore,
|
||||
crossSigningService,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
startReq.transactionID!!,
|
||||
startReq.transactionId,
|
||||
otherUserId,
|
||||
autoAccept).also { txConfigure(it) }
|
||||
addTransaction(tx)
|
||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||
tx.onVerificationStart(startReq)
|
||||
return null
|
||||
}
|
||||
VERIFICATION_METHOD_RECIPROCATE -> {
|
||||
is ValidVerificationInfoStart.ReciprocateVerificationInfoStart -> {
|
||||
// Other user has scanned my QR code
|
||||
if (existing is DefaultQrCodeVerificationTransaction) {
|
||||
existing.onStartReceived(startReq)
|
||||
return null
|
||||
} else {
|
||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionID}")
|
||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId}")
|
||||
return CancelCode.UnexpectedMessage
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||
return CancelCode.UnknownMethod
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return CancelCode.UnexpectedMessage
|
||||
|
@ -529,24 +529,27 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (cancelReq == null || cancelReq.isValid().not()) {
|
||||
|
||||
val validCancelReq = cancelReq?.asValidObject()
|
||||
|
||||
if (validCancelReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
getExistingVerificationRequest(event.senderId ?: "", cancelReq.transactionID)?.let {
|
||||
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(cancelReq.code)))
|
||||
getExistingVerificationRequest(event.senderId ?: "", validCancelReq.transactionId)?.let {
|
||||
updatePendingRequest(it.copy(cancelConclusion = safeValueOf(validCancelReq.code)))
|
||||
// Should we remove it from the list?
|
||||
}
|
||||
handleOnCancel(event.senderId!!, cancelReq)
|
||||
handleOnCancel(event.senderId!!, validCancelReq)
|
||||
}
|
||||
|
||||
private fun onCancelReceived(event: Event) {
|
||||
Timber.v("## SAS onCancelReceived")
|
||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()?.asValidObject()
|
||||
|
||||
if (!cancelReq.isValid()) {
|
||||
if (cancelReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid cancel request")
|
||||
return
|
||||
|
@ -556,11 +559,11 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
handleOnCancel(otherUserId, cancelReq)
|
||||
}
|
||||
|
||||
private fun handleOnCancel(otherUserId: String, cancelReq: VerificationInfoCancel) {
|
||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
||||
private fun handleOnCancel(otherUserId: String, cancelReq: ValidVerificationInfoCancel) {
|
||||
Timber.v("## SAS onCancelReceived otherUser: $otherUserId reason: ${cancelReq.reason}")
|
||||
|
||||
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
||||
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionID!!)
|
||||
val existingTransaction = getExistingTransaction(otherUserId, cancelReq.transactionId)
|
||||
val existingRequest = getExistingVerificationRequest(otherUserId, cancelReq.transactionId)
|
||||
|
||||
if (existingRequest != null) {
|
||||
// Mark this request as cancelled
|
||||
|
@ -582,30 +585,28 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
?: return
|
||||
handleAccept(accept, event.senderId!!)
|
||||
|
||||
val validAccept = accept.asValidObject() ?: return
|
||||
|
||||
handleAccept(validAccept, event.senderId!!)
|
||||
}
|
||||
|
||||
private fun onAcceptReceived(event: Event) {
|
||||
Timber.d("## SAS Received Accept $event")
|
||||
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>() ?: return
|
||||
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()?.asValidObject() ?: return
|
||||
handleAccept(acceptReq, event.senderId!!)
|
||||
}
|
||||
|
||||
private fun handleAccept(acceptReq: VerificationInfoAccept, senderId: String) {
|
||||
if (!acceptReq.isValid()) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
private fun handleAccept(acceptReq: ValidVerificationInfoAccept, senderId: String) {
|
||||
val otherUserId = senderId
|
||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(otherUserId, acceptReq)
|
||||
existing.onVerificationAccept(acceptReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
|
@ -617,7 +618,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (keyReq == null || keyReq.isValid().not()) {
|
||||
?.asValidObject()
|
||||
if (keyReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
// TODO should we cancel?
|
||||
|
@ -627,9 +629,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onKeyReceived(event: Event) {
|
||||
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()!!
|
||||
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()?.asValidObject()
|
||||
|
||||
if (!keyReq.isValid()) {
|
||||
if (keyReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
|
@ -637,16 +639,16 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
handleKeyReceived(event, keyReq)
|
||||
}
|
||||
|
||||
private fun handleKeyReceived(event: Event, keyReq: VerificationInfoKey) {
|
||||
private fun handleKeyReceived(event: Event, keyReq: ValidVerificationInfoKey) {
|
||||
Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq")
|
||||
val otherUserId = event.senderId!!
|
||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
}
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(otherUserId, keyReq)
|
||||
existing.onKeyVerificationKey(keyReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
|
@ -658,7 +660,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (macReq == null || macReq.isValid().not() || event.senderId == null) {
|
||||
?.asValidObject()
|
||||
if (macReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid mac request")
|
||||
// TODO should we cancel?
|
||||
|
@ -673,13 +676,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
|
||||
?.asValidObject()
|
||||
if (readyReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid ready request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
|
||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
||||
// TODO cancel?
|
||||
return
|
||||
|
@ -691,15 +695,15 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun onReadyReceived(event: Event) {
|
||||
val readyReq = event.getClearContent().toModel<KeyVerificationReady>()
|
||||
val readyReq = event.getClearContent().toModel<KeyVerificationReady>()?.asValidObject()
|
||||
|
||||
if (readyReq == null || readyReq.isValid().not() || event.senderId == null) {
|
||||
if (readyReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid ready request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
|
||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
||||
// TODO cancel?
|
||||
return
|
||||
|
@ -716,8 +720,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
?.asValidObject()
|
||||
|
||||
if (doneReq == null || doneReq.isValid().not() || event.senderId == null) {
|
||||
if (doneReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid Done request")
|
||||
// TODO should we cancel?
|
||||
|
@ -728,9 +733,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onMacReceived(event: Event) {
|
||||
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
|
||||
val macReq = event.getClearContent().toModel<KeyVerificationMac>()?.asValidObject()
|
||||
|
||||
if (!macReq.isValid() || event.senderId == null) {
|
||||
if (macReq == null || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid mac request")
|
||||
return
|
||||
|
@ -738,41 +743,41 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
handleMacReceived(event.senderId, macReq)
|
||||
}
|
||||
|
||||
private fun handleMacReceived(senderId: String, macReq: VerificationInfoMac) {
|
||||
private fun handleMacReceived(senderId: String, macReq: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS Received $macReq")
|
||||
val existing = getExistingTransaction(senderId, macReq.transactionID!!)
|
||||
val existing = getExistingTransaction(senderId, macReq.transactionId)
|
||||
if (existing == null) {
|
||||
Timber.e("## SAS Received invalid Mac request")
|
||||
return
|
||||
}
|
||||
if (existing is SASDefaultVerificationTransaction) {
|
||||
existing.acceptVerificationEvent(senderId, macReq)
|
||||
existing.onKeyVerificationMac(macReq)
|
||||
} else {
|
||||
// not other types known for now
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReadyReceived(senderId: String,
|
||||
readyReq: VerificationInfoReady,
|
||||
readyReq: ValidVerificationInfoReady,
|
||||
transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionId }
|
||||
if (existingRequest == null) {
|
||||
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
|
||||
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}")
|
||||
return
|
||||
}
|
||||
|
||||
val qrCodeData = readyReq.methods
|
||||
// Check if other user is able to scan QR code
|
||||
?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
|
||||
.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
|
||||
?.let {
|
||||
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId, readyReq.fromDevice)
|
||||
}
|
||||
|
||||
if (readyReq.methods.orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) {
|
||||
if (readyReq.methods.contains(VERIFICATION_METHOD_RECIPROCATE)) {
|
||||
// Create the pending transaction
|
||||
val tx = DefaultQrCodeVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
readyReq.transactionID!!,
|
||||
readyReq.transactionId,
|
||||
senderId,
|
||||
readyReq.fromDevice,
|
||||
crossSigningService,
|
||||
|
@ -886,10 +891,10 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionID }
|
||||
private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId }
|
||||
if (existingRequest == null) {
|
||||
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionID}")
|
||||
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}")
|
||||
return
|
||||
}
|
||||
updatePendingRequest(existingRequest.copy(isSuccessful = true))
|
||||
|
@ -975,15 +980,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
: PendingVerificationRequest {
|
||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||
|
||||
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
requestsForUser.toList().forEach { existingRequest ->
|
||||
existingRequest.transactionId?.let { tid ->
|
||||
if (!existingRequest.isFinished) {
|
||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||
|
@ -993,13 +995,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val localID = localId ?: LocalEcho.createLocalEchoId()
|
||||
val validLocalId = localId ?: LocalEcho.createLocalEchoId()
|
||||
|
||||
val verificationRequest = PendingVerificationRequest(
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
isIncoming = false,
|
||||
roomId = roomId,
|
||||
localID = localID,
|
||||
localId = validLocalId,
|
||||
otherUserId = otherUserId
|
||||
)
|
||||
|
||||
|
@ -1019,7 +1021,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId, null) { syncedId, info ->
|
||||
transport.sendVerificationRequest(methodValues, validLocalId, otherUserId, roomId, null) { syncedId, info ->
|
||||
// We need to update with the syncedID
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
transactionId = syncedId,
|
||||
|
@ -1039,15 +1041,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices")
|
||||
|
||||
val targetDevices = otherDevices ?: cryptoService.getUserDevices(otherUserId).map { it.deviceId }
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() }
|
||||
|
||||
val transport = verificationTransportToDeviceFactory.createTransport(null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
requestsForUser.toList().forEach { existingRequest ->
|
||||
existingRequest.transactionId?.let { tid ->
|
||||
if (!existingRequest.isFinished) {
|
||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||
|
@ -1059,14 +1058,14 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
|
||||
val verificationRequest = PendingVerificationRequest(
|
||||
transactionId = localID,
|
||||
transactionId = localId,
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
isIncoming = false,
|
||||
roomId = null,
|
||||
localID = localID,
|
||||
localId = localId,
|
||||
otherUserId = otherUserId,
|
||||
targetDevices = targetDevices
|
||||
)
|
||||
|
@ -1087,7 +1086,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, info ->
|
||||
transport.sendVerificationRequest(methodValues, localId, otherUserId, null, targetDevices) { _, info ->
|
||||
// Nothing special to do in to device mode
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
// localId stays different
|
||||
|
@ -1113,13 +1112,10 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updatePendingRequest(updated: PendingVerificationRequest) {
|
||||
val requestsForUser = pendingRequests[updated.otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[updated.otherUserId] = it
|
||||
}
|
||||
val requestsForUser = pendingRequests.getOrPut(updated.otherUserId) { mutableListOf() }
|
||||
val index = requestsForUser.indexOfFirst {
|
||||
it.transactionId == updated.transactionId
|
||||
|| it.transactionId == null && it.localID == updated.localID
|
||||
|| it.transactionId == null && it.localId == updated.localId
|
||||
}
|
||||
if (index != -1) {
|
||||
requestsForUser.removeAt(index)
|
||||
|
@ -1186,7 +1182,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
CancelCode.User,
|
||||
null // TODO handle error?
|
||||
)
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||
return true
|
||||
} else {
|
||||
Timber.e("## SAS readyPendingVerificationInDMs Verification not found")
|
||||
|
@ -1214,7 +1210,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
if (methods.isNullOrEmpty()) {
|
||||
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
||||
// TODO buttons should not be shown in this case?
|
||||
// TODO buttons should not be shown in this case?
|
||||
return false
|
||||
}
|
||||
// TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
|
||||
|
@ -1225,7 +1221,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
existingRequest.requestInfo?.fromDevice ?: "",
|
||||
null // TODO handle error?
|
||||
)
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg))
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyMsg.asValidObject()))
|
||||
return true
|
||||
} else {
|
||||
Timber.e("## SAS readyPendingVerification Verification not found")
|
||||
|
|
|
@ -15,12 +15,21 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Generic interactive key verification transaction
|
||||
*/
|
||||
internal abstract class DefaultVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val userId: String,
|
||||
override val transactionId: String,
|
||||
override val otherUserId: String,
|
||||
override var otherDeviceId: String? = null,
|
||||
|
@ -42,5 +51,50 @@ internal abstract class DefaultVerificationTransaction(
|
|||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo)
|
||||
protected fun trust(canTrustOtherUserMasterKey: Boolean,
|
||||
toVerifyDeviceIds: List<String>,
|
||||
eventuallyMarkMyMasterKeyAsTrusted: Boolean) {
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (canTrustOtherUserMasterKey) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
if (otherUserId != userId) {
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## Verification: Failed to trust User $otherUserId")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Notice other master key is mine because other is me
|
||||
if (eventuallyMarkMyMasterKeyAsTrusted) {
|
||||
// Mark my keys as trusted locally
|
||||
crossSigningService.markMyMasterKeyAsTrusted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (otherUserId == userId) {
|
||||
// If me it's reasonable to sign and upload the device signature
|
||||
// Notice that i might not have the private keys, so may not be able to do it
|
||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO what if the otherDevice is not in this list? and should we
|
||||
toVerifyDeviceIds.forEach {
|
||||
setDeviceVerified(otherUserId, it)
|
||||
}
|
||||
transport.done(transactionId)
|
||||
state = VerificationTxState.Verified
|
||||
}
|
||||
|
||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||
// TODO should not override cross sign status
|
||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
userId,
|
||||
deviceId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import android.os.Build
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation
|
||||
|
@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.crypto.verification.SasVerificationT
|
|||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||
|
@ -38,17 +37,25 @@ import timber.log.Timber
|
|||
* Represents an ongoing short code interactive key verification between two devices.
|
||||
*/
|
||||
internal abstract class SASDefaultVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
open val userId: String,
|
||||
open val deviceId: String?,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
crossSigningService: CrossSigningService,
|
||||
private val deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String?,
|
||||
isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), SasVerificationTransaction {
|
||||
) : DefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
crossSigningService,
|
||||
userId,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
SasVerificationTransaction {
|
||||
|
||||
companion object {
|
||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||
|
@ -89,15 +96,17 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
private var olmSas: OlmSAS? = null
|
||||
|
||||
var startReq: VerificationInfoStart? = null
|
||||
var accepted: VerificationInfoAccept? = null
|
||||
var otherKey: String? = null
|
||||
var shortCodeBytes: ByteArray? = null
|
||||
// Visible for test
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
// Visible for test
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
protected var otherKey: String? = null
|
||||
protected var shortCodeBytes: ByteArray? = null
|
||||
|
||||
var myMac: VerificationInfoMac? = null
|
||||
var theirMac: VerificationInfoMac? = null
|
||||
protected var myMac: ValidVerificationInfoMac? = null
|
||||
protected var theirMac: ValidVerificationInfoMac? = null
|
||||
|
||||
fun getSAS(): OlmSAS {
|
||||
protected fun getSAS(): OlmSAS {
|
||||
if (olmSas == null) olmSas = OlmSAS()
|
||||
return olmSas!!
|
||||
}
|
||||
|
@ -177,7 +186,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
|
||||
myMac = macMsg
|
||||
myMac = macMsg.asValidObject()
|
||||
state = VerificationTxState.SendingMac
|
||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
|
||||
if (state == VerificationTxState.SendingMac) {
|
||||
|
@ -187,9 +196,8 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
// Do I already have their Mac?
|
||||
if (theirMac != null) {
|
||||
verifyMacs()
|
||||
} // if not wait for it
|
||||
theirMac?.let { verifyMacs(it) }
|
||||
// if not wait for it
|
||||
}
|
||||
|
||||
override fun shortCodeDoesNotMatch() {
|
||||
|
@ -201,27 +209,15 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return transport is VerificationTransportToDevice
|
||||
}
|
||||
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
when (info) {
|
||||
is VerificationInfoStart -> onVerificationStart(info)
|
||||
is VerificationInfoAccept -> onVerificationAccept(info)
|
||||
is VerificationInfoKey -> onKeyVerificationKey(info)
|
||||
is VerificationInfoMac -> onKeyVerificationMac(info)
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart)
|
||||
|
||||
abstract fun onVerificationStart(startReq: VerificationInfoStart)
|
||||
abstract fun onVerificationAccept(accept: ValidVerificationInfoAccept)
|
||||
|
||||
abstract fun onVerificationAccept(accept: VerificationInfoAccept)
|
||||
abstract fun onKeyVerificationKey(vKey: ValidVerificationInfoKey)
|
||||
|
||||
abstract fun onKeyVerificationKey(vKey: VerificationInfoKey)
|
||||
abstract fun onKeyVerificationMac(vMac: ValidVerificationInfoMac)
|
||||
|
||||
abstract fun onKeyVerificationMac(vKey: VerificationInfoMac)
|
||||
|
||||
protected fun verifyMacs() {
|
||||
protected fun verifyMacs(theirMacSafe: ValidVerificationInfoMac) {
|
||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
||||
state = VerificationTxState.Verifying
|
||||
|
||||
|
@ -232,16 +228,12 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
|
||||
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
||||
// If everything matches, then consider Alice’s device keys as verified.
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
||||
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" +
|
||||
otherUserId + otherDeviceId +
|
||||
userId + deviceId +
|
||||
transactionId
|
||||
|
||||
val commaSeparatedListOfKeyIds = theirMac!!.mac!!.keys.sorted().joinToString(",")
|
||||
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
|
||||
|
||||
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
|
||||
if (theirMac!!.keys != keyStrings) {
|
||||
if (theirMacSafe.keys != keyStrings) {
|
||||
// WRONG!
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
|
@ -250,7 +242,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
val verifiedDevices = ArrayList<String>()
|
||||
|
||||
// cannot be empty because it has been validated
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
theirMacSafe.mac.keys.forEach {
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
||||
if (otherDeviceKey == null) {
|
||||
|
@ -259,7 +251,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return@forEach
|
||||
}
|
||||
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
||||
if (mac != theirMac?.mac?.get(it)) {
|
||||
if (mac != theirMacSafe.mac[it]) {
|
||||
// WRONG!
|
||||
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
|
@ -273,12 +265,12 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
|
||||
if (otherCrossSigningMasterKeyPublic != null) {
|
||||
// Did the user signed his master key
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
theirMacSafe.mac.keys.forEach {
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
||||
// Check the signature
|
||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
||||
if (mac != theirMac?.mac?.get(it)) {
|
||||
if (mac != theirMacSafe.mac.get(it)) {
|
||||
// WRONG!
|
||||
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
|
@ -298,47 +290,9 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (otherMasterKeyIsVerified) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
if (otherUserId != userId) {
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## SAS Verification: Failed to trust User $otherUserId")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Notice other master key is mine because other is me
|
||||
if (otherMasterKey?.trustLevel?.isVerified() == false) {
|
||||
crossSigningService.markMyMasterKeyAsTrusted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (otherUserId == userId) {
|
||||
// If me it's reasonable to sign and upload the device signature
|
||||
// Notice that i might not have the private keys, so may not be able to do it
|
||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO what if the otherDevice is not in this list? and should we
|
||||
verifiedDevices.forEach {
|
||||
setDeviceVerified(otherUserId, it)
|
||||
}
|
||||
transport.done(transactionId)
|
||||
state = VerificationTxState.Verified
|
||||
}
|
||||
|
||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||
// TODO should not override cross sign status
|
||||
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
||||
userId,
|
||||
deviceId)
|
||||
trust(otherMasterKeyIsVerified,
|
||||
verifiedDevices,
|
||||
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
|
@ -350,11 +304,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||
}
|
||||
|
||||
protected fun sendToOther(type: String,
|
||||
keyToDevice: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
protected fun <T> sendToOther(type: String,
|
||||
keyToDevice: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
||||
}
|
||||
|
||||
|
@ -376,11 +330,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
override fun supportsEmoji(): Boolean {
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI) == true
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI).orFalse()
|
||||
}
|
||||
|
||||
override fun supportsDecimal(): Boolean {
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL) == true
|
||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL).orFalse()
|
||||
}
|
||||
|
||||
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
|
||||
|
@ -393,7 +347,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return null
|
||||
}
|
||||
|
||||
protected fun macUsingAgreedMethod(message: String, info: String): String? {
|
||||
private fun macUsingAgreedMethod(message: String, info: String): String? {
|
||||
if (SAS_MAC_SHA256_LONGKDF.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
||||
return getSAS().calculateMacLongKdf(message, info)
|
||||
} else if (SAS_MAC_SHA256.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
|
||||
|
@ -443,7 +397,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
* For each group of 6 bits, look up the emoji from Appendix A corresponding
|
||||
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
||||
*/
|
||||
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
||||
private fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
||||
val b0 = byteArray[0].toUnsignedInt()
|
||||
val b1 = byteArray[1].toUnsignedInt()
|
||||
val b2 = byteArray[2].toUnsignedInt()
|
||||
|
|
|
@ -18,18 +18,16 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
||||
|
||||
interface VerificationInfo {
|
||||
interface VerificationInfo<ValidObjectType> {
|
||||
fun toEventContent(): Content? = null
|
||||
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
||||
fun isValid(): Boolean
|
||||
|
||||
fun asValidObject(): ValidObjectType?
|
||||
|
||||
/**
|
||||
* String to identify the transaction.
|
||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
val transactionID: String?
|
||||
|
||||
// TODO Refacto Put the relatesTo here or at least in Message sent in Room parent?
|
||||
// val relatesTo: RelationDefaultContent?
|
||||
val transactionId: String?
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
internal interface VerificationInfoAccept : VerificationInfo {
|
||||
internal interface VerificationInfoAccept : VerificationInfo<ValidVerificationInfoAccept> {
|
||||
/**
|
||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||
*/
|
||||
|
@ -41,6 +41,24 @@ internal interface VerificationInfoAccept : VerificationInfo {
|
|||
* and the canonical JSON representation of the m.key.verification.start message.
|
||||
*/
|
||||
var commitment: String?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoAccept? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validKeyAgreementProtocol = keyAgreementProtocol?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validHash = hash?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validMessageAuthenticationCode = messageAuthenticationCode?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validCommitment = commitment?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoAccept(
|
||||
validTransactionId,
|
||||
validKeyAgreementProtocol,
|
||||
validHash,
|
||||
validMessageAuthenticationCode,
|
||||
validShortAuthenticationStrings,
|
||||
validCommitment
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface VerificationInfoAcceptFactory {
|
||||
|
@ -52,3 +70,12 @@ internal interface VerificationInfoAcceptFactory {
|
|||
messageAuthenticationCode: String,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept
|
||||
}
|
||||
|
||||
internal data class ValidVerificationInfoAccept(
|
||||
val transactionId: String,
|
||||
val keyAgreementProtocol: String,
|
||||
val hash: String,
|
||||
val messageAuthenticationCode: String,
|
||||
val shortAuthenticationStrings: List<String>,
|
||||
var commitment: String?
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
internal interface VerificationInfoCancel : VerificationInfo {
|
||||
internal interface VerificationInfoCancel : VerificationInfo<ValidVerificationInfoCancel> {
|
||||
/**
|
||||
* machine-readable reason for cancelling, see [CancelCode]
|
||||
*/
|
||||
|
@ -25,4 +25,21 @@ internal interface VerificationInfoCancel : VerificationInfo {
|
|||
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
||||
*/
|
||||
val reason: String?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoCancel? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validCode = code?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoCancel(
|
||||
validTransactionId,
|
||||
validCode,
|
||||
reason
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ValidVerificationInfoCancel(
|
||||
val transactionId: String,
|
||||
val code: String,
|
||||
val reason: String?
|
||||
)
|
||||
|
|
|
@ -15,4 +15,14 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
interface VerificationInfoDone : VerificationInfo
|
||||
internal interface VerificationInfoDone : VerificationInfo<ValidVerificationInfoDone> {
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoDone? {
|
||||
if (transactionId.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return ValidVerificationInfoDone
|
||||
}
|
||||
}
|
||||
|
||||
internal object ValidVerificationInfoDone
|
||||
|
|
|
@ -18,13 +18,28 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||
/**
|
||||
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
||||
*/
|
||||
internal interface VerificationInfoKey : VerificationInfo {
|
||||
internal interface VerificationInfoKey : VerificationInfo<ValidVerificationInfoKey> {
|
||||
/**
|
||||
* The device’s ephemeral public key, as an unpadded base64 string
|
||||
*/
|
||||
val key: String?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoKey? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validKey = key?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoKey(
|
||||
validTransactionId,
|
||||
validKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface VerificationInfoKeyFactory {
|
||||
fun create(tid: String, pubKey: String): VerificationInfoKey
|
||||
}
|
||||
|
||||
internal data class ValidVerificationInfoKey(
|
||||
val transactionId: String,
|
||||
val key: String
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
internal interface VerificationInfoMac : VerificationInfo {
|
||||
internal interface VerificationInfoMac : VerificationInfo<ValidVerificationInfoMac> {
|
||||
/**
|
||||
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
|
||||
*/
|
||||
|
@ -28,8 +28,26 @@ internal interface VerificationInfoMac : VerificationInfo {
|
|||
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
||||
*/
|
||||
val keys: String?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoMac? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validMac = mac?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validKeys = keys?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoMac(
|
||||
validTransactionId,
|
||||
validMac,
|
||||
validKeys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface VerificationInfoMacFactory {
|
||||
fun create(tid: String, mac: Map<String, String>, keys: String) : VerificationInfoMac
|
||||
fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||
}
|
||||
|
||||
internal data class ValidVerificationInfoMac(
|
||||
val transactionId: String,
|
||||
val mac: Map<String, String>,
|
||||
val keys: String
|
||||
)
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoReady
|
||||
|
||||
/**
|
||||
* A new event type is added to the key verification framework: m.key.verification.ready,
|
||||
* which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event.
|
||||
|
@ -23,7 +25,7 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||
* with a m.key.verification.start event instead.
|
||||
*/
|
||||
|
||||
interface VerificationInfoReady : VerificationInfo {
|
||||
internal interface VerificationInfoReady : VerificationInfo<ValidVerificationInfoReady> {
|
||||
/**
|
||||
* The ID of the device that sent the m.key.verification.ready message
|
||||
*/
|
||||
|
@ -33,6 +35,18 @@ interface VerificationInfoReady : VerificationInfo {
|
|||
* An array of verification methods that the device supports
|
||||
*/
|
||||
val methods: List<String>?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoReady? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoReady(
|
||||
validTransactionId,
|
||||
validFromDevice,
|
||||
validMethods
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface MessageVerificationReadyFactory {
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
interface VerificationInfoRequest : VerificationInfo {
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||
|
||||
internal interface VerificationInfoRequest : VerificationInfo<ValidVerificationInfoRequest> {
|
||||
|
||||
/**
|
||||
* Required. The device ID which is initiating the request.
|
||||
|
@ -33,4 +35,18 @@ interface VerificationInfoRequest : VerificationInfo {
|
|||
* the message should be ignored by the receiver.
|
||||
*/
|
||||
val timestamp: Long?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoRequest? {
|
||||
// FIXME No check on Timestamp?
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return ValidVerificationInfoRequest(
|
||||
validTransactionId,
|
||||
validFromDevice,
|
||||
validMethods,
|
||||
timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
internal interface VerificationInfoStart : VerificationInfo {
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
|
||||
internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInfoStart> {
|
||||
|
||||
val method: String?
|
||||
|
||||
|
@ -57,5 +61,64 @@ internal interface VerificationInfoStart : VerificationInfo {
|
|||
*/
|
||||
val sharedSecret: String?
|
||||
|
||||
fun toCanonicalJson(): String?
|
||||
fun toCanonicalJson(): String
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoStart? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
return when (method) {
|
||||
VERIFICATION_METHOD_SAS -> {
|
||||
val validKeyAgreementProtocols = keyAgreementProtocols?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null
|
||||
val validMessageAuthenticationCodes = messageAuthenticationCodes
|
||||
?.takeIf {
|
||||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
|| it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
||||
}
|
||||
?: return null
|
||||
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
||||
|
||||
ValidVerificationInfoStart.SasVerificationInfoStart(
|
||||
validTransactionId,
|
||||
validFromDevice,
|
||||
validKeyAgreementProtocols,
|
||||
validHashes,
|
||||
validMessageAuthenticationCodes,
|
||||
validShortAuthenticationStrings,
|
||||
canonicalJson = toCanonicalJson()
|
||||
)
|
||||
}
|
||||
VERIFICATION_METHOD_RECIPROCATE -> {
|
||||
val validSharedSecret = sharedSecret?.takeIf { it.isNotEmpty() } ?: return null
|
||||
|
||||
ValidVerificationInfoStart.ReciprocateVerificationInfoStart(
|
||||
validTransactionId,
|
||||
validFromDevice,
|
||||
validSharedSecret
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ValidVerificationInfoStart(
|
||||
open val transactionId: String,
|
||||
open val fromDevice: String) {
|
||||
data class SasVerificationInfoStart(
|
||||
override val transactionId: String,
|
||||
override val fromDevice: String,
|
||||
val keyAgreementProtocols: List<String>,
|
||||
val hashes: List<String>,
|
||||
val messageAuthenticationCodes: List<String>,
|
||||
val shortAuthenticationStrings: List<String>,
|
||||
val canonicalJson: String
|
||||
) : ValidVerificationInfoStart(transactionId, fromDevice)
|
||||
|
||||
data class ReciprocateVerificationInfoStart(
|
||||
override val transactionId: String,
|
||||
override val fromDevice: String,
|
||||
val sharedSecret: String
|
||||
) : ValidVerificationInfoStart(transactionId, fromDevice)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
|
||||
|
@ -27,18 +28,18 @@ internal interface VerificationTransport {
|
|||
/**
|
||||
* Sends a message
|
||||
*/
|
||||
fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?)
|
||||
fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?)
|
||||
|
||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit)
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit)
|
||||
|
||||
fun cancelTransaction(transactionId: String,
|
||||
otherUserId: String,
|
||||
|
@ -64,7 +65,7 @@ internal interface VerificationTransport {
|
|||
* Create start for SAS verification
|
||||
*/
|
||||
fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -74,7 +75,7 @@ internal interface VerificationTransport {
|
|||
* Create start for QR code verification
|
||||
*/
|
||||
fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart
|
||||
|
||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||
|
|
|
@ -21,8 +21,8 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.Operation
|
||||
import androidx.work.WorkInfo
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
@ -65,16 +65,15 @@ internal class VerificationTransportRoomMessage(
|
|||
private val userId: String,
|
||||
private val userDeviceId: String?,
|
||||
private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val tx: DefaultVerificationTransaction?
|
||||
) : VerificationTransport {
|
||||
|
||||
override fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
override fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
Timber.d("## SAS sending msg type $type")
|
||||
Timber.v("## SAS sending msg info $verificationInfo")
|
||||
val event = createEventAndLocalEcho(
|
||||
|
@ -138,26 +137,33 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
|
||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||
// This transport requires a room
|
||||
requireNotNull(roomId)
|
||||
|
||||
val validInfo = ValidVerificationInfoRequest(
|
||||
transactionId = "",
|
||||
fromDevice = userDeviceId ?: "",
|
||||
methods = supportedMethods,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val info = MessageVerificationRequestContent(
|
||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
fromDevice = userDeviceId ?: "",
|
||||
fromDevice = validInfo.fromDevice,
|
||||
toUserId = otherUserId,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
methods = supportedMethods
|
||||
timestamp = validInfo.timestamp,
|
||||
methods = validInfo.methods
|
||||
)
|
||||
val content = info.toContent()
|
||||
|
||||
val event = createEventAndLocalEcho(
|
||||
localID,
|
||||
localId,
|
||||
EventType.MESSAGE,
|
||||
roomId,
|
||||
content
|
||||
|
@ -192,8 +198,8 @@ internal class VerificationTransportRoomMessage(
|
|||
?.let { wInfo ->
|
||||
if (wInfo.outputData.getBoolean("failed", false)) {
|
||||
callback(null, null)
|
||||
} else if (wInfo.outputData.getString(localID) != null) {
|
||||
callback(wInfo.outputData.getString(localID), info)
|
||||
} else if (wInfo.outputData.getString(localId) != null) {
|
||||
callback(wInfo.outputData.getString(localId), validInfo)
|
||||
} else {
|
||||
callback(null, null)
|
||||
}
|
||||
|
@ -272,7 +278,7 @@ internal class VerificationTransportRoomMessage(
|
|||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
|
||||
|
||||
override fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -286,14 +292,14 @@ internal class VerificationTransportRoomMessage(
|
|||
VERIFICATION_METHOD_SAS,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
eventId = transactionId
|
||||
),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return MessageVerificationStartContent(
|
||||
fromDevice,
|
||||
|
@ -304,7 +310,7 @@ internal class VerificationTransportRoomMessage(
|
|||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
eventId = transactionId
|
||||
),
|
||||
sharedSecret
|
||||
)
|
||||
|
@ -321,15 +327,15 @@ internal class VerificationTransportRoomMessage(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createEventAndLocalEcho(localID: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = System.currentTimeMillis(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = type,
|
||||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
).also {
|
||||
localEchoEventFactory.createLocalEcho(it)
|
||||
}
|
||||
|
@ -347,7 +353,6 @@ internal class VerificationTransportRoomMessage(
|
|||
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val monarchy: Monarchy,
|
||||
@SessionId
|
||||
private val sessionId: String,
|
||||
@UserId
|
||||
|
@ -357,6 +362,6 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
|||
private val localEchoEventFactory: LocalEchoEventFactory) {
|
||||
|
||||
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
||||
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, monarchy, localEchoEventFactory, tx)
|
||||
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -46,28 +47,34 @@ internal class VerificationTransportToDevice(
|
|||
) : VerificationTransport {
|
||||
|
||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
||||
localID: String,
|
||||
localId: String,
|
||||
otherUserId: String,
|
||||
roomId: String?,
|
||||
toDevices: List<String>?,
|
||||
callback: (String?, VerificationInfoRequest?) -> Unit) {
|
||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
val keyReq = KeyVerificationRequest(
|
||||
fromDevice = myDeviceId,
|
||||
val validKeyReq = ValidVerificationInfoRequest(
|
||||
transactionId = localId,
|
||||
fromDevice = myDeviceId ?: "",
|
||||
methods = supportedMethods,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
transactionID = localID
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
val keyReq = KeyVerificationRequest(
|
||||
fromDevice = validKeyReq.fromDevice,
|
||||
methods = validKeyReq.methods,
|
||||
timestamp = validKeyReq.timestamp,
|
||||
transactionId = validKeyReq.transactionId
|
||||
)
|
||||
toDevices?.forEach {
|
||||
contentMap.setObject(otherUserId, it, keyReq)
|
||||
}
|
||||
sendToDeviceTask
|
||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localID)) {
|
||||
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) {
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
||||
callback.invoke(localID, keyReq)
|
||||
callback.invoke(localId, validKeyReq)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
|
@ -103,11 +110,11 @@ internal class VerificationTransportToDevice(
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
override fun <T> sendToOther(type: String,
|
||||
verificationInfo: VerificationInfo<T>,
|
||||
nextState: VerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
Timber.d("## SAS sending msg type $type")
|
||||
Timber.v("## SAS sending msg info $verificationInfo")
|
||||
val tx = tx ?: return
|
||||
|
@ -197,7 +204,7 @@ internal class VerificationTransportToDevice(
|
|||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
|
||||
|
||||
override fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
|
@ -205,7 +212,7 @@ internal class VerificationTransportToDevice(
|
|||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
VERIFICATION_METHOD_SAS,
|
||||
transactionID,
|
||||
transactionId,
|
||||
keyAgreementProtocols,
|
||||
hashes,
|
||||
messageAuthenticationCodes,
|
||||
|
@ -214,12 +221,12 @@ internal class VerificationTransportToDevice(
|
|||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
transactionId: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
transactionID,
|
||||
transactionId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -229,7 +236,7 @@ internal class VerificationTransportToDevice(
|
|||
|
||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||
return KeyVerificationReady(
|
||||
transactionID = tid,
|
||||
transactionId = tid,
|
||||
fromDevice = fromDevice,
|
||||
methods = methods
|
||||
)
|
||||
|
|
|
@ -16,25 +16,22 @@
|
|||
|
||||
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64Safe
|
||||
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.crypto.verification.ValidVerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.exhaustive
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultQrCodeVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
override val transactionId: String,
|
||||
override val otherUserId: String,
|
||||
override var otherDeviceId: String?,
|
||||
|
@ -45,7 +42,15 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
val userId: String,
|
||||
val deviceId: String,
|
||||
override val isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction {
|
||||
) : DefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
crossSigningService,
|
||||
userId,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
QrCodeVerificationTransaction {
|
||||
|
||||
override val qrCodeText: String?
|
||||
get() = qrCodeData?.toEncodedString()
|
||||
|
@ -78,26 +83,24 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
}
|
||||
|
||||
// check master key
|
||||
val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
|
||||
when (otherQrCodeData) {
|
||||
is QrCodeData.VerifyingAnotherUser -> {
|
||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) {
|
||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
} else Unit
|
||||
}
|
||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
} else Unit
|
||||
}
|
||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
||||
!= crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
|
@ -154,10 +157,12 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
start(otherQrCodeData.sharedSecret)
|
||||
|
||||
// Trust the other user
|
||||
trust(canTrustOtherUserMasterKey, toVerifyDeviceIds.distinct())
|
||||
trust(canTrustOtherUserMasterKey,
|
||||
toVerifyDeviceIds.distinct(),
|
||||
eventuallyMarkMyMasterKeyAsTrusted = true)
|
||||
}
|
||||
|
||||
fun start(remoteSecret: String) {
|
||||
private fun start(remoteSecret: String) {
|
||||
if (state != VerificationTxState.None) {
|
||||
Timber.e("## Verification QR: start verification from invalid state")
|
||||
// should I cancel??
|
||||
|
@ -179,9 +184,6 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
)
|
||||
}
|
||||
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
cancel(CancelCode.User)
|
||||
}
|
||||
|
@ -194,14 +196,14 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
override fun isToDeviceTransport() = false
|
||||
|
||||
// Other user has scanned our QR code. check that the secret matched, so we can trust him
|
||||
fun onStartReceived(startReq: VerificationInfoStart) {
|
||||
fun onStartReceived(startReq: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
|
||||
if (qrCodeData == null) {
|
||||
// Should not happen
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if (startReq.sharedSecret?.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) {
|
||||
if (startReq.sharedSecret.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) {
|
||||
// Ok, we can trust the other user
|
||||
// We can only trust the master key in this case
|
||||
// But first, ask the user for a confirmation
|
||||
|
@ -213,7 +215,7 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
}
|
||||
|
||||
override fun otherUserScannedMyQrCode() {
|
||||
trust(true, emptyList())
|
||||
trust(true, emptyList(), true)
|
||||
}
|
||||
|
||||
override fun otherUserDidNotScannedMyQrCode() {
|
||||
|
@ -221,46 +223,4 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
// At least remove the transaction...
|
||||
state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true)
|
||||
}
|
||||
|
||||
private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) {
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (canTrustOtherUserMasterKey) {
|
||||
if (otherUserId != userId) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Mark my keys as trusted locally
|
||||
crossSigningService.markMyMasterKeyAsTrusted()
|
||||
}
|
||||
}
|
||||
|
||||
if (otherUserId == userId) {
|
||||
// If me it's reasonable to sign and upload the device signature
|
||||
// Notice that i might not have the private keys, so may not be able to do it
|
||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO what if the otherDevice is not in this list? and should we
|
||||
toVerifyDeviceIds.forEach {
|
||||
setDeviceVerified(otherUserId, it)
|
||||
}
|
||||
transport.done(transactionId)
|
||||
state = VerificationTxState.Verified
|
||||
}
|
||||
|
||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||
// TODO should not override cross sign status
|
||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
userId,
|
||||
deviceId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ import javax.inject.Inject
|
|||
* (the transaction ID), this id is used when receiving an event from a sync to check if this event
|
||||
* is matching an existing local echo.
|
||||
*
|
||||
* The transactionID is used as loc
|
||||
* The transactionId is used as loc
|
||||
*/
|
||||
internal class LocalEchoEventFactory @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
|
@ -335,25 +335,25 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = content.toContent(),
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
||||
fun createVerificationRequest(roomId: String, fromDevice: String, toUserId: String, methods: List<String>): Event {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = MessageVerificationRequestContent(
|
||||
body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
|
||||
|
@ -362,7 +362,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
timestamp = System.currentTimeMillis(),
|
||||
methods = methods
|
||||
).toContent(),
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -469,16 +469,16 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
}
|
||||
*/
|
||||
fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localID,
|
||||
eventId = localId,
|
||||
type = EventType.REDACTION,
|
||||
redacts = eventId,
|
||||
content = reason?.let { mapOf("reason" to it).toContent() },
|
||||
unsignedData = UnsignedData(age = null, transactionId = localID)
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.Session
|
|||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||
|
|
|
@ -178,7 +178,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
putParcelable(MvRx.KEY_ARG, VerificationArgs(
|
||||
state.otherUserMxItem?.id ?: "",
|
||||
// If it was outgoing it.transaction id would be null, but the pending request
|
||||
// would be updated (from localID to txId)
|
||||
// would be updated (from localId to txId)
|
||||
state.pendingRequest.invoke()?.transactionId ?: state.transactionId))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ import im.vector.matrix.android.api.util.MatrixItem
|
|||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import timber.log.Timber
|
||||
|
@ -155,10 +155,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
when (action) {
|
||||
is VerificationAction.RequestVerificationByDM -> {
|
||||
if (roomId == null) {
|
||||
val localID = LocalEcho.createLocalEchoId()
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
setState {
|
||||
copy(
|
||||
pendingLocalId = localID,
|
||||
pendingLocalId = localId,
|
||||
pendingRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
@ -387,8 +387,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
if (pr.localID == state.pendingLocalId
|
||||
|| pr.localID == state.pendingRequest.invoke()?.localID
|
||||
if (pr.localId == state.pendingLocalId
|
||||
|| pr.localId == state.pendingRequest.invoke()?.localId
|
||||
|| state.pendingRequest.invoke()?.transactionId == pr.transactionId) {
|
||||
setState {
|
||||
copy(pendingRequest = Success(pr))
|
||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.Session
|
|||
import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
|
|
Loading…
Add table
Reference in a new issue