mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-28 20:08:50 +03:00
Remove some dead code.
This commit is contained in:
parent
54c3b4192e
commit
f8ad024f1b
19 changed files with 8 additions and 4584 deletions
|
@ -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(
|
||||
private val olmMachine: OlmMachine,
|
||||
private val requestSender: RequestSender,
|
||||
) : DefaultVerificationTransaction.Listener, VerificationService {
|
||||
) : VerificationService {
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
private var listeners = ArrayList<VerificationService.Listener>()
|
||||
|
||||
|
@ -428,9 +428,4 @@ internal class RustVerificationService(
|
|||
val verificationRequest = this.getVerificationRequest(otherUserId, transactionId)
|
||||
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 validMessageAuthenticationCodes = messageAuthenticationCodes
|
||||
?.takeIf {
|
||||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
|| it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
||||
it.contains(SAS_MAC_SHA256)
|
||||
|| it.contains(SAS_MAC_SHA256_LONGKDF)
|
||||
}
|
||||
?: return null
|
||||
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
||||
|
@ -101,6 +101,11 @@ internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInf
|
|||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
||||
}
|
||||
}
|
||||
|
||||
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.SendGossipWorker
|
||||
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.federation.FederationModule
|
||||
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
||||
|
@ -129,8 +128,6 @@ internal interface SessionComponent {
|
|||
|
||||
fun inject(worker: AddHttpPusherWorker)
|
||||
|
||||
fun inject(worker: SendVerificationMessageWorker)
|
||||
|
||||
fun inject(worker: SendGossipRequestWorker)
|
||||
|
||||
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.tasks.DefaultRedactEventTask
|
||||
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.EventInsertLiveObserver
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
|
@ -314,10 +313,6 @@ internal abstract class SessionModule {
|
|||
@IntoSet
|
||||
abstract fun bindRoomCreateEventProcessor(processor: RoomCreateEventProcessor): EventInsertLiveProcessor
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindVerificationMessageProcessor(processor: VerificationMessageProcessor): EventInsertLiveProcessor
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindCallEventProcessor(processor: CallEventProcessor): EventInsertLiveProcessor
|
||||
|
|
Loading…
Reference in a new issue