E2ee dos not hinder verification
This commit is contained in:
parent
0b7e52e60b
commit
37458d41f2
|
@ -0,0 +1 @@
|
|||
Can't verify user when option to send keys to verified devices only is selected
|
|
@ -313,7 +313,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
}
|
||||
bobVerificationService.readyPendingVerification(listOf(VerificationMethod.SAS), alice.myUserId, incomingRequest.transactionId!!)
|
||||
bobVerificationService.readyPendingVerificationInDMs(listOf(VerificationMethod.SAS), alice.myUserId, roomId, incomingRequest.transactionId!!)
|
||||
|
||||
var requestID: String? = null
|
||||
// wait for it to be readied
|
||||
|
|
|
@ -33,6 +33,10 @@ import org.junit.runner.RunWith
|
|||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
|
@ -61,7 +65,10 @@ import org.matrix.android.sdk.common.CommonTestHelper
|
|||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.mustFail
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
||||
|
@ -607,6 +614,85 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceAuthParams = UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
val bobAuthParams = UserPasswordAuth(
|
||||
user = bobSession!!.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.waitForCallback {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
testHelper.waitForCallback {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
// add a second session for bob but not cross signed
|
||||
|
||||
val secondBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||
|
||||
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
|
||||
|
||||
// The two bob session should not be able to decrypt any message
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!!
|
||||
Timber.v("#TEST: Send a first message that should be withheld")
|
||||
val sentEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "Hello")!!
|
||||
|
||||
// wait for it to be synced back the other side
|
||||
Timber.v("#TEST: Wait for message to be synced back")
|
||||
testHelper.retryPeriodically {
|
||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||
}
|
||||
|
||||
// bob should not be able to decrypt
|
||||
Timber.v("#TEST: Ensure cannot be decrytped")
|
||||
cryptoTestHelper.ensureCannotDecrypt(listOf(sentEvent), bobSession, cryptoTestData.roomId)
|
||||
cryptoTestHelper.ensureCannotDecrypt(listOf(sentEvent), secondBobSession, cryptoTestData.roomId)
|
||||
|
||||
// let's try to verify, it should work even if bob devices are untrusted
|
||||
Timber.v("#TEST: Do the verification")
|
||||
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
|
||||
|
||||
Timber.v("#TEST: Send a second message, outbound session should have rotated and only bob 1rst session should decrypt")
|
||||
|
||||
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
|
||||
Timber.v("#TEST: Wait for message to be synced back")
|
||||
testHelper.retryPeriodically {
|
||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCanDecrypt(listOf(secondEvent), bobSession, cryptoTestData.roomId, listOf("World"))
|
||||
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
|
||||
}
|
||||
|
||||
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
return scope.async {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
|
|
|
@ -128,4 +128,17 @@ object EventType {
|
|||
type == CALL_REJECT ||
|
||||
type == CALL_REPLACES
|
||||
}
|
||||
|
||||
fun isVerificationEvent(type: String): Boolean {
|
||||
return when (type) {
|
||||
KEY_VERIFICATION_START,
|
||||
KEY_VERIFICATION_ACCEPT,
|
||||
KEY_VERIFICATION_KEY,
|
||||
KEY_VERIFICATION_MAC,
|
||||
KEY_VERIFICATION_CANCEL,
|
||||
KEY_VERIFICATION_DONE,
|
||||
KEY_VERIFICATION_READY -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
|||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
|
||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||
|
@ -92,7 +94,18 @@ internal class MXMegolmEncryption(
|
|||
): Content {
|
||||
val ts = clock.epochMillis()
|
||||
Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
|
||||
val devices = getDevicesInRoom(userIds)
|
||||
|
||||
/**
|
||||
* When using in-room messages and the room has encryption enabled,
|
||||
* clients should ensure that encryption does not hinder the verification.
|
||||
* For example, if the verification messages are encrypted, clients must ensure that all the recipient’s
|
||||
* unverified devices receive the keys necessary to decrypt the messages,
|
||||
* even if they would normally not be given the keys to decrypt messages in the room.
|
||||
*/
|
||||
val shouldSendToUnverified = isVerificationEvent(eventType, eventContent)
|
||||
|
||||
val devices = getDevicesInRoom(userIds, forceDistributeToUnverified = shouldSendToUnverified)
|
||||
|
||||
Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
|
||||
Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
|
||||
val outboundSession = ensureOutboundSession(devices.allowedDevices)
|
||||
|
@ -107,6 +120,11 @@ internal class MXMegolmEncryption(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isVerificationEvent(eventType: String, eventContent: Content) =
|
||||
EventType.isVerificationEvent(eventType) ||
|
||||
(eventType == EventType.MESSAGE &&
|
||||
eventContent.get(MessageContent.MSG_TYPE_JSON_KEY) == MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||
|
||||
private fun notifyWithheldForSession(devices: MXUsersDevicesMap<WithHeldCode>, outboundSession: MXOutboundSessionInfo) {
|
||||
// offload to computation thread
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
||||
|
@ -417,7 +435,7 @@ internal class MXMegolmEncryption(
|
|||
*
|
||||
* @param userIds the user ids whose devices must be checked.
|
||||
*/
|
||||
private suspend fun getDevicesInRoom(userIds: List<String>): DeviceInRoomInfo {
|
||||
private suspend fun getDevicesInRoom(userIds: List<String>, forceDistributeToUnverified: Boolean = false): DeviceInRoomInfo {
|
||||
// We are happy to use a cached version here: we assume that if we already
|
||||
// have a list of the user's devices, then we already share an e2e room
|
||||
// with them, which means that they will have announced any new devices via
|
||||
|
@ -444,7 +462,7 @@ internal class MXMegolmEncryption(
|
|||
continue
|
||||
}
|
||||
|
||||
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
|
||||
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly && !forceDistributeToUnverified) {
|
||||
devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED)
|
||||
continue
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue