From 9b320ed3c78de5590e96785c8100325eddff962f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2020 15:40:59 +0200 Subject: [PATCH] Fix unwedging --- .../android/internal/crypto/UnwedgingTest.kt | 92 +++++++--- .../api/session/crypto/CryptoService.kt | 2 + .../internal/crypto/DefaultCryptoService.kt | 167 +++++++++++------- .../internal/crypto/DeviceListManager.kt | 58 +++--- .../crypto/IncomingGossipingRequestManager.kt | 42 ++--- .../android/internal/crypto/MXOlmDevice.kt | 2 + .../crypto/OutgoingGossipingRequestManager.kt | 10 +- .../EnsureOlmSessionsForDevicesAction.kt | 12 +- .../crypto/algorithms/IMXEncrypting.kt | 14 ++ .../algorithms/megolm/MXMegolmDecryption.kt | 45 ++--- .../algorithms/megolm/MXMegolmEncryption.kt | 27 ++- .../crypto/algorithms/olm/MXOlmEncryption.kt | 4 + .../session/sync/CryptoSyncHandler.kt | 14 +- .../vector/riotx/features/command/Command.kt | 1 + .../riotx/features/command/CommandParser.kt | 3 + .../riotx/features/command/ParsedCommand.kt | 1 + .../home/room/detail/RoomDetailViewModel.kt | 3 + vector/src/main/res/values/strings_riotX.xml | 2 +- 18 files changed, 304 insertions(+), 195 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/UnwedgingTest.kt index 123c8a5c88..7f75d7d6fd 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/UnwedgingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/UnwedgingTest.kt @@ -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) { - 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()!!.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()!!.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(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()!!.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 { + 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 { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index ab8417b542..a923b2cc3d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -111,6 +111,8 @@ interface CryptoService { roomId: String, callback: MatrixCallback) + fun discardOutbundSession(roomId: String) + @Throws(MXCryptoError::class) fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 476af38ce7..e865998fa9 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -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) { 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() + 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() ?: 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() ?: 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() ?: 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>(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() ?: 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() ?: 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("type" to EventType.DUMMY) + + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + 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 * diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index 37a5ee18e1..680539d057 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -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?, forceDownload: Boolean): MXUsersDevicesMap { - Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds") + Timber.v("## CRYPTO | downloadKeys() : forceDownload $forceDownload : $userIds") // Map from userId -> deviceId -> MXDeviceInfo val stored = MXUsersDevicesMap() @@ -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): MXUsersDevicesMap { - 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") } ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt index b17143d93d..38f81ba47d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt @@ -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() 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(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") } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt index 0351c183ce..d6d8b06b5f 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt index c06f10b106..eb1c07cb92 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index d856331189..e630d14eab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt @@ -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) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt index 65119362bc..ddf605def4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt @@ -34,6 +34,20 @@ internal interface IMXEncrypting { */ suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): 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. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 815a4f7d12..59ffa5f874 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -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() ?: return @@ -210,11 +211,11 @@ internal class MXMegolmDecryption(private val userId: String, val forwardingCurve25519KeyChain: MutableList = 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() ?: 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("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() 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("type" to EventType.DUMMY) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) - Timber.v("## markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) - } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index addb8c2f76..3800e3c4f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -66,17 +66,25 @@ internal class MXMegolmEncryption( override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): 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() @@ -96,6 +104,7 @@ internal class MXMegolmEncryption( * @param devicesInRoom the devices list */ private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): 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>) { // 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() 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() 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt index b1181fc067..a9b84a8e48 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index 10e7ceb692..86ca561dfe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -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()?.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 } } diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 72f686c2c8..ebfea57044 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -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", "", R.string.command_description_shrug), PLAIN("/plain", "", R.string.command_description_plain), + DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session), // TODO temporary command VERIFY_USER("/verify", "", R.string.command_description_verify); diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 875fe92610..e7d2e9a62b 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -281,6 +281,9 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.POLL) } } + Command.DISCARD_SESSION.command -> { + ParsedCommand.DiscardSession + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index e4fee27ee6..63e016b0b6 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -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) : ParsedCommand() + object DiscardSession: ParsedCommand() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index cef172da73..73b03a7752 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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 -> { diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 3e23f61acf..a25d3bbe35 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -6,7 +6,7 @@ - + Forces the current outbound group session in an encrypted room to be discarded