Merge pull request #1111 from vector-im/feature/verification_code

Feature/verification code
This commit is contained in:
Benoit Marty 2020-03-06 15:02:57 +01:00 committed by GitHub
commit 35179509f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 652 additions and 627 deletions

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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>
)

View file

@ -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?
)

View file

@ -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

View file

@ -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 {

View file

@ -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(

View file

@ -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
)

View file

@ -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 {

View file

@ -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(

View file

@ -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(

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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.
* Alices 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 Bobs device has selected to use, out of the list proposed by Alices 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,

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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 devices 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
}
}

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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 Bobs m.key.verification.key and the content of Alices 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
}

View file

@ -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")

View file

@ -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)
}
}

View file

@ -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.
// Bobs device compares these with the HMAC values given in the m.key.verification.mac message.
// If everything matches, then consider Alices 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()

View file

@ -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.
* Alices 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?
}

View file

@ -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 Bobs device has selected to use, out of the list proposed by Alices 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?
)

View file

@ -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?
)

View file

@ -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

View file

@ -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 devices 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
)

View file

@ -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
)

View file

@ -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 {

View file

@ -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
)
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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
)

View file

@ -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)
}
}

View file

@ -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)
)
}

View file

@ -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

View file

@ -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))
})
}

View file

@ -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))

View file

@ -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