From 6e42f10bccddd19901fe859dd4b95c7fda04967a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 22 Feb 2024 09:38:21 +0000 Subject: [PATCH 01/70] Placeholder --- proposals/xxxx-oidc-qr-login.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 proposals/xxxx-oidc-qr-login.md diff --git a/proposals/xxxx-oidc-qr-login.md b/proposals/xxxx-oidc-qr-login.md new file mode 100644 index 00000000000..d9fdc93543c --- /dev/null +++ b/proposals/xxxx-oidc-qr-login.md @@ -0,0 +1,13 @@ +# MSC0000: Mechanism to allow OIDC sign in and E2EE set up via QR code + +## Proposal + +## Potential issues + +## Alternatives + +## Security considerations + +## Unstable prefix + +## Dependencies From d90eda1529c867b0acc6ef7adda02d9e0267a807 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 22 Feb 2024 09:40:21 +0000 Subject: [PATCH 02/70] MSC4108 --- proposals/{xxxx-oidc-qr-login.md => 4108-oidc-qr-login.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename proposals/{xxxx-oidc-qr-login.md => 4108-oidc-qr-login.md} (65%) diff --git a/proposals/xxxx-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md similarity index 65% rename from proposals/xxxx-oidc-qr-login.md rename to proposals/4108-oidc-qr-login.md index d9fdc93543c..c32c61a6696 100644 --- a/proposals/xxxx-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1,4 +1,4 @@ -# MSC0000: Mechanism to allow OIDC sign in and E2EE set up via QR code +# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code ## Proposal From f7bbba399123cf537358d1a6e01e63b00064d647 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Apr 2024 16:55:44 +0100 Subject: [PATCH 03/70] WIP of MSC4108 --- proposals/4108-oidc-qr-login.md | 1166 +++++++++++++++++++++++++++++++ 1 file changed, 1166 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index c32c61a6696..f72eb8cf3a4 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1,13 +1,1179 @@ # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code +We propose a method to allow an existing authenticated Matrix client to sign in a new client by scanning a QR code. The +new client will be a fully bootstrapped Matrix cryptographic device, possessing all the necessary secrets, namely the +cryptographic user identity ("cross-signing") and the server-side key backup decryption key (if used). + +This MSC supersedes [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906), +[MSC3903](https://github.com/matrix-org/matrix-spec-proposals/pull/3903) and +[MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886) which achieved a similar feature but did not +work with a homeserver using the delegated OIDC mechanism proposed by [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861). + ## Proposal +Depending on the pair of devices used, it may be preferable to scan the QR code on either the new or existing device, +based on the availability of a camera. As such, this proposal allows for the generation of the QR on either device. + +In order for the new device to be fully set up, it needs to exchange information with an existing device such that: + +- The new device knows which homeserver to use +- The existing device can facilitate the new device in getting an access token +- The existing device shares the secrets necessary to set up end-to-end encryption + +### Insecure rendezvous session + +It is proposed that an HTTP-based protocol be used to establish an ephemeral bi-directional communication session over +which the two devices can exchange the necessary data. This session is described as "insecure" as it provides no +end-to-end confidentiality nor authenticity by itself—these are layered on top of it. + +#### High-level description + +TODO + +#### The send mechanism + +Every send request MUST include an `ETag` header, whose value is supplied by the `ETag` header in the last `GET` +response seen by the requester. (The initiating device may also use the `ETag` supplied in the initial `POST` response +to immediately update the payload.) Updates will succeed only if the supplied `ETag` matches the server's current +revision of the payload. This prevents concurrent writes to the payload. + +The `ETag` header is standard, described by [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-etag). In this +proposal we only accept strong, single-valued `ETag` values; anything else constitutes a malformed request. + +n.b. Once a new payload has been sent there is no mechanism to retrieve previous payloads. + +#### Expiry + +The rendezvous session (i.e. the payload) SHOULD expire after a period of time communicated to clients via the `Expires` +header. After this point, any further attempts to query or update the payload MUST fail. The expiry time SHOULD be +extended every time the payload is updated. The rendezvous session can be manually expired with a `DELETE` call to the +rendezvous session. + +#### Threat analysis + +##### Denial of Service attack surface + +Because the rendezvous session protocol allows for the creation of arbitrary channels and storage of arbitrary data, it +is possible to use it as a denial of service attack surface. + +As such, the following standard mitigations such as the following may be deemed appropriate by homeserver +implementations and administrators: + +- rate limiting of requests +- imposing a low maximum payload size (e.g. kilobytes not megabytes) +- limiting the number of concurrent sessions + +##### Data exfiltration + +Because the rendezvous session protocol allows for the storage of arbitrary data, it +is possible to use it to circumvent firewalls and other network security measures. + +Implementation may want to block their production IP addresses from being able to make requests to the rendezvous +endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. + +#### API + +TODO + +### Secure channel + +The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or +even arbitrary network participants which possess the rendezvous session server and ID. To provide a secure channel on +top of this insecure rendezvous session transport, we propose the following scheme. + +This scheme is essentially[ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) +instantiated with X25519, HKDF-SHA256 for the KDF and ChaCha20-Poly1305 (as specified by +[RFC8439](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8)) for the authenticated encryption. Therefore, +existing security analyses of ECIES are applicable in this setting too. Nevertheless we include below a short +description of our instantiation of ECIES and discuss some potential pitfalls and attacks. + +The primary limitation of ECIES is that there is no authentication for the initiating party (the one to send the first +message; Device S in the text below). Thus the recipient party (the one to receive the first message; Device G in the +text below) has no assurance as to who actually sent the message. In QR code login, we work around this problem by +exploiting the fact that both of these devices are physically present during the exchange and offloading the check that +they are both in the correct state to the user performing the QR code login process. + +#### Establishment + +Participants: + +- Device G (the device generating the QR code) +- Device S (the device scanning the QR code) + +Regardless of which device generates the QR code, either device can be the existing (already signed in) device. The +other device is then the new device (one seeking to be signed in). + +Symmetric encryption uses deterministic nonces, incrementing by `2` with each message. Device S starts with `0`, using only +even nonces. Device A starts with `1`, using only odd nonces. + +1. **Ephemeral key pair generation** + + Both devices generate an _ephemeral_ Curve25519 key pair: + +- Device G generates **(Gp, Gs)**, where **Gp** is its public key and **Gs** the private (secret) key. +- Device S generates **(Sp, Ss)**, where **Sp** is its public key and **Ss** the private (secret) key. + +1. **Create rendezvous session** + +Device G creates a rendezvous session by making a `POST` request (as described previously) to the nominated homeserver +with an empty payload. It parses the **id** and **server** received. + +1. **Initial key exchange** + +Device G displays a QR code containing: + +- its public key **Gp** +- the insecure rendezvous session **URL** +- An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device +that wishes to "reciprocate" a login +- If the intent is to reciprocate a login, then the **homeserver base URL** + +To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a +similar structure to that of the existing Device Verification QR code encoding described in [Client-Server API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). + +This is defined in detail in a separate section of this proposal. + +Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the +**homeserver base URL**. + +At this point Device S should check that the received intent matches what the user has asked to do on the device. + +1. **Device S sends the initial message** + +Device S computes a shared secret **SH** using ECDH between **Ss** and **Gp**, thereby establishing a secure channel +with Device G which can be layered on top of the insecure rendezvous session transport. It then discards **Ss** and +derives a symmetric encryption **EncKey** from **SH** using HKDF_SHA256, each 32 bytes in length. + +Device S derives a confirmation message that Device G can use to confirm that the channel is secure. It contains: + +- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. +- Its public ephemeral key **Sp**. + +``` +Nonce := 0 +SH := ECDH(Ss, Gp) +EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN|" || Gp || "|" || Sp, salt=0, size=32) +NonceBytes := ToLowEndianBytes(Nonce)[..12] +TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_INITIATE") +Nonce := Nonce + 2 +LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp) +``` + +Device S then sends the **LoginInitiateMessage** as the payload to the rendezvous session using a `PUT` request with +`Content-Type` header set to `text/plain`. + +1. **Device G confirms** + +Device G receives **LoginInitiateMessage** (potentially coming from Device S) from the insecure rendezvous session by +polling with `GET` requests. + +It then does the reverse of the previous step, obtaining **Sp**, deriving the shared secret using **Gs** and **Sp**, +discarding **Gs** and decrypting (and authenticating) the **TaggedCiphertext**, obtaining a plaintext. + +It checks that the plaintext matches the string `MATRIX_QR_CODE_LOGIN_INITIATE`, failing and aborting if not. + +It then responds with a dummy message containing the string `MATRIX_QR_CODE_LOGIN_OK` encrypted with **SH** calculated +as follows: + +``` +Nonce := 1 +NonceBytes := ToLowEndianBytes(Nonce)[..12] +TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_OK") +Nonce := Nonce + 2 +LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) +``` + +Device G sends **LoginOkMessage** as the payload via `PUT` request with `Content-Type` header set to `text/plain` to the +insecure rendezvous session. + +1. **Verification by Device S** + +Device S receives a response over the insecure rendezvous session by polling with `GET` requests, potentially from +Device G. + +It decrypts (and authenticates) it using the previously computed encryption key, which will succeed provided the message +was indeed sent by Device G. It then verifies the plaintext matches `MATRIX_QR_CODE_LOGIN_OK`, failing otherwise. + +``` +Nonce_G := 1 +(TaggedCiphertext, Sp) := Unpack(Message) +NonceBytes := ToLowEndianBytes(Nonce)[..12] +Plaintext := ChaCha20Poly1305_Decrypt(EncKey, NonceBytes, TaggedCiphertext) +Nonce_G := Nonce_G + 2 + +unless Plaintext == "MATRIX_QR_CODE_LOGIN_OK": + FAIL +``` + +If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and **Sp**: + +``` +CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2) +CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) +``` + +Device S then displays an indication to the user that the secure channel has been established and that the **CheckCode** +should be entered on the other device when prompted. e.g. wording to say "secure connection established"; enter the code +XY on your other device; + +1. **Out-of-band confirmation** + +**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of ECIES.* + +Device G asks the user to enter the **CheckCode** that is being displayed on Device S. + +The purpose of the code being entered is to ensure that the user has actually checked their other device rather than +just pressing "continue", and that the Device S has been able to determine that the channel is secure. + +Device G compares the code that the user has entered with the **CheckCode** that it calculates using the same mechanism +as before: + +``` +CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2) +CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) +``` + +If the code that the user enters matches then the secure channel is established. + +Subsequent messages can be sent encrypted with **EncKey** with the nonces incremented as described above. + +#### Sequence diagram + +The sequence diagram for the above is as follows: + +```mermaid +sequenceDiagram + participant G as Device G + participant Z as Rendezvous server + participant S as Device S + + note over G,S: 1) Devices G and S each generate an ephemeral Curve25519 key pair + + activate G + note over G: 2) Device G creates a rendezvous session as follows + G->>+Z: POST /_matrix/client/rendezvous + Z->>-G: 201 Created
Location: /_matrix/client/rendezvous/abc-def
ETag: 1 + + note over G: 3) Device G generates and displays a QR code containing
its ephemeral public key and the rendezvous session URL + + G-->>S: Device S scans the QR code shown by Device G + deactivate G + + activate S + note over S: Device S validates QR scanned and the rendezvous session URL + + S->>+Z: GET /_matrix/client/rendezvous/abc-def + Z->>-S: 200 OK
ETag: 1 + + note over S: 4) Device S computes SH, EncKey and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session + S->>+Z: PUT /_matrix/client/rendezvous/abc-def
If-Match: 1
Body: LoginInitiateMessage + Z->>-S: 202 Accepted
ETag: 2 + deactivate S + + G->>+Z: GET /_matrix/client/rendezvous/abc-def
If-None-Match: 1 + activate G + Z->>-G: 200 OK
ETag: 2
Body: Data + + note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH and EncKey + note over G: Device G checks that the plaintext matches MATRIX_QR_CODE_LOGIN_INITIATE + + note over G: Device G computes LoginOkMessage and sends to the rendezvous session + + G->>+Z: PUT /_matrix/client/rendezvous/abc-def
If-Match: 2
Body: LoginOkMessage + Z->>-G: 202 Accepted
ETag: 3 + deactivate G + + activate S + S->>+Z: GET /_matrix/client/rendezvous/abc-def
If-None-Match: 2 + Z->>-S: 200 OK
ETag: 3
Body: Data + + note over S: 6) Device S attempts to parse Data as LoginOkMessage + note over S: Device S checks that the plaintext matches MATRIX_QR_CODE_LOGIN_OK + + note over S: If okay, Device S calculates the CheckCode to be displayed + note over S: Device S displays a green checkmark, "secure connection established" and the CheckCode + note over S: Device S knows that the channel is secure + deactivate S + + note over G: 7) Device G asks the user to confirm that the other device is showing a green checkmark and enter the CheckCode + note over G: If the user enters the correct CheckCode and confirms that a green checkmark is shown then Device G knows that the channel is secure +``` + +#### Secure operations + +Conceptually, once established, the secure channel offers two operations, `SecureSend` and `SecureReceive`, which wrap +the `Send` and `Receive` operations offered by the rendezvous session API to securely send and receive data between two devices. + +At the end of the establishment phase, the next nonce for Device G should be `3` and the next nonce for Device S should +be `2`. + +Device G sets: + +``` +Nonce := 3 +NonceOther = 2 +``` + +Device S sets: + +``` +Nonce := 2 +NonceOther := 3 +``` + +#### Threat analysis + +In an attack scenario, we add a participant called Specter with the following capabilities: + +- Specter is present for QR code generation/scanning ("shoulder-surfing") and can scan the code themselves. +- Specter has full control over the network (in a Dolev-Yao sense), being able to observe and modify all traffic. +- Specter controls both the homeserver and the rendezvous server. + +##### Replay protection + +Due to use of ephemeral key pairs which are immediately discarded after use, each QR code login session derives a unique +secret so messages from earlier sessions cannot be replayed. Each message in the session is unique and expected only +once. Finally, the use of deterministic nonces prevents any possibility of replay. + +##### Pure Dolev-Yao attacker + +An attacker with control over the network but _not_ present for the QR code scanning cannot thwart the process since +they are unable to obtain the ephemeral key **Gp** of Device G. + +##### Shoulder-surfing attacker (Specter) + +Since Device G has no way of authenticating Device S, an attacker present for the QR code scanning can learn **Gp** and +attempt to mimic Device S in order to get their Device S signed in instead. + +- In step 3, Specter can shoulder surf the QR code scanning to obtain **Gp**. +- In step 4, Specter can intercept S’s message and replace it with a message of their own, replacing **Sp** with its +own key. +- The attack is only thwarted in step 7, because Device S won’t ever display the indicator of success to the user. The +user then must cancel the process on Device G, preventing it from sharing any sensitive material. + +### The OIDC login part and set up of E2EE + +Once the secure channel has been established, the two devices can then communicate securely. + +#### Login via OIDC Device Authorization Grant + +In this section the sequence of steps depends on whether the new device generated or scanned the QR code. + +For example, in the case that the new device scanned the QR code it is the first to do a `SecureSend` whereas if the new +device generated the QR then the existing device is the first to do a `SecureSend`. + +This can make it hard to read what is going on. + +1. **Homeserver discovery** + +The new device needs to know which homeserver it will be authenticating with. + +In the case that the new device scanned the QR code then the homeserver base URL can be taken from the QR code and the +new device proceeds to step 2 immediately. + +Otherwise the new device waits to be informed by receiving an `m.login.protocols` message from the existing device. + +The existing device would need to determine which "protocols" are available for the new device to use. + +Currently this could only be device_authorization_grant meaning the OIDC Provider supports the +`urn:ietf:params:oauth:grant-type:device_code` grant type. + +If it is available then the existing device informs the new device by sending the `m.login.protocols` message with the +homeserver specified: + +*Existing device => New device via secure channel* + +```json +{ + "type": "m.login.protocols", + "protocols": ["device_authorization_grant"], + "homeserver": "https://synapse-oidc.lab.element.dev" +} +``` + +1. **New device checks if it can use an available protocol** + +Once the existing device knows which homeserver it is to use it then: + +- checks that the homeserver is using delegated OIDC by calling `GET /_matrix/client/v1/auth_issuer` from [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965): + +*New device => Homeserver via HTTP* + +```http +GET /_matrix/client/v1/auth_issuer HTTP/1.1 +Host: synapse-oidc.lab.element.dev +Accept: application/json +``` + +With response like: + +```http +200 OK +Content-Type: application/json + +{ + "issuer": "https://auth-oidc.lab.element.dev/" +} +``` + +- parses the OIDC Provider (`issuer`) from the response +- fetches the OIDC Provider metadata as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965): + +*New device => OIDC Provider via HTTP* + +```http +GET /.well-known/openid-configuration HTTP/1.1 +Host: auth-oidc.lab.element.dev +Accept: application/json +``` + +With response like: + +```http +200 OK +Content-Type: application/json + +{ + "issuer": "https://auth-oidc.lab.element.dev/", + "authorization_endpoint": "https://auth-oidc.lab.element.dev/authorize", + "token_endpoint": "https://auth-oidc.lab.element.dev/oauth2/token", + "jwks_uri": "https://auth-oidc.lab.element.dev/oauth2/keys.json", + "registration_endpoint": "https://auth-oidc.lab.element.dev/oauth2/registration", + "scopes_supported": ["openid", "email"], + "response_types_supported": [...], + "response_modes_supported": [...], + "grant_types_supported": [ + "authorization_code", + "refresh_token", + "client_credentials", + "urn:ietf:params:oauth:grant-type:device_code" + ], + ... + "device_authorization_endpoint": "https://auth-oidc.lab.element.dev/oauth2/device" +} +``` + +- either does Dynamic Client Registration as per [MSC2966](https://github.com/matrix-org/matrix-spec-proposals/pull/2966) +or uses a static OIDC client_id. We will use `my_client_id` as an example `client_id`. + +- sends a [RFC8628 Device Authorization Request](https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) to the OIDC +Provider using the `device_authorization_endpoint`: + +*New device => OIDC Provider via HTTP* + +```http +POST /oauth2/device HTTP/1.1 +Host: auth-oidc.lab.element.dev +Content-Type: application/x-www-form-urlencoded + +client_id=my_client_id&scope=openid%20urn%3Amatrix%3Aclient%3Aapi%3A%2A%20urn%3Amatrix%3Aclient%3Adevice%3AABCDEGH +``` + +With response like: + +```http +200 OK +Content-Type: application/json + +{ + "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + "user_code": "123456", + "verification_uri": "https://auth-oidc.lab.element.dev/link", + "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456", + "expires_in": 1800, + "interval": 5 +} +``` + +- parses the [Device Authorization Response](https://datatracker.ietf.org/doc/html/rfc8628#section-3.2) above + +At this point the new device knows that, subject to the user consenting, it should be able to complete the login + +1. **New device informs existing device that it wants to use the device_authorization_grant** + +The new device send the `verification_uri` and, if present, the `verification_uri_complete` over to the existing device and +indicates that want to use protocol `device_authorization_grant` along with the `device_id` that will be used: + +*New device => Existing device via secure channel* + +```json +{ + "type": "m.login.protocol", + "protocol": "device_authorization_grant", + "device_authorization_grant": { + "verification_uri": "https://auth-oidc.lab.element.dev/link", + "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" + }, + "device_id": "ABCDEFGH" +} +``` + +The sequence for steps 1 to 3 is as follows: (the sequence depending on which device has scanned the code varies for readability) + +_New device scanned QR code:_ + +```mermaid +sequenceDiagram + title: Variant: New device scanned QR code + participant E as Existing device
already signed in + participant Z as Rendezvous server + participant N as New device
wanting to sign in + participant OP as OIDC Provider + participant HS as Homeserver + + + rect rgba(255,0,0, 0.1) + #alt if New device scanned QR code + note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel + note over N: 1) New device got Homeserver base URL from QR code + + #else if Existing device scanned QR code + # note over E: Existing device completes step 6 + # note over E: Existing device displays checkmark and CheckCode + # note over E: 1) Existing device sends m.login.protocols message + # E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"}) + # note over N: New device waits for user to confirm secure channel from step 7 + # Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"} + # note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided + end + + + rect rgba(0,255,0, 0.1) + note over N: 2) New device checks if it can use an available protocol: + + N->>+HS: GET /_matrix/client/v1/auth_issuer + activate N + HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} + Note over N: New device checks that it can communicate
with the issuer (OIDC Provider). Completing dynamic registration if needed + N->>+OP: GET /.well-known/openid-configuration + OP->>-N: 200 OK {..., "device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} + Note over N: Device now knows the OP and what the endpoint is, so then attempts to start the login + N->>+OP: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... + OP->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} + note over N: 3) New device informs existing device of choice of protocol: + N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) + + deactivate N + end + + rect rgba(255,0,0, 0.1) + # alt if New device scanned QR code + note over N: New device displays checkmark and CheckCode + note over E: Existing device waits for user to enter CheckCode
and confirm secure channel from step 7 + end + + rect rgba(0,255,0, 0.1) + Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} + end + + rect rgba(255,0,0, 0.1) + # alt if New device scanned QR code + note over E: If user entered correct CheckCode
and confirms checkmark then existing device now trusts the channel + end + + + rect rgba(0,255,0, 0.1) + note over E: Existing device checks that requested protocol is supported + + alt if requested protocol is not valid + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported",
"homeserver": "https://matrix-client.matrix.org}) + end + end +``` + +_Existing device scanned QR code:_ + +```mermaid +sequenceDiagram + title: Variant: Existing device scanned QR code + participant E as Existing device
already signed in + participant Z as Rendezvous server + participant N as New device
wanting to sign in + participant OP as OIDC Provider + participant HS as Homeserver + + + #alt if New device scanned QR code + # note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel + # note over N: 1) New device got Homeserver base URL from QR code + + rect rgba(0,0,255, 0.1) + #else if Existing device scanned QR code + note over E: Existing device completes step 6 + note over E: Existing device displays checkmark and CheckCode + note over E: 1) Existing device sends m.login.protocols message + E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"}) + note over N: New device waits for user to confirm secure channel from step 7 + Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"} + note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided + end + + + rect rgba(0,255,0, 0.1) + note over N: 2) New device checks if it can use an available protocol: + N->>+HS: GET /_matrix/client/v1/auth_issuer + activate N + HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} + Note over N: New device checks that it can communicate
with the issuer (OIDC Provider). Completing dynamic registration if needed + N->>+OP: GET /.well-known/openid-configuration + OP->>-N: 200 OK {..., "device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} + Note over N: Device now knows the OP and what the endpoint is, so then attempts to start the login + N->>+OP: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... + OP->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} + note over N: 3) New device informs existing device of choice of protocol: + N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) + + deactivate N + end + + # alt if New device scanned QR code + # note over N: New device displays checkmark and CheckCode + # note over E: Existing device waits for user to enter CheckCode
and confirm secure channel from step 7 + #end + + rect rgba(0,255,0, 0.1) + Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} + end + + # alt if New device scanned QR code + # note over E: If user entered correct CheckCode
and confirms checkmark then existing device now trusts the channel + #end + + + rect rgba(0,255,0, 0.1) + note over E: Existing device checks that requested protocol is supported + + alt if requested protocol is not valid + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported",
"homeserver": "https://matrix-client.matrix.org}) + end + end +``` + +Then we continue with the actual login: + +1. **New device waits for approval from OIDC Provider** + +On receipt of the `m.login.protocol_accepted` message: + +- In accordance with [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1) the new device must display +the user_code in order that the user can confirm it on the OIDC Provider if required. +- The new device then starts to poll the OIDC Provider by making +[Device Access Token Requests](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4) using the interval and bounded +by expires_in. + +*New device => OIDC Provider via HTTP* + +```http +POST /oauth2/token HTTP/1.1 +Host: auth-oidc.lab.element.dev +Content-Type: application/x-www-form-urlencoded + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code + &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS + &client_id=my_client_id +``` + +- It then parses the [Device Access Token Response](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) and +handles the different responses +- If the user consents in the next step then the new device will receive an `access_token` and `refresh_token` etc. as +normal for OIDC with MSC3861. + +1. **User is asked by OIDC Provider to consent on existing device** + +On receipt of the `m.login.protocol` message above, and having completed step 7 of the secure channel establishment, the +existing device then asserts that there is no existing device corresponding to the `device_id` from the +`m.login.protocol` message. + +It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) +and expecting to receive an HTTP 404 response. + +If the device already exists then the login request should be rejected with an `m.login.failure`. + +If no existing device was found then the existing device opens the `verification_uri_complete` - falling back to the +`verification_uri`, if `verification_uri_complete` isn’t present - in a system browser. + +Ideally this is in a trusted/secure environment where the cookie jar and password manager features are available. e.g. +on iOS this could be a `ASWebAuthenticationSession` + +The existing device then sends an acknowledgement message to let the other device know that the consent process is in progress: + +*Existing device => New device via secure channel* + +```json +{ + "type": "m.login.protocol_accepted" +} +``` + +The user is then prompted to consent by the OIDC Provider. They may be prompted to undertake additional actions by the +OIDC Provider such as 2FA, but this is all handled within the browser. + +Note that the existing device does not see the new access token. This is one of the benefits of the OIDC architecture. + +The sequence diagram for steps 4 and 5 is as follows: + +```mermaid +sequenceDiagram + participant E as Existing device
already signed in + participant UA as Web Browser + participant N as New device
wanting to sign in + participant OP as OIDC Provider + participant HS as Homeserver + + rect rgba(0,255,0, 0.1) + + par + E->>N: SecureSend({"type":"m.login.protocol_accepted"}) + note over N: 4) New device polls the OIDC Provider awaiting the outcome as per RFC8628 OIDC + loop Poll for result at interval seconds + N->>OP: POST /token client_id=xyz
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=XYZ + alt pending + OP-->>N: 400 Bad Request {"error": "authorization_pending"} + else granted + OP-->>N: 200 OK {"access_token": "...", "token_type": "Bearer", ...} + else denied + OP-->>N: 400 Bad Request {"error": "authorization_declined"} + else expired + OP-->>N: 400 Bad Request {"error": "expired_token"} + end + end + and + E->>UA: 5) Existing device opens
verification_uri_complete (with fallback to verification_uri)
in the system web browser/ASWebAuthenticationSession: + Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen + rect rgba(240,240,240,0.5) + UA->>OP: GET https://id.matrix.org/device/abcde + OP->>UA: consent screen showing the user_code + UA->>OP: POST /allow or /deny + end + Note over UA: User closes browser + end + + alt declined + Note over E: or can the OP tell the existing device the outcome? + N->>E: SecureSend({"type":"m.login.declined"}) + else approved + Note over N: Device now has an access_token and can start to talk to the homeserver + N->>E: SecureSend({ "type": "m.login.success" }) + end + end +``` + +#### Secret sharing and device verification + +Once the new device has logged in and obtained an access token it will want to obtain the secrets necessary to set up +end-to-end encryption on the device and make itself cross-signed. + +Before sharing the end-to-end encryption secrets the existing device should validate that the new device has +successfully obtained an access token from the OIDC Provider. The purpose of this is so that, if the user or OIDC +Provider has disallowed the login, the secrets are not leaked. + +If checked successfully then the existing device sends the following secrets to the new device: + +- The private cross-signing key triplet: MSK, SSK, USK +- The backup recovery key and the currently used backup version. + +This is achieved as following: + +1. **Existing device confirms that the new device has indeed logged in successfully** + +On receipt of an m.login.success message the existing device queries the homeserver to check that the is a device online +with the corresponding device_id (from the `m.login.protocol` message). + +It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) +and expecting to receive an HTTP 200 response. + +If the device isn’t immediately visible it can repeat the `GET` request for up to, say, 10 seconds to allow for any latency. + +If no device is found then the process should be stopped. + +1. **Existing device shares secrets with new device** + +The existing device sends a `m.login.secrets` message via the secure channel: + +```json +{ + "type": "m.login.secrets", + "cross_signing": { + "master_key": "$base64_of_the_key", + "self_signing_key": "$base64_of_the_key", + "user_signing_key": "$base64_of_the_key" + }, + "backup": { + "algorithm": "foobar", + "key": "base64_of_the_backup_recovery_key", + "backup_version": "version_string" + } +} +``` + +1. **New device cross-signs itself and uploads device keys** + +On receipt of the m.login.secrets message the new device can store the secrets locally + +The new device can then generate the cross-signing signature for itself. + +It can then use a single request to upload the device keys and cross signing signature. This removes the chance of other +devices seeing the new device as unverified, incorrectly prompting the user to verify the already verified device. + +The request would look just like any other `/keys/upload` request, it would just include one additional signature, the +one from the self-signing key. The request would look like follows: + +```http +POST /_matrix/client/v3/keys/upload HTTP/1.1 +Host: synapse-oidc.lab.element.dev +Content-Type: application/json + +{ + "device_keys": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "SGKMSRAGBF", + "keys": { + "curve25519:SGKMSRAGBF": "I11VOe5quKuH/YjdOqn5VcW06fvPIJQ9JX8ryj6ario", + "ed25519:SGKMSRAGBF": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" + }, + "signatures": { + "@testing_35:morpheus.localhost": { + "ed25519:SGKMSRAGBF": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", + "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFo": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" + } + }, + "user_id": "@testing_35:morpheus.localhost" + } +} +``` + +The sequence diagram for this would look as follows: + +```mermaid +sequenceDiagram + participant E as Existing device
already signed in + participant N as New device
wanting to sign in + participant OP as OIDC Provider + participant HS as Homeserver + + rect rgba(0,255,0, 0.1) + + rect rgb(191, 223, 255) +note over N,E: This step is duplicated from the previous section for readability + N-->>+E: { "type": "m.login.success" } + end + + Note over E: 1) Existing device checks that the device is actually online: + + E->>HS: GET /_matrix/client/v3/devices/{device_id} +activate HS + + alt is device not found + HS->>E: 404 Not Found + E->>N: { "type": "m.login.failed", "reason": "TODO" } + else is device found + HS->>E: 200 OK +deactivate HS + +Note over E: TODO: how do we check that this is actually the newly signed in device? + + E->>-N: 2) { "type": "m.login.secrets", "cross_signing": {...}, "backup": {...} } + + activate N + note over N: 3) New device stores the secrets locally + +alt is cross_signing present in m.login.secrets? +note over N: New device signs itself + note over N: New device uploads device keys and cross-signing signature: + N->>+HS: POST /_matrix/client/v3/keys/upload + HS->>-N: 200 OK + +else + note over N: New device uploads device keys only: + N->>+HS: POST /_matrix/client/v3/keys/upload + HS->>-N: 200 OK +end + +alt is backup present in m.login.secrets? + note over N: New device connects to room-key backup +end + +note over N: All done! + deactivate N + end + end +``` + +#### Message reference + +These are the messages that are exchanged between the devices to negotiate the sign in and set up of E2EE. + +##### `m.login.protocols` + +Sent by: existing device + +Purpose: to state the available protocols for signing in. At the moment only "`device_authorization_grant` is supported + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.protocols`| +|`protocols`|required `string[]`|Array of: one of: `device_authorization_grant` | +|`homeserver`|required `string`|The base URL of the homeserver| + +```json +{ + "type": "m.login.protocols", + "protocols": ["device_authorization_grant"], + "homeserver": "https://matrix-client.matrix.org" +} +``` + +##### `m.login.protocol` + +Sent by: new device + +Purpose: the new device sends this to indicate which protocol it intends to use + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.protocol`| +|`protocol`|required `string`|One of: `device_authorization_grant`| +|`device_authorization_grant`|Required `object` where `protocol` is `device_authorization_grant`|These values are taken from the RFC8628 Device Authorization Response that the new device received from the OIDC Provider:
Field Type
verification_uri required string
verification_uri_complete string
| +|`device_id`|required `string`|The device ID that the new device will use| + +Example: + +```json +{ + "type": "m.login.protocol", + "protocol": "device_authorization_grant", + "device_authorization_grant": { + "verification_uri_complete": "https://id.matrix.org/device/abcde", + "verification_uri": "..." + }, + "device_id": "ABCDEFGH" +} +``` + +##### `m.login.protocol_accepted` + +Sent by: existing device + +Purpose: Indicates that the existing device has accepted the protocol request and will open the `verification_uri` (or +`verification_uri_complete`) for the user to grant consent + +Example: + +```json +{ + "type":"m.login.protocol_accepted" +} +``` + +##### `m.login.failure` + +Sent by: either device + +Purpose: used to indicate a failure + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.failure`| +|`reason`|required `string`| One of: `TODO`| +|`homeserver`|`string`| When the existing device is sending this it can include the Base URL of the homeserver so that the new device can at least save the user the hassle of typing it in| + +Example: + +```json +{ + "type":"m.login.failure", +"reason": "unsupported", + "homeserver": "https://matrix-client.matrix.org" +} +``` + +##### `m.login.declined` + +Sent by: existing device + +Purpose: Indicates that the user declined the request + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.declined`| + + +Example: + +```json +{ + "type":"m.login.declined" +} +``` + +##### `m.login.success` + +Sent by: new device + +Purpose: to inform the existing device that it has successfully obtained an access token. + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.success`| + +Example: + +```json +{ + "type": "m.login.success" +} +``` + +##### `m.login.secrets` + +Sent by: existing device + +Purpose: Shares the secrets used for cross-signing and room key backups + +Fields: + +|Field|Type|| +|--- |--- |--- | +|`type`|required `string`|`m.login.secrets`| +|`cross_signing`|`object`|
Field Type
master_key required string Unpadded base64 encoded private key
self_signing_key required string Unpadded base64 encoded private key
user_signing_key required string Unpadded base64 encoded private key
| +|`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string TODO
backup_version required string TODO
| + +Example: + +```json +{ + "type": "m.login.secrets", + "cross_signing": { + "master_key": "$base64_of_the_key", + "self_signing_key": "$base64_of_the_key", + "user_signing_key": "$base64_of_the_key" + }, + "backup": { + "algorithm": "foobar", + "key": "base64_of_the_backup_recovery_key", + "backup_version": "version_string" + } +} +``` + +### QR code format + +TODO + +### Discoverability of the capability + +Before offering this capability it would make sense that the device can check the availability of the feature. + +Where the homeserver is known: + +1. Check if the homeserver has a rendezvous session API available (/versions) from this MSC +1. Check that the homeserver is using the OIDC architecture (/auth_issuer) from MSC2965 +1. Check that the Device Authorization Grant is available on the OIDC Provider from MSC2965 + +For a new device it would need to know the homeserver ahead of time in order to do these checks. + +Additionally the new device needs to either have an existing (i.e. static) OIDC client registered with the OIDC Provider +already, or the OIDC Provider must support and allow dynamic client registration as described in [MSC2966](https://github.com/matrix-org/matrix-spec-proposals/pull/2966). + ## Potential issues +Because this is an entirely new set of functionality it should not cause issue with any existing Matrix functions or capabilities. + +The proposed protocol requires the devices to have IP connectivity to the server which might not be the case in P2P scenarios. + ## Alternatives +### Alternative to the rendezvous session protocol + +#### Send-to-Device messaging + +If you squint then this proposal looks similar in some regards to the existing +[Send-to-device messaging](https://spec.matrix.org/v1.9/client-server-api/#send-to-device-messaging) capability. + +Whilst to-device messaging already provides a mechanism for secure communication between two Matrix clients/devices, a +key consideration for the anticipated login with QR capability is that one of the clients is not yet authenticated with +a homeserver. + +Furthermore the client might not know which homeserver the user wishes to connect to. + +Conceptually, one could create a new type of "guest" login that would allow the unauthenticated client to connect to a +homeserver for the purposes of communicating with an existing authenticated client via to-device messages. + +Some considerations for this: + +Where the "actual" homeserver is not known then the "guest" homeserver nominated by the new client would need to be +federated with the "actual" homeserver. + +The "guest" homeserver would probably want to automatically clean up the "guest" accounts after a short period of time. + +The "actual" homeserver operator might not want to open up full "guest" access so a second type of "guest" account might +be required. + +Does the new device/client need to accept the T&Cs of the "guest" homeserver? + +#### Other existing protocols + +One could try and do something with STUN or TURN or [COAP](http://coap.technology/). + +#### Implementation details + +Rather than requiring the devices to poll for updates, "long-polling" could be used instead similar to `/sync`. Or WebSockets. + +### Alternative method of secret sharing + +Instead of the existing device sharing the secrets bundle instead the existing device could cross-sign the new device +and then use to-device messaging for sharing the secrets. + +For: + +- You re-use existing secret sharing + +Against: + +- The existing device needs to wait for the new device to upload the device keys for it to sign the new device. +- Takes several round-trips for the secrets to be be shared which will add latency to the overall flow +- The backup cannot be immediately enabled since we received the backup version as well, something the `m.secret.send` +mechanism does not offer. +- The new device cannot upload the cross-signing signature with the device keys in a single request. This introduces a +chance of other devices seeing the new device as unverified, incorrectly prompting the user to verify the device that +will soon be verified. + ## Security considerations +See individual threat analysis sections above. + ## Unstable prefix +While this feature is in development the new `POST` endpoint should be exposed using the following unstable prefix: + +- `/_matrix/client/unstable/org.matrix.msc4108/rendezvous` + +Additionally, the feature is to be advertised as unstable feature in the GET /_matrix/client/versions response, with the +key org.matrix.msc4108 set to true. So, the response could look then as following: + +```json +{ + "versions": ["..."], + "unstable_features": { + "org.matrix.msc4108": true + } +} +``` + ## Dependencies + +This MSC builds on [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) (and its dependencies) which +proposes the adoption of OIDC for authentication in Matrix. From 177a2db95a97e64d0b244a8c14f46c1d16254d7d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Apr 2024 16:58:36 +0100 Subject: [PATCH 04/70] Auto numbers don't work on non-sequential items --- proposals/4108-oidc-qr-login.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f72eb8cf3a4..22be50d10de 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -113,12 +113,12 @@ even nonces. Device A starts with `1`, using only odd nonces. - Device G generates **(Gp, Gs)**, where **Gp** is its public key and **Gs** the private (secret) key. - Device S generates **(Sp, Ss)**, where **Sp** is its public key and **Ss** the private (secret) key. -1. **Create rendezvous session** +2. **Create rendezvous session** Device G creates a rendezvous session by making a `POST` request (as described previously) to the nominated homeserver with an empty payload. It parses the **id** and **server** received. -1. **Initial key exchange** +3. **Initial key exchange** Device G displays a QR code containing: @@ -138,7 +138,7 @@ Device S scans and parses the QR code to obtain **Gp**, the rendezvous session * At this point Device S should check that the received intent matches what the user has asked to do on the device. -1. **Device S sends the initial message** +4. **Device S sends the initial message** Device S computes a shared secret **SH** using ECDH between **Ss** and **Gp**, thereby establishing a secure channel with Device G which can be layered on top of the insecure rendezvous session transport. It then discards **Ss** and @@ -162,7 +162,7 @@ LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase6 Device S then sends the **LoginInitiateMessage** as the payload to the rendezvous session using a `PUT` request with `Content-Type` header set to `text/plain`. -1. **Device G confirms** +5. **Device G confirms** Device G receives **LoginInitiateMessage** (potentially coming from Device S) from the insecure rendezvous session by polling with `GET` requests. @@ -186,7 +186,7 @@ LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) Device G sends **LoginOkMessage** as the payload via `PUT` request with `Content-Type` header set to `text/plain` to the insecure rendezvous session. -1. **Verification by Device S** +6. **Verification by Device S** Device S receives a response over the insecure rendezvous session by polling with `GET` requests, potentially from Device G. @@ -216,7 +216,7 @@ Device S then displays an indication to the user that the secure channel has bee should be entered on the other device when prompted. e.g. wording to say "secure connection established"; enter the code XY on your other device; -1. **Out-of-band confirmation** +7. **Out-of-band confirmation** **Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of ECIES.* @@ -391,7 +391,7 @@ homeserver specified: } ``` -1. **New device checks if it can use an available protocol** +2. **New device checks if it can use an available protocol** Once the existing device knows which homeserver it is to use it then: @@ -489,7 +489,7 @@ Content-Type: application/json At this point the new device knows that, subject to the user consenting, it should be able to complete the login -1. **New device informs existing device that it wants to use the device_authorization_grant** +3. **New device informs existing device that it wants to use the `device_authorization_grant`** The new device send the `verification_uri` and, if present, the `verification_uri_complete` over to the existing device and indicates that want to use protocol `device_authorization_grant` along with the `device_id` that will be used: @@ -651,7 +651,7 @@ sequenceDiagram Then we continue with the actual login: -1. **New device waits for approval from OIDC Provider** +4. **New device waits for approval from OIDC Provider** On receipt of the `m.login.protocol_accepted` message: @@ -678,7 +678,7 @@ handles the different responses - If the user consents in the next step then the new device will receive an `access_token` and `refresh_token` etc. as normal for OIDC with MSC3861. -1. **User is asked by OIDC Provider to consent on existing device** +5. **User is asked by OIDC Provider to consent on existing device** On receipt of the `m.login.protocol` message above, and having completed step 7 of the secure channel establishment, the existing device then asserts that there is no existing device corresponding to the `device_id` from the @@ -786,7 +786,7 @@ If the device isn’t immediately visible it can repeat the `GET` request for up If no device is found then the process should be stopped. -1. **Existing device shares secrets with new device** +2. **Existing device shares secrets with new device** The existing device sends a `m.login.secrets` message via the secure channel: @@ -806,7 +806,7 @@ The existing device sends a `m.login.secrets` message via the secure channel: } ``` -1. **New device cross-signs itself and uploads device keys** +3. **New device cross-signs itself and uploads device keys** On receipt of the m.login.secrets message the new device can store the secrets locally From f54e194584a638ad4225628178fe4adcf27083fb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 11:57:48 +0100 Subject: [PATCH 05/70] High level description of rendezvous protocol and consistency in payload vs message --- proposals/4108-oidc-qr-login.md | 37 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 22be50d10de..27cb35d5a4a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -28,7 +28,19 @@ end-to-end confidentiality nor authenticity by itself—these are layered on top #### High-level description -TODO +Suppose that Device A wants to establish communications with Device B. Device A can do so by creating a +_rendezvous session_ via a `POST /_matrix/client/v1/rendezvous` call to an appropriate homeserver. Its response includes +an HTTP _rendezvous URL_ which should be shared out-of-band with Device B. (This URL may be located on a different +domain to the initial `POST`.) + +The rendezvous URL points to an arbitrary data resource (the "payload"), which is initially populated using data from +A's initial `POST` request. There are no restrictions on the payload itself, but the rendezvous server SHOULD impose a +maximum size limit. + +Anyone who is able to reach the rendezvous URL - including: Device A; Device B; or a third party; - can then "receive" +the payload by polling via a `GET` request, and "send" a new a new payload by making a `PUT` request. + +In this way, Device A and Device B can communicate by repeatedly inspecting and updating the payload at the rendezvous URL. #### The send mechanism @@ -88,8 +100,8 @@ existing security analyses of ECIES are applicable in this setting too. Neverthe description of our instantiation of ECIES and discuss some potential pitfalls and attacks. The primary limitation of ECIES is that there is no authentication for the initiating party (the one to send the first -message; Device S in the text below). Thus the recipient party (the one to receive the first message; Device G in the -text below) has no assurance as to who actually sent the message. In QR code login, we work around this problem by +payload; Device S in the text below). Thus the recipient party (the one to receive the first payload; Device G in the +text below) has no assurance as to who actually sent the payload. In QR code login, we work around this problem by exploiting the fact that both of these devices are physically present during the exchange and offloading the check that they are both in the correct state to the user performing the QR code login process. @@ -103,7 +115,7 @@ Participants: Regardless of which device generates the QR code, either device can be the existing (already signed in) device. The other device is then the new device (one seeking to be signed in). -Symmetric encryption uses deterministic nonces, incrementing by `2` with each message. Device S starts with `0`, using only +Symmetric encryption uses deterministic nonces, incrementing by `2` with each payload. Device S starts with `0`, using only even nonces. Device A starts with `1`, using only odd nonces. 1. **Ephemeral key pair generation** @@ -138,13 +150,13 @@ Device S scans and parses the QR code to obtain **Gp**, the rendezvous session * At this point Device S should check that the received intent matches what the user has asked to do on the device. -4. **Device S sends the initial message** +4. **Device S sends the initial payload** Device S computes a shared secret **SH** using ECDH between **Ss** and **Gp**, thereby establishing a secure channel with Device G which can be layered on top of the insecure rendezvous session transport. It then discards **Ss** and derives a symmetric encryption **EncKey** from **SH** using HKDF_SHA256, each 32 bytes in length. -Device S derives a confirmation message that Device G can use to confirm that the channel is secure. It contains: +Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: - The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. - Its public ephemeral key **Sp**. @@ -172,7 +184,7 @@ discarding **Gs** and decrypting (and authenticating) the **TaggedCiphertext**, It checks that the plaintext matches the string `MATRIX_QR_CODE_LOGIN_INITIATE`, failing and aborting if not. -It then responds with a dummy message containing the string `MATRIX_QR_CODE_LOGIN_OK` encrypted with **SH** calculated +It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGIN_OK` encrypted with **SH** calculated as follows: ``` @@ -191,7 +203,7 @@ insecure rendezvous session. Device S receives a response over the insecure rendezvous session by polling with `GET` requests, potentially from Device G. -It decrypts (and authenticates) it using the previously computed encryption key, which will succeed provided the message +It decrypts (and authenticates) it using the previously computed encryption key, which will succeed provided the payload was indeed sent by Device G. It then verifies the plaintext matches `MATRIX_QR_CODE_LOGIN_OK`, failing otherwise. ``` @@ -235,7 +247,7 @@ CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) If the code that the user enters matches then the secure channel is established. -Subsequent messages can be sent encrypted with **EncKey** with the nonces incremented as described above. +Subsequent payloads can be sent encrypted with **EncKey** with the nonces incremented as described above. #### Sequence diagram @@ -332,7 +344,7 @@ In an attack scenario, we add a participant called Specter with the following ca ##### Replay protection Due to use of ephemeral key pairs which are immediately discarded after use, each QR code login session derives a unique -secret so messages from earlier sessions cannot be replayed. Each message in the session is unique and expected only +secret so payloads from earlier sessions cannot be replayed. Each payload in the session is unique and expected only once. Finally, the use of deterministic nonces prevents any possibility of replay. ##### Pure Dolev-Yao attacker @@ -346,7 +358,7 @@ Since Device G has no way of authenticating Device S, an attacker present for th attempt to mimic Device S in order to get their Device S signed in instead. - In step 3, Specter can shoulder surf the QR code scanning to obtain **Gp**. -- In step 4, Specter can intercept S’s message and replace it with a message of their own, replacing **Sp** with its +- In step 4, Specter can intercept S’s payload and replace it with a payload of their own, replacing **Sp** with its own key. - The attack is only thwarted in step 7, because Device S won’t ever display the indicator of success to the user. The user then must cancel the process on Device G, preventing it from sharing any sensitive material. @@ -904,7 +916,8 @@ note over N: All done! #### Message reference -These are the messages that are exchanged between the devices to negotiate the sign in and set up of E2EE. +These are the messages that are exchanged between the devices via the secure channel to negotiate the sign in and set up +of E2EE. ##### `m.login.protocols` From f34bec334a8fa5fa5de9fe4f1f5570d87cd785f2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 12:45:02 +0100 Subject: [PATCH 06/70] Cheat spell checker --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 27cb35d5a4a..2b092d95d56 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -849,7 +849,7 @@ Content-Type: application/json "signatures": { "@testing_35:morpheus.localhost": { "ed25519:SGKMSRAGBF": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", - "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFo": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" + "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFol": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" } }, "user_id": "@testing_35:morpheus.localhost" From 2830e88bfc5d2893cf8f9de61a2771d93befed30 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 13:55:55 +0100 Subject: [PATCH 07/70] Description of rendezvous session API --- proposals/4108-oidc-qr-login.md | 219 +++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 2b092d95d56..c7b23eb362b 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -61,6 +61,220 @@ header. After this point, any further attempts to query or update the payload MU extended every time the payload is updated. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. +####  API + +A new endpoint for the Client-Server API: + +##### Create a rendezvous session and send initial payload: `POST /_matrix/client/v1/rendezvous` + +HTTP request headers: + +- `Content-Length` - required +- `Content-Type` - required + +HTTP request body: + +- any data up to maximum size allowed by the server + +HTTP response codes, and Matrix error codes: + +- `201 Created` - rendezvous session created +- `400 Bad Request` (``M_MISSING_PARAM``) - no `Content-Length` was provided. +- `403 Forbidden` (``M_FORBIDDEN``) - forbidden by server policy +- `413 Payload Too Large` (``M_TOO_LARGE``) - the supplied payload is too large +- `429 Too Many Requests` (``M_UNKNOWN``) - the request has been rate limited +- `307 Temporary Redirect` - if the request should be served from somewhere else specified in the `Location` response header + +n.b. the [`307 Temporary Redirect`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) response code has been chosen explicitly for the behaviour of ensuring that the method and body will not change whilst the user-agent follows the redirect. For this reason, no other `30x` response codes are allowed. + +HTTP response headers for `201 Created`: + +- `Content-Type`- required, application/json +- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) +- `Expires` - required, the expiry time of the rendezvous as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires) +- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) + +Example response: + +``` +HTTP 201 Created +ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= +Expires: Wed, 07 Sep 2022 14:28:51 GMT +Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT +Content-Type: application/json + +{ + "url": "http://example.org/abcdEFG12345" +} +``` + +##### Send a payload to the rendezvous session: `PUT ` + +HTTP request headers: + +- `Content-Length` - required +- `Content-Type` - required +- `If-Match` - required. The ETag of the last payload seen by the requesting device. + +HTTP request body: + +- any data up to maximum size allowed by the server + +HTTP response codes, and Matrix error codes: + +- `202 Accepted` - payload updated +- `400 Bad Request` (`M_MISSING_PARAM`) - a required header was not provided. +- `400 Bad Request` (`M_INVALID_PARAM`) - a malformed [`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) header was provided. +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `412 Precondition Failed` (`M_CONCURRENT_WRITE`, a new error code) - when the ETag does not match +- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large +- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited + +HTTP response headers for `202 Accepted` and `412 Precondition Failed`: + +- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) +- `Expires` - required, the expiry time of the rendezvous session as per [RFC7233](https://httpwg.org/specs/rfc7234.html#header.expires) +- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) + +##### Receive a payload from the rendezvous session: `GET ` + +HTTP request headers: + +- `If-None-Match` - optional, as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.if-none-match) server will only return data if given ETag does not match + +HTTP response codes, and Matrix error codes: + +- `200 OK` - payload returned +- `304 Not Modified` - when `If-None-Match` is supplied and the ETag does not match +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) +- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited + +HTTP response headers for `200 OK` and `304 Not Modified`: + +- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) +- `Expires` - required, the expiry time of the rendezvous session as per [RFC7233](https://httpwg.org/specs/rfc7234.html#header.expires) +- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) +- `Content-Type` - required for `200 OK` + +HTTP response body: + +- The payload last set for this rendezvous session, either via the creation POST request or a subsequent PUT request, up to the maximum size allowed by the server. + +##### Cancel a rendezvous session: `DELETE ` + +HTTP response codes: + +- `204 No Content` - rendezvous session cancelled +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited + +##### Authentication + +These API endpoints do not require authentication because trust is established at the secure channel layer which is described later. + +##### Maximum payload size + +The server should allow a minimum payload size of 10KB and enforce a maximum payload size which is recommended to be 100KB. + +###### Maximum duration of a rendezvous + +The rendezvous session only needs to persist for the duration of the handshake. So a timeout such as 30 seconds is adequate. + +Clients should handle the case of the rendezvous session being cancelled or timed out by the server. + +###### ETags + +The ETag generated should be unique to the rendezvous session and the last modified time so that two clients can distinguish between identical payloads sent by either client. + +###### CORS + +For the POST /_matrix/client/rendezvous API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) headers, the ETag response header should also be allowed by exposing the following CORS header: + +```http +Access-Control-Expose-Headers: ETag +``` + +To support usage from web browsers the rendezvous URLs should allow CORS requests from any origin and expose the headers which aren’t on the CORS [request header safelist](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and [response header safelist](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header): + +```http +Access-Control-Allow-Headers: If-Match,If-None-Match +Access-Control-Allow-Methods: GET, PUT, DELETE +Access-Control-Allow-Origin: * +Access-Control-Expose-Headers: ETag +``` + +##### Choice of server + +Ultimately it will be up to the Matrix client implementation to decide which rendezvous server to use. + +However, it is suggested that the following logic is used by the device/client to choose the rendezvous server in order of preference: + +1. If the client is already logged in: try and use the current homeserver. +1. If the client is not logged in and it is known which homeserver the user wants to connect to: try and use that homeserver. +1. Otherwise use a default server. + +#### Example API usage + +```mermaid +sequenceDiagram + participant A as Device A + participant HS as Homeserver + participant R as Rendezvous Server
https://rz.example.com + participant B as Device B + Note over A: Device A determines which rendezvous server to use + + A->>+HS: POST /_matrix/client/rendezvous
Content-Type: text/plain
"Hello from A" + HS->>-A: 307 https://rz.example.com/foo + A->>+R: POST /foo
Content-Type: text/plain
"Hello from A" + R->>-A: 201 Created
ETag: 1
{"url":"https://rz.example.com/abc-def-123-456"} + + A-->>B: Rendezvous URL shared out of band as QR code: e.g. https://rz.example.com/abc-def-123-456 + + Note over A: Device A starts polling for new payloads at the
rendezvous session using the returned ETag + activate A + + B->>+R: GET /abc-def-123-456 + R->>-B: 200 OK
ETag: 1
Content-Type: text/plain
"Hello from A" + + loop Device A polls the rendezvous session for a new payload + A->>+R: GET /abc-def-123-456
If-None-Match: 1 + alt is not modified + R->>-A: 304 Not Modified + end + end + + note over B: Device B sends a new payload + B->>+R: PUT /abc-def-123-456
If-Match: 1
Content-Type: text/plain
"Hello from B" + R->>-B: 202 Accepted
ETag: 2 + + Note over B: Device B starts polling for new payloads at the
rendezvous session using the new ETag + activate B + + loop Device B polls the rendezvous session for a new payload + B->>+R: GET /abc-def-123-456
If-None-Match: 2 + alt is not modified + R->>-B: 304 Not Modified + end + end + + note over A: Device A then receives the new payload + opt modified + R->>A: 200 OK
ETag: 2
Content-Type: text/plain
"Hello from B" + end + deactivate A + + note over A: Device A sends a new payload + A->>+R: PUT /abc-def-123-456
If-None-Match: 2
Content-Type: text/plain
"Hello again from A" + R->>-A: 202 Accepted
ETag: 3 + + note over B: Device B then receives the new payload + opt modified + R->>B: 200 OK
ETag: 3
Content-Type: text/plain
... + end + + deactivate B +``` + #### Threat analysis ##### Denial of Service attack surface @@ -90,7 +304,7 @@ TODO ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or -even arbitrary network participants which possess the rendezvous session server and ID. To provide a secure channel on +even arbitrary network participants which possess the rendezvous session URL. To provide a secure channel on top of this insecure rendezvous session transport, we propose the following scheme. This scheme is essentially[ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) @@ -145,7 +359,7 @@ similar structure to that of the existing Device Verification QR code encoding d This is defined in detail in a separate section of this proposal. -Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the +Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the **homeserver base URL**. At this point Device S should check that the received intent matches what the user has asked to do on the device. @@ -1021,7 +1235,6 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.declined`| - Example: ```json From 24e2242f8d1e790df641b084d5541b8752ea9b16 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 15:53:03 +0100 Subject: [PATCH 08/70] Add description of QR format --- proposals/4108-oidc-qr-login.md | 63 +++++++++++++++++++++++++++- proposals/images/4108-qr-mode03.png | Bin 0 -> 921 bytes proposals/images/4108-qr-mode04.png | Bin 0 -> 1019 bytes 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 proposals/images/4108-qr-mode03.png create mode 100644 proposals/images/4108-qr-mode04.png diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index c7b23eb362b..7a9a605f3e4 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1297,7 +1297,68 @@ Example: ### QR code format -TODO +The proposed format of the QR code intends to be similar to that which is already described in the Client-Server API for +[device verification](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). + +Additional modes are added to the byte used for "QR code verification mode" to allow for the two login intents: initiate +on a new device; reciprocate on an existing device; + +The QR codes to be displayed and scanned using this format will encode binary strings in the general form: + +- the ASCII string `MATRIX` +- one byte indicating the QR code version (must be `0x02`) +- one byte indicating the QR code intent/mode. Should be one of the following values: + - `0x03` a new device wishing to initiate a login and self-verify + - `0x04` an existing device wishing to reciprocate the login of a new device and self-verify that other device +- the ephemeral Curve25519 public key, as 32 bytes +- the rendezvous session URL encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session URL as a UTF-8 + string + - the rendezvous session URL as a UTF-8 string +- If the QR code intent/mode is `0x04` then the homeserver base URL encode as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the homeserver base URL as a UTF-8 string + - the homeserver base URL as a UTF-8 string + +For example, if Alice displays a QR code encoding the following binary string: + +This indicates that Alice is a new device that wishes to initiate a login using her ephemeral public key of +`0001020304050607...` (which is `AAECAwQFBg…` in base64), via the rendezvous session at URL `https:/…`. + +#### Example for QR code generated on new device + +A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded) at rendezvous session `https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668` is as follows: +(Whitespace is for readability only) + +``` +4D 41 54 52 49 58 02 03 +d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b +00 47 +68 74 74 70 73 3a 2f 2f 72 65 6e 64 65 7a 76 6f 75 73 2e 6c 61 62 2e 65 6c 65 6d 65 6e 74 2e 64 65 76 2f 65 38 64 61 36 33 35 35 2d 35 35 30 62 2d 34 61 33 32 2d 61 31 39 33 2d 31 36 31 39 64 39 38 33 30 36 36 38 +``` + +Which looks as follows as a QR with error correction level Q: + +![Example QR for mode 0x03](images/4108-qr-mode03.png) + +#### Example for QR code generated on existing device + +A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded), at rendezvous session `https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) + +``` +4D 41 54 52 49 58 02 04 +d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b +00 47 +68 74 74 70 73 3a 2f 2f 72 65 6e 64 65 7a 76 6f 75 73 2e 6c 61 62 2e 65 6c 65 6d 65 6e 74 2e 64 65 76 2f 65 38 64 61 36 33 35 35 2d 35 35 30 62 2d 34 61 33 32 2d 61 31 39 33 2d 31 36 31 39 64 39 38 33 30 36 36 38 +00 20 +68 74 74 70 73 3a 2f 2f 6d 61 74 72 69 78 2d 63 6c 69 65 6e 74 2e 6d 61 74 72 69 78 2e 6f 72 67 +``` + +Which looks as follows as a QR with error correction level Q: + +![Example QR for mode 0x04](images/4108-qr-mode04.png) ### Discoverability of the capability diff --git a/proposals/images/4108-qr-mode03.png b/proposals/images/4108-qr-mode03.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1a629f17d2a711eb724b615c60a25e2ff2390c GIT binary patch literal 921 zcmV;K17`e*P)-o(5S${5q66 zbxOYQwZ-wx=j_HIj%q%2d=y>@@y*^GC6oE=y-jo-QNt6}A)N0r_Rb#h zc@7$$Ba+3Rw3I6cyL)py`L^;zIKfIDw}$%e*>=!yB)dsZn%$jCxYq7Mk16==&K|Ym zaVR)PwCFc}D^J*4xVY5bGlH+4xo9}z6VVVPcu)2wvpw~1<;h=LvDoMA$GNk2$J^(< zjXX2Lqsc2o00Q;GMfMt=JW{T`JKyn2&L)2jtl_zwL!sGUIM9UC?C>@XPaf@!u7JLo z9SRWfH4V?Pk`rxQyNadT@$RjHBgfT0H`kf1h}Pja6#RBX9$6dcEmpBb>U12DuZ;Yd9mm&Ek(VLoGext9X$HSUXs@*&@`5$Z0sEVscX+ zM|1Y}CVhOQm2-uxZPBb9ERs21pZn6vF*e*JbI1_jAZUj1yZ=U>Jj51Dv_$wU`rKsJ za3r`pc=R|hif=g5^yJ&hbEq>m8K1<-X1Rl`;Ver_G`l~^!5q&wYdAKB;cAJJH5VM{ z_|cPQUM`rm1Sgpvl603lc(q~R<_ zm$}h?{koMiW-8PTrZ4%f-tOPIv5hC_8{rvK1Vjn0jb3F1hav8r;ez2owD<1Ns8+!r z$H$n5HZTX~^GL6t;tYR8rZ5qhBjUr|G+gH4zz5?f%_dU~Mq*a*XFi9RRLtk(Wynt- zziZ_|^glg)zLWU%Ctn4ZhQzHAvBhyn8Fd^LgSWyWk3yLGe(6jjN4_5A!oUyp#$_?T v=BqZ2`Y)nvk}vLWPmmN1|5N`j|DF6Fc-by`AI33y00000NkvXXu0mjfFv!BI literal 0 HcmV?d00001 diff --git a/proposals/images/4108-qr-mode04.png b/proposals/images/4108-qr-mode04.png new file mode 100644 index 0000000000000000000000000000000000000000..68dc9c93b337ca6361f5a6ff9c74ca2389568f7e GIT binary patch literal 1019 zcmVCx^6yHZ!DU#pYmCk>|l zy!RO5|N77$^1tLHnk#cG7dNs*ArFx{Ey0C3-&$+ zeM<1T@$_tt)cQbEkjtzkcW51>sNf_=uDl1{wB(ZeU=nEw%qf$_9OXevaGxRZZP8~r z{>tb)>4`Nrj#(X9j8B`aTVtN}q)i@oaFfSGn=QJ5$3sg_UryOtv*Eneb%3?4T5=Qm zgYhvZxgq5#j2C8HOHOYNmbvZ&23&fDN~~IP)lB%?$>DA!V_5D`SW9jiPtUu>qdQ38 z5&0EHHQ`EYLc`reWvk;+F*v!?k}H-Ml`DxbZ>XqueQ>LmFpHd=)*Kbruq&uRQxmCy z9dA!vl%1Pn?$Wqv3DkX-^FoYWZ_%ID)6bS@2tzd!T9onVL&Y^fTD9c#Q1rY&u@cn% z5m79PmT)pxg=6H2(ByK;5VcuLt|Ya*WCmg2Z3>yl99nV`Rs_}!CBP~}m7j&ylB;5( z8=?eRM;@!po9v&GFq#v_JB+*dt!UoGr6y zFD*Ge_O;y5X2Px%t};G@mf(r6WM08Q?2%UBy2w6MaVHCn-St z^=%EgDy-F*AR%xR%xOq`+MG^#DPvrm|KUpu4rB)HF#dde)^*$ z5>If_Enz>LI2-@o=X3H%yV#BhgQO>e%(!Zayw9B@$|rYj>iB1002ovPDHLkV1m3z<&ppZ literal 0 HcmV?d00001 From 21ae2cac8a7bc58aa109146810a9eb3f110ca742 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 15:55:31 +0100 Subject: [PATCH 09/70] Lint --- proposals/4108-oidc-qr-login.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 7a9a605f3e4..23a022bac5c 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -85,7 +85,9 @@ HTTP response codes, and Matrix error codes: - `429 Too Many Requests` (``M_UNKNOWN``) - the request has been rate limited - `307 Temporary Redirect` - if the request should be served from somewhere else specified in the `Location` response header -n.b. the [`307 Temporary Redirect`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) response code has been chosen explicitly for the behaviour of ensuring that the method and body will not change whilst the user-agent follows the redirect. For this reason, no other `30x` response codes are allowed. +n.b. the [`307 Temporary Redirect`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) response code has been +chosen explicitly for the behaviour of ensuring that the method and body will not change whilst the user-agent follows +the redirect. For this reason, no other `30x` response codes are allowed. HTTP response headers for `201 Created`: @@ -124,7 +126,9 @@ HTTP response codes, and Matrix error codes: - `202 Accepted` - payload updated - `400 Bad Request` (`M_MISSING_PARAM`) - a required header was not provided. -- `400 Bad Request` (`M_INVALID_PARAM`) - a malformed [`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) header was provided. +- `400 Bad Request` (`M_INVALID_PARAM`) - a malformed +[`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) +header was provided. - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) - `412 Precondition Failed` (`M_CONCURRENT_WRITE`, a new error code) - when the ETag does not match - `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large @@ -140,7 +144,8 @@ HTTP response headers for `202 Accepted` and `412 Precondition Failed`: HTTP request headers: -- `If-None-Match` - optional, as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.if-none-match) server will only return data if given ETag does not match +- `If-None-Match` - optional, as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.if-none-match) server will +only return data if given ETag does not match HTTP response codes, and Matrix error codes: @@ -158,7 +163,8 @@ HTTP response headers for `200 OK` and `304 Not Modified`: HTTP response body: -- The payload last set for this rendezvous session, either via the creation POST request or a subsequent PUT request, up to the maximum size allowed by the server. +- The payload last set for this rendezvous session, either via the creation POST request or a subsequent PUT request, up +to the maximum size allowed by the server. ##### Cancel a rendezvous session: `DELETE ` @@ -170,7 +176,8 @@ HTTP response codes: ##### Authentication -These API endpoints do not require authentication because trust is established at the secure channel layer which is described later. +These API endpoints do not require authentication because trust is established at the secure channel layer which is +described later. ##### Maximum payload size @@ -188,13 +195,16 @@ The ETag generated should be unique to the rendezvous session and the last modif ###### CORS -For the POST /_matrix/client/rendezvous API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) headers, the ETag response header should also be allowed by exposing the following CORS header: +For the POST /_matrix/client/rendezvous API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) +headers, the ETag response header should also be allowed by exposing the following CORS header: ```http Access-Control-Expose-Headers: ETag ``` -To support usage from web browsers the rendezvous URLs should allow CORS requests from any origin and expose the headers which aren’t on the CORS [request header safelist](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and [response header safelist](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header): +To support usage from web browsers the rendezvous URLs should allow CORS requests from any origin and expose the headers +which aren’t on the CORS [request header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and +[response header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header) safelists: ```http Access-Control-Allow-Headers: If-Match,If-None-Match @@ -207,7 +217,8 @@ Access-Control-Expose-Headers: ETag Ultimately it will be up to the Matrix client implementation to decide which rendezvous server to use. -However, it is suggested that the following logic is used by the device/client to choose the rendezvous server in order of preference: +However, it is suggested that the following logic is used by the device/client to choose the rendezvous server in order +of preference: 1. If the client is already logged in: try and use the current homeserver. 1. If the client is not logged in and it is known which homeserver the user wants to connect to: try and use that homeserver. @@ -297,10 +308,6 @@ is possible to use it to circumvent firewalls and other network security measure Implementation may want to block their production IP addresses from being able to make requests to the rendezvous endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. -#### API - -TODO - ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or From 38eb66151ee558d2615fbda429954455b80e5eee Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 16:55:58 +0100 Subject: [PATCH 10/70] Notes on threat model --- proposals/4108-oidc-qr-login.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 23a022bac5c..f8ded79a1ed 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1447,7 +1447,17 @@ will soon be verified. ## Security considerations -See individual threat analysis sections above. +This proposed mechanism has been designed to protects users and their devices from the following threats: + +- A malicious actor who is able to scan the QR code generated by the legitimate user. +- A malicious actor who can intercept and modify traffic on the application layer, even if protected by encryption like TLS. +- Both of the above at the same time. + +Additionally, the OIDC Provider is able to define and enforce policies that can prevent a sign in on a new device. +Such policies depend on the OIDC Provider in use and could include, but are not limited to, time of day, day of the week, +source IP address and geolocation. + +A threat analysis has been done within each of the key layers in the proposal above. ## Unstable prefix From 9cd724f5008fc9a6f7960447d56400c0bd959ae9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 17:05:53 +0100 Subject: [PATCH 11/70] Fix broken link --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f8ded79a1ed..4a8ccc0897c 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1420,7 +1420,7 @@ Does the new device/client need to accept the T&Cs of the "guest" homeserver? #### Other existing protocols -One could try and do something with STUN or TURN or [COAP](http://coap.technology/). +One could try and do something with STUN or TURN or [COAP](https://datatracker.ietf.org/doc/html/rfc7252). #### Implementation details From db759eacce2c54cdedcf609f701ec361bb7fb59c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 4 Apr 2024 17:32:39 +0100 Subject: [PATCH 12/70] Resolve some more TODOs --- proposals/4108-oidc-qr-login.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f8ded79a1ed..6443611db84 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1088,26 +1088,23 @@ sequenceDiagram participant HS as Homeserver rect rgba(0,255,0, 0.1) - rect rgb(191, 223, 255) note over N,E: This step is duplicated from the previous section for readability N-->>+E: { "type": "m.login.success" } end - Note over E: 1) Existing device checks that the device is actually online: - + Note over E: 1) Existing device checks that the device is actually online E->>HS: GET /_matrix/client/v3/devices/{device_id} activate HS alt is device not found + note over E: We should wait and retry for 10 seconds HS->>E: 404 Not Found E->>N: { "type": "m.login.failed", "reason": "TODO" } else is device found HS->>E: 200 OK deactivate HS -Note over E: TODO: how do we check that this is actually the newly signed in device? - E->>-N: 2) { "type": "m.login.secrets", "cross_signing": {...}, "backup": {...} } activate N @@ -1282,7 +1279,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.secrets`| |`cross_signing`|`object`|
Field Type
master_key required string Unpadded base64 encoded private key
self_signing_key required string Unpadded base64 encoded private key
user_signing_key required string Unpadded base64 encoded private key
| -|`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string TODO
backup_version required string TODO
| +|`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string Unpadded base64 encoded private/secret key
backup_version required string The backup version as returned by [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3room_keysversion)
| Example: From 4e425afe49a34298f878fa7b71470569327e2179 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 5 Apr 2024 15:41:13 +0100 Subject: [PATCH 13/70] Define POST response body explicitly --- proposals/4108-oidc-qr-login.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f44bdd2de43..738579a7fc3 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -96,9 +96,13 @@ HTTP response headers for `201 Created`: - `Expires` - required, the expiry time of the rendezvous as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires) - `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) +HTTP response body for `201 Created`: + +- a JSON object with a single key `url` whose value is the absolute URL of the rendezvous session + Example response: -``` +```http HTTP 201 Created ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= Expires: Wed, 07 Sep 2022 14:28:51 GMT From a302c39faf81d36d75acc1f6e089ef8fb13c1794 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 9 Apr 2024 11:38:04 +0100 Subject: [PATCH 14/70] Add Cache-Control and Pragma HTTP response headers --- proposals/4108-oidc-qr-login.md | 67 +++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 738579a7fc3..ecb196dd72e 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -63,10 +63,18 @@ rendezvous session. ####  API -A new endpoint for the Client-Server API: +##### Common HTTP response headers + +- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) +- `Expires` - required, the expiry time of the rendezvous as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires) +- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) +- `Cache-Control` - required, `no-store` as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.cache-control) +- `Pragma` - required, `no-cache` as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.pragma) ##### Create a rendezvous session and send initial payload: `POST /_matrix/client/v1/rendezvous` +This would be part of the Client-Server API. + HTTP request headers: - `Content-Length` - required @@ -92,9 +100,7 @@ the redirect. For this reason, no other `30x` response codes are allowed. HTTP response headers for `201 Created`: - `Content-Type`- required, application/json -- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) -- `Expires` - required, the expiry time of the rendezvous as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires) -- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) +- common headers as defined above HTTP response body for `201 Created`: @@ -104,10 +110,12 @@ Example response: ```http HTTP 201 Created +Content-Type: application/json ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= Expires: Wed, 07 Sep 2022 14:28:51 GMT Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT -Content-Type: application/json +Cache-Control: no-store +Pragma: no-cache { "url": "http://example.org/abcdEFG12345" @@ -140,9 +148,7 @@ header was provided. HTTP response headers for `202 Accepted` and `412 Precondition Failed`: -- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) -- `Expires` - required, the expiry time of the rendezvous session as per [RFC7233](https://httpwg.org/specs/rfc7234.html#header.expires) -- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) +- common headers as defined above ##### Receive a payload from the rendezvous session: `GET ` @@ -158,18 +164,43 @@ HTTP response codes, and Matrix error codes: - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited -HTTP response headers for `200 OK` and `304 Not Modified`: +HTTP response headers for `200 OK`: -- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) -- `Expires` - required, the expiry time of the rendezvous session as per [RFC7233](https://httpwg.org/specs/rfc7234.html#header.expires) -- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) -- `Content-Type` - required for `200 OK` +- `Content-Type` - required +- common headers as defined above + +HTTP response headers for `304 Not Modified`: -HTTP response body: +- common headers as defined above + +HTTP response body for `200 OK`:: - The payload last set for this rendezvous session, either via the creation POST request or a subsequent PUT request, up to the maximum size allowed by the server. +Example responses: + +```http +HTTP 200 OK +Content-Type: text/plain +ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= +Expires: Wed, 07 Sep 2022 14:28:51 GMT +Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT +Cache-Control: no-store +Pragma: no-cache + +foo +``` + +```http +HTTP 304 Not Modified +ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= +Expires: Wed, 07 Sep 2022 14:28:51 GMT +Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT +Cache-Control: no-store +Pragma: no-cache +``` + ##### Cancel a rendezvous session: `DELETE ` HTTP response codes: @@ -195,11 +226,15 @@ Clients should handle the case of the rendezvous session being cancelled or time ###### ETags -The ETag generated should be unique to the rendezvous session and the last modified time so that two clients can distinguish between identical payloads sent by either client. +The ETag generated should be unique to the rendezvous session and the last modified time so that two clients can +distinguish between identical payloads sent by either client. + +In order to make sure that no intermediate caches manipulate the ETags, the rendezvous server MUST include the HTTP +`Cache-Control` response header with a value of `no-store` and `Pragma` response header with a value of `no-cache`. ###### CORS -For the POST /_matrix/client/rendezvous API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) +For the `POST /_matrix/client/rendezvous` API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) headers, the ETag response header should also be allowed by exposing the following CORS header: ```http From a81491ca2b7143402ef15870d49ac3254615203c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 10 Apr 2024 16:23:26 +0100 Subject: [PATCH 15/70] Add error codes --- proposals/4108-oidc-qr-login.md | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index ecb196dd72e..962ddcf392a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -848,7 +848,7 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported",
"homeserver": "https://matrix-client.matrix.org}) + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "https://matrix-client.matrix.org}) end end ``` @@ -916,7 +916,7 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported",
"homeserver": "https://matrix-client.matrix.org}) + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "https://matrix-client.matrix.org}) end end ``` @@ -959,7 +959,7 @@ existing device then asserts that there is no existing device corresponding to t It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) and expecting to receive an HTTP 404 response. -If the device already exists then the login request should be rejected with an `m.login.failure`. +If the device already exists then the login request should be rejected with an `m.login.failure` and reason `device_already_exists`. If no existing device was found then the existing device opens the `verification_uri_complete` - falling back to the `verification_uri`, if `verification_uri_complete` isn’t present - in a system browser. @@ -1003,10 +1003,14 @@ sequenceDiagram OP-->>N: 400 Bad Request {"error": "authorization_pending"} else granted OP-->>N: 200 OK {"access_token": "...", "token_type": "Bearer", ...} + N->>E: SecureSend({ "type": "m.login.success" }) + Note over N: Device now has an access_token and can start to talk to the homeserver else denied OP-->>N: 400 Bad Request {"error": "authorization_declined"} + N->>E: SecureSend({"type":"m.login.declined"}) else expired OP-->>N: 400 Bad Request {"error": "expired_token"} + N->>E: SecureSend({"type":"m.login.failure", "reason": "authorization_expired"}) end end and @@ -1019,14 +1023,6 @@ sequenceDiagram end Note over UA: User closes browser end - - alt declined - Note over E: or can the OP tell the existing device the outcome? - N->>E: SecureSend({"type":"m.login.declined"}) - else approved - Note over N: Device now has an access_token and can start to talk to the homeserver - N->>E: SecureSend({ "type": "m.login.success" }) - end end ``` @@ -1048,7 +1044,7 @@ This is achieved as following: 1. **Existing device confirms that the new device has indeed logged in successfully** -On receipt of an m.login.success message the existing device queries the homeserver to check that the is a device online +On receipt of an `m.login.success` message the existing device queries the homeserver to check that the is a device online with the corresponding device_id (from the `m.login.protocol` message). It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) @@ -1080,7 +1076,7 @@ The existing device sends a `m.login.secrets` message via the secure channel: 3. **New device cross-signs itself and uploads device keys** -On receipt of the m.login.secrets message the new device can store the secrets locally +On receipt of the `m.login.secrets` message the new device can store the secrets locally The new device can then generate the cross-signing signature for itself. @@ -1139,7 +1135,7 @@ activate HS alt is device not found note over E: We should wait and retry for 10 seconds HS->>E: 404 Not Found - E->>N: { "type": "m.login.failed", "reason": "TODO" } + E->>N: { "type": "m.login.failure", "reason": "device_not_found" } else is device found HS->>E: 200 OK deactivate HS @@ -1253,9 +1249,9 @@ Fields: |Field|Type|| |--- |--- |--- | |`type`|required `string`|`m.login.failure`| -|`reason`|required `string`| One of: `TODO`| +|`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| |`homeserver`|`string`| When the existing device is sending this it can include the Base URL of the homeserver so that the new device can at least save the user the hassle of typing it in| - + Example: ```json From e1f7367b55a916cc1cf27b97aa2c0e12ccd998fa Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 15 Apr 2024 09:59:17 +0100 Subject: [PATCH 16/70] Formatting --- proposals/4108-oidc-qr-login.md | 168 ++++++++++++++++---------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 962ddcf392a..cee6057b0e0 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -24,7 +24,7 @@ In order for the new device to be fully set up, it needs to exchange information It is proposed that an HTTP-based protocol be used to establish an ephemeral bi-directional communication session over which the two devices can exchange the necessary data. This session is described as "insecure" as it provides no -end-to-end confidentiality nor authenticity by itself—these are layered on top of it. +end-to-end confidentiality nor authenticity by itself---these are layered on top of it. #### High-level description @@ -242,7 +242,7 @@ Access-Control-Expose-Headers: ETag ``` To support usage from web browsers the rendezvous URLs should allow CORS requests from any origin and expose the headers -which aren’t on the CORS [request header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and +which aren't on the CORS [request header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and [response header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header) safelists: ```http @@ -394,8 +394,8 @@ with an empty payload. It parses the **id** and **server** received. Device G displays a QR code containing: -- its public key **Gp** -- the insecure rendezvous session **URL** +- Its public key **Gp** +- The insecure rendezvous session **URL** - An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device that wishes to "reciprocate" a login - If the intent is to reciprocate a login, then the **homeserver base URL** @@ -618,9 +618,9 @@ Since Device G has no way of authenticating Device S, an attacker present for th attempt to mimic Device S in order to get their Device S signed in instead. - In step 3, Specter can shoulder surf the QR code scanning to obtain **Gp**. -- In step 4, Specter can intercept S’s payload and replace it with a payload of their own, replacing **Sp** with its +- In step 4, Specter can intercept S's payload and replace it with a payload of their own, replacing **Sp** with its own key. -- The attack is only thwarted in step 7, because Device S won’t ever display the indicator of success to the user. The +- The attack is only thwarted in step 7, because Device S won't ever display the indicator of success to the user. The user then must cancel the process on Device G, preventing it from sharing any sensitive material. ### The OIDC login part and set up of E2EE @@ -657,9 +657,9 @@ homeserver specified: ```json { - "type": "m.login.protocols", - "protocols": ["device_authorization_grant"], - "homeserver": "https://synapse-oidc.lab.element.dev" + "type": "m.login.protocols", + "protocols": ["device_authorization_grant"], + "homeserver": "https://synapse-oidc.lab.element.dev" } ``` @@ -684,7 +684,7 @@ With response like: Content-Type: application/json { - "issuer": "https://auth-oidc.lab.element.dev/" + "issuer": "https://auth-oidc.lab.element.dev/" } ``` @@ -706,22 +706,22 @@ With response like: Content-Type: application/json { - "issuer": "https://auth-oidc.lab.element.dev/", - "authorization_endpoint": "https://auth-oidc.lab.element.dev/authorize", - "token_endpoint": "https://auth-oidc.lab.element.dev/oauth2/token", - "jwks_uri": "https://auth-oidc.lab.element.dev/oauth2/keys.json", - "registration_endpoint": "https://auth-oidc.lab.element.dev/oauth2/registration", - "scopes_supported": ["openid", "email"], - "response_types_supported": [...], - "response_modes_supported": [...], - "grant_types_supported": [ - "authorization_code", - "refresh_token", - "client_credentials", - "urn:ietf:params:oauth:grant-type:device_code" - ], - ... - "device_authorization_endpoint": "https://auth-oidc.lab.element.dev/oauth2/device" + "issuer": "https://auth-oidc.lab.element.dev/", + "authorization_endpoint": "https://auth-oidc.lab.element.dev/authorize", + "token_endpoint": "https://auth-oidc.lab.element.dev/oauth2/token", + "jwks_uri": "https://auth-oidc.lab.element.dev/oauth2/keys.json", + "registration_endpoint": "https://auth-oidc.lab.element.dev/oauth2/registration", + "scopes_supported": ["openid", "email"], + "response_types_supported": [...], + "response_modes_supported": [...], + "grant_types_supported": [ + "authorization_code", + "refresh_token", + "client_credentials", + "urn:ietf:params:oauth:grant-type:device_code" + ], + ... + "device_authorization_endpoint": "https://auth-oidc.lab.element.dev/oauth2/device" } ``` @@ -748,12 +748,12 @@ With response like: Content-Type: application/json { - "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", - "user_code": "123456", - "verification_uri": "https://auth-oidc.lab.element.dev/link", - "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456", - "expires_in": 1800, - "interval": 5 + "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + "user_code": "123456", + "verification_uri": "https://auth-oidc.lab.element.dev/link", + "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456", + "expires_in": 1800, + "interval": 5 } ``` @@ -770,13 +770,13 @@ indicates that want to use protocol `device_authorization_grant` along with the ```json { - "type": "m.login.protocol", - "protocol": "device_authorization_grant", - "device_authorization_grant": { - "verification_uri": "https://auth-oidc.lab.element.dev/link", - "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" - }, - "device_id": "ABCDEFGH" + "type": "m.login.protocol", + "protocol": "device_authorization_grant", + "device_authorization_grant": { + "verification_uri": "https://auth-oidc.lab.element.dev/link", + "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" + }, + "device_id": "ABCDEFGH" } ``` @@ -928,10 +928,10 @@ Then we continue with the actual login: On receipt of the `m.login.protocol_accepted` message: - In accordance with [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1) the new device must display -the user_code in order that the user can confirm it on the OIDC Provider if required. +the `user_code` in order that the user can confirm it on the OIDC Provider if required. - The new device then starts to poll the OIDC Provider by making [Device Access Token Requests](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4) using the interval and bounded -by expires_in. +by `expires_in`. *New device => OIDC Provider via HTTP* @@ -962,7 +962,7 @@ and expecting to receive an HTTP 404 response. If the device already exists then the login request should be rejected with an `m.login.failure` and reason `device_already_exists`. If no existing device was found then the existing device opens the `verification_uri_complete` - falling back to the -`verification_uri`, if `verification_uri_complete` isn’t present - in a system browser. +`verification_uri`, if `verification_uri_complete` isn't present - in a system browser. Ideally this is in a trusted/secure environment where the cookie jar and password manager features are available. e.g. on iOS this could be a `ASWebAuthenticationSession` @@ -973,7 +973,7 @@ The existing device then sends an acknowledgement message to let the other devic ```json { - "type": "m.login.protocol_accepted" + "type": "m.login.protocol_accepted" } ``` @@ -1050,7 +1050,7 @@ with the corresponding device_id (from the `m.login.protocol` message). It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) and expecting to receive an HTTP 200 response. -If the device isn’t immediately visible it can repeat the `GET` request for up to, say, 10 seconds to allow for any latency. +If the device isn't immediately visible it can repeat the `GET` request for up to, say, 10 seconds to allow for any latency. If no device is found then the process should be stopped. @@ -1060,17 +1060,17 @@ The existing device sends a `m.login.secrets` message via the secure channel: ```json { - "type": "m.login.secrets", - "cross_signing": { - "master_key": "$base64_of_the_key", - "self_signing_key": "$base64_of_the_key", - "user_signing_key": "$base64_of_the_key" - }, - "backup": { - "algorithm": "foobar", - "key": "base64_of_the_backup_recovery_key", - "backup_version": "version_string" - } + "type": "m.login.secrets", + "cross_signing": { + "master_key": "$base64_of_the_key", + "self_signing_key": "$base64_of_the_key", + "user_signing_key": "$base64_of_the_key" + }, + "backup": { + "algorithm": "foobar", + "key": "base64_of_the_backup_recovery_key", + "backup_version": "version_string" + } } ``` @@ -1144,7 +1144,7 @@ deactivate HS activate N note over N: 3) New device stores the secrets locally - + alt is cross_signing present in m.login.secrets? note over N: New device signs itself note over N: New device uploads device keys and cross-signing signature: @@ -1188,9 +1188,9 @@ Fields: ```json { - "type": "m.login.protocols", - "protocols": ["device_authorization_grant"], - "homeserver": "https://matrix-client.matrix.org" + "type": "m.login.protocols", + "protocols": ["device_authorization_grant"], + "homeserver": "https://matrix-client.matrix.org" } ``` @@ -1213,13 +1213,13 @@ Example: ```json { - "type": "m.login.protocol", - "protocol": "device_authorization_grant", - "device_authorization_grant": { - "verification_uri_complete": "https://id.matrix.org/device/abcde", - "verification_uri": "..." - }, - "device_id": "ABCDEFGH" + "type": "m.login.protocol", + "protocol": "device_authorization_grant", + "device_authorization_grant": { + "verification_uri_complete": "https://id.matrix.org/device/abcde", + "verification_uri": "..." + }, + "device_id": "ABCDEFGH" } ``` @@ -1234,7 +1234,7 @@ Example: ```json { - "type":"m.login.protocol_accepted" + "type":"m.login.protocol_accepted" } ``` @@ -1251,14 +1251,14 @@ Fields: |`type`|required `string`|`m.login.failure`| |`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| |`homeserver`|`string`| When the existing device is sending this it can include the Base URL of the homeserver so that the new device can at least save the user the hassle of typing it in| - + Example: ```json { - "type":"m.login.failure", -"reason": "unsupported", - "homeserver": "https://matrix-client.matrix.org" + "type":"m.login.failure", + "reason": "unsupported", + "homeserver": "https://matrix-client.matrix.org" } ``` @@ -1278,7 +1278,7 @@ Example: ```json { - "type":"m.login.declined" + "type":"m.login.declined" } ``` @@ -1298,7 +1298,7 @@ Example: ```json { - "type": "m.login.success" + "type": "m.login.success" } ``` @@ -1320,17 +1320,17 @@ Example: ```json { - "type": "m.login.secrets", - "cross_signing": { - "master_key": "$base64_of_the_key", - "self_signing_key": "$base64_of_the_key", - "user_signing_key": "$base64_of_the_key" - }, - "backup": { - "algorithm": "foobar", - "key": "base64_of_the_backup_recovery_key", - "backup_version": "version_string" - } + "type": "m.login.secrets", + "cross_signing": { + "master_key": "$base64_of_the_key", + "self_signing_key": "$base64_of_the_key", + "user_signing_key": "$base64_of_the_key" + }, + "backup": { + "algorithm": "foobar", + "key": "base64_of_the_backup_recovery_key", + "backup_version": "version_string" + } } ``` From d8c62ed866c394bc582f10b17d8c2ae217e77403 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 15 Apr 2024 09:59:59 +0100 Subject: [PATCH 17/70] Whitespace --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index cee6057b0e0..0ab65c6e82e 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -353,7 +353,7 @@ The above rendezvous session is insecure, providing no confidentiality nor authe even arbitrary network participants which possess the rendezvous session URL. To provide a secure channel on top of this insecure rendezvous session transport, we propose the following scheme. -This scheme is essentially[ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) +This scheme is essentially [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) instantiated with X25519, HKDF-SHA256 for the KDF and ChaCha20-Poly1305 (as specified by [RFC8439](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8)) for the authenticated encryption. Therefore, existing security analyses of ECIES are applicable in this setting too. Nevertheless we include below a short From ad31acf891f29095231da1a6f8e711c60c23c907 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 15 Apr 2024 10:04:28 +0100 Subject: [PATCH 18/70] More formatting --- proposals/4108-oidc-qr-login.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 0ab65c6e82e..92425ba676a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1068,7 +1068,7 @@ The existing device sends a `m.login.secrets` message via the secure channel: }, "backup": { "algorithm": "foobar", - "key": "base64_of_the_backup_recovery_key", + "key": "$base64_of_the_backup_recovery_key", "backup_version": "version_string" } } @@ -1093,7 +1093,7 @@ Content-Type: application/json { "device_keys": { - "algorithms": [ + "algorithms": [ "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2" ], @@ -1328,7 +1328,7 @@ Example: }, "backup": { "algorithm": "foobar", - "key": "base64_of_the_backup_recovery_key", + "key": "$base64_of_the_backup_recovery_key", "backup_version": "version_string" } } From aa37af9b38ed9426655de07795a74b459b25ba4a Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 16 Apr 2024 12:23:23 +0000 Subject: [PATCH 19/70] Tweaks to the QR code login crypto (#4129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Derive separate encryption keys and nonces for the two sides of the secure channel - Spell out HKDF parameters in text too - Misc style fixes --------- Co-authored-by: Damir Jelić Co-authored-by: Hugh Nimmo-Smith --- proposals/4108-oidc-qr-login.md | 92 +++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 92425ba676a..fcb9c95e8db 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -375,8 +375,9 @@ Participants: Regardless of which device generates the QR code, either device can be the existing (already signed in) device. The other device is then the new device (one seeking to be signed in). -Symmetric encryption uses deterministic nonces, incrementing by `2` with each payload. Device S starts with `0`, using only -even nonces. Device A starts with `1`, using only odd nonces. +Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF. A +separate deterministic, monotonically-incrementing nonce is used for each sender. Devices initially set both nonces to +`0` and increment the corresponding nonce by `1` for each message sent and received. 1. **Ephemeral key pair generation** @@ -401,7 +402,8 @@ that wishes to "reciprocate" a login - If the intent is to reciprocate a login, then the **homeserver base URL** To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a -similar structure to that of the existing Device Verification QR code encoding described in [Client-Server API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). +similar structure to that of the existing Device Verification QR code encoding described in [Client-Server +API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). This is defined in detail in a separate section of this proposal. @@ -412,22 +414,41 @@ At this point Device S should check that the received intent matches what the us 4. **Device S sends the initial payload** -Device S computes a shared secret **SH** using ECDH between **Ss** and **Gp**, thereby establishing a secure channel -with Device G which can be layered on top of the insecure rendezvous session transport. It then discards **Ss** and -derives a symmetric encryption **EncKey** from **SH** using HKDF_SHA256, each 32 bytes in length. +Device S computes a shared secret **SH** by performing ECDH between **Ss** and **Gp**. It then discards **Ss** and +derives two 32-byte symmetric encryption keys from **SH** using HKDF-SHA256. One of those keys, **EncKey_S** is +used for messages encrypted by device S, while the other, **EncKey_G** is used for encryption by device G. -Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: +The keys are generated with the following HKDF parameters: -- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. +**EncKey_S** + +- `MATRIX_QR_CODE_LOGIN_ENCKEY_S|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating + device's and the scanning device's ephemeral public keys, encoded as unpadded base64. +- An all-zero salt. + +**EncKey_G** + +- `MATRIX_QR_CODE_LOGIN_ENCKEY_G|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating + device's and the scanning device's ephemeral public keys, encoded as unpadded base64. +- An all-zero salt. + +With this, Device S has established its side of the secure channel. Device S then derives a confirmation payload that +Device G can use to confirm that the channel is secure. It contains: + +- The string `MATRIX_QR_CODE_LOGIN_ENCKEY_S`, encrypted and authenticated with ChaCha20-Poly1305. - Its public ephemeral key **Sp**. ``` -Nonce := 0 +Nonce_S := 0 SH := ECDH(Ss, Gp) -EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN|" || Gp || "|" || Sp, salt=0, size=32) -NonceBytes := ToLowEndianBytes(Nonce)[..12] -TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_INITIATE") -Nonce := Nonce + 2 +EncKey_S := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_S|" || Gp || "|" || Sp, salt=0, size=32) + +// Stored, but not yet used +EncKey_G := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_G|" || Gp || "|" || Sp, salt=0, size=32) + +NonceBytes_S := ToLowEndianBytes(Nonce_S)[..12] +TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_S, NonceBytes_S, "MATRIX_QR_CODE_LOGIN_INITIATE") +Nonce_S := Nonce_S + 1 LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp) ``` @@ -440,7 +461,8 @@ Device G receives **LoginInitiateMessage** (potentially coming from Device S) fr polling with `GET` requests. It then does the reverse of the previous step, obtaining **Sp**, deriving the shared secret using **Gs** and **Sp**, -discarding **Gs** and decrypting (and authenticating) the **TaggedCiphertext**, obtaining a plaintext. +discarding **Gs**, deriving the two symmetric encryption keys **EncKey_S** and **EncKey_G**, then finally +decrypting (and authenticating) the **TaggedCiphertext** using **EncKey_S**, obtaining a plaintext. It checks that the plaintext matches the string `MATRIX_QR_CODE_LOGIN_INITIATE`, failing and aborting if not. @@ -448,10 +470,10 @@ It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGI as follows: ``` -Nonce := 1 -NonceBytes := ToLowEndianBytes(Nonce)[..12] -TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_OK") -Nonce := Nonce + 2 +Nonce_G := 1 +NonceBytes_G := ToLowEndianBytes(Nonce_G)[..12] +TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_G, NonceBytes_G, "MATRIX_QR_CODE_LOGIN_OK") +Nonce_G := Nonce_G + 1 LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) ``` @@ -470,27 +492,29 @@ was indeed sent by Device G. It then verifies the plaintext matches `MATRIX_QR_C Nonce_G := 1 (TaggedCiphertext, Sp) := Unpack(Message) NonceBytes := ToLowEndianBytes(Nonce)[..12] -Plaintext := ChaCha20Poly1305_Decrypt(EncKey, NonceBytes, TaggedCiphertext) -Nonce_G := Nonce_G + 2 +Plaintext := ChaCha20Poly1305_Decrypt(EncKey_G, NonceBytes, TaggedCiphertext) +Nonce_G := Nonce_G + 1 unless Plaintext == "MATRIX_QR_CODE_LOGIN_OK": FAIL ``` -If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and **Sp**: +If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and +**Sp**: ``` CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2) CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) ``` -Device S then displays an indication to the user that the secure channel has been established and that the **CheckCode** -should be entered on the other device when prompted. e.g. wording to say "secure connection established"; enter the code -XY on your other device; +Device S then displays an indicator to the user that the secure channel has been established and that the **CheckCode** +should be entered on the other device when prompted. Example wording could say "Secure connection established. Enter the +code XY on your other device." 7. **Out-of-band confirmation** -**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of ECIES.* +**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of +ECIES.* Device G asks the user to enter the **CheckCode** that is being displayed on Device S. @@ -507,7 +531,8 @@ CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) If the code that the user enters matches then the secure channel is established. -Subsequent payloads can be sent encrypted with **EncKey** with the nonces incremented as described above. +Subsequent payloads sent from G should be encrypted using **EncKey_G**, while payloads sent from S should be +encrypted with **EncKey_S**, incrementing the corresponding nonce for each message sent/received. #### Sequence diagram @@ -537,7 +562,7 @@ sequenceDiagram S->>+Z: GET /_matrix/client/rendezvous/abc-def Z->>-S: 200 OK
ETag: 1 - note over S: 4) Device S computes SH, EncKey and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session + note over S: 4) Device S computes SH, EncKey_S, EncKey_G and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session S->>+Z: PUT /_matrix/client/rendezvous/abc-def
If-Match: 1
Body: LoginInitiateMessage Z->>-S: 202 Accepted
ETag: 2 deactivate S @@ -546,7 +571,7 @@ sequenceDiagram activate G Z->>-G: 200 OK
ETag: 2
Body: Data - note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH and EncKey + note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH, EncKey_S and EncKey_G note over G: Device G checks that the plaintext matches MATRIX_QR_CODE_LOGIN_INITIATE note over G: Device G computes LoginOkMessage and sends to the rendezvous session @@ -576,21 +601,20 @@ sequenceDiagram Conceptually, once established, the secure channel offers two operations, `SecureSend` and `SecureReceive`, which wrap the `Send` and `Receive` operations offered by the rendezvous session API to securely send and receive data between two devices. -At the end of the establishment phase, the next nonce for Device G should be `3` and the next nonce for Device S should -be `2`. +At the end of the establishment phase, the next nonce for each device should be `1`. Device G sets: ``` -Nonce := 3 -NonceOther = 2 +Nonce_G := 1 +Nonce_S := 1 ``` Device S sets: ``` -Nonce := 2 -NonceOther := 3 +Nonce_G := 1 +Nonce_S := 1 ``` #### Threat analysis From 289a810f605c51b03d6405879a460b3b9b3c277b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 16 Apr 2024 16:06:07 +0100 Subject: [PATCH 20/70] Add missing device id check step to sequence diagram --- proposals/4108-oidc-qr-login.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index fcb9c95e8db..49fddffb066 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1018,6 +1018,13 @@ sequenceDiagram rect rgba(0,255,0, 0.1) + E->>HS: GET /_matrix/client/v3/devices/{device_id} + alt device already exists + HS->>E: 200 OK + E->>N: SecureSend({ "type": "m.login.failure", "reason": "device_already_exists" }) + else device not found + HS->>E: 404 Not Found + end par E->>N: SecureSend({"type":"m.login.protocol_accepted"}) note over N: 4) New device polls the OIDC Provider awaiting the outcome as per RFC8628 OIDC From 25e8fcb2c34089efd707a98917d24a57d26d23b9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 18 Apr 2024 18:29:00 +0100 Subject: [PATCH 21/70] Remove references to rendezvous session ID --- proposals/4108-oidc-qr-login.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 49fddffb066..7b5b6015acc 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -141,7 +141,7 @@ HTTP response codes, and Matrix error codes: - `400 Bad Request` (`M_INVALID_PARAM`) - a malformed [`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) header was provided. -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) - `412 Precondition Failed` (`M_CONCURRENT_WRITE`, a new error code) - when the ETag does not match - `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited @@ -206,7 +206,7 @@ Pragma: no-cache HTTP response codes: - `204 No Content` - rendezvous session cancelled -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited ##### Authentication @@ -389,7 +389,7 @@ separate deterministic, monotonically-incrementing nonce is used for each sender 2. **Create rendezvous session** Device G creates a rendezvous session by making a `POST` request (as described previously) to the nominated homeserver -with an empty payload. It parses the **id** and **server** received. +with an empty payload. It parses the **url** received. 3. **Initial key exchange** From e12945c5fdb9b831edc4f702862cdb433a96961b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 18 Apr 2024 18:43:34 +0100 Subject: [PATCH 22/70] Fix POST endpoint and Location references --- proposals/4108-oidc-qr-login.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 7b5b6015acc..4012a598426 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -234,7 +234,7 @@ In order to make sure that no intermediate caches manipulate the ETags, the rend ###### CORS -For the `POST /_matrix/client/rendezvous` API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) +For the `POST /_matrix/client/v1/rendezvous` API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) headers, the ETag response header should also be allowed by exposing the following CORS header: ```http @@ -265,6 +265,8 @@ of preference: #### Example API usage +n.b. This example demonstrates how the 307 response can be used to delegate the rendezvous session to a different server. + ```mermaid sequenceDiagram participant A as Device A @@ -273,8 +275,8 @@ sequenceDiagram participant B as Device B Note over A: Device A determines which rendezvous server to use - A->>+HS: POST /_matrix/client/rendezvous
Content-Type: text/plain
"Hello from A" - HS->>-A: 307 https://rz.example.com/foo + A->>+HS: POST /_matrix/client/v1/rendezvous
Content-Type: text/plain
"Hello from A" + HS->>-A: 307 Temporary Redirect
Location: https://rz.example.com/foo A->>+R: POST /foo
Content-Type: text/plain
"Hello from A" R->>-A: 201 Created
ETag: 1
{"url":"https://rz.example.com/abc-def-123-456"} @@ -541,15 +543,15 @@ The sequence diagram for the above is as follows: ```mermaid sequenceDiagram participant G as Device G - participant Z as Rendezvous server + participant Z as Homeserver with embedded Rendezvous Server
matrix.example.com participant S as Device S note over G,S: 1) Devices G and S each generate an ephemeral Curve25519 key pair activate G note over G: 2) Device G creates a rendezvous session as follows - G->>+Z: POST /_matrix/client/rendezvous - Z->>-G: 201 Created
Location: /_matrix/client/rendezvous/abc-def
ETag: 1 + G->>+Z: POST /_matrix/client/v1/rendezvous + Z->>-G: 201 Created
ETag: 1
{"url": "https://matrix.example.com/_synapse/client/rendezvous/abc-def"} note over G: 3) Device G generates and displays a QR code containing
its ephemeral public key and the rendezvous session URL @@ -559,15 +561,15 @@ sequenceDiagram activate S note over S: Device S validates QR scanned and the rendezvous session URL - S->>+Z: GET /_matrix/client/rendezvous/abc-def + S->>+Z: GET /_synapse/client/rendezvous/abc-def Z->>-S: 200 OK
ETag: 1 note over S: 4) Device S computes SH, EncKey_S, EncKey_G and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session - S->>+Z: PUT /_matrix/client/rendezvous/abc-def
If-Match: 1
Body: LoginInitiateMessage + S->>+Z: PUT /_synapse/client/rendezvous/abc-def
If-Match: 1
Body: LoginInitiateMessage Z->>-S: 202 Accepted
ETag: 2 deactivate S - G->>+Z: GET /_matrix/client/rendezvous/abc-def
If-None-Match: 1 + G->>+Z: GET /_synapse/client/rendezvous/abc-def
If-None-Match: 1 activate G Z->>-G: 200 OK
ETag: 2
Body: Data @@ -576,12 +578,12 @@ sequenceDiagram note over G: Device G computes LoginOkMessage and sends to the rendezvous session - G->>+Z: PUT /_matrix/client/rendezvous/abc-def
If-Match: 2
Body: LoginOkMessage + G->>+Z: PUT /_synapse/client/rendezvous/abc-def
If-Match: 2
Body: LoginOkMessage Z->>-G: 202 Accepted
ETag: 3 deactivate G activate S - S->>+Z: GET /_matrix/client/rendezvous/abc-def
If-None-Match: 2 + S->>+Z: GET /_synapse/client/rendezvous/abc-def
If-None-Match: 2 Z->>-S: 200 OK
ETag: 3
Body: Data note over S: 6) Device S attempts to parse Data as LoginOkMessage From 4f9a4a42e6ba442989bee06a037eaedecc1cc846 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Apr 2024 17:54:11 +0100 Subject: [PATCH 23/70] Rendezvous sessions should have a fixed lifetime and allow enough time to complete login --- proposals/4108-oidc-qr-login.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 4012a598426..ff71531f7af 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -57,9 +57,8 @@ n.b. Once a new payload has been sent there is no mechanism to retrieve previous #### Expiry The rendezvous session (i.e. the payload) SHOULD expire after a period of time communicated to clients via the `Expires` -header. After this point, any further attempts to query or update the payload MUST fail. The expiry time SHOULD be -extended every time the payload is updated. The rendezvous session can be manually expired with a `DELETE` call to the -rendezvous session. +header. After this point, any further attempts to query or update the payload MUST fail. The rendezvous session can be +manually expired with a `DELETE` call to the rendezvous session. ####  API @@ -220,7 +219,10 @@ The server should allow a minimum payload size of 10KB and enforce a maximum pay ###### Maximum duration of a rendezvous -The rendezvous session only needs to persist for the duration of the handshake. So a timeout such as 30 seconds is adequate. +The rendezvous session needs to persist for the duration of the login. So a timeout such as 60 seconds should be adequate. + +It does need to allow the user another time to confirm that the secure channel has been established and complete any extra +OIDC Provider mandated login steps such as MFA. Clients should handle the case of the rendezvous session being cancelled or timed out by the server. From fbb30ec812b5ad5d05c6988d6cb4dd1c10802658 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Apr 2024 11:03:39 +0100 Subject: [PATCH 24/70] Set max payload size to 4KB and fix content-type as text/plain (#4134) * Set max payload size and fix content-type as text/plain * Set max payload size to 4KB --- proposals/4108-oidc-qr-login.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index ff71531f7af..824da5c202b 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -77,7 +77,7 @@ This would be part of the Client-Server API. HTTP request headers: - `Content-Length` - required -- `Content-Type` - required +- `Content-Type` - required, must be `text/plain` HTTP request body: @@ -86,7 +86,8 @@ HTTP request body: HTTP response codes, and Matrix error codes: - `201 Created` - rendezvous session created -- `400 Bad Request` (``M_MISSING_PARAM``) - no `Content-Length` was provided. +- `400 Bad Request` (``M_MISSING_PARAM``) - either `Content-Length` and/or `Content-Type` was not provided. +- `400 Bad Request` (`M_INVALID_PARAM`) - an invalid `Content-Type` was given. - `403 Forbidden` (``M_FORBIDDEN``) - forbidden by server policy - `413 Payload Too Large` (``M_TOO_LARGE``) - the supplied payload is too large - `429 Too Many Requests` (``M_UNKNOWN``) - the request has been rate limited @@ -126,7 +127,7 @@ Pragma: no-cache HTTP request headers: - `Content-Length` - required -- `Content-Type` - required +- `Content-Type` - required, must be `text/plain` - `If-Match` - required. The ETag of the last payload seen by the requesting device. HTTP request body: @@ -139,7 +140,7 @@ HTTP response codes, and Matrix error codes: - `400 Bad Request` (`M_MISSING_PARAM`) - a required header was not provided. - `400 Bad Request` (`M_INVALID_PARAM`) - a malformed [`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) -header was provided. +header was provided or invalid `Content-Type`. - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) - `412 Precondition Failed` (`M_CONCURRENT_WRITE`, a new error code) - when the ETag does not match - `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large @@ -165,7 +166,7 @@ HTTP response codes, and Matrix error codes: HTTP response headers for `200 OK`: -- `Content-Type` - required +- `Content-Type` - required, `text/plain` - common headers as defined above HTTP response headers for `304 Not Modified`: @@ -215,7 +216,7 @@ described later. ##### Maximum payload size -The server should allow a minimum payload size of 10KB and enforce a maximum payload size which is recommended to be 100KB. +The server enforce a maximum payload size of 4KB. ###### Maximum duration of a rendezvous @@ -351,6 +352,21 @@ is possible to use it to circumvent firewalls and other network security measure Implementation may want to block their production IP addresses from being able to make requests to the rendezvous endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. +##### Unsafe content + +Because the rendezvous session is not authenticated, it is possible for an attacker to use it to distribute malicious +content. + +This could lead to a reputational problem for the homeserver domain or IPs, as well as potentially causing harm to users. + +Mitigations that are included in this proposal: + +- the low maximum payload size +- restricted allowed content type +- the rendezvous session should be short-lived +- the ability for the rendezvous session to be hosted on a different domain to the homeserver (via +the `307 Temporary Redirect` response behaviour) + ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or From fe939bed89d9b043984aa483593b986c1f105e01 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Apr 2024 16:41:00 +0100 Subject: [PATCH 25/70] Cross signing is mandatory The user must having cross signing set up and the private keys available on the existing device for the process to complete. --- proposals/4108-oidc-qr-login.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 824da5c202b..e099b9291ad 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -20,6 +20,12 @@ In order for the new device to be fully set up, it needs to exchange information - The existing device can facilitate the new device in getting an access token - The existing device shares the secrets necessary to set up end-to-end encryption +This proposal is split into three parts: + +1. An insecure rendezvous session API to allow the two devices to exchange the necessary data +2. A secure channel to protect the data exchanged over the rendezvous session +3. The OIDC login part and set up of E2EE + ### Insecure rendezvous session It is proposed that an HTTP-based protocol be used to establish an ephemeral bi-directional communication session over @@ -1196,18 +1202,11 @@ deactivate HS activate N note over N: 3) New device stores the secrets locally -alt is cross_signing present in m.login.secrets? note over N: New device signs itself note over N: New device uploads device keys and cross-signing signature: N->>+HS: POST /_matrix/client/v3/keys/upload HS->>-N: 200 OK -else - note over N: New device uploads device keys only: - N->>+HS: POST /_matrix/client/v3/keys/upload - HS->>-N: 200 OK -end - alt is backup present in m.login.secrets? note over N: New device connects to room-key backup end @@ -1364,7 +1363,7 @@ Fields: |Field|Type|| |--- |--- |--- | |`type`|required `string`|`m.login.secrets`| -|`cross_signing`|`object`|
Field Type
master_key required string Unpadded base64 encoded private key
self_signing_key required string Unpadded base64 encoded private key
user_signing_key required string Unpadded base64 encoded private key
| +|`cross_signing`|required `object`|
Field Type
master_key required string Unpadded base64 encoded private key
self_signing_key required string Unpadded base64 encoded private key
user_signing_key required string Unpadded base64 encoded private key
| |`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string Unpadded base64 encoded private/secret key
backup_version required string The backup version as returned by [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3room_keysversion)
| Example: @@ -1465,6 +1464,9 @@ For a new device it would need to know the homeserver ahead of time in order to Additionally the new device needs to either have an existing (i.e. static) OIDC client registered with the OIDC Provider already, or the OIDC Provider must support and allow dynamic client registration as described in [MSC2966](https://github.com/matrix-org/matrix-spec-proposals/pull/2966). +The feature is also only available where a user has cross-signing set up and the existing device to be used has the +Master Signing Key, Self Signing Key and User Signing Key stored locally so that they can be shared with the new device. + ## Potential issues Because this is an entirely new set of functionality it should not cause issue with any existing Matrix functions or capabilities. From 76f175b0110dbda4deb33c33c692a1a7202a42ba Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Apr 2024 16:57:32 +0100 Subject: [PATCH 26/70] Use unstable prefix for errcode --- proposals/4108-oidc-qr-login.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index e099b9291ad..da5d535682e 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1562,6 +1562,26 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin } ``` +Furthermore, where a new `errcode` is being introduced the existing `M_UNKNOWN` code should be used instead, with the new +code placed in a `org.matrix.msc4108.errcode` field instead. For example, instead of: + +```json +{ + "errcode": "M_CONCURRENT_WRITE", + "error": "Data was modified" +} +``` + +The server should send: + +```json +{ + "errcode": "M_UNKNOWN", + "org.matrix.msc4108.errcode": "M_CONCURRENT_WRITE", + "error": "Data was modified" +} +``` + ## Dependencies This MSC builds on [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) (and its dependencies) which From 0ca3dea0bd9422011f440da79b32df189dcdf479 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Apr 2024 17:26:39 +0100 Subject: [PATCH 27/70] The If-Match header on PUT requests contains the ETag --- proposals/4108-oidc-qr-login.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index da5d535682e..ac85b678781 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -50,9 +50,9 @@ In this way, Device A and Device B can communicate by repeatedly inspecting and #### The send mechanism -Every send request MUST include an `ETag` header, whose value is supplied by the `ETag` header in the last `GET` +Every send request MUST include an `If-Match` header whose value is the `ETag` header in the last `GET` response seen by the requester. (The initiating device may also use the `ETag` supplied in the initial `POST` response -to immediately update the payload.) Updates will succeed only if the supplied `ETag` matches the server's current +to immediately update the payload.) Sends will succeed only if the supplied `ETag` matches the server's current revision of the payload. This prevents concurrent writes to the payload. The `ETag` header is standard, described by [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-etag). In this From 02f18e1a303cd669e5f22e407f4e310776a4c48e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 8 May 2024 11:15:29 +0100 Subject: [PATCH 28/70] Fix description of 304 GET response --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index ac85b678781..ea3bfce078c 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -166,7 +166,7 @@ only return data if given ETag does not match HTTP response codes, and Matrix error codes: - `200 OK` - payload returned -- `304 Not Modified` - when `If-None-Match` is supplied and the ETag does not match +- `304 Not Modified` - when `If-None-Match` is supplied and the ETag matched the existing payload - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited From f49fd7f5827b3be9a3d9d62f95c71620a8029104 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 28 May 2024 10:12:48 +0100 Subject: [PATCH 29/70] Fix m.login.failure reason typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Damir Jelić --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index ea3bfce078c..a4ed9e88141 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1307,7 +1307,7 @@ Example: ```json { "type":"m.login.failure", - "reason": "unsupported", + "reason": "unsupported_protocol", "homeserver": "https://matrix-client.matrix.org" } ``` From 73da95a970733f625340cb7bd928268b95a76aa9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 30 May 2024 13:39:39 +0100 Subject: [PATCH 30/70] Fix originator of m.login.declined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Damir Jelić --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index a4ed9e88141..56e4cbf4327 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1314,7 +1314,7 @@ Example: ##### `m.login.declined` -Sent by: existing device +Sent by: new device Purpose: Indicates that the user declined the request From 87f8317a902cd7bc5c2d2d225f71021b3a509e2d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 12 Jun 2024 13:32:34 +0100 Subject: [PATCH 31/70] Use server name rather than base URL and clarify well-known discovery --- proposals/4108-oidc-qr-login.md | 54 +++++++++++++++------------- proposals/images/4108-qr-mode04.png | Bin 1019 -> 917 bytes 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 56e4cbf4327..9d26cc56b32 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -425,7 +425,7 @@ Device G displays a QR code containing: - The insecure rendezvous session **URL** - An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device that wishes to "reciprocate" a login -- If the intent is to reciprocate a login, then the **homeserver base URL** +- If the intent is to reciprocate a login, then the Matrix homeserver **[server name](https://spec.matrix.org/v1.10/appendices/#server-name)** To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a similar structure to that of the existing Device Verification QR code encoding described in [Client-Server @@ -433,8 +433,8 @@ API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). This is defined in detail in a separate section of this proposal. -Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the -**homeserver base URL**. +Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the Matrix homeserver +**[server name](https://spec.matrix.org/v1.10/appendices/#server-name)**. At this point Device S should check that the received intent matches what the user has asked to do on the device. @@ -690,7 +690,7 @@ This can make it hard to read what is going on. The new device needs to know which homeserver it will be authenticating with. -In the case that the new device scanned the QR code then the homeserver base URL can be taken from the QR code and the +In the case that the new device scanned the QR code then the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver can be taken from the QR code and the new device proceeds to step 2 immediately. Otherwise the new device waits to be informed by receiving an `m.login.protocols` message from the existing device. @@ -709,14 +709,17 @@ homeserver specified: { "type": "m.login.protocols", "protocols": ["device_authorization_grant"], - "homeserver": "https://synapse-oidc.lab.element.dev" + "homeserver": "synapse-oidc.lab.element.dev" } ``` 2. **New device checks if it can use an available protocol** -Once the existing device knows which homeserver it is to use it then: +Once the existing device has determined the server name it then undertakes steps to determine if it is able to work with the homeserver. +The steps are as follows: + +- use [Server Discovery](https://spec.matrix.org/v1.10/client-server-api/#server-discovery) to determine the `base_url` from the well-known URI - checks that the homeserver is using delegated OIDC by calling `GET /_matrix/client/v1/auth_issuer` from [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965): *New device => Homeserver via HTTP* @@ -847,22 +850,22 @@ sequenceDiagram rect rgba(255,0,0, 0.1) #alt if New device scanned QR code note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel - note over N: 1) New device got Homeserver base URL from QR code + note over N: 1) New device got server name from the QR code #else if Existing device scanned QR code # note over E: Existing device completes step 6 # note over E: Existing device displays checkmark and CheckCode # note over E: 1) Existing device sends m.login.protocols message - # E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"}) + # E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) # note over N: New device waits for user to confirm secure channel from step 7 - # Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"} + # Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} # note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: - + note over N: Use well-known discovery to get the homeserver base URL N->>+HS: GET /_matrix/client/v1/auth_issuer activate N HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} @@ -898,7 +901,7 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "https://matrix-client.matrix.org}) + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) end end ``` @@ -917,22 +920,23 @@ sequenceDiagram #alt if New device scanned QR code # note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel - # note over N: 1) New device got Homeserver base URL from QR code + # note over N: 1) New device got server name from the QR code rect rgba(0,0,255, 0.1) #else if Existing device scanned QR code note over E: Existing device completes step 6 note over E: Existing device displays checkmark and CheckCode note over E: 1) Existing device sends m.login.protocols message - E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"}) + E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) note over N: New device waits for user to confirm secure channel from step 7 - Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "https://matrix-client.matrix.org"} + Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: + note over N: Use well-known discovery to get the homeserver base URL N->>+HS: GET /_matrix/client/v1/auth_issuer activate N HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} @@ -966,7 +970,7 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "https://matrix-client.matrix.org}) + E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) end end ``` @@ -1234,13 +1238,13 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.protocols`| |`protocols`|required `string[]`|Array of: one of: `device_authorization_grant` | -|`homeserver`|required `string`|The base URL of the homeserver| +|`homeserver`|required `string`|The [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver| ```json { "type": "m.login.protocols", "protocols": ["device_authorization_grant"], - "homeserver": "https://matrix-client.matrix.org" + "homeserver": "matrix.org" } ``` @@ -1300,7 +1304,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.failure`| |`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| -|`homeserver`|`string`| When the existing device is sending this it can include the Base URL of the homeserver so that the new device can at least save the user the hassle of typing it in| +|`homeserver`|`string`| When the existing device is sending this it can include the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| Example: @@ -1308,7 +1312,7 @@ Example: { "type":"m.login.failure", "reason": "unsupported_protocol", - "homeserver": "https://matrix-client.matrix.org" + "homeserver": "matrix.org" } ``` @@ -1404,9 +1408,9 @@ The QR codes to be displayed and scanned using this format will encode binary st - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session URL as a UTF-8 string - the rendezvous session URL as a UTF-8 string -- If the QR code intent/mode is `0x04` then the homeserver base URL encode as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the homeserver base URL as a UTF-8 string - - the homeserver base URL as a UTF-8 string +- If the QR code intent/mode is `0x04` then the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the homeserver encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string + - the server name as a UTF-8 string For example, if Alice displays a QR code encoding the following binary string: @@ -1434,15 +1438,15 @@ Which looks as follows as a QR with error correction level Q: A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 encoded), at rendezvous session `https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) +`matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 04 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 47 68 74 74 70 73 3a 2f 2f 72 65 6e 64 65 7a 76 6f 75 73 2e 6c 61 62 2e 65 6c 65 6d 65 6e 74 2e 64 65 76 2f 65 38 64 61 36 33 35 35 2d 35 35 30 62 2d 34 61 33 32 2d 61 31 39 33 2d 31 36 31 39 64 39 38 33 30 36 36 38 -00 20 -68 74 74 70 73 3a 2f 2f 6d 61 74 72 69 78 2d 63 6c 69 65 6e 74 2e 6d 61 74 72 69 78 2e 6f 72 67 +00 0A +6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: diff --git a/proposals/images/4108-qr-mode04.png b/proposals/images/4108-qr-mode04.png index 68dc9c93b337ca6361f5a6ff9c74ca2389568f7e..b01ee1e3672f4a71d14c35a58647679c87c3b632 100644 GIT binary patch delta 876 zcmV-y1C#vw2bBjQiBL{Q4GJ0x0000DNk~Le0002D0002D0RsR40BWFJ+>s$Te9VC^0}O0{*;2Y}BT_a1U5W_bQ2*$oPuB6o+&SJylQzxx5xHm6_0ieG|MBq z)Hc`lFiQU9!pSb2S8wNJ)%;=jfAG8!)*lbkl$_gx_?7a&v=R;JPae8B^1on{|M>kr zmoAR$_T@A*u-iNWcfaeTi{FGwV33pSGrFtt{@!OV=UA@fBx#(d;0`LUq~RCmIp$}z zQ$Y6GGommw{0`+dZHOb+az(d=@%4$flb@6qwiMhMXfiww zSL1HO`{%u#{9?lT12>2O1Zt=(TkYp(;0wX}yrhDoe1tr+hF>*LL~D?0NB2zzuX751 zBG|;2=-Y^9;gsiZ9{Tu|e;Rp$ra}3aN~;&GQ}ENHN2GH%Kkiku?t3ns{PskWx)D-< zr$9tH?oQtzwy5x#?Eq;?PQkA#4Faj$MQpL{Z@+YNcJKRw0$Y8Nzd^+-0)$6H_QyTF ztt4tVBVz&WRyH`AN572c$47cOH^|x^M+n09md#JU^m2&FP4+<$e>XS?ioufC@G~@m zbVDR1SjTC+Si|oy9v&WzVnF|noajML!LQg>%CJZYoKRHgAl7hxKQiSonLNx9wxZ!2 zWw?5>M9mdaX+Qna%@aHb6_p2(Zn!`0orXsgCm+FzO=D28FwIdc zfPxJ9c#DSPdx4+;8K-a%z8AjnYB&lT#|N5(Bge~n2* zK~z|U?U+A~v^WsO3psEG7qG-NocT&{0cb7&S90cSSn>rRb6`>Xis{kr2D?&Mr(dg+ z<|hrN{=D}X;{W>4AM(HCB$_L8EEhMjL?I86IxWG4IpsOEHMPZ4Pkba?)RO2HKc1A= zXs(E#c}O&WLDrhPIKC*)mF8HEfA7d*TUX10N3Z3!{1xGb^KzsiwJzn}=k@REd`zT2 z$^Gm5kIsEc@VW8yY>w3WKvR&*tR;779iyn=BuB2i2i~;glKWs1X$j0Jlf@k6K}&F- zA@Oa|XF2}L=sf9(H8+k~9a)S|o2*-7p7o?n9(Qn)$3&Ygx`D?-OHN--f7x2I;k?y# zfVHh!aufQ4@i8a4A>}EI7iL^bPHzsDx$Xl7TzZ8{tXgu_O!(W$;cg^jSng0*OKuuZ z&%4E=J4oOW`4vVr;Yw>l!`(z>tK(5IIJwi3E0!0PD~T{~sHk^+aI2Oui=3R+92M8F zE2u$J6RCk6Z%yl99nV`Rs_}!CBP~} zm7j&ylB;5(8=?eRM;@!po9v&GFq#v_JB+*dt!UoGck>dQh=*Wm6L}FN?u}|2$rlAc4QS^EqKz zU+OIinI|bg`}J)Nf4M5G)tpum(;3`CZFt{DLvGX{VRmS!2(utK`)}I#oM2@UdAOFB zWlZ92F^q4YQ;<{MMa(WC5e|v(h@xIv0)gMbv2Ark-b79z?*=WwT$61JB)Jl<^>S)H zv;=wpMPaBNVys2vX%o3wOD@S@;P^F)UBEQLPg>Uyh{{%Jf1)S*d)}hNflf=9TZgx< zXkH{WcwWVR`lBNfPjJ#LVLzNW8~@(tbMi>L*p3K;q$h*SxN3>K&#{{VRG}dYEFKH@ z1&N|nD-tn1YZnmoe%wq;62fNLEsN6Q3EB@$|rYj>iB1002ovPDHLkV1gaB B*)0G7 From 0b315f54398222b5ec114f7ae1aaf6801e341aab Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sat, 21 Sep 2024 16:11:45 +0100 Subject: [PATCH 32/70] Update 4108-oidc-qr-login.md Co-authored-by: Denis Kasak --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 9d26cc56b32..cd9b7272b2e 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -529,7 +529,7 @@ If the above was successful, Device S then calculates a two digit **CheckCode** **Sp**: ``` -CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2) +CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp || "|" || Sp , salt=0, size=2) CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) ``` From 3545ca0498f1dba5ed7df6fe1085a726d61bfc45 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Sep 2025 14:01:13 +0100 Subject: [PATCH 33/70] Update to match spec 1.15 and MSC4341 --- proposals/4108-oidc-qr-login.md | 143 +++++++++++++------------------- 1 file changed, 58 insertions(+), 85 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index cd9b7272b2e..2d517f9c252 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1,4 +1,4 @@ -# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code +# MSC4108: Mechanism to allow OAuth sign in and E2EE set up via QR code We propose a method to allow an existing authenticated Matrix client to sign in a new client by scanning a QR code. The new client will be a fully bootstrapped Matrix cryptographic device, possessing all the necessary secrets, namely the @@ -7,7 +7,7 @@ cryptographic user identity ("cross-signing") and the server-side key backup dec This MSC supersedes [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906), [MSC3903](https://github.com/matrix-org/matrix-spec-proposals/pull/3903) and [MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886) which achieved a similar feature but did not -work with a homeserver using the delegated OIDC mechanism proposed by [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861). +work with a homeserver using [OAuth 2.0 API](https://spec.matrix.org/v1.15/client-server-api/#oauth-20-api). ## Proposal @@ -24,7 +24,7 @@ This proposal is split into three parts: 1. An insecure rendezvous session API to allow the two devices to exchange the necessary data 2. A secure channel to protect the data exchanged over the rendezvous session -3. The OIDC login part and set up of E2EE +3. The OAuth login part and set up of E2EE ### Insecure rendezvous session @@ -229,7 +229,7 @@ The server enforce a maximum payload size of 4KB. The rendezvous session needs to persist for the duration of the login. So a timeout such as 60 seconds should be adequate. It does need to allow the user another time to confirm that the secure channel has been established and complete any extra -OIDC Provider mandated login steps such as MFA. +homeserver mandated login steps such as MFA. Clients should handle the case of the rendezvous session being cancelled or timed out by the server. @@ -673,11 +673,11 @@ own key. - The attack is only thwarted in step 7, because Device S won't ever display the indicator of success to the user. The user then must cancel the process on Device G, preventing it from sharing any sensitive material. -### The OIDC login part and set up of E2EE +### The OAuth login part and set up of E2EE Once the secure channel has been established, the two devices can then communicate securely. -#### Login via OIDC Device Authorization Grant +#### Login via OAuth Device Authorization Grant In this section the sequence of steps depends on whether the new device generated or scanned the QR code. @@ -697,7 +697,7 @@ Otherwise the new device waits to be informed by receiving an `m.login.protocols The existing device would need to determine which "protocols" are available for the new device to use. -Currently this could only be device_authorization_grant meaning the OIDC Provider supports the +Currently this could only be device_authorization_grant meaning the homeserver supports the `urn:ietf:params:oauth:grant-type:device_code` grant type. If it is available then the existing device informs the new device by sending the `m.login.protocols` message with the @@ -720,12 +720,12 @@ Once the existing device has determined the server name it then undertakes steps The steps are as follows: - use [Server Discovery](https://spec.matrix.org/v1.10/client-server-api/#server-discovery) to determine the `base_url` from the well-known URI -- checks that the homeserver is using delegated OIDC by calling `GET /_matrix/client/v1/auth_issuer` from [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965): +- checks that the homeserver has the OAuth 2.0 API available by [`GET /_matrix/client/v1/auth_metadata`](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) *New device => Homeserver via HTTP* ```http -GET /_matrix/client/v1/auth_issuer HTTP/1.1 +GET /_matrix/client/v1/auth_metadata HTTP/1.1 Host: synapse-oidc.lab.element.dev Accept: application/json ``` @@ -736,28 +736,6 @@ With response like: 200 OK Content-Type: application/json -{ - "issuer": "https://auth-oidc.lab.element.dev/" -} -``` - -- parses the OIDC Provider (`issuer`) from the response -- fetches the OIDC Provider metadata as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965): - -*New device => OIDC Provider via HTTP* - -```http -GET /.well-known/openid-configuration HTTP/1.1 -Host: auth-oidc.lab.element.dev -Accept: application/json -``` - -With response like: - -```http -200 OK -Content-Type: application/json - { "issuer": "https://auth-oidc.lab.element.dev/", "authorization_endpoint": "https://auth-oidc.lab.element.dev/authorize", @@ -778,13 +756,13 @@ Content-Type: application/json } ``` -- either does Dynamic Client Registration as per [MSC2966](https://github.com/matrix-org/matrix-spec-proposals/pull/2966) -or uses a static OIDC client_id. We will use `my_client_id` as an example `client_id`. +- either does Dynamic Client Registration as per the existing [spec](https://spec.matrix.org/v1.15/client-server-api/#client-registration) +or uses a static `client_id`. We will use `my_client_id` as an example `client_id`. -- sends a [RFC8628 Device Authorization Request](https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) to the OIDC -Provider using the `device_authorization_endpoint`: +- sends a [RFC8628 Device Authorization Request](https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) to the homeserver +using the `device_authorization_endpoint` as described by [MSC4341]: -*New device => OIDC Provider via HTTP* +*New device => Homeserver via HTTP* ```http POST /oauth2/device HTTP/1.1 @@ -843,7 +821,6 @@ sequenceDiagram participant E as Existing device
already signed in participant Z as Rendezvous server participant N as New device
wanting to sign in - participant OP as OIDC Provider participant HS as Homeserver @@ -866,15 +843,13 @@ sequenceDiagram rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: note over N: Use well-known discovery to get the homeserver base URL - N->>+HS: GET /_matrix/client/v1/auth_issuer + N->>+HS: GET /_matrix/client/v1/auth_metadata activate N - HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} - Note over N: New device checks that it can communicate
with the issuer (OIDC Provider). Completing dynamic registration if needed - N->>+OP: GET /.well-known/openid-configuration - OP->>-N: 200 OK {..., "device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} - Note over N: Device now knows the OP and what the endpoint is, so then attempts to start the login - N->>+OP: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... - OP->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} + HS->>-N: 200 OK {"device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} + Note over N: New device checks that it can communicate with the homeserver. Completing dynamic registration if needed + Note over N: Device now knows the device_authorization_endpoint, so then attempts to start the login + N->>+HS: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... + HS->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} note over N: 3) New device informs existing device of choice of protocol: N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) @@ -914,7 +889,6 @@ sequenceDiagram participant E as Existing device
already signed in participant Z as Rendezvous server participant N as New device
wanting to sign in - participant OP as OIDC Provider participant HS as Homeserver @@ -937,15 +911,13 @@ sequenceDiagram rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: note over N: Use well-known discovery to get the homeserver base URL - N->>+HS: GET /_matrix/client/v1/auth_issuer + N->>+HS: GET /_matrix/client/v1/auth_metadata activate N - HS-->>-N: 200 OK {"issuer": "https://id.matrix.org"} - Note over N: New device checks that it can communicate
with the issuer (OIDC Provider). Completing dynamic registration if needed - N->>+OP: GET /.well-known/openid-configuration - OP->>-N: 200 OK {..., "device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} - Note over N: Device now knows the OP and what the endpoint is, so then attempts to start the login - N->>+OP: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... - OP->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} + HS->>-N: 200 OK {"device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} + Note over N: New device checks that it can communicate
with the homeserver. Completing dynamic registration if needed + Note over N: Device now knows the device_authorization_endpoint, so then attempts to start the login + N->>+HS: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... + HS->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} note over N: 3) New device informs existing device of choice of protocol: N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) @@ -977,17 +949,19 @@ sequenceDiagram Then we continue with the actual login: -4. **New device waits for approval from OIDC Provider** +4. **New device waits for approval from homeserver** On receipt of the `m.login.protocol_accepted` message: - In accordance with [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1) the new device must display -the `user_code` in order that the user can confirm it on the OIDC Provider if required. -- The new device then starts to poll the OIDC Provider by making +the `user_code` in order that the user can confirm it on the homeserver if required. +- The new device then starts to poll the homeserver by making [Device Access Token Requests](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4) using the interval and bounded by `expires_in`. -*New device => OIDC Provider via HTTP* +The above is as per [MSC4341]. + +*New device => Homeserver via HTTP* ```http POST /oauth2/token HTTP/1.1 @@ -1002,9 +976,9 @@ grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code - It then parses the [Device Access Token Response](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) and handles the different responses - If the user consents in the next step then the new device will receive an `access_token` and `refresh_token` etc. as -normal for OIDC with MSC3861. +normal as per [MSC4341]. -5. **User is asked by OIDC Provider to consent on existing device** +5. **User is asked by homeserver to consent on existing device** On receipt of the `m.login.protocol` message above, and having completed step 7 of the secure channel establishment, the existing device then asserts that there is no existing device corresponding to the `device_id` from the @@ -1031,10 +1005,10 @@ The existing device then sends an acknowledgement message to let the other devic } ``` -The user is then prompted to consent by the OIDC Provider. They may be prompted to undertake additional actions by the -OIDC Provider such as 2FA, but this is all handled within the browser. +The user is then prompted to consent by the homeserver. They may be prompted to undertake additional actions by the +homeserver such as 2FA, but this is all handled within the browser. -Note that the existing device does not see the new access token. This is one of the benefits of the OIDC architecture. +Note that the existing device does not see the new access token. This is one of the benefits of the OAuth architecture. The sequence diagram for steps 4 and 5 is as follows: @@ -1043,7 +1017,6 @@ sequenceDiagram participant E as Existing device
already signed in participant UA as Web Browser participant N as New device
wanting to sign in - participant OP as OIDC Provider participant HS as Homeserver rect rgba(0,255,0, 0.1) @@ -1057,20 +1030,20 @@ sequenceDiagram end par E->>N: SecureSend({"type":"m.login.protocol_accepted"}) - note over N: 4) New device polls the OIDC Provider awaiting the outcome as per RFC8628 OIDC + note over N: 4) New device polls the homeserver awaiting the outcome as per RFC 8628 / MSC4341 loop Poll for result at interval seconds - N->>OP: POST /token client_id=xyz
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=XYZ + N->>HS: POST /token client_id=xyz
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=XYZ alt pending - OP-->>N: 400 Bad Request {"error": "authorization_pending"} + HS-->>N: 400 Bad Request {"error": "authorization_pending"} else granted - OP-->>N: 200 OK {"access_token": "...", "token_type": "Bearer", ...} + HS-->>N: 200 OK {"access_token": "...", "token_type": "Bearer", ...} N->>E: SecureSend({ "type": "m.login.success" }) Note over N: Device now has an access_token and can start to talk to the homeserver else denied - OP-->>N: 400 Bad Request {"error": "authorization_declined"} + HS-->>N: 400 Bad Request {"error": "authorization_declined"} N->>E: SecureSend({"type":"m.login.declined"}) else expired - OP-->>N: 400 Bad Request {"error": "expired_token"} + HS-->>N: 400 Bad Request {"error": "expired_token"} N->>E: SecureSend({"type":"m.login.failure", "reason": "authorization_expired"}) end end @@ -1078,9 +1051,9 @@ sequenceDiagram E->>UA: 5) Existing device opens
verification_uri_complete (with fallback to verification_uri)
in the system web browser/ASWebAuthenticationSession: Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen rect rgba(240,240,240,0.5) - UA->>OP: GET https://id.matrix.org/device/abcde - OP->>UA: consent screen showing the user_code - UA->>OP: POST /allow or /deny + UA->>HS: GET https://id.matrix.org/device/abcde + HS->>UA: consent screen showing the user_code + UA->>HS: POST /allow or /deny end Note over UA: User closes browser end @@ -1093,8 +1066,8 @@ Once the new device has logged in and obtained an access token it will want to o end-to-end encryption on the device and make itself cross-signed. Before sharing the end-to-end encryption secrets the existing device should validate that the new device has -successfully obtained an access token from the OIDC Provider. The purpose of this is so that, if the user or OIDC -Provider has disallowed the login, the secrets are not leaked. +successfully obtained an access token from the homeserver. The purpose of this is so that, if the user or homeserver +has disallowed the login, the secrets are not leaked. If checked successfully then the existing device sends the following secrets to the new device: @@ -1180,7 +1153,6 @@ The sequence diagram for this would look as follows: sequenceDiagram participant E as Existing device
already signed in participant N as New device
wanting to sign in - participant OP as OIDC Provider participant HS as Homeserver rect rgba(0,255,0, 0.1) @@ -1260,7 +1232,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.protocol`| |`protocol`|required `string`|One of: `device_authorization_grant`| -|`device_authorization_grant`|Required `object` where `protocol` is `device_authorization_grant`|These values are taken from the RFC8628 Device Authorization Response that the new device received from the OIDC Provider:
Field Type
verification_uri required string
verification_uri_complete string
| +|`device_authorization_grant`|Required `object` where `protocol` is `device_authorization_grant`|These values are taken from the RFC8628 Device Authorization Response that the new device received from the homeserver:
Field Type
verification_uri required string
verification_uri_complete string
| |`device_id`|required `string`|The device ID that the new device will use| Example: @@ -1460,13 +1432,13 @@ Before offering this capability it would make sense that the device can check th Where the homeserver is known: 1. Check if the homeserver has a rendezvous session API available (/versions) from this MSC -1. Check that the homeserver is using the OIDC architecture (/auth_issuer) from MSC2965 -1. Check that the Device Authorization Grant is available on the OIDC Provider from MSC2965 +1. Check that the homeserver is using the OAuth 2.0 API using [server metadata discovery](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) +1. Check that the Device Authorization Grant is available as per [MSC4341] For a new device it would need to know the homeserver ahead of time in order to do these checks. -Additionally the new device needs to either have an existing (i.e. static) OIDC client registered with the OIDC Provider -already, or the OIDC Provider must support and allow dynamic client registration as described in [MSC2966](https://github.com/matrix-org/matrix-spec-proposals/pull/2966). +Additionally the new device needs to either have an existing (i.e. static) OAuth client registered with the homeserver +already, or the homeserver must support and allow dynamic client registration as described in the [spec](https://spec.matrix.org/v1.15/client-server-api/#client-registration). The feature is also only available where a user has cross-signing set up and the existing device to be used has the Master Signing Key, Self Signing Key and User Signing Key stored locally so that they can be shared with the new device. @@ -1542,8 +1514,8 @@ This proposed mechanism has been designed to protects users and their devices fr - A malicious actor who can intercept and modify traffic on the application layer, even if protected by encryption like TLS. - Both of the above at the same time. -Additionally, the OIDC Provider is able to define and enforce policies that can prevent a sign in on a new device. -Such policies depend on the OIDC Provider in use and could include, but are not limited to, time of day, day of the week, +Additionally, the homeserver is able to define and enforce policies that can prevent a sign in on a new device. +Such policies depend on the homeserver in use and could include, but are not limited to, time of day, day of the week, source IP address and geolocation. A threat analysis has been done within each of the key layers in the proposal above. @@ -1588,5 +1560,6 @@ The server should send: ## Dependencies -This MSC builds on [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) (and its dependencies) which -proposes the adoption of OIDC for authentication in Matrix. +This MSC builds on [MSC4341] which proposes support for RFC 8628 Device Authorization Grant in Matrix. + +[MSC4341]: https://github.com/matrix-org/matrix-spec-proposals/pull/4341 "MSC4341 Support for RFC 8628 Device Authorization Grant" From bb5f080baa9e17e096909844e6289432749651be Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Sep 2025 14:05:14 +0100 Subject: [PATCH 34/70] Update links from spec 1.10 to 1.15 --- proposals/4108-oidc-qr-login.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 2d517f9c252..175781d6296 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -425,7 +425,7 @@ Device G displays a QR code containing: - The insecure rendezvous session **URL** - An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device that wishes to "reciprocate" a login -- If the intent is to reciprocate a login, then the Matrix homeserver **[server name](https://spec.matrix.org/v1.10/appendices/#server-name)** +- If the intent is to reciprocate a login, then the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a similar structure to that of the existing Device Verification QR code encoding described in [Client-Server @@ -434,7 +434,7 @@ API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). This is defined in detail in a separate section of this proposal. Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the Matrix homeserver -**[server name](https://spec.matrix.org/v1.10/appendices/#server-name)**. +**[server name](https://spec.matrix.org/v1.15/appendices/#server-name)**. At this point Device S should check that the received intent matches what the user has asked to do on the device. @@ -690,8 +690,8 @@ This can make it hard to read what is going on. The new device needs to know which homeserver it will be authenticating with. -In the case that the new device scanned the QR code then the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver can be taken from the QR code and the -new device proceeds to step 2 immediately. +In the case that the new device scanned the QR code then the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) +of the Matrix homeserver can be taken from the QR code and the new device proceeds to step 2 immediately. Otherwise the new device waits to be informed by receiving an `m.login.protocols` message from the existing device. @@ -719,7 +719,7 @@ Once the existing device has determined the server name it then undertakes steps The steps are as follows: -- use [Server Discovery](https://spec.matrix.org/v1.10/client-server-api/#server-discovery) to determine the `base_url` from the well-known URI +- use [Server Discovery](https://spec.matrix.org/v1.15/client-server-api/#server-discovery) to determine the `base_url` from the well-known URI - checks that the homeserver has the OAuth 2.0 API available by [`GET /_matrix/client/v1/auth_metadata`](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) *New device => Homeserver via HTTP* @@ -1210,7 +1210,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.protocols`| |`protocols`|required `string[]`|Array of: one of: `device_authorization_grant` | -|`homeserver`|required `string`|The [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver| +|`homeserver`|required `string`|The [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the Matrix homeserver| ```json { @@ -1276,7 +1276,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.failure`| |`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| -|`homeserver`|`string`| When the existing device is sending this it can include the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| +|`homeserver`|`string`| When the existing device is sending this it can include the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| Example: @@ -1340,7 +1340,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.secrets`| |`cross_signing`|required `object`|
Field Type
master_key required string Unpadded base64 encoded private key
self_signing_key required string Unpadded base64 encoded private key
user_signing_key required string Unpadded base64 encoded private key
| -|`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string Unpadded base64 encoded private/secret key
backup_version required string The backup version as returned by [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3room_keysversion)
| +|`backup`|`object`|
Field Type
algorithm required string One of the algorithms listed at https://spec.matrix.org/v1.9/client-server-api/#server-side-key-backups
key required string Unpadded base64 encoded private/secret key
backup_version required string The backup version as returned by [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.15/client-server-api/#post_matrixclientv3room_keysversion)
| Example: @@ -1380,7 +1380,7 @@ The QR codes to be displayed and scanned using this format will encode binary st - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session URL as a UTF-8 string - the rendezvous session URL as a UTF-8 string -- If the QR code intent/mode is `0x04` then the [server name](https://spec.matrix.org/v1.10/appendices/#server-name) of the homeserver encoded as: +- If the QR code intent/mode is `0x04` then the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string - the server name as a UTF-8 string From 2dc580e85f7fc79b3f67cf3f6c32a55ee44092ea Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Sep 2025 17:39:50 +0100 Subject: [PATCH 35/70] Feedback from review See https://github.com/matrix-org/matrix-spec-proposals/pull/4108#discussion_r1954778980 --- proposals/4108-oidc-qr-login.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 175781d6296..6282788a3be 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1520,6 +1520,31 @@ source IP address and geolocation. A threat analysis has been done within each of the key layers in the proposal above. +### Malicious session spawning + +This mechanism could be used by an attacker who has gained temporary access to a client to escalate the attack to creation +of a new client session that has ongoing access. + +For example, if you leave your if you leave your phone unlocked briefly someone could quickly use QR code login to login on +their device. + +It also makes it easier to get the private keys of the user from an unlocked client, as you can login with a new device, +extract the keys from that, and logout again to cover your tracks. + +Sophisticated attackers can today already use specialist equipment to extract private keys and access tokens from the memory +of a process. However: a) that is a much higher bar for attack; and b) cloning an access token will quickly be detected via +refresh tokens. + +Recommendations to mitigate this are: + +- Before the login on the existing device, native clients SHOULD gate QR code login behind some form of extra protection, + e.g. biometrics on mobile apps. These should be minimally invasive though as otherwise it heavily erodes the benefit of + using QR code login in the first place. We don't necessarily think this protection is worth while on web clients, as it is + trivial to access the devtools to extract the secrets directly and/or bypass any extra protections. +- During the login, servers MAY require additional factors of authentication (e.g. biometrics or smart card). +- After the login, servers SHOULD send new device login notifications to the user (this could be to other Matrix devices or + out of band such as by email). + ## Unstable prefix While this feature is in development the new `POST` endpoint should be exposed using the following unstable prefix: From 34ade3c1ac66916430cc7e20ad3e7bb0d2518a2a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 3 Sep 2025 17:48:44 +0100 Subject: [PATCH 36/70] Min and mix rendezvous timeouts See https://github.com/matrix-org/matrix-spec-proposals/pull/4108#discussion_r1575855901 --- proposals/4108-oidc-qr-login.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 6282788a3be..8009811a07a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -226,13 +226,16 @@ The server enforce a maximum payload size of 4KB. ###### Maximum duration of a rendezvous -The rendezvous session needs to persist for the duration of the login. So a timeout such as 60 seconds should be adequate. - -It does need to allow the user another time to confirm that the secure channel has been established and complete any extra -homeserver mandated login steps such as MFA. +The rendezvous session needs to persist for the duration of the login including allowing the user another time to +confirm that the secure channel has been established and complete any extra homeserver mandated login steps such as MFA. Clients should handle the case of the rendezvous session being cancelled or timed out by the server. +The server MUST enforce a timeout on each rendezvous. When picking a value to use: + +- the minimum timeout SHOULD be 120 seconds for usability +- the maximum timeout SHOULD be 300 seconds for security + ###### ETags The ETag generated should be unique to the rendezvous session and the last modified time so that two clients can From 0e1dd7c033f11edf7f58ae04086ab03cd88ed9fb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 15 Sep 2025 16:01:26 +0100 Subject: [PATCH 37/70] Add table of contents --- proposals/4108-oidc-qr-login.md | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 8009811a07a..d241c0148bf 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -9,6 +9,41 @@ This MSC supersedes [MSC3906](https://github.com/matrix-org/matrix-spec-proposal [MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886) which achieved a similar feature but did not work with a homeserver using [OAuth 2.0 API](https://spec.matrix.org/v1.15/client-server-api/#oauth-20-api). +Table of contents: + +- [Proposal](#proposal) + - [Insecure rendezvous session](#insecure-rendezvous-session) + - [High-level description](#high-level-description) + - [The send mechanism](#the-send-mechanism) + - [Expiry](#expiry) + - [API](#api) + - [Example API usage](#example-api-usage) + - [Threat analysis](#threat-analysis) + - [Secure channel](#secure-channel) + - [Establishment](#establishment) + - [Sequence diagram](#sequence-diagram) + - [Secure operations](#secure-operations) + - [Threat analysis](#threat-analysis) + - [The OAuth login part and set up of E2EE](#the-oauth-login-part-and-set-up-of-e2ee) + - [Login via OAuth Device Authorization Grant](#login-via-oauth-device-authorization-grant) + - [Secret sharing and device verification](#secret-sharing-and-device-verification) + - [Message reference](#message-reference) + - [QR code format](#qr-code-format) + - [Example for QR code generated on new device](#example-for-qr-code-generated-on-new-device) + - [Example for QR code generated on existing device](#example-for-qr-code-generated-on-existing-device) + - [Discoverability of the capability](#discoverability-of-the-capability) +- [Potential issues](#potential-issues) +- [Alternatives](#alternatives) + - [Alternative to the rendezvous session protocol](#alternative-to-the-rendezvous-session-protocol) + - [Send-to-Device messaging](#send-to-device-messaging) + - [Other existing protocols](#other-existing-protocols) + - [Implementation details](#implementation-details) + - [Alternative method of secret sharing](#alternative-method-of-secret-sharing) +- [Security considerations](#security-considerations) + - [Malicious session spawning](#malicious-session-spawning) +- [Unstable prefix](#unstable-prefix) +- [Dependencies](#dependencies) + ## Proposal Depending on the pair of devices used, it may be preferable to scan the QR code on either the new or existing device, From 98aedb5556fe4a89331c01fd4553e039746e55b1 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 10:03:13 +0100 Subject: [PATCH 38/70] Suggestions from @uhoreg --- proposals/4108-oidc-qr-login.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index d241c0148bf..90ff4e6a3ea 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -832,8 +832,10 @@ At this point the new device knows that, subject to the user consenting, it shou 3. **New device informs existing device that it wants to use the `device_authorization_grant`** -The new device send the `verification_uri` and, if present, the `verification_uri_complete` over to the existing device and -indicates that want to use protocol `device_authorization_grant` along with the `device_id` that will be used: +The new device sends the `verification_uri` and, if present, the `verification_uri_complete` over to the existing device and +indicates that it wants to use protocol `device_authorization_grant` and that it will be authenticating as the Matrix +device with ID `device_id` (i.e. it will be requesting the [OAuth 2.0 API scope](https://spec.matrix.org/v1.16/client-server-api/#login-flow) +containing the specified device ID): *New device => Existing device via secure channel* @@ -1116,7 +1118,8 @@ This is achieved as following: 1. **Existing device confirms that the new device has indeed logged in successfully** -On receipt of an `m.login.success` message the existing device queries the homeserver to check that the is a device online +On receipt of an `m.login.success` message the existing device queries the homeserver to check that there is a device online + with the corresponding device_id (from the `m.login.protocol` message). It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) @@ -1563,8 +1566,8 @@ A threat analysis has been done within each of the key layers in the proposal ab This mechanism could be used by an attacker who has gained temporary access to a client to escalate the attack to creation of a new client session that has ongoing access. -For example, if you leave your if you leave your phone unlocked briefly someone could quickly use QR code login to login on -their device. +For example, if you leave your phone unlocked briefly someone could quickly use QR code login to sign in on their device +as you. It also makes it easier to get the private keys of the user from an unlocked client, as you can login with a new device, extract the keys from that, and logout again to cover your tracks. From 3bbba4026de5fe0c72f645990c9a951d9052f7b4 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 12:00:00 +0100 Subject: [PATCH 39/70] Fix incorrect string --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 90ff4e6a3ea..e1c4afc03f4 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -499,7 +499,7 @@ The keys are generated with the following HKDF parameters: With this, Device S has established its side of the secure channel. Device S then derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: -- The string `MATRIX_QR_CODE_LOGIN_ENCKEY_S`, encrypted and authenticated with ChaCha20-Poly1305. +- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. - Its public ephemeral key **Sp**. ``` From d6a491b28ad895d9bd7c23e587964ddf04f0c4fd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 12:01:40 +0100 Subject: [PATCH 40/70] All nonces start at 0 --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index e1c4afc03f4..f90051ff63e 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -534,7 +534,7 @@ It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGI as follows: ``` -Nonce_G := 1 +Nonce_G := 0 NonceBytes_G := ToLowEndianBytes(Nonce_G)[..12] TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_G, NonceBytes_G, "MATRIX_QR_CODE_LOGIN_OK") Nonce_G := Nonce_G + 1 From 6f05cf2bce47d73214b7dfc895257d7a07489f47 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 12:44:14 +0100 Subject: [PATCH 41/70] Make rendezvous API more like rests of Client-Server API --- proposals/4108-oidc-qr-login.md | 392 +++++++++++++--------------- proposals/images/4108-qr-mode03.png | Bin 921 -> 809 bytes proposals/images/4108-qr-mode04.png | Bin 917 -> 816 bytes 3 files changed, 185 insertions(+), 207 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f90051ff63e..3cf6327198b 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -71,183 +71,163 @@ end-to-end confidentiality nor authenticity by itself---these are layered on top Suppose that Device A wants to establish communications with Device B. Device A can do so by creating a _rendezvous session_ via a `POST /_matrix/client/v1/rendezvous` call to an appropriate homeserver. Its response includes -an HTTP _rendezvous URL_ which should be shared out-of-band with Device B. (This URL may be located on a different -domain to the initial `POST`.) +an _rendezvous ID_ which, along with the server name, should be shared out-of-band with Device B. -The rendezvous URL points to an arbitrary data resource (the "payload"), which is initially populated using data from -A's initial `POST` request. There are no restrictions on the payload itself, but the rendezvous server SHOULD impose a -maximum size limit. +The rendezvous ID points to an arbitrary data resource (the "payload") on the homeserver, which is initially populated +using data from A's initial `POST` request. The payload is a string which the homeserver must enforce a maximum length on. -Anyone who is able to reach the rendezvous URL - including: Device A; Device B; or a third party; - can then "receive" -the payload by polling via a `GET` request, and "send" a new a new payload by making a `PUT` request. +Anyone who is able to reach the homeserver and has the rendezvous ID - including: Device A; Device B; or a third party; - +can then "receive" the payload by polling via a `GET` request, and "send" a new a new payload by making a `PUT` request. -In this way, Device A and Device B can communicate by repeatedly inspecting and updating the payload at the rendezvous URL. +In this way, Device A and Device B can communicate by repeatedly inspecting and updating the payload at the rendezvous session. #### The send mechanism -Every send request MUST include an `If-Match` header whose value is the `ETag` header in the last `GET` -response seen by the requester. (The initiating device may also use the `ETag` supplied in the initial `POST` response -to immediately update the payload.) Sends will succeed only if the supplied `ETag` matches the server's current +Every send request MUST include an `sequence_token` value whose value is the `sequence_token` from the last `GET` +response seen by the requester. (The initiating device may also use the `sequence_token` supplied in the initial `POST` response +to immediately update the payload.) Sends will succeed only if the supplied `sequence_token` matches the server's current revision of the payload. This prevents concurrent writes to the payload. -The `ETag` header is standard, described by [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-etag). In this -proposal we only accept strong, single-valued `ETag` values; anything else constitutes a malformed request. - n.b. Once a new payload has been sent there is no mechanism to retrieve previous payloads. #### Expiry -The rendezvous session (i.e. the payload) SHOULD expire after a period of time communicated to clients via the `Expires` -header. After this point, any further attempts to query or update the payload MUST fail. The rendezvous session can be -manually expired with a `DELETE` call to the rendezvous session. +The rendezvous session (i.e. the payload) SHOULD expire after a period of time communicated to clients via the +`expires_ts` field on the `POST` and `GET` response bodies. After this point, any further attempts to query or update +the payload MUST fail. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. ####  API -##### Common HTTP response headers - -- `ETag` - required, ETag for the current payload at the rendezvous session as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag) -- `Expires` - required, the expiry time of the rendezvous as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires) -- `Last-Modified` - required, the last modified date of the payload as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified) -- `Cache-Control` - required, `no-store` as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.cache-control) -- `Pragma` - required, `no-cache` as per [RFC7234](https://httpwg.org/specs/rfc7234.html#header.pragma) - ##### Create a rendezvous session and send initial payload: `POST /_matrix/client/v1/rendezvous` This would be part of the Client-Server API. -HTTP request headers: - -- `Content-Length` - required -- `Content-Type` - required, must be `text/plain` - -HTTP request body: +Request body is `application/json` with contents: -- any data up to maximum size allowed by the server +|Field|Type|| +|-|-|-| +|`data`|required `string`|The data payload to be sent| -HTTP response codes, and Matrix error codes: +For example: -- `201 Created` - rendezvous session created -- `400 Bad Request` (``M_MISSING_PARAM``) - either `Content-Length` and/or `Content-Type` was not provided. -- `400 Bad Request` (`M_INVALID_PARAM`) - an invalid `Content-Type` was given. -- `403 Forbidden` (``M_FORBIDDEN``) - forbidden by server policy -- `413 Payload Too Large` (``M_TOO_LARGE``) - the supplied payload is too large -- `429 Too Many Requests` (``M_UNKNOWN``) - the request has been rate limited -- `307 Temporary Redirect` - if the request should be served from somewhere else specified in the `Location` response header +```http +POST /_matrix/client/v1/rendezvous HTTP/1.1 +Content-Type: application/json -n.b. the [`307 Temporary Redirect`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) response code has been -chosen explicitly for the behaviour of ensuring that the method and body will not change whilst the user-agent follows -the redirect. For this reason, no other `30x` response codes are allowed. +{ + "data": "initial data" +} +``` -HTTP response headers for `201 Created`: +HTTP response codes, and Matrix error codes: -- `Content-Type`- required, application/json -- common headers as defined above +- `200 OK` - rendezvous session created +- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large +- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited -HTTP response body for `201 Created`: +Response body for `200 OK` is `application/json` with contents: -- a JSON object with a single key `url` whose value is the absolute URL of the rendezvous session +|Field|Type|| +|-|-|-| +|`id`|required `string`|Opaque identifier for the rendezvous session| +|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| +|`expires_ts`|required `integer`|The timestamp (in milliseconds since the epoch) at which the rendezvous session will expire| Example response: ```http -HTTP 201 Created +HTTP 200 OK Content-Type: application/json -ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= -Expires: Wed, 07 Sep 2022 14:28:51 GMT -Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT -Cache-Control: no-store -Pragma: no-cache { - "url": "http://example.org/abcdEFG12345" + "id": "abcdEFG12345", + "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", + "expires_ts": 1662560931000 } ``` -##### Send a payload to the rendezvous session: `PUT ` +##### Send a payload to the rendezvous session: `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` -HTTP request headers: +Request body is `application/json` with contents: -- `Content-Length` - required -- `Content-Type` - required, must be `text/plain` -- `If-Match` - required. The ETag of the last payload seen by the requesting device. +|Field|Type|| +|-|-|-| +|`sequence_token`|required `string`|The value of `sequence_token` from the last payload seen by the requesting device.| +|`data`|required `string`|The data payload to be sent.| -HTTP request body: +For example: -- any data up to maximum size allowed by the server +```http +PUT /_matrix/client/v1/rendezvous/abcdEFG12345 HTTP/1.1 +Content-Type: application/json + +{ + "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", + "data": "new data" +} +``` HTTP response codes, and Matrix error codes: -- `202 Accepted` - payload updated -- `400 Bad Request` (`M_MISSING_PARAM`) - a required header was not provided. -- `400 Bad Request` (`M_INVALID_PARAM`) - a malformed -[`ETag`](https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/simple-rendezvous-capability/proposals/3886-simple-rendezvous-capability.md#the-update-mechanism) -header was provided or invalid `Content-Type`. -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) -- `412 Precondition Failed` (`M_CONCURRENT_WRITE`, a new error code) - when the ETag does not match +- `200 OK` - payload updated +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `409 Conflict` (`M_CONCURRENT_WRITE`, a new error code) - when the `sequence_token` does not match - `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited -HTTP response headers for `202 Accepted` and `412 Precondition Failed`: +The response body for `200 OK` is `application/json` with contents: -- common headers as defined above +|Field|Type|| +|-|-|-| +|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| -##### Receive a payload from the rendezvous session: `GET ` +For example: + +```http +HTTP 200 OK +Content-Type: application/json -HTTP request headers: +{ + "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=" +} +``` -- `If-None-Match` - optional, as per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.if-none-match) server will -only return data if given ETag does not match +##### Receive a payload from the rendezvous session: `GET /_matrix/client/v1/rendezvous/{rendezvousId}` HTTP response codes, and Matrix error codes: - `200 OK` - payload returned -- `304 Not Modified` - when `If-None-Match` is supplied and the ETag matched the existing payload -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited -HTTP response headers for `200 OK`: - -- `Content-Type` - required, `text/plain` -- common headers as defined above - -HTTP response headers for `304 Not Modified`: +Response body for `200 OK` is `application/json` with contents: -- common headers as defined above - -HTTP response body for `200 OK`:: - -- The payload last set for this rendezvous session, either via the creation POST request or a subsequent PUT request, up -to the maximum size allowed by the server. - -Example responses: +|Field|Type|| +|-|-|-| +|`data`|required `string`|The data payload from the last POST or PUT.| +|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| +|`expires_ts`|required `integer`|The timestamp (in milliseconds since the epoch) at which the rendezvous session will expire| ```http HTTP 200 OK -Content-Type: text/plain -ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= -Expires: Wed, 07 Sep 2022 14:28:51 GMT -Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT -Cache-Control: no-store -Pragma: no-cache - -foo -``` +Content-Type: application/json -```http -HTTP 304 Not Modified -ETag: VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o= -Expires: Wed, 07 Sep 2022 14:28:51 GMT -Last-Modified: Wed, 07 Sep 2022 14:27:51 GMT -Cache-Control: no-store -Pragma: no-cache +{ + "data": "data from the previous POST/PUT", + "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", + "expires_ts": 1662560931000 +} ``` -##### Cancel a rendezvous session: `DELETE ` +A future optimisation could be allow the client to "long-poll" by sending the previous `sequence_token` as a query parameter +and then the server returns when the is new data or some timeout has passed. + +##### Cancel a rendezvous session: `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` HTTP response codes: -- `204 No Content` - rendezvous session cancelled -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session URL is not valid (it could have expired) +- `200 OK` - rendezvous session cancelled +- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) - `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited ##### Authentication @@ -257,9 +237,14 @@ described later. ##### Maximum payload size -The server enforce a maximum payload size of 4KB. +The server MUST enforce a maximum payload size of 4KB. + +##### `sequence_token` values -###### Maximum duration of a rendezvous +The `sequence_token` values should be unique to the rendezvous session and the last modified time so that two clients can +distinguish between identical payloads sent by either client. + +##### Maximum duration of a rendezvous The rendezvous session needs to persist for the duration of the login including allowing the user another time to confirm that the secure channel has been established and complete any extra homeserver mandated login steps such as MFA. @@ -271,34 +256,6 @@ The server MUST enforce a timeout on each rendezvous. When picking a value to us - the minimum timeout SHOULD be 120 seconds for usability - the maximum timeout SHOULD be 300 seconds for security -###### ETags - -The ETag generated should be unique to the rendezvous session and the last modified time so that two clients can -distinguish between identical payloads sent by either client. - -In order to make sure that no intermediate caches manipulate the ETags, the rendezvous server MUST include the HTTP -`Cache-Control` response header with a value of `no-store` and `Pragma` response header with a value of `no-cache`. - -###### CORS - -For the `POST /_matrix/client/v1/rendezvous` API endpoint, in addition to the standard Client-Server API [CORS](https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients) -headers, the ETag response header should also be allowed by exposing the following CORS header: - -```http -Access-Control-Expose-Headers: ETag -``` - -To support usage from web browsers the rendezvous URLs should allow CORS requests from any origin and expose the headers -which aren't on the CORS [request header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header) and -[response header](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header) safelists: - -```http -Access-Control-Allow-Headers: If-Match,If-None-Match -Access-Control-Allow-Methods: GET, PUT, DELETE -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag -``` - ##### Choice of server Ultimately it will be up to the Matrix client implementation to decide which rendezvous server to use. @@ -312,63 +269,59 @@ of preference: #### Example API usage -n.b. This example demonstrates how the 307 response can be used to delegate the rendezvous session to a different server. - ```mermaid sequenceDiagram participant A as Device A participant HS as Homeserver - participant R as Rendezvous Server
https://rz.example.com participant B as Device B Note over A: Device A determines which rendezvous server to use - A->>+HS: POST /_matrix/client/v1/rendezvous
Content-Type: text/plain
"Hello from A" - HS->>-A: 307 Temporary Redirect
Location: https://rz.example.com/foo - A->>+R: POST /foo
Content-Type: text/plain
"Hello from A" - R->>-A: 201 Created
ETag: 1
{"url":"https://rz.example.com/abc-def-123-456"} + A->>+HS: POST /_matrix/client/v1/rendezvous
{"data":"Hello from A"} + HS->>-A: 200 OK
{"id":"abc-def-123-456","sequence_token": "1", "expires_ts": 1234567} - A-->>B: Rendezvous URL shared out of band as QR code: e.g. https://rz.example.com/abc-def-123-456 + A-->>B: Rendezvous ID and homeserver shared out of band as QR code: e.g. id=abc-def-123-456 servername=example.com - Note over A: Device A starts polling for new payloads at the
rendezvous session using the returned ETag + Note over A: Device A starts polling for new payloads at the
rendezvous session using the returned `sequence_token` activate A - B->>+R: GET /abc-def-123-456 - R->>-B: 200 OK
ETag: 1
Content-Type: text/plain
"Hello from A" + Note over B: Device B resolves the servername to the homeserver + B->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 + HS->>-B: 200 OK
{"sequence_token": "1", "data": "Hello from A"} loop Device A polls the rendezvous session for a new payload - A->>+R: GET /abc-def-123-456
If-None-Match: 1 + A->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 alt is not modified - R->>-A: 304 Not Modified + HS->>-A: 200 OK
{"sequence_token": "1", "data": "Hello from A", "expires_ts": 1234567} end end note over B: Device B sends a new payload - B->>+R: PUT /abc-def-123-456
If-Match: 1
Content-Type: text/plain
"Hello from B" - R->>-B: 202 Accepted
ETag: 2 + B->>+HS: PUT /_matrix/client/v1/rendezvous/abc-def-123-456
{"sequence_token": "1", "data": "Hello from B"} + HS->>-B: 200 OK
{"sequence_token": "2"} - Note over B: Device B starts polling for new payloads at the
rendezvous session using the new ETag + Note over B: Device B starts polling for new payloads at the
rendezvous session using the new `sequence_token` activate B loop Device B polls the rendezvous session for a new payload - B->>+R: GET /abc-def-123-456
If-None-Match: 2 + B->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 alt is not modified - R->>-B: 304 Not Modified + HS->>-B: 200 OK
{"sequence_token": "2", "data": "Hello from A", "expires_ts": 1234567} end end note over A: Device A then receives the new payload opt modified - R->>A: 200 OK
ETag: 2
Content-Type: text/plain
"Hello from B" + HS->>A: 200 OK
{"sequence_token": "2", "data": "Hello from B", "expires_ts": 1234567} end deactivate A note over A: Device A sends a new payload - A->>+R: PUT /abc-def-123-456
If-None-Match: 2
Content-Type: text/plain
"Hello again from A" - R->>-A: 202 Accepted
ETag: 3 + A->>+HS: PUT /_matrix/client/v1/rendezvous/abc-def-123-456
{"sequence_token": "2", "data": "Hello again from A"} + HS->>-A: 200 OK
{"sequence_token": "3"} note over B: Device B then receives the new payload opt modified - R->>B: 200 OK
ETag: 3
Content-Type: text/plain
... + HS->>B: 200 OK
{"sequence_token": "3", "data": "Hello again from B", "expires_ts": 1234567} end deactivate B @@ -408,14 +361,12 @@ Mitigations that are included in this proposal: - the low maximum payload size - restricted allowed content type - the rendezvous session should be short-lived -- the ability for the rendezvous session to be hosted on a different domain to the homeserver (via -the `307 Temporary Redirect` response behaviour) ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or -even arbitrary network participants which possess the rendezvous session URL. To provide a secure channel on -top of this insecure rendezvous session transport, we propose the following scheme. +even arbitrary network participants which possess the rendezvous session ID and server name. +To provide a secure channel on top of this insecure rendezvous session transport, we propose the following scheme. This scheme is essentially [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) instantiated with X25519, HKDF-SHA256 for the KDF and ChaCha20-Poly1305 (as specified by @@ -453,17 +404,17 @@ separate deterministic, monotonically-incrementing nonce is used for each sender 2. **Create rendezvous session** Device G creates a rendezvous session by making a `POST` request (as described previously) to the nominated homeserver -with an empty payload. It parses the **url** received. +with an empty payload. It parses the **ID** received. 3. **Initial key exchange** Device G displays a QR code containing: - Its public key **Gp** -- The insecure rendezvous session **URL** +- The insecure rendezvous session **ID** - An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device that wishes to "reciprocate" a login -- If the intent is to reciprocate a login, then the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** +- the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a similar structure to that of the existing Device Verification QR code encoding described in [Client-Server @@ -471,7 +422,7 @@ API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). This is defined in detail in a separate section of this proposal. -Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **URL**, **intent** and optionally the Matrix homeserver +Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **ID**, **intent** and the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)**. At this point Device S should check that the received intent matches what the user has asked to do on the device. @@ -516,8 +467,7 @@ Nonce_S := Nonce_S + 1 LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp) ``` -Device S then sends the **LoginInitiateMessage** as the payload to the rendezvous session using a `PUT` request with -`Content-Type` header set to `text/plain`. +Device S then sends the **LoginInitiateMessage** as the `data` payload to the rendezvous session using a `PUT` request. 5. **Device G confirms** @@ -541,8 +491,7 @@ Nonce_G := Nonce_G + 1 LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) ``` -Device G sends **LoginOkMessage** as the payload via `PUT` request with `Content-Type` header set to `text/plain` to the -insecure rendezvous session. +Device G sends **LoginOkMessage** as the `data` payload via a `PUT` request to the insecure rendezvous session. 6. **Verification by Device S** @@ -605,48 +554,47 @@ The sequence diagram for the above is as follows: ```mermaid sequenceDiagram participant G as Device G - participant Z as Homeserver with embedded Rendezvous Server
matrix.example.com + participant Z as Homeserver participant S as Device S note over G,S: 1) Devices G and S each generate an ephemeral Curve25519 key pair activate G note over G: 2) Device G creates a rendezvous session as follows - G->>+Z: POST /_matrix/client/v1/rendezvous - Z->>-G: 201 Created
ETag: 1
{"url": "https://matrix.example.com/_synapse/client/rendezvous/abc-def"} + G->>+Z: POST /_matrix/client/v1/rendezvous
{"data": ""} + Z->>-G: 200 OK
{"id": "abc-def", "sequence_token": "1", "expires_ts": 1234567} - note over G: 3) Device G generates and displays a QR code containing
its ephemeral public key and the rendezvous session URL + note over G: 3) Device G generates and displays a QR code containing:
its ephemeral public key, the rendezvous session ID, the server name G-->>S: Device S scans the QR code shown by Device G deactivate G activate S - note over S: Device S validates QR scanned and the rendezvous session URL + note over S: Device S validates QR scanned and the rendezvous session ID - S->>+Z: GET /_synapse/client/rendezvous/abc-def - Z->>-S: 200 OK
ETag: 1 + S->>+Z: GET /_matrix/client/v1/rendezvous/abc-def + Z->>-S: 200 OK
{"sequence_token": "1", "expires_ts": 1234567, "data": ""} note over S: 4) Device S computes SH, EncKey_S, EncKey_G and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session - S->>+Z: PUT /_synapse/client/rendezvous/abc-def
If-Match: 1
Body: LoginInitiateMessage - Z->>-S: 202 Accepted
ETag: 2 + S->>+Z: PUT /_matrix/client/v1/rendezvous/abc-def
{"sequence_token": "1", "data": ""} + Z->>-S: 200 OK
{"sequence_token": "2"} deactivate S - G->>+Z: GET /_synapse/client/rendezvous/abc-def
If-None-Match: 1 + G->>+Z: GET /_matrix/client/v1/rendezvous/abc-def activate G - Z->>-G: 200 OK
ETag: 2
Body: Data - + Z->>-G: 200 OK
{"sequence_token": "2", "expires_ts": 1234567, "data": ""} note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH, EncKey_S and EncKey_G note over G: Device G checks that the plaintext matches MATRIX_QR_CODE_LOGIN_INITIATE note over G: Device G computes LoginOkMessage and sends to the rendezvous session - G->>+Z: PUT /_synapse/client/rendezvous/abc-def
If-Match: 2
Body: LoginOkMessage - Z->>-G: 202 Accepted
ETag: 3 + G->>+Z: PUT /_matrix/client/v1/rendezvous/abc-def
{"sequence_token": "2", "data": ""} + Z->>-G: 200 OK
{"sequence_token": "3"} deactivate G activate S - S->>+Z: GET /_synapse/client/rendezvous/abc-def
If-None-Match: 2 - Z->>-S: 200 OK
ETag: 3
Body: Data + S->>+Z: GET /_matrix/client/v1/rendezvous/abc-def + Z->>-S: 200 OK
{"sequence_token": "3", "expires_ts": 1234567, "data": ""} note over S: 6) Device S attempts to parse Data as LoginOkMessage note over S: Device S checks that the plaintext matches MATRIX_QR_CODE_LOGIN_OK @@ -1417,53 +1365,69 @@ The QR codes to be displayed and scanned using this format will encode binary st - `0x03` a new device wishing to initiate a login and self-verify - `0x04` an existing device wishing to reciprocate the login of a new device and self-verify that other device - the ephemeral Curve25519 public key, as 32 bytes -- the rendezvous session URL encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session URL as a UTF-8 +- the rendezvous session ID encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 string - - the rendezvous session URL as a UTF-8 string -- If the QR code intent/mode is `0x04` then the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: + - the rendezvous session ID as a UTF-8 string +- the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string - the server name as a UTF-8 string -For example, if Alice displays a QR code encoding the following binary string: - -This indicates that Alice is a new device that wishes to initiate a login using her ephemeral public key of -`0001020304050607...` (which is `AAECAwQFBg…` in base64), via the rendezvous session at URL `https:/…`. - #### Example for QR code generated on new device A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded) at rendezvous session `https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668` is as follows: +encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 03 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 47 -68 74 74 70 73 3a 2f 2f 72 65 6e 64 65 7a 76 6f 75 73 2e 6c 61 62 2e 65 6c 65 6d 65 6e 74 2e 64 65 76 2f 65 38 64 61 36 33 35 35 2d 35 35 30 62 2d 34 61 33 32 2d 61 31 39 33 2d 31 36 31 39 64 39 38 33 30 36 36 38 +00 24 +65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 +00 0A +6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: + ![Example QR for mode 0x03](images/4108-qr-mode03.png) #### Example for QR code generated on existing device A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session `https://rendezvous.lab.element.dev/e8da6355-550b-4a32-a193-1619d9830668` on homeserver +encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver `matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 04 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 47 -68 74 74 70 73 3a 2f 2f 72 65 6e 64 65 7a 76 6f 75 73 2e 6c 61 62 2e 65 6c 65 6d 65 6e 74 2e 64 65 76 2f 65 38 64 61 36 33 35 35 2d 35 35 30 62 2d 34 61 33 32 2d 61 31 39 33 2d 31 36 31 39 64 39 38 33 30 36 36 38 +00 24 +65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 00 0A 6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: + ![Example QR for mode 0x04](images/4108-qr-mode04.png) ### Discoverability of the capability @@ -1494,6 +1458,20 @@ The proposed protocol requires the devices to have IP connectivity to the server ### Alternative to the rendezvous session protocol +#### ETag based rendezvous API + +An earlier iteration of this MSC used an alternative rendezvous API that was based on +[MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886). + +However, it was found to have issues including: + +- the ETags were getting mangled by proxies and load balancers +- the semantics of the API are different from the rest of the Matrix Client-Server API +- the CORS header changes required additional configuration work + +The present iteration of the rendezvous API described in this MSC attempts to "feel" more like a Matrix Client-Server +API. + #### Send-to-Device messaging If you squint then this proposal looks similar in some regards to the existing @@ -1588,9 +1566,9 @@ Recommendations to mitigate this are: ## Unstable prefix -While this feature is in development the new `POST` endpoint should be exposed using the following unstable prefix: +While this feature is in development the new API endpoints should be exposed using the following unstable prefix: -- `/_matrix/client/unstable/org.matrix.msc4108/rendezvous` +- `/_matrix/client/unstable/org.matrix.msc4108/rendezvous` instead of `/_matrix/client/v1/rendezvous` Additionally, the feature is to be advertised as unstable feature in the GET /_matrix/client/versions response, with the key org.matrix.msc4108 set to true. So, the response could look then as following: diff --git a/proposals/images/4108-qr-mode03.png b/proposals/images/4108-qr-mode03.png index 4e1a629f17d2a711eb724b615c60a25e2ff2390c..f7c459e69d041215ce01bd7382a83d12f9e360ba 100644 GIT binary patch delta 768 zcmV+b1ONP)2dM@jiBL{Q4GJ0x0000DNk~Le00021000210RsR403#;;T9F|)e*(Kn zL_t(YiRG6;uB$K%MU5C_1{;W!ZDf`$!3GN5K-p4e*+!&n0A?9P_+QgLy*Kz@`eynN zqUf{=vAMo>iT`@g@5VdP63goCgZO3mh}7xv!EK`Q3jD;4Z(Rb><7ns9AE|%V>zZi3 zg_r8Km`BcI?YF!55fG~-lI8gxf9AF%UIVU{d|tkTS779OPWJo!ehmtI9pyA_iTq4$ z{3(e4#wX>43s*$h29sx&Z?t$1j*}f@zXm&a_cHjE8gpt0ZlHq1Px7dqZ{R5F&504{ zoM@?b(c?4F2>DbRf|Z-tW<5USRTEKJ1J3sFH0tqbEE(!z>lpIscjNdDe|Cq2NaV{i z<_@{PgArDquxiM2FFoq<9tdw9`XvM3g$>(9k56*D&r?7vu#<<2 ze=?}U!Ol#us5!#zP+K2Cf2i<7&`Xuu-4{-IFz=qG!zjv^*bL-c zj#^&+9ilHR9+u;7V0?0g03iW81&khf)MNBCc-1WKe{09&&d&yQSTY2& zM0^AV^;|!VQHL>wVd|+^m|xyKqwVyVt79=v1d82(z3Wnk&yWd$CR8@E4&Tko4SelU zCQ6Tz;??(u9`B|NvMQdy;y0f@ufsTwms$Se*-H? zL_t(oh3%I;j^sEDg;`MG%)WpDT|=3!OD5`e}CwI{^f~ia^pm2@5XnrsjEa*!86w-p6_sFzK&qGP#0G5FwUL^ zSwj3elsa`vzVNlh@y+M##vzVse?E156kZAO&E8cd=Q`ncJYP&R(JTJ0LlZ~+-`M28 zetyoSiQ{v->+a~qc6Ap=`_m^){3hz1EF~J3U~}Q^*=H-qLymSOllknuO>`Ym!xPmZ zobNLB&K~i34jP^#lEt62lq(0jdviSbw(>+c!Ac&thWhT=cF=GnyGc))f8Cu+xYq7M zk16==&K|YmaVR)PwCFc}D^J*4xVY5bGlH+4xo9}z6VVVPcu)2wvpw~1<;h=LvDoMA z$GNk2$J^(@X zPaf@!u7JLo9SRWfH4V?Pf07ezT)T>;+wtzLf+NS(KR4Hzt%%m)I28PLL>^fi=q*;U zMe1}Mk*|#jn?zTO|WY%ybxI1|CI53KDIMVdw+sbpOGd3BY#K~s4gRJ2!OG`AnKgq!y&o^s0 zHiqG9iIO!J9O(GblV)Bnn6(5anIDpLmsN17zf_Mfe6nnq*PnB2;ao6t!Xo7@kJ!>@ zPHW{T+5|g$8h)3#f6;#Zx|K6#D%1_8FZr(C?%%nwjVI_E;Tcl|L2>~$C!sUFbC%INUxyc41YwXFcFv|;=|oET;}1x2jeKsCQ}VYVpi~H zK8Ki8%;)4~$WI@?Yvn=oKRtcEllb)~Uj>(j#H|sr#c@a(M|B((gSWyWk3yLGe(6jj zN4_5A!oUyp#$_?T=BqZ2`Y)nvk}vLWPmmN1|5N`j|DF6Fc-by`AI33y00000NkvXX Hu0mjfo9?xk diff --git a/proposals/images/4108-qr-mode04.png b/proposals/images/4108-qr-mode04.png index b01ee1e3672f4a71d14c35a58647679c87c3b632..829ee0032017c28c6b73c49e032ac1e6ba982a0b 100644 GIT binary patch delta 775 zcmV+i1Ni)v2e1YqiBL{Q4GJ0x0000DNk~Le00021000210RsR403#;;T9F|)e*(fu zL_t(YiRG6u?xZ{n#f?&=g9}8;HPYouCU{}$gTt!Wn_ zgno=5cI@{&#Q%BFzl~3#B_69YFXCNui8RP@;WpE;d){#4+K@oxIGT`DNotFqYohQ7 zFV((d9(|swdOyW4k5~_27yq+o*Hk{zQsTZ#kMNnIW0c**WgG@*Q|-M#5Iu?0CUeXEJ+SUt}6ax^``hCB_m4UF{3aYd>&$cxfZH=f*lQaOSPA`SZ|Tz6FuEQ8IjChD^RZ&0dw5C!DD*a(vP8PDs+R>O67ph9JkMC(^xsiO^M$VYA5bMfP-g_OJqU z6;%ArpbR?`m>^LhLhW)$fA`LdaH+OLix8Ixfx%DgTaLNQWX}*p0=2x7WjVey`rAxV z?Sws7+-q5knZeLTxb`ZT{ryQ1z8qeb17t=+zabK4kmD2eQv2=c4o-R0Ur&=^Xw|N1 zh-UOhsmJcVQOYoUb=HkM+WI)1Ey+H>rPmAIjS?`k$LNtKIfke8e;TYA-7qD4-3yfA zo*{@O;v-a$;(8mS3}Xtbs?@G7yR%y~gB){(yY|uPa#$VMw=ZS*@_luMp;c}4(A~T| zz&%ica*qxz#j8_Aj!)f2Y7=~*KhN{+^D>O1V8{CGgK&?H%^=608M*iOXa0gA{B{xv z?lZDh7wjm)NzhMfU3-9GK^i^)7028Gt-7amDgM|<9|qwDqc#sQW6!DgCH47*A7I9? zz^lf%fk7M7<+prfxaXVI3XwpAR#@CORWkfP|E2z6{0phaYDJ-luW$eW002ovPDHLk FV1fbSeMkTR delta 877 zcmV-z1Cso(29*aPiBL{Q4GJ0x0000DNk~Le0002D0002D0RsR40BWFJ+>s$Se*-5; zL_t(oh3%Iy&a65Ng^eiE!3H8_8|kv8VFL_ofZ0;IY$H-O09}d*-{YD2?|sEWYzp(fB5jc5!N3M)0CXsgZP#5z_bz#=}#WIIP$+>lmGbr zK9??z>-Oa|G_cz|0(Zabq>JB#N??$a>@&Kn^8VguFXvdU9!%u_M6|N0=+f3O4bJcLHPX0JADh&du+(m4$?Qg$yb9V3hf&yE8k-tI3D*}W^L-xl# zy{#l_I3r^L?N&B8nn%Bk=f_8SIXB4K9!Chm_Lj|0zw~m5$xZe_e-Jk~2#Udy*YGnm zf^9R2&YGsZLm*B0wnaFD}{uZlW% zqP+%%>J%LDlE;`Qdh)`2o}ecx&KMyi(zmj?Lkva3Wgf2ZeYdA^hi+FHp|VCr7+e zPApR~e9e@f`Kpg2|EqzrNxU2}@rsw=Gr00000NkvXXu0mjf Dd`PbF From 2eba2182ed5381dac869fe8cc76121511b3ecd61 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 12:48:36 +0100 Subject: [PATCH 42/70] Make 429 errcode be M_LIMIT_EXCEEDED --- proposals/4108-oidc-qr-login.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 3cf6327198b..2584528cfb0 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -173,7 +173,7 @@ HTTP response codes, and Matrix error codes: - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) - `409 Conflict` (`M_CONCURRENT_WRITE`, a new error code) - when the `sequence_token` does not match - `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large -- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited +- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited The response body for `200 OK` is `application/json` with contents: @@ -198,7 +198,7 @@ HTTP response codes, and Matrix error codes: - `200 OK` - payload returned - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) -- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited +- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited Response body for `200 OK` is `application/json` with contents: @@ -228,7 +228,7 @@ HTTP response codes: - `200 OK` - rendezvous session cancelled - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) -- `429 Too Many Requests` (`M_UNKNOWN`) - the request has been rate limited +- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited ##### Authentication From ad719369197fda5459d55759949932503f25f529 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 17:19:34 +0100 Subject: [PATCH 43/70] Add note bout message prefix --- proposals/4108-oidc-qr-login.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 2584528cfb0..0d35c470f52 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -659,6 +659,15 @@ own key. - The attack is only thwarted in step 7, because Device S won't ever display the indicator of success to the user. The user then must cancel the process on Device G, preventing it from sharing any sensitive material. +#### Choice of message prefix + +During the secure channel establishment the messages have been prefixed with `MATRIX_QR_CODE_LOGIN_` rather than +something more generic. The purpose is to bind the protocol to this specific application. + +Whilst the could be other uses for the secure channel mechanism or we might establish communication between devices +using another mechanism (e.g. NFC or sound), this proposal only considers the scenario where the communication is +initiated via QR code and we make the prefix explicitly named to match. + ### The OAuth login part and set up of E2EE Once the secure channel has been established, the two devices can then communicate securely. From 1ec7d9cc3934b60cf5348e02c8050789622b551e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 17:45:03 +0100 Subject: [PATCH 44/70] Remove further references to rendezvous server --- proposals/4108-oidc-qr-login.md | 42 ++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 0d35c470f52..9eef5d12744 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -549,7 +549,7 @@ encrypted with **EncKey_S**, incrementing the corresponding nonce for each messa #### Sequence diagram -The sequence diagram for the above is as follows: +The sequence diagram for the secure channel establishment is as follows: ```mermaid sequenceDiagram @@ -816,7 +816,6 @@ _New device scanned QR code:_ sequenceDiagram title: Variant: New device scanned QR code participant E as Existing device
already signed in - participant Z as Rendezvous server participant N as New device
wanting to sign in participant HS as Homeserver @@ -830,9 +829,9 @@ sequenceDiagram # note over E: Existing device completes step 6 # note over E: Existing device displays checkmark and CheckCode # note over E: 1) Existing device sends m.login.protocols message - # E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) + # E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) # note over N: New device waits for user to confirm secure channel from step 7 - # Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} + # HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} # note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end @@ -848,7 +847,7 @@ sequenceDiagram N->>+HS: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... HS->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} note over N: 3) New device informs existing device of choice of protocol: - N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) + N->>HS: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) deactivate N end @@ -860,7 +859,7 @@ sequenceDiagram end rect rgba(0,255,0, 0.1) - Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} + HS->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} end rect rgba(255,0,0, 0.1) @@ -884,7 +883,6 @@ _Existing device scanned QR code:_ sequenceDiagram title: Variant: Existing device scanned QR code participant E as Existing device
already signed in - participant Z as Rendezvous server participant N as New device
wanting to sign in participant HS as Homeserver @@ -898,9 +896,9 @@ sequenceDiagram note over E: Existing device completes step 6 note over E: Existing device displays checkmark and CheckCode note over E: 1) Existing device sends m.login.protocols message - E->>Z: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) + E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) note over N: New device waits for user to confirm secure channel from step 7 - Z->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} + HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end @@ -916,7 +914,7 @@ sequenceDiagram N->>+HS: POST /auth/device client_id=xyz&scope=openid+urn:matrix:api:*+urn:matrix:device:ABCDEFGH... HS->>-N: 200 OK {"user_code": "123456",
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"expires_in_ms": 120000, "device_code": "XYZ", "interval": 1} note over N: 3) New device informs existing device of choice of protocol: - N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) + N->>HS: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"}) deactivate N end @@ -927,7 +925,7 @@ sequenceDiagram #end rect rgba(0,255,0, 0.1) - Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} + HS->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",
"device_authorization_grant":{
"verification_uri_complete": "https://id.matrix.org/device/abcde",
"verification_uri": ...}, "device_id": "ABCDEFGH"} end # alt if New device scanned QR code @@ -1009,6 +1007,9 @@ Note that the existing device does not see the new access token. This is one of The sequence diagram for steps 4 and 5 is as follows: +(for readability a pair of `SecureSend,SecureReceive` operations via the Homeserver is represented by a single +`SecureSendReceive` between the two devices) + ```mermaid sequenceDiagram participant E as Existing device
already signed in @@ -1021,12 +1022,12 @@ sequenceDiagram E->>HS: GET /_matrix/client/v3/devices/{device_id} alt device already exists HS->>E: 200 OK - E->>N: SecureSend({ "type": "m.login.failure", "reason": "device_already_exists" }) + E->>N: SecureSendReceive({ "type": "m.login.failure", "reason": "device_already_exists" }) else device not found HS->>E: 404 Not Found end par - E->>N: SecureSend({"type":"m.login.protocol_accepted"}) + E->>N: SecureSendReceive({"type":"m.login.protocol_accepted"}) note over N: 4) New device polls the homeserver awaiting the outcome as per RFC 8628 / MSC4341 loop Poll for result at interval seconds N->>HS: POST /token client_id=xyz
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=XYZ @@ -1034,14 +1035,14 @@ sequenceDiagram HS-->>N: 400 Bad Request {"error": "authorization_pending"} else granted HS-->>N: 200 OK {"access_token": "...", "token_type": "Bearer", ...} - N->>E: SecureSend({ "type": "m.login.success" }) + N->>E: SecureSendReceive({ "type": "m.login.success" }) Note over N: Device now has an access_token and can start to talk to the homeserver else denied HS-->>N: 400 Bad Request {"error": "authorization_declined"} - N->>E: SecureSend({"type":"m.login.declined"}) + N->>E: SecureSendReceive({"type":"m.login.declined"}) else expired HS-->>N: 400 Bad Request {"error": "expired_token"} - N->>E: SecureSend({"type":"m.login.failure", "reason": "authorization_expired"}) + N->>E: SecureSendReceive({"type":"m.login.failure", "reason": "authorization_expired"}) end end and @@ -1147,6 +1148,9 @@ Content-Type: application/json The sequence diagram for this would look as follows: +(for readability a pair of `SecureSend,SecureReceive` operations via the Homeserver is represented by a single +`SecureSendReceive` between the two devices) + ```mermaid sequenceDiagram participant E as Existing device
already signed in @@ -1156,7 +1160,7 @@ sequenceDiagram rect rgba(0,255,0, 0.1) rect rgb(191, 223, 255) note over N,E: This step is duplicated from the previous section for readability - N-->>+E: { "type": "m.login.success" } + N-->>+E: SecureSendReceive({ "type": "m.login.success" }) end Note over E: 1) Existing device checks that the device is actually online @@ -1166,12 +1170,12 @@ activate HS alt is device not found note over E: We should wait and retry for 10 seconds HS->>E: 404 Not Found - E->>N: { "type": "m.login.failure", "reason": "device_not_found" } + E->>N: SecureSendReceive({ "type": "m.login.failure", "reason": "device_not_found" }) else is device found HS->>E: 200 OK deactivate HS - E->>-N: 2) { "type": "m.login.secrets", "cross_signing": {...}, "backup": {...} } + E->>-N: 2) SecureSendReceive({ "type": "m.login.secrets", "cross_signing": {...}, "backup": {...} }) activate N note over N: 3) New device stores the secrets locally From a92f12865ab8fbdb541f252c3e6b529685e6b2ff Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 17:54:21 +0100 Subject: [PATCH 45/70] Fix more references to OAuth and MSC4341 --- proposals/4108-oidc-qr-login.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 9eef5d12744..5e8e1a44b14 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1,4 +1,4 @@ -# MSC4108: Mechanism to allow OAuth sign in and E2EE set up via QR code +# MSC4108: Mechanism to allow OAuth 2.0 API sign in and E2EE set up via QR code We propose a method to allow an existing authenticated Matrix client to sign in a new client by scanning a QR code. The new client will be a fully bootstrapped Matrix cryptographic device, possessing all the necessary secrets, namely the @@ -24,8 +24,8 @@ Table of contents: - [Sequence diagram](#sequence-diagram) - [Secure operations](#secure-operations) - [Threat analysis](#threat-analysis) - - [The OAuth login part and set up of E2EE](#the-oauth-login-part-and-set-up-of-e2ee) - - [Login via OAuth Device Authorization Grant](#login-via-oauth-device-authorization-grant) + - [The OAuth 2.0 login part and set up of E2EE](#the-oauth-20-login-part-and-set-up-of-e2ee) + - [Login via OAuth 2.0 Device Authorization Grant from MSC4341](#login-via-oauth-20-device-authorization-grant-from-msc4341) - [Secret sharing and device verification](#secret-sharing-and-device-verification) - [Message reference](#message-reference) - [QR code format](#qr-code-format) @@ -35,6 +35,7 @@ Table of contents: - [Potential issues](#potential-issues) - [Alternatives](#alternatives) - [Alternative to the rendezvous session protocol](#alternative-to-the-rendezvous-session-protocol) + - [ETag based rendezvous API](#etag-based-rendezvous-api) - [Send-to-Device messaging](#send-to-device-messaging) - [Other existing protocols](#other-existing-protocols) - [Implementation details](#implementation-details) @@ -59,7 +60,7 @@ This proposal is split into three parts: 1. An insecure rendezvous session API to allow the two devices to exchange the necessary data 2. A secure channel to protect the data exchanged over the rendezvous session -3. The OAuth login part and set up of E2EE +3. The OAuth 2.0 login part and set up of E2EE ### Insecure rendezvous session @@ -96,7 +97,7 @@ The rendezvous session (i.e. the payload) SHOULD expire after a period of time c `expires_ts` field on the `POST` and `GET` response bodies. After this point, any further attempts to query or update the payload MUST fail. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. -####  API +#### API ##### Create a rendezvous session and send initial payload: `POST /_matrix/client/v1/rendezvous` @@ -668,14 +669,16 @@ Whilst the could be other uses for the secure channel mechanism or we might esta using another mechanism (e.g. NFC or sound), this proposal only considers the scenario where the communication is initiated via QR code and we make the prefix explicitly named to match. -### The OAuth login part and set up of E2EE +### The OAuth 2.0 login part and set up of E2EE Once the secure channel has been established, the two devices can then communicate securely. -#### Login via OAuth Device Authorization Grant +#### Login via OAuth 2.0 Device Authorization Grant from MSC4341 In this section the sequence of steps depends on whether the new device generated or scanned the QR code. +This is where we start to make use of [MSC4341] Support for RFC 8628 Device Authorization Grant. + For example, in the case that the new device scanned the QR code it is the first to do a `SecureSend` whereas if the new device generated the QR then the existing device is the first to do a `SecureSend`. @@ -1003,7 +1006,7 @@ The existing device then sends an acknowledgement message to let the other devic The user is then prompted to consent by the homeserver. They may be prompted to undertake additional actions by the homeserver such as 2FA, but this is all handled within the browser. -Note that the existing device does not see the new access token. This is one of the benefits of the OAuth architecture. +Note that the existing device does not see the new access token. This is one of the benefits of the OAuth 2.0 API. The sequence diagram for steps 4 and 5 is as follows: @@ -1455,7 +1458,7 @@ Where the homeserver is known: For a new device it would need to know the homeserver ahead of time in order to do these checks. -Additionally the new device needs to either have an existing (i.e. static) OAuth client registered with the homeserver +Additionally the new device needs to either have an existing (i.e. static) OAuth 2.0 client registered with the homeserver already, or the homeserver must support and allow dynamic client registration as described in the [spec](https://spec.matrix.org/v1.15/client-server-api/#client-registration). The feature is also only available where a user has cross-signing set up and the existing device to be used has the From ced446612c61971aafb3cdfce0c5ab9bc3c5fd57 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Sep 2025 18:05:53 +0100 Subject: [PATCH 46/70] More consistency on SecureSend/SecureReceive --- proposals/4108-oidc-qr-login.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 5e8e1a44b14..9a4cb162673 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -875,7 +875,8 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) end end ``` @@ -940,7 +941,8 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>N: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) end end ``` From 441616108174764f57a5ee6601c66e2a3c0a3d76 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Sep 2025 11:42:15 +0100 Subject: [PATCH 47/70] Split out steps and reorder for clarity --- proposals/4108-oidc-qr-login.md | 90 +++++++++++++++++---------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 9a4cb162673..00cf5f37777 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -949,36 +949,7 @@ sequenceDiagram Then we continue with the actual login: -4. **New device waits for approval from homeserver** - -On receipt of the `m.login.protocol_accepted` message: - -- In accordance with [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1) the new device must display -the `user_code` in order that the user can confirm it on the homeserver if required. -- The new device then starts to poll the homeserver by making -[Device Access Token Requests](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4) using the interval and bounded -by `expires_in`. - -The above is as per [MSC4341]. - -*New device => Homeserver via HTTP* - -```http -POST /oauth2/token HTTP/1.1 -Host: auth-oidc.lab.element.dev -Content-Type: application/x-www-form-urlencoded - -grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code - &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS - &client_id=my_client_id -``` - -- It then parses the [Device Access Token Response](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) and -handles the different responses -- If the user consents in the next step then the new device will receive an `access_token` and `refresh_token` etc. as -normal as per [MSC4341]. - -5. **User is asked by homeserver to consent on existing device** +4. **Existing device checks device_id and accepts protocol to use** On receipt of the `m.login.protocol` message above, and having completed step 7 of the secure channel establishment, the existing device then asserts that there is no existing device corresponding to the `device_id` from the @@ -1005,12 +976,43 @@ The existing device then sends an acknowledgement message to let the other devic } ``` +5. **User is asked by homeserver to consent on existing device** + The user is then prompted to consent by the homeserver. They may be prompted to undertake additional actions by the homeserver such as 2FA, but this is all handled within the browser. Note that the existing device does not see the new access token. This is one of the benefits of the OAuth 2.0 API. -The sequence diagram for steps 4 and 5 is as follows: +6. **New device waits for approval from homeserver** + +In parallel to step 5, on receipt of the `m.login.protocol_accepted` message the new device: + +- In accordance with [RFC8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1) the new device must display +the `user_code` in order that the user can confirm it on the homeserver if required. +- The new device then starts to poll the homeserver by making +[Device Access Token Requests](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4) using the interval and bounded +by `expires_in`. + +The above is as per [MSC4341]. + +*New device => Homeserver via HTTP* + +```http +POST /oauth2/token HTTP/1.1 +Host: auth-oidc.lab.element.dev +Content-Type: application/x-www-form-urlencoded + +grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code + &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS + &client_id=my_client_id +``` + +- It then parses the [Device Access Token Response](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) and +handles the different responses +- If the user consents in the next step then the new device will receive an `access_token` and `refresh_token` etc. as +normal as per [MSC4341]. + +The sequence diagram for steps 4, 5 and 6 is as follows: (for readability a pair of `SecureSend,SecureReceive` operations via the Homeserver is represented by a single `SecureSendReceive` between the two devices) @@ -1023,17 +1025,28 @@ sequenceDiagram participant HS as Homeserver rect rgba(0,255,0, 0.1) - + note over E: 4) Existing device checks device_id from m.login.protocol message E->>HS: GET /_matrix/client/v3/devices/{device_id} alt device already exists HS->>E: 200 OK E->>N: SecureSendReceive({ "type": "m.login.failure", "reason": "device_already_exists" }) else device not found HS->>E: 404 Not Found + E->>UA: Existing device opens
verification_uri_complete (with fallback to verification_uri)
in the system web browser/ASWebAuthenticationSession: + Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen + E->>HS: SecureSend({"type":"m.login.protocol_accepted"}) end par - E->>N: SecureSendReceive({"type":"m.login.protocol_accepted"}) - note over N: 4) New device polls the homeserver awaiting the outcome as per RFC 8628 / MSC4341 + Note over UA: 5) User is asked by the homeserver to consent + rect rgba(240,240,240,0.5) + UA->>HS: GET https://id.matrix.org/device/abcde + HS->>UA: consent screen showing the user_code + UA->>HS: Allow or Deny + end + Note over UA: User closes browser + and + HS->>N: SecureReceive({"type":"m.login.protocol_accepted"}) + note over N: 6) New device polls the homeserver awaiting the outcome as per RFC 8628 / MSC4341 loop Poll for result at interval seconds N->>HS: POST /token client_id=xyz
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=XYZ alt pending @@ -1050,15 +1063,6 @@ sequenceDiagram N->>E: SecureSendReceive({"type":"m.login.failure", "reason": "authorization_expired"}) end end - and - E->>UA: 5) Existing device opens
verification_uri_complete (with fallback to verification_uri)
in the system web browser/ASWebAuthenticationSession: - Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen - rect rgba(240,240,240,0.5) - UA->>HS: GET https://id.matrix.org/device/abcde - HS->>UA: consent screen showing the user_code - UA->>HS: POST /allow or /deny - end - Note over UA: User closes browser end end ``` From e032ea78b9b9209707d5604276c2857f2f2d4aee Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Sep 2025 11:42:37 +0100 Subject: [PATCH 48/70] And example for `device_already_exists` outcome --- proposals/4108-oidc-qr-login.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 00cf5f37777..bb445cf0fef 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -958,7 +958,16 @@ existing device then asserts that there is no existing device corresponding to t It does so by calling [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) and expecting to receive an HTTP 404 response. -If the device already exists then the login request should be rejected with an `m.login.failure` and reason `device_already_exists`. +If the device already exists then the login request should be rejected with an `m.login.failure` and reason `device_already_exists`: + +*Existing device => New device via secure channel* + +```json +{ + "type": "m.login.failure", + "reason": "device_already_exists" +} +``` If no existing device was found then the existing device opens the `verification_uri_complete` - falling back to the `verification_uri`, if `verification_uri_complete` isn't present - in a system browser. From 9dab408720d89ad4d96dabc7fb3ba353f8072aea Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Sep 2025 17:46:03 +0100 Subject: [PATCH 49/70] Rendezvous authentication and optionality Make rendezvous API optional and return 404. Make authentication on creating rendezvous optional and return 403. Add client header filtering for unsafe content. Clean up for readability. --- proposals/4108-oidc-qr-login.md | 72 +++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index bb445cf0fef..6ea1ffb7764 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -16,7 +16,11 @@ Table of contents: - [High-level description](#high-level-description) - [The send mechanism](#the-send-mechanism) - [Expiry](#expiry) - - [API](#api) + - [POST /_matrix/client/v1/rendezvous](#rz-post) + - [PUT /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-put) + - [GET /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-get) + - [DELETE /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-delete) + - [Implementation notes](#rz-implementation-notes) - [Example API usage](#example-api-usage) - [Threat analysis](#threat-analysis) - [Secure channel](#secure-channel) @@ -68,6 +72,8 @@ It is proposed that an HTTP-based protocol be used to establish an ephemeral bi- which the two devices can exchange the necessary data. This session is described as "insecure" as it provides no end-to-end confidentiality nor authenticity by itself---these are layered on top of it. +New optional HTTP endpoints are to be added to the Client-Server API. + #### High-level description Suppose that Device A wants to establish communications with Device B. Device A can do so by creating a @@ -97,11 +103,10 @@ The rendezvous session (i.e. the payload) SHOULD expire after a period of time c `expires_ts` field on the `POST` and `GET` response bodies. After this point, any further attempts to query or update the payload MUST fail. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. -#### API - -##### Create a rendezvous session and send initial payload: `POST /_matrix/client/v1/rendezvous` +#### `POST /_matrix/client/v1/rendezvous` - Create a rendezvous session and send initial payload {#rz-post} -This would be part of the Client-Server API. +Rate-limited: Yes +Requires authentication: Optional - depending on server policy Request body is `application/json` with contents: @@ -123,7 +128,9 @@ Content-Type: application/json HTTP response codes, and Matrix error codes: - `200 OK` - rendezvous session created -- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large +- `403 Forbidden` (`M_FORBIDDEN`) - the requester is not authorized to create the rendezvous session +- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled +- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied `data` value is larger than the 4096 UTF8 character limit - `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited Response body for `200 OK` is `application/json` with contents: @@ -147,7 +154,20 @@ Content-Type: application/json } ``` -##### Send a payload to the rendezvous session: `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` +The server can chose what level of authentication is required to create a rendezvous session. Suitable policies might +include: + +- Public/open - anyone can create a rendezvous session without an access token. This allows for the QR code to be + created on either the new or existing Matrix client. +- Requires authenticated user - this would reduce abuse to known users, but would restrict the mechanism so that the QR + code must be created on the existing Matrix client (and therefore the new Matrix client must have a camera). + +The expiry time is detailed [below](#rz-ttl). + +#### `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` - Send a payload to an existing rendezvous {#rz-put} + +Rate-limited: Yes +Requires authentication: No Request body is `application/json` with contents: @@ -172,8 +192,9 @@ HTTP response codes, and Matrix error codes: - `200 OK` - payload updated - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled - `409 Conflict` (`M_CONCURRENT_WRITE`, a new error code) - when the `sequence_token` does not match -- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied payload is too large +- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied `data` value is larger than the 4096 UTF8 character limit - `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited The response body for `200 OK` is `application/json` with contents: @@ -193,12 +214,17 @@ Content-Type: application/json } ``` -##### Receive a payload from the rendezvous session: `GET /_matrix/client/v1/rendezvous/{rendezvousId}` +#### `GET /_matrix/client/v1/rendezvous/{rendezvousId}` - Receive a payload from a rendezvous session {#rz-get} + +Rate-limited: Yes +Requires authentication: No HTTP response codes, and Matrix error codes: - `200 OK` - payload returned +- `403 Forbidden` (`M_FORBIDDEN`) - request is not allowed due to the unsafe content policy (see below) - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled - `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited Response body for `200 OK` is `application/json` with contents: @@ -220,21 +246,23 @@ Content-Type: application/json } ``` -A future optimisation could be allow the client to "long-poll" by sending the previous `sequence_token` as a query parameter -and then the server returns when the is new data or some timeout has passed. +To help mitigate the threat of [unsafe content](#unsafe-content), the server SHOULD inspect the `Sec-Fetch-*` +[Fetch Metadata Request Headers](https://www.w3.org/TR/fetch-metadata/) (or other suitable headers) to identify +top-level navigation requests and return a `403` HTTP response with error code `M_FORBIDDEN` instead. + +#### `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` - cancel a rendezvous session {#rz-delete} -##### Cancel a rendezvous session: `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` +Rate-limited: Yes +Requires authentication: No HTTP response codes: - `200 OK` - rendezvous session cancelled - `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) +- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled - `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited -##### Authentication - -These API endpoints do not require authentication because trust is established at the secure channel layer which is -described later. +#### Implementation notes {#rz-implementation-notes} ##### Maximum payload size @@ -245,7 +273,7 @@ The server MUST enforce a maximum payload size of 4KB. The `sequence_token` values should be unique to the rendezvous session and the last modified time so that two clients can distinguish between identical payloads sent by either client. -##### Maximum duration of a rendezvous +##### Maximum duration of a rendezvous {#rz-ttl} The rendezvous session needs to persist for the duration of the login including allowing the user another time to confirm that the secure channel has been established and complete any extra homeserver mandated login steps such as MFA. @@ -339,9 +367,10 @@ As such, the following standard mitigations such as the following may be deemed implementations and administrators: - rate limiting of requests -- imposing a low maximum payload size (e.g. kilobytes not megabytes) - limiting the number of concurrent sessions +Furthermore, this proposal limits the maximum payload size to 4KB. + ##### Data exfiltration Because the rendezvous session protocol allows for the storage of arbitrary data, it @@ -350,7 +379,7 @@ is possible to use it to circumvent firewalls and other network security measure Implementation may want to block their production IP addresses from being able to make requests to the rendezvous endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. -##### Unsafe content +##### Unsafe content {#unsafe-content} Because the rendezvous session is not authenticated, it is possible for an attacker to use it to distribute malicious content. @@ -359,9 +388,10 @@ This could lead to a reputational problem for the homeserver domain or IPs, as w Mitigations that are included in this proposal: -- the low maximum payload size -- restricted allowed content type +- the low maximum payload size (4KB) +- payload is restricted to string - the rendezvous session should be short-lived +- use of `Sec-Fetch-*` headers to not return payload content when browser has navigated to the session URL ### Secure channel From da563329480b92eeed548310ed543100d72d3e3d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 22 Sep 2025 17:56:33 +0100 Subject: [PATCH 50/70] Add alternative about unauthenticated device creating "redirect channel" --- proposals/4108-oidc-qr-login.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 6ea1ffb7764..8432b2ca38b 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1567,6 +1567,34 @@ One could try and do something with STUN or TURN or [COAP](https://datatracker.i Rather than requiring the devices to poll for updates, "long-polling" could be used instead similar to `/sync`. Or WebSockets. +#### Unauthenticated device could crated "redirect channel" without payload + +In the current proposal the server operator may choose to not allow unauthenticated devices to create a rendezvous +session to reduce abuse/attack vectors. + +In this scenario it means that the unauthenticated client cannot create the QR code. + +An alternative would be to do something like this: + +1. Unauthenticated device (UD) creates a "redirect channel" on HS1 and sets that in the QR code. +1. The authenticated device (AD) creates a rendezvous channel on HS2. +1. HS2 POSTS to the redirect channel on HS1 with the homeserver and rendezvous channel ID. HS1 validates its from HS2. +1. HS1 returns the homeserver (HS2) and rendezvous channel ID to UD, who then uses that channel as normal. + +This has the following properties: + +1. It limits how much information can be persisted on an unauthenticated channel. We can severely restrict the size + of the request ID for example. +1. An abuser must use a domain they own if they want to encode dodgy data in the rendezvous channel ID. We can then ban + abusive domains. +1. An unauthenticated device can only receive information, rather than create a 2-way channel. Not sure that's at all + useful thing to assert, but it is nonetheless a property. +1. For each redirect channel created, you can only send one payload. This makes it easier to heavily ratelimit. + +Erik [said](https://github.com/matrix-org/matrix-spec-proposals/pull/4108#discussion_r2336295451): +> I think this sort of flow would reduce potential abuse vectors, but equally makes things more complicated and may not +> be worth it. + ### Alternative method of secret sharing Instead of the existing device sharing the secrets bundle instead the existing device could cross-sign the new device From 6612944f15aa4422c71aec45fd7265e8ed2c167b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 09:37:09 +0100 Subject: [PATCH 51/70] Fix description of discovery steps --- proposals/4108-oidc-qr-login.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 8432b2ca38b..870fc0549c0 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1497,9 +1497,10 @@ Before offering this capability it would make sense that the device can check th Where the homeserver is known: -1. Check if the homeserver has a rendezvous session API available (/versions) from this MSC 1. Check that the homeserver is using the OAuth 2.0 API using [server metadata discovery](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) 1. Check that the Device Authorization Grant is available as per [MSC4341] +1. Check if the homeserver has a rendezvous session API available by attempting a POST to the create rendezvous endpoint + from this MSC. For a new device it would need to know the homeserver ahead of time in order to do these checks. From 9ea6d7db6587a7c4a92ec3bc678302e3f418d97b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 09:55:11 +0100 Subject: [PATCH 52/70] Move QR format part of proposal to where it sits in the flow --- proposals/4108-oidc-qr-login.md | 175 ++++++++++++++++---------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 870fc0549c0..d8c1c17e65f 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -23,6 +23,9 @@ Table of contents: - [Implementation notes](#rz-implementation-notes) - [Example API usage](#example-api-usage) - [Threat analysis](#threat-analysis) + - [QR code format](#qr-code-format) + - [Example for QR code generated on new device](#example-for-qr-code-generated-on-new-device) + - [Example for QR code generated on existing device](#example-for-qr-code-generated-on-existing-device) - [Secure channel](#secure-channel) - [Establishment](#establishment) - [Sequence diagram](#sequence-diagram) @@ -32,9 +35,6 @@ Table of contents: - [Login via OAuth 2.0 Device Authorization Grant from MSC4341](#login-via-oauth-20-device-authorization-grant-from-msc4341) - [Secret sharing and device verification](#secret-sharing-and-device-verification) - [Message reference](#message-reference) - - [QR code format](#qr-code-format) - - [Example for QR code generated on new device](#example-for-qr-code-generated-on-new-device) - - [Example for QR code generated on existing device](#example-for-qr-code-generated-on-existing-device) - [Discoverability of the capability](#discoverability-of-the-capability) - [Potential issues](#potential-issues) - [Alternatives](#alternatives) @@ -60,11 +60,12 @@ In order for the new device to be fully set up, it needs to exchange information - The existing device can facilitate the new device in getting an access token - The existing device shares the secrets necessary to set up end-to-end encryption -This proposal is split into three parts: +This proposal is split into four parts: 1. An insecure rendezvous session API to allow the two devices to exchange the necessary data -2. A secure channel to protect the data exchanged over the rendezvous session -3. The OAuth 2.0 login part and set up of E2EE +2. How the location of the rendezvous session is represented as a QR +3. A secure channel to protect the data exchanged over the rendezvous session +4. The OAuth 2.0 login part and set up of E2EE ### Insecure rendezvous session @@ -393,6 +394,87 @@ Mitigations that are included in this proposal: - the rendezvous session should be short-lived - use of `Sec-Fetch-*` headers to not return payload content when browser has navigated to the session URL +### QR code format + +The proposed format of the QR code intends to be similar to that which is already described in the Client-Server API for +[device verification](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). + +Additional modes are added to the byte used for "QR code verification mode" to allow for the two login intents: initiate +on a new device; reciprocate on an existing device; + +The QR codes to be displayed and scanned using this format will encode binary strings in the general form: + +- the ASCII string `MATRIX` +- one byte indicating the QR code version (must be `0x02`) +- one byte indicating the QR code intent/mode. Should be one of the following values: + - `0x03` a new device wishing to initiate a login and self-verify + - `0x04` an existing device wishing to reciprocate the login of a new device and self-verify that other device +- the ephemeral Curve25519 public key, as 32 bytes +- the rendezvous session ID encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 + string + - the rendezvous session ID as a UTF-8 string +- the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string + - the server name as a UTF-8 string + +#### Example for QR code generated on new device + +A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`matrix.org` is as follows: +(Whitespace is for readability only) + +``` +4D 41 54 52 49 58 02 03 +d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b +00 24 +65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 +00 0A +6d 61 74 72 69 78 2e 6f 72 67 +``` + +Which looks as follows as a QR with error correction level Q: + +![Example QR for mode 0x03](images/4108-qr-mode03.png) + +#### Example for QR code generated on existing device + +A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`matrix.org` is as follows: (Whitespace is for readability only) + +``` +4D 41 54 52 49 58 02 04 +d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b +00 24 +65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 +00 0A +6d 61 74 72 69 78 2e 6f 72 67 +``` + +Which looks as follows as a QR with error correction level Q: + +![Example QR for mode 0x04](images/4108-qr-mode04.png) + ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or @@ -1410,87 +1492,6 @@ Example: } ``` -### QR code format - -The proposed format of the QR code intends to be similar to that which is already described in the Client-Server API for -[device verification](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). - -Additional modes are added to the byte used for "QR code verification mode" to allow for the two login intents: initiate -on a new device; reciprocate on an existing device; - -The QR codes to be displayed and scanned using this format will encode binary strings in the general form: - -- the ASCII string `MATRIX` -- one byte indicating the QR code version (must be `0x02`) -- one byte indicating the QR code intent/mode. Should be one of the following values: - - `0x03` a new device wishing to initiate a login and self-verify - - `0x04` an existing device wishing to reciprocate the login of a new device and self-verify that other device -- the ephemeral Curve25519 public key, as 32 bytes -- the rendezvous session ID encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 - string - - the rendezvous session ID as a UTF-8 string -- the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string - - the server name as a UTF-8 string - -#### Example for QR code generated on new device - -A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: -(Whitespace is for readability only) - -``` -4D 41 54 52 49 58 02 03 -d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 24 -65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67 -``` - -Which looks as follows as a QR with error correction level Q: - -![Example QR for mode 0x03](images/4108-qr-mode03.png) - -#### Example for QR code generated on existing device - -A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: (Whitespace is for readability only) - -``` -4D 41 54 52 49 58 02 04 -d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 24 -65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67 -``` - -Which looks as follows as a QR with error correction level Q: - -![Example QR for mode 0x04](images/4108-qr-mode04.png) - ### Discoverability of the capability Before offering this capability it would make sense that the device can check the availability of the feature. From 66a9124bd2ff591ca234009fe04caca55cde09da Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 10:53:20 +0100 Subject: [PATCH 53/70] QR code clarifications Clarify original thinking behind QR format. Add notes about versioning and unstable prefixes --- proposals/4108-oidc-qr-login.md | 53 +++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index d8c1c17e65f..8a8a33f9764 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -396,19 +396,25 @@ Mitigations that are included in this proposal: ### QR code format -The proposed format of the QR code intends to be similar to that which is already described in the Client-Server API for -[device verification](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). +Once a device creates the rendezvous session it then generates a QR code that contains sufficient information for the +scanning device to locate the rendezvous session. -Additional modes are added to the byte used for "QR code verification mode" to allow for the two login intents: initiate -on a new device; reciprocate on an existing device; +It is proposed that the QR code format that is currently used in the Client-Server API for +[device verification](https://spec.matrix.org/v1.16/client-server-api/#qr-code-format) be extended to be more general +purpose and accommodate this new use case. + +The "QR code verification mode" would be changed to be something more general like "QR code mode". + +We then define two new modes to allow for the two login device dispositions: the new device wishing to login; existing +device wishing to facilitate the login; The QR codes to be displayed and scanned using this format will encode binary strings in the general form: - the ASCII string `MATRIX` - one byte indicating the QR code version (must be `0x02`) -- one byte indicating the QR code intent/mode. Should be one of the following values: - - `0x03` a new device wishing to initiate a login and self-verify - - `0x04` an existing device wishing to reciprocate the login of a new device and self-verify that other device +- one byte indicating the QR code mode. Should be one of the following values: + - `0x03` a new device wishing to login and self-verify + - `0x04` an existing device wishing to facilitate the login of a new device and self-verify that other device - the ephemeral Curve25519 public key, as 32 bytes - the rendezvous session ID encoded as: - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 @@ -418,12 +424,13 @@ The QR codes to be displayed and scanned using this format will encode binary st - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string - the server name as a UTF-8 string +If a new version of this QR sign in capability is needed in future (perhaps with updated secure channel protocol) then +additional "mode" values can be added. + #### Example for QR code generated on new device -A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: -(Whitespace is for readability only) +A full example for a new device at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 03 @@ -449,8 +456,7 @@ d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 #### Example for QR code generated on existing device -A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +A full example for an existing device at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver `matrix.org` is as follows: (Whitespace is for readability only) ``` @@ -1616,6 +1622,18 @@ mechanism does not offer. chance of other devices seeing the new device as unverified, incorrectly prompting the user to verify the device that will soon be verified. +### Alternative QR code formats + +Instead of extending the existing QR code format and adding new "modes", alternatives include: + +- using a different prefix to namespace the data (e.g. `MATRIX_LOGIN` instead of `MATRIX`) +- keep the `MATRIX` prefix and repurpose the current "version" byte to be something like "type". For example: + - type = `0x02` could be the verification format described in the current spec + - type = `0x03` could be for this proposal + +The purpose being that we end up with the QR being better namespaced (whilst also remaining compact) making future +versioning simpler. + ## Security considerations This proposed mechanism has been designed to protects users and their devices from the following threats: @@ -1657,6 +1675,8 @@ Recommendations to mitigate this are: ## Unstable prefix +### Rendezvous API prefix + While this feature is in development the new API endpoints should be exposed using the following unstable prefix: - `/_matrix/client/unstable/org.matrix.msc4108/rendezvous` instead of `/_matrix/client/v1/rendezvous` @@ -1673,6 +1693,13 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin } ``` +### Unstable QR code format + +It would be helpful to make it clear that the QR code format is unstable, but need some guidance on what would be the +best way to do this. See notes above about QR format/versioning. + +### M_CONCURRENT_WRITE errcode + Furthermore, where a new `errcode` is being introduced the existing `M_UNKNOWN` code should be used instead, with the new code placed in a `org.matrix.msc4108.errcode` field instead. For example, instead of: From fce6f150653c3ac7d3dcaaa0f1000473594767e3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 10:54:50 +0100 Subject: [PATCH 54/70] Wording on new/existing device --- proposals/4108-oidc-qr-login.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 8a8a33f9764..a08421f2932 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -405,8 +405,8 @@ purpose and accommodate this new use case. The "QR code verification mode" would be changed to be something more general like "QR code mode". -We then define two new modes to allow for the two login device dispositions: the new device wishing to login; existing -device wishing to facilitate the login; +We then define two new modes to allow for the two login device dispositions: the new device wishing to login; an existing +device wishing to facilitate the login of the new device; The QR codes to be displayed and scanned using this format will encode binary strings in the general form: From 95be8fd0b1faaa57add67c231ddc44c5fb3b8273 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 10:57:06 +0100 Subject: [PATCH 55/70] Remove another reference to reciprocate --- proposals/4108-oidc-qr-login.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index a08421f2932..28c18d2f14c 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -531,8 +531,8 @@ Device G displays a QR code containing: - Its public key **Gp** - The insecure rendezvous session **ID** -- An indicator (the **intent**) to say if this is a new device which wishes to "initiate" a login, or an existing device -that wishes to "reciprocate" a login +- An indicator (the **intent**) to say if this is the new device which wishes to login, or an existing device +that wishes to facilitate the login of the new device - the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a From 740da4b5ccc8d6363f96329883def52f387bc49f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 11:00:00 +0100 Subject: [PATCH 56/70] Reinstate note about long poll for future --- proposals/4108-oidc-qr-login.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 28c18d2f14c..2a6ef06908a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -251,6 +251,9 @@ To help mitigate the threat of [unsafe content](#unsafe-content), the server SHO [Fetch Metadata Request Headers](https://www.w3.org/TR/fetch-metadata/) (or other suitable headers) to identify top-level navigation requests and return a `403` HTTP response with error code `M_FORBIDDEN` instead. +A future optimisation could be allow the client to "long-poll" by sending the previous `sequence_token` as a query parameter +and then the server returns when the is new data or some timeout has passed. + #### `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` - cancel a rendezvous session {#rz-delete} Rate-limited: Yes From 7d768f70fea93b852a76e7aa59fc8d3bbb86bea3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 13:36:54 +0100 Subject: [PATCH 57/70] Revert removal of public key from example QRs --- proposals/4108-oidc-qr-login.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 2a6ef06908a..cb8b7a6f034 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -432,8 +432,10 @@ additional "mode" values can be added. #### Example for QR code generated on new device -A full example for a new device at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: (Whitespace is for readability only) +A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver +`matrix.org` is as follows: +(Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 03 @@ -459,8 +461,8 @@ d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 #### Example for QR code generated on existing device -A full example for an existing device at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: (Whitespace is for readability only) +A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 +encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver`matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 02 04 From 82e775f4e456a8543e68de65b60affc4fd6448b8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 14:11:36 +0100 Subject: [PATCH 58/70] Clarify 4KB --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index cb8b7a6f034..c6e5e1a0943 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -270,7 +270,7 @@ HTTP response codes: ##### Maximum payload size -The server MUST enforce a maximum payload size of 4KB. +The server MUST enforce a maximum payload size of 4096 UTF8 characters. ##### `sequence_token` values From 3f1321b908e94fa2b695831689e67a631638e92d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Sep 2025 14:18:56 +0100 Subject: [PATCH 59/70] Clarifications around public key in QR --- proposals/4108-oidc-qr-login.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index c6e5e1a0943..30c61b8cae7 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -63,7 +63,7 @@ In order for the new device to be fully set up, it needs to exchange information This proposal is split into four parts: 1. An insecure rendezvous session API to allow the two devices to exchange the necessary data -2. How the location of the rendezvous session is represented as a QR +2. The structure of the QR code that contains the rendezvous session details 3. A secure channel to protect the data exchanged over the rendezvous session 4. The OAuth 2.0 login part and set up of E2EE @@ -400,7 +400,8 @@ Mitigations that are included in this proposal: ### QR code format Once a device creates the rendezvous session it then generates a QR code that contains sufficient information for the -scanning device to locate the rendezvous session. +scanning device to locate the rendezvous session and establish the secure channel (as described in the +[next section](#secure-channel)). It is proposed that the QR code format that is currently used in the Client-Server API for [device verification](https://spec.matrix.org/v1.16/client-server-api/#qr-code-format) be extended to be more general @@ -418,7 +419,7 @@ The QR codes to be displayed and scanned using this format will encode binary st - one byte indicating the QR code mode. Should be one of the following values: - `0x03` a new device wishing to login and self-verify - `0x04` an existing device wishing to facilitate the login of a new device and self-verify that other device -- the ephemeral Curve25519 public key, as 32 bytes +- the ephemeral Curve25519 public key that will be used for [secure channel establishment](#establishment), as 32 bytes - the rendezvous session ID encoded as: - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 string From 1d45957f74dd6cd1bd19e8a3377e5baa40c7a840 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 14 Oct 2025 16:19:17 +0200 Subject: [PATCH 60/70] GitHub doesn't like the link syntax I used --- proposals/4108-oidc-qr-login.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 30c61b8cae7..c88d71bc249 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -16,11 +16,11 @@ Table of contents: - [High-level description](#high-level-description) - [The send mechanism](#the-send-mechanism) - [Expiry](#expiry) - - [POST /_matrix/client/v1/rendezvous](#rz-post) - - [PUT /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-put) - - [GET /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-get) - - [DELETE /_matrix/client/v1/rendezvous/{rendezvousId}](#rz-delete) - - [Implementation notes](#rz-implementation-notes) + - [POST /_matrix/client/v1/rendezvous](#post-_matrixclientv1rendezvous---create-a-rendezvous-session-and-send-initial-payload) + - [PUT /_matrix/client/v1/rendezvous/{rendezvousId}](#put-_matrixclientv1rendezvousrendezvousid---send-a-payload-to-an-existing-rendezvous) + - [GET /_matrix/client/v1/rendezvous/{rendezvousId}](#get-_matrixclientv1rendezvousrendezvousid---receive-a-payload-from-a-rendezvous-session) + - [DELETE /_matrix/client/v1/rendezvous/{rendezvousId}](#delete-_matrixclientv1rendezvousrendezvousid---cancel-a-rendezvous-session) + - [Implementation notes](#implementation-notes) - [Example API usage](#example-api-usage) - [Threat analysis](#threat-analysis) - [QR code format](#qr-code-format) @@ -104,7 +104,7 @@ The rendezvous session (i.e. the payload) SHOULD expire after a period of time c `expires_ts` field on the `POST` and `GET` response bodies. After this point, any further attempts to query or update the payload MUST fail. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. -#### `POST /_matrix/client/v1/rendezvous` - Create a rendezvous session and send initial payload {#rz-post} +#### `POST /_matrix/client/v1/rendezvous` - Create a rendezvous session and send initial payload Rate-limited: Yes Requires authentication: Optional - depending on server policy @@ -163,9 +163,9 @@ include: - Requires authenticated user - this would reduce abuse to known users, but would restrict the mechanism so that the QR code must be created on the existing Matrix client (and therefore the new Matrix client must have a camera). -The expiry time is detailed [below](#rz-ttl). +The expiry time is detailed [below](#maximum-duration-of-a-rendezvous). -#### `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` - Send a payload to an existing rendezvous {#rz-put} +#### `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` - Send a payload to an existing rendezvous Rate-limited: Yes Requires authentication: No @@ -215,7 +215,7 @@ Content-Type: application/json } ``` -#### `GET /_matrix/client/v1/rendezvous/{rendezvousId}` - Receive a payload from a rendezvous session {#rz-get} +#### `GET /_matrix/client/v1/rendezvous/{rendezvousId}` - Receive a payload from a rendezvous session Rate-limited: Yes Requires authentication: No @@ -254,7 +254,7 @@ top-level navigation requests and return a `403` HTTP response with error code ` A future optimisation could be allow the client to "long-poll" by sending the previous `sequence_token` as a query parameter and then the server returns when the is new data or some timeout has passed. -#### `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` - cancel a rendezvous session {#rz-delete} +#### `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` - cancel a rendezvous session Rate-limited: Yes Requires authentication: No @@ -266,7 +266,7 @@ HTTP response codes: - `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled - `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited -#### Implementation notes {#rz-implementation-notes} +#### Implementation notes ##### Maximum payload size @@ -277,7 +277,7 @@ The server MUST enforce a maximum payload size of 4096 UTF8 characters. The `sequence_token` values should be unique to the rendezvous session and the last modified time so that two clients can distinguish between identical payloads sent by either client. -##### Maximum duration of a rendezvous {#rz-ttl} +##### Maximum duration of a rendezvous The rendezvous session needs to persist for the duration of the login including allowing the user another time to confirm that the secure channel has been established and complete any extra homeserver mandated login steps such as MFA. @@ -383,7 +383,7 @@ is possible to use it to circumvent firewalls and other network security measure Implementation may want to block their production IP addresses from being able to make requests to the rendezvous endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. -##### Unsafe content {#unsafe-content} +##### Unsafe content Because the rendezvous session is not authenticated, it is possible for an attacker to use it to distribute malicious content. From af0a6bfdde75a7472bd172d6d3e1e5588a5234a3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 31 Oct 2025 11:52:42 +0000 Subject: [PATCH 61/70] Update unstable prefixes including on QR code --- proposals/4108-oidc-qr-login.md | 69 +++++++++++-------- proposals/images/4108-qr-mode04-unstable.png | Bin 0 -> 813 bytes 2 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 proposals/images/4108-qr-mode04-unstable.png diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index c88d71bc249..78e906786c7 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -439,7 +439,8 @@ encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on home (Whitespace is for readability only) ``` -4D 41 54 52 49 58 02 03 +4D 41 54 52 49 58 +02 03 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 @@ -451,7 +452,8 @@ Which looks as follows as a QR with error correction level Q: +![Example QR for mode 0x04](images/4108-qr-mode04-unstable.png) -The server should send: +### M_CONCURRENT_WRITE errcode -```json -{ - "errcode": "M_UNKNOWN", - "org.matrix.msc4108.errcode": "M_CONCURRENT_WRITE", - "error": "Data was modified" -} -``` +The unstable value of `IO_ELEMENT_MSC4108_CONCURRENT_WRITE` should be used instead of `M_CONCURRENT_WRITE`. ## Dependencies diff --git a/proposals/images/4108-qr-mode04-unstable.png b/proposals/images/4108-qr-mode04-unstable.png new file mode 100644 index 0000000000000000000000000000000000000000..5ab3939a8a93e8882700e12838a7cf12533936b7 GIT binary patch literal 813 zcmV+|1JeA7P)C zmpzW-Fbsuhs8APQK!C2HPFJ!o;GYZFS5l{I2+#$r&V^v^lQWswVvD8Q2?8Ve3^3*s z`N$Ce^F_ZKA4DUomCYEbi?#&nzP+M=9rLHVN?*x2OmuL0JGgRZ;l3MFS08u zet<(AW1Ts#K^>X~&8r+AE_2KWrAaG}_)5JTqv(2u#&sfFPU_Cu$#Dr>B4oCgGvsM< z=m|MSuxJm>i??eu1HH>fhWWG`<;xpFerKH=&2oG=ID|dCd3iIQxc{y}8Aj|96V7Ts z@frP8dpSmPzo?R{(COtf3gH1hJS2zAC}$Wj@2qw?#%U80jfn^t*LOF_@IlbYE*dUX z;V~EpMUF3m-Y}RnMtjTljxEF<^$VLpY_t^m()4l+od~Cmt5#^QrR6HdXv#&mso1xu zY4UHqWw^C9)$UX~5=CRtXGBYIVT(lfsP^WX7zlQiWB5~tW7h_Thb%98&om)+YHyuX z@pKH&dxXoqDGW|+gkMnC=NLrzg252=7^Plh?9m#;I*=N5baDr6bH(3gz1 zu7gJ*w|T;H%$x>-*fjZ8MB~@8Aj3y1I#o_}xx8Vfoq3mI*d#pWwkvcwdVo?#(a#~)uYll7800000NkvXXu0mjf4_I}f literal 0 HcmV?d00001 From a4af2d6ad26fc90b49b82a43943cc8ba189a5b1e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 3 Nov 2025 16:20:33 +0000 Subject: [PATCH 62/70] Update QR code format under "type" 0x03 The byte immediately following the `MATRIX` prefix is repurposed as a type which gives a more sensible way to namespace in future. This also means that we can call the second byte the "intent" very clearly rather than having to call it "mode" to match the existing cross verification QR spec. --- proposals/4108-oidc-qr-login.md | 77 +++++++++--------- proposals/images/4108-qr-intent00.png | Bin 0 -> 810 bytes .../images/4108-qr-intent01-unstable.png | Bin 0 -> 815 bytes proposals/images/4108-qr-intent01.png | Bin 0 -> 817 bytes proposals/images/4108-qr-mode03.png | Bin 809 -> 0 bytes proposals/images/4108-qr-mode04-unstable.png | Bin 813 -> 0 bytes proposals/images/4108-qr-mode04.png | Bin 816 -> 0 bytes 7 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 proposals/images/4108-qr-intent00.png create mode 100644 proposals/images/4108-qr-intent01-unstable.png create mode 100644 proposals/images/4108-qr-intent01.png delete mode 100644 proposals/images/4108-qr-mode03.png delete mode 100644 proposals/images/4108-qr-mode04-unstable.png delete mode 100644 proposals/images/4108-qr-mode04.png diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 78e906786c7..bf488099c65 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -399,26 +399,29 @@ Mitigations that are included in this proposal: ### QR code format -Once a device creates the rendezvous session it then generates a QR code that contains sufficient information for the -scanning device to locate the rendezvous session and establish the secure channel (as described in the -[next section](#secure-channel)). +To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a +similar structure to that of the existing Device Verification QR code encoding described in [Client-Server +API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). It is proposed that the QR code format that is currently used in the Client-Server API for [device verification](https://spec.matrix.org/v1.16/client-server-api/#qr-code-format) be extended to be more general -purpose and accommodate this new use case. +purpose and accommodate this new use case, and future use cases. -The "QR code verification mode" would be changed to be something more general like "QR code mode". +The "QR code version" would be repurposed to be a "QR code type" and used as the way to distinguish the format of the +subsequent data. -We then define two new modes to allow for the two login device dispositions: the new device wishing to login; an existing -device wishing to facilitate the login of the new device; +The existing cross verification code would be type `0x02`. I suspect that type `0x01` and `0x00` might correspond to +earlier iterations of the cross signing flow and so might want to be "reserved". + +This proposal then adds a new type `0x03`. The QR codes to be displayed and scanned using this format will encode binary strings in the general form: - the ASCII string `MATRIX` -- one byte indicating the QR code version (must be `0x02`) -- one byte indicating the QR code mode. Should be one of the following values: - - `0x03` a new device wishing to login and self-verify - - `0x04` an existing device wishing to facilitate the login of a new device and self-verify that other device +- one byte indicating the QR code type: `0x03` which identifies that the QR is part of this proposal +- one byte indicating the intent of the device generating the QR: + - `0x00` a new device wishing to login and self-verify + - `0x01` an existing device wishing to facilitate the login of a new device and self-verify that other device - the ephemeral Curve25519 public key that will be used for [secure channel establishment](#establishment), as 32 bytes - the rendezvous session ID encoded as: - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 @@ -429,7 +432,7 @@ The QR codes to be displayed and scanned using this format will encode binary st - the server name as a UTF-8 string If a new version of this QR sign in capability is needed in future (perhaps with updated secure channel protocol) then -additional "mode" values can be added. +an additional type can then be allocated which would clearly distinguish this later version. #### Example for QR code generated on new device @@ -440,7 +443,7 @@ encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on home ``` 4D 41 54 52 49 58 -02 03 +03 00 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 @@ -453,14 +456,14 @@ Which looks as follows as a QR with error correction level Q: Generated with: nix-shell -p qrencode --run 'echo "4D 41 54 52 49 58 -02 03 +03 00 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-mode03.png' +6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent00.png' --> -![Example QR for mode 0x03](images/4108-qr-mode03.png) +![Example QR for intent 0x00](images/4108-qr-intent00.png) #### Example for QR code generated on existing device @@ -469,7 +472,7 @@ encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on hom ``` 4D 41 54 52 49 58 -02 04 +03 01 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 @@ -482,14 +485,14 @@ Which looks as follows as a QR with error correction level Q: Generated with: nix-shell -p qrencode --run 'echo "4D 41 54 52 49 58 -02 04 +03 01 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-mode04.png' +6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01.png' --> -![Example QR for mode 0x04](images/4108-qr-mode04.png) +![Example QR for intent 0x01](images/4108-qr-intent01.png) ### Secure channel @@ -537,7 +540,10 @@ with an empty payload. It parses the **ID** received. 3. **Initial key exchange** -Device G displays a QR code containing: +Device G displays a QR code containing sufficient information for the scanning device to locate the rendezvous session +and establish the secure channel (as described in the [next section](#secure-channel)). + +The information to be encoded is: - Its public key **Gp** - The insecure rendezvous session **ID** @@ -545,11 +551,7 @@ Device G displays a QR code containing: that wishes to facilitate the login of the new device - the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** -To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a -similar structure to that of the existing Device Verification QR code encoding described in [Client-Server -API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). - -This is defined in detail in a separate section of this proposal. +The format of this QR is defined in detail in a [separate section](#qr-code-format) of this proposal. Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **ID**, **intent** and the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)**. @@ -1634,15 +1636,14 @@ will soon be verified. ### Alternative QR code formats -Instead of extending the existing QR code format and adding new "modes", alternatives include: +An earlier version of this proposal kept the "version" byte at `0x02` and added additional "mode" +values of `0x03` (which is now intent `0x00`) and `0x04` (which is now intent `0x01`). -- using a different prefix to namespace the data (e.g. `MATRIX_LOGIN` instead of `MATRIX`) -- keep the `MATRIX` prefix and repurpose the current "version" byte to be something like "type". For example: - - type = `0x02` could be the verification format described in the current spec - - type = `0x03` could be for this proposal +The current usage of converting the "version" to be a "type" _feels_ like a more intuitive use of +the bytespace. -The purpose being that we end up with the QR being better namespaced (whilst also remaining compact) making future -versioning simpler. +Another alternative was to use a human readable prefix such as `MATRIX_LOGIN` instead of `MATRIX`. +This was discounted on the basis of wanting to keep the QR reasonably compact. ## Security considerations @@ -1715,7 +1716,7 @@ encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on hom ``` 49 4F 5F 45 4C 45 4D 45 4E 54 5F 4D 53 43 34 31 30 38 -02 04 +03 01 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 @@ -1728,14 +1729,16 @@ Which looks as follows as a QR with error correction level Q: Generated with: nix-shell -p qrencode --run 'echo "49 4F 5F 45 4C 45 4D 45 4E 54 5F 4D 53 43 34 31 30 38 -02 04 +03 01 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-mode04-unstable.png' +6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01-unstable.png' --> -![Example QR for mode 0x04](images/4108-qr-mode04-unstable.png) +![Example QR for intent 0x01](images/4108-qr-intent01-unstable.png) + +It is suggested that this unstable QR prefix convention could be used by future proposals. ### M_CONCURRENT_WRITE errcode diff --git a/proposals/images/4108-qr-intent00.png b/proposals/images/4108-qr-intent00.png new file mode 100644 index 0000000000000000000000000000000000000000..beed4414556306c188e53900d408c650ceed3f7c GIT binary patch literal 810 zcmV+_1J(SAP)C zmpjg*FcgI^a^Ws)z>>G&&RfD82(SUzk~?q1k~aXI3yV9)dFY>FMw&}IC?V)eL~@_! zg!qpK{onW?nqyghMSy@BF>D2uFp1Af1PppRR?fZs5z&@ENK0^!C#Bl;eZ!-a(sCJq)^2yBwbpj3%#; zr!lrMwONiYDtqcZxad6cs~_d~K)H19Um`OktYI_D@kRD<86nQmOb%Q8lR+8QCNN<| zg@|pJLwZ+UhELoqT7)mSg1m!dOSO5NfWt_p%r>gJFzt z?$SE>?^jCj<#4kcDt;sOHgPVn`Qk;xZop`lKBIEn^UZ3} ojWD1U68BD(4FBc7)PIbB0cUlCI3%rw5dZ)H07*qoM6N<$f@(~GhX4Qo literal 0 HcmV?d00001 diff --git a/proposals/images/4108-qr-intent01-unstable.png b/proposals/images/4108-qr-intent01-unstable.png new file mode 100644 index 0000000000000000000000000000000000000000..886b8b0eb559342452c6579dc0951b4496e130d6 GIT binary patch literal 815 zcmV+~1JL}5P)C zm%WYTFbsuhC{SiMU_fmsQ%iOO{%OE&NtxOZpa$$rfneU#%q(_;3y#7?cZ0#e_zc)2 z#rMb|{_ThUZ+sC=npRGCj=eLtr%sMTx5v*aZ+d_Txh7vgu^AXSD~K+c4q^ z<{`YqoUM4yWkGb5HmA4^bD79+j%&d0xPB)uz^L~e#_#Xf%Q5@X6BL;aoe=3_Qv-e|ogQU2JFCl)rw2utGd#vTuFRR%5PQuH z7;5MpdIvWVW7_DezaQb)<2er%b^)m0x!Zpiep)GUE@g1o1u_q`S*9niG<@nOo?IueTCII~V zqe~gSxn_1}K5XnQ{N`OM8GbZx4vw2Piy6zff66gh5B6}8LZoobnyVZ?7|uTJ!JHUt zoZwrh9Amm7xgn2>9^cJ4H{eiZjTv&(lD`%ZIer{`C7l7!HoK2&{$we_cc=wE2ooLw z)<$T+>@if$VPO@JxcXXKWEg@*y?iD)`fA%=)O2W<@8yJL>M3V)9F#~vRF*EaggKJj*KF~tLnah_snu!_d)mC2Yp%V zx?%1HT-)F5q3$e@6y{_j_L$Pxj;o~Z{2oVQJZa3sh!Ks29h=?WfRQC!%0kwtcNs#N tC zmobi{Fc3uxIdBFSu;gnv^Of)g8n^&l$(gTVi3>pHz@qlMo6$_LD{YYuq7i!C0=ugI zuWI7I9`w8MK{UmEcIHLAYCa=%a(r?fX#rgmKtg-3WQc8j_9 zc_?aoieDakHAHGDil1SwQ{vg9EM_-}ks zo*4V9M77sBaQjAz4{tdk;Ra{#?5goAF$N2BS?$_tnKR5p)Jetne3sFc_OzjF(c}O93$SN z-_NKHpF`x0zg{W92VqgUi|pYt);L9|3NC&}P=>t; zOt7dB;dVKsPv&L#EcT3chA|*cjGx#|j`8W<5h4vW>@(e^DZ-aiSmO{C3}$1+t(L`@ z84NPQwYApKf4x$IFNcTaP_Rb$H%!8Gatu9%(ryp8bIQH`^fVcUSFM`5XnIJz?5q3i zQijo2QFoA3DEr|oT;~BkTfHFdC;Eon4~o5ws;F^#W z&rY3keCRqvBnt87<~Mhrmth2R>)O1O0e&Gk0F?{i?F>YYA+_`+p vM}}LxSZFUEfW<2r29PK#cE00000NkvXXu0mjfE;WM> literal 0 HcmV?d00001 diff --git a/proposals/images/4108-qr-mode03.png b/proposals/images/4108-qr-mode03.png deleted file mode 100644 index f7c459e69d041215ce01bd7382a83d12f9e360ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 809 zcmV+^1J?YBP)C zmqD(pFbqYF7-R+;h?H$)mMy^s3f(~2QfApkq-+3Y8ASMB(>}d7_+R>F`VgY%vg|L0W%!8H>G8pBqVfv-#EoxV0@34W=hPpmf7a`oXugG)>b96i z&SUMjyZ8|ht0j`<`5xxBBwhoqmV92mgI8eWdrtQI{C*7zd>!R9ZHfF$ZTu;S|Hdcf zg$q|i*#?tmmT$Cp4~~-^W4{JFc=s~+l^Szu2yUQ)#82|5o^RkN>&=M~=$vS&cG2TA z&38G!4t9rxNaV{i<_@{PgArDq zuxiM2FFoq<9tdw9`XvM3g$>(9k56*D&r?7vu#<<2e=?}U!Ol#u zs5!#zP+K2CsPIwU5#0)7h%gxZ#BF*E!|tvO5z4}sl^pBw>1B`bDj3YhJ&Tsrm>CRZ zgsZgHDSRHO!dQFbJXh=yH35^bogVMdOO@N*7fyLF@1CZ^D9V@E4CGvnT3-Ggr4FO7 zPUs26b361hYhZ1O@e6KaEj` zF@<63saTj_-aVu3^q8w-F-`=E-GRO9Qisow34tb5HnI-i&C3mZ?NKI5kCNim_lF+u zrVX+xp1|TapFXd{IF6VJO&}uN@?_iT@iik~jwSOKCeyD@qQNpFoBouw2~|Ghn;RG{ z$P`SqWH5I$NU^(x(MDFWhlXx27<&^l@=a2gB=Za3z>MLGSA%f_gEpqiw|sP1^3DBf nnkccN#l2Id!~gg%^^5Tz4G?LvM%Jpu00000NkvXXu0mjfj#`Oo diff --git a/proposals/images/4108-qr-mode04-unstable.png b/proposals/images/4108-qr-mode04-unstable.png deleted file mode 100644 index 5ab3939a8a93e8882700e12838a7cf12533936b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 813 zcmV+|1JeA7P)C zmpzW-Fbsuhs8APQK!C2HPFJ!o;GYZFS5l{I2+#$r&V^v^lQWswVvD8Q2?8Ve3^3*s z`N$Ce^F_ZKA4DUomCYEbi?#&nzP+M=9rLHVN?*x2OmuL0JGgRZ;l3MFS08u zet<(AW1Ts#K^>X~&8r+AE_2KWrAaG}_)5JTqv(2u#&sfFPU_Cu$#Dr>B4oCgGvsM< z=m|MSuxJm>i??eu1HH>fhWWG`<;xpFerKH=&2oG=ID|dCd3iIQxc{y}8Aj|96V7Ts z@frP8dpSmPzo?R{(COtf3gH1hJS2zAC}$Wj@2qw?#%U80jfn^t*LOF_@IlbYE*dUX z;V~EpMUF3m-Y}RnMtjTljxEF<^$VLpY_t^m()4l+od~Cmt5#^QrR6HdXv#&mso1xu zY4UHqWw^C9)$UX~5=CRtXGBYIVT(lfsP^WX7zlQiWB5~tW7h_Thb%98&om)+YHyuX z@pKH&dxXoqDGW|+gkMnC=NLrzg252=7^Plh?9m#;I*=N5baDr6bH(3gz1 zu7gJ*w|T;H%$x>-*fjZ8MB~@8Aj3y1I#o_}xx8Vfoq3mI*d#pWwkvcwdVo?#(a#~)uYll7800000NkvXXu0mjf4_I}f diff --git a/proposals/images/4108-qr-mode04.png b/proposals/images/4108-qr-mode04.png deleted file mode 100644 index 829ee0032017c28c6b73c49e032ac1e6ba982a0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 816 zcmV-01JC@4P)C zmoe_7JPgH+Qlx_mM9MYN{3aYd>&$cxfZH=f*lQaOSPA`SZ|Tz6FuEQ8IjChD^RZ z&0dw5C!DD*a(vP8PDs+R>O67ph9JkMC(^xsiO^M$VYA5bMfP-g_OJqU6;%ArpbR?` zm>^LhLhW)$_s)xOskTIm5SIvn!B6a4j=9TZ&k#fcwY-vLIleUd+e}gIggsZ>Ygvq$ z!O%vy_9~eD{YeqN9A1_KWJW{3ArfYg;}i8#`|artPI=T{Pm^J2)vjrXX7op?$L_vS z$}oI&){Q*c`Z%2}$v(iP*9+c_5-_vJ=#eKmhNtx!tQp-fC41cql;NHsh$Z49RFL9& z8>0+k3ahHrt}eT?TQq|lbA`M1(dlwn9oV-oW%%-ab%mi-ZS>IHyga}?P=a!g4lTv2 zQ$>zX-9~B?e4szi^X>C8jH6)3`s{;nkB!YB$DbLw_xET1f+75N5((}zvQ`)DD8fn5 zPilLBVL=)`02RmF0j;{Hbt(SXNFN5_2BS6)F=NlE_a*iDg&$zXu)wRvxPd_%)8)5( uWVq*>)e4b7gH~AFH&rtHKmVovVf+iJ$7)5Ph_7$}0000R6- From 83071d97b9081a4558330f926ac535bb8e4fe456 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 7 Nov 2025 11:48:45 +0000 Subject: [PATCH 63/70] Use base URL in QR code and m.login.protocols message --- proposals/4108-oidc-qr-login.md | 55 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index bf488099c65..6782dbd0222 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -79,7 +79,7 @@ New optional HTTP endpoints are to be added to the Client-Server API. Suppose that Device A wants to establish communications with Device B. Device A can do so by creating a _rendezvous session_ via a `POST /_matrix/client/v1/rendezvous` call to an appropriate homeserver. Its response includes -an _rendezvous ID_ which, along with the server name, should be shared out-of-band with Device B. +an _rendezvous ID_ which, along with the server [base URL], should be shared out-of-band with Device B. The rendezvous ID points to an arbitrary data resource (the "payload") on the homeserver, which is initially populated using data from A's initial `POST` request. The payload is a string which the homeserver must enforce a maximum length on. @@ -427,9 +427,9 @@ The QR codes to be displayed and scanned using this format will encode binary st - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 string - the rendezvous session ID as a UTF-8 string -- the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the homeserver encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the server name as a UTF-8 string - - the server name as a UTF-8 string +- the [base URL] of the homeserver for client-server connections encoded as: + - two bytes in network byte order (big-endian) indicating the length in bytes of the base URL as a UTF-8 string + - the base URL as a UTF-8 string If a new version of this QR sign in capability is needed in future (perhaps with updated secure channel protocol) then an additional type can then be allocated which would clearly distinguish this later version. @@ -497,7 +497,7 @@ d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 ### Secure channel The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or -even arbitrary network participants which possess the rendezvous session ID and server name. +even arbitrary network participants which possess the rendezvous session ID and server base URL. To provide a secure channel on top of this insecure rendezvous session transport, we propose the following scheme. This scheme is essentially [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) @@ -549,12 +549,12 @@ The information to be encoded is: - The insecure rendezvous session **ID** - An indicator (the **intent**) to say if this is the new device which wishes to login, or an existing device that wishes to facilitate the login of the new device -- the Matrix homeserver **[server name](https://spec.matrix.org/v1.15/appendices/#server-name)** +- the Matrix homeserver **[base URL]** The format of this QR is defined in detail in a [separate section](#qr-code-format) of this proposal. Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **ID**, **intent** and the Matrix homeserver -**[server name](https://spec.matrix.org/v1.15/appendices/#server-name)**. +**[base URL]**. At this point Device S should check that the received intent matches what the user has asked to do on the device. @@ -695,7 +695,7 @@ sequenceDiagram G->>+Z: POST /_matrix/client/v1/rendezvous
{"data": ""} Z->>-G: 200 OK
{"id": "abc-def", "sequence_token": "1", "expires_ts": 1234567} - note over G: 3) Device G generates and displays a QR code containing:
its ephemeral public key, the rendezvous session ID, the server name + note over G: 3) Device G generates and displays a QR code containing:
its ephemeral public key, the rendezvous session ID, the server base URL G-->>S: Device S scans the QR code shown by Device G deactivate G @@ -818,7 +818,7 @@ This can make it hard to read what is going on. The new device needs to know which homeserver it will be authenticating with. -In the case that the new device scanned the QR code then the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) +In the case that the new device scanned the QR code then the [base URL] of the Matrix homeserver can be taken from the QR code and the new device proceeds to step 2 immediately. Otherwise the new device waits to be informed by receiving an `m.login.protocols` message from the existing device. @@ -837,18 +837,17 @@ homeserver specified: { "type": "m.login.protocols", "protocols": ["device_authorization_grant"], - "homeserver": "synapse-oidc.lab.element.dev" + "base_url": "https://synapse-oidc.lab.element.dev" } ``` 2. **New device checks if it can use an available protocol** -Once the existing device has determined the server name it then undertakes steps to determine if it is able to work with the homeserver. +The existing device then undertakes steps to determine if it is able to work with the homeserver. The steps are as follows: -- use [Server Discovery](https://spec.matrix.org/v1.15/client-server-api/#server-discovery) to determine the `base_url` from the well-known URI -- checks that the homeserver has the OAuth 2.0 API available by [`GET /_matrix/client/v1/auth_metadata`](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) +- checks that the homeserver has the OAuth 2.0 API available by [`GET /_matrix/client/v1/auth_metadata`](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) on the homeserver [base URL] *New device => Homeserver via HTTP* @@ -956,22 +955,21 @@ sequenceDiagram rect rgba(255,0,0, 0.1) #alt if New device scanned QR code note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel - note over N: 1) New device got server name from the QR code + note over N: 1) New device got server base URL from the QR code #else if Existing device scanned QR code # note over E: Existing device completes step 6 # note over E: Existing device displays checkmark and CheckCode # note over E: 1) Existing device sends m.login.protocols message - # E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) + # E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "http://matrix-client.matrix.org"}) # note over N: New device waits for user to confirm secure channel from step 7 - # HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} + # HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "https://matrix-client.matrix.org"} # note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: - note over N: Use well-known discovery to get the homeserver base URL N->>+HS: GET /_matrix/client/v1/auth_metadata activate N HS->>-N: 200 OK {"device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} @@ -1005,8 +1003,8 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) - HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org"}) + HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org"}) end end ``` @@ -1023,23 +1021,22 @@ sequenceDiagram #alt if New device scanned QR code # note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel - # note over N: 1) New device got server name from the QR code + # note over N: 1) New device got server base URL from the QR code rect rgba(0,0,255, 0.1) #else if Existing device scanned QR code note over E: Existing device completes step 6 note over E: Existing device displays checkmark and CheckCode note over E: 1) Existing device sends m.login.protocols message - E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"}) + E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "https://matrix-client.matrix.org"}) note over N: New device waits for user to confirm secure channel from step 7 - HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"homeserver": "matrix.org"} + HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "https://matrix-client.matrix.org"} note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end rect rgba(0,255,0, 0.1) note over N: 2) New device checks if it can use an available protocol: - note over N: Use well-known discovery to get the homeserver base URL N->>+HS: GET /_matrix/client/v1/auth_metadata activate N HS->>-N: 200 OK {"device_authorization_endpoint":
"https://id.matrix.org/auth/device", ...} @@ -1071,8 +1068,8 @@ sequenceDiagram note over E: Existing device checks that requested protocol is supported alt if requested protocol is not valid - E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) - HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org}) + E->>HS: SecureSend({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org"}) + HS->>N: SecureReceive({"type":"m.login.failure", "reason":"unsupported_protocol",
"homeserver": "matrix.org"}) end end ``` @@ -1360,13 +1357,13 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.protocols`| |`protocols`|required `string[]`|Array of: one of: `device_authorization_grant` | -|`homeserver`|required `string`|The [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the Matrix homeserver| +|`base_url`|required `string`|The [base URL] of the Matrix homeserver for client-server connections| ```json { "type": "m.login.protocols", "protocols": ["device_authorization_grant"], - "homeserver": "matrix.org" + "base_url": "https://matrix-client.matrix.org" } ``` @@ -1426,7 +1423,7 @@ Fields: |--- |--- |--- | |`type`|required `string`|`m.login.failure`| |`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| -|`homeserver`|`string`| When the existing device is sending this it can include the [server name](https://spec.matrix.org/v1.15/appendices/#server-name) of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| +|`homeserver`|`string`| When the existing device is sending this it can include the [server name] of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| Example: @@ -1749,3 +1746,5 @@ The unstable value of `IO_ELEMENT_MSC4108_CONCURRENT_WRITE` should be used inste This MSC builds on [MSC4341] which proposes support for RFC 8628 Device Authorization Grant in Matrix. [MSC4341]: https://github.com/matrix-org/matrix-spec-proposals/pull/4341 "MSC4341 Support for RFC 8628 Device Authorization Grant" +[server name]: https://spec.matrix.org/v1.16/appendices/#server-name +[base URL]: https://spec.matrix.org/v1.16/client-server-api/#getwell-knownmatrixclient From c5a9dc75396102c8a982e1e86dd0ebdb7fb0db4a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Nov 2025 17:58:53 +0000 Subject: [PATCH 64/70] Update QR examples to match description --- proposals/4108-oidc-qr-login.md | 33 +++++++++--------- proposals/images/4108-qr-intent00.png | Bin 810 -> 918 bytes .../images/4108-qr-intent01-unstable.png | Bin 815 -> 914 bytes proposals/images/4108-qr-intent01.png | Bin 817 -> 918 bytes 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 6782dbd0222..dde70a14b81 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -437,8 +437,8 @@ an additional type can then be allocated which would clearly distinguish this la #### Example for QR code generated on new device A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver -`matrix.org` is as follows: +encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver with base URL +`https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) ``` @@ -447,8 +447,8 @@ encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on home d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67 +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: @@ -460,15 +460,16 @@ nix-shell -p qrencode --run 'echo "4D 41 54 52 49 58 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent00.png' +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent00.png' --> ![Example QR for intent 0x00](images/4108-qr-intent00.png) #### Example for QR code generated on existing device A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver`matrix.org` is as follows: (Whitespace is for readability only) +encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver with base URL +`https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) ``` 4D 41 54 52 49 58 @@ -476,8 +477,8 @@ encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on hom d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67 +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: @@ -489,8 +490,8 @@ nix-shell -p qrencode --run 'echo "4D 41 54 52 49 58 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01.png' +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01.png' --> ![Example QR for intent 0x01](images/4108-qr-intent01.png) @@ -1709,7 +1710,7 @@ key `io.element.msc4108` set to true. So, the response could look then as follow The unstable value of `IO_ELEMENT_MSC4108` should be used instead of `MATRIX` in the QR code. A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver `matrix.org` is as follows: (Whitespace is for readability only) +encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver `https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) ``` 49 4F 5F 45 4C 45 4D 45 4E 54 5F 4D 53 43 34 31 30 38 @@ -1717,8 +1718,8 @@ encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on hom d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67 +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 ``` Which looks as follows as a QR with error correction level Q: @@ -1730,8 +1731,8 @@ nix-shell -p qrencode --run 'echo "49 4F 5F 45 4C 45 4D 45 4E 54 5F 4D 53 43 34 d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b 00 24 65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 0A -6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01-unstable.png' +00 20 +68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67" | xxd -r -p | qrencode -8 -l Q -t PNG -o ./proposals/images/4108-qr-intent01-unstable.png' --> ![Example QR for intent 0x01](images/4108-qr-intent01-unstable.png) diff --git a/proposals/images/4108-qr-intent00.png b/proposals/images/4108-qr-intent00.png index beed4414556306c188e53900d408c650ceed3f7c..9cef72ef3abf29fd1d086a87054180f545be2c2a 100644 GIT binary patch delta 878 zcmV-!1Cjiy29^gQiBL{Q4GJ0x0000DNk~Le0002D0002D0RsR40BWFJ+>s$Se*-8< zL_t(oh3%I;j-)UUg$p@wMqj{^uR-Q3!3Fra09?tLuYtq`Aah_*`wC`fHrT|K?zTp) z=?}?myI#F|P5k#4{pVl&BpRaM^4^mBB-h$PrGS8=_ZmX%Oedt`KOVkj+{08wluMul53Kjz5ksbs(FX)xerD_L<{@%f2oLqUzyM^vy>9k z7R_Q7)^ObU644{n6RgQ#@hxfig~Mj}El$m{IJ6pA!A}`ySl_IYYTPHgw|}d7M|}s8 z(`dYWZo+ERN)wY~WFnW}}94 zDi}tqEe#uqG?C}8e|cBO(fdn6B04Tcp4p)ae)Ukv&*!ikVobsaFpw)*4M*mL9wu>1 z%n=D!+ne_)Im=kYY=xg-wBg`HR>hG&tt4|1yv%Os+$*@`;fzNZLrAa?s(7!0OFoC9 zVAn_v0g~|Nch0Zk@_PZD!n34n92WD4bsTdRN+5(!LLJd}L`ptIhg{S!wQ#3UhU_(* z%ag2-9^tpRJ+v?@IPQN$F0%O5iTCG=RJ`_g@xR4?0lRwkPbone4*&oF07*qoM6N<$ Ef((SVk^lez delta 769 zcmV+c1OEJ$2dV}kiBL{Q4GJ0x0000DNk~Le00021000210RsR403#;;T9F|)e*(No zL_t(YiRG6&&Z96Cg)eg9E^NS(x8crP!W#&%0oal|Z^M!|0G$hqJI8tGpJGOuOFJkb z=u1R$pXY@5j|ctV_#m2NS)6$iZ<^0YgB+jSCMuif6&J1z2}F*g2}zwvEv}!}MBx^m ztKDOM_&ip%-^EXlSPhX{tKxf@f7_gR@i-fDdHxArfRXQz%+KSe3VvoLWeYG)>M-{i_t;z_H&i#2#o! zG*>gr@#Sem`&6}g(=TERa(wnu6H!?_9_{I9yn_vSGt@RP+D9GQ@*Ql?e^l@@$(1$c zA(`hMSy@BF>D2uFp1Af1PppRR?fZs5z&@ENK0^!C#Bl;eZ!-a(sCJq)^2 zyBwbpj3%#;r!lrMwONiYDtqcZxad6cs~_d~K)H19Um`OktYI_D@kRD<86nQmOb%Q8 zlR+8QCNN<|g@|pJLwZ+Ue}+%oELwy(BTtNv*js$Se*+{* zL_t(oh3%KIjpQ&4MQJEdW;Y-}Z75Sqo(9I_2F5KZQyT)@fSoBY#QXHl?p<&fi18VN z#o`&TDC+Z*BL4eB|M`a>M2}>)>d#u{;D&q=c?CcDdPcJ*TC!QQ+0wPJiYNE9Wno8Q ze}!wt&uTgO7WOUkJNh*{HJ5Exe{9?^?`yu4oUb_=PHtc~lH~0t4_#dHpWUCo-{;c9 zf1zk+F&9@R}1Yw6&xaa%?zbKGH-vPy8{?I+zl7|w2tfG9fn zl1Nm+n|P5~u}xd>eZ1n#e+qu$?661pw%uQ`%yOV!!C@nC$f?3lpa(Z48nT9S_DA3r zEkUy-7vqOzMGKA{T+TIavzzW`iKUw<7$pmS^1U~iK$q$JTXIBXO&G&v2D>w?I z#WJDXf=7RZHoo%K%MS}dqScy}VYY_F##zA|?JbzMrKfc>m8acNe?bb4F@_?`Z}R-qXx1c7qF!!bNw3 z!NB+o*d)dG$RYmihyHJT5lxy_PIr#IGqgw~aBmZPT*OX}FKsSOy^f2; zxy8<>TaKC0KB&-0H8geTuX;=HV{zb$bLpxq410CUG5gXJ6qyd45b0u5e*=CfogQU2 zJFCl)rw2utGd#vT zuFRR%5PQuH7;5MpdIvWVW7_DezaZw`)|Hj5d{xPQtqS`YSckwTWsMnf)RMmz5IKGve|#mK0nawOk8A#9 zDZ+QC1wIH99s$-yXu#|-RLx;w6_B|4T3ciof=0bzHe|=%;U%#NLrih@>2e9nAg;Rx zOuScBK?FFi0_#2H7$??+ameVtI6&p}S&2j#ANbShQSY)?Otx{5s$Se*-8< zL_t(oh3%KW&Ez-?#c3!|Hf}(G+EAvJ><0X&0lOt-YQumVurmdMxsRXcb}zU@Om7DR z1O5rnh}7dFhxorg^p}76MKmVAm*Zn=pBY7aG|=me>3z4^3S1pWVN|f9|D) zL{TZk@Sd}kKla>+`b?9BI3cZ{f72>>bX`Ko zW-aEiZhWt@opVOWo>QGu9U%4SufQ74G+5W?bBVkrvx)M(h9_Q2odOh&q*Ywv_U_-v zA6utLB&Anj2unQrZ$8`j4MEz2BIIN)IzD8UpyI{NaL9z$I&!7p7pvi1%w5EtKG8hn z2C*hq@H^31%w-+i`-58Me{;|3rzu0vuqBnMpp1k+SxH@vqcP7t}pJb-)0_Yz}c8) z@kvL>|J$b;xpe$^a>kryI719>e(SfHV}DEze#Jl%WdGs2e;dD|f!_|nbuhYwp6mLg z;hZrBpPQ{68yRV2e}H5KhuN+f=Y-2)o;jcje)Z7Ek$=c?&p6(>1b8@+)o{$5q6cMZ9W0BXx3oavf=3JukR%wM!s!98E~-OlqcfT@r;y zcq(>_x%YV}YI}-b9(y%JYAK4Je_^gu;@RVB$mQvK^8$=`hh)Cb?=%ST``9mCr^rjH z!_R{FZ+uan82hV4wbwat`$mcnZ#g01250Z=s_`o^1`Dx{+KkSO?Ny2&;MkrwB=gcW zB$|qui|pYt);L9| z3NC&}P=>t;Ot7dB;dVKse^2IR_$>B}c7`z^PK=+}O^)&D-Vq`VHS9Cpr76OfQ&{5= z6%1x$#jTdbm>CQ*!nL*5(SN;Cf-i@M{5o&S5bG6R4Du5EL`USK3lyY?I-~Qzgf}kEBXYnTL{-8xCYUN;XgT98H`fQw`9;F<1eU5ejrqz!}U2E8^9F(c>HbV+S~;Rl#8 zeDSO?ZeX Date: Wed, 10 Dec 2025 17:42:25 +0000 Subject: [PATCH 65/70] Split secure channel into MSC4388 + add intro diagram --- proposals/4108-oidc-qr-login.md | 1042 +++---------------------------- 1 file changed, 78 insertions(+), 964 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index dde70a14b81..f814aef8a08 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -12,47 +12,18 @@ work with a homeserver using [OAuth 2.0 API](https://spec.matrix.org/v1.15/clien Table of contents: - [Proposal](#proposal) - - [Insecure rendezvous session](#insecure-rendezvous-session) - - [High-level description](#high-level-description) - - [The send mechanism](#the-send-mechanism) - - [Expiry](#expiry) - - [POST /_matrix/client/v1/rendezvous](#post-_matrixclientv1rendezvous---create-a-rendezvous-session-and-send-initial-payload) - - [PUT /_matrix/client/v1/rendezvous/{rendezvousId}](#put-_matrixclientv1rendezvousrendezvousid---send-a-payload-to-an-existing-rendezvous) - - [GET /_matrix/client/v1/rendezvous/{rendezvousId}](#get-_matrixclientv1rendezvousrendezvousid---receive-a-payload-from-a-rendezvous-session) - - [DELETE /_matrix/client/v1/rendezvous/{rendezvousId}](#delete-_matrixclientv1rendezvousrendezvousid---cancel-a-rendezvous-session) - - [Implementation notes](#implementation-notes) - - [Example API usage](#example-api-usage) - - [Threat analysis](#threat-analysis) - - [QR code format](#qr-code-format) - - [Example for QR code generated on new device](#example-for-qr-code-generated-on-new-device) - - [Example for QR code generated on existing device](#example-for-qr-code-generated-on-existing-device) - - [Secure channel](#secure-channel) - - [Establishment](#establishment) - - [Sequence diagram](#sequence-diagram) - - [Secure operations](#secure-operations) - - [Threat analysis](#threat-analysis) - - [The OAuth 2.0 login part and set up of E2EE](#the-oauth-20-login-part-and-set-up-of-e2ee) - - [Login via OAuth 2.0 Device Authorization Grant from MSC4341](#login-via-oauth-20-device-authorization-grant-from-msc4341) - - [Secret sharing and device verification](#secret-sharing-and-device-verification) - - [Message reference](#message-reference) - - [Discoverability of the capability](#discoverability-of-the-capability) +- [Message reference](#message-reference) +- [Discoverability of the capability](#discoverability-of-the-capability) - [Potential issues](#potential-issues) - [Alternatives](#alternatives) - - [Alternative to the rendezvous session protocol](#alternative-to-the-rendezvous-session-protocol) - - [ETag based rendezvous API](#etag-based-rendezvous-api) - - [Send-to-Device messaging](#send-to-device-messaging) - - [Other existing protocols](#other-existing-protocols) - - [Implementation details](#implementation-details) - - [Alternative method of secret sharing](#alternative-method-of-secret-sharing) - [Security considerations](#security-considerations) - - [Malicious session spawning](#malicious-session-spawning) - [Unstable prefix](#unstable-prefix) - [Dependencies](#dependencies) ## Proposal -Depending on the pair of devices used, it may be preferable to scan the QR code on either the new or existing device, -based on the availability of a camera. As such, this proposal allows for the generation of the QR on either device. +We rely on the mechanisms described in [MSC4388] to establish a secure out-of-band channel between +the new and existing device over which we can pass messages. In order for the new device to be fully set up, it needs to exchange information with an existing device such that: @@ -60,760 +31,62 @@ In order for the new device to be fully set up, it needs to exchange information - The existing device can facilitate the new device in getting an access token - The existing device shares the secrets necessary to set up end-to-end encryption -This proposal is split into four parts: - -1. An insecure rendezvous session API to allow the two devices to exchange the necessary data -2. The structure of the QR code that contains the rendezvous session details -3. A secure channel to protect the data exchanged over the rendezvous session -4. The OAuth 2.0 login part and set up of E2EE - -### Insecure rendezvous session - -It is proposed that an HTTP-based protocol be used to establish an ephemeral bi-directional communication session over -which the two devices can exchange the necessary data. This session is described as "insecure" as it provides no -end-to-end confidentiality nor authenticity by itself---these are layered on top of it. - -New optional HTTP endpoints are to be added to the Client-Server API. - -#### High-level description - -Suppose that Device A wants to establish communications with Device B. Device A can do so by creating a -_rendezvous session_ via a `POST /_matrix/client/v1/rendezvous` call to an appropriate homeserver. Its response includes -an _rendezvous ID_ which, along with the server [base URL], should be shared out-of-band with Device B. - -The rendezvous ID points to an arbitrary data resource (the "payload") on the homeserver, which is initially populated -using data from A's initial `POST` request. The payload is a string which the homeserver must enforce a maximum length on. - -Anyone who is able to reach the homeserver and has the rendezvous ID - including: Device A; Device B; or a third party; - -can then "receive" the payload by polling via a `GET` request, and "send" a new a new payload by making a `PUT` request. - -In this way, Device A and Device B can communicate by repeatedly inspecting and updating the payload at the rendezvous session. - -#### The send mechanism - -Every send request MUST include an `sequence_token` value whose value is the `sequence_token` from the last `GET` -response seen by the requester. (The initiating device may also use the `sequence_token` supplied in the initial `POST` response -to immediately update the payload.) Sends will succeed only if the supplied `sequence_token` matches the server's current -revision of the payload. This prevents concurrent writes to the payload. - -n.b. Once a new payload has been sent there is no mechanism to retrieve previous payloads. - -#### Expiry - -The rendezvous session (i.e. the payload) SHOULD expire after a period of time communicated to clients via the -`expires_ts` field on the `POST` and `GET` response bodies. After this point, any further attempts to query or update -the payload MUST fail. The rendezvous session can be manually expired with a `DELETE` call to the rendezvous session. - -#### `POST /_matrix/client/v1/rendezvous` - Create a rendezvous session and send initial payload - -Rate-limited: Yes -Requires authentication: Optional - depending on server policy - -Request body is `application/json` with contents: - -|Field|Type|| -|-|-|-| -|`data`|required `string`|The data payload to be sent| - -For example: - -```http -POST /_matrix/client/v1/rendezvous HTTP/1.1 -Content-Type: application/json - -{ - "data": "initial data" -} -``` - -HTTP response codes, and Matrix error codes: - -- `200 OK` - rendezvous session created -- `403 Forbidden` (`M_FORBIDDEN`) - the requester is not authorized to create the rendezvous session -- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled -- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied `data` value is larger than the 4096 UTF8 character limit -- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited - -Response body for `200 OK` is `application/json` with contents: - -|Field|Type|| -|-|-|-| -|`id`|required `string`|Opaque identifier for the rendezvous session| -|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| -|`expires_ts`|required `integer`|The timestamp (in milliseconds since the epoch) at which the rendezvous session will expire| - -Example response: - -```http -HTTP 200 OK -Content-Type: application/json - -{ - "id": "abcdEFG12345", - "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", - "expires_ts": 1662560931000 -} -``` - -The server can chose what level of authentication is required to create a rendezvous session. Suitable policies might -include: - -- Public/open - anyone can create a rendezvous session without an access token. This allows for the QR code to be - created on either the new or existing Matrix client. -- Requires authenticated user - this would reduce abuse to known users, but would restrict the mechanism so that the QR - code must be created on the existing Matrix client (and therefore the new Matrix client must have a camera). - -The expiry time is detailed [below](#maximum-duration-of-a-rendezvous). - -#### `PUT /_matrix/client/v1/rendezvous/{rendezvousId}` - Send a payload to an existing rendezvous - -Rate-limited: Yes -Requires authentication: No - -Request body is `application/json` with contents: - -|Field|Type|| -|-|-|-| -|`sequence_token`|required `string`|The value of `sequence_token` from the last payload seen by the requesting device.| -|`data`|required `string`|The data payload to be sent.| - -For example: - -```http -PUT /_matrix/client/v1/rendezvous/abcdEFG12345 HTTP/1.1 -Content-Type: application/json - -{ - "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", - "data": "new data" -} -``` - -HTTP response codes, and Matrix error codes: - -- `200 OK` - payload updated -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) -- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled -- `409 Conflict` (`M_CONCURRENT_WRITE`, a new error code) - when the `sequence_token` does not match -- `413 Payload Too Large` (`M_TOO_LARGE`) - the supplied `data` value is larger than the 4096 UTF8 character limit -- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited - -The response body for `200 OK` is `application/json` with contents: - -|Field|Type|| -|-|-|-| -|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| - -For example: - -```http -HTTP 200 OK -Content-Type: application/json - -{ - "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=" -} -``` - -#### `GET /_matrix/client/v1/rendezvous/{rendezvousId}` - Receive a payload from a rendezvous session - -Rate-limited: Yes -Requires authentication: No - -HTTP response codes, and Matrix error codes: - -- `200 OK` - payload returned -- `403 Forbidden` (`M_FORBIDDEN`) - request is not allowed due to the unsafe content policy (see below) -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) -- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled -- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited - -Response body for `200 OK` is `application/json` with contents: - -|Field|Type|| -|-|-|-| -|`data`|required `string`|The data payload from the last POST or PUT.| -|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed| -|`expires_ts`|required `integer`|The timestamp (in milliseconds since the epoch) at which the rendezvous session will expire| - -```http -HTTP 200 OK -Content-Type: application/json - -{ - "data": "data from the previous POST/PUT", - "sequence_token": "VmbxF13QDusTgOCt8aoa0d2PQcnBOXeIxEqhw5aQ03o=", - "expires_ts": 1662560931000 -} -``` - -To help mitigate the threat of [unsafe content](#unsafe-content), the server SHOULD inspect the `Sec-Fetch-*` -[Fetch Metadata Request Headers](https://www.w3.org/TR/fetch-metadata/) (or other suitable headers) to identify -top-level navigation requests and return a `403` HTTP response with error code `M_FORBIDDEN` instead. - -A future optimisation could be allow the client to "long-poll" by sending the previous `sequence_token` as a query parameter -and then the server returns when the is new data or some timeout has passed. - -#### `DELETE /_matrix/client/v1/rendezvous/{rendezvousId}` - cancel a rendezvous session - -Rate-limited: Yes -Requires authentication: No - -HTTP response codes: - -- `200 OK` - rendezvous session cancelled -- `404 Not Found` (`M_NOT_FOUND`) - rendezvous session ID is not valid (it could have expired) -- `404 Not Found` (`M_UNRECOGNIZED`) - the rendezvous API is not enabled -- `429 Too Many Requests` (`M_LIMIT_EXCEEDED`) - the request has been rate limited - -#### Implementation notes - -##### Maximum payload size - -The server MUST enforce a maximum payload size of 4096 UTF8 characters. - -##### `sequence_token` values - -The `sequence_token` values should be unique to the rendezvous session and the last modified time so that two clients can -distinguish between identical payloads sent by either client. - -##### Maximum duration of a rendezvous - -The rendezvous session needs to persist for the duration of the login including allowing the user another time to -confirm that the secure channel has been established and complete any extra homeserver mandated login steps such as MFA. - -Clients should handle the case of the rendezvous session being cancelled or timed out by the server. - -The server MUST enforce a timeout on each rendezvous. When picking a value to use: - -- the minimum timeout SHOULD be 120 seconds for usability -- the maximum timeout SHOULD be 300 seconds for security - -##### Choice of server - -Ultimately it will be up to the Matrix client implementation to decide which rendezvous server to use. - -However, it is suggested that the following logic is used by the device/client to choose the rendezvous server in order -of preference: - -1. If the client is already logged in: try and use the current homeserver. -1. If the client is not logged in and it is known which homeserver the user wants to connect to: try and use that homeserver. -1. Otherwise use a default server. - -#### Example API usage - -```mermaid -sequenceDiagram - participant A as Device A - participant HS as Homeserver - participant B as Device B - Note over A: Device A determines which rendezvous server to use - - A->>+HS: POST /_matrix/client/v1/rendezvous
{"data":"Hello from A"} - HS->>-A: 200 OK
{"id":"abc-def-123-456","sequence_token": "1", "expires_ts": 1234567} - - A-->>B: Rendezvous ID and homeserver shared out of band as QR code: e.g. id=abc-def-123-456 servername=example.com - - Note over A: Device A starts polling for new payloads at the
rendezvous session using the returned `sequence_token` - activate A - - Note over B: Device B resolves the servername to the homeserver - - B->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 - HS->>-B: 200 OK
{"sequence_token": "1", "data": "Hello from A"} - loop Device A polls the rendezvous session for a new payload - A->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 - alt is not modified - HS->>-A: 200 OK
{"sequence_token": "1", "data": "Hello from A", "expires_ts": 1234567} - end - end - - note over B: Device B sends a new payload - B->>+HS: PUT /_matrix/client/v1/rendezvous/abc-def-123-456
{"sequence_token": "1", "data": "Hello from B"} - HS->>-B: 200 OK
{"sequence_token": "2"} - - Note over B: Device B starts polling for new payloads at the
rendezvous session using the new `sequence_token` - activate B - - loop Device B polls the rendezvous session for a new payload - B->>+HS: GET /_matrix/client/v1/rendezvous/abc-def-123-456 - alt is not modified - HS->>-B: 200 OK
{"sequence_token": "2", "data": "Hello from A", "expires_ts": 1234567} - end - end - - note over A: Device A then receives the new payload - opt modified - HS->>A: 200 OK
{"sequence_token": "2", "data": "Hello from B", "expires_ts": 1234567} - end - deactivate A - - note over A: Device A sends a new payload - A->>+HS: PUT /_matrix/client/v1/rendezvous/abc-def-123-456
{"sequence_token": "2", "data": "Hello again from A"} - HS->>-A: 200 OK
{"sequence_token": "3"} - - note over B: Device B then receives the new payload - opt modified - HS->>B: 200 OK
{"sequence_token": "3", "data": "Hello again from B", "expires_ts": 1234567} - end - - deactivate B -``` - -#### Threat analysis - -##### Denial of Service attack surface - -Because the rendezvous session protocol allows for the creation of arbitrary channels and storage of arbitrary data, it -is possible to use it as a denial of service attack surface. - -As such, the following standard mitigations such as the following may be deemed appropriate by homeserver -implementations and administrators: - -- rate limiting of requests -- limiting the number of concurrent sessions - -Furthermore, this proposal limits the maximum payload size to 4KB. - -##### Data exfiltration - -Because the rendezvous session protocol allows for the storage of arbitrary data, it -is possible to use it to circumvent firewalls and other network security measures. - -Implementation may want to block their production IP addresses from being able to make requests to the rendezvous -endpoints in order to avoid attackers using it as a dead-drop for exfiltrating data. - -##### Unsafe content - -Because the rendezvous session is not authenticated, it is possible for an attacker to use it to distribute malicious -content. - -This could lead to a reputational problem for the homeserver domain or IPs, as well as potentially causing harm to users. - -Mitigations that are included in this proposal: - -- the low maximum payload size (4KB) -- payload is restricted to string -- the rendezvous session should be short-lived -- use of `Sec-Fetch-*` headers to not return payload content when browser has navigated to the session URL - -### QR code format - -To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a -similar structure to that of the existing Device Verification QR code encoding described in [Client-Server -API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format). - -It is proposed that the QR code format that is currently used in the Client-Server API for -[device verification](https://spec.matrix.org/v1.16/client-server-api/#qr-code-format) be extended to be more general -purpose and accommodate this new use case, and future use cases. - -The "QR code version" would be repurposed to be a "QR code type" and used as the way to distinguish the format of the -subsequent data. - -The existing cross verification code would be type `0x02`. I suspect that type `0x01` and `0x00` might correspond to -earlier iterations of the cross signing flow and so might want to be "reserved". - -This proposal then adds a new type `0x03`. - -The QR codes to be displayed and scanned using this format will encode binary strings in the general form: - -- the ASCII string `MATRIX` -- one byte indicating the QR code type: `0x03` which identifies that the QR is part of this proposal -- one byte indicating the intent of the device generating the QR: - - `0x00` a new device wishing to login and self-verify - - `0x01` an existing device wishing to facilitate the login of a new device and self-verify that other device -- the ephemeral Curve25519 public key that will be used for [secure channel establishment](#establishment), as 32 bytes -- the rendezvous session ID encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the rendezvous session ID as a UTF-8 - string - - the rendezvous session ID as a UTF-8 string -- the [base URL] of the homeserver for client-server connections encoded as: - - two bytes in network byte order (big-endian) indicating the length in bytes of the base URL as a UTF-8 string - - the base URL as a UTF-8 string - -If a new version of this QR sign in capability is needed in future (perhaps with updated secure channel protocol) then -an additional type can then be allocated which would clearly distinguish this later version. - -#### Example for QR code generated on new device - -A full example for a new device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded) at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver with base URL -`https://matrix-client.matrix.org` is as follows: -(Whitespace is for readability only) - -``` -4D 41 54 52 49 58 -03 00 -d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 24 -65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 20 -68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 -``` - -Which looks as follows as a QR with error correction level Q: - -![Example QR for intent 0x00](images/4108-qr-intent00.png) - -#### Example for QR code generated on existing device - -A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver with base URL -`https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) - -``` -4D 41 54 52 49 58 -03 01 -d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 24 -65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 20 -68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 -``` - -Which looks as follows as a QR with error correction level Q: - -![Example QR for intent 0x01](images/4108-qr-intent01.png) - -### Secure channel - -The above rendezvous session is insecure, providing no confidentiality nor authenticity against the rendezvous server or -even arbitrary network participants which possess the rendezvous session ID and server base URL. -To provide a secure channel on top of this insecure rendezvous session transport, we propose the following scheme. - -This scheme is essentially [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme#Formal_description_of_ECIES) -instantiated with X25519, HKDF-SHA256 for the KDF and ChaCha20-Poly1305 (as specified by -[RFC8439](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8)) for the authenticated encryption. Therefore, -existing security analyses of ECIES are applicable in this setting too. Nevertheless we include below a short -description of our instantiation of ECIES and discuss some potential pitfalls and attacks. - -The primary limitation of ECIES is that there is no authentication for the initiating party (the one to send the first -payload; Device S in the text below). Thus the recipient party (the one to receive the first payload; Device G in the -text below) has no assurance as to who actually sent the payload. In QR code login, we work around this problem by -exploiting the fact that both of these devices are physically present during the exchange and offloading the check that -they are both in the correct state to the user performing the QR code login process. - -#### Establishment - -Participants: - -- Device G (the device generating the QR code) -- Device S (the device scanning the QR code) - -Regardless of which device generates the QR code, either device can be the existing (already signed in) device. The -other device is then the new device (one seeking to be signed in). - -Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF. A -separate deterministic, monotonically-incrementing nonce is used for each sender. Devices initially set both nonces to -`0` and increment the corresponding nonce by `1` for each message sent and received. - -1. **Ephemeral key pair generation** - - Both devices generate an _ephemeral_ Curve25519 key pair: - -- Device G generates **(Gp, Gs)**, where **Gp** is its public key and **Gs** the private (secret) key. -- Device S generates **(Sp, Ss)**, where **Sp** is its public key and **Ss** the private (secret) key. - -2. **Create rendezvous session** - -Device G creates a rendezvous session by making a `POST` request (as described previously) to the nominated homeserver -with an empty payload. It parses the **ID** received. - -3. **Initial key exchange** - -Device G displays a QR code containing sufficient information for the scanning device to locate the rendezvous session -and establish the secure channel (as described in the [next section](#secure-channel)). - -The information to be encoded is: - -- Its public key **Gp** -- The insecure rendezvous session **ID** -- An indicator (the **intent**) to say if this is the new device which wishes to login, or an existing device -that wishes to facilitate the login of the new device -- the Matrix homeserver **[base URL]** - -The format of this QR is defined in detail in a [separate section](#qr-code-format) of this proposal. - -Device S scans and parses the QR code to obtain **Gp**, the rendezvous session **ID**, **intent** and the Matrix homeserver -**[base URL]**. - -At this point Device S should check that the received intent matches what the user has asked to do on the device. - -4. **Device S sends the initial payload** - -Device S computes a shared secret **SH** by performing ECDH between **Ss** and **Gp**. It then discards **Ss** and -derives two 32-byte symmetric encryption keys from **SH** using HKDF-SHA256. One of those keys, **EncKey_S** is -used for messages encrypted by device S, while the other, **EncKey_G** is used for encryption by device G. - -The keys are generated with the following HKDF parameters: - -**EncKey_S** - -- `MATRIX_QR_CODE_LOGIN_ENCKEY_S|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating - device's and the scanning device's ephemeral public keys, encoded as unpadded base64. -- An all-zero salt. - -**EncKey_G** - -- `MATRIX_QR_CODE_LOGIN_ENCKEY_G|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating - device's and the scanning device's ephemeral public keys, encoded as unpadded base64. -- An all-zero salt. - -With this, Device S has established its side of the secure channel. Device S then derives a confirmation payload that -Device G can use to confirm that the channel is secure. It contains: - -- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. -- Its public ephemeral key **Sp**. - -``` -Nonce_S := 0 -SH := ECDH(Ss, Gp) -EncKey_S := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_S|" || Gp || "|" || Sp, salt=0, size=32) - -// Stored, but not yet used -EncKey_G := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_G|" || Gp || "|" || Sp, salt=0, size=32) - -NonceBytes_S := ToLowEndianBytes(Nonce_S)[..12] -TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_S, NonceBytes_S, "MATRIX_QR_CODE_LOGIN_INITIATE") -Nonce_S := Nonce_S + 1 -LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp) -``` - -Device S then sends the **LoginInitiateMessage** as the `data` payload to the rendezvous session using a `PUT` request. - -5. **Device G confirms** - -Device G receives **LoginInitiateMessage** (potentially coming from Device S) from the insecure rendezvous session by -polling with `GET` requests. - -It then does the reverse of the previous step, obtaining **Sp**, deriving the shared secret using **Gs** and **Sp**, -discarding **Gs**, deriving the two symmetric encryption keys **EncKey_S** and **EncKey_G**, then finally -decrypting (and authenticating) the **TaggedCiphertext** using **EncKey_S**, obtaining a plaintext. - -It checks that the plaintext matches the string `MATRIX_QR_CODE_LOGIN_INITIATE`, failing and aborting if not. - -It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGIN_OK` encrypted with **SH** calculated -as follows: - -``` -Nonce_G := 0 -NonceBytes_G := ToLowEndianBytes(Nonce_G)[..12] -TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_G, NonceBytes_G, "MATRIX_QR_CODE_LOGIN_OK") -Nonce_G := Nonce_G + 1 -LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) -``` - -Device G sends **LoginOkMessage** as the `data` payload via a `PUT` request to the insecure rendezvous session. - -6. **Verification by Device S** - -Device S receives a response over the insecure rendezvous session by polling with `GET` requests, potentially from -Device G. - -It decrypts (and authenticates) it using the previously computed encryption key, which will succeed provided the payload -was indeed sent by Device G. It then verifies the plaintext matches `MATRIX_QR_CODE_LOGIN_OK`, failing otherwise. - -``` -Nonce_G := 1 -(TaggedCiphertext, Sp) := Unpack(Message) -NonceBytes := ToLowEndianBytes(Nonce)[..12] -Plaintext := ChaCha20Poly1305_Decrypt(EncKey_G, NonceBytes, TaggedCiphertext) -Nonce_G := Nonce_G + 1 - -unless Plaintext == "MATRIX_QR_CODE_LOGIN_OK": - FAIL -``` - -If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and -**Sp**: - -``` -CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp || "|" || Sp , salt=0, size=2) -CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) -``` - -Device S then displays an indicator to the user that the secure channel has been established and that the **CheckCode** -should be entered on the other device when prompted. Example wording could say "Secure connection established. Enter the -code XY on your other device." - -7. **Out-of-band confirmation** - -**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of -ECIES.* - -Device G asks the user to enter the **CheckCode** that is being displayed on Device S. - -The purpose of the code being entered is to ensure that the user has actually checked their other device rather than -just pressing "continue", and that the Device S has been able to determine that the channel is secure. - -Device G compares the code that the user has entered with the **CheckCode** that it calculates using the same mechanism -as before: - -``` -CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2) -CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10) -``` - -If the code that the user enters matches then the secure channel is established. - -Subsequent payloads sent from G should be encrypted using **EncKey_G**, while payloads sent from S should be -encrypted with **EncKey_S**, incrementing the corresponding nonce for each message sent/received. - -#### Sequence diagram - -The sequence diagram for the secure channel establishment is as follows: +At a high-level the process works as follows: ```mermaid sequenceDiagram - participant G as Device G - participant Z as Homeserver - participant S as Device S - - note over G,S: 1) Devices G and S each generate an ephemeral Curve25519 key pair - - activate G - note over G: 2) Device G creates a rendezvous session as follows - G->>+Z: POST /_matrix/client/v1/rendezvous
{"data": ""} - Z->>-G: 200 OK
{"id": "abc-def", "sequence_token": "1", "expires_ts": 1234567} - - note over G: 3) Device G generates and displays a QR code containing:
its ephemeral public key, the rendezvous session ID, the server base URL - - G-->>S: Device S scans the QR code shown by Device G - deactivate G - - activate S - note over S: Device S validates QR scanned and the rendezvous session ID - - S->>+Z: GET /_matrix/client/v1/rendezvous/abc-def - Z->>-S: 200 OK
{"sequence_token": "1", "expires_ts": 1234567, "data": ""} - - note over S: 4) Device S computes SH, EncKey_S, EncKey_G and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session - S->>+Z: PUT /_matrix/client/v1/rendezvous/abc-def
{"sequence_token": "1", "data": ""} - Z->>-S: 200 OK
{"sequence_token": "2"} - deactivate S - - G->>+Z: GET /_matrix/client/v1/rendezvous/abc-def - activate G - Z->>-G: 200 OK
{"sequence_token": "2", "expires_ts": 1234567, "data": ""} - note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH, EncKey_S and EncKey_G - note over G: Device G checks that the plaintext matches MATRIX_QR_CODE_LOGIN_INITIATE - - note over G: Device G computes LoginOkMessage and sends to the rendezvous session + participant E as Existing device
already signed in + participant N as New device
wanting to sign in + participant UA as Web Browser on
existing device + participant HS as Homeserver - G->>+Z: PUT /_matrix/client/v1/rendezvous/abc-def
{"sequence_token": "2", "data": ""} - Z->>-G: 200 OK
{"sequence_token": "3"} - deactivate G + note over N,E: Existing and new device establish a secure out-of-band channel from MSC4388 - activate S - S->>+Z: GET /_matrix/client/v1/rendezvous/abc-def - Z->>-S: 200 OK
{"sequence_token": "3", "expires_ts": 1234567, "data": ""} + E->>N: Existing device informs new device of homeserver base URL and available login protocols
=> m.login.protocols - note over S: 6) Device S attempts to parse Data as LoginOkMessage - note over S: Device S checks that the plaintext matches MATRIX_QR_CODE_LOGIN_OK + note over N: New device checks if it can use one of the available protocols - note over S: If okay, Device S calculates the CheckCode to be displayed - note over S: Device S displays a green checkmark, "secure connection established" and the CheckCode - note over S: Device S knows that the channel is secure - deactivate S + N->>HS: New device starts an authentication with the homeserver - note over G: 7) Device G asks the user to confirm that the other device is showing a green checkmark and enter the CheckCode - note over G: If the user enters the correct CheckCode and confirms that a green checkmark is shown then Device G knows that the channel is secure -``` + HS-->>N: Homeserver gives a verification URL that can be used to complete authentication -#### Secure operations + N->>E: New device informs the existing device which protocol it wants to use and the verification URL
<= m.login.protocol -Conceptually, once established, the secure channel offers two operations, `SecureSend` and `SecureReceive`, which wrap -the `Send` and `Receive` operations offered by the rendezvous session API to securely send and receive data between two devices. + E->>UA: Existing device opens the verification URL in a browser session -At the end of the establishment phase, the next nonce for each device should be `1`. + par + HS-->>UA: Homeserver serves web page asking user to consent -Device G sets: + note over UA: User is asked by the homeserver to log in and consent + UA->>HS: User consents + and + E->>N: Existing device informs new device that the verification is started
=> m.login.protocol_accepted -``` -Nonce_G := 1 -Nonce_S := 1 -``` + note over N, HS: New device polls the homeserver awaiting the outcome + -Device S sets: + HS->>N: Once user consents, the homeserver returns an access token + N->>E: New device informs existing device that it has completed authentication
<= m.login.success + E->>HS: Existing device checks that new device has complete authentication + HS-->>E: Homeserver confirms new device has completed authentication + E->>N: Existing device sends new device the E2EE secrets
=>m.login.secrets + note over N: New device stores secrets and verifies itself + N->>HS: New device uploads device verification signature to homeserver + end ``` -Nonce_G := 1 -Nonce_S := 1 -``` - -#### Threat analysis -In an attack scenario, we add a participant called Specter with the following capabilities: +### Login via OAuth 2.0 Device Authorization Grant from MSC4341 -- Specter is present for QR code generation/scanning ("shoulder-surfing") and can scan the code themselves. -- Specter has full control over the network (in a Dolev-Yao sense), being able to observe and modify all traffic. -- Specter controls both the homeserver and the rendezvous server. - -##### Replay protection - -Due to use of ephemeral key pairs which are immediately discarded after use, each QR code login session derives a unique -secret so payloads from earlier sessions cannot be replayed. Each payload in the session is unique and expected only -once. Finally, the use of deterministic nonces prevents any possibility of replay. - -##### Pure Dolev-Yao attacker - -An attacker with control over the network but _not_ present for the QR code scanning cannot thwart the process since -they are unable to obtain the ephemeral key **Gp** of Device G. - -##### Shoulder-surfing attacker (Specter) - -Since Device G has no way of authenticating Device S, an attacker present for the QR code scanning can learn **Gp** and -attempt to mimic Device S in order to get their Device S signed in instead. - -- In step 3, Specter can shoulder surf the QR code scanning to obtain **Gp**. -- In step 4, Specter can intercept S's payload and replace it with a payload of their own, replacing **Sp** with its -own key. -- The attack is only thwarted in step 7, because Device S won't ever display the indicator of success to the user. The -user then must cancel the process on Device G, preventing it from sharing any sensitive material. - -#### Choice of message prefix - -During the secure channel establishment the messages have been prefixed with `MATRIX_QR_CODE_LOGIN_` rather than -something more generic. The purpose is to bind the protocol to this specific application. - -Whilst the could be other uses for the secure channel mechanism or we might establish communication between devices -using another mechanism (e.g. NFC or sound), this proposal only considers the scenario where the communication is -initiated via QR code and we make the prefix explicitly named to match. - -### The OAuth 2.0 login part and set up of E2EE - -Once the secure channel has been established, the two devices can then communicate securely. - -#### Login via OAuth 2.0 Device Authorization Grant from MSC4341 - -In this section the sequence of steps depends on whether the new device generated or scanned the QR code. - -This is where we start to make use of [MSC4341] Support for RFC 8628 Device Authorization Grant. +In this section the sequence of steps depends on whether the new device generated or scanned the QR code from [MSC4388]. For example, in the case that the new device scanned the QR code it is the first to do a `SecureSend` whereas if the new device generated the QR then the existing device is the first to do a `SecureSend`. -This can make it hard to read what is going on. +Unfortunately, this can make it hard to read what is going on. Sequence diagrams are included for both variants after +the steps are described. + +We use the `SecureSend` and `SecureReceive` operations from [MSC4388] which are sent via the out-of-band channel. 1. **Homeserver discovery** @@ -826,7 +99,7 @@ Otherwise the new device waits to be informed by receiving an `m.login.protocols The existing device would need to determine which "protocols" are available for the new device to use. -Currently this could only be device_authorization_grant meaning the homeserver supports the +Currently this could only be `device_authorization_grant` meaning the homeserver supports the `urn:ietf:params:oauth:grant-type:device_code` grant type. If it is available then the existing device informs the new device by sending the `m.login.protocols` message with the @@ -955,7 +228,7 @@ sequenceDiagram rect rgba(255,0,0, 0.1) #alt if New device scanned QR code - note over N: New device completes checks from secure channel establishment step 6 - it now trusts the channel + note over N: New device completes checks from MSC4388 secure channel establishment step 6 - it now trusts the channel note over N: 1) New device got server base URL from the QR code #else if Existing device scanned QR code @@ -986,8 +259,8 @@ sequenceDiagram rect rgba(255,0,0, 0.1) # alt if New device scanned QR code - note over N: New device displays checkmark and CheckCode - note over E: Existing device waits for user to enter CheckCode
and confirm secure channel from step 7 + note over N: New device displays checkmark and CheckCode from MSC4388 + note over E: Existing device waits for user to enter CheckCode
and confirm secure channel from MSC4388 step 7 end rect rgba(0,255,0, 0.1) @@ -1026,11 +299,11 @@ sequenceDiagram rect rgba(0,0,255, 0.1) #else if Existing device scanned QR code - note over E: Existing device completes step 6 - note over E: Existing device displays checkmark and CheckCode + note over E: Existing device completes MSC4388 step 6 + note over E: Existing device displays checkmark and CheckCode from MSC4388 note over E: 1) Existing device sends m.login.protocols message E->>HS: SecureSend({"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "https://matrix-client.matrix.org"}) - note over N: New device waits for user to confirm secure channel from step 7 + note over N: New device waits for user to confirm secure channel from MSC4388 step 7 HS->>N: SecureReceive() => {"type":"m.login.protocols", "protocols":["device_authorization_grant],
"base_url": "https://matrix-client.matrix.org"} note over N: If user enters the correct CheckCode and confirms checkmark
then new device now trusts the channel, and uses the homeserver provided end @@ -1204,7 +477,7 @@ sequenceDiagram end ``` -#### Secret sharing and device verification +### Secret sharing and device verification Once the new device has logged in and obtained an access token it will want to obtain the secrets necessary to set up end-to-end encryption on the device and make itself cross-signed. @@ -1341,16 +614,15 @@ note over N: All done! end ``` -#### Message reference +## Message reference These are the messages that are exchanged between the devices via the secure channel to negotiate the sign in and set up of E2EE. -##### `m.login.protocols` +### `m.login.protocols` -Sent by: existing device - -Purpose: to state the available protocols for signing in. At the moment only "`device_authorization_grant` is supported +- Sent by: existing device +- Purpose: to state the available protocols for signing in. At the moment only `device_authorization_grant` is supported Fields: @@ -1368,11 +640,10 @@ Fields: } ``` -##### `m.login.protocol` - -Sent by: new device +### `m.login.protocol` -Purpose: the new device sends this to indicate which protocol it intends to use +- Sent by: new device +- Purpose: the new device sends this to indicate which protocol it intends to use Fields: @@ -1397,12 +668,11 @@ Example: } ``` -##### `m.login.protocol_accepted` - -Sent by: existing device +### `m.login.protocol_accepted` -Purpose: Indicates that the existing device has accepted the protocol request and will open the `verification_uri` (or -`verification_uri_complete`) for the user to grant consent +- Sent by: existing device +- Purpose: Indicates that the existing device has accepted the protocol request and will open the `verification_uri` (or + `verification_uri_complete`) for the user to grant consent Example: @@ -1412,11 +682,10 @@ Example: } ``` -##### `m.login.failure` +### `m.login.failure` -Sent by: either device - -Purpose: used to indicate a failure +- Sent by: either device +- Purpose: used to indicate a failure Fields: @@ -1436,11 +705,10 @@ Example: } ``` -##### `m.login.declined` - -Sent by: new device +### `m.login.declined` -Purpose: Indicates that the user declined the request +- Sent by: new device +- Purpose: Indicates that the user declined the request Fields: @@ -1456,11 +724,10 @@ Example: } ``` -##### `m.login.success` +### `m.login.success` -Sent by: new device - -Purpose: to inform the existing device that it has successfully obtained an access token. +- Sent by: new device +- Purpose: to inform the existing device that it has successfully obtained an access token. Fields: @@ -1476,11 +743,10 @@ Example: } ``` -##### `m.login.secrets` - -Sent by: existing device +### `m.login.secrets` -Purpose: Shares the secrets used for cross-signing and room key backups +- Sent by: existing device +- Purpose: Shares the secrets used for cross-signing and room key backups Fields: @@ -1508,7 +774,7 @@ Example: } ``` -### Discoverability of the capability +## Discoverability of the capability Before offering this capability it would make sense that the device can check the availability of the feature. @@ -1517,7 +783,7 @@ Where the homeserver is known: 1. Check that the homeserver is using the OAuth 2.0 API using [server metadata discovery](https://spec.matrix.org/v1.15/client-server-api/#server-metadata-discovery) 1. Check that the Device Authorization Grant is available as per [MSC4341] 1. Check if the homeserver has a rendezvous session API available by attempting a POST to the create rendezvous endpoint - from this MSC. + from [MSC4388]. For a new device it would need to know the homeserver ahead of time in order to do these checks. @@ -1529,90 +795,10 @@ Master Signing Key, Self Signing Key and User Signing Key stored locally so that ## Potential issues -Because this is an entirely new set of functionality it should not cause issue with any existing Matrix functions or capabilities. - -The proposed protocol requires the devices to have IP connectivity to the server which might not be the case in P2P scenarios. +TODO ## Alternatives -### Alternative to the rendezvous session protocol - -#### ETag based rendezvous API - -An earlier iteration of this MSC used an alternative rendezvous API that was based on -[MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886). - -However, it was found to have issues including: - -- the ETags were getting mangled by proxies and load balancers -- the semantics of the API are different from the rest of the Matrix Client-Server API -- the CORS header changes required additional configuration work - -The present iteration of the rendezvous API described in this MSC attempts to "feel" more like a Matrix Client-Server -API. - -#### Send-to-Device messaging - -If you squint then this proposal looks similar in some regards to the existing -[Send-to-device messaging](https://spec.matrix.org/v1.9/client-server-api/#send-to-device-messaging) capability. - -Whilst to-device messaging already provides a mechanism for secure communication between two Matrix clients/devices, a -key consideration for the anticipated login with QR capability is that one of the clients is not yet authenticated with -a homeserver. - -Furthermore the client might not know which homeserver the user wishes to connect to. - -Conceptually, one could create a new type of "guest" login that would allow the unauthenticated client to connect to a -homeserver for the purposes of communicating with an existing authenticated client via to-device messages. - -Some considerations for this: - -Where the "actual" homeserver is not known then the "guest" homeserver nominated by the new client would need to be -federated with the "actual" homeserver. - -The "guest" homeserver would probably want to automatically clean up the "guest" accounts after a short period of time. - -The "actual" homeserver operator might not want to open up full "guest" access so a second type of "guest" account might -be required. - -Does the new device/client need to accept the T&Cs of the "guest" homeserver? - -#### Other existing protocols - -One could try and do something with STUN or TURN or [COAP](https://datatracker.ietf.org/doc/html/rfc7252). - -#### Implementation details - -Rather than requiring the devices to poll for updates, "long-polling" could be used instead similar to `/sync`. Or WebSockets. - -#### Unauthenticated device could crated "redirect channel" without payload - -In the current proposal the server operator may choose to not allow unauthenticated devices to create a rendezvous -session to reduce abuse/attack vectors. - -In this scenario it means that the unauthenticated client cannot create the QR code. - -An alternative would be to do something like this: - -1. Unauthenticated device (UD) creates a "redirect channel" on HS1 and sets that in the QR code. -1. The authenticated device (AD) creates a rendezvous channel on HS2. -1. HS2 POSTS to the redirect channel on HS1 with the homeserver and rendezvous channel ID. HS1 validates its from HS2. -1. HS1 returns the homeserver (HS2) and rendezvous channel ID to UD, who then uses that channel as normal. - -This has the following properties: - -1. It limits how much information can be persisted on an unauthenticated channel. We can severely restrict the size - of the request ID for example. -1. An abuser must use a domain they own if they want to encode dodgy data in the rendezvous channel ID. We can then ban - abusive domains. -1. An unauthenticated device can only receive information, rather than create a 2-way channel. Not sure that's at all - useful thing to assert, but it is nonetheless a property. -1. For each redirect channel created, you can only send one payload. This makes it easier to heavily ratelimit. - -Erik [said](https://github.com/matrix-org/matrix-spec-proposals/pull/4108#discussion_r2336295451): -> I think this sort of flow would reduce potential abuse vectors, but equally makes things more complicated and may not -> be worth it. - ### Alternative method of secret sharing Instead of the existing device sharing the secrets bundle instead the existing device could cross-sign the new device @@ -1632,31 +818,8 @@ mechanism does not offer. chance of other devices seeing the new device as unverified, incorrectly prompting the user to verify the device that will soon be verified. -### Alternative QR code formats - -An earlier version of this proposal kept the "version" byte at `0x02` and added additional "mode" -values of `0x03` (which is now intent `0x00`) and `0x04` (which is now intent `0x01`). - -The current usage of converting the "version" to be a "type" _feels_ like a more intuitive use of -the bytespace. - -Another alternative was to use a human readable prefix such as `MATRIX_LOGIN` instead of `MATRIX`. -This was discounted on the basis of wanting to keep the QR reasonably compact. - ## Security considerations -This proposed mechanism has been designed to protects users and their devices from the following threats: - -- A malicious actor who is able to scan the QR code generated by the legitimate user. -- A malicious actor who can intercept and modify traffic on the application layer, even if protected by encryption like TLS. -- Both of the above at the same time. - -Additionally, the homeserver is able to define and enforce policies that can prevent a sign in on a new device. -Such policies depend on the homeserver in use and could include, but are not limited to, time of day, day of the week, -source IP address and geolocation. - -A threat analysis has been done within each of the key layers in the proposal above. - ### Malicious session spawning This mechanism could be used by an attacker who has gained temporary access to a client to escalate the attack to creation @@ -1687,65 +850,16 @@ Recommendations to mitigate this are: n.b. the [2024 version](https://github.com/matrix-org/matrix-spec-proposals/blob/87f8317a902cd7bc5c2d2d225f71021b3a509e2d/proposals/4108-oidc-qr-login.md#unstable-prefix) of this proposal used a different set of unstable prefixes. -### Rendezvous API prefix - -While this feature is in development the new API endpoints should be exposed using the following unstable prefix: - -- `/_matrix/client/unstable/io.element.msc4108/rendezvous` instead of `/_matrix/client/v1/rendezvous` - -Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with the -key `io.element.msc4108` set to true. So, the response could look then as following: - -```json -{ - "versions": ["..."], - "unstable_features": { - "io.element.msc4108": true - } -} -``` - -### Unstable QR code format - -The unstable value of `IO_ELEMENT_MSC4108` should be used instead of `MATRIX` in the QR code. - -A full example for an existing device using ephemeral public key `2IZoarIZe3gOMAqdSiFHSAcA15KfOasxueUUNwJI7Ws` (base64 -encoded), at rendezvous session ID `e8da6355-550b-4a32-a193-1619d9830668` on homeserver `https://matrix-client.matrix.org` is as follows: (Whitespace is for readability only) - -``` -49 4F 5F 45 4C 45 4D 45 4E 54 5F 4D 53 43 34 31 30 38 -03 01 -d8 86 68 6a b2 19 7b 78 0e 30 0a 9d 4a 21 47 48 07 00 d7 92 9f 39 ab 31 b9 e5 14 37 02 48 ed 6b -00 24 -65 38 64 61 36 33 35 35 2D 35 35 30 62 2D 34 61 33 32 2D 61 31 39 33 2D 31 36 31 39 64 39 38 33 30 36 36 38 -00 20 -68 74 74 70 73 3A 2F 2F 6D 61 74 72 69 78 2D 63 6C 69 65 6E 74 2E 6d 61 74 72 69 78 2e 6f 72 67 -``` - -Which looks as follows as a QR with error correction level Q: - -![Example QR for intent 0x01](images/4108-qr-intent01-unstable.png) - -It is suggested that this unstable QR prefix convention could be used by future proposals. - -### M_CONCURRENT_WRITE errcode - -The unstable value of `IO_ELEMENT_MSC4108_CONCURRENT_WRITE` should be used instead of `M_CONCURRENT_WRITE`. +This proposal does not have an unstable prefix itself, but instead relies on the unstable names from MSC4388. ## Dependencies -This MSC builds on [MSC4341] which proposes support for RFC 8628 Device Authorization Grant in Matrix. +This MSC builds on: + +- [MSC4388] which provides the secure out-of-band channel for the devices to communicate via. +- [MSC4341] which proposes support for RFC 8628 Device Authorization Grant in Matrix. [MSC4341]: https://github.com/matrix-org/matrix-spec-proposals/pull/4341 "MSC4341 Support for RFC 8628 Device Authorization Grant" [server name]: https://spec.matrix.org/v1.16/appendices/#server-name [base URL]: https://spec.matrix.org/v1.16/client-server-api/#getwell-knownmatrixclient +[MSC4388]: https://github.com/matrix-org/matrix-spec-proposals/pull/4338 "MSC4388 Secure out-of-band channel for sign in with QR" From c0431eb8fe51436a72bc7d42e639497dd05518dc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 12 Dec 2025 14:36:41 +0000 Subject: [PATCH 66/70] Update proposals/4108-oidc-qr-login.md Co-authored-by: networkException --- proposals/4108-oidc-qr-login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f814aef8a08..8442f452262 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -862,4 +862,4 @@ This MSC builds on: [MSC4341]: https://github.com/matrix-org/matrix-spec-proposals/pull/4341 "MSC4341 Support for RFC 8628 Device Authorization Grant" [server name]: https://spec.matrix.org/v1.16/appendices/#server-name [base URL]: https://spec.matrix.org/v1.16/client-server-api/#getwell-knownmatrixclient -[MSC4388]: https://github.com/matrix-org/matrix-spec-proposals/pull/4338 "MSC4388 Secure out-of-band channel for sign in with QR" +[MSC4388]: https://github.com/matrix-org/matrix-spec-proposals/pull/4388 "MSC4388 Secure out-of-band channel for sign in with QR" From 5f6828da09c0ba21df36704f0911994daf78a7a5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Mar 2026 10:57:38 +0000 Subject: [PATCH 67/70] Remove old QR codes as now part of MSC4388 --- proposals/images/4108-qr-intent00.png | Bin 918 -> 0 bytes proposals/images/4108-qr-intent01-unstable.png | Bin 914 -> 0 bytes proposals/images/4108-qr-intent01.png | Bin 918 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 proposals/images/4108-qr-intent00.png delete mode 100644 proposals/images/4108-qr-intent01-unstable.png delete mode 100644 proposals/images/4108-qr-intent01.png diff --git a/proposals/images/4108-qr-intent00.png b/proposals/images/4108-qr-intent00.png deleted file mode 100644 index 9cef72ef3abf29fd1d086a87054180f545be2c2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 918 zcmV;H18Mw;P)=>)<}1Mk__+XF$(gT##04O8U{U)DW@k3o#Fg&0My=@&$!)t{ zy?Raj_ZR)=U;HE*qTlk~lKUjr+Ct29yR$` z*f8Vl{#z$i{OE$vM%eK=0LLk#aeYx$LqmFSabv=Aqx_3XBuLzzvak<60K zZQbyevYvCoNtt7rV(B6E;LnaVoT;~Pl4B0MM6-(Wy@p3#av3`)9G6y5#PwajlHayW ziKr`+ltA3z^WIw}zeJEb1<1*q1h+JEr{WoY0;%I;8MsjAXRG0yp#gEHHkg}SFV@5g zenlGa$U3rni(ckqNg93!G3_=uh~b7l$!{utF*VVj1W{3woVkkNqt%BZOuqE~Qf5b~>q-}*avhyfm@;TGb=gvC>{M1K=~&4pSnQim`3 zr;(+OoHhKmG_jkKYm%J3|D7MId57(}4@N*l3;Xq{h=O04&@Z!;64MsVViwkL-1!pG zBh(YD$zbsQ%jx(#^VOYEh$qQ0^ER|DKNzQ^v>>Ga2JU28H2^*8L%ko z^OGX}`$PZvhaW_bWVY(hTIS$}d=YsCKlyq_vnE=yS+m*FwXljO_q1hUM`3@3YsSxN zIr$d$E%Q72H9IwzZB}gDFz;)=l$@_Q8cuFtHIT3u!>(x-MN}A?=O2Txr|oD z6R#CV0A1lg_cQs8HM~(7APHLtAFpmn=KSQLk8|2j=9eu~En|@7j;P_y;+sT~Wy@S1 z^`bj(y_`|AwV-5Pa-ay6&xaa%?zbKGH-vPy8{?I+zl7|w2tfG9fnl1Nm+n|P5~ zu}xd>eZ1n#3V!13ut)f|-CwcHa-d$pVIy$Jslrd72R9`evW9c^N8lFaEzy3EKdg$g zLybH#gzC`R-VtuV3f_W0S^CJy1ZlwpKK0wl4~Yo_xSRqPTr!y z5r_k1Sc*TSgL?&+e#B@2{=}4(X-PkR)yGj~n|KkN1V@S1R=$0>lQaCyO_?5|D>Qw) z@(O-L32&-2BkdIjQ9gR;A-t2mity~5dUK?Qt=>3BZP{EVljN85qJe}7M6nSdC7dbpz}FsI9GT+rV$jK zG&s(PR>c{@c9^do?6-s|;^S|<{Jy_nhQTEK<&z)3?d39`!}cR|jz&OIfK|nl!}l@a z&yZG!gW4;pp;tmvbSSi`fE%;T4?O--&moDVm*6yf0D1pD5RNr}MFXPo6XNtl?KU zujoGV^iROCD_Pd?NFT}DF+@=*#PFW8l|S~}i26*Eg*YLtpVKOMbX`KoW-aEiZhWt@ zopVOWo>QGu9U%4SufQ74G+5W?bBVkrvx)M(h9_Q2odOh&q*Ywv_U_-vA6utLB&Anj z2unQrZ$8`j4MEz2BIIN)IzD8UpyI{NaL9z$I&!7p7pvi1%w5EtKG8hn2C*hq@H^31 z%w-+i`-58MbIlt5K;H5AL*=Oey3j z_sB)VA4@Z%Mb<7$?886vLo*LJ9((}FM+y7w=SCF#E;J`JBV1+e3yQ_8tl`M{3ZJwz z5Uj~y^F3?$jbIB+0a3GT4y{I3@Jrg+F=w+y3|Fo%?ycWu9%#VXm}c=wN67!%ry99* z{CIN4oMt#f3~qkwx0z#qOb&j9y++Oj zJ&3m;PTMmAC$cJz>$8|cP7%EHZs^=AxXi<{gf@f;7OD`}RdAWl346l5{T3k!fBl~G zo4EX6K&9{^Qy+pwKVlt6pM?_8@gKrHqVAM@PHqyNV=|{9*0FdE=lU~QVS0q$B6}!d sR&eBhiU-0R6Q1u6X?W}Z;{O-_1wb?VEVkkRz5oCK07*qoM6N<$g8h%a_5c6? From 042e5ab90a8d4544271b83c9f6466f51a9b25684 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Mar 2026 11:11:43 +0000 Subject: [PATCH 68/70] Add unable_to_open_verification_uri failure reason --- proposals/4108-oidc-qr-login.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 8442f452262..5a3b1b6b521 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -374,7 +374,7 @@ If no existing device was found then the existing device opens the `verification `verification_uri`, if `verification_uri_complete` isn't present - in a system browser. Ideally this is in a trusted/secure environment where the cookie jar and password manager features are available. e.g. -on iOS this could be a `ASWebAuthenticationSession` +on iOS this could be a `ASWebAuthenticationSession`. The existing device then sends an acknowledgement message to let the other device know that the consent process is in progress: @@ -386,6 +386,18 @@ The existing device then sends an acknowledgement message to let the other devic } ``` +If the URI could not be opened (e.g. unsupported URI scheme, no browser available, or the user refused permission to +open) then instead the existing device sends an `m.login.failure` with reason `unable_to_open_verification_uri`: + +*Existing device => New device via secure channel* + +```json +{ + "type": "m.login.failure", + "reason": "unable_to_open_verification_uri" +} +``` + 5. **User is asked by homeserver to consent on existing device** The user is then prompted to consent by the homeserver. They may be prompted to undertake additional actions by the @@ -444,7 +456,11 @@ sequenceDiagram HS->>E: 404 Not Found E->>UA: Existing device opens
verification_uri_complete (with fallback to verification_uri)
in the system web browser/ASWebAuthenticationSession: Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen - E->>HS: SecureSend({"type":"m.login.protocol_accepted"}) + alt URI opened + E->>HS: SecureSend({"type":"m.login.protocol_accepted"}) + else + E->>N: SecureSendReceive({"type":"m.login.failure", "reason":"unable_to_open_verification_uri"}) + end end par Note over UA: 5) User is asked by the homeserver to consent @@ -692,7 +708,7 @@ Fields: |Field|Type|| |--- |--- |--- | |`type`|required `string`|`m.login.failure`| -|`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
| +|`reason`|required `string`| One of:
Value Description
authorization_expired The Device Authorization Grant expired
device_already_exists The device ID specified by the new client already exists in the Homeserver provided device list
device_not_foundThe new device is not present in the device list as returned by the Homeserver
unexpected_message_receivedSent by either device to indicate that they received a message of a type that they weren't expecting
unsupported_protocolSent by a device where no suitable protocol is available or the requested protocol requested is not supported
user_cancelledSent by either new or existing device to indicate that the user has cancelled the login
unable_to_open_verification_uriSent by existing device to indicate that it was unable to open the `verification_uri_complete` (or `verification_uri`)
| |`homeserver`|`string`| When the existing device is sending this it can include the [server name] of the Matrix homeserver so that the new device can at least save the user the hassle of typing it in| Example: From 59cb21c9c819ca38255efcdd172242dd19c2cdfc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 17 Mar 2026 11:21:56 +0000 Subject: [PATCH 69/70] Add an example of where user_cancelled can be used --- proposals/4108-oidc-qr-login.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 5a3b1b6b521..8501fe5130f 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -386,8 +386,7 @@ The existing device then sends an acknowledgement message to let the other devic } ``` -If the URI could not be opened (e.g. unsupported URI scheme, no browser available, or the user refused permission to -open) then instead the existing device sends an `m.login.failure` with reason `unable_to_open_verification_uri`: +If the URI could not be opened (e.g. unsupported URI scheme, no browser available) then instead the existing device sends an `m.login.failure` with reason `unable_to_open_verification_uri`: *Existing device => New device via secure channel* @@ -398,6 +397,18 @@ open) then instead the existing device sends an `m.login.failure` with reason `u } ``` +If the user denied permission to open the browser then instead the existing device sends an `m.login.failure` with +reason `user_cancelled`: + +*Existing device => New device via secure channel* + +```json +{ + "type": "m.login.failure", + "reason": "user_cancelled" +} +``` + 5. **User is asked by homeserver to consent on existing device** The user is then prompted to consent by the homeserver. They may be prompted to undertake additional actions by the @@ -458,6 +469,8 @@ sequenceDiagram Note over E: n.b. in the case of a Web Browser the user needs to have
clicked a button in order for the navigation to happen alt URI opened E->>HS: SecureSend({"type":"m.login.protocol_accepted"}) + else user cancelled + E->>N: SecureSendReceive({"type":"m.login.failure", "reason":"user_cancelled"}) else E->>N: SecureSendReceive({"type":"m.login.failure", "reason":"unable_to_open_verification_uri"}) end From ec24672822fdae9e7ebeba0db2542a587611b57a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 30 Mar 2026 18:35:49 +0100 Subject: [PATCH 70/70] Complete the "potential issues" section --- proposals/4108-oidc-qr-login.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 8501fe5130f..75445a8056a 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -824,7 +824,17 @@ Master Signing Key, Self Signing Key and User Signing Key stored locally so that ## Potential issues -TODO +This proposal adds new functionality it is not anticipated that it would conflict with other existing features. + +Although this is providing a new authentication mechanism to Matrix it builds on top of the well established OAuth +Device Authorization Grant. + +Because the cryptograpgic identity that is used for end-to-end encryption is being shared it is particularly important +to ensure that new attack vectors are not opened up. + +A possible source of issues is the size and complexity of the proposal. + +Please also see the potential issues from the dependent MSCs. ## Alternatives