diff --git a/library/rustCrypto/matrix-rust-sdk-crypto.aar b/library/rustCrypto/matrix-rust-sdk-crypto.aar index 2893c95a32..3668255b72 100644 --- a/library/rustCrypto/matrix-rust-sdk-crypto.aar +++ b/library/rustCrypto/matrix-rust-sdk-crypto.aar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c87e7bd9c5e7c623708958eebddbc58c26269b0c1d81e0dc3510a657a28a6515 -size 20719656 +oid sha256:c025a7047c3276b09f8cfaddc6323688b4c0174385148aa20f21080ba74d236d +size 32306804 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt index 8b12092b79..226277fef2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import android.util.Log import androidx.test.filters.LargeTest import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder @@ -98,32 +99,34 @@ class E2eeConfigTest : InstrumentedTest { val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! - val beforeMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first() + val beforeMessage = testHelper.sendMessageInRoom(roomAlicePOV, "you can read") val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.retryPeriodically { - roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null - } + Log.v("#E2E TEST", "Wait for bob to get the message") + testHelper.ensureMessage(roomBobPOV, beforeMessage) { true } + Log.v("#E2E TEST", "ensure bob Can Decrypt first message") cryptoTestHelper.ensureCanDecrypt( - listOf(beforeMessage.eventId), + listOf(beforeMessage), cryptoTestData.secondSession!!, cryptoTestData.roomId, - listOf(beforeMessage.getLastMessageContent()!!.body) + listOf("you can read") ) + Log.v("#E2E TEST", "setRoomBlockUnverifiedDevices true") cryptoTestData.firstSession.cryptoService().setRoomBlockUnverifiedDevices(cryptoTestData.roomId, true) - val afterMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first() + Log.v("#E2E TEST", "let alice send the message") + val afterMessage = testHelper.sendMessageInRoom(roomAlicePOV, "you are blocked") // ensure received - testHelper.retryPeriodically { - cryptoTestData.secondSession?.getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(afterMessage.eventId)?.root != null - } + + Log.v("#E2E TEST", "Ensure bob received second message") + testHelper.ensureMessage(roomBobPOV, afterMessage) { true } cryptoTestHelper.ensureCannotDecrypt( - listOf(afterMessage.eventId), + listOf(afterMessage), cryptoTestData.secondSession!!, cryptoTestData.roomId, MXCryptoError.ErrorType.KEYS_WITHHELD diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index f790eb1b6d..bd813002b8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -81,7 +81,7 @@ class WithHeldTests : InstrumentedTest { val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first() // await for bob unverified session to get the message - testHelper.retryPeriodically { + testHelper.retryWithBackoff { bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null } @@ -106,24 +106,26 @@ class WithHeldTests : InstrumentedTest { bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") } - // Let's see if the reply we got from bob first session is unverified - testHelper.retryPeriodically { - bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests() - .firstOrNull { it.sessionId == megolmSessionId } - ?.results - ?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId } - ?.result - ?.let { - it as? RequestResult.Failure - } - ?.code == WithHeldCode.UNVERIFIED + if (bobUnverifiedSession.cryptoService().supportsForwardedKeyWiththeld()) { + // Let's see if the reply we got from bob first session is unverified + testHelper.retryWithBackoff { + bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests() + .firstOrNull { it.sessionId == megolmSessionId } + ?.results + ?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId } + ?.result + ?.let { + it as? RequestResult.Failure + } + ?.code == WithHeldCode.UNVERIFIED + } } // enable back sending to unverified aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false) val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first() - testHelper.retryPeriodically { + testHelper.retryWithBackoff { val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId) // wait until it's decrypted ev?.root?.getClearType() == EventType.MESSAGE @@ -234,9 +236,19 @@ class WithHeldTests : InstrumentedTest { // initialize to force request keys if missing cryptoTestHelper.initializeCrossSigning(bobSecondSession) - // Trust bob second device from Alice POV - aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId) - bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId) +// // wait until alice downloaded the new device +// testHelper.retryWithBackoff { +// aliceSession.cryptoService().getUserDevices(bobSession.myUserId).any { it.deviceId == bobSecondSession.sessionParams.deviceId} +// } +// +// // Trust bob second device from Alice POV +// aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId) +// +// // wait until bob downloaded alice device +// testHelper.retryWithBackoff { +// bobSecondSession.cryptoService().getUserDevices(aliceSession.myUserId).any { it.deviceId == aliceSession.sessionParams.deviceId} +// } +// bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId) var sessionId: String? = null // Check that the @@ -252,10 +264,23 @@ class WithHeldTests : InstrumentedTest { timeLineEvent != null } - // Check that bob second session requested the key - testHelper.retryPeriodically { - val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) - wc?.code == WithHeldCode.UNAUTHORISED + + mustFail( + message = "This session should not be able to decrypt", + failureBlock = { failure -> + val type = (failure as MXCryptoError.Base).errorType + val technicalMessage = failure.technicalMessage + Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) + Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) + } + ) { + val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) + bobSecondSession.cryptoService().decryptEvent(timeLineEvent!!.root, "") } +// // Check that bob second session requested the key +// testHelper.retryPeriodically { +// val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) +// wc?.code == WithHeldCode.UNAUTHORISED +// } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 6eb7d06e76..326e4ac2a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -94,6 +94,9 @@ interface CryptoService { */ fun supportsShareKeysOnInvite(): Boolean + fun supportsKeyWithheld(): Boolean + fun supportsForwardedKeyWiththeld(): Boolean + /** * As per MSC3061. * If true will make it possible to share part of e2ee room history diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 9c52ef9da5..7d0126a32e 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.MessageVerificationState import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.events.model.Content @@ -77,6 +78,8 @@ import org.matrix.rustcomponents.sdk.crypto.MegolmV1BackupKey import org.matrix.rustcomponents.sdk.crypto.Request import org.matrix.rustcomponents.sdk.crypto.RequestType import org.matrix.rustcomponents.sdk.crypto.RoomKeyCounts +import org.matrix.rustcomponents.sdk.crypto.ShieldColor +import org.matrix.rustcomponents.sdk.crypto.ShieldState import org.matrix.rustcomponents.sdk.crypto.setLogger import timber.log.Timber import java.io.File @@ -180,7 +183,7 @@ internal class OlmMachine @Inject constructor( val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) { is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice() - else -> false + else -> false } return CryptoDeviceInfo( @@ -437,7 +440,7 @@ internal class OlmMachine @Inject constructor( throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) } val serializedEvent = adapter.toJson(event) - val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false) + val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false, false) val deserializationAdapter = moshi.adapter(Map::class.java) @@ -449,16 +452,20 @@ internal class OlmMachine @Inject constructor( senderCurve25519Key = decrypted.senderCurve25519Key, claimedEd25519Key = decrypted.claimedEd25519Key, forwardingCurve25519KeyChain = decrypted.forwardingCurve25519Chain, - messageVerificationState = decrypted.verificationState.fromInner(), + messageVerificationState = decrypted.shieldState.toVerificationState(), ) } catch (throwable: Throwable) { val reThrow = when (throwable) { is DecryptionException.MissingRoomKey -> { - MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, throwable.message.orEmpty()) + if (throwable.withheldCode != null) { + MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD, throwable.withheldCode!!) + } else { + MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, throwable.error) + } } is DecryptionException.Megolm -> { // TODO check if it's the correct binding? - MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, throwable.message.orEmpty()) + MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, throwable.error) } is DecryptionException.Identifier -> { MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON) @@ -480,6 +487,24 @@ internal class OlmMachine @Inject constructor( } } + private fun ShieldState.toVerificationState(): MessageVerificationState? { + return when (this.color) { + ShieldColor.GREEN -> MessageVerificationState.VERIFIED + ShieldColor.RED -> { + when (this.message) { + "Encrypted by an unverified device." -> MessageVerificationState.UN_SIGNED_DEVICE + "Encrypted by a device not verified by its owner." -> MessageVerificationState.UN_SIGNED_DEVICE_OF_VERIFIED_USER + "Encrypted by an unknown or deleted device." -> MessageVerificationState.UNKNOWN_DEVICE + else -> MessageVerificationState.UN_SIGNED_DEVICE + } + } + ShieldColor.GRAY -> { + MessageVerificationState.UNSAFE_SOURCE + } + ShieldColor.NONE -> null + } + } + /** * Request the room key that was used to encrypt the given undecrypted event. * diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index 268e217ace..64ce0fcc14 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -727,9 +727,13 @@ internal class RustCryptoService @Inject constructor( override fun supportsShareKeysOnInvite() = false + override fun supportsKeyWithheld() = true + override fun supportsForwardedKeyWiththeld() = false + + override fun enableShareKeyOnInvite(enable: Boolean) { - if (enable) { - TODO("Enable share key on invite not implemented") + if (enable && !supportsShareKeysOnInvite()) { + throw java.lang.UnsupportedOperationException("Enable share key on invite not implemented in rust"); } } @@ -872,10 +876,12 @@ internal class RustCryptoService @Inject constructor( } override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap { + // TODO not exposed in rust? return cryptoStore.getSharedWithInfo(roomId, sessionId) } override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? { + // TODO not exposed in rust. return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustExt.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustExt.kt deleted file mode 100644 index 0e4f9aeac5..0000000000 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustExt.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2023 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import org.matrix.android.sdk.api.session.crypto.model.MessageVerificationState -import org.matrix.rustcomponents.sdk.crypto.VerificationState as InnerVerificationState - -fun InnerVerificationState.fromInner(): MessageVerificationState { - return when (this) { - InnerVerificationState.VERIFIED -> MessageVerificationState.VERIFIED - InnerVerificationState.SIGNED_DEVICE_OF_UNVERIFIED_USER -> MessageVerificationState.SIGNED_DEVICE_OF_UNVERIFIED_USER - InnerVerificationState.UN_SIGNED_DEVICE_OF_VERIFIED_USER -> MessageVerificationState.UN_SIGNED_DEVICE_OF_VERIFIED_USER - InnerVerificationState.UN_SIGNED_DEVICE_OF_UNVERIFIED_USER -> MessageVerificationState.UN_SIGNED_DEVICE - InnerVerificationState.UNKNOWN_DEVICE -> MessageVerificationState.UNKNOWN_DEVICE - InnerVerificationState.UNSAFE_SOURCE -> MessageVerificationState.UNSAFE_SOURCE - } -}