Skip to content

Conversation

@hughns
Copy link
Member

@hughns hughns commented Dec 10, 2025

Rendered

This was originally part of MSC4108 but has been split out to make review easier.

@hughns hughns changed the title Secure out-of-band channel for sign in with QR MSC4388: Secure out-of-band channel for sign in with QR Dec 10, 2025
@turt2live turt2live added proposal A matrix spec change proposal client-server Client-Server API kind:core MSC which is critical to the protocol's success needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. labels Dec 10, 2025
Copy link
Member Author

@hughns hughns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@poljar I've reviewed the HPKE changes alongside the RFCs and have some questions/things needing clarification.

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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF.
Bidirectional encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF.

Perhaps?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was meant to talk about symmetric encryption as opposed to public key encryption, not specifically about the bidirectional part of the symmetric encryption.

Not sure how to clarify this more. Perhaps we just leave the jargon out and describe this just as "encryption" or "message encryption"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I think leaving the jargon out would be clearer.

How about this?

Suggested change
Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF.
Each device uses a distinct encryption key for sending messages. These encryption keys are derived from a shared secret using HKDF.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearer for whom? Certainly not clearer for cryptographic engineers who will be familiar with the jargon and who will hopefully be implementing this. Leaving it out can sometimes make clear things less clear.

That said, I don't believe the original formulation was adding much and your suggestion is fine-ish.

However, what I'm missing is a connection to the HPKE RFC. If we're telling people "this is HPKE" and linking to the RFC, surely we should be connecting it to the RFC terminology as much as possible? We also say above that this is "HPKE [...] instantiated with [...] ChaCha20-Poly1305 for the authenticated encryption" but we never mention the term "authenticated encryption" again. So for a reader who is not familiar with the jargon (i.e. is not a cryptographic engineer) and who therefore cannot connect all the dots themselves, it's entirely unclear which encryption key we're describing here, so I'm not sure the clarification actually helps.

Relatedly, why are we even retelling this here when it should be obvious from the RFC? Rather than talking about the key schedule directly, we should be tying the language of the MSC with the Context<ROLE> language of the RFC.


Symmetric encryption uses a separate encryption key for each sender, both derived from a shared secret using HKDF.
Separate nonces are used for each direction of the communication channel. Device S will create a base nonce from the
shared secret, while Device G will create a new random base nonce. Each base nonce is mixed with a
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
shared secret, while Device G will create a new random base nonce. Each base nonce is mixed with a
shared secret, while Device G will create a new random base nonce. Each base nonce is then mixed with a

Also, "mixed" sounds odd to me here. Maybe "combined" is better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a term that's somewhat commonly used when different sources of randomness are combined.

But I think "combined" works better here. Agree.


```
TaggedCiphertext := Context_S_S.Seal("MATRIX_QR_CODE_LOGIN_INITIATE", "")
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency does it make sense to get rid of the | string separator and rely on the known length of the keys? (e.g. 32 bytes in this case)

Suggested change
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp)
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext || Sp)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I don't mind them being gone. Not sure if @dkasak has some attachment to those separators.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we would chop off a 32 byte suffix in order to reconstruct the ciphertext? It might make more sense then to swap the order of Sp and TaggedCiphertext.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would expect us to change the order if the | is gone.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp)
LoginInitiateMessage := UnpaddedBase64(Sp || TaggedCiphertext)

|-|-|-|
|`id`|required `string`|Opaque identifier for the rendezvous session|
|`sequence_token`|required `string`|The token opaque token to identify if the payload has changed|
|`expires_ts`|required `integer`|The timestamp (in milliseconds since the epoch) at which the rendezvous session will expire|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concern: using absolute timestamps is hazardous because clients and servers can be desynchronised, leading to severe disagreements in when the rendezvous session should expire, including the client expiring the session immediately (see a concrete & real-world-triggered issue with the JS SDK's implementation of MSC4108 (2024 version) here: matrix-org/matrix-js-sdk#5141)

Should prefer expires_in or at least provide (and require use of) some way of converting this timestamp into a relative time.


```
TaggedCiphertext := ResponseContext_G.Seal("MATRIX_QR_CODE_LOGIN_OK", "")
LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext || ResponseBaseNonce)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LoginOkMessage := UnpaddedBase64Encode(TaggedCiphertext || ResponseBaseNonce)
LoginOkMessage := UnpaddedBase64Encode(ResponseBaseNonce || TaggedCiphertext)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client-server Client-Server API kind:core MSC which is critical to the protocol's success needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. proposal A matrix spec change proposal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants