Add option to disable key gossip, clear key request on trust change

This commit is contained in:
Valere 2022-03-18 17:11:56 +01:00
parent 6a509ce22d
commit 1d948d6b20
12 changed files with 696 additions and 577 deletions

View File

@ -64,9 +64,6 @@ import java.util.concurrent.CountDownLatch
@LargeTest
class E2eeSanityTests : InstrumentedTest {
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
/**
* Simple test that create an e2ee room.
* Some new members are added, and a message is sent.
@ -78,16 +75,24 @@ class E2eeSanityTests : InstrumentedTest {
*/
@Test
fun testSendingE2EEMessages() {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
// we want to disable key gossiping to just check initial sending of keys
aliceSession.cryptoService().enableKeyGossiping(false)
cryptoTestData.secondSession?.cryptoService()?.enableKeyGossiping(false)
// add some more users and invite them
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
.map {
testHelper.createAccount(it, SessionTestParams(true))
testHelper.createAccount(it, SessionTestParams(true)).also {
it.cryptoService().enableKeyGossiping(false)
}
}
Log.v("#E2E TEST", "All accounts created")
@ -101,18 +106,18 @@ class E2eeSanityTests : InstrumentedTest {
// All user should accept invite
otherAccounts.forEach { otherSession ->
waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID)
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
}
// check that alice see them as joined (not really necessary?)
ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID)
ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
Log.v("#E2E TEST", "All users have joined the room")
Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
Assert.assertTrue("Message should be sent", sentEventId != null)
@ -142,10 +147,10 @@ class E2eeSanityTests : InstrumentedTest {
}
newAccount.forEach {
waitForAndAcceptInviteInRoom(it, e2eRoomID)
waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID)
}
ensureMembersHaveJoined(aliceSession, newAccount, e2eRoomID)
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
// wait a bit
testHelper.runBlockingTest {
@ -170,7 +175,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends a new message")
val secondMessage = "2 This is my message"
val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
val secondSentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, secondMessage)
// new members should be able to decrypt it
newAccount.forEach { otherSession ->
@ -194,6 +199,14 @@ class E2eeSanityTests : InstrumentedTest {
cryptoTestData.cleanUp(testHelper)
}
@Test
fun testKeyGossipingIsEnabledByDefault() {
val testHelper = CommonTestHelper(context())
val session = testHelper.createAccount("alice", SessionTestParams(true))
Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled())
testHelper.signOutAndClose(session)
}
/**
* Quick test for basic key backup
* 1. Create e2e between Alice and Bob
@ -210,6 +223,9 @@ class E2eeSanityTests : InstrumentedTest {
*/
@Test
fun testBasicBackupImport() {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
@ -233,7 +249,7 @@ class E2eeSanityTests : InstrumentedTest {
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
@ -327,6 +343,9 @@ class E2eeSanityTests : InstrumentedTest {
*/
@Test
fun testSimpleGossip() {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
@ -334,15 +353,13 @@ class E2eeSanityTests : InstrumentedTest {
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
cryptoTestHelper.initializeCrossSigning(bobSession)
// let's send a few message to bob
val sentEventIds = mutableListOf<String>()
val messagesText = listOf("1. Hello", "2. Bob")
Log.v("#E2E TEST", "Alice sends some messages")
messagesText.forEach { text ->
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
sentEventIds.add(it)
}
@ -357,7 +374,7 @@ class E2eeSanityTests : InstrumentedTest {
}
// Ensure bob can decrypt
ensureIsDecrypted(sentEventIds, bobSession, e2eRoomID)
ensureIsDecrypted(testHelper, sentEventIds, bobSession, e2eRoomID)
// Let's now add a new bob session
// Create a new session for bob
@ -431,6 +448,9 @@ class E2eeSanityTests : InstrumentedTest {
*/
@Test
fun testForwardBetterKey() {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession
val bobSessionWithBetterKey = cryptoTestData.secondSession!!
@ -438,15 +458,13 @@ class E2eeSanityTests : InstrumentedTest {
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
cryptoTestHelper.initializeCrossSigning(bobSessionWithBetterKey)
// let's send a few message to bob
var firstEventId: String
val firstMessage = "1. Hello"
Log.v("#E2E TEST", "Alice sends some messages")
firstMessage.let { text ->
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
@ -459,7 +477,7 @@ class E2eeSanityTests : InstrumentedTest {
}
// Ensure bob can decrypt
ensureIsDecrypted(listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
ensureIsDecrypted(testHelper, listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
// Let's add a new unverified session from bob
val newBobSession = testHelper.logIntoAccount(bobSessionWithBetterKey.myUserId, SessionTestParams(true))
@ -474,7 +492,7 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Alice sends some messages")
secondMessage.let { text ->
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
@ -524,7 +542,7 @@ class E2eeSanityTests : InstrumentedTest {
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
// now let new session request
newBobSession.cryptoService().requestRoomKeyForEvent(firstEventNewBobPov.root)
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
// We need to wait for the key request to be sent out and then a reply to be received
@ -552,11 +570,12 @@ class E2eeSanityTests : InstrumentedTest {
}
}
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSessionWithBetterKey)
testHelper.signOutAndClose(newBobSession)
}
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendTextMessage(text)
var sentEventId: String? = null
testHelper.waitWithLatch(4 * 60_000L) { latch ->
@ -586,6 +605,9 @@ class E2eeSanityTests : InstrumentedTest {
*/
@Test
fun testSelfInteractiveVerificationAndGossip() {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
cryptoTestHelper.bootstrapSecurity(aliceSession)
@ -614,16 +636,16 @@ class E2eeSanityTests : InstrumentedTest {
Log.d("##TEST", "exitsingPov: $tx")
val sasTx = tx as OutgoingSasVerificationTransaction
when (sasTx.uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
// for the test we just accept?
oldCode = sasTx.getDecimalCodeRepresentation()
sasTx.userHasVerifiedShortCode()
}
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
// we can release this latch?
oldCompleteLatch.countDown()
}
else -> Unit
else -> Unit
}
}
})
@ -647,20 +669,20 @@ class E2eeSanityTests : InstrumentedTest {
val sasTx = tx as IncomingSasVerificationTransaction
when (sasTx.uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
// no need to accept as there was a request first it will auto accept
}
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
if (matchOnce) {
sasTx.userHasVerifiedShortCode()
newCode = sasTx.getDecimalCodeRepresentation()
matchOnce = false
}
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
newCompleteLatch.countDown()
}
else -> Unit
else -> Unit
}
}
})
@ -727,7 +749,7 @@ class E2eeSanityTests : InstrumentedTest {
testHelper.signOutAndClose(aliceNewSession)
}
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
otherAccounts.map {
@ -739,7 +761,7 @@ class E2eeSanityTests : InstrumentedTest {
}
}
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
@ -751,7 +773,8 @@ class E2eeSanityTests : InstrumentedTest {
}
}
testHelper.runBlockingTest(60_000) {
// not sure why it's taking so long :/
testHelper.runBlockingTest(90_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
try {
otherSession.roomService().joinRoom(e2eRoomID)
@ -769,7 +792,7 @@ class E2eeSanityTests : InstrumentedTest {
}
}
private fun ensureIsDecrypted(sentEventIds: List<String>, session: Session, e2eRoomID: String) {
private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
sentEventIds.forEach { sentEventId ->
testHelper.retryPeriodicallyWithLatch(latch) {

View File

@ -50,11 +50,11 @@ import org.matrix.android.sdk.internal.crypto.RequestResult
@LargeTest
class KeyShareTests : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
@Test
fun test_DoNotSelfShareIfNotTrusted() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
@ -71,13 +71,17 @@ class KeyShareTests : InstrumentedTest {
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
val sentEvent = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first()
val sentEventId = sentEvent.eventId
val sentEventText = sentEvent.getLastMessageContent()?.body
// Open a new sessionx
// Open a new session
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false))
// block key requesting for now as decrypt will send requests (room summary is trying to decrypt)
aliceSession2.cryptoService().enableKeyGossiping(false)
commonTestHelper.syncSession(aliceSession2)
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
Log.v("TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}")
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
@ -95,7 +99,10 @@ class KeyShareTests : InstrumentedTest {
}
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
assertEquals("There should be no request as it's disabled", 0, outgoingRequestsBefore.size)
// Try to request
aliceSession2.cryptoService().enableKeyGossiping(true)
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
@ -105,10 +112,6 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
.filter { req ->
// filter out request that was known before
!outgoingRequestsBefore.any { req.requestId == it.requestId }
}
.let {
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
outGoingRequestId = outgoing?.requestId
@ -156,7 +159,7 @@ class KeyShareTests : InstrumentedTest {
val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
val reply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
val resultCode = (reply?.result as? RequestResult.Failure)?.code
resultCode == WithHeldCode.UNAUTHORISED
resultCode == WithHeldCode.UNVERIFIED
}
}
@ -189,6 +192,9 @@ class KeyShareTests : InstrumentedTest {
*/
@Test
fun test_reShareIfWasIntendedToBeShared() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
@ -219,6 +225,9 @@ class KeyShareTests : InstrumentedTest {
*/
@Test
fun test_reShareToUnverifiedIfWasIntendedToBeShared() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
val aliceSession = testData.firstSession
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
@ -254,6 +263,9 @@ class KeyShareTests : InstrumentedTest {
*/
@Test
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
@ -263,7 +275,9 @@ class KeyShareTests : InstrumentedTest {
val sentEventMegolmSession = sentEvents.first().root.content.toModel<EncryptedEventContent>()!!.sessionId!!
// Let alice now add a new session
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false))
aliceNewSession.cryptoService().enableKeyGossiping(false)
commonTestHelper.syncSession(aliceNewSession)
// we wait bob first session to be aware of that session?
commonTestHelper.waitWithLatch { latch ->
@ -289,7 +303,8 @@ class KeyShareTests : InstrumentedTest {
}
assertEquals(sentEventMegolmSession, newEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId)
// Request a first time, bob and alice should reply with unauthorized
// Request a first time, bob should reply with unauthorized and alice should reply with unverified
aliceNewSession.cryptoService().enableKeyGossiping(true)
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
commonTestHelper.waitWithLatch { latch ->
@ -309,6 +324,7 @@ class KeyShareTests : InstrumentedTest {
val bobDeviceReply = outgoing?.results
?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId }
val result = bobDeviceReply?.result
Log.v("TEST", "bob device result is $result")
result != null && result is RequestResult.Success && result.chainIndex > 0
}
}
@ -322,7 +338,7 @@ class KeyShareTests : InstrumentedTest {
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
// Let's now try to request
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
@ -368,6 +384,9 @@ class KeyShareTests : InstrumentedTest {
*/
@Test
fun test_dontCancelToEarly() {
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!

View File

@ -76,6 +76,15 @@ interface CryptoService {
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
/**
* Enable or disable key gossiping.
* Default is true.
* If set to false this device won't send key_request nor will accept key forwarded
*/
fun enableKeyGossiping(enable: Boolean)
fun isKeyGossipingEnabled(): Boolean
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
fun getDeviceTrackingStatus(userId: String): Int

View File

@ -1080,6 +1080,12 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.setGlobalBlacklistUnverifiedDevices(block)
}
override fun enableKeyGossiping(enable: Boolean) {
cryptoStore.enableKeyGossiping(enable)
}
override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled()
/**
* Tells whether the client should ever send encrypted messages to unverified devices.
* The default value is false.

View File

@ -23,18 +23,19 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
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.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
@ -75,7 +76,7 @@ internal class OutgoingKeyRequestManager @Inject constructor(
// We only have one active key request per session, so we don't request if it's already requested
// But it could make sense to check more the backup, as it's evolving.
// We keep a stack as we consider that the key requested last is more likely to be on screen?
private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack<String>()
private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack<Pair<String, String>>()
fun requestKeyForEvent(event: Event, force: Boolean) {
val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return
@ -157,6 +158,20 @@ internal class OutgoingKeyRequestManager @Inject constructor(
}
}
fun onSelfCrossSigningTrustChanged(newTrust: Boolean) {
if (newTrust) {
// we were previously not cross signed, but we are now
// so there is now more chances to get better replies for existing request
// Let's forget about sent request so that next time we try to decrypt we will resend requests
// We don't resend all because we don't want to generate a bulk of traffic
outgoingRequestScope.launch {
sequencer.post {
cryptoStore.deleteOutgoingRoomKeyRequestInState(OutgoingRoomKeyRequestState.SENT)
}
}
}
}
fun onRoomKeyForwarded(sessionId: String,
algorithm: String,
roomId: String,
@ -274,6 +289,15 @@ internal class OutgoingKeyRequestManager @Inject constructor(
}
private fun internalQueueRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int, force: Boolean) {
if (!cryptoStore.isKeyGossipingEnabled()) {
// we might want to try backup?
if (requestBody.roomId != null && requestBody.sessionId != null) {
requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(requestBody.roomId to requestBody.sessionId)
}
Timber.tag(loggerTag.value).d("discarding request for ${requestBody.sessionId} as gossiping is disabled")
return
}
Timber.tag(loggerTag.value).d("Queueing key request for ${requestBody.sessionId} force:$force")
val existing = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
Timber.tag(loggerTag.value).v("Queueing key request exiting is ${existing?.state}")
@ -293,7 +317,9 @@ internal class OutgoingKeyRequestManager @Inject constructor(
Timber.tag(loggerTag.value).d(".. force to request ${requestBody.sessionId}")
cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND)
} else {
requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(existing.requestId)
if (existing.roomId != null && existing.sessionId != null) {
requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(existing.roomId to existing.sessionId)
}
}
}
OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> {
@ -343,10 +369,7 @@ internal class OutgoingKeyRequestManager @Inject constructor(
var currentCalls = 0
measureTimeMillis {
while (requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.isNotEmpty() && currentCalls < maxBackupCallsBySync) {
requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.pop().let {
val req = cryptoStore.getOutgoingRoomKeyRequest(it)
val sessionId = req?.sessionId ?: return@let
val roomId = req.roomId ?: return@let
requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.pop().let { (roomId, sessionId) ->
// we want to rate limit that somehow :/
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
}

View File

@ -54,10 +54,7 @@ internal class MXMegolmDecryption(
@Throws(MXCryptoError::class)
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
// If cross signing is enabled, we don't send request until the keys are trusted
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
return decryptEvent(event, timeline, requestOnFail)
return decryptEvent(event, timeline, true)
}
@Throws(MXCryptoError::class)
@ -164,6 +161,11 @@ internal class MXMegolmDecryption(
return
}
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
if (!cryptoStore.isKeyGossipingEnabled()) {
Timber.tag(loggerTag.value)
.i("onRoomKeyEvent(), ignore forward adding as per crypto config : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
return
}
Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
?: return

View File

@ -38,6 +38,8 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
@ -70,6 +72,7 @@ internal class DefaultCrossSigningService @Inject constructor(
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
private val workManagerProvider: WorkManagerProvider,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
) : CrossSigningService,
DeviceListManager.UserDevicesUpdateListener {
@ -781,7 +784,8 @@ internal class DefaultCrossSigningService @Inject constructor(
// If it's me, recheck trust of all users and devices?
val users = ArrayList<String>()
if (otherUserId == userId && currentTrust != trusted) {
// reRequestAllPendingRoomKeyRequest()
// notify key requester
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
cryptoStore.updateUsersTrust {
users.add(it)
checkUserTrust(it).isVerified()
@ -796,19 +800,4 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}
}
// private fun reRequestAllPendingRoomKeyRequest() {
// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// Timber.d("## CrossSigning - reRequest pending outgoing room key requests")
// cryptoStore.getOutgoingRoomKeyRequests().forEach {
// it.requestBody?.let { requestBody ->
// if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) {
// outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
// } else {
// outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
// }
// }
// }
// }
// }
}

View File

@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldCo
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.internal.crypto.model.AuditTrail
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
@ -81,6 +81,15 @@ internal interface IMXCryptoStore {
*/
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
/**
* Enable or disable key gossiping.
* Default is true.
* If set to false this device won't send key_request nor will accept key forwarded
*/
fun enableKeyGossiping(enable: Boolean)
fun isKeyGossipingEnabled(): Boolean
/**
* Provides the rooms ids list in which the messages are not encrypted for the unverified devices.
*
@ -386,6 +395,7 @@ internal interface IMXCryptoStore {
event: Event)
fun deleteOutgoingRoomKeyRequest(requestId: String)
fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState)
fun saveIncomingKeyRequestAuditTrail(
requestId: String,

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
@ -59,5 +60,12 @@ class MigrateCryptoTo016(realm: DynamicRealm) : RealmMigrator(realm, 15) {
.addField(KeyRequestReplyEntityFields.SENDER_ID, String::class.java)
.addField(KeyRequestReplyEntityFields.FROM_DEVICE, String::class.java)
.addField(KeyRequestReplyEntityFields.EVENT_JSON, String::class.java)
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_REQUESTING_AND_SHARING, Boolean::class.java)
?.transform {
// set the default value to true
it.setBoolean(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_REQUESTING_AND_SHARING, true)
}
}
}

View File

@ -33,6 +33,8 @@ internal open class CryptoMetadataEntity(
var deviceSyncToken: String? = null,
// Settings for blacklisting unverified devices.
var globalBlacklistUnverifiedDevices: Boolean = false,
// setting to enable or disable key gossiping
var globalEnableKeyRequestingAndSharing: Boolean = true,
// The keys backup version currently used. Null means no backup.
var backupVersion: String? = null,

View File

@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.membership.joining
import io.realm.RealmConfiguration
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
@ -52,6 +54,7 @@ internal class DefaultJoinRoomTask @Inject constructor(
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val coroutineDispatcher: MatrixCoroutineDispatchers,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
private val globalErrorReceiver: GlobalErrorReceiver
) : JoinRoomTask {
@ -68,11 +71,13 @@ internal class DefaultJoinRoomTask @Inject constructor(
}
val joinRoomResponse = try {
executeRequest(globalErrorReceiver) {
roomAPI.join(
roomIdOrAlias = params.roomIdOrAlias,
viaServers = params.viaServers.take(3),
params = extraParams
)
withContext(coroutineDispatcher.io) {
roomAPI.join(
roomIdOrAlias = params.roomIdOrAlias,
viaServers = params.viaServers.take(3),
params = extraParams
)
}
}
} catch (failure: Throwable) {
roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.FailedJoining(failure))