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