Remove some dead code.
This commit is contained in:
parent
54c3b4192e
commit
f8ad024f1b
@ -1,627 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertNotNull
|
|
||||||
import org.junit.Assert.assertNull
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Assert.fail
|
|
||||||
import org.junit.FixMethodOrder
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
|
||||||
class SASTest : InstrumentedTest {
|
|
||||||
private val mTestHelper = CommonTestHelper(context())
|
|
||||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_aliceStartThenAliceCancel() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val bobTxCreatedLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
bobTxCreatedLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
|
||||||
bobSession.myUserId,
|
|
||||||
bobSession.cryptoService().getMyDevice().deviceId,
|
|
||||||
null)
|
|
||||||
assertNotNull("Alice should have a started transaction", txID)
|
|
||||||
|
|
||||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
|
||||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
|
||||||
|
|
||||||
mTestHelper.await(bobTxCreatedLatch)
|
|
||||||
bobVerificationService.removeListener(bobListener)
|
|
||||||
|
|
||||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
|
||||||
|
|
||||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
|
||||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
|
||||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
|
||||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
|
||||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
|
||||||
|
|
||||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
|
||||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
|
||||||
|
|
||||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
|
||||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
|
||||||
|
|
||||||
// Let's cancel from alice side
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
|
|
||||||
val bobListener2 = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if (tx.transactionId == txID) {
|
|
||||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
|
||||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
|
||||||
cancelLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener2)
|
|
||||||
|
|
||||||
aliceSasTx.cancel(CancelCode.User)
|
|
||||||
mTestHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
|
||||||
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
|
||||||
|
|
||||||
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
|
||||||
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
|
||||||
|
|
||||||
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
|
||||||
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
|
||||||
|
|
||||||
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
|
||||||
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
|
||||||
|
|
||||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
|
||||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_key_agreement_protocols_must_include_curve25519() {
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val protocols = listOf("meh_dont_know")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var cancelReason: CancelCode? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
|
||||||
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
|
||||||
cancelLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobSession.cryptoService().verificationService().addListener(bobListener)
|
|
||||||
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
|
||||||
|
|
||||||
mTestHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val mac = listOf("shaBit")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var canceledToDeviceEvent: Event? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
|
||||||
|
|
||||||
mTestHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_key_agreement_short_code_include_decimal() {
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val codes = listOf("bin", "foo", "bar")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var canceledToDeviceEvent: Event? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
|
||||||
|
|
||||||
mTestHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fakeBobStart(bobSession: Session,
|
|
||||||
aliceUserID: String?,
|
|
||||||
aliceDevice: String?,
|
|
||||||
tid: String,
|
|
||||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
|
||||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
|
||||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
|
||||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
|
|
||||||
val startMessage = KeyVerificationStart(
|
|
||||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
|
||||||
method = VerificationMethod.SAS.toValue(),
|
|
||||||
transactionId = tid,
|
|
||||||
keyAgreementProtocols = protocols,
|
|
||||||
hashes = hashes,
|
|
||||||
messageAuthenticationCodes = mac,
|
|
||||||
shortAuthenticationStrings = codes
|
|
||||||
)
|
|
||||||
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
|
||||||
|
|
||||||
// TODO val sendLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession.cryptoRestClient.sendToDevice(
|
|
||||||
// TODO EventType.KEY_VERIFICATION_START,
|
|
||||||
// TODO contentMap,
|
|
||||||
// TODO tid,
|
|
||||||
// TODO TestMatrixCallback<Void>(sendLatch)
|
|
||||||
// TODO )
|
|
||||||
}
|
|
||||||
|
|
||||||
// any two devices may only have at most one key verification in flight at a time.
|
|
||||||
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
|
||||||
@Test
|
|
||||||
fun test_aliceStartTwoRequests() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceCreatedLatch = CountDownLatch(2)
|
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
|
||||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
|
||||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
|
||||||
aliceCreatedLatch.countDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
|
||||||
aliceCancelledLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession!!.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
|
|
||||||
mTestHelper.await(aliceCreatedLatch)
|
|
||||||
mTestHelper.await(aliceCancelledLatch)
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that when alice starts a 'correct' request, bob agrees.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun test_aliceAndBobAgreement() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
var accepted: ValidVerificationInfoAccept? = null
|
|
||||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
|
||||||
accepted = at.accepted
|
|
||||||
startReq = at.startReq
|
|
||||||
aliceAcceptedLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
bobVerificationService.removeListener(this)
|
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
|
||||||
at.performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
mTestHelper.await(aliceAcceptedLatch)
|
|
||||||
|
|
||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
|
||||||
|
|
||||||
// check that agreement is valid
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_aliceAndBobSASCode() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
|
||||||
when (uxState) {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
aliceSASLatch.countDown()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
|
||||||
when (uxState) {
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
|
||||||
bobSASLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
mTestHelper.await(aliceSASLatch)
|
|
||||||
mTestHelper.await(bobSASLatch)
|
|
||||||
|
|
||||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
|
||||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
|
||||||
|
|
||||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
|
||||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_happyPath() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
var matchOnce = true
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
|
||||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
|
||||||
when (uxState) {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
tx.userHasVerifiedShortCode()
|
|
||||||
}
|
|
||||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
|
||||||
if (matchOnce) {
|
|
||||||
matchOnce = false
|
|
||||||
aliceSASLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
var acceptOnce = true
|
|
||||||
var matchOnce = true
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
|
||||||
Log.v("TEST", "== bobState ${uxState.name}")
|
|
||||||
when (uxState) {
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
|
||||||
if (acceptOnce) {
|
|
||||||
acceptOnce = false
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
if (matchOnce) {
|
|
||||||
matchOnce = false
|
|
||||||
tx.userHasVerifiedShortCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
|
||||||
bobSASLatch.countDown()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
mTestHelper.await(aliceSASLatch)
|
|
||||||
mTestHelper.await(bobSASLatch)
|
|
||||||
|
|
||||||
// Assert that devices are verified
|
|
||||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
|
|
||||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
|
||||||
|
|
||||||
// latch wait a bit again
|
|
||||||
Thread.sleep(1000)
|
|
||||||
|
|
||||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
|
||||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_ConcurrentStart() {
|
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
|
||||||
bobSession.myUserId,
|
|
||||||
cryptoTestData.roomId
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestID : String? = null
|
|
||||||
|
|
||||||
mTestHelper.waitWithLatch {
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
|
||||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
|
||||||
requestID = prAlicePOV?.transactionId
|
|
||||||
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
|
||||||
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.v("TEST", "== requestID is $requestID")
|
|
||||||
|
|
||||||
mTestHelper.waitWithLatch {
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
|
||||||
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
|
|
||||||
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
|
||||||
prBobPOV?.transactionId == requestID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bobVerificationService.readyPendingVerification(
|
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
|
||||||
aliceSession.myUserId,
|
|
||||||
requestID!!
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait for alice to get the ready
|
|
||||||
mTestHelper.waitWithLatch {
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
|
||||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
|
||||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
|
||||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start concurrent!
|
|
||||||
aliceVerificationService.beginKeyVerificationInDMs(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
requestID!!,
|
|
||||||
cryptoTestData.roomId,
|
|
||||||
bobSession.myUserId,
|
|
||||||
bobSession.sessionParams.deviceId!!)
|
|
||||||
|
|
||||||
bobVerificationService.beginKeyVerificationInDMs(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
requestID!!,
|
|
||||||
cryptoTestData.roomId,
|
|
||||||
aliceSession.myUserId,
|
|
||||||
aliceSession.sessionParams.deviceId!!)
|
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
|
||||||
var alicePovTx: SasVerificationTransaction?
|
|
||||||
var bobPovTx: SasVerificationTransaction?
|
|
||||||
|
|
||||||
mTestHelper.waitWithLatch {
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
|
||||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
|
||||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
|
||||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// wait for alice to get the ready
|
|
||||||
mTestHelper.waitWithLatch {
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
|
||||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
|
||||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.actions
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class SetDeviceVerificationAction @Inject constructor(
|
|
||||||
private val cryptoStore: IMXCryptoStore,
|
|
||||||
@UserId private val userId: String,
|
|
||||||
private val defaultKeysBackupService: DefaultKeysBackupService) {
|
|
||||||
|
|
||||||
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
|
||||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
if (null == device) {
|
|
||||||
Timber.w("## setDeviceVerification() : Unknown device $userId:$deviceId")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device.isVerified != trustLevel.isVerified()) {
|
|
||||||
if (userId == this.userId) {
|
|
||||||
// If one of the user's own devices is being marked as verified / unverified,
|
|
||||||
// check the key backup status, since whether or not we use this depends on
|
|
||||||
// whether it has a signature from a verified device
|
|
||||||
defaultKeysBackupService.checkAndStartKeysBackup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device.trustLevel != trustLevel) {
|
|
||||||
device.trustLevel = trustLevel
|
|
||||||
cryptoStore.setDeviceTrust(userId, deviceId, trustLevel.crossSigningVerified, trustLevel.locallyVerified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,268 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import org.matrix.android.sdk.BuildConfig
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
|
||||||
override val userId: String,
|
|
||||||
override val deviceId: String?,
|
|
||||||
private val cryptoStore: IMXCryptoStore,
|
|
||||||
crossSigningService: CrossSigningService,
|
|
||||||
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager: IncomingGossipingRequestManager,
|
|
||||||
deviceFingerprint: String,
|
|
||||||
transactionId: String,
|
|
||||||
otherUserID: String,
|
|
||||||
private val autoAccept: Boolean = false
|
|
||||||
) : SASDefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
cryptoStore,
|
|
||||||
crossSigningService,
|
|
||||||
outgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager,
|
|
||||||
deviceFingerprint,
|
|
||||||
transactionId,
|
|
||||||
otherUserID,
|
|
||||||
null,
|
|
||||||
isIncoming = true),
|
|
||||||
IncomingSasVerificationTransaction {
|
|
||||||
|
|
||||||
override val uxState: IncomingSasVerificationTransaction.UxState
|
|
||||||
get() {
|
|
||||||
return when (val immutableState = state) {
|
|
||||||
is VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT
|
|
||||||
is VerificationTxState.SendingAccept,
|
|
||||||
is VerificationTxState.Accepted,
|
|
||||||
is VerificationTxState.OnKeyReceived,
|
|
||||||
is VerificationTxState.SendingKey,
|
|
||||||
is VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
|
|
||||||
is VerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS
|
|
||||||
is VerificationTxState.ShortCodeAccepted,
|
|
||||||
is VerificationTxState.SendingMac,
|
|
||||||
is VerificationTxState.MacSent,
|
|
||||||
is VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
|
|
||||||
is VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED
|
|
||||||
is VerificationTxState.Cancelled -> {
|
|
||||||
if (immutableState.byMe) {
|
|
||||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME
|
|
||||||
} else {
|
|
||||||
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> IncomingSasVerificationTransaction.UxState.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun acceptVerification() {
|
|
||||||
this.performAccept()
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
// should I cancel??
|
|
||||||
throw IllegalStateException("Interactive Key verification already started")
|
|
||||||
}
|
|
||||||
this.startReq = startReq
|
|
||||||
state = VerificationTxState.OnStarted
|
|
||||||
this.otherDeviceId = startReq.fromDevice
|
|
||||||
|
|
||||||
if (autoAccept) {
|
|
||||||
performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun performAccept() {
|
|
||||||
if (state != VerificationTxState.OnStarted) {
|
|
||||||
Timber.e("## SAS Cannot perform accept from state $state")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) }
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
// hashing, hmac, or SAS method, then it should send a m.key.verification.cancel message
|
|
||||||
if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() }
|
|
||||||
|| agreedShortCode.isNullOrEmpty()) {
|
|
||||||
// Failed to find agreement
|
|
||||||
Timber.e("## SAS Failed to find agreement ")
|
|
||||||
cancel(CancelCode.UnknownMethod)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bob’s device ensures that it has a copy of Alice’s device key.
|
|
||||||
val mxDeviceInfo = cryptoStore.getUserDevice(userId = otherUserId, deviceId = otherDeviceId!!)
|
|
||||||
|
|
||||||
if (mxDeviceInfo?.fingerprint() == null) {
|
|
||||||
Timber.e("## SAS Failed to find device key ")
|
|
||||||
// TODO force download keys!!
|
|
||||||
// would be probably better to download the keys
|
|
||||||
// for now I cancel
|
|
||||||
cancel(CancelCode.User)
|
|
||||||
} else {
|
|
||||||
// val otherKey = info.identityKey()
|
|
||||||
// need to jump back to correct thread
|
|
||||||
val accept = transport.createAccept(
|
|
||||||
tid = transactionId,
|
|
||||||
keyAgreementProtocol = agreedProtocol!!,
|
|
||||||
hash = agreedHash!!,
|
|
||||||
messageAuthenticationCode = agreedMac!!,
|
|
||||||
shortAuthenticationStrings = agreedShortCode,
|
|
||||||
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
|
|
||||||
)
|
|
||||||
doAccept(accept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doAccept(accept: VerificationInfoAccept) {
|
|
||||||
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!!.canonicalJson
|
|
||||||
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
|
||||||
// we need to send this to other device now
|
|
||||||
state = VerificationTxState.SendingAccept
|
|
||||||
sendToOther(EventType.KEY_VERIFICATION_ACCEPT, accept, VerificationTxState.Accepted, CancelCode.User) {
|
|
||||||
if (state == VerificationTxState.SendingAccept) {
|
|
||||||
// It is possible that we receive the next event before this one :/, in this case we should keep state
|
|
||||||
state = VerificationTxState.Accepted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
|
||||||
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
otherKey = vKey.key
|
|
||||||
// Upon receipt of the m.key.verification.key message from Alice’s device,
|
|
||||||
// Bob’s device replies with a to_device message with type set to m.key.verification.key,
|
|
||||||
// sending Bob’s public key QB
|
|
||||||
val pubKey = getSAS().publicKey
|
|
||||||
|
|
||||||
val keyToDevice = transport.createKey(transactionId, pubKey)
|
|
||||||
// we need to send this to other device now
|
|
||||||
state = VerificationTxState.SendingKey
|
|
||||||
this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, VerificationTxState.KeySent, CancelCode.User) {
|
|
||||||
if (state == VerificationTxState.SendingKey) {
|
|
||||||
// It is possible that we receive the next event before this one :/, in this case we should keep state
|
|
||||||
state = VerificationTxState.KeySent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alice’s and Bob’s devices perform an Elliptic-curve Diffie-Hellman
|
|
||||||
// (calculate the point (x,y)=dAQB=dBQA and use x as the result of the ECDH),
|
|
||||||
// using the result as the shared secret.
|
|
||||||
|
|
||||||
getSAS().setTheirPublicKey(otherKey)
|
|
||||||
|
|
||||||
shortCodeBytes = calculateSASBytes()
|
|
||||||
|
|
||||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
|
||||||
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
|
||||||
Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
|
|
||||||
}
|
|
||||||
|
|
||||||
state = VerificationTxState.ShortCodeReady
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateSASBytes(): ByteArray {
|
|
||||||
when (accepted?.keyAgreementProtocol) {
|
|
||||||
KEY_AGREEMENT_V1 -> {
|
|
||||||
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
|
|
||||||
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
|
|
||||||
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
|
|
||||||
// - the Matrix ID of the user who sent the m.key.verification.start message,
|
|
||||||
// - the device ID of the device that sent the m.key.verification.start message,
|
|
||||||
// - the Matrix ID of the user who sent the m.key.verification.accept message,
|
|
||||||
// - he device ID of the device that sent the m.key.verification.accept message
|
|
||||||
// - the transaction ID.
|
|
||||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
|
||||||
|
|
||||||
// decimal: generate five bytes by using HKDF.
|
|
||||||
// emoji: generate six bytes by using HKDF.
|
|
||||||
return getSAS().generateShortCode(sasInfo, 6)
|
|
||||||
}
|
|
||||||
KEY_AGREEMENT_V2 -> {
|
|
||||||
// Adds the SAS public key, and separate by |
|
|
||||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$otherUserId|$otherDeviceId|$otherKey|$userId|$deviceId|${getSAS().publicKey}|$transactionId"
|
|
||||||
return getSAS().generateShortCode(sasInfo, 6)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Protocol has been checked earlier
|
|
||||||
throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
|
||||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
|
||||||
// Check for state?
|
|
||||||
if (state != VerificationTxState.SendingKey
|
|
||||||
&& state != VerificationTxState.KeySent
|
|
||||||
&& state != VerificationTxState.ShortCodeReady
|
|
||||||
&& state != VerificationTxState.ShortCodeAccepted
|
|
||||||
&& state != VerificationTxState.SendingMac
|
|
||||||
&& state != VerificationTxState.MacSent) {
|
|
||||||
Timber.e("## SAS I: received key from invalid state $state")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
theirMac = vMac
|
|
||||||
|
|
||||||
// Do I have my Mac?
|
|
||||||
if (myMac != null) {
|
|
||||||
// I can check
|
|
||||||
verifyMacs(vMac)
|
|
||||||
}
|
|
||||||
// Wait for ShortCode Accepted
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
|
||||||
userId: String,
|
|
||||||
deviceId: String?,
|
|
||||||
cryptoStore: IMXCryptoStore,
|
|
||||||
crossSigningService: CrossSigningService,
|
|
||||||
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager: IncomingGossipingRequestManager,
|
|
||||||
deviceFingerprint: String,
|
|
||||||
transactionId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String
|
|
||||||
) : SASDefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
cryptoStore,
|
|
||||||
crossSigningService,
|
|
||||||
outgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager,
|
|
||||||
deviceFingerprint,
|
|
||||||
transactionId,
|
|
||||||
otherUserId,
|
|
||||||
otherDeviceId,
|
|
||||||
isIncoming = false),
|
|
||||||
OutgoingSasVerificationTransaction {
|
|
||||||
|
|
||||||
override val uxState: OutgoingSasVerificationTransaction.UxState
|
|
||||||
get() {
|
|
||||||
return when (val immutableState = state) {
|
|
||||||
is VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START
|
|
||||||
is VerificationTxState.SendingStart,
|
|
||||||
is VerificationTxState.Started,
|
|
||||||
is VerificationTxState.OnAccepted,
|
|
||||||
is VerificationTxState.SendingKey,
|
|
||||||
is VerificationTxState.KeySent,
|
|
||||||
is VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
|
|
||||||
is VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS
|
|
||||||
is VerificationTxState.ShortCodeAccepted,
|
|
||||||
is VerificationTxState.SendingMac,
|
|
||||||
is VerificationTxState.MacSent,
|
|
||||||
is VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
|
|
||||||
is VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED
|
|
||||||
is VerificationTxState.Cancelled -> {
|
|
||||||
if (immutableState.byMe) {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
|
|
||||||
} else {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
|
|
||||||
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun acceptVerification() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
if (state != VerificationTxState.None) {
|
|
||||||
Timber.e("## SAS O: start verification from invalid state")
|
|
||||||
// should I cancel??
|
|
||||||
throw IllegalStateException("Interactive Key verification already started")
|
|
||||||
}
|
|
||||||
|
|
||||||
val startMessage = transport.createStartForSas(
|
|
||||||
deviceId ?: "",
|
|
||||||
transactionId,
|
|
||||||
KNOWN_AGREEMENT_PROTOCOLS,
|
|
||||||
KNOWN_HASHES,
|
|
||||||
KNOWN_MACS,
|
|
||||||
KNOWN_SHORT_CODES
|
|
||||||
)
|
|
||||||
|
|
||||||
startReq = startMessage.asValidObject() as? ValidVerificationInfoStart.SasVerificationInfoStart
|
|
||||||
state = VerificationTxState.SendingStart
|
|
||||||
|
|
||||||
sendToOther(
|
|
||||||
EventType.KEY_VERIFICATION_START,
|
|
||||||
startMessage,
|
|
||||||
VerificationTxState.Started,
|
|
||||||
CancelCode.User,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun request() {
|
|
||||||
// if (state != VerificationTxState.None) {
|
|
||||||
// Timber.e("## start verification from invalid state")
|
|
||||||
// // should I cancel??
|
|
||||||
// throw IllegalStateException("Interactive Key verification already started")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val requestMessage = KeyVerificationRequest(
|
|
||||||
// fromDevice = session.sessionParams.deviceId ?: "",
|
|
||||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
|
||||||
// timestamp = System.currentTimeMillis().toInt(),
|
|
||||||
// transactionId = transactionId
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// sendToOther(
|
|
||||||
// EventType.KEY_VERIFICATION_REQUEST,
|
|
||||||
// requestMessage,
|
|
||||||
// VerificationTxState.None,
|
|
||||||
// CancelCode.User,
|
|
||||||
// null
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
|
|
||||||
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
|
|
||||||
if (state != VerificationTxState.Started && state != VerificationTxState.SendingStart) {
|
|
||||||
Timber.e("## SAS O: received accept request from invalid state $state")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check that the agreement is correct
|
|
||||||
if (!KNOWN_AGREEMENT_PROTOCOLS.contains(accept.keyAgreementProtocol)
|
|
||||||
|| !KNOWN_HASHES.contains(accept.hash)
|
|
||||||
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
|
||||||
|| accept.shortAuthenticationStrings.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
|
||||||
Timber.e("## SAS O: received invalid accept")
|
|
||||||
cancel(CancelCode.UnknownMethod)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upon receipt of the m.key.verification.accept message from Bob’s device,
|
|
||||||
// Alice’s device stores the commitment value for later use.
|
|
||||||
accepted = accept
|
|
||||||
state = VerificationTxState.OnAccepted
|
|
||||||
|
|
||||||
// Alice’s device creates an ephemeral Curve25519 key pair (dA,QA),
|
|
||||||
// and replies with a to_device message with type set to “m.key.verification.key”, sending Alice’s public key QA
|
|
||||||
val pubKey = getSAS().publicKey
|
|
||||||
|
|
||||||
val keyToDevice = transport.createKey(transactionId, pubKey)
|
|
||||||
// we need to send this to other device now
|
|
||||||
state = VerificationTxState.SendingKey
|
|
||||||
sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, VerificationTxState.KeySent, CancelCode.User) {
|
|
||||||
// It is possible that we receive the next event before this one :/, in this case we should keep state
|
|
||||||
if (state == VerificationTxState.SendingKey) {
|
|
||||||
state = VerificationTxState.KeySent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
otherKey = vKey.key
|
|
||||||
// Upon receipt of the m.key.verification.key message from Bob’s device,
|
|
||||||
// Alice’s device checks that the commitment property from the Bob’s m.key.verification.accept
|
|
||||||
// message is the same as the expected value based on the value of the key property received
|
|
||||||
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
|
||||||
|
|
||||||
// check commitment
|
|
||||||
val concat = vKey.key + startReq!!.canonicalJson
|
|
||||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
|
||||||
|
|
||||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
|
||||||
getSAS().setTheirPublicKey(otherKey)
|
|
||||||
shortCodeBytes = calculateSASBytes()
|
|
||||||
state = VerificationTxState.ShortCodeReady
|
|
||||||
} else {
|
|
||||||
// bad commitment
|
|
||||||
cancel(CancelCode.MismatchedCommitment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateSASBytes(): ByteArray {
|
|
||||||
when (accepted?.keyAgreementProtocol) {
|
|
||||||
KEY_AGREEMENT_V1 -> {
|
|
||||||
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
|
|
||||||
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
|
|
||||||
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
|
|
||||||
// - the Matrix ID of the user who sent the m.key.verification.start message,
|
|
||||||
// - the device ID of the device that sent the m.key.verification.start message,
|
|
||||||
// - the Matrix ID of the user who sent the m.key.verification.accept message,
|
|
||||||
// - he device ID of the device that sent the m.key.verification.accept message
|
|
||||||
// - the transaction ID.
|
|
||||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
|
|
||||||
|
|
||||||
// decimal: generate five bytes by using HKDF.
|
|
||||||
// emoji: generate six bytes by using HKDF.
|
|
||||||
return getSAS().generateShortCode(sasInfo, 6)
|
|
||||||
}
|
|
||||||
KEY_AGREEMENT_V2 -> {
|
|
||||||
// Adds the SAS public key, and separate by |
|
|
||||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId"
|
|
||||||
return getSAS().generateShortCode(sasInfo, 6)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Protocol has been checked earlier
|
|
||||||
throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
|
|
||||||
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
|
||||||
// There is starting to be a huge amount of state / race here :/
|
|
||||||
if (state != VerificationTxState.OnKeyReceived
|
|
||||||
&& state != VerificationTxState.ShortCodeReady
|
|
||||||
&& state != VerificationTxState.ShortCodeAccepted
|
|
||||||
&& state != VerificationTxState.KeySent
|
|
||||||
&& state != VerificationTxState.SendingMac
|
|
||||||
&& state != VerificationTxState.MacSent) {
|
|
||||||
Timber.e("## SAS O: received mac from invalid state $state")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
theirMac = vMac
|
|
||||||
|
|
||||||
// Do I have my Mac?
|
|
||||||
if (myMac != null) {
|
|
||||||
// I can check
|
|
||||||
verifyMacs(vMac)
|
|
||||||
}
|
|
||||||
// Wait for ShortCode Accepted
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
|
||||||
import org.matrix.android.sdk.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 outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
|
|
||||||
private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
|
|
||||||
private val userId: String,
|
|
||||||
override val transactionId: String,
|
|
||||||
override val otherUserId: String,
|
|
||||||
override var otherDeviceId: String? = null,
|
|
||||||
override val isIncoming: Boolean) : VerificationTransaction {
|
|
||||||
|
|
||||||
lateinit var transport: VerificationTransport
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun transactionUpdated(tx: VerificationTransaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected var listeners = ArrayList<Listener>()
|
|
||||||
|
|
||||||
fun addListener(listener: Listener) {
|
|
||||||
if (!listeners.contains(listener)) listeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeListener(listener: Listener) {
|
|
||||||
listeners.remove(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun trust(canTrustOtherUserMasterKey: Boolean,
|
|
||||||
toVerifyDeviceIds: List<String>,
|
|
||||||
eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone: Boolean = true) {
|
|
||||||
Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds")
|
|
||||||
Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted")
|
|
||||||
|
|
||||||
// TODO what if the otherDevice is not in this list? and should we
|
|
||||||
toVerifyDeviceIds.forEach {
|
|
||||||
setDeviceVerified(otherUserId, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
incomingGossipingRequestManager.onVerificationCompleteForDevice(otherDeviceId!!)
|
|
||||||
|
|
||||||
// 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("## Verification: Failed to sign new device $otherDeviceId, ${failure.localizedMessage}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoDone) {
|
|
||||||
state = VerificationTxState.Verified
|
|
||||||
transport.done(transactionId) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
|
||||||
// TODO should not override cross sign status
|
|
||||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
|
||||||
userId,
|
|
||||||
deviceId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -66,7 +66,7 @@ private fun getFlowId(event: Event): String? {
|
|||||||
internal class RustVerificationService(
|
internal class RustVerificationService(
|
||||||
private val olmMachine: OlmMachine,
|
private val olmMachine: OlmMachine,
|
||||||
private val requestSender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
) : DefaultVerificationTransaction.Listener, VerificationService {
|
) : VerificationService {
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
private var listeners = ArrayList<VerificationService.Listener>()
|
private var listeners = ArrayList<VerificationService.Listener>()
|
||||||
|
|
||||||
@ -428,9 +428,4 @@ internal class RustVerificationService(
|
|||||||
val verificationRequest = this.getVerificationRequest(otherUserId, transactionId)
|
val verificationRequest = this.getVerificationRequest(otherUserId, transactionId)
|
||||||
runBlocking { verificationRequest?.cancel() }
|
runBlocking { verificationRequest?.cancel() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
// TODO this isn't really used anymore
|
|
||||||
dispatchTxUpdated(tx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,423 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|
||||||
import org.matrix.android.sdk.internal.extensions.toUnsignedInt
|
|
||||||
import org.matrix.olm.OlmSAS
|
|
||||||
import org.matrix.olm.OlmUtility
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an ongoing short code interactive key verification between two devices.
|
|
||||||
*/
|
|
||||||
internal abstract class SASDefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
|
||||||
open val userId: String,
|
|
||||||
open val deviceId: String?,
|
|
||||||
private val cryptoStore: IMXCryptoStore,
|
|
||||||
crossSigningService: CrossSigningService,
|
|
||||||
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager: IncomingGossipingRequestManager,
|
|
||||||
private val deviceFingerprint: String,
|
|
||||||
transactionId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String?,
|
|
||||||
isIncoming: Boolean
|
|
||||||
) : DefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction,
|
|
||||||
crossSigningService,
|
|
||||||
outgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager,
|
|
||||||
userId,
|
|
||||||
transactionId,
|
|
||||||
otherUserId,
|
|
||||||
otherDeviceId,
|
|
||||||
isIncoming),
|
|
||||||
SasVerificationTransaction {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
|
||||||
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
|
||||||
|
|
||||||
// Deprecated maybe removed later, use V2
|
|
||||||
const val KEY_AGREEMENT_V1 = "curve25519"
|
|
||||||
const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256"
|
|
||||||
|
|
||||||
// ordered by preferred order
|
|
||||||
val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1)
|
|
||||||
|
|
||||||
// ordered by preferred order
|
|
||||||
val KNOWN_HASHES = listOf("sha256")
|
|
||||||
|
|
||||||
// ordered by preferred order
|
|
||||||
val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF)
|
|
||||||
|
|
||||||
// older devices have limited support of emoji but SDK offers images for the 64 verification emojis
|
|
||||||
// so always send that we support EMOJI
|
|
||||||
val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
override var state: VerificationTxState = VerificationTxState.None
|
|
||||||
set(newState) {
|
|
||||||
field = newState
|
|
||||||
|
|
||||||
listeners.forEach {
|
|
||||||
try {
|
|
||||||
it.transactionUpdated(this)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e(e, "## Error while notifying listeners")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newState is VerificationTxState.TerminalTxState) {
|
|
||||||
releaseSAS()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var olmSas: OlmSAS? = 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
|
|
||||||
|
|
||||||
protected var myMac: ValidVerificationInfoMac? = null
|
|
||||||
protected var theirMac: ValidVerificationInfoMac? = null
|
|
||||||
|
|
||||||
protected fun getSAS(): OlmSAS {
|
|
||||||
if (olmSas == null) olmSas = OlmSAS()
|
|
||||||
return olmSas!!
|
|
||||||
}
|
|
||||||
|
|
||||||
// To override finalize(), all you need to do is simply declare it, without using the override keyword:
|
|
||||||
protected fun finalize() {
|
|
||||||
releaseSAS()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun releaseSAS() {
|
|
||||||
// finalization logic
|
|
||||||
olmSas?.releaseSas()
|
|
||||||
olmSas = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To be called by the client when the user has verified that
|
|
||||||
* both short codes do match
|
|
||||||
*/
|
|
||||||
override fun userHasVerifiedShortCode() {
|
|
||||||
Timber.v("## SAS short code verified by user for id:$transactionId")
|
|
||||||
if (state != VerificationTxState.ShortCodeReady) {
|
|
||||||
// ignore and cancel?
|
|
||||||
Timber.e("## Accepted short code from invalid state $state")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
state = VerificationTxState.ShortCodeAccepted
|
|
||||||
// Alice and Bob’ devices calculate the HMAC of their own device keys and a comma-separated,
|
|
||||||
// sorted list of the key IDs that they wish the other user to verify,
|
|
||||||
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
|
|
||||||
// - the string “MATRIX_KEY_VERIFICATION_MAC”,
|
|
||||||
// - the Matrix ID of the user whose key is being MAC-ed,
|
|
||||||
// - the device ID of the device sending the MAC,
|
|
||||||
// - the Matrix ID of the other user,
|
|
||||||
// - the device ID of the device receiving the MAC,
|
|
||||||
// - the transaction ID, and
|
|
||||||
// - the key ID of the key being MAC-ed, or the string “KEY_IDS” if the item being MAC-ed is the list of key IDs.
|
|
||||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$userId$deviceId$otherUserId$otherDeviceId$transactionId"
|
|
||||||
|
|
||||||
// Previously, with SAS verification, the m.key.verification.mac message only contained the user's device key.
|
|
||||||
// It should now contain both the device key and the MSK.
|
|
||||||
// So when Alice and Bob verify with SAS, the verification will verify the MSK.
|
|
||||||
|
|
||||||
val keyMap = HashMap<String, String>()
|
|
||||||
|
|
||||||
val keyId = "ed25519:$deviceId"
|
|
||||||
val macString = macUsingAgreedMethod(deviceFingerprint, baseInfo + keyId)
|
|
||||||
|
|
||||||
if (macString.isNullOrBlank()) {
|
|
||||||
// Should not happen
|
|
||||||
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyMap[keyId] = macString
|
|
||||||
|
|
||||||
cryptoStore.getMyCrossSigningInfo()?.takeIf { it.isTrusted() }
|
|
||||||
?.masterKey()
|
|
||||||
?.unpaddedBase64PublicKey
|
|
||||||
?.let { masterPublicKey ->
|
|
||||||
val crossSigningKeyId = "ed25519:$masterPublicKey"
|
|
||||||
macUsingAgreedMethod(masterPublicKey, baseInfo + crossSigningKeyId)?.let { mskMacString ->
|
|
||||||
keyMap[crossSigningKeyId] = mskMacString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val keyStrings = macUsingAgreedMethod(keyMap.keys.sorted().joinToString(","), baseInfo + "KEY_IDS")
|
|
||||||
|
|
||||||
if (macString.isNullOrBlank() || keyStrings.isNullOrBlank()) {
|
|
||||||
// Should not happen
|
|
||||||
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
|
|
||||||
myMac = macMsg.asValidObject()
|
|
||||||
state = VerificationTxState.SendingMac
|
|
||||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
|
|
||||||
if (state == VerificationTxState.SendingMac) {
|
|
||||||
// It is possible that we receive the next event before this one :/, in this case we should keep state
|
|
||||||
state = VerificationTxState.MacSent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do I already have their Mac?
|
|
||||||
theirMac?.let { verifyMacs(it) }
|
|
||||||
// if not wait for it
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shortCodeDoesNotMatch() {
|
|
||||||
Timber.v("## SAS short code do not match for id:$transactionId")
|
|
||||||
cancel(CancelCode.MismatchedSas)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isToDeviceTransport(): Boolean {
|
|
||||||
return transport is VerificationTransportToDevice
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart)
|
|
||||||
|
|
||||||
abstract fun onVerificationAccept(accept: ValidVerificationInfoAccept)
|
|
||||||
|
|
||||||
abstract fun onKeyVerificationKey(vKey: ValidVerificationInfoKey)
|
|
||||||
|
|
||||||
abstract fun onKeyVerificationMac(vMac: ValidVerificationInfoMac)
|
|
||||||
|
|
||||||
protected fun verifyMacs(theirMacSafe: ValidVerificationInfoMac) {
|
|
||||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
|
||||||
state = VerificationTxState.Verifying
|
|
||||||
|
|
||||||
// Keys have been downloaded earlier in process
|
|
||||||
val otherUserKnownDevices = cryptoStore.getUserDevices(otherUserId)
|
|
||||||
|
|
||||||
// Bob’s device calculates the HMAC (as above) of its copies of Alice’s keys given in the message (as identified by their key ID),
|
|
||||||
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
|
|
||||||
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
|
||||||
// If everything matches, then consider Alice’s device keys as verified.
|
|
||||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
|
||||||
|
|
||||||
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
|
|
||||||
|
|
||||||
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
|
|
||||||
if (theirMacSafe.keys != keyStrings) {
|
|
||||||
// WRONG!
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val verifiedDevices = ArrayList<String>()
|
|
||||||
|
|
||||||
// cannot be empty because it has been validated
|
|
||||||
theirMacSafe.mac.keys.forEach {
|
|
||||||
val keyIDNoPrefix = it.removePrefix("ed25519:")
|
|
||||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
|
||||||
if (otherDeviceKey == null) {
|
|
||||||
Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
|
|
||||||
// just ignore and continue
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
|
||||||
if (mac != theirMacSafe.mac[it]) {
|
|
||||||
// WRONG!
|
|
||||||
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
verifiedDevices.add(keyIDNoPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
var otherMasterKeyIsVerified = false
|
|
||||||
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
|
|
||||||
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
|
|
||||||
if (otherCrossSigningMasterKeyPublic != null) {
|
|
||||||
// Did the user signed his master key
|
|
||||||
theirMacSafe.mac.keys.forEach {
|
|
||||||
val keyIDNoPrefix = it.removePrefix("ed25519:")
|
|
||||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
|
||||||
// Check the signature
|
|
||||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
|
||||||
if (mac != theirMacSafe.mac[it]) {
|
|
||||||
// WRONG!
|
|
||||||
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
otherMasterKeyIsVerified = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if none of the keys could be verified, then error because the app
|
|
||||||
// should be informed about that
|
|
||||||
if (verifiedDevices.isEmpty() && !otherMasterKeyIsVerified) {
|
|
||||||
Timber.e("## SAS Verification: No devices verified")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
trust(otherMasterKeyIsVerified,
|
|
||||||
verifiedDevices,
|
|
||||||
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
cancel(CancelCode.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel(code: CancelCode) {
|
|
||||||
state = VerificationTxState.Cancelled(code, true)
|
|
||||||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun <T> sendToOther(type: String,
|
|
||||||
keyToDevice: VerificationInfo<T>,
|
|
||||||
nextState: VerificationTxState,
|
|
||||||
onErrorReason: CancelCode,
|
|
||||||
onDone: (() -> Unit)?) {
|
|
||||||
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? {
|
|
||||||
if (shortCodeBytes == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
when (shortAuthenticationStringMode) {
|
|
||||||
SasMode.DECIMAL -> {
|
|
||||||
if (shortCodeBytes!!.size < 5) return null
|
|
||||||
return getDecimalCodeRepresentation(shortCodeBytes!!)
|
|
||||||
}
|
|
||||||
SasMode.EMOJI -> {
|
|
||||||
if (shortCodeBytes!!.size < 6) return null
|
|
||||||
return getEmojiCodeRepresentation(shortCodeBytes!!).joinToString(" ") { it.emoji }
|
|
||||||
}
|
|
||||||
else -> return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun supportsEmoji(): Boolean {
|
|
||||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI).orFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun supportsDecimal(): Boolean {
|
|
||||||
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL).orFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
|
|
||||||
if ("sha256" == accepted?.hash?.lowercase(Locale.ROOT)) {
|
|
||||||
val olmUtil = OlmUtility()
|
|
||||||
val hashBytes = olmUtil.sha256(toHash)
|
|
||||||
olmUtil.releaseUtility()
|
|
||||||
return hashBytes
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun macUsingAgreedMethod(message: String, info: String): String? {
|
|
||||||
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
|
|
||||||
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
|
|
||||||
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDecimalCodeRepresentation(): String {
|
|
||||||
return getDecimalCodeRepresentation(shortCodeBytes!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* decimal: generate five bytes by using HKDF.
|
|
||||||
* Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive),
|
|
||||||
* and add 1000 (resulting in a number between 1000 and 9191 inclusive).
|
|
||||||
* Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers.
|
|
||||||
* In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000,
|
|
||||||
* the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000.
|
|
||||||
* (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic,
|
|
||||||
* and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.)
|
|
||||||
* The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers,
|
|
||||||
* or with the three numbers on separate lines.
|
|
||||||
*/
|
|
||||||
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
|
|
||||||
val b0 = byteArray[0].toUnsignedInt() // need unsigned byte
|
|
||||||
val b1 = byteArray[1].toUnsignedInt() // need unsigned byte
|
|
||||||
val b2 = byteArray[2].toUnsignedInt() // need unsigned byte
|
|
||||||
val b3 = byteArray[3].toUnsignedInt() // need unsigned byte
|
|
||||||
val b4 = byteArray[4].toUnsignedInt() // need unsigned byte
|
|
||||||
// (B0 << 5 | B1 >> 3) + 1000
|
|
||||||
val first = (b0.shl(5) or b1.shr(3)) + 1000
|
|
||||||
// ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
|
|
||||||
val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000
|
|
||||||
// ((B3 & 0x3f) << 7 | B4 >> 1) + 1000
|
|
||||||
val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000
|
|
||||||
return "$first $second $third"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
|
|
||||||
return getEmojiCodeRepresentation(shortCodeBytes!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* emoji: generate six bytes by using HKDF.
|
|
||||||
* Split the first 42 bits into 7 groups of 6 bits, as one would do when creating a base64 encoding.
|
|
||||||
* 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)
|
|
||||||
*/
|
|
||||||
private fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
|
||||||
val b0 = byteArray[0].toUnsignedInt()
|
|
||||||
val b1 = byteArray[1].toUnsignedInt()
|
|
||||||
val b2 = byteArray[2].toUnsignedInt()
|
|
||||||
val b3 = byteArray[3].toUnsignedInt()
|
|
||||||
val b4 = byteArray[4].toUnsignedInt()
|
|
||||||
val b5 = byteArray[5].toUnsignedInt()
|
|
||||||
return listOf(
|
|
||||||
getEmojiForCode((b0 and 0xFC).shr(2)),
|
|
||||||
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
|
|
||||||
getEmojiForCode((b1 and 0xF).shl(2) or (b2 and 0xC0).shr(6)),
|
|
||||||
getEmojiForCode((b2 and 0x3F)),
|
|
||||||
getEmojiForCode((b3 and 0xFC).shr(2)),
|
|
||||||
getEmojiForCode((b3 and 0x3).shl(4) or (b4 and 0xF0).shr(4)),
|
|
||||||
getEmojiForCode((b4 and 0xF).shl(2) or (b5 and 0xC0).shr(6))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Possible previous worker: None
|
|
||||||
* Possible next worker : None
|
|
||||||
*/
|
|
||||||
internal class SendVerificationMessageWorker(context: Context,
|
|
||||||
params: WorkerParameters)
|
|
||||||
: SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
internal data class Params(
|
|
||||||
override val sessionId: String,
|
|
||||||
val eventId: String,
|
|
||||||
override val lastFailureMessage: String? = null
|
|
||||||
) : SessionWorkerParams
|
|
||||||
|
|
||||||
@Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask
|
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
|
||||||
|
|
||||||
override fun injectWith(injector: SessionComponent) {
|
|
||||||
injector.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun doSafeWork(params: Params): Result {
|
|
||||||
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return buildErrorResult(params, "Event not found")
|
|
||||||
val localEventId = localEvent.eventId ?: ""
|
|
||||||
val roomId = localEvent.roomId ?: ""
|
|
||||||
|
|
||||||
if (cancelSendTracker.isCancelRequestedFor(localEventId, roomId)) {
|
|
||||||
return Result.success()
|
|
||||||
.also {
|
|
||||||
cancelSendTracker.markCancelled(localEventId, roomId)
|
|
||||||
Timber.e("## SendEvent: Event sending has been cancelled $localEventId")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val resultEventId = sendVerificationMessageTask.execute(
|
|
||||||
SendVerificationMessageTask.Params(
|
|
||||||
event = localEvent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Result.success(Data.Builder().putString(localEventId, resultEventId).build())
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
if (throwable.shouldBeRetried()) {
|
|
||||||
Result.retry()
|
|
||||||
} else {
|
|
||||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun buildErrorParams(params: Params, message: String): Params {
|
|
||||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
|
||||||
}
|
|
||||||
}
|
|
@ -73,8 +73,8 @@ internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInf
|
|||||||
val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null
|
val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null
|
||||||
val validMessageAuthenticationCodes = messageAuthenticationCodes
|
val validMessageAuthenticationCodes = messageAuthenticationCodes
|
||||||
?.takeIf {
|
?.takeIf {
|
||||||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
it.contains(SAS_MAC_SHA256)
|
||||||
|| it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
|| it.contains(SAS_MAC_SHA256_LONGKDF)
|
||||||
}
|
}
|
||||||
?: return null
|
?: return null
|
||||||
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
||||||
@ -101,6 +101,11 @@ internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInf
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||||
|
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ValidVerificationInfoStart(
|
sealed class ValidVerificationInfoStart(
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
|
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
|
||||||
import io.realm.Realm
|
|
||||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.ArrayList
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class VerificationMessageProcessor @Inject constructor(
|
|
||||||
private val eventDecryptor: EventDecryptor,
|
|
||||||
private val verificationService: DefaultVerificationService,
|
|
||||||
@UserId private val userId: String,
|
|
||||||
@DeviceId private val deviceId: String?
|
|
||||||
) : EventInsertLiveProcessor {
|
|
||||||
|
|
||||||
private val transactionsHandledByOtherDevice = ArrayList<String>()
|
|
||||||
|
|
||||||
private val allowedTypes = listOf(
|
|
||||||
EventType.KEY_VERIFICATION_START,
|
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
|
||||||
EventType.KEY_VERIFICATION_MAC,
|
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
|
||||||
EventType.KEY_VERIFICATION_READY,
|
|
||||||
EventType.MESSAGE,
|
|
||||||
EventType.ENCRYPTED
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
|
||||||
if (insertType != EventInsertType.INCREMENTAL_SYNC) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return allowedTypes.contains(eventType) && !LocalEcho.isLocalEchoId(eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
|
||||||
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
|
|
||||||
|
|
||||||
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
|
|
||||||
// the message should be ignored by the receiver.
|
|
||||||
|
|
||||||
if (!VerificationService.isValidRequest(event.ageLocalTs
|
|
||||||
?: event.originServerTs)) return Unit.also {
|
|
||||||
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated")
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt if needed?
|
|
||||||
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
|
||||||
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
|
||||||
// for now decrypt sync
|
|
||||||
try {
|
|
||||||
val result = eventDecryptor.decryptEvent(event, "")
|
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
} catch (e: MXCryptoError) {
|
|
||||||
Timber.e("## SAS Failed to decrypt event: ${event.eventId}")
|
|
||||||
verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
|
|
||||||
|
|
||||||
// Relates to is not encrypted
|
|
||||||
val relatesToEventId = event.content.toModel<MessageRelationContent>()?.relatesTo?.eventId
|
|
||||||
|
|
||||||
if (event.senderId == userId) {
|
|
||||||
// If it's send from me, we need to keep track of Requests or Start
|
|
||||||
// done from another device of mine
|
|
||||||
|
|
||||||
if (EventType.MESSAGE == event.getClearType()) {
|
|
||||||
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
|
|
||||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
|
||||||
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
|
||||||
if (it.fromDevice != deviceId) {
|
|
||||||
// The verification is requested from another device
|
|
||||||
Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ")
|
|
||||||
event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (EventType.KEY_VERIFICATION_START == event.getClearType()) {
|
|
||||||
event.getClearContent().toModel<MessageVerificationStartContent>()?.let {
|
|
||||||
if (it.fromDevice != deviceId) {
|
|
||||||
// The verification is started from another device
|
|
||||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
|
||||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
|
||||||
verificationService.onRoomRequestHandledByOtherDevice(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
|
|
||||||
event.getClearContent().toModel<MessageVerificationReadyContent>()?.let {
|
|
||||||
if (it.fromDevice != deviceId) {
|
|
||||||
// The verification is started from another device
|
|
||||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
|
||||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
|
||||||
verificationService.onRoomRequestHandledByOtherDevice(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
|
|
||||||
relatesToEventId?.let {
|
|
||||||
transactionsHandledByOtherDevice.remove(it)
|
|
||||||
verificationService.onRoomRequestHandledByOtherDevice(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) {
|
|
||||||
// Ignore this event, it is directed to another of my devices
|
|
||||||
Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (event.getClearType()) {
|
|
||||||
EventType.KEY_VERIFICATION_START,
|
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
|
||||||
EventType.KEY_VERIFICATION_MAC,
|
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
|
||||||
EventType.KEY_VERIFICATION_READY,
|
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
|
||||||
verificationService.onRoomEvent(event)
|
|
||||||
}
|
|
||||||
EventType.MESSAGE -> {
|
|
||||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
|
||||||
verificationService.onRoomRequestReceived(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verification can be performed using toDevice events or via DM.
|
|
||||||
* This class abstracts the concept of transport for verification
|
|
||||||
*/
|
|
||||||
internal interface VerificationTransport {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a message
|
|
||||||
*/
|
|
||||||
fun <T> sendToOther(type: String,
|
|
||||||
verificationInfo: VerificationInfo<T>,
|
|
||||||
nextState: VerificationTxState,
|
|
||||||
onErrorReason: CancelCode,
|
|
||||||
onDone: (() -> Unit)?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callback will be called with eventId and ValidVerificationInfoRequest in case of success
|
|
||||||
*/
|
|
||||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
|
||||||
localId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
roomId: String?,
|
|
||||||
toDevices: List<String>?,
|
|
||||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit)
|
|
||||||
|
|
||||||
fun cancelTransaction(transactionId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
otherUserDeviceId: String?,
|
|
||||||
code: CancelCode)
|
|
||||||
|
|
||||||
fun done(transactionId: String,
|
|
||||||
onDone: (() -> Unit)?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an accept message suitable for this transport
|
|
||||||
*/
|
|
||||||
fun createAccept(tid: String,
|
|
||||||
keyAgreementProtocol: String,
|
|
||||||
hash: String,
|
|
||||||
commitment: String,
|
|
||||||
messageAuthenticationCode: String,
|
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept
|
|
||||||
|
|
||||||
fun createKey(tid: String,
|
|
||||||
pubKey: String): VerificationInfoKey
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create start for SAS verification
|
|
||||||
*/
|
|
||||||
fun createStartForSas(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
keyAgreementProtocols: List<String>,
|
|
||||||
hashes: List<String>,
|
|
||||||
messageAuthenticationCodes: List<String>,
|
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoStart
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create start for QR code verification
|
|
||||||
*/
|
|
||||||
fun createStartForQrCode(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
sharedSecret: String): VerificationInfoStart
|
|
||||||
|
|
||||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
|
||||||
|
|
||||||
fun createReady(tid: String,
|
|
||||||
fromDevice: String,
|
|
||||||
methods: List<String>): VerificationInfoReady
|
|
||||||
|
|
||||||
// TODO Refactor
|
|
||||||
fun sendVerificationReady(keyReq: VerificationInfoReady,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String?,
|
|
||||||
callback: (() -> Unit)?)
|
|
||||||
}
|
|
@ -1,382 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.work.BackoffPolicy
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.Operation
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationAcceptContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationDoneContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationKeyContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationMacContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.UUID
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
internal class VerificationTransportRoomMessage(
|
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
|
||||||
private val sessionId: String,
|
|
||||||
private val userId: String,
|
|
||||||
private val userDeviceId: String?,
|
|
||||||
private val roomId: String,
|
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
|
||||||
private val tx: DefaultVerificationTransaction?,
|
|
||||||
private val coroutineScope: CoroutineScope
|
|
||||||
) : VerificationTransport {
|
|
||||||
|
|
||||||
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(
|
|
||||||
type = type,
|
|
||||||
roomId = roomId,
|
|
||||||
content = verificationInfo.toEventContent()!!
|
|
||||||
)
|
|
||||||
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
|
||||||
sessionId = sessionId,
|
|
||||||
eventId = event.eventId ?: ""
|
|
||||||
))
|
|
||||||
val enqueueInfo = enqueueSendWork(workerParams)
|
|
||||||
|
|
||||||
// I cannot just listen to the given work request, because when used in a uniqueWork,
|
|
||||||
// The callback is called while it is still Running ...
|
|
||||||
|
|
||||||
// Futures.addCallback(enqueueInfo.first.result, object : FutureCallback<Operation.State.SUCCESS> {
|
|
||||||
// override fun onSuccess(result: Operation.State.SUCCESS?) {
|
|
||||||
// if (onDone != null) {
|
|
||||||
// onDone()
|
|
||||||
// } else {
|
|
||||||
// tx?.state = nextState
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override fun onFailure(t: Throwable) {
|
|
||||||
// Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}, reason: ${t.localizedMessage}")
|
|
||||||
// tx?.cancel(onErrorReason)
|
|
||||||
// }
|
|
||||||
// }, listenerExecutor)
|
|
||||||
|
|
||||||
val workLiveData = workManagerProvider.workManager
|
|
||||||
.getWorkInfosForUniqueWorkLiveData(uniqueQueueName())
|
|
||||||
|
|
||||||
val observer = object : Observer<List<WorkInfo>> {
|
|
||||||
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
|
||||||
workInfoList
|
|
||||||
?.firstOrNull { it.id == enqueueInfo.second }
|
|
||||||
?.let { wInfo ->
|
|
||||||
when (wInfo.state) {
|
|
||||||
WorkInfo.State.FAILED -> {
|
|
||||||
tx?.cancel(onErrorReason)
|
|
||||||
workLiveData.removeObserver(this)
|
|
||||||
}
|
|
||||||
WorkInfo.State.SUCCEEDED -> {
|
|
||||||
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
|
||||||
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
|
|
||||||
tx?.cancel(onErrorReason)
|
|
||||||
} else {
|
|
||||||
if (onDone != null) {
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
tx?.state = nextState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workLiveData.removeObserver(this)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// nop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
|
||||||
workLiveData.observeForever(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
|
||||||
localId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
roomId: String?,
|
|
||||||
toDevices: List<String>?,
|
|
||||||
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 = "$userId is requesting to verify your key, but your client does not support in-chat key verification." +
|
|
||||||
" You will need to use legacy key verification to verify keys.",
|
|
||||||
fromDevice = validInfo.fromDevice,
|
|
||||||
toUserId = otherUserId,
|
|
||||||
timestamp = validInfo.timestamp,
|
|
||||||
methods = validInfo.methods
|
|
||||||
)
|
|
||||||
val content = info.toContent()
|
|
||||||
|
|
||||||
val event = createEventAndLocalEcho(
|
|
||||||
localId,
|
|
||||||
EventType.MESSAGE,
|
|
||||||
roomId,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
|
||||||
sessionId = sessionId,
|
|
||||||
eventId = event.eventId ?: ""
|
|
||||||
))
|
|
||||||
|
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(workerParams)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
workManagerProvider.workManager
|
|
||||||
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
|
||||||
.enqueue()
|
|
||||||
|
|
||||||
// I cannot just listen to the given work request, because when used in a uniqueWork,
|
|
||||||
// The callback is called while it is still Running ...
|
|
||||||
|
|
||||||
val workLiveData = workManagerProvider.workManager
|
|
||||||
.getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork")
|
|
||||||
|
|
||||||
val observer = object : Observer<List<WorkInfo>> {
|
|
||||||
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
|
||||||
workInfoList
|
|
||||||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
|
||||||
?.firstOrNull { it.id == workRequest.id }
|
|
||||||
?.let { wInfo ->
|
|
||||||
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
|
||||||
callback(null, null)
|
|
||||||
} else {
|
|
||||||
val eventId = wInfo.outputData.getString(localId)
|
|
||||||
if (eventId != null) {
|
|
||||||
callback(eventId, validInfo)
|
|
||||||
} else {
|
|
||||||
callback(null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workLiveData.removeObserver(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
|
||||||
workLiveData.observeForever(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
|
||||||
val event = createEventAndLocalEcho(
|
|
||||||
type = EventType.KEY_VERIFICATION_CANCEL,
|
|
||||||
roomId = roomId,
|
|
||||||
content = MessageVerificationCancelContent.create(transactionId, code).toContent()
|
|
||||||
)
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
|
||||||
sessionId = sessionId,
|
|
||||||
eventId = event.eventId ?: ""
|
|
||||||
))
|
|
||||||
enqueueSendWork(workerParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun done(transactionId: String,
|
|
||||||
onDone: (() -> Unit)?) {
|
|
||||||
Timber.d("## SAS sending done for $transactionId")
|
|
||||||
val event = createEventAndLocalEcho(
|
|
||||||
type = EventType.KEY_VERIFICATION_DONE,
|
|
||||||
roomId = roomId,
|
|
||||||
content = MessageVerificationDoneContent(
|
|
||||||
relatesTo = RelationDefaultContent(
|
|
||||||
RelationType.REFERENCE,
|
|
||||||
transactionId
|
|
||||||
)
|
|
||||||
).toContent()
|
|
||||||
)
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
|
||||||
sessionId = sessionId,
|
|
||||||
eventId = event.eventId ?: ""
|
|
||||||
))
|
|
||||||
val enqueueInfo = enqueueSendWork(workerParams)
|
|
||||||
|
|
||||||
val workLiveData = workManagerProvider.workManager
|
|
||||||
.getWorkInfosForUniqueWorkLiveData(uniqueQueueName())
|
|
||||||
val observer = object : Observer<List<WorkInfo>> {
|
|
||||||
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
|
||||||
workInfoList
|
|
||||||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
|
||||||
?.firstOrNull { it.id == enqueueInfo.second }
|
|
||||||
?.let { _ ->
|
|
||||||
onDone?.invoke()
|
|
||||||
workLiveData.removeObserver(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
|
||||||
workLiveData.observeForever(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun enqueueSendWork(workerParams: Data): Pair<Operation, UUID> {
|
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(workerParams)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
return workManagerProvider.workManager
|
|
||||||
.beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
|
||||||
.enqueue() to workRequest.id
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun uniqueQueueName() = "${roomId}_VerificationWork"
|
|
||||||
|
|
||||||
override fun createAccept(tid: String,
|
|
||||||
keyAgreementProtocol: String,
|
|
||||||
hash: String,
|
|
||||||
commitment: String,
|
|
||||||
messageAuthenticationCode: String,
|
|
||||||
shortAuthenticationStrings: List<String>)
|
|
||||||
: VerificationInfoAccept = MessageVerificationAcceptContent.create(
|
|
||||||
tid,
|
|
||||||
keyAgreementProtocol,
|
|
||||||
hash,
|
|
||||||
commitment,
|
|
||||||
messageAuthenticationCode,
|
|
||||||
shortAuthenticationStrings
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey)
|
|
||||||
|
|
||||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
|
|
||||||
|
|
||||||
override fun createStartForSas(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
keyAgreementProtocols: List<String>,
|
|
||||||
hashes: List<String>,
|
|
||||||
messageAuthenticationCodes: List<String>,
|
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
|
||||||
return MessageVerificationStartContent(
|
|
||||||
fromDevice,
|
|
||||||
hashes,
|
|
||||||
keyAgreementProtocols,
|
|
||||||
messageAuthenticationCodes,
|
|
||||||
shortAuthenticationStrings,
|
|
||||||
VERIFICATION_METHOD_SAS,
|
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.REFERENCE,
|
|
||||||
eventId = transactionId
|
|
||||||
),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createStartForQrCode(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
sharedSecret: String): VerificationInfoStart {
|
|
||||||
return MessageVerificationStartContent(
|
|
||||||
fromDevice,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
VERIFICATION_METHOD_RECIPROCATE,
|
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.REFERENCE,
|
|
||||||
eventId = transactionId
|
|
||||||
),
|
|
||||||
sharedSecret
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
|
||||||
return MessageVerificationReadyContent(
|
|
||||||
fromDevice = fromDevice,
|
|
||||||
relatesTo = RelationDefaultContent(
|
|
||||||
type = RelationType.REFERENCE,
|
|
||||||
eventId = tid
|
|
||||||
),
|
|
||||||
methods = methods
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
type = type,
|
|
||||||
content = content,
|
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
|
||||||
).also {
|
|
||||||
localEchoEventFactory.createLocalEcho(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String?,
|
|
||||||
callback: (() -> Unit)?) {
|
|
||||||
// Not applicable (send event is called directly)
|
|
||||||
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
|
||||||
@SessionId
|
|
||||||
private val sessionId: String,
|
|
||||||
@UserId
|
|
||||||
private val userId: String,
|
|
||||||
@DeviceId
|
|
||||||
private val deviceId: String?,
|
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
|
||||||
private val taskExecutor: TaskExecutor
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
|
||||||
return VerificationTransportRoomMessage(workManagerProvider,
|
|
||||||
sessionId,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
roomId,
|
|
||||||
localEchoEventFactory,
|
|
||||||
tx,
|
|
||||||
taskExecutor.executorScope)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationKey
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationMac
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationReady
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class VerificationTransportToDevice(
|
|
||||||
private var tx: DefaultVerificationTransaction?,
|
|
||||||
private var sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val myDeviceId: String?,
|
|
||||||
private var taskExecutor: TaskExecutor
|
|
||||||
) : VerificationTransport {
|
|
||||||
|
|
||||||
override fun sendVerificationRequest(supportedMethods: List<String>,
|
|
||||||
localId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
roomId: String?,
|
|
||||||
toDevices: List<String>?,
|
|
||||||
callback: (String?, ValidVerificationInfoRequest?) -> Unit) {
|
|
||||||
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
val validKeyReq = ValidVerificationInfoRequest(
|
|
||||||
transactionId = localId,
|
|
||||||
fromDevice = myDeviceId ?: "",
|
|
||||||
methods = supportedMethods,
|
|
||||||
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)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
|
||||||
callback.invoke(localId, validKeyReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e("## verification [$tx.transactionId] failed to send toDevice request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String?,
|
|
||||||
callback: (() -> Unit)?) {
|
|
||||||
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
|
|
||||||
contentMap.setObject(otherUserId, otherDeviceId, keyReq)
|
|
||||||
|
|
||||||
sendToDeviceTask
|
|
||||||
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_READY, contentMap)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
Timber.v("## verification [$tx.transactionId] send toDevice request success")
|
|
||||||
callback?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e("## verification [$tx.transactionId] failed to send toDevice request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 stateBeforeCall = tx?.state
|
|
||||||
val tx = tx ?: return
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
val toSendToDeviceObject = verificationInfo.toSendToDeviceObject()
|
|
||||||
?: return Unit.also { tx.cancel() }
|
|
||||||
|
|
||||||
contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject)
|
|
||||||
|
|
||||||
sendToDeviceTask
|
|
||||||
.configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.")
|
|
||||||
if (onDone != null) {
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
// we may have received next state (e.g received accept in sending_start)
|
|
||||||
// We only put next state if the state was what is was before we started
|
|
||||||
if (tx.state == stateBeforeCall) {
|
|
||||||
tx.state = nextState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e("## SAS verification [$tx.transactionId] failed to send toDevice in state : $tx.state")
|
|
||||||
tx.cancel(onErrorReason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun done(transactionId: String, onDone: (() -> Unit)?) {
|
|
||||||
val otherUserId = tx?.otherUserId ?: return
|
|
||||||
val otherUserDeviceId = tx?.otherDeviceId ?: return
|
|
||||||
val cancelMessage = KeyVerificationDone(transactionId)
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
|
|
||||||
sendToDeviceTask
|
|
||||||
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
onDone?.invoke()
|
|
||||||
Timber.v("## SAS verification [$transactionId] done")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "## SAS verification [$transactionId] failed to done.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
|
||||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
|
|
||||||
sendToDeviceTask
|
|
||||||
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createAccept(tid: String,
|
|
||||||
keyAgreementProtocol: String,
|
|
||||||
hash: String,
|
|
||||||
commitment: String,
|
|
||||||
messageAuthenticationCode: String,
|
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoAccept = KeyVerificationAccept.create(
|
|
||||||
tid,
|
|
||||||
keyAgreementProtocol,
|
|
||||||
hash,
|
|
||||||
commitment,
|
|
||||||
messageAuthenticationCode,
|
|
||||||
shortAuthenticationStrings)
|
|
||||||
|
|
||||||
override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey)
|
|
||||||
|
|
||||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
|
|
||||||
|
|
||||||
override fun createStartForSas(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
keyAgreementProtocols: List<String>,
|
|
||||||
hashes: List<String>,
|
|
||||||
messageAuthenticationCodes: List<String>,
|
|
||||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
|
||||||
return KeyVerificationStart(
|
|
||||||
fromDevice,
|
|
||||||
VERIFICATION_METHOD_SAS,
|
|
||||||
transactionId,
|
|
||||||
keyAgreementProtocols,
|
|
||||||
hashes,
|
|
||||||
messageAuthenticationCodes,
|
|
||||||
shortAuthenticationStrings,
|
|
||||||
null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createStartForQrCode(fromDevice: String,
|
|
||||||
transactionId: String,
|
|
||||||
sharedSecret: String): VerificationInfoStart {
|
|
||||||
return KeyVerificationStart(
|
|
||||||
fromDevice,
|
|
||||||
VERIFICATION_METHOD_RECIPROCATE,
|
|
||||||
transactionId,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
sharedSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
|
||||||
return KeyVerificationReady(
|
|
||||||
transactionId = tid,
|
|
||||||
fromDevice = fromDevice,
|
|
||||||
methods = methods
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class VerificationTransportToDeviceFactory @Inject constructor(
|
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
@DeviceId val myDeviceId: String?,
|
|
||||||
private val taskExecutor: TaskExecutor) {
|
|
||||||
|
|
||||||
fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
|
|
||||||
return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,283 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart
|
|
||||||
import org.matrix.android.sdk.internal.util.exhaustive
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class DefaultQrCodeVerificationTransaction(
|
|
||||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
|
||||||
override val transactionId: String,
|
|
||||||
override val otherUserId: String,
|
|
||||||
override var otherDeviceId: String?,
|
|
||||||
private val crossSigningService: CrossSigningService,
|
|
||||||
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager: IncomingGossipingRequestManager,
|
|
||||||
private val cryptoStore: IMXCryptoStore,
|
|
||||||
// Not null only if other user is able to scan QR code
|
|
||||||
private val qrCodeData: QrCodeData?,
|
|
||||||
val userId: String,
|
|
||||||
val deviceId: String,
|
|
||||||
override val isIncoming: Boolean
|
|
||||||
) : DefaultVerificationTransaction(
|
|
||||||
setDeviceVerificationAction,
|
|
||||||
crossSigningService,
|
|
||||||
outgoingGossipingRequestManager,
|
|
||||||
incomingGossipingRequestManager,
|
|
||||||
userId,
|
|
||||||
transactionId,
|
|
||||||
otherUserId,
|
|
||||||
otherDeviceId,
|
|
||||||
isIncoming),
|
|
||||||
QrCodeVerificationTransaction {
|
|
||||||
|
|
||||||
override val qrCodeText: String?
|
|
||||||
get() = qrCodeData?.toEncodedString()
|
|
||||||
|
|
||||||
override var state: VerificationTxState = VerificationTxState.None
|
|
||||||
set(newState) {
|
|
||||||
field = newState
|
|
||||||
|
|
||||||
listeners.forEach {
|
|
||||||
try {
|
|
||||||
it.transactionUpdated(this)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e(e, "## Error while notifying listeners")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
|
||||||
val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run {
|
|
||||||
Timber.d("## Verification QR: Invalid QR Code Data")
|
|
||||||
cancel(CancelCode.QrCodeInvalid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform some checks
|
|
||||||
if (otherQrCodeData.transactionId != transactionId) {
|
|
||||||
Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId")
|
|
||||||
cancel(CancelCode.QrCodeInvalid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check master key
|
|
||||||
val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
|
|
||||||
var canTrustOtherUserMasterKey = false
|
|
||||||
|
|
||||||
// Check the other device view of my MSK
|
|
||||||
when (otherQrCodeData) {
|
|
||||||
is QrCodeData.VerifyingAnotherUser -> {
|
|
||||||
// key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is.
|
|
||||||
// Let's check that it's correct
|
|
||||||
// If not -> Cancel
|
|
||||||
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) {
|
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else Unit
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
|
||||||
// key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
|
||||||
// Let's check that I see the same MSK
|
|
||||||
// If not -> Cancel
|
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// I can trust the MSK then (i see the same one, and other session tell me it's trusted by him)
|
|
||||||
canTrustOtherUserMasterKey = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
|
||||||
// key2 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
|
|
||||||
// Let's check that it's the good one
|
|
||||||
// If not -> Cancel
|
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
|
|
||||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.exhaustive
|
|
||||||
|
|
||||||
val toVerifyDeviceIds = mutableListOf<String>()
|
|
||||||
|
|
||||||
// Let's now check the other user/device key material
|
|
||||||
when (otherQrCodeData) {
|
|
||||||
is QrCodeData.VerifyingAnotherUser -> {
|
|
||||||
// key1(aka userMasterCrossSigningPublicKey) is the MSK of the one displaying the QR code (i.e other user)
|
|
||||||
// Let's check that it matches what I think it should be
|
|
||||||
if (otherQrCodeData.userMasterCrossSigningPublicKey
|
|
||||||
!= crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
|
|
||||||
Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// It does so i should mark it as trusted
|
|
||||||
canTrustOtherUserMasterKey = true
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
|
||||||
// key2 (aka otherDeviceKey) is my current device key in POV of the one displaying the QR code (i.e other device)
|
|
||||||
// Let's check that it's correct
|
|
||||||
if (otherQrCodeData.otherDeviceKey
|
|
||||||
!= cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) {
|
|
||||||
Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else Unit // Nothing special here, we will send a reciprocate start event, and then the other session will trust my device
|
|
||||||
// and thus allow me to request SSSS secret
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
|
||||||
// key1 (aka otherDeviceKey) is the device key of the one displaying the QR code (i.e other device)
|
|
||||||
// Let's check that it matches what I have locally
|
|
||||||
if (otherQrCodeData.deviceKey
|
|
||||||
!= cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) {
|
|
||||||
Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}")
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// Yes it does -> i should trust it and sign then upload the signature
|
|
||||||
toVerifyDeviceIds.add(otherDeviceId ?: "")
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.exhaustive
|
|
||||||
|
|
||||||
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
|
||||||
// Nothing to verify
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// All checks are correct
|
|
||||||
// Send the shared secret so that sender can trust me
|
|
||||||
// qrCodeData.sharedSecret will be used to send the start request
|
|
||||||
start(otherQrCodeData.sharedSecret)
|
|
||||||
|
|
||||||
trust(
|
|
||||||
canTrustOtherUserMasterKey = canTrustOtherUserMasterKey,
|
|
||||||
toVerifyDeviceIds = toVerifyDeviceIds.distinct(),
|
|
||||||
eventuallyMarkMyMasterKeyAsTrusted = true,
|
|
||||||
autoDone = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) {
|
|
||||||
if (state != VerificationTxState.None) {
|
|
||||||
Timber.e("## Verification QR: start verification from invalid state")
|
|
||||||
// should I cancel??
|
|
||||||
throw IllegalStateException("Interactive Key verification already started")
|
|
||||||
}
|
|
||||||
|
|
||||||
state = VerificationTxState.Started
|
|
||||||
val startMessage = transport.createStartForQrCode(
|
|
||||||
deviceId,
|
|
||||||
transactionId,
|
|
||||||
remoteSecret
|
|
||||||
)
|
|
||||||
|
|
||||||
transport.sendToOther(
|
|
||||||
EventType.KEY_VERIFICATION_START,
|
|
||||||
startMessage,
|
|
||||||
VerificationTxState.WaitingOtherReciprocateConfirm,
|
|
||||||
CancelCode.User,
|
|
||||||
onDone
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
cancel(CancelCode.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel(code: CancelCode) {
|
|
||||||
state = VerificationTxState.Cancelled(code, true)
|
|
||||||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isToDeviceTransport() = false
|
|
||||||
|
|
||||||
// Other user has scanned our QR code. check that the secret matched, so we can trust him
|
|
||||||
fun onStartReceived(startReq: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
|
|
||||||
if (qrCodeData == null) {
|
|
||||||
// Should not happen
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
state = VerificationTxState.QrScannedByOther
|
|
||||||
} else {
|
|
||||||
// Display a warning
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDoneReceived() {
|
|
||||||
if (state != VerificationTxState.WaitingOtherReciprocateConfirm) {
|
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
state = VerificationTxState.Verified
|
|
||||||
transport.done(transactionId) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun otherUserScannedMyQrCode() {
|
|
||||||
when (qrCodeData) {
|
|
||||||
is QrCodeData.VerifyingAnotherUser -> {
|
|
||||||
// Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key,
|
|
||||||
trust(true, emptyList(), false)
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
|
||||||
// I now know that I have the correct device key for other session,
|
|
||||||
// and can sign it with the self-signing key and upload the signature
|
|
||||||
trust(false, listOf(otherDeviceId ?: ""), false)
|
|
||||||
}
|
|
||||||
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
|
|
||||||
// I now know that i can trust my MSK
|
|
||||||
trust(true, emptyList(), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun otherUserDidNotScannedMyQrCode() {
|
|
||||||
// What can I do then?
|
|
||||||
// At least remove the transaction...
|
|
||||||
cancel(CancelCode.MismatchedKeys)
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.crypto.CryptoModule
|
|||||||
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
|
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
|
||||||
import org.matrix.android.sdk.internal.crypto.SendGossipWorker
|
import org.matrix.android.sdk.internal.crypto.SendGossipWorker
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
|
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
|
|
||||||
import org.matrix.android.sdk.internal.di.MatrixComponent
|
import org.matrix.android.sdk.internal.di.MatrixComponent
|
||||||
import org.matrix.android.sdk.internal.federation.FederationModule
|
import org.matrix.android.sdk.internal.federation.FederationModule
|
||||||
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
||||||
@ -129,8 +128,6 @@ internal interface SessionComponent {
|
|||||||
|
|
||||||
fun inject(worker: AddHttpPusherWorker)
|
fun inject(worker: AddHttpPusherWorker)
|
||||||
|
|
||||||
fun inject(worker: SendVerificationMessageWorker)
|
|
||||||
|
|
||||||
fun inject(worker: SendGossipRequestWorker)
|
fun inject(worker: SendGossipRequestWorker)
|
||||||
|
|
||||||
fun inject(worker: CancelGossipRequestWorker)
|
fun inject(worker: CancelGossipRequestWorker)
|
||||||
|
@ -46,7 +46,6 @@ import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
|||||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
|
|
||||||
import org.matrix.android.sdk.internal.database.DatabaseCleaner
|
import org.matrix.android.sdk.internal.database.DatabaseCleaner
|
||||||
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
@ -314,10 +313,6 @@ internal abstract class SessionModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindRoomCreateEventProcessor(processor: RoomCreateEventProcessor): EventInsertLiveProcessor
|
abstract fun bindRoomCreateEventProcessor(processor: RoomCreateEventProcessor): EventInsertLiveProcessor
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun bindVerificationMessageProcessor(processor: VerificationMessageProcessor): EventInsertLiveProcessor
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindCallEventProcessor(processor: CallEventProcessor): EventInsertLiveProcessor
|
abstract fun bindCallEventProcessor(processor: CallEventProcessor): EventInsertLiveProcessor
|
||||||
|
Loading…
Reference in New Issue
Block a user