Merge branch 'element-hq/oidc-qr-login' into dkasak/oidc-qr-identity-key

pull/4130/head
Hugh Nimmo-Smith 2 months ago committed by GitHub
commit 1fe39b431e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -375,8 +375,9 @@ Participants:
Regardless of which device generates the QR code, either device can be the existing (already signed in) device. The
other device is then the new device (one seeking to be signed in).
Symmetric encryption uses deterministic nonces, incrementing by `2` with each payload. Device S starts with `0`, using only
even nonces. Device A starts with `1`, using only odd nonces.
Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF. A
separate deterministic, monotonically-incrementing nonce is used for each sender. Devices initially set both nonces to
`0` and increment the corresponding nonce by `1` for each message sent and received.
1. **Ephemeral key pair generation**
@ -401,7 +402,8 @@ that wishes to "reciprocate" a login
- If the intent is to reciprocate a login, then the **homeserver base URL**
To get a good trade-off between visual compactness and high level of error correction we use a binary mode QR with a
similar structure to that of the existing Device Verification QR code encoding described in [Client-Server API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format).
similar structure to that of the existing Device Verification QR code encoding described in [Client-Server
API](https://spec.matrix.org/v1.9/client-server-api/#qr-code-format).
This is defined in detail in a separate section of this proposal.
@ -412,22 +414,41 @@ At this point Device S should check that the received intent matches what the us
4. **Device S sends the initial payload**
Device S computes a shared secret **SH** using ECDH between **Ss** and **Gp**, thereby establishing a secure channel
with Device G which can be layered on top of the insecure rendezvous session transport. It then discards **Ss** and
derives a symmetric encryption **EncKey** from **SH** using HKDF_SHA256, each 32 bytes in length.
Device S computes a shared secret **SH** by performing ECDH between **Ss** and **Gp**. It then discards **Ss** and
derives two 32-byte symmetric encryption keys from **SH** using HKDF-SHA256. One of those keys, **EncKey_S** is
used for messages encrypted by device S, while the other, **EncKey_G** is used for encryption by device G.
Device S derives a confirmation payload that Device G can use to confirm that the channel is secure. It contains:
The keys are generated with the following HKDF parameters:
- The string `MATRIX_QR_CODE_LOGIN_INITIATE`, encrypted and authenticated with ChaCha20-Poly1305.
**EncKey_S**
- `MATRIX_QR_CODE_LOGIN_ENCKEY_S|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating
device's and the scanning device's ephemeral public keys, encoded as unpadded base64.
- An all-zero salt.
**EncKey_G**
- `MATRIX_QR_CODE_LOGIN_ENCKEY_G|Gp|Sp` as the info, where **Gp** and **Sp** stand for the generating
device's and the scanning device's ephemeral public keys, encoded as unpadded base64.
- An all-zero salt.
With this, Device S has established its side of the secure channel. Device S then derives a confirmation payload that
Device G can use to confirm that the channel is secure. It contains:
- The string `MATRIX_QR_CODE_LOGIN_ENCKEY_S`, encrypted and authenticated with ChaCha20-Poly1305.
- Its public ephemeral key **Sp**.
```
Nonce := 0
Nonce_S := 0
SH := ECDH(Ss, Gp)
EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN|" || Gp || "|" || Sp, salt=0, size=32)
NonceBytes := ToLowEndianBytes(Nonce)[..12]
TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_INITIATE")
Nonce := Nonce + 2
EncKey_S := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_S|" || Gp || "|" || Sp, salt=0, size=32)
// Stored, but not yet used
EncKey_G := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_ENCKEY_G|" || Gp || "|" || Sp, salt=0, size=32)
NonceBytes_S := ToLowEndianBytes(Nonce_S)[..12]
TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_S, NonceBytes_S, "MATRIX_QR_CODE_LOGIN_INITIATE")
Nonce_S := Nonce_S + 1
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp)
```
@ -440,7 +461,8 @@ Device G receives **LoginInitiateMessage** (potentially coming from Device S) fr
polling with `GET` requests.
It then does the reverse of the previous step, obtaining **Sp**, deriving the shared secret using **Gs** and **Sp**,
discarding **Gs** and decrypting (and authenticating) the **TaggedCiphertext**, obtaining a plaintext.
discarding **Gs**, deriving the two symmetric encryption keys **EncKey_S** and **EncKey_G**, then finally
decrypting (and authenticating) the **TaggedCiphertext** using **EncKey_S**, obtaining a plaintext.
It checks that the plaintext matches the string `MATRIX_QR_CODE_LOGIN_INITIATE`, failing and aborting if not.
@ -448,10 +470,10 @@ It then responds with a dummy payload containing the string `MATRIX_QR_CODE_LOGI
as follows:
```
Nonce := 1
NonceBytes := ToLowEndianBytes(Nonce)[..12]
TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, NonceBytes, "MATRIX_QR_CODE_LOGIN_OK")
Nonce := Nonce + 2
Nonce_G := 1
NonceBytes_G := ToLowEndianBytes(Nonce_G)[..12]
TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey_G, NonceBytes_G, "MATRIX_QR_CODE_LOGIN_OK")
Nonce_G := Nonce_G + 1
LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext)
```
@ -470,27 +492,29 @@ was indeed sent by Device G. It then verifies the plaintext matches `MATRIX_QR_C
Nonce_G := 1
(TaggedCiphertext, Sp) := Unpack(Message)
NonceBytes := ToLowEndianBytes(Nonce)[..12]
Plaintext := ChaCha20Poly1305_Decrypt(EncKey, NonceBytes, TaggedCiphertext)
Nonce_G := Nonce_G + 2
Plaintext := ChaCha20Poly1305_Decrypt(EncKey_G, NonceBytes, TaggedCiphertext)
Nonce_G := Nonce_G + 1
unless Plaintext == "MATRIX_QR_CODE_LOGIN_OK":
FAIL
```
If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and **Sp**:
If the above was successful, Device S then calculates a two digit **CheckCode** code derived from **SH**, **Gp** and
**Sp**:
```
CheckBytes := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN_CHECKCODE|" || Gp "|" || Sp , salt=0, size=2)
CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10)
```
Device S then displays an indication to the user that the secure channel has been established and that the **CheckCode**
should be entered on the other device when prompted. e.g. wording to say "secure connection established"; enter the code
XY on your other device;
Device S then displays an indicator to the user that the secure channel has been established and that the **CheckCode**
should be entered on the other device when prompted. Example wording could say "Secure connection established. Enter the
code XY on your other device."
7. **Out-of-band confirmation**
**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of ECIES.*
**Warning**: *This step is crucial for the security of the scheme since it overcomes the aforementioned limitation of
ECIES.*
Device G asks the user to enter the **CheckCode** that is being displayed on Device S.
@ -507,7 +531,8 @@ CheckCode := NumToString(CheckBytes[0] % 10) || NumToString(CheckBytes[1] % 10)
If the code that the user enters matches then the secure channel is established.
Subsequent payloads can be sent encrypted with **EncKey** with the nonces incremented as described above.
Subsequent payloads sent from G should be encrypted using **EncKey_G**, while payloads sent from S should be
encrypted with **EncKey_S**, incrementing the corresponding nonce for each message sent/received.
#### Sequence diagram
@ -537,7 +562,7 @@ sequenceDiagram
S->>+Z: GET /_matrix/client/rendezvous/abc-def
Z->>-S: 200 OK<br>ETag: 1
note over S: 4) Device S computes SH, EncKey and LoginInitiateMessage.<br>It sends LoginInitiateMessage via the rendezvous session
note over S: 4) Device S computes SH, EncKey_S, EncKey_G and LoginInitiateMessage.<br>It sends LoginInitiateMessage via the rendezvous session
S->>+Z: PUT /_matrix/client/rendezvous/abc-def<br>If-Match: 1<br>Body: LoginInitiateMessage
Z->>-S: 202 Accepted<br>ETag: 2
deactivate S
@ -546,7 +571,7 @@ sequenceDiagram
activate G
Z->>-G: 200 OK<br>ETag: 2<br>Body: Data
note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH and EncKey
note over G: 5) Device G attempts to parse Data as LoginInitiateMessage after calculating SH, EncKey_S and EncKey_G
note over G: Device G checks that the plaintext matches MATRIX_QR_CODE_LOGIN_INITIATE
note over G: Device G computes LoginOkMessage and sends to the rendezvous session
@ -576,21 +601,20 @@ sequenceDiagram
Conceptually, once established, the secure channel offers two operations, `SecureSend` and `SecureReceive`, which wrap
the `Send` and `Receive` operations offered by the rendezvous session API to securely send and receive data between two devices.
At the end of the establishment phase, the next nonce for Device G should be `3` and the next nonce for Device S should
be `2`.
At the end of the establishment phase, the next nonce for each device should be `1`.
Device G sets:
```
Nonce := 3
NonceOther = 2
Nonce_G := 1
Nonce_S := 1
```
Device S sets:
```
Nonce := 2
NonceOther := 3
Nonce_G := 1
Nonce_S := 1
```
#### Threat analysis

Loading…
Cancel
Save