mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Fix unwedging
This commit is contained in:
parent
a6368c473e
commit
9b320ed3c7
18 changed files with 304 additions and 195 deletions
|
@ -18,20 +18,29 @@ package im.vector.matrix.android.internal.crypto
|
|||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.olm.OlmSession
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
|
@ -79,9 +88,8 @@ class UnwedgingTest : InstrumentedTest {
|
|||
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
//bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
//aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
@ -100,10 +108,12 @@ class UnwedgingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val decryptedEventReceivedByBob = snapshot.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
val decryptedEventReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
Timber.d("Bob can now decrypt ${decryptedEventReceivedByBob.size} messages")
|
||||
if (decryptedEventReceivedByBob.size == 3) {
|
||||
bobFinalLatch.countDown()
|
||||
if (decryptedEventReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
bobFinalLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +132,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 1
|
||||
val firstMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
|
@ -130,14 +141,17 @@ class UnwedgingTest : InstrumentedTest {
|
|||
sessionIdsForBob!!.size shouldBe 1
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
||||
|
||||
// Sam join the room, so it will force a new session creation
|
||||
val samSession = mCryptoTestHelper.createSamAccountAndInviteToTheRoom(roomFromAlicePOV)
|
||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||
|
||||
aliceSession.cryptoService().discardOutbundSession(roomFromAlicePOV.roomId)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
latch = CountDownLatch(1)
|
||||
bobEventsListener = createEventListener(latch, 2)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session")
|
||||
// - Alice sends a 2nd message with a 2nd megolm session
|
||||
roomFromAlicePOV.sendTextMessage("Second message")
|
||||
|
||||
|
@ -146,36 +160,70 @@ class UnwedgingTest : InstrumentedTest {
|
|||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 2
|
||||
// Session should have changed
|
||||
val secondMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Assert.assertNotEquals(firstMessageSession, secondMessageSession)
|
||||
|
||||
// Let us wedge the session now. Set crypto state like after the first message
|
||||
aliceCryptoStore.storeSession(olmSession, bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
||||
|
||||
latch = CountDownLatch(1)
|
||||
bobEventsListener = createEventListener(latch, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendTextMessage("Third message")
|
||||
// Force new session, and key share
|
||||
aliceSession.cryptoService().discardOutbundSession(roomFromAlicePOV.roomId)
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.waitWithLatch {
|
||||
bobEventsListener = createEventListener(it, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendTextMessage("Third message")
|
||||
// Bob should not be able to decrypt, because the session key could not be sent
|
||||
}
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 3
|
||||
|
||||
messagesReceivedByBob[0].root.getClearType() shouldBeEqualTo EventType.ENCRYPTED
|
||||
messagesReceivedByBob[1].root.getClearType() shouldBeEqualTo EventType.MESSAGE
|
||||
messagesReceivedByBob[2].root.getClearType() shouldBeEqualTo EventType.MESSAGE
|
||||
val thirdMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Timber.i("## CRYPTO | testUnwedging: third message session ID $thirdMessageSession")
|
||||
Assert.assertNotEquals(secondMessageSession, thirdMessageSession)
|
||||
|
||||
// Wait for all the message to be decrypted by bob
|
||||
Assert.assertEquals(EventType.ENCRYPTED, messagesReceivedByBob[0].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
|
||||
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
|
||||
mTestHelper.await(bobFinalLatch)
|
||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryThis {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
result != null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
mTestHelper.signOutAndClose(samSession)
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
|
|
|
@ -111,6 +111,8 @@ interface CryptoService {
|
|||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
|
||||
fun discardOutbundSession(roomId: String)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
|
|
|
@ -45,10 +45,11 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
|
||||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmDecryption
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||
|
@ -60,6 +61,7 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.OlmEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
|
@ -73,6 +75,7 @@ import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
|||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||
|
@ -168,7 +171,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val monarchy: Monarchy,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val messageEncrypter: MessageEncrypter
|
||||
) : CryptoService {
|
||||
|
||||
init {
|
||||
|
@ -501,14 +507,14 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
||||
|
||||
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
|
||||
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
||||
Timber.e("## CRYPTO | setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
||||
return false
|
||||
}
|
||||
|
||||
val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm)
|
||||
|
||||
if (!encryptingClass) {
|
||||
Timber.e("## setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
|
||||
Timber.e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -597,7 +603,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
if (!isStarted()) {
|
||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||
Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init")
|
||||
internalStart(false)
|
||||
}
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
|
@ -613,22 +619,28 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
val safeAlgorithm = alg
|
||||
if (safeAlgorithm != null) {
|
||||
val t0 = System.currentTimeMillis()
|
||||
Timber.v("## encryptEventContent() starts")
|
||||
Timber.v("## CRYPTO | encryptEventContent() starts")
|
||||
runCatching {
|
||||
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
}.foldToCallback(callback)
|
||||
} else {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
|
||||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
Timber.e("## encryptEventContent() : $reason")
|
||||
Timber.e("## CRYPTO | encryptEventContent() : $reason")
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun discardOutbundSession(roomId: String) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
roomEncryptorsStore.get(roomId)?.discardSessionKey()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an event
|
||||
*
|
||||
|
@ -670,24 +682,35 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
val eventContent = event.content
|
||||
if (eventContent == null) {
|
||||
Timber.e("## decryptEvent : empty event content")
|
||||
Timber.e("## CRYPTO | decryptEvent : empty event content")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||
} else {
|
||||
val algorithm = eventContent["algorithm"]?.toString()
|
||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||
if (alg == null) {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||
Timber.e("## decryptEvent() : $reason")
|
||||
Timber.e("## CRYPTO | decryptEvent() : $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||
} else {
|
||||
try {
|
||||
return alg.decryptEvent(event, timeline)
|
||||
} catch (mxCryptoError: MXCryptoError) {
|
||||
if (mxCryptoError is MXCryptoError.Base
|
||||
&& mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE
|
||||
&& alg is MXMegolmDecryption) {
|
||||
// TODO Do it on decryption thread like on iOS?
|
||||
markOlmSessionForUnwedging(event, alg)
|
||||
Timber.d("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
|
||||
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
|
||||
if (mxCryptoError is MXCryptoError.Base
|
||||
&& mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
|
||||
// need to find sending device
|
||||
val olmContent = event.content.toModel<OlmEventContent>()
|
||||
cryptoStore.getUserDevices(event.senderId ?: "")
|
||||
?.values
|
||||
?.firstOrNull { it.identityKey() == olmContent?.senderKey }
|
||||
?.let {
|
||||
markOlmSessionForUnwedging(event.senderId ?: "", it)
|
||||
}
|
||||
?: run {
|
||||
Timber.v("## CRYPTO | markOlmSessionForUnwedging() : Failed to find sender crypto device")
|
||||
}
|
||||
}
|
||||
}
|
||||
throw mxCryptoError
|
||||
}
|
||||
|
@ -695,36 +718,37 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun markOlmSessionForUnwedging(event: Event, mxMegolmDecryption: MXMegolmDecryption) {
|
||||
val senderId = event.senderId ?: return
|
||||
val encryptedMessage = event.content.toModel<EncryptedEventContent>() ?: return
|
||||
val deviceKey = encryptedMessage.senderKey ?: return
|
||||
encryptedMessage.algorithm?.takeIf { it == MXCRYPTO_ALGORITHM_MEGOLM } ?: return
|
||||
|
||||
if (senderId == userId
|
||||
&& deviceKey == olmDevice.deviceCurve25519Key) {
|
||||
Timber.d("[MXCrypto] markOlmSessionForUnwedging: Do not unwedge ourselves")
|
||||
return
|
||||
}
|
||||
|
||||
val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
|
||||
Timber.d("[MXCrypto] markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
|
||||
return
|
||||
}
|
||||
|
||||
// Establish a new olm session with this device since we're failing to decrypt messages
|
||||
// on a current session.
|
||||
val deviceInfo = getDeviceInfo(senderId, deviceKey) ?: return Unit.also {
|
||||
Timber.d("[MXCrypto] markOlmSessionForUnwedging: Couldn't find device for identity key $deviceKey: not re-establishing session")
|
||||
}
|
||||
|
||||
Timber.d("[MXCrypto] markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
|
||||
lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
|
||||
|
||||
mxMegolmDecryption.markOlmSessionForUnwedging(senderId, deviceInfo)
|
||||
}
|
||||
// private fun markOlmSessionForUnwedging(event: Event, mxMegolmDecryption: MXMegolmDecryption) {
|
||||
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: ${event.eventId}")
|
||||
// val senderId = event.senderId ?: return
|
||||
// val encryptedMessage = event.content.toModel<EncryptedEventContent>() ?: return
|
||||
// val deviceKey = encryptedMessage.senderKey ?: return
|
||||
// encryptedMessage.algorithm?.takeIf { it == MXCRYPTO_ALGORITHM_MEGOLM } ?: return
|
||||
//
|
||||
// if (senderId == userId
|
||||
// && deviceKey == olmDevice.deviceCurve25519Key) {
|
||||
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: Do not unwedge ourselves")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// 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
|
||||
// }
|
||||
//
|
||||
// // Establish a new olm session with this device since we're failing to decrypt messages
|
||||
// // on a current session.
|
||||
// val deviceInfo = getDeviceInfo(senderId, deviceKey) ?: return Unit.also {
|
||||
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: Couldn't find device for identity key $deviceKey: not re-establishing session")
|
||||
// }
|
||||
//
|
||||
// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
|
||||
// lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
|
||||
//
|
||||
// mxMegolmDecryption.markOlmSessionForUnwedging(senderId, deviceInfo)
|
||||
// }
|
||||
|
||||
/**
|
||||
* Reset replay attack data for the given timeline.
|
||||
|
@ -774,30 +798,30 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
private fun onRoomKeyEvent(event: Event) {
|
||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||
Timber.v("## GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>")
|
||||
Timber.v("## CRYPTO | GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>")
|
||||
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
|
||||
Timber.e("## GOSSIP onRoomKeyEvent() : missing fields")
|
||||
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields")
|
||||
return
|
||||
}
|
||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
|
||||
if (alg == null) {
|
||||
Timber.e("## GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
|
||||
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
|
||||
return
|
||||
}
|
||||
alg.onRoomKeyEvent(event, keysBackupService)
|
||||
}
|
||||
|
||||
private fun onSecretSendReceived(event: Event) {
|
||||
Timber.i("## GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}")
|
||||
Timber.i("## CRYPTO | GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}")
|
||||
if (!event.isEncrypted()) {
|
||||
// secret send messages must be encrypted
|
||||
Timber.e("## GOSSIP onSecretSend() :Received unencrypted secret send event")
|
||||
Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event")
|
||||
return
|
||||
}
|
||||
|
||||
// Was that sent by us?
|
||||
if (event.senderId != userId) {
|
||||
Timber.e("## GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
|
||||
Timber.e("## CRYPTO | GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -807,13 +831,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
.getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
|
||||
|
||||
if (existingRequest == null) {
|
||||
Timber.i("## GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
|
||||
Timber.i("## CRYPTO | GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
|
||||
return
|
||||
}
|
||||
|
||||
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
|
||||
// TODO Ask to application layer?
|
||||
Timber.v("## onSecretSend() : secret not handled by SDK")
|
||||
Timber.v("## CRYPTO | onSecretSend() : secret not handled by SDK")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -849,7 +873,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
try {
|
||||
loadRoomMembersTask.execute(params)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||
Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||
} finally {
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||
|
@ -974,13 +998,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
runCatching {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Timber.v("## importRoomKeys starts")
|
||||
Timber.v("## CRYPTO | importRoomKeys starts")
|
||||
|
||||
val t0 = System.currentTimeMillis()
|
||||
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
|
||||
val t1 = System.currentTimeMillis()
|
||||
|
||||
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
|
||||
Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
|
||||
|
||||
val importedSessions = MoshiProvider.providesMoshi()
|
||||
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
|
||||
|
@ -988,7 +1012,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
val t2 = System.currentTimeMillis()
|
||||
|
||||
Timber.v("## importRoomKeys : JSON parsing ${t2 - t1} ms")
|
||||
Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
|
||||
|
||||
if (importedSessions == null) {
|
||||
throw Exception("Error")
|
||||
|
@ -1123,7 +1147,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
override fun reRequestRoomKeyForEvent(event: Event) {
|
||||
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
|
||||
Timber.e("## reRequestRoomKeyForEvent Failed to re-request key, null content")
|
||||
Timber.e("## CRYPTO | reRequestRoomKeyForEvent Failed to re-request key, null content")
|
||||
}
|
||||
|
||||
val requestBody = RoomKeyRequestBody(
|
||||
|
@ -1138,18 +1162,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
override fun requestRoomKeyForEvent(event: Event) {
|
||||
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
|
||||
Timber.e("## requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
|
||||
Timber.e("## CRYPTO | requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
|
||||
}
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
if (!isStarted()) {
|
||||
Timber.v("## requestRoomKeyForEvent() : wait after e2e init")
|
||||
Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
|
||||
internalStart(false)
|
||||
}
|
||||
roomDecryptorProvider
|
||||
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
|
||||
?.requestKeysForEvent(event) ?: run {
|
||||
Timber.v("## requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
|
||||
Timber.v("## CRYPTO | requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1172,6 +1196,27 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
|
||||
}
|
||||
|
||||
private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
|
||||
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
|
||||
*
|
||||
|
|
|
@ -108,7 +108,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## canRetryKeysDownload() failed")
|
||||
Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +137,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
|
||||
for (userId in userIds) {
|
||||
if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
|
||||
Timber.v("## startTrackingDeviceList() : Now tracking device list for $userId")
|
||||
Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
|
||||
for (userId in changed) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : Marking device list outdated for $userId")
|
||||
Timber.v("## CRYPTO | invalidateUserDeviceList() : Marking device list outdated for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
|
||||
for (userId in left) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : No longer tracking device list for $userId")
|
||||
Timber.v("## CRYPTO | invalidateUserDeviceList() : No longer tracking device list for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
|
||||
isUpdated = true
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
* @param forceDownload Always download the keys even if cached.
|
||||
*/
|
||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||
Timber.v("## CRYPTO | downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||
// Map from userId -> deviceId -> MXDeviceInfo
|
||||
val stored = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||
|
||||
|
@ -288,13 +288,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
}
|
||||
}
|
||||
return if (downloadUsers.isEmpty()) {
|
||||
Timber.v("## downloadKeys() : no new user device")
|
||||
Timber.v("## CRYPTO | downloadKeys() : no new user device")
|
||||
stored
|
||||
} else {
|
||||
Timber.v("## downloadKeys() : starts")
|
||||
Timber.v("## CRYPTO | downloadKeys() : starts")
|
||||
val t0 = System.currentTimeMillis()
|
||||
val result = doKeyDownloadForUsers(downloadUsers)
|
||||
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
result.also {
|
||||
it.addEntriesFromMap(stored)
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
* @param downloadUsers the user ids list
|
||||
*/
|
||||
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
||||
Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
||||
// get the user ids which did not already trigger a keys download
|
||||
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
||||
if (filteredUsers.isEmpty()) {
|
||||
|
@ -318,16 +318,16 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
val response = try {
|
||||
downloadKeysForUsersTask.execute(params)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable, "##doKeyDownloadForUsers(): error")
|
||||
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
|
||||
onKeysDownloadFailed(filteredUsers)
|
||||
throw throwable
|
||||
}
|
||||
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||
for (userId in filteredUsers) {
|
||||
// al devices =
|
||||
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||
|
||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||
if (!models.isNullOrEmpty()) {
|
||||
val workingCopy = models.toMutableMap()
|
||||
for ((deviceId, deviceInfo) in models) {
|
||||
|
@ -361,13 +361,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
|
||||
// Handle cross signing keys update
|
||||
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
|
||||
Timber.v("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
|
||||
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
|
||||
}
|
||||
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
|
||||
Timber.v("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
|
||||
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
|
||||
}
|
||||
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
|
||||
Timber.v("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
|
||||
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
|
||||
}
|
||||
cryptoStore.storeUserCrossSigningKeys(
|
||||
userId,
|
||||
|
@ -395,28 +395,28 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
*/
|
||||
private fun validateDeviceKeys(deviceKeys: CryptoDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: CryptoDeviceInfo?): Boolean {
|
||||
if (null == deviceKeys) {
|
||||
Timber.e("## validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
if (null == deviceKeys.keys) {
|
||||
Timber.e("## validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
if (null == deviceKeys.signatures) {
|
||||
Timber.e("## validateDeviceKeys() : deviceKeys.signatures is null from $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.signatures is null from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check that the user_id and device_id in the received deviceKeys are correct
|
||||
if (deviceKeys.userId != userId) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
if (deviceKeys.deviceId != deviceId) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -424,21 +424,21 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
val signKey = deviceKeys.keys[signKeyId]
|
||||
|
||||
if (null == signKey) {
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
|
||||
return false
|
||||
}
|
||||
|
||||
val signatureMap = deviceKeys.signatures[userId]
|
||||
|
||||
if (null == signatureMap) {
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
|
||||
return false
|
||||
}
|
||||
|
||||
val signature = signatureMap[signKeyId]
|
||||
|
||||
if (null == signature) {
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -453,7 +453,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
}
|
||||
|
||||
if (!isVerified) {
|
||||
Timber.e("## validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
|
||||
+ deviceKeys.deviceId + " with error " + errorMessage)
|
||||
return false
|
||||
}
|
||||
|
@ -464,12 +464,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
// best off sticking with the original keys.
|
||||
//
|
||||
// Should we warn the user about it somehow?
|
||||
Timber.e("## validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
|
||||
+ deviceKeys.deviceId + " has changed : "
|
||||
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
|
||||
|
||||
Timber.e("## validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
|
||||
Timber.e("## validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
|
||||
Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -501,10 +501,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||
doKeyDownloadForUsers(users)
|
||||
}.fold(
|
||||
{
|
||||
Timber.v("## refreshOutdatedDeviceLists() : done")
|
||||
Timber.v("## CRYPTO | refreshOutdatedDeviceLists() : done")
|
||||
},
|
||||
{
|
||||
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
|
||||
Timber.e(it, "## CRYPTO | refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
* @param event the announcement event.
|
||||
*/
|
||||
fun onGossipingRequestEvent(event: Event) {
|
||||
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
|
||||
Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
|
||||
val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
|
||||
val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
|
||||
when (roomKeyShare?.action) {
|
||||
|
@ -161,7 +161,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
}
|
||||
|
||||
receivedRequestCancellations?.forEach { request ->
|
||||
Timber.v("## GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
|
||||
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
|
||||
// we should probably only notify the app of cancellations we told it
|
||||
// about, but we don't currently have a record of that, so we just pass
|
||||
// everything through.
|
||||
|
@ -190,9 +190,9 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
val roomId = body.roomId ?: return
|
||||
val alg = body.algorithm ?: return
|
||||
|
||||
Timber.v("## GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
if (credentials.userId != userId) {
|
||||
Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request from other user")
|
||||
Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
|
||||
val senderKey = body.senderKey ?: return Unit
|
||||
.also { Timber.w("missing senderKey") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
|
@ -227,18 +227,18 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
// the keys for the requested events, and can drop the requests.
|
||||
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
|
||||
if (null == decryptor) {
|
||||
Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
|
||||
Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
if (!decryptor.hasKeysForKeyRequest(request)) {
|
||||
Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
|
||||
Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
|
||||
if (credentials.deviceId == deviceId && credentials.userId == userId) {
|
||||
Timber.v("## GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
|
||||
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
|
@ -253,13 +253,13 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||
if (device != null) {
|
||||
if (device.isVerified) {
|
||||
Timber.v("## GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
|
||||
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
|
||||
request.share?.run()
|
||||
return
|
||||
}
|
||||
|
||||
if (device.isBlocked) {
|
||||
Timber.v("## GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
|
||||
Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
|
||||
// As per config we automatically discard untrusted devices request
|
||||
if (cryptoConfig.discardRoomKeyRequestsFromUntrustedDevices) {
|
||||
Timber.v("## processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices")
|
||||
Timber.v("## CRYPTO | processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices")
|
||||
// At this point the device is unknown, we don't want to bother user with that
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
|
@ -280,30 +280,30 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
|
||||
val secretName = request.secretName ?: return Unit.also {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Missing secret name")
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Missing secret name")
|
||||
}
|
||||
|
||||
val userId = request.userId
|
||||
if (userId == null || credentials.userId != userId) {
|
||||
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
|
||||
Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
|
||||
val deviceId = request.deviceId
|
||||
?: return Unit.also {
|
||||
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
|
||||
Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
}
|
||||
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||
?: return Unit.also {
|
||||
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
|
||||
Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
}
|
||||
|
||||
if (!device.isVerified || device.isBlocked) {
|
||||
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
return
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
}
|
||||
else -> null
|
||||
}?.let { secretValue ->
|
||||
Timber.i("## GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
|
||||
Timber.i("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
|
||||
if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) {
|
||||
val params = SendGossipWorker.Params(
|
||||
sessionId = sessionId,
|
||||
|
@ -332,13 +332,13 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
val workRequest = gossipingWorkManager.createWork<SendGossipWorker>(WorkerParamsFactory.toData(params), true)
|
||||
gossipingWorkManager.postWork(workRequest)
|
||||
} else {
|
||||
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Timber.v("## GOSSIP processIncomingSecretShareRequest() : $secretName unknown at SDK level, asking to app layer")
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : $secretName unknown at SDK level, asking to app layer")
|
||||
|
||||
request.ignore = Runnable {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
|
@ -372,7 +372,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
try {
|
||||
listener.onRoomKeyRequest(request)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onRoomKeyRequest() failed")
|
||||
Timber.e(e, "## CRYPTO | onRoomKeyRequest() failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## GOSSIP onRoomKeyRequest() failed")
|
||||
Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequest() failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +408,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
try {
|
||||
listener.onRoomKeyRequestCancellation(request)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## GOSSIP onRoomKeyRequestCancellation() failed")
|
||||
Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequestCancellation() failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -342,6 +342,8 @@ internal class MXOlmDevice @Inject constructor(
|
|||
} catch (e: Exception) {
|
||||
Timber.e(e, "## encryptMessage() : failed")
|
||||
}
|
||||
} else {
|
||||
Timber.e("## encryptMessage() : Failed to encrypt unknown session $sessionId")
|
||||
}
|
||||
|
||||
return res
|
||||
|
|
|
@ -55,7 +55,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||
cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let {
|
||||
// Don't resend if it's already done, you need to cancel first (reRequest)
|
||||
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
|
||||
Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
|
||||
Timber.v("## CRYPTO - GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||
cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
|
||||
// TODO check if there is already one that is being sent?
|
||||
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
|
||||
Timber.v("## GOSSIP sendSecretShareRequest() : we already request for that session: $it")
|
||||
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we already request for that session: $it")
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||
val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
|
||||
?: // no request was made for this key
|
||||
return Unit.also {
|
||||
Timber.v("## GOSSIP cancelRoomKeyRequest() Unknown request")
|
||||
Timber.v("## CRYPTO - GOSSIP cancelRoomKeyRequest() Unknown request $requestBody")
|
||||
}
|
||||
|
||||
sendOutgoingRoomKeyRequestCancellation(req, andResend)
|
||||
|
@ -125,7 +125,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||
* @param request the request
|
||||
*/
|
||||
private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) {
|
||||
Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : Requesting keys $request")
|
||||
Timber.v("## CRYPTO - GOSSIP sendOutgoingRoomKeyRequest() : Requesting keys $request")
|
||||
|
||||
val params = SendGossipRequestWorker.Params(
|
||||
sessionId = sessionId,
|
||||
|
@ -143,7 +143,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
|||
* @param request the request
|
||||
*/
|
||||
private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest, resend: Boolean = false) {
|
||||
Timber.v("$request")
|
||||
Timber.v("## CRYPTO - sendOutgoingRoomKeyRequestCancellation $request")
|
||||
val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request)
|
||||
cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING)
|
||||
|
||||
|
|
|
@ -69,11 +69,11 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
|||
//
|
||||
// That should eventually resolve itself, but it's poor form.
|
||||
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
||||
Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
|
||||
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||
Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||
for ((userId, deviceInfos) in devicesByUser) {
|
||||
for (deviceInfo in deviceInfos) {
|
||||
var oneTimeKey: MXKey? = null
|
||||
|
@ -90,7 +90,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
|||
oneTimeKey = key
|
||||
}
|
||||
if (oneTimeKey == null) {
|
||||
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
|
||||
Timber.v("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
|
||||
+ " for device " + userId + " : " + deviceId)
|
||||
continue
|
||||
}
|
||||
|
@ -126,14 +126,14 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
|||
sessionId = olmDevice.createOutboundSession(deviceInfo.identityKey()!!, oneTimeKey.value)
|
||||
|
||||
if (!sessionId.isNullOrEmpty()) {
|
||||
Timber.v("## verifyKeyAndStartSession() : Started new sessionid " + sessionId
|
||||
Timber.v("## CRYPTO | verifyKeyAndStartSession() : Started new sessionid " + sessionId
|
||||
+ " for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
|
||||
} else {
|
||||
// Possibly a bad key
|
||||
Timber.e("## verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
|
||||
Timber.e("## CRYPTO | verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
|
||||
}
|
||||
} else {
|
||||
Timber.e("## verifyKeyAndStartSession() : Unable to verify signature on one-time key for device " + userId
|
||||
Timber.e("## CRYPTO | verifyKeyAndStartSession() : Unable to verify signature on one-time key for device " + userId
|
||||
+ ":" + deviceId + " Error " + errorMessage)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,20 @@ internal interface IMXEncrypting {
|
|||
*/
|
||||
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
|
||||
|
||||
/**
|
||||
* In Megolm, each recipient maintains a record of the ratchet value which allows
|
||||
* them to decrypt any messages sent in the session after the corresponding point
|
||||
* in the conversation. If this value is compromised, an attacker can similarly
|
||||
* decrypt past messages which were encrypted by a key derived from the
|
||||
* compromised or subsequent ratchet values. This gives 'partial' forward
|
||||
* secrecy.
|
||||
*
|
||||
* To mitigate this issue, the application should offer the user the option to
|
||||
* discard historical conversations, by winding forward any stored ratchet values,
|
||||
* or discarding sessions altogether.
|
||||
*/
|
||||
fun discardSessionKey()
|
||||
|
||||
/**
|
||||
* Re-shares a session key with devices if the key has already been
|
||||
* sent to them.
|
||||
|
|
|
@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
|
|||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||
|
@ -74,6 +73,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
|
||||
@Throws(MXCryptoError::class)
|
||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
||||
Timber.v("## CRYPTO | decryptEvent ${event.eventId} , requestKeysOnFail:$requestKeysOnFail")
|
||||
if (event.roomId.isNullOrBlank()) {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
val events = timeline.getOrPut(timelineId) { ArrayList() }
|
||||
|
||||
if (event !in events) {
|
||||
Timber.v("## addEventToPendingList() : add Event ${event.eventId} in room id ${event.roomId}")
|
||||
Timber.v("## CRYPTO | addEventToPendingList() : add Event ${event.eventId} in room id ${event.roomId}")
|
||||
events.add(event)
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +202,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
* @param event the key event.
|
||||
*/
|
||||
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
|
||||
Timber.v("## CRYPTO | onRoomKeyEvent()")
|
||||
var exportFormat = false
|
||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||
|
||||
|
@ -210,11 +211,11 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
val forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
|
||||
|
||||
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) {
|
||||
Timber.e("## onRoomKeyEvent() : Key event is missing fields")
|
||||
Timber.e("## CRYPTO | onRoomKeyEvent() : Key event is missing fields")
|
||||
return
|
||||
}
|
||||
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
|
||||
Timber.v("## onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" +
|
||||
Timber.v("## CRYPTO | onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" +
|
||||
" sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}")
|
||||
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||
?: return
|
||||
|
@ -224,7 +225,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
}
|
||||
|
||||
if (senderKey == null) {
|
||||
Timber.e("## onRoomKeyEvent() : event is missing sender_key field")
|
||||
Timber.e("## CRYPTO | onRoomKeyEvent() : event is missing sender_key field")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -233,18 +234,18 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
exportFormat = true
|
||||
senderKey = forwardedRoomKeyContent.senderKey
|
||||
if (null == senderKey) {
|
||||
Timber.e("## onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
|
||||
Timber.e("## CRYPTO | onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
|
||||
return
|
||||
}
|
||||
|
||||
if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
|
||||
Timber.e("## forwarded_room_key_event is missing sender_claimed_ed25519_key field")
|
||||
Timber.e("## CRYPTO | forwarded_room_key_event is missing sender_claimed_ed25519_key field")
|
||||
return
|
||||
}
|
||||
|
||||
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
|
||||
} else {
|
||||
Timber.v("## onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
|
||||
Timber.v("## CRYPTO | onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
|
||||
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
|
||||
|
||||
if (null == senderKey) {
|
||||
|
@ -256,6 +257,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
keysClaimed = event.getKeysClaimed().toMutableMap()
|
||||
}
|
||||
|
||||
Timber.e("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
|
||||
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
|
||||
roomKeyContent.sessionKey,
|
||||
roomKeyContent.roomId,
|
||||
|
@ -287,7 +289,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
* @param sessionId the session id
|
||||
*/
|
||||
override fun onNewSession(senderKey: String, sessionId: String) {
|
||||
Timber.v("ON NEW SESSION $sessionId - $senderKey")
|
||||
Timber.v(" CRYPTO | ON NEW SESSION $sessionId - $senderKey")
|
||||
newSessionListener?.onNewSession(null, senderKey, sessionId)
|
||||
}
|
||||
|
||||
|
@ -321,7 +323,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
// were no one-time keys.
|
||||
return@mapCatching
|
||||
}
|
||||
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
|
||||
Timber.v("## CRYPTO | shareKeysWithDevice() : sharing keys for session" +
|
||||
" ${body.senderKey}|${body.sessionId} with device $userId:$deviceId")
|
||||
|
||||
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
|
||||
|
@ -340,32 +342,11 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||
Timber.v("## CRYPTO | shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
|
||||
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("## markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,17 +66,25 @@ internal class MXMegolmEncryption(
|
|||
override suspend fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
userIds: List<String>): Content {
|
||||
val ts = System.currentTimeMillis()
|
||||
Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
|
||||
val devices = getDevicesInRoom(userIds)
|
||||
Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.map}")
|
||||
val outboundSession = ensureOutboundSession(devices)
|
||||
return encryptContent(outboundSession, eventType, eventContent)
|
||||
}
|
||||
|
||||
override fun discardSessionKey() {
|
||||
outboundSession = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a new session.
|
||||
*
|
||||
* @return the session description
|
||||
*/
|
||||
private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
|
||||
Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
|
||||
val sessionId = olmDevice.createOutboundGroupSession()
|
||||
|
||||
val keysClaimedMap = HashMap<String, String>()
|
||||
|
@ -96,6 +104,7 @@ internal class MXMegolmEncryption(
|
|||
* @param devicesInRoom the devices list
|
||||
*/
|
||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
|
||||
Timber.v("## CRYPTO | ensureOutboundSession start")
|
||||
var session = outboundSession
|
||||
if (session == null
|
||||
// Need to make a brand new session?
|
||||
|
@ -132,7 +141,7 @@ internal class MXMegolmEncryption(
|
|||
devicesByUsers: Map<String, List<CryptoDeviceInfo>>) {
|
||||
// nothing to send, the task is done
|
||||
if (devicesByUsers.isEmpty()) {
|
||||
Timber.v("## shareKey() : nothing more to do")
|
||||
Timber.v("## CRYPTO | shareKey() : nothing more to do")
|
||||
return
|
||||
}
|
||||
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
||||
|
@ -145,7 +154,7 @@ internal class MXMegolmEncryption(
|
|||
break
|
||||
}
|
||||
}
|
||||
Timber.v("## shareKey() ; userId ${subMap.keys}")
|
||||
Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
|
||||
shareUserDevicesKey(session, subMap)
|
||||
val remainingDevices = devicesByUsers - subMap.keys
|
||||
shareKey(session, remainingDevices)
|
||||
|
@ -174,10 +183,10 @@ internal class MXMegolmEncryption(
|
|||
payload["content"] = submap
|
||||
|
||||
var t0 = System.currentTimeMillis()
|
||||
Timber.v("## shareUserDevicesKey() : starts")
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : starts")
|
||||
|
||||
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
|
||||
+ (System.currentTimeMillis() - t0) + " ms")
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
var haveTargets = false
|
||||
|
@ -200,17 +209,17 @@ internal class MXMegolmEncryption(
|
|||
// so just skip it.
|
||||
continue
|
||||
}
|
||||
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
|
||||
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
|
||||
haveTargets = true
|
||||
}
|
||||
}
|
||||
if (haveTargets) {
|
||||
t0 = System.currentTimeMillis()
|
||||
Timber.v("## shareUserDevicesKey() : has target")
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : has target")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after "
|
||||
+ (System.currentTimeMillis() - t0) + " ms")
|
||||
|
||||
// Add the devices we have shared with to session.sharedWithDevices.
|
||||
|
@ -224,7 +233,7 @@ internal class MXMegolmEncryption(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Timber.v("## shareUserDevicesKey() : no need to sharekey")
|
||||
Timber.v("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +354,7 @@ internal class MXMegolmEncryption(
|
|||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||
Timber.v("## CRYPTO | CRYPTO | shareKeysWithDevice() : sending to $userId:$deviceId")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
return true
|
||||
|
|
|
@ -79,6 +79,10 @@ internal class MXOlmEncryption(
|
|||
ensureOlmSessionsForUsersAction.handle(users)
|
||||
}
|
||||
|
||||
override fun discardSessionKey() {
|
||||
// No need for olm
|
||||
}
|
||||
|
||||
override suspend fun reshareKey(sessionId: String, userId: String, deviceId: String, senderKey: String): Boolean {
|
||||
// No need for olm
|
||||
return false
|
||||
|
|
|
@ -39,10 +39,10 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
|
|||
toDevice.events?.forEachIndexed { index, event ->
|
||||
initialSyncProgressService?.reportProgress(((index / total.toFloat()) * 100).toInt())
|
||||
// Decrypt event if necessary
|
||||
decryptEvent(event, null)
|
||||
decryptToDeviceEvent(event, null)
|
||||
if (event.getClearType() == EventType.MESSAGE
|
||||
&& event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
|
||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
|
||||
Timber.e("## CRYPTO | handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
|
||||
} else {
|
||||
verificationService.onToDeviceEvent(event)
|
||||
cryptoService.onToDeviceEvent(event)
|
||||
|
@ -61,28 +61,24 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
|
|||
* @param timelineId the timeline identifier
|
||||
* @return true if the event has been decrypted
|
||||
*/
|
||||
private fun decryptEvent(event: Event, timelineId: String?): Boolean {
|
||||
private fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
|
||||
Timber.v("## CRYPTO | decryptToDeviceEvent")
|
||||
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||
var result: MXEventDecryptionResult? = null
|
||||
try {
|
||||
result = cryptoService.decryptEvent(event, timelineId ?: "")
|
||||
} catch (exception: MXCryptoError) {
|
||||
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
|
||||
Timber.e("## CRYPTO | Failed to decrypt to device event: ${event.mCryptoError ?: exception}")
|
||||
}
|
||||
|
||||
if (null != result) {
|
||||
// event.mxDecryptionResult = MXDecryptionResult(
|
||||
// payload = result.clearEvent,
|
||||
// keysClaimed = map
|
||||
// )
|
||||
// TODO persist that?
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
// event.setClearData(result)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll),
|
||||
SHRUG("/shrug", "<message>", R.string.command_description_shrug),
|
||||
PLAIN("/plain", "<message>", R.string.command_description_plain),
|
||||
DISCARD_SESSION("/discardsession", "<message>", R.string.command_description_discard_session),
|
||||
// TODO temporary command
|
||||
VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify);
|
||||
|
||||
|
|
|
@ -281,6 +281,9 @@ object CommandParser {
|
|||
ParsedCommand.ErrorSyntax(Command.POLL)
|
||||
}
|
||||
}
|
||||
Command.DISCARD_SESSION.command -> {
|
||||
ParsedCommand.DiscardSession
|
||||
}
|
||||
else -> {
|
||||
// Unknown command
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
|
|
|
@ -52,4 +52,5 @@ sealed class ParsedCommand {
|
|||
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||
class VerifyUser(val userId: String) : ParsedCommand()
|
||||
class SendPoll(val question: String, val options: List<String>) : ParsedCommand()
|
||||
object DiscardSession: ParsedCommand()
|
||||
}
|
||||
|
|
|
@ -447,6 +447,9 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
// TODO
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.DiscardSession -> {
|
||||
session.cryptoService().discardOutbundSession(room.roomId)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- Sections has been created to limit merge conflicts. -->
|
||||
|
||||
<!-- BEGIN Strings added by Valere -->
|
||||
|
||||
<string name="command_description_discard_session">Forces the current outbound group session in an encrypted room to be discarded</string>
|
||||
<!-- END Strings added by Valere -->
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue