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 {
|
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
||||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
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
|
var requestID: String? = null
|
||||||
// wait for it to be readied
|
// wait for it to be readied
|
||||||
|
|
|
@ -33,6 +33,10 @@ import org.junit.runner.RunWith
|
||||||
import org.junit.runners.JUnit4
|
import org.junit.runners.JUnit4
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
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.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
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.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.mustFail
|
import org.matrix.android.sdk.mustFail
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
// @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> {
|
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||||
return scope.async {
|
return scope.async {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
|
|
@ -128,4 +128,17 @@ object EventType {
|
||||||
type == CALL_REJECT ||
|
type == CALL_REJECT ||
|
||||||
type == CALL_REPLACES
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
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.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.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
|
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
|
||||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||||
|
@ -92,7 +94,18 @@ internal class MXMegolmEncryption(
|
||||||
): Content {
|
): Content {
|
||||||
val ts = clock.epochMillis()
|
val ts = clock.epochMillis()
|
||||||
Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
|
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).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()}")
|
Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
|
||||||
val outboundSession = ensureOutboundSession(devices.allowedDevices)
|
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) {
|
private fun notifyWithheldForSession(devices: MXUsersDevicesMap<WithHeldCode>, outboundSession: MXOutboundSessionInfo) {
|
||||||
// offload to computation thread
|
// offload to computation thread
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
|
||||||
|
@ -417,7 +435,7 @@ internal class MXMegolmEncryption(
|
||||||
*
|
*
|
||||||
* @param userIds the user ids whose devices must be checked.
|
* @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
|
// 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
|
// 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
|
// with them, which means that they will have announced any new devices via
|
||||||
|
@ -444,7 +462,7 @@ internal class MXMegolmEncryption(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
|
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly && !forceDistributeToUnverified) {
|
||||||
devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED)
|
devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue