diff --git a/proposals/4108-oidc-qr-login.md b/proposals/4108-oidc-qr-login.md index 76821053..461a0e1e 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