Fix tests and better logs

This commit is contained in:
valere 2022-12-15 18:47:48 +01:00
parent 3efaa8e171
commit f07aa9f6f0
10 changed files with 567 additions and 469 deletions

View file

@ -46,7 +46,7 @@ class DecryptRedactedEventTest : InstrumentedTest {
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason) roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
// get the event from bob // get the event from bob
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
} }

View file

@ -101,7 +101,7 @@ class E2eeSanityTests : InstrumentedTest {
fail("${otherSession.myUserId.take(10)} should be able to decrypt") fail("${otherSession.myUserId.take(10)} should be able to decrypt")
}) { }) {
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also { val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}|${it?.root?.mxDecryptionResult?.isSafe}")
} }
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log import android.util.Log
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.internal.assertNotEquals import org.amshove.kluent.internal.assertNotEquals
import org.junit.Assert import org.junit.Assert
@ -42,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.wrapWithTimeout
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -99,19 +99,25 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper) val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper)
Assert.assertTrue("Message should be sent", aliceMessageId != null) Assert.assertTrue("Message should be sent", aliceMessageId != null)
Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") Log.v("#E2E TEST", "Alice has sent message to roomId: $e2eRoomID")
// Bob should be able to decrypt the message // Bob should be able to decrypt the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) onFail = {
(timelineEvent != null && fail("Bob should be able to decrypt $aliceMessageId")
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE &&
timelineEvent.root.mxDecryptionResult?.isSafe == true).also {
if (it) {
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
}
} }
) {
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)?.also {
Log.v("#E2E TEST", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null &&
timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE &&
timelineEvent.root.mxDecryptionResult?.isSafe == true).also {
if (it) {
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
}
}
} }
// Create a new user // Create a new user
@ -135,23 +141,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
null null
-> { -> {
// Aris should be able to decrypt the message // Aris should be able to decrypt the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) onFail = {
(timelineEvent != null && fail("Aris should be able to decrypt $aliceMessageId")
timelineEvent.isEncrypted() && }
timelineEvent.root.getClearType() == EventType.MESSAGE && ) {
timelineEvent.root.mxDecryptionResult?.isSafe == false val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
).also { (timelineEvent != null &&
if (it) { timelineEvent.isEncrypted() &&
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") timelineEvent.root.getClearType() == EventType.MESSAGE &&
} timelineEvent.root.mxDecryptionResult?.isSafe == false
).also {
if (it) {
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
} }
}
} }
} }
RoomHistoryVisibility.INVITED, RoomHistoryVisibility.INVITED,
RoomHistoryVisibility.JOINED -> { RoomHistoryVisibility.JOINED -> {
// Aris should not even be able to get the message // Aris should not even be able to get the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Aris should not even be able to get the message")
}
) {
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
?.timelineService() ?.timelineService()
?.getTimelineEvent(aliceMessageId!!) ?.getTimelineEvent(aliceMessageId!!)
@ -258,11 +272,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
// Bob should be able to decrypt the message // Bob should be able to decrypt the message
var firstAliceMessageMegolmSessionId: String? = null var firstAliceMessageMegolmSessionId: String? = null
val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID) val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)!!
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt $aliceMessageId")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(aliceMessageId!!) .getTimelineEvent(aliceMessageId!!)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -279,11 +299,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId) Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
var secondAliceMessageSessionId: String? = null var secondAliceMessageSessionId: String? = null
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage -> sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)!!.let { secondMessage ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt the second message $secondMessage")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(secondMessage) .getTimelineEvent(secondMessage)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -309,29 +335,44 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
).toContent() ).toContent()
) )
Log.v("#E2E TEST ROTATION", "State update sent")
// ensure that the state did synced down // ensure that the state did synced down
testHelper.retryPeriodically { testHelper.retryWithBackoff(
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content onFail = {
fail("Alice state should be updated to ${nextRoomHistoryVisibility.historyVisibilityStr}")
}
) {
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
?.content
?.also {
Log.v("#E2E TEST ROTATION", "Alice sees state as $it")
}
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
} }
testHelper.retryPeriodically { // testHelper.retryPeriodically {
val roomVisibility = aliceSession.getRoom(e2eRoomID)!! // val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
.stateService() // .stateService()
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) // .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
?.content // ?.content
?.toModel<RoomHistoryVisibilityContent>() // ?.toModel<RoomHistoryVisibilityContent>()
Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}") // Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility // roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
} // }
var aliceThirdMessageSessionId: String? = null var aliceThirdMessageSessionId: String? = null
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage -> sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)!!.let { thirdMessage ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt $thirdMessage")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(thirdMessage) .getTimelineEvent(thirdMessage)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -364,7 +405,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) { private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
testHelper.retryPeriodically { testHelper.retryWithBackoff {
otherAccounts.map { otherAccounts.map {
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
}.all { }.all {
@ -374,7 +415,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) { private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also { (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) { if (it) {
@ -383,17 +424,15 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
} }
wrapWithTimeout(60_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
try { try {
otherSession.roomService().joinRoom(e2eRoomID) otherSession.roomService().joinRoom(e2eRoomID)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) { } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
// it's ok we will wait after // it's ok we will wait after
} }
}
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
roomSummary != null && roomSummary.membership == Membership.JOIN roomSummary != null && roomSummary.membership == Membership.JOIN
} }

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import org.amshove.kluent.fail
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
@ -37,7 +38,11 @@ class SasVerificationTestHelper(private val testHelper: CommonTestHelper) {
) )
.transactionId .transactionId
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("bob should see an incoming verification request with id $transactionId")
}
) {
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId) val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
if (incomingRequest != null) { if (incomingRequest != null) {
bobVerificationService.readyPendingVerification( bobVerificationService.readyPendingVerification(
@ -52,7 +57,11 @@ class SasVerificationTestHelper(private val testHelper: CommonTestHelper) {
} }
// wait for alice to see the ready // wait for alice to see the ready
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Alice request whould be ready $transactionId")
}
) {
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobUserId, transactionId) val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobUserId, transactionId)
pendingRequest?.state == EVerificationState.Ready pendingRequest?.state == EVerificationState.Ready
} }
@ -67,7 +76,7 @@ class SasVerificationTestHelper(private val testHelper: CommonTestHelper) {
val requestID = session1VerificationService.requestSelfKeyVerification(supportedMethods).transactionId val requestID = session1VerificationService.requestSelfKeyVerification(supportedMethods).transactionId
val myUserId = session1.myUserId val myUserId = session1.myUserId
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val incomingRequest = session2VerificationService.getExistingVerificationRequest(myUserId, requestID) val incomingRequest = session2VerificationService.getExistingVerificationRequest(myUserId, requestID)
if (incomingRequest != null) { if (incomingRequest != null) {
session2VerificationService.readyPendingVerification( session2VerificationService.readyPendingVerification(

View file

@ -17,305 +17,323 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import timber.log.Timber
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Ignore
class SASTest : InstrumentedTest { class SASTest : InstrumentedTest {
/* val scope = CoroutineScope(SupervisorJob())
@Test @Test
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
// TODO
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
// cryptoTestData.initializeCrossSigning(cryptoTestHelper)
// 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 bobDevice = bobSession.cryptoService().getMyCryptoDevice()
//
// aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), forceDownload = true)
// val txID = aliceVerificationService.beginKeyVerification(bobSession.myUserId, bobDevice.deviceId)
//
// assertNotNull("Alice should have a started transaction", txID)
//
// val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
// assertNotNull("Alice should have a started transaction", aliceKeyTx)
//
// testHelper.await(bobTxCreatedLatch)
// bobVerificationService.removeListener(bobListener)
//
// val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
//
// assertNotNull("Bob should have started verif transaction", bobKeyTx)
// assertTrue(bobKeyTx is SasVerificationTransaction)
// assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
// assertTrue(aliceKeyTx is SasVerificationTransaction)
// assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
//
// assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
// assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.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 SasVerificationTransaction).state
// if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
// cancelLatch.countDown()
// }
// }
// }
// }
// bobVerificationService.addListener(bobListener2)
//
// aliceKeyTx.cancel(CancelCode.User)
//
// testHelper.await(cancelLatch)
//
// assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
// assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
//
// val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
// val bobCancelState = bobKeyTx.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))
}
@Test Timber.v("verification: doE2ETestWithAliceAndBobInARoom")
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
Timber.v("verification: initializeCrossSigning")
val bobSession = cryptoTestData.secondSession!! cryptoTestData.initializeCrossSigning(cryptoTestHelper)
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) {
tx as SasVerificationTransaction
if (tx.transactionId == tid && tx.state() is SasTransactionState.Cancelled) {
cancelReason = (tx.state() as SasTransactionState.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().getMyCryptoDevice().deviceId
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.state() is SasTransactionState.SasStarted) {
runBlocking {
tx.acceptVerification()
}
}
}
}
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val mac = listOf("shaBit")
val tid = "00000000"
// Bob should receive a cancel
val 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().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.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().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
private suspend fun fakeBobStart(
bobSession: Session,
aliceUserID: String?,
aliceDevice: String?,
tid: String,
protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
) {
val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyCryptoDevice().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() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2) Timber.v("verification: requestVerificationAndWaitForReadyState")
val aliceCancelledLatch = CountDownLatch(1) val txId = SasVerificationTestHelper(testHelper)
val createdTx = mutableListOf<VerificationTransaction>() .requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) { Timber.v("verification: startKeyVerification")
createdTx.add(tx) aliceVerificationService.startKeyVerification(
aliceCreatedLatch.countDown() VerificationMethod.SAS,
bobSession.myUserId,
txId
)
Timber.v("verification: ensure bob has received starete")
testHelper.retryWithBackoff {
bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)?.state == EVerificationState.Started
}
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId)
assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SasVerificationTransaction)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId)
assertTrue(aliceKeyTx is SasVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceCancelled = CompletableDeferred<Unit>()
aliceVerificationService.requestEventFlow().onEach {
println("alice flow event $it")
if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
aliceCancelled.complete(Unit)
}
} }
}.launchIn(scope)
override fun transactionUpdated(tx: VerificationTransaction) { val bobCancelled = CompletableDeferred<Unit>()
tx as SasVerificationTransaction bobVerificationService.requestEventFlow().onEach {
if (tx.state() is SasTransactionState.Cancelled && !(tx.state() as SasTransactionState.Cancelled).byMe) { println("alice flow event $it")
aliceCancelledLatch.countDown() if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
bobCancelled.complete(Unit)
}
}
}.launchIn(scope)
aliceVerificationService.cancelVerificationRequest(bobSession.myUserId, txId)
aliceCancelled.await()
bobCancelled.await()
val cancelledAlice = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, txId)!!
val cancelledBob = aliceVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)!!
assertEquals("Should be cancelled on alice side", cancelledAlice.state, EVerificationState.Cancelled)
assertEquals("Should be cancelled on alice side", cancelledBob.state, EVerificationState.Cancelled)
assertEquals("Should be User cancelled on alice side", CancelCode.User, cancelledAlice.cancelConclusion)
assertEquals("Should be User cancelled on bob side", CancelCode.User, cancelledBob.cancelConclusion)
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId))
}
/*
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.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) {
tx as SasVerificationTransaction
if (tx.transactionId == tid && tx.state() is SasTransactionState.Cancelled) {
cancelReason = (tx.state() as SasTransactionState.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().getMyCryptoDevice().deviceId
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.state() is SasTransactionState.SasStarted) {
runBlocking {
tx.acceptVerification()
} }
} }
} }
}
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val mac = listOf("shaBit")
val tid = "00000000"
// Bob should receive a cancel
val 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().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.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().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
private suspend fun fakeBobStart(
bobSession: Session,
aliceUserID: String?,
aliceDevice: String?,
tid: String,
protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
) {
val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyCryptoDevice().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() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(1)
val createdTx = mutableListOf<VerificationTransaction>()
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx)
aliceCreatedLatch.countDown()
}
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.state() is SasTransactionState.Cancelled && !(tx.state() as SasTransactionState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}
}
// aliceVerificationService.addListener(aliceListener) // aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId
// TODO // TODO
// aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), forceDownload = true) // aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), forceDownload = true)
// aliceVerificationService.beginKeyVerification(listOf(VerificationMethod.SAS), bobUserId, bobDeviceId) // aliceVerificationService.beginKeyVerification(listOf(VerificationMethod.SAS), bobUserId, bobDeviceId)
// aliceVerificationService.beginKeyVerification(bobUserId, bobDeviceId) // aliceVerificationService.beginKeyVerification(bobUserId, bobDeviceId)
// testHelper.await(aliceCreatedLatch) // testHelper.await(aliceCreatedLatch)
// testHelper.await(aliceCancelledLatch) // testHelper.await(aliceCancelledLatch)
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
* Test that when alice starts a 'correct' request, bob agrees. * Test that when alice starts a 'correct' request, bob agrees.
*/ */
// @Test // @Test
@ -416,65 +434,65 @@ class SASTest : InstrumentedTest {
// ) // )
// } // }
@Test @Test
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper) cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper) val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS)) val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val verifiedLatch = CountDownLatch(2) val verifiedLatch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated pr=$pr") Timber.v("RequestUpdated pr=$pr")
} }
var matched = false var matched = false
var verified = false var verified = false
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return if (tx !is SasVerificationTransaction) return
Timber.v("Alice transactionUpdated: ${tx.state()} on thread:${Thread.currentThread()}") Timber.v("Alice transactionUpdated: ${tx.state()} on thread:${Thread.currentThread()}")
when (tx.state()) { when (tx.state()) {
SasTransactionState.SasShortCodeReady -> { SasTransactionState.SasShortCodeReady -> {
if (!matched) { if (!matched) {
matched = true matched = true
runBlocking { runBlocking {
delay(500) delay(500)
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
}
} }
} }
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
} }
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
} }
} }
}
// aliceVerificationService.addListener(aliceListener) // aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
var accepted = false var accepted = false
var matched = false var matched = false
var verified = false var verified = false
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr") Timber.v("RequestUpdated: pr=$pr")
} }
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return if (tx !is SasVerificationTransaction) return
Timber.v("Bob transactionUpdated: ${tx.state()} on thread: ${Thread.currentThread()}") Timber.v("Bob transactionUpdated: ${tx.state()} on thread: ${Thread.currentThread()}")
when (tx.state()) { when (tx.state()) {
// VerificationTxState.SasStarted -> { // VerificationTxState.SasStarted -> {
// if (!accepted) { // if (!accepted) {
// accepted = true // accepted = true
@ -483,113 +501,113 @@ class SASTest : InstrumentedTest {
// } // }
// } // }
// } // }
SasTransactionState.SasShortCodeReady -> { SasTransactionState.SasShortCodeReady -> {
if (!matched) { if (!matched) {
matched = true matched = true
runBlocking { runBlocking {
delay(500) delay(500)
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
}
} }
} }
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
} }
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
} }
} }
}
// bobVerificationService.addListener(bobListener) // bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = runBlocking { val bobDeviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId bobSession.cryptoService().getMyCryptoDevice().deviceId
}
aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
Timber.v("Await after beginKey ${Thread.currentThread()}")
testHelper.await(verifiedLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
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)
} }
aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
@Test Timber.v("Await after beginKey ${Thread.currentThread()}")
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> testHelper.await(verifiedLatch)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceVerificationService = aliceSession.cryptoService().verificationService() // Assert that devices are verified
val bobVerificationService = bobSession.cryptoService().verificationService() val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
val req = aliceVerificationService.requestKeyVerificationInDMs( assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
bobSession.myUserId, }
cryptoTestData.roomId
)
val requestID = req.transactionId @Test
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
Log.v("TEST", "== requestID is $requestID") val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
testHelper.retryPeriodically { val req = aliceVerificationService.requestKeyVerificationInDMs(
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), listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId, bobSession.myUserId,
requestID cryptoTestData.roomId
) )
// wait for alice to get the ready val requestID = req.transactionId
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
}
// Start concurrent! Log.v("TEST", "== requestID is $requestID")
aliceVerificationService.startKeyVerification(
method = VerificationMethod.SAS,
otherUserId = bobSession.myUserId,
requestId = requestID,
)
bobVerificationService.startKeyVerification( testHelper.retryPeriodically {
method = VerificationMethod.SAS, val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
otherUserId = aliceSession.myUserId, Log.v("TEST", "== prBobPOV is $prBobPOV")
requestId = requestID, prBobPOV?.transactionId == requestID
)
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
testHelper.retryPeriodically {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state() == SasTransactionState.SasShortCodeReady
}
// wait for alice to get the ready
testHelper.retryPeriodically {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
}
} }
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID
)
// wait for alice to get the ready
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
}
// Start concurrent!
aliceVerificationService.startKeyVerification(
method = VerificationMethod.SAS,
otherUserId = bobSession.myUserId,
requestId = requestID,
)
bobVerificationService.startKeyVerification(
method = VerificationMethod.SAS,
otherUserId = aliceSession.myUserId,
requestId = requestID,
)
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
testHelper.retryPeriodically {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state() == SasTransactionState.SasShortCodeReady
}
// wait for alice to get the ready
testHelper.retryPeriodically {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
}
}
*/ */
} }

View file

@ -263,6 +263,8 @@ internal class VerificationActor @AssistedInject constructor(
} }
is VerificationIntent.GetExistingRequestsForUser -> { is VerificationIntent.GetExistingRequestsForUser -> {
verificationRequestsStore.getExistingRequestsForUser(msg.userId).let { requests -> verificationRequestsStore.getExistingRequestsForUser(msg.userId).let { requests ->
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: Found $requests")
msg.deferred.complete(requests.map { it.toPendingVerificationRequest() }) msg.deferred.complete(requests.map { it.toPendingVerificationRequest() })
} }
} }
@ -306,6 +308,8 @@ internal class VerificationActor @AssistedInject constructor(
private fun dispatchUpdate(update: VerificationEvent) { private fun dispatchUpdate(update: VerificationEvent) {
// We don't want to block on emit. // We don't want to block on emit.
// If no subscriber there is a small buffer // If no subscriber there is a small buffer
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}] Dispatch Request update ${update.transactionId}")
scope.launch { scope.launch {
eventFlow.emit(update) eventFlow.emit(update)
} }
@ -565,21 +569,29 @@ internal class VerificationActor @AssistedInject constructor(
private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) { private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) {
val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId) val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId)
?: return Unit.also { ?: return Unit.also {
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: Can't start unknown request ${msg.requestId}")
msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Unknown request")) msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Unknown request"))
} }
if (matchingRequest.state != EVerificationState.Ready) { if (matchingRequest.state != EVerificationState.Ready) {
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: Can't start a non ready request ${msg.requestId}")
msg.deferred.completeExceptionally(java.lang.IllegalStateException("Can't start a non ready request")) msg.deferred.completeExceptionally(java.lang.IllegalStateException("Can't start a non ready request"))
return return
} }
val otherDeviceId = matchingRequest.otherDeviceId() ?: return Unit.also { val otherDeviceId = matchingRequest.otherDeviceId() ?: return Unit.also {
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: Can't start null other device id ${msg.requestId}")
msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Failed to find other device Id")) msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Failed to find other device Id"))
} }
val existingTransaction = getExistingTransaction<VerificationTransaction>(msg.otherUserId, msg.requestId) val existingTransaction = getExistingTransaction<VerificationTransaction>(msg.otherUserId, msg.requestId)
if (existingTransaction is SasVerificationTransaction) { if (existingTransaction is SasVerificationTransaction) {
// there is already an existing transaction?? // there is already an existing transaction??
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: Can't start, already started ${msg.requestId}")
msg.deferred.completeExceptionally(IllegalStateException("Already started")) msg.deferred.completeExceptionally(IllegalStateException("Already started"))
return return
} }
@ -589,12 +601,17 @@ internal class VerificationActor @AssistedInject constructor(
requestId = msg.requestId requestId = msg.requestId
) )
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]:sending start to other ${msg.requestId} in room ${matchingRequest.roomId}")
transportLayer.sendToOther( transportLayer.sendToOther(
matchingRequest, matchingRequest,
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
startMessage, startMessage,
) )
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: start sent to other ${msg.requestId}")
// should check if already one (and cancel it) // should check if already one (and cancel it)
val tx = KotlinSasTransaction( val tx = KotlinSasTransaction(
channel = channel, channel = channel,
@ -1262,6 +1279,9 @@ internal class VerificationActor @AssistedInject constructor(
null null
} }
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}] Request ${msg.transactionId} code is $qrCodeData")
val readyInfo = ValidVerificationInfoReady( val readyInfo = ValidVerificationInfoReady(
msg.transactionId, msg.transactionId,
verificationTrustBackend.getMyDeviceId(), verificationTrustBackend.getMyDeviceId(),
@ -1274,9 +1294,14 @@ internal class VerificationActor @AssistedInject constructor(
methods = commonMethods, methods = commonMethods,
fromDevice = verificationTrustBackend.getMyDeviceId() fromDevice = verificationTrustBackend.getMyDeviceId()
) )
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}] Request ${msg.transactionId} sending ready")
try { try {
transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message) transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}] Request ${msg.transactionId} failed to send ready")
msg.deferred.completeExceptionally(failure) msg.deferred.completeExceptionally(failure)
return return
} }

View file

@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService 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.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toModel 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.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.MessageType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.DeviceId
@ -55,19 +55,19 @@ internal class VerificationMessageProcessor @Inject constructor(
} }
suspend fun process(roomId: String, event: Event) { suspend fun process(roomId: String, event: Event) {
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.getClearType()} from ${event.senderId}") Timber.v("## SAS Verification[${userId.take(5)}] live observer: received msgId: ${event.eventId} msgtype: ${event.getClearType()} from ${event.senderId}")
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // 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. // the message should be ignored by the receiver.
if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:${event.ageLocalTs} ms") Timber.d("## SAS Verification[${userId.take(5)}] live observer: msgId: ${event.eventId} is outdated age:${event.ageLocalTs} ms")
} }
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") Timber.v("## SAS Verification[${userId.take(5)}] live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
// Relates to is not encrypted // Relates to is not encrypted
val relatesToEventId = event.content.toModel<MessageRelationContent>()?.relatesTo?.eventId val relatesToEventId = event.getRelationContent()?.eventId
if (event.senderId == userId) { if (event.senderId == userId) {
// If it's send from me, we need to keep track of Requests or Start // If it's send from me, we need to keep track of Requests or Start
@ -78,7 +78,7 @@ internal class VerificationMessageProcessor @Inject constructor(
// event.getClearContent().toModel<MessageVerificationRequestContent>()?.let { // event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
// if (it.fromDevice != deviceId) { // if (it.fromDevice != deviceId) {
// // The verification is requested from another device // // The verification is requested from another device
// Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") // Timber.v("## SAS Verification[$userItakeng5 live observer: Transaction requested from other device tid:${event.eventId} ")
// event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } // event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
// } // }
// } // }
@ -87,7 +87,7 @@ internal class VerificationMessageProcessor @Inject constructor(
// event.getClearContent().toModel<MessageVerificationStartContent>()?.let { // event.getClearContent().toModel<MessageVerificationStartContent>()?.let {
// if (it.fromDevice != deviceId) { // if (it.fromDevice != deviceId) {
// // The verification is started from another device // // The verification is started from another device
// Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") // Timber.v("## SAS Verification[$userItakeng5 live observer: Transaction started by other device tid:$relatesToEventId ")
// relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } // relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
// verificationService.onRoomRequestHandledByOtherDevice(event) // verificationService.onRoomRequestHandledByOtherDevice(event)
// } // }
@ -98,14 +98,15 @@ internal class VerificationMessageProcessor @Inject constructor(
event.getClearContent().toModel<MessageVerificationReadyContent>()?.let { event.getClearContent().toModel<MessageVerificationReadyContent>()?.let {
if (it.fromDevice != deviceId) { if (it.fromDevice != deviceId) {
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") Timber.v("## SAS Verification[${userId.take(5)}] live observer: Transaction started by other device tid:$relatesToEventId ")
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
verificationService.onRoomReadyFromOneOfMyOtherDevice(event) verificationService.onRoomReadyFromOneOfMyOtherDevice(event)
} }
} }
} else {
Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
} }
// else {
// Timber.v("## SAS Verification[${userId.take(5)}] ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
// }
// } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { // } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
// relatesToEventId?.let { // relatesToEventId?.let {
// transactionsHandledByOtherDevice.remove(it) // transactionsHandledByOtherDevice.remove(it)
@ -114,13 +115,13 @@ internal class VerificationMessageProcessor @Inject constructor(
// } else if (EventType.ENCRYPTED == event.getClearType()) { // } else if (EventType.ENCRYPTED == event.getClearType()) {
// verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event) // verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
// } // }
Timber.v("## SAS Verification[${userId.take(5)}] discard from me msgId: ${event.eventId}")
return return
} }
if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) { if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) {
// Ignore this event, it is directed to another of my devices // 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 ") Timber.v("## SAS Verification[${userId.take(5)}] live observer: Ignore Transaction handled by other device tid:$relatesToEventId ")
return return
} }
when (event.getClearType()) { when (event.getClearType()) {

View file

@ -16,11 +16,15 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.BuildConfig
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class SecretShareManager @Inject constructor() { internal class SecretShareManager @Inject constructor() {
suspend fun requestSecretTo(deviceId: String, secretName: String) { suspend fun requestSecretTo(deviceId: String, secretName: String) {
// nop in rust? // nop in rust?
if (BuildConfig.DEBUG) TODO("requestSecretTo Not implemented in Rust")
Timber.e("SecretShareManager Not supported in rust $deviceId, $secretName")
} }
} }

View file

@ -16,11 +16,13 @@
package org.matrix.android.sdk.internal.crypto.algorithms.megolm package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
// empty in rust // empty in rust
class UnRequestedForwardManager @Inject constructor() { class UnRequestedForwardManager @Inject constructor() {
fun onInviteReceived(roomId: String, orEmpty: String, epochMillis: Long) { fun onInviteReceived(roomId: String, inviterId: String, epochMillis: Long) {
Timber.e("UnRequestedForwardManager not yet implemented $roomId, $inviterId, $epochMillis")
} }
} }

View file

@ -239,9 +239,9 @@ internal class RequestSender @Inject constructor(
val hashMap = content as? Map<*, *> val hashMap = content as? Map<*, *>
val action = hashMap?.get("action")?.toString() val action = hashMap?.get("action")?.toString()
if (GossipingToDeviceObject.ACTION_SHARE_REQUEST == action) { if (GossipingToDeviceObject.ACTION_SHARE_REQUEST == action) {
val body = hashMap.get("body") as? Map<*, *> val requestBody = hashMap["body"] as? Map<*, *>
val roomId = body?.get("room_id") as? String val roomId = requestBody?.get("room_id") as? String
val sessionId = body?.get("session_id") as? String val sessionId = requestBody?.get("session_id") as? String
if (roomId != null && sessionId != null) { if (roomId != null && sessionId != null) {
rateLimiter.tryFromBackupIfPossible(sessionId, roomId) rateLimiter.tryFromBackupIfPossible(sessionId, roomId)
} }