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
oid sha256:c87e7bd9c5e7c623708958eebddbc58c26269b0c1d81e0dc3510a657a28a6515
size 20719656
oid sha256:c025a7047c3276b09f8cfaddc6323688b4c0174385148aa20f21080ba74d236d
size 32306804

View File

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

View File

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

View File

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

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.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<JsonDict>(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.
*

View File

@ -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<Int> {
// 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)
}

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