diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/WithHeldTests.kt index 581624ba16..51ae09827a 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/WithHeldTests.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.crypto.gossiping import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.NoOpMatrixCallback +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 @@ -128,9 +130,8 @@ class WithHeldTests : InstrumentedTest { mTestHelper.signOutAndClose(bobUnverifiedSession) } - @Test - fun test_WithHeldNoOlm() { + fun test_WithHeldNoOlm() { val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! @@ -148,8 +149,6 @@ class WithHeldTests : InstrumentedTest { val roomAlicePov = aliceSession.getRoom(testData.roomId)!! - // can we force one-time key shortage?? - val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId // await for bob session to get the message @@ -176,7 +175,7 @@ class WithHeldTests : InstrumentedTest { val sessionId = eventBobPOV!!.root.content.toModel()!!.sessionId!! val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId) - Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex) + Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex) // Add a new device for bob aliceInterceptor.clearRules() @@ -194,10 +193,55 @@ class WithHeldTests : InstrumentedTest { val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId) - Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) + Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) - mTestHelper.signOutAndClose(bobSecondSession) + aliceInterceptor.clearRules() testData.cleanUp(mTestHelper) + mTestHelper.signOutAndClose(bobSecondSession) + } + @Test + fun test_WithHeldKeyRequest() { + val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + val aliceSession = testData.firstSession + val bobSession = testData.secondSession!! + + val roomAlicePov = aliceSession.getRoom(testData.roomId)!! + + val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId + + mTestHelper.signOutAndClose(bobSession) + + // Create a new session for bob + + val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) + // initialize to force request keys if missing + mCryptoTestHelper.initializeCrossSigning(bobSecondSession) + + // Trust bob second device from Alice POV + aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback()) + bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback()) + + var sessionId: String? = null + // Check that the + // await for bob SecondSession session to get the message + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { + val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also { + // try to decrypt and force key request + tryThis { bobSecondSession.cryptoService().decryptEvent(it.root, "") } + } + sessionId = timeLineEvent?.root?.content?.toModel()?.sessionId + timeLineEvent != null + } + } + + //Check that bob second session requested the key + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { + val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) + wc?.code == WithHeldCode.UNAUTHORISED + } + } } } 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 5e6eb6d4ff..edddf90412 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 @@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult 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.RoomKeyWithHeldContent import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody @@ -148,4 +149,5 @@ interface CryptoService { //For testing shared session fun getSharedWithInfo(roomId: String?, sessionId: String) : MXUsersDevicesMap + fun getWithHeldMegolmSession(roomId: String, sessionId: String) : RoomKeyWithHeldContent? } 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 c0033447bd..29517ea6b3 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 @@ -1334,6 +1334,9 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getSharedWithInfo(roomId, sessionId) } + override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? { + return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) + } /* ========================================================================================== * For test only * ========================================================================================== */ 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 83a6bce70e..94c4d9ae3e 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 @@ -94,7 +94,7 @@ internal class MXMegolmEncryption( { it.second }, { it.first } ).forEach { (code, targets) -> - notifyKeyWithHeld(targets, outboundSession.sessionId, code) + notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code) } } @@ -229,6 +229,7 @@ internal class MXMegolmEncryption( notifyKeyWithHeld( listOf(UserDevice(userId, deviceID)), session.sessionId, + olmDevice.deviceCurve25519Key, WithHeldCode.NO_OLM ) @@ -267,10 +268,10 @@ internal class MXMegolmEncryption( } } - private fun notifyKeyWithHeld(targets: List, sessionId: String, code: WithHeldCode) { + private fun notifyKeyWithHeld(targets: List, sessionId: String, senderKey: String?, code: WithHeldCode) { val withHeldContent = RoomKeyWithHeldContent( roomId = roomId, - senderKey = olmDevice.deviceCurve25519Key, + senderKey = senderKey, algorithm = MXCRYPTO_ALGORITHM_MEGOLM, sessionId = sessionId, codeString = code.value @@ -383,7 +384,11 @@ internal class MXMegolmEncryption( // Get the chain index of the key we previously sent this device val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId,deviceId) ?: return false - .also { Timber.w("[MXMegolmEncryption] reshareKey : ERROR : Never share megolm with this device") } + .also { + // Send a room key with held + notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED) + Timber.w("[MXMegolmEncryption] reshareKey : ERROR : Never share megolm with this device") + } val devicesByUser = mapOf(userId to listOf(deviceInfo)) val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)