Move proof to m.login.protocol message

pull/4130/head
Hugh Nimmo-Smith 2 months ago
parent 83064887e9
commit 461313312e

@ -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",<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"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",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...,<br>"device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"})
N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...,<br>"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",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}
Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"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",<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"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",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"})
N->>Z: SecureSend({"type": "m.login.protocol", "protocol": "device_authorization_grant",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"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",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"device_id": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}
Z->>E: SecureReceive() => {"type": "m.login.protocol", "protocol": "device_authorization_grant",<br> "device_authorization_grant":{<br>"verification_uri_complete": "https://id.matrix.org/device/abcde",<br>"verification_uri": ...},<br>"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/<device_id>](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/<device_id>](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/<device_id>](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: <table> <tr> <td><strong>Field</strong> </td> <td><strong>Type</strong> </td> </tr> <tr> <td><code>verification_uri</code> </td> <td>required <code>string</code> </td> </tr> <tr> <td><code>verification_uri_complete</code> </td> <td><code>string</code> </td> </tr></table>|
|`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"
}
```

Loading…
Cancel
Save