Add share test + fix

Crypto config to only request to own device. Only cancel request if ratchet index is low enough
This commit is contained in:
Valere 2022-03-16 14:50:50 +01:00
parent 9177cb11d5
commit 9747eb2432
39 changed files with 1364 additions and 1911 deletions

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
import android.os.SystemClock
import android.util.Log
import androidx.lifecycle.Observer
import org.amshove.kluent.fail
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
@ -39,6 +41,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
@ -46,11 +49,13 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
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.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
@ -299,7 +304,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
)
)
}
}, it)
}, it
)
}
}
@ -308,7 +314,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
*/
fun bootstrapSecurity(session: Session) {
initializeCrossSigning(session)
val ssssService = session.sharedSecretStorageService
val ssssService = session.sharedSecretStorageService()
testHelper.runBlockingTest {
val keyInfo = ssssService.generateKey(
UUID.randomUUID().toString(),
@ -369,7 +375,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
requestID,
roomId,
bob.myUserId,
bob.sessionParams.credentials.deviceId!!)
bob.sessionParams.credentials.deviceId!!
)
// we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null
@ -451,4 +458,50 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
return CryptoTestData(roomId, sessions)
}
fun ensureCanDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
sentEventIds.forEachIndexed { index, sentEventId ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "").let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
}
} catch (error: MXCryptoError) {
// nop
}
}
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
event.getClearType() == EventType.MESSAGE &&
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
}
}
}
}
fun ensureCannotDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) {
sentEventIds.forEach { sentEventId ->
val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "")
fail("Should not be able to decrypt event")
} catch (error: MXCryptoError) {
val errorType = (error as? MXCryptoError.Base)?.errorType
if (expectedError == null) {
assertNotNull(errorType)
} else {
assertEquals("Unexpected reason", expectedError, errorType)
}
}
}
}
}
}

View File

@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
@ -296,7 +295,7 @@ class E2eeSanityTests : InstrumentedTest {
}
}
// after initial sync events are not decrypted, so we have to try manually
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Let's now import keys from backup
@ -317,7 +316,7 @@ class E2eeSanityTests : InstrumentedTest {
}
// ensure bob can now decrypt
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
testHelper.signOutAndClose(newBobSession)
}
@ -368,7 +367,7 @@ class E2eeSanityTests : InstrumentedTest {
// check that new bob can't currently decrypt
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// newBobSession.cryptoService().getOutgoingRoomKeyRequests()
// .firstOrNull {
// it.sessionId ==
@ -408,7 +407,7 @@ class E2eeSanityTests : InstrumentedTest {
}
}
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// Now mark new bob session as verified
@ -421,7 +420,7 @@ class E2eeSanityTests : InstrumentedTest {
newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
}
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(newBobSession)
@ -467,7 +466,7 @@ class E2eeSanityTests : InstrumentedTest {
// check that new bob can't currently decrypt
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
cryptoTestHelper.ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Now let alice send a new message. this time the new bob session will be able to decrypt
var secondEventId: String
@ -686,8 +685,13 @@ class E2eeSanityTests : InstrumentedTest {
// wait for secret gossiping to happen
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown() &&
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
}
}
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
}
}
@ -765,32 +769,6 @@ class E2eeSanityTests : InstrumentedTest {
}
}
private fun ensureCanDecrypt(sentEventIds: MutableList<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
sentEventIds.forEachIndexed { index, sentEventId ->
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
session.cryptoService().decryptEvent(event, "").let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
}
} catch (error: MXCryptoError) {
// nop
}
}
event.getClearType() == EventType.MESSAGE &&
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
}
}
}
}
private fun ensureIsDecrypted(sentEventIds: List<String>, session: Session, e2eRoomID: String) {
testHelper.waitWithLatch { latch ->
sentEventIds.forEach { sentEventId ->
@ -803,23 +781,4 @@ class E2eeSanityTests : InstrumentedTest {
}
}
}
private fun ensureCannotDecrypt(sentEventIds: List<String>, newBobSession: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType?) {
sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
testHelper.runBlockingTest {
try {
newBobSession.cryptoService().decryptEvent(event, "")
fail("Should not be able to decrypt event")
} catch (error: MXCryptoError) {
val errorType = (error as? MXCryptoError.Base)?.errorType
if (expectedError == null) {
Assert.assertNotNull(errorType)
} else {
assertEquals(expectedError, errorType, "Unexpected reason")
}
}
}
}
}
}

View File

@ -19,45 +19,31 @@ package org.matrix.android.sdk.internal.crypto.gossiping
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.fail
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
import org.junit.Assert.assertNull
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.internal.crypto.RequestResult
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@ -65,11 +51,12 @@ import kotlin.coroutines.resume
class KeyShareTests : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_DoNotSelfShareIfNotTrusted() {
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
// Create an encrypted room and add a message
val roomId = commonTestHelper.runBlockingTest {
@ -84,11 +71,14 @@ class KeyShareTests : InstrumentedTest {
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
val sentEvent = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first()
val sentEventId = sentEvent.eventId
val sentEventText = sentEvent.getLastMessageContent()?.body
// Open a new sessionx
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
Log.v("TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}")
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
@ -139,17 +129,34 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
// aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
// Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
// Log.v("TEST", "=========================")
// it.forEach { keyRequest ->
// Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
// }
// Log.v("TEST", "=========================")
// }
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
incoming != null
}
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
Log.v("TEST", "=========================")
it.forEach { keyRequest ->
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
}
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
Log.v("TEST", "=========================")
}
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
incoming?.state == GossipingRequestState.REJECTED
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
}
}
@ -168,249 +175,279 @@ class KeyShareTests : InstrumentedTest {
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1")
Log.v("TEST", "=========================")
it.forEach {
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
}
Log.v("TEST", "=========================")
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
}
}
}
Thread.sleep(6_000)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
// It should have been deleted from store
val outgoingRoomKeyRequests = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
outgoingRoomKeyRequests.isEmpty()
}
}
try {
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
cryptoTestHelper.ensureCanDecrypt(listOf(receivedEvent.eventId), aliceSession2, roomId, listOf(sentEventText ?: ""))
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(aliceSession2)
}
// See E2ESanityTest for a test regarding secret sharing
/**
* Test that the sender of a message accepts to re-share to another user
* if the key was originally shared with him
*/
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_ShareSSSSSecret() {
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
fun test_reShareIfWasIntendedToBeShared() {
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
val bobSession = testData.secondSession!!
commonTestHelper.doSync<Unit> {
aliceSession1.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
user = aliceSession1.myUserId,
password = TestConstants.PASSWORD
)
)
}
}, it)
}
val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
// Also bootstrap keybackup on first session
val creationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
}
val version = commonTestHelper.doSync<KeysVersion> {
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
// bob should be able to decrypt
cryptoTestHelper.ensureCanDecrypt(listOf(sentEvent.eventId), bobSession, testData.roomId, listOf(sentEvent.getLastMessageContent()?.body ?: ""))
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
// force keys download
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
}
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
}
var session1ShortCode: String? = null
var session2ShortCode: String? = null
aliceVerificationService1.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.OnStarted) {
(tx as IncomingSasVerificationTransaction).performAccept()
}
if (tx.state == VerificationTxState.ShortCodeReady) {
session1ShortCode = tx.getDecimalCodeRepresentation()
Thread.sleep(500)
tx.userHasVerifiedShortCode()
}
}
}
})
aliceVerificationService2.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.ShortCodeReady) {
session2ShortCode = tx.getDecimalCodeRepresentation()
Thread.sleep(500)
tx.userHasVerifiedShortCode()
}
}
}
})
val txId = "m.testVerif12"
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
?: "", txId)
// Let's try to request any how.
// As it was share previously alice should accept to reshare
bobSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
val outgoing = bobSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val aliceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
aliceReply != null && aliceReply.result is RequestResult.Success
}
}
assertNotNull(session1ShortCode)
Log.d("#TEST", "session1ShortCode: $session1ShortCode")
assertNotNull(session2ShortCode)
Log.d("#TEST", "session2ShortCode: $session2ShortCode")
assertEquals(session1ShortCode, session2ShortCode)
// SSK and USK private keys should have been shared
commonTestHelper.waitWithLatch(60_000) { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
aliceSession2.cryptoService().crossSigningService().canCrossSign()
}
}
// Test that key backup key has been shared to
commonTestHelper.waitWithLatch(60_000) { latch ->
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
commonTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
}
}
commonTestHelper.signOutAndClose(aliceSession1)
commonTestHelper.signOutAndClose(aliceSession2)
}
/**
* Test that our own devices accept to reshare to unverified device if it was shared initialy
* if the key was originally shared with him
*/
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_ImproperKeyShareBug() {
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
fun test_reShareToUnverifiedIfWasIntendedToBeShared() {
val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
val aliceSession = testData.firstSession
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
commonTestHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD,
session = flowResponse.session
)
)
}
}, it)
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
// we wait for alice first session to be aware of that session?
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val newSession = aliceSession.cryptoService().getUserDevices(aliceSession.myUserId)
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
newSession != null
}
}
val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
// Create an encrypted room and send a couple of messages
val roomId = commonTestHelper.runBlockingTest {
aliceSession.roomService().createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
}
)
// Let's try to request any how.
// As it was share previously alice should accept to reshare
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val ownDeviceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
}
}
val roomAlicePov = aliceSession.getRoom(roomId)
assertNotNull(roomAlicePov)
Thread.sleep(1_000)
assertTrue(roomAlicePov?.isEncrypted() == true)
val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
}
// Create bob session
/**
* Tests that keys reshared with own verified session are done from the earliest known index
*/
@Test
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() {
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
val roomFromBob = bobSession.getRoom(testData.roomId)!!
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
commonTestHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
user = bobSession.myUserId,
password = TestConstants.PASSWORD,
session = flowResponse.session
)
)
}
}, it)
}
val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3)
val sentEventMegolmSession = sentEvents.first().root.content.toModel<EncryptedEventContent>()!!.sessionId!!
// Let alice invite bob
commonTestHelper.runBlockingTest {
roomAlicePov.invite(bobSession.myUserId, null)
}
// Let alice now add a new session
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
commonTestHelper.runBlockingTest {
bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList())
}
// we want to discard alice outbound session
aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
// and now resend a new message to reset index to 0
commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)
val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
var dRes = tryOrNull {
commonTestHelper.runBlockingTest {
bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "")
// we wait bob first session to be aware of that session?
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
newSession != null
}
}
assert(dRes == null)
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
val newEventId = newEvent.eventId
val newEventText = newEvent.getLastMessageContent()!!.body
// Try to re-ask the keys
// alice should be able to decrypt the new one
cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText))
// but not the first one!
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
bobSession.cryptoService().reRequestRoomKeyForEvent(beforeJoin!!.root)
// All should be using the same session id
sentEvents.forEach {
assertEquals(sentEventMegolmSession, it.root.content.toModel<EncryptedEventContent>()!!.sessionId)
}
assertEquals(sentEventMegolmSession, newEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId)
Thread.sleep(3_000)
// Request a first time, bob and alice should reply with unauthorized
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
// With the bug the first session would have improperly reshare that key :/
dRes = tryOrNull {
commonTestHelper.runBlockingTest {
bobSession.cryptoService().decryptEvent(beforeJoin.root, "")
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val ownDeviceReply = outgoing?.results
?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
val result = ownDeviceReply?.result
Log.v("TEST", "own device result is $result")
result != null && result is RequestResult.Failure && result.code == WithHeldCode.UNVERIFIED
}
}
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
assert(dRes?.clearEvent == null)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val bobDeviceReply = outgoing?.results
?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId }
val result = bobDeviceReply?.result
result != null && result is RequestResult.Success && result.chainIndex > 0
}
}
// it's a success but still can't decrypt first message
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
// Mark the new session as verified
aliceSession.cryptoService()
.verificationService()
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
// Let's now try to request
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
Log.v("TEST", "=========================")
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
Log.v("TEST", "=========================")
}
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val ownDeviceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
val result = ownDeviceReply?.result
result != null && result is RequestResult.Success && result.chainIndex == 0
}
}
// now the new session should be able to decrypt all!
cryptoTestHelper.ensureCanDecrypt(
sentEvents.map { it.eventId },
aliceNewSession,
testData.roomId,
sentEvents.map { it.getLastMessageContent()!!.body }
)
// Additional test, can we check that bob replied successfully but with a ratcheted key
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
val result = bobReply?.result
result != null && result is RequestResult.Success && result.chainIndex == 3
}
}
commonTestHelper.signOutAndClose(aliceNewSession)
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)
}
/**
* Tests that we don't cancel a request to early on first forward if the index is not good enough
*/
@Test
fun test_dontCancelToEarly() {
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
val roomFromBob = bobSession.getRoom(testData.roomId)!!
val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3)
val sentEventMegolmSession = sentEvents.first().root.content.toModel<EncryptedEventContent>()!!.sessionId!!
// Let alice now add a new session
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
// we wait bob first session to be aware of that session?
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
newSession != null
}
}
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
val newEventId = newEvent.eventId
val newEventText = newEvent.getLastMessageContent()!!.body
// alice should be able to decrypt the new one
cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText))
// but not the first one!
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
// All should be using the same session id
sentEvents.forEach {
assertEquals(sentEventMegolmSession, it.root.content.toModel<EncryptedEventContent>()!!.sessionId)
}
assertEquals(sentEventMegolmSession, newEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId)
// Mark the new session as verified
aliceSession.cryptoService()
.verificationService()
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
// /!\ Stop initial alice session syncing so that it can't reply
aliceSession.stopSync()
// Let's now try to request
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
// Should get a reply from bob and not from alice
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
val result = bobReply?.result
result != null && result is RequestResult.Success && result.chainIndex == 3
}
}
val outgoingReq = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
assertNull("We should not have a reply from first session", outgoingReq!!.results.firstOrNull { it.fromDevice == aliceSession.sessionParams.deviceId })
assertEquals("The request should not be canceled", OutgoingRoomKeyRequestState.SENT, outgoingReq.state)
// let's wake up alice
aliceSession.startSync(true)
// We should now get a reply from first session
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
val ownDeviceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
val result = ownDeviceReply?.result
result != null && result is RequestResult.Success && result.chainIndex == 0
}
}
// It should be in sent then cancel
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
assertEquals("The request should be canceled", OutgoingRoomKeyRequestState.SENT_THEN_CANCELED, outgoing!!.state)
commonTestHelper.signOutAndClose(aliceNewSession)
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)
}
}

View File

@ -31,5 +31,11 @@ data class MXCryptoConfig constructor(
* If set to false, the request will be forwarded to the application layer; in this
* case the application can decide to prompt the user.
*/
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true,
/**
* Currently megolm keys are requested to the sender device and to all of our devices.
* You can limit request only to your sessions by turning this setting to `true`
*/
val limitRoomKeyRequestsToMyDevices: Boolean = false
)

View File

@ -146,6 +146,12 @@ interface CryptoService {
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
/**
* Can be called by the app layer to accept a request manually
* Use carefully as it is prone to social attacks
*/
suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest)
fun getGossipingEventsTrail(): LiveData<PagedList<AuditTrail>>
fun getGossipingEvents(): List<AuditTrail>

View File

@ -16,9 +16,8 @@
package org.matrix.android.sdk.api.session.crypto.keyshare
import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
/**
* Room keys events listener
@ -35,12 +34,12 @@ interface GossipingRequestListener {
* Returns the secret value to be shared
* @return true if is handled
*/
fun onSecretShareRequest(request: IncomingSecretShareRequest): Boolean
fun onSecretShareRequest(request: SecretShareRequest): Boolean
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
fun onRequestCancelled(requestId: IncomingRoomKeyRequest)
}

View File

@ -1,63 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.model
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
/**
* IncomingRequestCancellation describes the incoming room key cancellation.
*/
data class IncomingRequestCancellation(
/**
* The user id
*/
override val userId: String? = null,
/**
* The device id
*/
override val deviceId: String? = null,
/**
* The request id
*/
override val requestId: String? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingRequestCancellation? {
return event.getClearContent()
.toModel<ShareRequestCancellation>()
?.let {
IncomingRequestCancellation(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId,
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
)
}
}
}
}

View File

@ -16,9 +16,9 @@
package org.matrix.android.sdk.api.session.crypto.model
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
import org.matrix.android.sdk.internal.crypto.model.AuditTrail
import org.matrix.android.sdk.internal.crypto.model.IncomingKeyRequestInfo
import org.matrix.android.sdk.internal.crypto.model.TrailType
/**
* IncomingRoomKeyRequest class defines the incoming room keys request.
@ -27,56 +27,61 @@ data class IncomingRoomKeyRequest(
/**
* The user id
*/
override val userId: String? = null,
val userId: String? = null,
/**
* The device id
*/
override val deviceId: String? = null,
val deviceId: String? = null,
/**
* The request id
*/
override val requestId: String? = null,
val requestId: String? = null,
/**
* The request body
*/
val requestBody: RoomKeyRequestBody? = null,
val state: GossipingRequestState = GossipingRequestState.NONE,
/**
* The runnable to call to accept to share the keys
*/
@Transient
var share: Runnable? = null,
/**
* The runnable to call to ignore the key share request.
*/
@Transient
var ignore: Runnable? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
val localCreationTimestamp: Long?
) {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingRoomKeyRequest? {
return event.getClearContent()
.toModel<RoomKeyShareRequest>()
fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? {
return trail
.takeIf { it.type == TrailType.IncomingKeyRequest }
?.let {
it.info as? IncomingKeyRequestInfo
}
?.let {
IncomingRoomKeyRequest(
userId = event.senderId,
deviceId = it.requestingDeviceId,
userId = it.userId,
deviceId = it.deviceId,
requestId = it.requestId,
requestBody = it.body ?: RoomKeyRequestBody(),
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
requestBody = RoomKeyRequestBody(
algorithm = it.alg,
roomId = it.roomId,
senderKey = it.senderKey,
sessionId = it.sessionId
),
localCreationTimestamp = trail.ageLocalTs
)
}
}
fun fromRestRequest(senderId: String, request: RoomKeyShareRequest): IncomingRoomKeyRequest? {
return IncomingRoomKeyRequest(
userId = senderId,
deviceId = request.requestingDeviceId,
requestId = request.requestId,
requestBody = request.body,
localCreationTimestamp = System.currentTimeMillis()
)
}
}
}

View File

@ -1,82 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.model
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
/**
* IncomingSecretShareRequest class defines the incoming secret keys request.
*/
data class IncomingSecretShareRequest(
/**
* The user id
*/
override val userId: String? = null,
/**
* The device id
*/
override val deviceId: String? = null,
/**
* The request id
*/
override val requestId: String? = null,
/**
* The request body
*/
val secretName: String? = null,
/**
* The runnable to call to accept to share the keys
*/
@Transient
var share: ((String) -> Unit)? = null,
/**
* The runnable to call to ignore the key share request.
*/
@Transient
var ignore: Runnable? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingSecretShareRequest? {
return event.getClearContent()
.toModel<SecretShareRequest>()
?.let {
IncomingSecretShareRequest(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId,
secretName = it.secretName,
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
)
}
}
}
}

View File

@ -81,6 +81,13 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.AuditTrail
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.TrailType
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -156,7 +163,7 @@ internal class DefaultCryptoService @Inject constructor(
private val incomingKeyRequestManager: IncomingKeyRequestManager,
private val secretShareManager: SecretShareManager,
//
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
// Actions
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val megolmSessionDataImporter: MegolmSessionDataImporter,
@ -387,7 +394,7 @@ internal class DefaultCryptoService @Inject constructor(
fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
incomingKeyRequestManager.close()
outgoingGossipingRequestManager.close()
outgoingKeyRequestManager.close()
olmDevice.release()
cryptoStore.close()
}
@ -458,7 +465,7 @@ internal class DefaultCryptoService @Inject constructor(
try {
if (toDevices.isEmpty()) {
// this is not blocking
outgoingGossipingRequestManager.requireProcessAllPendingKeyRequests()
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
} else {
Timber.tag(loggerTag.value)
.w("Don't process key requests yet as their might be more to_device to catchup")
@ -778,26 +785,18 @@ internal class DefaultCryptoService @Inject constructor(
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
// gossipingBuffer.add(event)
// Keys are imported directly, not waiting for end of sync
onRoomKeyEvent(event)
}
EventType.REQUEST_SECRET -> {
secretShareManager.handleSecretRequest(event)
// incomingGossipingRequestManager.onGossipingRequestEvent(event)
}
EventType.ROOM_KEY_REQUEST -> {
Timber.w("VALR: key request ${event.getClearContent()}")
// save audit trail
// gossipingBuffer.add(event)
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
Timber.w("VALR: sender Id is ${event.senderId} full ev $event")
event.getClearContent().toModel<RoomKeyShareRequest>()?.let { req ->
event.senderId?.let { incomingKeyRequestManager.addNewIncomingRequest(it, req) }
}
}
EventType.SEND_SECRET -> {
// gossipingBuffer.add(event)
onSecretSendReceived(event)
}
EventType.ROOM_KEY_WITHHELD -> {
@ -842,7 +841,7 @@ internal class DefaultCryptoService @Inject constructor(
withHeldContent.algorithm ?: return
withHeldContent.roomId ?: return
withHeldContent.senderKey ?: return
outgoingGossipingRequestManager.onRoomKeyWithHeld(
outgoingKeyRequestManager.onRoomKeyWithHeld(
sessionId = withHeldContent.sessionId,
algorithm = withHeldContent.algorithm,
roomId = withHeldContent.roomId,
@ -854,14 +853,6 @@ internal class DefaultCryptoService @Inject constructor(
content = event.getClearContent()
)
)
// Timber.tag(loggerTag.value).i("onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
// val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
// if (alg is IMXWithHeldExtension) {
// alg.onRoomKeyWithHeldEvent(senderId, withHeldContent)
// } else {
// Timber.tag(loggerTag.value).e("onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
// return
// }
}
private suspend fun onSecretSendReceived(event: Event) {
@ -1158,52 +1149,11 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the event to decrypt again.
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val sender = event.senderId ?: return
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.tag(loggerTag.value).e("reRequestRoomKeyForEvent Failed to re-request key, null content")
}
val recipients = if (event.senderId == userId) {
mapOf(
userId to listOf("*")
)
} else {
// for the case where you share the key with a device that has a broken olm session
// The other user might Re-shares a megolm session key with devices if the key has already been
// sent to them.
mapOf(
userId to listOf("*"),
// TODO we might not have deviceId in the future due to https://github.com/matrix-org/matrix-spec-proposals/pull/3700
// so in this case query to all
sender to listOf(wireContent.deviceId ?: "*")
)
}
val requestBody = RoomKeyRequestBody(
algorithm = wireContent.algorithm,
roomId = event.roomId,
senderKey = wireContent.senderKey,
sessionId = wireContent.sessionId
)
outgoingGossipingRequestManager.postRoomKeyRequest(requestBody, recipients, true)
outgoingKeyRequestManager.requestKeyForEvent(event, true)
}
override fun requestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// if (!isStarted()) {
// Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
// internalStart(false)
// }
roomDecryptorProvider
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
?.requestKeysForEvent(event, false) ?: run {
Timber.tag(loggerTag.value).v("requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
}
}
outgoingKeyRequestManager.requestKeyForEvent(event, false)
}
/**
@ -1213,7 +1163,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
incomingKeyRequestManager.addRoomKeysRequestListener(listener)
// TODO same for secret manager
secretShareManager.addListener(listener)
}
/**
@ -1223,42 +1173,9 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
incomingKeyRequestManager.removeRoomKeysRequestListener(listener)
// TODO same for secret manager
secretShareManager.addListener(listener)
}
// private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
// val deviceKey = deviceInfo.identityKey()
//
// val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
// val now = System.currentTimeMillis()
// if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
// return
// }
//
// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
// lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
//
// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
//
// // Now send a blank message on that session so the other side knows about it.
// // (The keyshare request is sent in the clear so that won't do)
// // We send this first such that, as long as the toDevice messages arrive in the
// // same order we sent them, the other end will get this first, set up the new session,
// // then get the keyshare request and send the key over this new session (because it
// // is the session it has most recently received a message on).
// val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY)
//
// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
// val sendToDeviceMap = MXUsersDevicesMap<Any>()
// sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
// sendToDeviceTask.execute(sendToDeviceParams)
// }
// }
/**
* Provides the list of unknown devices
*
@ -1312,12 +1229,26 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getOutgoingRoomKeyRequestsPaged()
}
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
return cryptoStore.getIncomingRoomKeyRequestsPaged()
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
return cryptoStore.getGossipingEvents()
.mapNotNull {
IncomingRoomKeyRequest.fromEvent(it)
}
}
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
return cryptoStore.getIncomingRoomKeyRequests()
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
return cryptoStore.getGossipingEventsTrail(TrailType.IncomingKeyRequest) {
IncomingRoomKeyRequest.fromEvent(it)
?: IncomingRoomKeyRequest(localCreationTimestamp = 0L)
}
}
/**
* If you registered a `GossipingRequestListener`, you will be notified of key request
* that was not accepted by the SDK. You can call back this manually to accept anyhow.
*/
override suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
incomingKeyRequestManager.manuallyAcceptRoomKeyRequest(request)
}
override fun getGossipingEventsTrail(): LiveData<PagedList<AuditTrail>> {

View File

@ -311,10 +311,19 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} else {
Timber.v("## CRYPTO | downloadKeys() : starts")
val t0 = System.currentTimeMillis()
val result = doKeyDownloadForUsers(downloadUsers)
Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
result.also {
it.addEntriesFromMap(stored)
try {
val result = doKeyDownloadForUsers(downloadUsers)
Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
result.also {
it.addEntriesFromMap(stored)
}
} catch (failure: Throwable) {
Timber.w(failure, "## CRYPTO | downloadKeys() : doKeyDownloadForUsers failed after ${System.currentTimeMillis() - t0} ms")
if (forceDownload) {
throw failure
} else {
stored
}
}
}
}

View File

@ -26,10 +26,14 @@ import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.data.Credentials
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.keyshare.GossipingRequestListener
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
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.EventType
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
@ -53,6 +57,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val olmDevice: MXOlmDevice,
private val cryptoConfig: MXCryptoConfig,
private val messageEncrypter: MessageEncrypter,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sendToDeviceTask: SendToDeviceTask) {
@ -71,6 +76,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
data class ValidMegolmRequestBody(
val requestId: String,
val requestingUserId: String,
val requestingDeviceId: String,
val roomId: String,
@ -87,6 +93,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
val roomId = body.roomId ?: return null
val sessionId = body.sessionId ?: return null
val senderKey = body.senderKey ?: return null
val requestId = this.requestId ?: return null
if (body.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
val action = when (this.action) {
"request" -> MegolmRequestAction.Request
@ -94,6 +101,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
else -> null
} ?: return null
return ValidMegolmRequestBody(
requestId = requestId,
requestingUserId = senderId,
requestingDeviceId = deviceId,
roomId = roomId,
@ -121,6 +129,21 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
MegolmRequestAction.Cancel -> {
// ignore, we can't cancel as it's not known (probably already processed)
// still notify app layer if it was passed up previously
IncomingRoomKeyRequest.fromRestRequest(senderId, request)?.let { iReq ->
outgoingRequestScope.launch(coroutineDispatchers.computation) {
val listenersCopy = synchronized(gossipingRequestListeners) {
gossipingRequestListeners.toList()
}
listenersCopy.onEach {
tryOrNull {
withContext(coroutineDispatchers.main) {
it.onRequestCancelled(iReq)
}
}
}
}
}
}
}
} else {
@ -131,6 +154,18 @@ internal class IncomingKeyRequestManager @Inject constructor(
MegolmRequestAction.Cancel -> {
// discard the request in buffer
incomingRequestBuffer.remove(existing)
outgoingRequestScope.launch(coroutineDispatchers.computation) {
val listenersCopy = synchronized(gossipingRequestListeners) {
gossipingRequestListeners.toList()
}
listenersCopy.onEach {
IncomingRoomKeyRequest.fromRestRequest(senderId, request)?.let { iReq ->
withContext(coroutineDispatchers.main) {
tryOrNull { it.onRequestCancelled(iReq) }
}
}
}
}
}
}
}
@ -170,6 +205,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
cryptoStore.saveIncomingKeyRequestAuditTrail(
request.requestId,
request.roomId,
request.sessionId,
request.senderKey,
@ -205,6 +241,10 @@ internal class IncomingKeyRequestManager @Inject constructor(
shareIfItWasPreviouslyShared(request, requestingDevice)
}
} else {
if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
Timber.tag(loggerTag.value).v("Ignore request from other user as per crypto config: ${request.shortDbgString()}")
return
}
Timber.tag(loggerTag.value).v("handling request from other user: megolm session ${request.sessionId}")
if (requestingDevice.isBlocked) {
// it's blocked, so send a withheld code
@ -224,12 +264,39 @@ internal class IncomingKeyRequestManager @Inject constructor(
// we share from the index it was previously shared with
shareMegolmKey(request, requestingDevice, wasSessionSharedWithUser.chainIndex.toLong())
} else {
sendWithheldForRequest(request, WithHeldCode.UNAUTHORISED)
// TODO if it's our device we could delegate to the app layer to decide?
val isOwnDevice = requestingDevice.userId == credentials.userId
sendWithheldForRequest(request, if (isOwnDevice) WithHeldCode.UNVERIFIED else WithHeldCode.UNAUTHORISED)
// if it's our device we could delegate to the app layer to decide
if (isOwnDevice) {
outgoingRequestScope.launch(coroutineDispatchers.computation) {
val listenersCopy = synchronized(gossipingRequestListeners) {
gossipingRequestListeners.toList()
}
val iReq = IncomingRoomKeyRequest(
userId = requestingDevice.userId,
deviceId = requestingDevice.deviceId,
requestId = request.requestId,
requestBody = RoomKeyRequestBody(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
senderKey = request.senderKey,
sessionId = request.sessionId,
roomId = request.roomId
),
localCreationTimestamp = System.currentTimeMillis()
)
listenersCopy.onEach {
withContext(coroutineDispatchers.main) {
tryOrNull { it.onRoomKeyRequest(iReq) }
}
}
}
}
}
}
private suspend fun sendWithheldForRequest(request: ValidMegolmRequestBody, code: WithHeldCode) {
Timber.tag(loggerTag.value)
.w("Send withheld $code for req: ${request.shortDbgString()}")
val withHeldContent = RoomKeyWithHeldContent(
roomId = request.roomId,
senderKey = request.senderKey,
@ -253,13 +320,13 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
cryptoStore.saveWithheldAuditTrail(
request.roomId,
request.sessionId,
request.senderKey,
MXCRYPTO_ALGORITHM_MEGOLM,
code,
request.requestingUserId,
request.requestingDeviceId
roomId = request.roomId,
sessionId = request.sessionId,
senderKey = request.senderKey,
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
code = code,
userId = request.requestingUserId,
deviceId = request.requestingDeviceId
)
} catch (failure: Throwable) {
// Ignore it's not that important?
@ -269,6 +336,31 @@ internal class IncomingKeyRequestManager @Inject constructor(
}
}
suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
request.requestId ?: return
request.deviceId ?: return
request.userId ?: return
request.requestBody?.roomId ?: return
request.requestBody.senderKey ?: return
request.requestBody.sessionId ?: return
val validReq = ValidMegolmRequestBody(
requestId = request.requestId,
requestingDeviceId = request.deviceId,
requestingUserId = request.userId,
roomId = request.requestBody.roomId,
senderKey = request.requestBody.senderKey,
sessionId = request.requestBody.sessionId,
action = MegolmRequestAction.Request
)
val requestingDevice =
cryptoStore.getUserDevice(request.userId, request.deviceId)
?: return Unit.also {
Timber.tag(loggerTag.value).d("Ignoring key request: ${validReq.shortDbgString()}")
}
shareMegolmKey(validReq, requestingDevice, null)
}
private suspend fun shareMegolmKey(validRequest: ValidMegolmRequestBody,
requestingDevice: CryptoDeviceInfo,
chainIndex: Long?): Boolean {
@ -341,14 +433,12 @@ internal class IncomingKeyRequestManager @Inject constructor(
fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
// TODO
gossipingRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
// TODO
gossipingRequestListeners.remove(listener)
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto
internal interface IncomingShareRequestCommon {
/**
* The user id
*/
val userId: String?
/**
* The device id
*/
val deviceId: String?
/**
* The request id
*/
val requestId: String?
val localCreationTimestamp: Long?
}

View File

@ -585,6 +585,13 @@ internal class MXOlmDevice @Inject constructor(
// Inbound group session
sealed class AddSessionResult {
data class Imported(val ratchetIndex: Int) : AddSessionResult()
abstract class Failure : AddSessionResult()
object NotImported : Failure()
data class NotImportedHigherIndex(val newIndex: Int) : AddSessionResult()
}
/**
* Add an inbound group session to the session store.
*
@ -603,7 +610,7 @@ internal class MXOlmDevice @Inject constructor(
senderKey: String,
forwardingCurve25519KeyChain: List<String>,
keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean {
exportFormat: Boolean): AddSessionResult {
val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
val existingSession = existingSessionHolder?.wrapper
@ -611,7 +618,7 @@ internal class MXOlmDevice @Inject constructor(
if (existingSession != null) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
try {
val existingFirstKnown = existingSession.firstKnownIndex ?: return false.also {
val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also {
// This is quite unexpected, could throw if native was released?
Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
candidateSession.olmInboundGroupSession?.releaseSession()
@ -622,12 +629,12 @@ internal class MXOlmDevice @Inject constructor(
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
candidateSession.olmInboundGroupSession?.releaseSession()
return false
return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
}
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
candidateSession.olmInboundGroupSession?.releaseSession()
return false
return AddSessionResult.NotImported
}
}
@ -637,19 +644,19 @@ internal class MXOlmDevice @Inject constructor(
val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
if (null == candidateOlmInboundSession) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>")
return false
return AddSessionResult.NotImported
}
try {
if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
candidateOlmInboundSession.releaseSession()
return false
return AddSessionResult.NotImported
}
} catch (e: Throwable) {
candidateOlmInboundSession.releaseSession()
Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
return false
return AddSessionResult.NotImported
}
candidateSession.senderKey = senderKey
@ -663,7 +670,7 @@ internal class MXOlmDevice @Inject constructor(
inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
}
return true
return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0)
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -27,7 +27,7 @@ data class RequestReply(
)
sealed class RequestResult {
object Success : RequestResult()
data class Success(val chainIndex: Int) : RequestResult()
data class Failure(val code: WithHeldCode) : RequestResult()
}
@ -35,6 +35,7 @@ data class OutgoingKeyRequest(
var requestBody: RoomKeyRequestBody?,
// recipients for the request map of users to list of deviceId
val recipients: Map<String, List<String>>,
val fromIndex: Int,
// Unique id for this request. Used for both
// an id within the request for later pairing with a cancellation, and for
// the transaction id when sending the to_device messages to our local

View File

@ -23,19 +23,23 @@ 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.OutgoingRoomKeyRequestState
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.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.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
import timber.log.Timber
@ -44,7 +48,7 @@ import java.util.concurrent.Executors
import javax.inject.Inject
import kotlin.system.measureTimeMillis
private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.CRYPTO)
private val loggerTag = LoggerTag("OutgoingKeyRequestManager", LoggerTag.CRYPTO)
/**
* This class is responsible for sending key requests to other devices when a message failed to decrypt.
@ -54,10 +58,13 @@ private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.C
* If a request failed it will be retried at the end of the next sync
*/
@SessionScope
internal class OutgoingGossipingRequestManager @Inject constructor(
internal class OutgoingKeyRequestManager @Inject constructor(
@SessionId private val sessionId: String,
@UserId private val myUserId: String,
private val cryptoStore: IMXCryptoStore,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoConfig: MXCryptoConfig,
private val inboundGroupSessionStore: InboundGroupSessionStore,
private val sendToDeviceTask: DefaultSendToDeviceTask,
private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter) {
@ -70,10 +77,71 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
// We keep a stack as we consider that the key requested last is more likely to be on screen?
private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack<String>()
fun postRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, force: Boolean = false) {
fun requestKeyForEvent(event: Event, force: Boolean) {
val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return
val index = ratchetIndexForMessage(event) ?: 0
postRoomKeyRequest(body, targets, index, force)
}
private fun getRoomKeyRequestTargetForEvent(event: Event): Pair<Map<String, List<String>>, RoomKeyRequestBody>? {
val sender = event.senderId ?: return null
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return null.also {
Timber.tag(loggerTag.value).e("getRoomKeyRequestTargetForEvent Failed to re-request key, null content")
}
if (encryptedEventContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
val senderDevice = encryptedEventContent.deviceId
val recipients = if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
mapOf(
myUserId to listOf("*")
)
} else {
if (event.senderId == myUserId) {
mapOf(
myUserId to listOf("*")
)
} else {
// for the case where you share the key with a device that has a broken olm session
// The other user might Re-shares a megolm session key with devices if the key has already been
// sent to them.
mapOf(
myUserId to listOf("*"),
// TODO we might not have deviceId in the future due to https://github.com/matrix-org/matrix-spec-proposals/pull/3700
// so in this case query to all
sender to listOf(senderDevice ?: "*")
)
}
}
val requestBody = RoomKeyRequestBody(
roomId = event.roomId,
algorithm = encryptedEventContent.algorithm,
senderKey = encryptedEventContent.senderKey,
sessionId = encryptedEventContent.sessionId
)
return recipients to requestBody
}
private fun ratchetIndexForMessage(event: Event): Int? {
val encryptedContent = event.content.toModel<EncryptedEventContent>() ?: return null
if (encryptedContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
return encryptedContent.ciphertext?.fromBase64()?.inputStream()?.reader()?.let {
tryOrNull {
val megolmVersion = it.read()
if (megolmVersion != 3) return@tryOrNull null
/** Int tag */
/** Int tag */
if (it.read() != 8) return@tryOrNull null
it.read()
}
}
}
fun postRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int, force: Boolean = false) {
outgoingRequestScope.launch {
sequencer.post {
internalQueueRequest(requestBody, recipients, force)
internalQueueRequest(requestBody, recipients, fromIndex, force)
}
}
}
@ -81,10 +149,10 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
/**
* Typically called when we the session as been imported or received meanwhile
*/
fun postCancelRequestForSessionIfNeeded(sessionId: String, roomId: String, senderKey: String) {
fun postCancelRequestForSessionIfNeeded(sessionId: String, roomId: String, senderKey: String, fromIndex: Int) {
outgoingRequestScope.launch {
sequencer.post {
internalQueueCancelRequest(sessionId, roomId, senderKey)
internalQueueCancelRequest(sessionId, roomId, senderKey, fromIndex)
}
}
}
@ -94,17 +162,24 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
roomId: String,
senderKey: String,
fromDevice: String?,
fromIndex: Int,
event: Event) {
Timber.tag(loggerTag.value).v("Key forwarded for $sessionId from ${event.senderId}|$fromDevice")
Timber.tag(loggerTag.value).d("Key forwarded for $sessionId from ${event.senderId}|$fromDevice at index $fromIndex")
outgoingRequestScope.launch {
sequencer.post {
cryptoStore.updateOutgoingRoomKeyReply(
roomId,
sessionId,
algorithm,
senderKey,
fromDevice,
event
roomId = roomId,
sessionId = sessionId,
algorithm = algorithm,
senderKey = senderKey,
fromDevice = fromDevice,
// strip out encrypted stuff as it's just a trail?
event = event.copy(
type = event.getClearType(),
content = mapOf(
"chain_index" to fromIndex
)
)
)
}
}
@ -120,12 +195,12 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
sequencer.post {
Timber.tag(loggerTag.value).d("Withheld received for $sessionId from ${event.senderId}|$fromDevice")
cryptoStore.updateOutgoingRoomKeyReply(
roomId,
sessionId,
algorithm,
senderKey,
fromDevice,
event
roomId = roomId,
sessionId = sessionId,
algorithm = algorithm,
senderKey = senderKey,
fromDevice = fromDevice,
event = event
)
}
}
@ -142,7 +217,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
}
}
private fun internalQueueCancelRequest(sessionId: String, roomId: String, senderKey: String) {
private fun internalQueueCancelRequest(sessionId: String, roomId: String, senderKey: String, localKnownChainIndex: Int) {
// do we have known requests for that session??
Timber.tag(loggerTag.value).v("Cancel Key Request if needed for $sessionId")
val knownRequest = cryptoStore.getOutgoingRoomKeyRequest(
@ -161,18 +236,29 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
knownRequest.forEach { request ->
when (request.state) {
OutgoingRoomKeyRequestState.UNSENT -> {
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
if (request.fromIndex >= localKnownChainIndex) {
// we have a good index we can cancel
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
}
}
OutgoingRoomKeyRequestState.SENT -> {
// It was already sent, so cancel
cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
// It was already sent, and index satisfied we can cancel
if (request.fromIndex >= localKnownChainIndex) {
cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
}
}
OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> {
// It is already marked to be cancelled
}
OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
// we just want to cancel now
cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
if (request.fromIndex >= localKnownChainIndex) {
// we just want to cancel now
cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
}
}
OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> {
// was already canceled
// if we need a better index, should we resend?
}
}
}
@ -187,19 +273,24 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
}
}
private fun internalQueueRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, force: Boolean) {
Timber.tag(loggerTag.value).d("Queueing key request for ${requestBody.sessionId}")
val existing = cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)
when (existing.state) {
private fun internalQueueRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int, force: Boolean) {
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}")
when (existing?.state) {
null -> {
// create a new one
cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex)
}
OutgoingRoomKeyRequestState.UNSENT -> {
// nothing it's new or not yet handled
}
OutgoingRoomKeyRequestState.SENT -> {
// it was already requested
Timber.tag(loggerTag.value).w("The session ${requestBody.sessionId} is already requested")
Timber.tag(loggerTag.value).d("The session ${requestBody.sessionId} is already requested")
if (force) {
// update to UNSENT
Timber.tag(loggerTag.value).w(".. force to request ${requestBody.sessionId}")
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)
@ -214,6 +305,17 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
// It's already going to resend
}
OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> {
if (force) {
cryptoStore.deleteOutgoingRoomKeyRequest(existing.requestId)
cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex)
}
}
}
if (existing != null && existing.fromIndex >= fromIndex) {
// update the required index
cryptoStore.updateOutgoingRoomKeyRequiredIndex(existing.requestId, fromIndex)
}
}
@ -227,6 +329,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
OutgoingRoomKeyRequestState.UNSENT -> handleUnsentRequest(it)
OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> handleRequestToCancel(it)
OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> handleRequestToCancelWillResend(it)
OutgoingRoomKeyRequestState.SENT_THEN_CANCELED,
OutgoingRoomKeyRequestState.SENT -> {
// these are filtered out
}
@ -260,10 +363,16 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
val sessionId = request.sessionId ?: return
val roomId = request.roomId ?: return
if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) {
// we found the key in backup, so we can just mark as cancelled, no need to send request
Timber.tag(loggerTag.value).v("Megolm session $sessionId successfully restored from backup, do not send request")
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
return
// let's see what's the index
val knownIndex = tryOrNull {
inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex
}
if (knownIndex != null && knownIndex <= request.fromIndex) {
// we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request
Timber.tag(loggerTag.value).v("Megolm session $sessionId successfully restored from backup, do not send request")
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
return
}
}
// we need to send the request
@ -322,9 +431,9 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
withContext(coroutineDispatchers.io) {
sendToDeviceTask.executeRetry(params, 3)
}
// The request cancellation was sent, we can forget about it
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
// TODO update the audit trail
// The request cancellation was sent, we don't delete yet because we want
// to keep trace of the sent replies
cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.SENT_THEN_CANCELED)
true
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).v("Failed to cancel request ${request.requestId} for session $sessionId targets:${request.recipients}")
@ -334,10 +443,9 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
private suspend fun handleRequestToCancelWillResend(request: OutgoingKeyRequest) {
if (handleRequestToCancel(request)) {
// we have to create a new unsent one
val body = request.requestBody ?: return
// this will create a new unsent request that will be process in the following call
cryptoStore.getOrAddOutgoingRoomKeyRequest(body, request.recipients)
// this will create a new unsent request with no replies that will be process in the following call
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
request.requestBody?.let { cryptoStore.getOrAddOutgoingRoomKeyRequest(it, request.recipients, request.fromIndex) }
}
}
}

View File

@ -14,11 +14,12 @@
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.model
package org.matrix.android.sdk.internal.crypto
enum class OutgoingRoomKeyRequestState {
UNSENT,
SENT,
SENT_THEN_CANCELED,
CANCELLATION_PENDING,
CANCELLATION_PENDING_AND_WILL_RESEND;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
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.toModel
@ -75,6 +76,21 @@ internal class SecretShareManager @Inject constructor(
*/
private val outgoingSecretRequests = mutableListOf<SecretShareRequest>()
// the listeners
private val gossipingRequestListeners: MutableSet<GossipingRequestListener> = HashSet()
fun addListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
gossipingRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
gossipingRequestListeners.remove(listener)
}
}
/**
* Called when a session has been verified.
* This information can be used by the manager to decide whether or not to fullfill gossiping requests.
@ -140,7 +156,11 @@ internal class SecretShareManager @Inject constructor(
else -> null
}
if (secretValue == null) {
Timber.e("The secret is unknown $secretName")
Timber.i("The secret is unknown $secretName, passing to app layer")
val toList = synchronized(gossipingRequestListeners) { gossipingRequestListeners.toList() }
toList.onEach { listener ->
listener.onSecretShareRequest(request)
}
return
}

View File

@ -17,12 +17,13 @@
package org.matrix.android.sdk.internal.crypto.actions
import androidx.annotation.WorkerThread
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -33,7 +34,7 @@ private val loggerTag = LoggerTag("MegolmSessionDataImporter", LoggerTag.CRYPTO)
internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice,
private val roomDecryptorProvider: RoomDecryptorProvider,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val cryptoStore: IMXCryptoStore) {
/**
@ -71,10 +72,15 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
// cancel any outstanding room key requests for this session
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
outgoingGossipingRequestManager.postCancelRequestForSessionIfNeeded(
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
megolmSessionData.sessionId ?: "",
megolmSessionData.roomId ?: "",
megolmSessionData.senderKey ?: ""
megolmSessionData.senderKey ?: "",
tryOrNull {
olmInboundGroupSessionWrappers
.firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId }
?.firstKnownIndex?.toInt()
} ?: 0
)
// Have another go at decrypting events sent with this session

View File

@ -17,8 +17,6 @@
package org.matrix.android.sdk.internal.crypto.algorithms
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
@ -44,23 +42,4 @@ internal interface IMXDecrypting {
* @param event the key event.
*/
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
/**
* Determine if we have the keys necessary to respond to a room key request
*
* @param request keyRequest
* @return true if we have the keys and could (theoretically) share
*/
fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean = false
/**
* Send the response to a room key request.
*
* @param request keyRequest
*/
fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {}
fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) {}
fun requestKeysForEvent(event: Event, withHeld: Boolean)
}

View File

@ -17,48 +17,31 @@
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
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.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.content.RoomKeyContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.session.StreamEventsManager
import timber.log.Timber
private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
private val liveEventManager: Lazy<StreamEventsManager>
internal class MXMegolmDecryption(
private val olmDevice: MXOlmDevice,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val cryptoStore: IMXCryptoStore,
private val liveEventManager: Lazy<StreamEventsManager>
) : IMXDecrypting {
var newSessionListener: NewSessionListener? = null
@ -122,23 +105,9 @@ internal class MXMegolmDecryption(private val userId: String,
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
// addEventToPendingList(event, timeline)
// The session might has been partially withheld (and only pass ratcheted)
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
if (requestKeysOnFail) {
requestKeysForEvent(event, true)
}
// Encapsulate as withHeld exception
throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason)
}
if (requestKeysOnFail) {
requestKeysForEvent(event, false)
requestKeysForEvent(event)
}
throw MXCryptoError.Base(
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
"UNKNOWN_MESSAGE_INDEX",
@ -154,25 +123,9 @@ internal class MXMegolmDecryption(private val userId: String,
detailedReason)
}
if (throwable is MXCryptoError.Base) {
if (
/** if the session is unknown*/
throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
) {
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
if (requestKeysOnFail) {
requestKeysForEvent(event, true)
}
// Encapsulate as withHeld exception
throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason)
} else {
// This is un-used in Matrix Android SDK2, not sure if needed
// addEventToPendingList(event, timeline)
if (requestKeysOnFail) {
requestKeysForEvent(event, false)
}
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
}
}
@ -188,60 +141,10 @@ internal class MXMegolmDecryption(private val userId: String,
*
* @param event the event
*/
override fun requestKeysForEvent(event: Event, withHeld: Boolean) {
val sender = event.senderId ?: return
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to re-request key, null content")
}
val senderDevice = encryptedEventContent.deviceId
val recipients = if (event.senderId == userId || withHeld) {
mapOf(
userId to listOf("*")
)
} else {
// for the case where you share the key with a device that has a broken olm session
// The other user might Re-shares a megolm session key with devices if the key has already been
// sent to them.
mapOf(
userId to listOf("*"),
// TODO we might not have deviceId in the future due to https://github.com/matrix-org/matrix-spec-proposals/pull/3700
// so in this case query to all
sender to listOf(senderDevice ?: "*")
)
}
val requestBody = RoomKeyRequestBody(
roomId = event.roomId,
algorithm = encryptedEventContent.algorithm,
senderKey = encryptedEventContent.senderKey,
sessionId = encryptedEventContent.sessionId
)
outgoingGossipingRequestManager.postRoomKeyRequest(requestBody, recipients, force = withHeld)
private fun requestKeysForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false)
}
// /**
// * Add an event to the list of those we couldn't decrypt the first time we
// * saw them.
// *
// * @param event the event to try to decrypt later
// * @param timelineId the timeline identifier
// */
// private fun addEventToPendingList(event: Event, timelineId: String) {
// val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return
// val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
//
// val timeline = pendingEvents.getOrPut(pendingEventsKey) { HashMap() }
// val events = timeline.getOrPut(timelineId) { ArrayList() }
//
// if (event !in events) {
// Timber.v("## CRYPTO | addEventToPendingList() : add Event ${event.eventId} in room id ${event.roomId}")
// events.add(event)
// }
// }
/**
* Handle a key event.
*
@ -289,16 +192,6 @@ internal class MXMegolmDecryption(private val userId: String,
}
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
val fromDevice = event.getSenderKey()?.let { cryptoStore.deviceWithIdentityKey(it) }?.deviceId
outgoingGossipingRequestManager.onRoomKeyForwarded(
sessionId = roomKeyContent.sessionId,
algorithm = roomKeyContent.algorithm ?: "",
roomId = roomKeyContent.roomId,
senderKey = forwardedRoomKeyContent.senderKey ?: "",
fromDevice = fromDevice,
event = event)
} else {
Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
if (null == senderKey) {
@ -319,13 +212,38 @@ internal class MXMegolmDecryption(private val userId: String,
keysClaimed,
exportFormat)
if (added) {
when (added) {
is MXOlmDevice.AddSessionResult.Imported -> added.ratchetIndex
is MXOlmDevice.AddSessionResult.NotImportedHigherIndex -> added.newIndex
else -> null
}?.let { index ->
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
val fromDevice = (event.content?.get("sender_key") as? String)?.let { senderDeviceIdentityKey ->
cryptoStore.getUserDeviceList(event.senderId ?: "")
?.firstOrNull {
it.identityKey() == senderDeviceIdentityKey
}
}?.deviceId
outgoingKeyRequestManager.onRoomKeyForwarded(
sessionId = roomKeyContent.sessionId,
algorithm = roomKeyContent.algorithm ?: "",
roomId = roomKeyContent.roomId,
senderKey = senderKey,
fromIndex = index,
fromDevice = fromDevice,
event = event)
// The index is used to decide if we cancel sent request or if we wait for a better key
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey, index)
}
}
if (added is MXOlmDevice.AddSessionResult.Imported) {
Timber.tag(loggerTag.value)
.d("onRoomKeyEvent(${event.getClearType()}) : Added megolm session ${roomKeyContent.sessionId} in ${roomKeyContent.roomId}")
defaultKeysBackupService.maybeBackupKeys()
outgoingGossipingRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey)
onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId)
}
}
@ -341,72 +259,4 @@ internal class MXMegolmDecryption(private val userId: String,
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
newSessionListener?.onNewSession(roomId, senderKey, sessionId)
}
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
val roomId = request.requestBody?.roomId ?: return false
val senderKey = request.requestBody.senderKey ?: return false
val sessionId = request.requestBody.sessionId ?: return false
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
}
override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
// sanity checks
if (request.requestBody == null) {
return
}
val userId = request.userId ?: return
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val body = request.requestBody
val sessionHolder = try {
olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session for request $body")
return@launch
}
val export = sessionHolder.mutex.withLock {
sessionHolder.wrapper.exportKeys()
} ?: return@launch Unit.also {
Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session ${body.sessionId}")
}
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.mapCatching {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "")
if (deviceInfo == null) {
throw RuntimeException()
} else {
val devicesByUser = mapOf(userId to listOf(deviceInfo))
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.")
return@mapCatching
}
Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
val payloadJson = mapOf(
"type" to EventType.FORWARDED_ROOM_KEY,
"content" to export
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
}
}
}
}
}
}

View File

@ -17,45 +17,24 @@
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.StreamEventsManager
import javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(
@UserId private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
private val eventsManager: Lazy<StreamEventsManager>
) {
fun create(): MXMegolmDecryption {
return MXMegolmDecryption(
userId,
olmDevice,
deviceListManager,
outgoingGossipingRequestManager,
messageEncrypter,
ensureOlmSessionsForDevicesAction,
outgoingKeyRequestManager,
cryptoStore,
sendToDeviceTask,
coroutineDispatchers,
cryptoCoroutineScope,
eventsManager)
}
}

View File

@ -241,8 +241,4 @@ internal class MXOlmDecryption(
return res["payload"]
}
override fun requestKeysForEvent(event: Event, withHeld: Boolean) {
// nop
}
}

View File

@ -65,7 +65,8 @@ data class IncomingKeyRequestInfo(
override val senderKey: String,
override val alg: String,
override val userId: String,
override val deviceId: String
override val deviceId: String,
val requestId: String
) : AuditInfo
data class AuditTrail(

View File

@ -25,22 +25,19 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
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.IncomingShareRequestCommon
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest
import org.matrix.android.sdk.api.session.crypto.model.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
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.TrailType
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmOutboundGroupSession
@ -127,18 +124,6 @@ internal interface IMXCryptoStore {
*/
fun getDeviceTrackingStatuses(): Map<String, Int>
/**
* @return the pending IncomingRoomKeyRequest requests
*/
fun getPendingIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
fun getPendingIncomingGossipingRequests(): List<IncomingShareRequestCommon>
fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?)
fun storeIncomingGossipingRequests(requests: List<IncomingShareRequestCommon>)
// fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest>
/**
* Indicate if the store contains data for the passed account.
*
@ -389,25 +374,21 @@ internal interface IMXCryptoStore {
* @param request the request
* @return either the same instance as passed in, or the existing one.
*/
fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>): OutgoingKeyRequest
fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int): OutgoingKeyRequest
fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState)
fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int)
fun updateOutgoingRoomKeyReply(
roomId: String,
sessionId: String,
algorithm: String,
senderKey: String, fromDevice: String?, event: Event)
senderKey: String,
fromDevice: String?,
event: Event)
fun deleteOutgoingRoomKeyRequest(requestId: String)
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
@Deprecated("TODO")
fun saveGossipingEvent(event: Event) = saveGossipingEvents(listOf(event))
@Deprecated("TODO")
fun saveGossipingEvents(events: List<Event>)
fun saveIncomingKeyRequestAuditTrail(
requestId: String,
roomId: String,
sessionId: String,
senderKey: String,
@ -436,32 +417,6 @@ internal interface IMXCryptoStore {
chainIndex: Long?
)
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
updateGossipingRequestState(
requestUserId = request.userId,
requestDeviceId = request.deviceId,
requestId = request.requestId,
state = state
)
}
fun updateGossipingRequestState(requestUserId: String?,
requestDeviceId: String?,
requestId: String?,
state: GossipingRequestState)
/**
* Search an IncomingRoomKeyRequest
*
* @param userId the user id
* @param deviceId the device id
* @param requestId the request id
* @return an IncomingRoomKeyRequest if it exists, else null
*/
fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest?
fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingRoomKeyRequestState)
fun addNewSessionListener(listener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)
@ -522,11 +477,8 @@ internal interface IMXCryptoStore {
fun getOutgoingRoomKeyRequests(): List<OutgoingKeyRequest>
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingKeyRequest>>
fun getOutgoingSecretKeyRequests(): List<OutgoingSecretRequest>
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
fun getGossipingEventsTrail(): LiveData<PagedList<AuditTrail>>
fun <T> getGossipingEventsTrail(type: TrailType, mapper: ((AuditTrail) -> T)): LiveData<PagedList<T>>
fun getGossipingEvents(): List<AuditTrail>
fun setDeviceKeysUploaded(uploaded: Boolean)

View File

@ -22,8 +22,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEnt
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -45,7 +45,6 @@ class MigrateCryptoTo016(realm: DynamicRealm) : RealmMigrator(realm, 15) {
.addField(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, Long::class.java)
.setNullable(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, true)
realm.schema.create("AuditTrailEntity")
.addField(AuditTrailEntityFields.AGE_LOCAL_TS, Long::class.java)
.setNullable(AuditTrailEntityFields.AGE_LOCAL_TS, true)
@ -56,6 +55,5 @@ 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)
}
}

View File

@ -18,13 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject
import io.realm.annotations.Index
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
import org.matrix.android.sdk.internal.crypto.GossipRequestType
import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
// not used anymore, just here for db migration
internal open class IncomingGossipingRequestEntity(@Index var requestId: String? = "",
@ -35,56 +28,7 @@ internal open class IncomingGossipingRequestEntity(@Index var requestId: String?
var localCreationTimestamp: Long? = null
) : RealmObject() {
fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) {
requestedInfoStr
} else null
fun getRequestedKeyInfo(): RoomKeyRequestBody? = if (type == GossipRequestType.KEY) {
RoomKeyRequestBody.fromJson(requestedInfoStr)
} else null
var type: GossipRequestType
get() {
return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
}
set(value) {
typeStr = value.name
}
private var requestStateStr: String = GossipingRequestState.NONE.name
var requestState: GossipingRequestState
get() {
return tryOrNull { GossipingRequestState.valueOf(requestStateStr) }
?: GossipingRequestState.NONE
}
set(value) {
requestStateStr = value.name
}
private var requestStateStr: String = ""
companion object
fun toIncomingGossipingRequest(): IncomingShareRequestCommon {
return when (type) {
GossipRequestType.KEY -> {
IncomingRoomKeyRequest(
requestBody = getRequestedKeyInfo(),
deviceId = otherDeviceId,
userId = otherUserId,
requestId = requestId,
state = requestState,
localCreationTimestamp = localCreationTimestamp
)
}
GossipRequestType.SECRET -> {
IncomingSecretShareRequest(
secretName = getRequestedSecretName(),
deviceId = otherDeviceId,
userId = otherUserId,
requestId = requestId,
localCreationTimestamp = localCreationTimestamp
)
}
}
}
}

View File

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject
import io.realm.annotations.Index
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
// not used anymore, just here for db migration
internal open class OutgoingGossipingRequestEntity(
@ -28,5 +27,5 @@ internal open class OutgoingGossipingRequestEntity(
@Index var typeStr: String? = null
) : RealmObject() {
private var requestStateStr: String = OutgoingGossipingRequestState.UNSENT.name
private var requestStateStr: String = ""
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,10 +32,10 @@ import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.internal.crypto.RequestReply
import org.matrix.android.sdk.internal.crypto.RequestResult
import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
internal open class OutgoingKeyRequestEntity(
@Index var requestId: String? = null,
var requestedIndex: Int? = null,
var recipientsData: String? = null,
var requestedInfoStr: String? = null,
var creationTimeStamp: Long? = null,
@ -84,7 +84,6 @@ internal open class OutgoingKeyRequestEntity(
}
fun addReply(userId: String, fromDevice: String?, event: Event) {
Timber.w("##VALR: add reply $userId / $fromDevice / $event")
val newReply = KeyRequestReplyEntity(
senderId = userId,
fromDevice = fromDevice,
@ -98,6 +97,7 @@ internal open class OutgoingKeyRequestEntity(
requestBody = getRequestedKeyInfo(),
recipients = getRecipients().orEmpty(),
requestId = requestId ?: "",
fromIndex = requestedIndex ?: 0,
state = requestState,
results = replies.mapNotNull { entity ->
val userId = entity.senderId ?: return@mapNotNull null
@ -107,9 +107,9 @@ internal open class OutgoingKeyRequestEntity(
eventToResult(event)
} ?: return@mapNotNull null
RequestReply(
userId,
entity.fromDevice,
result
userId = userId,
fromDevice = entity.fromDevice,
result = result
)
}
)
@ -123,7 +123,7 @@ internal open class OutgoingKeyRequestEntity(
}
}
EventType.FORWARDED_ROOM_KEY -> {
RequestResult.Success
RequestResult.Success((event.content?.get("chain_index") as? Number)?.toInt() ?: 0)
}
else -> null
}

View File

@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerific
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -35,7 +35,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
override val deviceId: String?,
private val cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
deviceFingerprint: String,
transactionId: String,
@ -47,7 +47,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
deviceId,
cryptoStore,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
deviceFingerprint,
transactionId,

View File

@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -32,7 +32,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
deviceId: String?,
cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
deviceFingerprint: String,
transactionId: String,
@ -44,7 +44,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
deviceId,
cryptoStore,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
deviceFingerprint,
transactionId,

View File

@ -60,7 +60,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
import org.matrix.android.sdk.api.session.room.model.message.ValidVerificationDone
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept
@ -94,7 +94,7 @@ internal class DefaultVerificationService @Inject constructor(
@UserId private val userId: String,
@DeviceId private val deviceId: String?,
private val cryptoStore: IMXCryptoStore,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val secretShareManager: SecretShareManager,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
private val deviceListManager: DeviceListManager,
@ -547,7 +547,7 @@ internal class DefaultVerificationService @Inject constructor(
deviceId,
cryptoStore,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionId,
@ -909,7 +909,7 @@ internal class DefaultVerificationService @Inject constructor(
otherUserId = senderId,
otherDeviceId = readyReq.fromDevice,
crossSigningService = crossSigningService,
outgoingGossipingRequestManager = outgoingGossipingRequestManager,
outgoingKeyRequestManager = outgoingKeyRequestManager,
secretShareManager = secretShareManager,
cryptoStore = cryptoStore,
qrCodeData = qrCodeData,
@ -1108,7 +1108,7 @@ internal class DefaultVerificationService @Inject constructor(
deviceId,
cryptoStore,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
txID,
@ -1300,7 +1300,7 @@ internal class DefaultVerificationService @Inject constructor(
deviceId,
cryptoStore,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
transactionId,
@ -1438,7 +1438,7 @@ internal class DefaultVerificationService @Inject constructor(
otherUserId = otherUserId,
otherDeviceId = otherDeviceId,
crossSigningService = crossSigningService,
outgoingGossipingRequestManager = outgoingGossipingRequestManager,
outgoingKeyRequestManager = outgoingKeyRequestManager,
secretShareManager = secretShareManager,
cryptoStore = cryptoStore,
qrCodeData = qrCodeData,

View File

@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import timber.log.Timber
@ -31,7 +31,7 @@ import timber.log.Timber
internal abstract class DefaultVerificationTransaction(
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val crossSigningService: CrossSigningService,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val secretShareManager: SecretShareManager,
private val userId: String,
override val transactionId: String,

View File

@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -42,7 +42,7 @@ internal abstract class SASDefaultVerificationTransaction(
open val deviceId: String?,
private val cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
private val deviceFingerprint: String,
transactionId: String,
@ -52,7 +52,7 @@ internal abstract class SASDefaultVerificationTransaction(
) : DefaultVerificationTransaction(
setDeviceVerificationAction,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
userId,
transactionId,

View File

@ -21,8 +21,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe
@ -37,7 +36,7 @@ internal class DefaultQrCodeVerificationTransaction(
override val otherUserId: String,
override var otherDeviceId: String?,
private val crossSigningService: CrossSigningService,
outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
private val cryptoStore: IMXCryptoStore,
// Not null only if other user is able to scan QR code
@ -48,7 +47,7 @@ internal class DefaultQrCodeVerificationTransaction(
) : DefaultVerificationTransaction(
setDeviceVerificationAction,
crossSigningService,
outgoingGossipingRequestManager,
outgoingKeyRequestManager,
secretShareManager,
userId,
transactionId,

View File

@ -22,6 +22,8 @@ import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.features.popup.DefaultVectorAlert
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
@ -36,6 +38,12 @@ import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTra
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -72,10 +80,9 @@ class KeyRequestHandler @Inject constructor(
session = null
}
override fun onSecretShareRequest(request: IncomingSecretShareRequest): Boolean {
override fun onSecretShareRequest(request: SecretShareRequest): Boolean {
// By default Element will not prompt if the SDK has decided that the request should not be fulfilled
Timber.v("## onSecretShareRequest() : Ignoring $request")
request.ignore?.run()
return true
}
@ -195,15 +202,14 @@ class KeyRequestHandler @Inject constructor(
}
private fun denyAllRequests(mappingKey: String) {
alertsToRequests[mappingKey]?.forEach {
it.ignore?.run()
}
alertsToRequests.remove(mappingKey)
}
private fun shareAllSessions(mappingKey: String) {
alertsToRequests[mappingKey]?.forEach {
it.share?.run()
session?.coroutineScope?.launch {
session?.cryptoService()?.manuallyAcceptRoomKeyRequest(it)
}
}
alertsToRequests.remove(mappingKey)
}
@ -213,7 +219,7 @@ class KeyRequestHandler @Inject constructor(
*
* @param request the cancellation request.
*/
override fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
override fun onRequestCancelled(request: IncomingRoomKeyRequest) {
// see if we can find the request in the queue
val userId = request.userId
val deviceId = request.deviceId

View File

@ -62,10 +62,6 @@ class IncomingKeyRequestPagedController @Inject constructor(
textStyle = "bold"
}
+"${roomKeyRequest.deviceId}"
span("\nstate: ") {
textStyle = "bold"
}
+roomKeyRequest.state.name
}.toEpoxyCharSequence()
)
}