From 998963bd0fcc8f9657f9e0e8a57be77eba5e4b76 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 5 Apr 2024 17:45:34 +0200 Subject: [PATCH 01/17] Fix style. --- proposals/4108-oidc-qr-login.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 962ddcf392a..d812b04ee4b 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,14 +394,15 @@ 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** 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. @@ -414,7 +415,7 @@ At this point Device S should check that the received intent matches what the us 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. +derives a symmetric encryption **EncKey** from **SH** using HKDF-SHA256, each 32 bytes in length. Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: @@ -618,9 +619,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 @@ -962,7 +963,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` @@ -1050,7 +1051,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. @@ -1068,7 +1069,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" } } @@ -1144,7 +1145,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: @@ -1513,3 +1514,4 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin 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 7007bbf361eae035fa3595e4e64e20f2a0f05ec5 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 11 Apr 2024 04:33:09 +0200 Subject: [PATCH 02/17] Spell out HKDF parameters in text and change HKDF info. --- proposals/4108-oidc-qr-login.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index d812b04ee4b..d382b822d8d 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -413,19 +413,23 @@ 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 a 32-byte symmetric encryption **EncKey** from **SH** using HKDF-SHA256 with the following parameters: -Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: +- `MATRIX_QR_CODE_LOGIN_ENCRYPTION|Gp|Sp` as the info 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. -- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. +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_ENCRYPTION`, 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) +EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCRYPTION|" || Gp || "|" || Sp, salt=0, size=32) NonceBytes := ToLowEndianBytes(Nonce)[..12] TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_INITIATE") Nonce := Nonce + 2 @@ -1514,4 +1518,3 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin 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 be6f22f851ffa334a3acb0d8428308a0ce76f7e5 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 11 Apr 2024 05:14:21 +0200 Subject: [PATCH 03/17] Use two encryption keys and two nonces, one for each sender. --- proposals/4108-oidc-qr-login.md | 86 ++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index d382b822d8d..76821053f73 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 @@ -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** @@ -414,25 +415,40 @@ 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** by performing ECDH between **Ss** and **Gp**. It then discards **Ss** and -derives a 32-byte symmetric encryption **EncKey** from **SH** using HKDF-SHA256 with the following parameters: +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. -- `MATRIX_QR_CODE_LOGIN_ENCRYPTION|Gp|Sp` as the info the info, where Gp and Sp stand for the generating device's and - the scanning device's ephemeral public keys, encoded as unpadded base64. +The keys are generated with the following HKDF parameters: + +**EncKey_S** + +- `MATRIX_QR_CODE_LOGIN_ENCKEY_S|Gp|Sp` as the info 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 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_ENCRYPTION`, encrypted and authenticated with ChaCha20-Poly1305. +- 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_ENCRYPTION|" || 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_S|" || 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) ``` @@ -445,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. @@ -453,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) ``` @@ -475,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. @@ -512,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 @@ -542,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 @@ -551,7 +571,8 @@ 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 @@ -581,21 +602,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 84326e6a1790d1538ec9f1d852e17de07ecddcf8 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 11 Apr 2024 15:15:18 +0200 Subject: [PATCH 04/17] Commit to device ID = identity key and prove ownership of that key. --- proposals/4108-oidc-qr-login.md | 78 +++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 76821053f73..461a0e1e1fc 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -788,8 +788,16 @@ 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: +At this point, the new device should ensure it has generated its Olm account, so that it has its Curve25519 and Ed25519 +device identity keys. + +It then sends a `m.login.protocol` message to the existing device, containing: + +- An indicator that it wants to use protocol `device_authorization_grant` +- The `verification_uri` +- The `verification_uri_complete`, if present +- The device ID it will be using, which MUST equal the unpadded base64-encoded form of the Curve25519 identity key of + the new device *New device => Existing device via secure channel* @@ -801,7 +809,7 @@ indicates that want to use protocol `device_authorization_grant` along with the "verification_uri": "https://auth-oidc.lab.element.dev/link", "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" }, - "device_id": "ABCDEFGH" + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI" } ``` @@ -848,7 +856,7 @@ sequenceDiagram 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"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}) deactivate N end @@ -860,7 +868,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"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"} end rect rgba(255,0,0, 0.1) @@ -918,7 +926,7 @@ sequenceDiagram 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"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}) deactivate N end @@ -929,7 +937,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"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"} end # alt if New device scanned QR code @@ -953,10 +961,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* @@ -1028,7 +1036,7 @@ 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" }) + N->>E: SecureSend({ "type": "m.login.success", "proof": proof_of_identity_key_ownership }) 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"} @@ -1051,6 +1059,9 @@ sequenceDiagram end ``` +The reader will note that the `m.login.success` contains a proof that the new device owns the identity key it had +previously committed to, in the `m.login.protocol` step. This is explained in the next section. + #### 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 @@ -1067,7 +1078,7 @@ If checked successfully then the existing device sends the following secrets to This is achieved as following: -1. **Existing device confirms that the new device has indeed logged in successfully** +1. **Existing device confirms that a device with the previously committed-to device ID (device identity key) 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). @@ -1079,9 +1090,40 @@ If the device isn't immediately visible it can repeat the `GET` request for up t If no device is found then the process should be stopped. -2. **Existing device shares secrets with new device** +2. **Existing device confirms that the new device owns the private part of the committed-to device identity key** + +The new device then proves it controls the public key to which it previously committed. It does this by doing an ECDH +between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, +which is used to construct a proof of ownership. Due to the properties of ECDH, the other device knows that the new +device can only do this if it possesses the private part of the committed-to identity key. + +The new device does: + +``` +SH := ECDH(Is, Ep) +EncKey_Proof := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_PROOF|" || Ip || "|" || Ep, salt=0, size=32) +NonceBytes := ToLowEndianBytes(0)[..12] +Proof := ChaCha20Poly1305_Encrypt(EncKey_Proof, NonceBytes, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") +``` -The existing device sends a `m.login.secrets` message via the secure channel: +And sends the **Proof** to the existing device. + +The existing device does the following to verify the proof: + +``` +SH := ECDH(Es, Ip) +EncKey_Proof := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_PROOF|" || Ip || "|" || Ep, salt=0, size=32) +NonceBytes := ToLowEndianBytes(0)[..12] +Plaintext := ChaCha20Poly1305_Decrypt(EncKey_Proof, NonceBytes, Proof) + +unless Plaintext == "MATRIX_QR_CODE_PROOF_OF_POSSESSION": + FAIL +``` + +3. **Existing device shares secrets with new device** + +If both previous steps succeeded, the existing device proceeds to send a `m.login.secrets` message via the secure +channel: ```json { @@ -1122,14 +1164,14 @@ Content-Type: application/json "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2" ], - "device_id": "SGKMSRAGBF", + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "keys": { - "curve25519:SGKMSRAGBF": "I11VOe5quKuH/YjdOqn5VcW06fvPIJQ9JX8ryj6ario", - "ed25519:SGKMSRAGBF": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" + "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" }, "signatures": { "@testing_35:morpheus.localhost": { - "ed25519:SGKMSRAGBF": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", + "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFol": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" } }, @@ -1276,7 +1318,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| - + Example: ```json From db90b1ca18314a624923bedddf0eb930a92c50d0 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 11 Apr 2024 16:36:01 +0200 Subject: [PATCH 05/17] Fix typo and tweak styling. --- 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 461a0e1e1fc..b0ec180d595 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -422,13 +422,13 @@ The keys are generated with the following HKDF parameters: **EncKey_S** -- `MATRIX_QR_CODE_LOGIN_ENCKEY_S|Gp|Sp` as the info the info, where Gp and Sp stand for the generating +- `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 the info, where Gp and Sp stand for the generating +- `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. From 6c7c55a536cef2f53afefa0095d4b8d18e176496 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 12 Apr 2024 12:20:58 +0200 Subject: [PATCH 06/17] Apply suggestions from code review 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 | 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 b0ec180d595..f7fbc231401 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -444,7 +444,7 @@ 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_S|" || Gp || "|" || Sp, salt=0, size=32) +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") @@ -1092,7 +1092,7 @@ If no device is found then the process should be stopped. 2. **Existing device confirms that the new device owns the private part of the committed-to device identity key** -The new device then proves it controls the public key to which it previously committed. It does this by doing an ECDH +The new device then proves it controls the private key to which it previously committed. It does this by doing an ECDH between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, which is used to construct a proof of ownership. Due to the properties of ECDH, the other device knows that the new device can only do this if it possesses the private part of the committed-to identity key. From 2e6c3beb84fb56dbadc18a89dda65f068a8583a8 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 12 Apr 2024 12:36:41 +0200 Subject: [PATCH 07/17] Use HMAC-SHA256 instead of ChaCha20Poly1305 for proof of possession. --- proposals/4108-oidc-qr-login.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index f7fbc231401..7ed8429370f 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1094,16 +1094,15 @@ If no device is found then the process should be stopped. The new device then proves it controls the private key to which it previously committed. It does this by doing an ECDH between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, -which is used to construct a proof of ownership. Due to the properties of ECDH, the other device knows that the new -device can only do this if it possesses the private part of the committed-to identity key. +which is used to construct a proof of ownership based on HMAC-SHA256. Due to the properties of ECDH, the other device +knows that the new device can only do this if it possesses the private part of the committed-to identity key. The new device does: ``` SH := ECDH(Is, Ep) -EncKey_Proof := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_PROOF|" || Ip || "|" || Ep, salt=0, size=32) -NonceBytes := ToLowEndianBytes(0)[..12] -Proof := ChaCha20Poly1305_Encrypt(EncKey_Proof, NonceBytes, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") +ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) +Proof := HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") ``` And sends the **Proof** to the existing device. @@ -1112,11 +1111,9 @@ The existing device does the following to verify the proof: ``` SH := ECDH(Es, Ip) -EncKey_Proof := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_PROOF|" || Ip || "|" || Ep, salt=0, size=32) -NonceBytes := ToLowEndianBytes(0)[..12] -Plaintext := ChaCha20Poly1305_Decrypt(EncKey_Proof, NonceBytes, Proof) +ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) -unless Plaintext == "MATRIX_QR_CODE_PROOF_OF_POSSESSION": +unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == Proof: FAIL ``` @@ -1160,7 +1157,7 @@ Content-Type: application/json { "device_keys": { - "algorithms": [ + "algorithms": [ "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2" ], @@ -1580,3 +1577,4 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin 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 2725967ffdce480e5ca147f0d3a89bc17349c7ca Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 12 Apr 2024 12:56:59 +0200 Subject: [PATCH 08/17] More style fixes. --- proposals/4108-oidc-qr-login.md | 35 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 7ed8429370f..032d73abbf3 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1126,14 +1126,14 @@ channel: { "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" + "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" + "algorithm": "foobar", + "key": "$base64_of_the_backup_recovery_key", + "backup_version": "version_string" } } ``` @@ -1320,9 +1320,9 @@ 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" } ``` @@ -1342,7 +1342,7 @@ Example: ```json { - "type":"m.login.declined" + "type":"m.login.declined" } ``` @@ -1362,7 +1362,7 @@ Example: ```json { - "type": "m.login.success" + "type": "m.login.success", } ``` @@ -1386,14 +1386,14 @@ Example: { "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" + "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" + "algorithm": "foobar", + "key": "base64_of_the_backup_recovery_key", + "backup_version": "version_string" } } ``` @@ -1578,3 +1578,4 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin 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 f71d295dd8d269e6da3a3f1d66a4845f89323c9b Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 12 Apr 2024 12:58:55 +0200 Subject: [PATCH 09/17] Fix m.login.success examples and document "proof" key. --- proposals/4108-oidc-qr-login.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 032d73abbf3..25da693b505 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1036,7 +1036,7 @@ 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", "proof": proof_of_identity_key_ownership }) + N->>E: SecureSend({ "type": "m.login.success", "proof": base64_encoded_proof_of_identity_key_ownership }) 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"} @@ -1102,7 +1102,8 @@ The new device does: ``` SH := ECDH(Is, Ep) ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) -Proof := HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") +ProofBytes := HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") +Proof := UnpaddedBase64Encode(ProofBytes) ``` And sends the **Proof** to the existing device. @@ -1110,13 +1111,22 @@ And sends the **Proof** to the existing device. The existing device does the following to verify the proof: ``` +ProofBytes := UnpaddedBase64_Decode(Proof) + SH := ECDH(Es, Ip) ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) -unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == Proof: +unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == ProofBytes: FAIL ``` +```json +{ + "type": "m.login.success", + "proof": "$Proof" +} +``` + 3. **Existing device shares secrets with new device** If both previous steps succeeded, the existing device proceeds to send a `m.login.secrets` message via the secure @@ -1357,12 +1367,14 @@ Fields: |Field|Type|| |--- |--- |--- | |`type`|required `string`|`m.login.success`| +|`proof`|required `string`|New device's proof of identity key ownership, base64-encoded| Example: ```json { "type": "m.login.success", + "proof": base64_encoded_proof_of_identity_key_ownership } ``` @@ -1577,5 +1589,3 @@ key org.matrix.msc4108 set to true. So, the response could look then as followin 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 a0fd350473a68d315a684047673a2caf31c39a67 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 15 Apr 2024 13:02:21 +0100 Subject: [PATCH 10/17] Typo --- proposals/4108-oidc-qr-login.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 768e3a83091..37662a60188 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -571,8 +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, EncKey_S and - EncKey_G + 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 From 83064887e980786e7a65b7f3bdde7d52aec30093 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 16 Apr 2024 13:11:28 +0100 Subject: [PATCH 11/17] Revert secure channel changes --- proposals/4108-oidc-qr-login.md | 92 ++++++++++++--------------------- 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 37662a60188..dad680e9896 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -375,9 +375,8 @@ 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 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. +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** @@ -402,8 +401,7 @@ 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. @@ -414,41 +412,22 @@ 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** 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 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. -The keys are generated with the following HKDF parameters: +Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains: -**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. +- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305. - Its public ephemeral key **Sp**. ``` -Nonce_S := 0 +Nonce := 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 +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) ``` @@ -461,8 +440,7 @@ 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**, deriving the two symmetric encryption keys **EncKey_S** and **EncKey_G**, then finally -decrypting (and authenticating) the **TaggedCiphertext** using **EncKey_S**, obtaining a plaintext. +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. @@ -470,10 +448,10 @@ It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGI as follows: ``` -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 +Nonce := 1 +NonceBytes := ToLowEndianBytes(Nonce)[..12] +TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_OK") +Nonce := Nonce + 2 LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext) ``` @@ -492,29 +470,27 @@ 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_G, NonceBytes, TaggedCiphertext) -Nonce_G := Nonce_G + 1 +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**: +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." +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; 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. @@ -531,8 +507,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 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. +Subsequent payloads can be sent encrypted with **EncKey** with the nonces incremented as described above. #### Sequence diagram @@ -562,7 +537,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_S, EncKey_G and LoginInitiateMessage.
It sends LoginInitiateMessage via the rendezvous session + 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 @@ -571,7 +546,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, EncKey_S and EncKey_G + 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 @@ -601,20 +576,21 @@ 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 each device should be `1`. +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_G := 1 -Nonce_S := 1 +Nonce := 3 +NonceOther = 2 ``` Device S sets: ``` -Nonce_G := 1 -Nonce_S := 1 +Nonce := 2 +NonceOther := 3 ``` #### Threat analysis From 461313312e528bdf68474042e4ea184aab9ab63b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 16 Apr 2024 15:30:19 +0100 Subject: [PATCH 12/17] Move proof to m.login.protocol message --- proposals/4108-oidc-qr-login.md | 109 +++++++++++++++----------------- 1 file changed, 50 insertions(+), 59 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index dad680e9896..064ed3090c2 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -773,6 +773,23 @@ It then sends a `m.login.protocol` message to the existing device, containing: - The `verification_uri_complete`, if present - The device ID it will be using, which MUST equal the unpadded base64-encoded form of the Curve25519 identity key of the new device +- A proof of ownership of the device ID, which is a base64-encoded form of the proof described below + +The new device then proves it controls the private key to which it previously committed. It does this by doing an ECDH +between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, +which is used to construct a proof of ownership based on HMAC-SHA256. Due to the properties of ECDH, the other device +knows that the new device can only do this if it possesses the private part of the committed-to identity key. + +TODO: a paragraph to say why we do this + +To calculate the proof the new device does: + +``` +SH := ECDH(Is, Ep) +ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) +ProofBytes := HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") +Proof := UnpaddedBase64Encode(ProofBytes) +``` *New device => Existing device via secure channel* @@ -784,7 +801,8 @@ It then sends a `m.login.protocol` message to the existing device, containing: "verification_uri": "https://auth-oidc.lab.element.dev/link", "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" }, - "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI" + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` @@ -831,7 +849,7 @@ sequenceDiagram 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -843,7 +861,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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end rect rgba(255,0,0, 0.1) @@ -901,7 +919,7 @@ sequenceDiagram 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -912,7 +930,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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end # alt if New device scanned QR code @@ -961,11 +979,25 @@ normal for OIDC with MSC3861. 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 -`m.login.protocol` message. +existing device then verifies the `device_id_proof` and 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. +The existing device does the following to verify the proof: + +``` +ProofBytes := UnpaddedBase64_Decode(device_id_proof) + +SH := ECDH(Es, Ip) +ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) + +unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == ProofBytes: + FAIL +``` + +If the proof does not match then the login request should be rejected with an `m.login.failure` and reason `device_proof_invalid`. + +To asset the device does not already exist it calls [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) +and expects 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`. @@ -1011,7 +1043,7 @@ 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", "proof": base64_encoded_proof_of_identity_key_ownership }) + 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"} @@ -1034,9 +1066,6 @@ sequenceDiagram end ``` -The reader will note that the `m.login.success` contains a proof that the new device owns the identity key it had -previously committed to, in the `m.login.protocol` step. This is explained in the next section. - #### 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 @@ -1053,7 +1082,7 @@ If checked successfully then the existing device sends the following secrets to This is achieved as following: -1. **Existing device confirms that a device with the previously committed-to device ID (device identity key) has indeed logged in successfully** +1. **Existing device confirms that a device with the previously committed-to device ID 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). @@ -1061,51 +1090,13 @@ 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 some latency. If no device is found then the process should be stopped. -2. **Existing device confirms that the new device owns the private part of the committed-to device identity key** - -The new device then proves it controls the private key to which it previously committed. It does this by doing an ECDH -between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, -which is used to construct a proof of ownership based on HMAC-SHA256. Due to the properties of ECDH, the other device -knows that the new device can only do this if it possesses the private part of the committed-to identity key. - -The new device does: - -``` -SH := ECDH(Is, Ep) -ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) -ProofBytes := HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") -Proof := UnpaddedBase64Encode(ProofBytes) -``` - -And sends the **Proof** to the existing device. - -The existing device does the following to verify the proof: - -``` -ProofBytes := UnpaddedBase64_Decode(Proof) - -SH := ECDH(Es, Ip) -ProofKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_PROOFKEY|" || Ip || "|" || Ep, salt=0, size=32) - -unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == ProofBytes: - FAIL -``` - -```json -{ - "type": "m.login.success", - "proof": "$Proof" -} -``` - -3. **Existing device shares secrets with new device** +2. **Existing device shares secrets with new device** -If both previous steps succeeded, the existing device proceeds to send a `m.login.secrets` message via the secure -channel: +The existing device sends a `m.login.secrets` message via the secure channel: ```json { @@ -1257,6 +1248,7 @@ Fields: |`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| +|`device_id_proof`|required `string`|New device's proof of identity key ownership, base64-encoded| Example: @@ -1268,7 +1260,8 @@ Example: "verification_uri_complete": "https://id.matrix.org/device/abcde", "verification_uri": "..." }, - "device_id": "ABCDEFGH" + "device_id": "ABCDEFGH", + "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` @@ -1342,14 +1335,12 @@ Fields: |Field|Type|| |--- |--- |--- | |`type`|required `string`|`m.login.success`| -|`proof`|required `string`|New device's proof of identity key ownership, base64-encoded| Example: ```json { - "type": "m.login.success", - "proof": base64_encoded_proof_of_identity_key_ownership + "type": "m.login.success" } ``` From c91d7486959bd12edf4e801a0ac9eda2582eba0c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 16 Apr 2024 16:12:16 +0100 Subject: [PATCH 13/17] Update proof description and failure reason --- proposals/4108-oidc-qr-login.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 064ed3090c2..6ba9ba5511b 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -771,14 +771,16 @@ It then sends a `m.login.protocol` message to the existing device, containing: - An indicator that it wants to use protocol `device_authorization_grant` - The `verification_uri` - The `verification_uri_complete`, if present -- The device ID it will be using, which MUST equal the unpadded base64-encoded form of the Curve25519 identity key of - the new device +- The device ID it will be using, which MUST equal the unpadded base64-encoded form of the public part of the Curve25519 +identity key that the new device has generated - A proof of ownership of the device ID, which is a base64-encoded form of the proof described below -The new device then proves it controls the private key to which it previously committed. It does this by doing an ECDH -between the committed-to identity key and the other device's secure channel ephemeral key to derive a shared secret, -which is used to construct a proof of ownership based on HMAC-SHA256. Due to the properties of ECDH, the other device -knows that the new device can only do this if it possesses the private part of the committed-to identity key. +The new device proves it controls the Curve25519 key - with public part `Ip` and private part `Is` - by doing an ECDH +between the private part of the Curve25519 identity key and the other device's secure channel ephemeral public key (`Ep` +) to derive a shared secret. `Ep` equates to either `Gp` or `Sp` from the secure channel set up depending on which +device did the QR code scanning. This derived secret is then used to to construct a proof of ownership based on +HMAC-SHA256. Due to the properties of ECDH, the existing device knows that the new device can only do this if it +possesses the private part of the Curve25519 identity key. TODO: a paragraph to say why we do this @@ -994,7 +996,7 @@ unless HMAC_SHA256(ProofKey, "MATRIX_QR_CODE_PROOF_OF_POSSESSION") == ProofBytes FAIL ``` -If the proof does not match then the login request should be rejected with an `m.login.failure` and reason `device_proof_invalid`. +If the proof does not match then the login request should be rejected with an `m.login.failure` and reason `device_proof_failed`. To asset the device does not already exist it calls [GET /_matrix/client/v3/devices/](https://spec.matrix.org/v1.9/client-server-api/#get_matrixclientv3devicesdeviceid) and expects to receive an HTTP 404 response. @@ -1034,6 +1036,18 @@ sequenceDiagram rect rgba(0,255,0, 0.1) + note over E: Existing device validates the proof of device key ownership + alt proof invalid + E->>N: SecureSend({ "type": "m.login.failure", "reason": "device_proof_failed" }) + end + + 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 @@ -1291,7 +1305,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_proof_failedThe proof of device key ownership failed
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: From 49f3db393cc0cbc30dc4150cefad1cb0b8e9e33f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Apr 2024 18:08:49 +0100 Subject: [PATCH 14/17] Missed a device_id format change --- 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 aea3ba80487..afc1700cd5f 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -1298,7 +1298,7 @@ Example: "verification_uri_complete": "https://id.matrix.org/device/abcde", "verification_uri": "..." }, - "device_id": "ABCDEFGH", + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` From e66f39f18aa8fd249d604b8ae30bbfec1a5fc987 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Apr 2024 18:10:46 +0100 Subject: [PATCH 15/17] Add curve25519: prefix to device_id --- proposals/4108-oidc-qr-login.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index afc1700cd5f..71e3265c362 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -827,7 +827,7 @@ Proof := UnpaddedBase64Encode(ProofBytes) "verification_uri": "https://auth-oidc.lab.element.dev/link", "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" }, - "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` @@ -875,7 +875,7 @@ sequenceDiagram 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) + 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -887,7 +887,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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} + 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end rect rgba(255,0,0, 0.1) @@ -945,7 +945,7 @@ sequenceDiagram 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) + 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -956,7 +956,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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} + 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end # alt if New device scanned QR code @@ -1175,14 +1175,14 @@ Content-Type: application/json "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2" ], - "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "keys": { - "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", - "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" + "curve25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" }, "signatures": { "@testing_35:morpheus.localhost": { - "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", + "ed25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFol": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" } }, @@ -1298,7 +1298,7 @@ Example: "verification_uri_complete": "https://id.matrix.org/device/abcde", "verification_uri": "..." }, - "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` From 91d4bbefc12eb1ebc1659bd9e25a16070afd879f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Apr 2024 09:36:35 +0100 Subject: [PATCH 16/17] Revert "Add curve25519: prefix to device_id" This reverts commit e66f39f18aa8fd249d604b8ae30bbfec1a5fc987. --- proposals/4108-oidc-qr-login.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 71e3265c362..afc1700cd5f 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -827,7 +827,7 @@ Proof := UnpaddedBase64Encode(ProofBytes) "verification_uri": "https://auth-oidc.lab.element.dev/link", "verification_uri_complete": "https://auth-oidc.lab.element.dev/link?code=123456" }, - "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` @@ -875,7 +875,7 @@ sequenceDiagram 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -887,7 +887,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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end rect rgba(255,0,0, 0.1) @@ -945,7 +945,7 @@ sequenceDiagram 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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"}) deactivate N end @@ -956,7 +956,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": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} + 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": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership"} end # alt if New device scanned QR code @@ -1175,14 +1175,14 @@ Content-Type: application/json "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2" ], - "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "keys": { - "curve25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", - "ed25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" + "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "b8gROFh+UIHLD/obY0+IlxoWiGtYVhKdqixvw4QHcN8" }, "signatures": { "@testing_35:morpheus.localhost": { - "ed25519:curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", + "ed25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI": "ziHEUIsHnrYBH4CqYpN1JC/ex3t4VG3zvo16D8ORqN6yAErpsKsnd/5LDdZERIOB1MGffKGfCL6ny5V7rT9FCQ", "ed25519:bkYgAVUNqvuyy8b1w09utJNJxBvK3hZB65xxoLPVzFol": "p257k0tfPF98OIDuXnFSJS2DmVlxO4sgTHdF41DTdZBCpTZfPwok6iASo3xMRKdyy3WMEgkQ6lzhEyRKKZBGBQ" } }, @@ -1298,7 +1298,7 @@ Example: "verification_uri_complete": "https://id.matrix.org/device/abcde", "verification_uri": "..." }, - "device_id": "curve25519:3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "device_id_proof": "$base64_encoded_proof_of_identity_key_ownership" } ``` From 26f8aed7a8540332d2fc95ff241010d0629a882d Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 25 Apr 2024 13:32:59 +0200 Subject: [PATCH 17/17] Add rational paragraph. --- 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 afc1700cd5f..ef71a27061c 100644 --- a/proposals/4108-oidc-qr-login.md +++ b/proposals/4108-oidc-qr-login.md @@ -806,7 +806,16 @@ device did the QR code scanning. This derived secret is then used to to construc HMAC-SHA256. Due to the properties of ECDH, the existing device knows that the new device can only do this if it possesses the private part of the Curve25519 identity key. -TODO: a paragraph to say why we do this +By requiring the device ID to equal the device identity key, we reduce the number of (unnecessarily) free parameters, +allowing a user's E2EE devices to be uniquely identified only by their identity key, rather than by a (device ID, +identity key) 2-tuple. This paves the way for potentially making this a strict requirement for all E2EE-supporting +devices in a future iteration of the Matrix E2EE protocol. This would provide a marked increase in protocol robustness +and reduces potential for implementation errors. + +Separately, the proof of ownership of the identity key ensures that the new device cannot submit a key it does not +control, either by accident or maliciously. While this scenario doesn't represent an outright security +compromise---because a device cannot decrypt traffic for an identity key it does not control---it further reduces the +margin for implementation error. To calculate the proof the new device does: