Remove some dead code.

This commit is contained in:
Benoit Marty 2021-07-08 18:36:37 +02:00
parent 54c3b4192e
commit f8ad024f1b
19 changed files with 8 additions and 4584 deletions

View file

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

View file

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

View file

@ -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
}
// Bobs device ensures that it has a copy of Alices 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 Alices device,
// Bobs device replies with a to_device message with type set to m.key.verification.key,
// sending Bobs 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
}
}
// Alices and Bobs 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
}
}

View file

@ -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 Bobs device,
// Alices device stores the commitment value for later use.
accepted = accept
state = VerificationTxState.OnAccepted
// Alices 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 Alices 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 Bobs device,
// Alices device checks that the commitment property from the Bobs m.key.verification.accept
// message is the same as the expected value based on the value of the key property received
// in Bobs m.key.verification.key and the content of Alices 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
}
}

View file

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

View file

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

View file

@ -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)
// Bobs device calculates the HMAC (as above) of its copies of Alices 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.
// Bobs device compares these with the HMAC values given in the m.key.verification.mac message.
// If everything matches, then consider Alices device keys as verified.
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
val 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))
)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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