Test integration of rust shield states

This commit is contained in:
valere 2023-03-10 10:30:37 +01:00
parent 5f185c51e7
commit 238d10d4cb
7 changed files with 102 additions and 71 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c87e7bd9c5e7c623708958eebddbc58c26269b0c1d81e0dc3510a657a28a6515 oid sha256:c025a7047c3276b09f8cfaddc6323688b4c0174385148aa20f21080ba74d236d
size 20719656 size 32306804

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
@ -98,32 +99,34 @@ class E2eeConfigTest : InstrumentedTest {
val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! 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)!! val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!!
// ensure other received // ensure other received
testHelper.retryPeriodically { Log.v("#E2E TEST", "Wait for bob to get the message")
roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null testHelper.ensureMessage(roomBobPOV, beforeMessage) { true }
}
Log.v("#E2E TEST", "ensure bob Can Decrypt first message")
cryptoTestHelper.ensureCanDecrypt( cryptoTestHelper.ensureCanDecrypt(
listOf(beforeMessage.eventId), listOf(beforeMessage),
cryptoTestData.secondSession!!, cryptoTestData.secondSession!!,
cryptoTestData.roomId, cryptoTestData.roomId,
listOf(beforeMessage.getLastMessageContent()!!.body) listOf("you can read")
) )
Log.v("#E2E TEST", "setRoomBlockUnverifiedDevices true")
cryptoTestData.firstSession.cryptoService().setRoomBlockUnverifiedDevices(cryptoTestData.roomId, 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 // 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( cryptoTestHelper.ensureCannotDecrypt(
listOf(afterMessage.eventId), listOf(afterMessage),
cryptoTestData.secondSession!!, cryptoTestData.secondSession!!,
cryptoTestData.roomId, cryptoTestData.roomId,
MXCryptoError.ErrorType.KEYS_WITHHELD MXCryptoError.ErrorType.KEYS_WITHHELD

View File

@ -81,7 +81,7 @@ class WithHeldTests : InstrumentedTest {
val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first() val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
// await for bob unverified session to get the message // await for bob unverified session to get the message
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null
} }
@ -106,24 +106,26 @@ class WithHeldTests : InstrumentedTest {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
} }
// Let's see if the reply we got from bob first session is unverified if (bobUnverifiedSession.cryptoService().supportsForwardedKeyWiththeld()) {
testHelper.retryPeriodically { // Let's see if the reply we got from bob first session is unverified
bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests() testHelper.retryWithBackoff {
.firstOrNull { it.sessionId == megolmSessionId } bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests()
?.results .firstOrNull { it.sessionId == megolmSessionId }
?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId } ?.results
?.result ?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId }
?.let { ?.result
it as? RequestResult.Failure ?.let {
} it as? RequestResult.Failure
?.code == WithHeldCode.UNVERIFIED }
?.code == WithHeldCode.UNVERIFIED
}
} }
// enable back sending to unverified // enable back sending to unverified
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false) aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first() val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId) val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId)
// wait until it's decrypted // wait until it's decrypted
ev?.root?.getClearType() == EventType.MESSAGE ev?.root?.getClearType() == EventType.MESSAGE
@ -234,9 +236,19 @@ class WithHeldTests : InstrumentedTest {
// initialize to force request keys if missing // initialize to force request keys if missing
cryptoTestHelper.initializeCrossSigning(bobSecondSession) cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV // // wait until alice downloaded the new device
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId) // testHelper.retryWithBackoff {
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId) // 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 var sessionId: String? = null
// Check that the // Check that the
@ -252,10 +264,23 @@ class WithHeldTests : InstrumentedTest {
timeLineEvent != null timeLineEvent != null
} }
// Check that bob second session requested the key
testHelper.retryPeriodically { mustFail(
val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) message = "This session should not be able to decrypt",
wc?.code == WithHeldCode.UNAUTHORISED 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
// }
} }
} }

View File

@ -94,6 +94,9 @@ interface CryptoService {
*/ */
fun supportsShareKeysOnInvite(): Boolean fun supportsShareKeysOnInvite(): Boolean
fun supportsKeyWithheld(): Boolean
fun supportsForwardedKeyWiththeld(): Boolean
/** /**
* As per MSC3061. * As per MSC3061.
* If true will make it possible to share part of e2ee room history * If true will make it possible to share part of e2ee room history

View File

@ -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.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult 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.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.model.UnsignedDeviceInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.events.model.Content 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.Request
import org.matrix.rustcomponents.sdk.crypto.RequestType import org.matrix.rustcomponents.sdk.crypto.RequestType
import org.matrix.rustcomponents.sdk.crypto.RoomKeyCounts 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 org.matrix.rustcomponents.sdk.crypto.setLogger
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -180,7 +183,7 @@ internal class OlmMachine @Inject constructor(
val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) { val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) {
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice() is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
else -> false else -> false
} }
return CryptoDeviceInfo( return CryptoDeviceInfo(
@ -437,7 +440,7 @@ internal class OlmMachine @Inject constructor(
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
} }
val serializedEvent = adapter.toJson(event) val serializedEvent = adapter.toJson(event)
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false) val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false, false)
val deserializationAdapter = val deserializationAdapter =
moshi.adapter<JsonDict>(Map::class.java) moshi.adapter<JsonDict>(Map::class.java)
@ -449,16 +452,20 @@ internal class OlmMachine @Inject constructor(
senderCurve25519Key = decrypted.senderCurve25519Key, senderCurve25519Key = decrypted.senderCurve25519Key,
claimedEd25519Key = decrypted.claimedEd25519Key, claimedEd25519Key = decrypted.claimedEd25519Key,
forwardingCurve25519KeyChain = decrypted.forwardingCurve25519Chain, forwardingCurve25519KeyChain = decrypted.forwardingCurve25519Chain,
messageVerificationState = decrypted.verificationState.fromInner(), messageVerificationState = decrypted.shieldState.toVerificationState(),
) )
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
val reThrow = when (throwable) { val reThrow = when (throwable) {
is DecryptionException.MissingRoomKey -> { 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 -> { is DecryptionException.Megolm -> {
// TODO check if it's the correct binding? // 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 -> { is DecryptionException.Identifier -> {
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON) 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. * Request the room key that was used to encrypt the given undecrypted event.
* *

View File

@ -727,9 +727,13 @@ internal class RustCryptoService @Inject constructor(
override fun supportsShareKeysOnInvite() = false override fun supportsShareKeysOnInvite() = false
override fun supportsKeyWithheld() = true
override fun supportsForwardedKeyWiththeld() = false
override fun enableShareKeyOnInvite(enable: Boolean) { override fun enableShareKeyOnInvite(enable: Boolean) {
if (enable) { if (enable && !supportsShareKeysOnInvite()) {
TODO("Enable share key on invite not implemented") 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<Int> { override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> {
// TODO not exposed in rust?
return cryptoStore.getSharedWithInfo(roomId, sessionId) return cryptoStore.getSharedWithInfo(roomId, sessionId)
} }
override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? { override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? {
// TODO not exposed in rust.
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
} }

View File

@ -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
}
}