From 3b982456691ff37bad67625b5d5079bf67f4b526 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 3 Mar 2023 12:13:12 +0000 Subject: [PATCH 1/4] Support for MSC3906 v2 --- .../sdk/api/rendezvous/RendezvousTest.kt | 39 +++++++-- .../android/sdk/api/rendezvous/Rendezvous.kt | 86 ++++++++++++++++--- .../channels/ECDHRendezvousChannel.kt | 2 + .../rendezvous/model/ECDHRendezvousCode.kt | 1 + .../sdk/api/rendezvous/model/FailureReason.kt | 35 ++++++++ .../sdk/api/rendezvous/model/Outcome.kt | 3 + .../sdk/api/rendezvous/model/Payload.kt | 6 +- .../sdk/api/rendezvous/model/PayloadType.kt | 29 ++++++- .../api/rendezvous/model/RendezvousCode.kt | 1 + .../api/rendezvous/model/RendezvousFlow.kt | 30 +++++++ .../SimpleHttpRendezvousTransport.kt | 2 + 11 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/FailureReason.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt index 5b5aad4c511..a2a49494e7f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/rendezvous/RendezvousTest.kt @@ -25,27 +25,53 @@ import org.junit.Test import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousFlow import org.matrix.android.sdk.common.CommonTestHelper class RendezvousTest : InstrumentedTest { @Test - fun shouldSuccessfullyBuildChannels() = CommonTestHelper.runCryptoTest(context()) { _, _ -> + fun shouldSuccessfullyBuildMSC3906V1Channels() = CommonTestHelper.runCryptoTest(context()) { _, _ -> val cases = listOf( - // v1: + // MSC3903 v1 + MSC3906 v1: "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + "\"intent\":\"login.reciprocate\"}", - // v2: + // MSC3903 v2 + MSC3906 v1: "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256\"," + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + - "\"intent\":\"login.reciprocate\"}", + "\"intent\":\"login.reciprocate\"}" + ) + + cases.forEach { input -> + val rz = Rendezvous.buildChannelFromCode(input) + rz.channel shouldBeInstanceOf ECDHRendezvousChannel::class + rz.flow shouldBeEqualTo RendezvousFlow.SETUP_ADDITIONAL_DEVICE_V1 + } + } + + fun shouldSuccessfullyBuildMSC3906V2Channels() = CommonTestHelper.runCryptoTest(context()) { _, _ -> + val cases = listOf( + // MSC3903 v1: + "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," + + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + + "\"flow\":\"org.matrix.msc3906.setup.additional_device.v2\"," + + "\"intent\":\"login.reciprocate\"}", + // MSC3903 v2: + "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256\"," + + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + + "\"flow\":\"org.matrix.msc3906.setup.additional_device.v2\"," + + "\"intent\":\"login.reciprocate\"}" ) cases.forEach { input -> - Rendezvous.buildChannelFromCode(input).channel shouldBeInstanceOf ECDHRendezvousChannel::class + val rz = Rendezvous.buildChannelFromCode(input) + rz.channel shouldBeInstanceOf ECDHRendezvousChannel::class + rz.flow shouldBeEqualTo RendezvousFlow.SETUP_ADDITIONAL_DEVICE_V2 } } @@ -56,6 +82,7 @@ class RendezvousTest : InstrumentedTest { "{\"rendezvous\":{\"algorithm\":\"bad algo\"," + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + + "\"flow\":\"org.matrix.msc3906.setup.additional_device.v2\"," + "\"intent\":\"login.reciprocate\"}" ) } shouldThrow RendezvousError::class with { @@ -70,6 +97,7 @@ class RendezvousTest : InstrumentedTest { "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + "{\"type\":\"bad transport\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + + "\"flow\":\"org.matrix.msc3906.setup.additional_device.v2\"," + "\"intent\":\"login.reciprocate\"}" ) } shouldThrow RendezvousError::class with { @@ -84,6 +112,7 @@ class RendezvousTest : InstrumentedTest { "{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," + "\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" + "{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," + + "\"flow\":\"org.matrix.msc3906.setup.additional_device.v2\"," + "\"intent\":\"foo\"}" ) } shouldThrow RendezvousError::class with { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 91f3c2a5068..ef4cb2a8c16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -22,12 +22,14 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.FailureReason import org.matrix.android.sdk.api.rendezvous.model.Outcome import org.matrix.android.sdk.api.rendezvous.model.Payload import org.matrix.android.sdk.api.rendezvous.model.PayloadType import org.matrix.android.sdk.api.rendezvous.model.Protocol import org.matrix.android.sdk.api.rendezvous.model.RendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousFlow import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportType import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm @@ -42,11 +44,14 @@ import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber /** - * Implementation of MSC3906 to sign in + E2EE set up using a QR code. + * Implementation of MSC3906 to sign in + E2EE set up using a QR code: https://github.com/matrix-org/matrix-spec-proposals/pull/3906 + * + * @alpha This is an experimental API, and is subject to change until MSC3906 is stabilised and accepted. */ class Rendezvous( val channel: RendezvousChannel, val theirIntent: RendezvousIntent, + val flow: RendezvousFlow, ) { companion object { private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value @@ -61,6 +66,11 @@ class Rendezvous( throw RendezvousError("Malformed code", RendezvousFailureReason.InvalidCode) } ?: throw RendezvousError("Code is null", RendezvousFailureReason.InvalidCode) + // then we check that flow is supported + if (genericParsed.flow != null && !RendezvousFlow.values().map { it.value }.contains(genericParsed.flow)) { + throw RendezvousError("Unsupported flow", RendezvousFailureReason.UnsupportedAlgorithm) + } + // then we check that algorithm is supported if (!SecureRendezvousChannelAlgorithm.values().map { it.value }.contains(genericParsed.rendezvous.algorithm)) { throw RendezvousError("Unsupported algorithm", RendezvousFailureReason.UnsupportedAlgorithm) @@ -83,25 +93,33 @@ class Rendezvous( return Rendezvous( ECDHRendezvousChannel(transport, supportedParsed.rendezvous.algorithm, supportedParsed.rendezvous.key), - supportedParsed.intent + supportedParsed.intent, + // default to v1 if not specified: + supportedParsed.flow ?: RendezvousFlow.SETUP_ADDITIONAL_DEVICE_V1 ) } } private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java) - // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE - val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE + fun isUsingV1(): Boolean = flow == RendezvousFlow.SETUP_ADDITIONAL_DEVICE_V1 @Throws(RendezvousError::class) private suspend fun checkCompatibility() { + // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE + val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE + val incompatible = theirIntent == ourIntent - Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible") + Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible, flow: $flow") if (incompatible) { // inform the other side - send(Payload(PayloadType.FINISH, intent = ourIntent)) + if (isUsingV1()) { + send(Payload(PayloadType.FINISH, intent = ourIntent)) + } else { + send(Payload(PayloadType.FAILURE, intent = ourIntent)) + } if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn) } else { @@ -123,11 +141,19 @@ class Rendezvous( val protocolsResponse = receive() if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) { - send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED)) + if (isUsingV1()) { + send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED)) + } else { + send(Payload(PayloadType.FAILURE, reason = FailureReason.UNSUPPORTED)) + } throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver) } - send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN)) + if (isUsingV1()) { + send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN)) + } else { + send(Payload(PayloadType.PROTOCOL, protocol = Protocol.LOGIN_TOKEN)) + } return checksum } @@ -138,6 +164,7 @@ class Rendezvous( val loginToken = receive() + // v1: if (loginToken?.type == PayloadType.FINISH) { when (loginToken.outcome) { Outcome.DECLINED -> { @@ -152,6 +179,28 @@ class Rendezvous( } } + // v2: + if (loginToken?.type == PayloadType.DECLINED) { + throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined) + } + if (loginToken?.type == PayloadType.FAILURE) { + when (loginToken.reason) { + FailureReason.UNSUPPORTED -> { + throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver) + } + FailureReason.CANCELLED -> { + throw RendezvousError("Login cancelled by other device", RendezvousFailureReason.UserDeclined) + } + FailureReason.E2EE_SECURITY_ERROR -> { + throw RendezvousError("E2EE security error", RendezvousFailureReason.E2EESecurityIssue) + } + // incompatible intent shouldn't be received at this stage + else -> { + throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown) + } + } + } + val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError) val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError) @@ -167,8 +216,11 @@ class Rendezvous( val crypto = session.cryptoService() val deviceId = crypto.getMyCryptoDevice().deviceId val deviceKey = crypto.getMyCryptoDevice().fingerprint() - send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) - + if (isUsingV1()) { + send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) + } else { + send(Payload(PayloadType.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) + } try { // explicitly download keys for ourself rather than racing with initial sync which might not complete in time crypto.downloadKeysIfNeeded(listOf(userId), false) @@ -179,7 +231,7 @@ class Rendezvous( // await confirmation of verification val verificationResponse = receive() - if (verificationResponse?.outcome == Outcome.VERIFIED) { + if (verificationResponse?.outcome == Outcome.VERIFIED || verificationResponse?.type == PayloadType.VERIFIED) { val verifyingDeviceId = verificationResponse.verifyingDeviceId ?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError) val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) @@ -190,7 +242,11 @@ class Rendezvous( } vs ${verificationResponse.verifyingDeviceKey})" ) // inform the other side - send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + if (isUsingV1()) { + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + } else { + send(Payload(PayloadType.FAILURE, reason = FailureReason.E2EE_SECURITY_ERROR)) + } throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) } @@ -204,7 +260,11 @@ class Rendezvous( if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") // inform the other side - send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + if (isUsingV1()) { + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + } else { + send(Payload(PayloadType.FAILURE, reason = FailureReason.E2EE_SECURITY_ERROR)) + } throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index bcde4a2a7f4..d0797c5a7e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -40,6 +40,8 @@ import javax.crypto.spec.SecretKeySpec /** * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903: * https://github.com/matrix-org/matrix-spec-proposals/pull/3903 + * + * @alpha This is an experimental API, and is subject to change until MSC3903 is stabilised and accepted. */ class ECDHRendezvousChannel( override var transport: RendezvousTransport, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt index 575b5d4bfd3..91502c083f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ECDHRendezvousCode( val intent: RendezvousIntent, + val flow: RendezvousFlow?, val rendezvous: ECDHRendezvous ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/FailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/FailureReason.kt new file mode 100644 index 00000000000..dd4a8a0f673 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/FailureReason.kt @@ -0,0 +1,35 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class FailureReason(val value: String) { + @Json(name = "cancelled") + CANCELLED("cancelled"), + + @Json(name = "unsupported") + UNSUPPORTED("unsupported"), + + @Json(name = "e2ee_security_error") + E2EE_SECURITY_ERROR("e2ee_security_error"), + + @Json(name = "incompatible_intent") + INCOMPATIBLE_INTENT("incompatible_intent") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt index 0ebd1f88b34..54fa447f0e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt @@ -19,6 +19,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +/** + * This is only used for v1 of MSC3906 and will be removed in future. FailureReason is used in v2. + */ @JsonClass(generateAdapter = false) enum class Outcome(val value: String) { @Json(name = "success") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt index 04631ce9599..2bd8ae39234 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt @@ -23,7 +23,11 @@ import com.squareup.moshi.JsonClass internal data class Payload( val type: PayloadType, val intent: RendezvousIntent? = null, + /** + * This is only used in v1 of MSC3906 and will be removed in future. + */ val outcome: Outcome? = null, + val reason: FailureReason? = null, val protocols: List? = null, val protocol: Protocol? = null, val homeserver: String? = null, @@ -32,5 +36,5 @@ internal data class Payload( @Json(name = "device_key") val deviceKey: String? = null, @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null, @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null, - @Json(name = "master_key") val masterKey: String? = null + @Json(name = "master_key") val masterKey: String? = null, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt index 33beb1f5250..2a982406283 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -21,12 +21,33 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) internal enum class PayloadType(val value: String) { - @Json(name = "m.login.start") - START("m.login.start"), - + /** + * This is only used in v1 of MSC3906 and will be removed in future + */ @Json(name = "m.login.finish") FINISH("m.login.finish"), @Json(name = "m.login.progress") - PROGRESS("m.login.progress") + PROGRESS("m.login.progress"), + + @Json(name = "m.login.protocol") + PROTOCOL("m.login.protocol"), + + @Json(name = "m.login.protocols") + PROTOCOLS("m.login.protocols"), + + @Json(name = "m.login.approved") + APPROVED("m.login.approved"), + + @Json(name = "m.login.success") + SUCCESS("m.login.success"), + + @Json(name = "m.login.verified") + VERIFIED("m.login.verified"), + + @Json(name = "m.login.failure") + FAILURE("m.login.failure"), + + @Json(name = "m.login.declined") + DECLINED("m.login.declined"), } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt index ffa8bf66610..94441ed03a4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousCode.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) open class RendezvousCode( open val intent: RendezvousIntent, + open val flow: String?, open val rendezvous: Rendezvous ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt new file mode 100644 index 00000000000..a16ea9ec913 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt @@ -0,0 +1,30 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class RendezvousFlow(val value: String) { + /** + * v1 is never represented in JSON so we don't annotate it + */ + SETUP_ADDITIONAL_DEVICE_V1("org.matrix.msc3906.v1"), + @Json(name = "org.matrix.msc3906.setup.additional_device.v2") + SETUP_ADDITIONAL_DEVICE_V2("org.matrix.msc3906.setup.additional_device.v2"), +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 620b599e3df..d57b5d3e95a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -33,6 +33,8 @@ import java.util.Locale /** * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 + * + * @alpha This is an experimental API, and is subject to change until MSC3886 is stabilised and accepted. */ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport { companion object { From 49f740514a60c03be743b0b2fe5a724dfe576a6d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 6 Mar 2023 18:01:14 +0000 Subject: [PATCH 2/4] Changelog --- changelog.d/8212.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8212.feature diff --git a/changelog.d/8212.feature b/changelog.d/8212.feature new file mode 100644 index 00000000000..d4c4dae9c5c --- /dev/null +++ b/changelog.d/8212.feature @@ -0,0 +1 @@ +Updates to protocol used for Sign in with QR code From 8e416cdaee2b426e0f1b2bca1c361cb6d48b2ceb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 6 Mar 2023 18:07:28 +0000 Subject: [PATCH 3/4] doc fixes --- .../matrix/android/sdk/api/rendezvous/model/PayloadType.kt | 2 +- .../matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt index 2a982406283..a3b52248513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) internal enum class PayloadType(val value: String) { /** - * This is only used in v1 of MSC3906 and will be removed in future + * This is only used in v1 of MSC3906 and will be removed in future. */ @Json(name = "m.login.finish") FINISH("m.login.finish"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt index a16ea9ec913..14c5cfefa50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousFlow.kt @@ -21,9 +21,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) enum class RendezvousFlow(val value: String) { - /** - * v1 is never represented in JSON so we don't annotate it - */ + // v1 is never represented in JSON so we don't annotate it SETUP_ADDITIONAL_DEVICE_V1("org.matrix.msc3906.v1"), @Json(name = "org.matrix.msc3906.setup.additional_device.v2") SETUP_ADDITIONAL_DEVICE_V2("org.matrix.msc3906.setup.additional_device.v2"), From fb3b8aabf6ea80fbc13e60bd373602c4c582d3fc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 9 Mar 2023 13:56:42 -0500 Subject: [PATCH 4/4] Reduce complexity of function --- .../android/sdk/api/rendezvous/Rendezvous.kt | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index ef4cb2a8c16..9bf349522c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -234,65 +234,72 @@ class Rendezvous( if (verificationResponse?.outcome == Outcome.VERIFIED || verificationResponse?.type == PayloadType.VERIFIED) { val verifyingDeviceId = verificationResponse.verifyingDeviceId ?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError) - val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) - if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { - Timber.tag(TAG).w( - "Verifying device $verifyingDeviceId key doesn't match: ${ - verifyingDeviceFromServer?.fingerprint() - } vs ${verificationResponse.verifyingDeviceKey})" - ) + handleVerification(session, verifyingDeviceId, verificationResponse.verifyingDeviceKey, verificationResponse.masterKey) + } else { + Timber.tag(TAG).i("Not doing verification") + } + } + + @Throws(RendezvousError::class) + private suspend fun handleVerification(session: Session, verifyingDeviceId: String, verifyingDeviceKey: String?, masterKey: String?) { + var crypto = session.cryptoService() + var userId = session.myUserId + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verifyingDeviceKey) { + Timber.tag(TAG).w( + "Verifying device $verifyingDeviceId key doesn't match: ${ + verifyingDeviceFromServer?.fingerprint() + } vs $verifyingDeviceKey)" + ) + // inform the other side + if (isUsingV1()) { + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + } else { + send(Payload(PayloadType.FAILURE, reason = FailureReason.E2EE_SECURITY_ERROR)) + } + throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } + + masterKey?.let { masterKeyFromVerifyingDevice -> + // verifying device provided us with a master key, so use it to check integrity + + // see what the homeserver told us + val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey() + + // n.b. if no local master key this is a problem, as well as it not matching + if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { + Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") // inform the other side if (isUsingV1()) { send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) } else { send(Payload(PayloadType.FAILURE, reason = FailureReason.E2EE_SECURITY_ERROR)) } - throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) } - verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice -> - // verifying device provided us with a master key, so use it to check integrity - - // see what the homeserver told us - val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey() - - // n.b. if no local master key this is a problem, as well as it not matching - if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { - Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") - // inform the other side - if (isUsingV1()) { - send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) - } else { - send(Payload(PayloadType.FAILURE, reason = FailureReason.E2EE_SECURITY_ERROR)) - } - throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) - } - - // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - Timber.tag(TAG).i("Setting master key as trusted") - crypto.crossSigningService().markMyMasterKeyAsTrusted() - } ?: run { - // set other device as verified anyway - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + Timber.tag(TAG).i("Setting master key as trusted") + crypto.crossSigningService().markMyMasterKeyAsTrusted() + } ?: run { + // set other device as verified anyway + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - Timber.tag(TAG).i("No master key given by verifying device") - } + Timber.tag(TAG).i("No master key given by verifying device") + } - // request secrets from the verifying device - Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") - session.sharedSecretStorageService().let { - it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) - } - } else { - Timber.tag(TAG).i("Not doing verification") + session.sharedSecretStorageService().let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) } }