utls: [BUG] (Fake|Utls)PreSharedKeyExtension

I noticed while trying to implement / understand implementing a PSK for UtlsPreSharedKeyExtension on ClientHelloSpec i ran into a weird problem… so i started looking at full raw bytes from wireshark for a full client hello replication , the PSK is not actually populated and errors out , im not sure if this due to the recent change with fakePSK to UtlsPreSharedKeyExtension

empty psk detected; remove the psk extension for this connection or set OmitEmptyPsk to true to conceal it in utls

1603010200010001fc0303cee3cca75d5c385f42d7389f48cf75535b403d58159fecdfdf8ef276bf97deba20a581ab3a59648aead9f3a8715e523c82bee686636cf76c50226295badba9e2ee0020aaaa130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193aaaa0000000500050100000000ff0100010000230000000b00020100000d0012001004030804040105030805050108060601001200000010000e000c02683208687474702f312e310033002b00299a9a000100001d00203fcef7f05620d78f9aee265cee94d71e0ced2e692532c18f7cdb6add0055a22d446900050003026832001b000302000200170000000a000a00089a9a001d00170018002d0002010100000010000e00000b746c732e706565742e7773002b000706fafa03040303caca0001000015002c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029009c00770071854aabbb2c615e18010431cde6cd36b8e5184aab74d3f8e663befea450d68633ad4fbdf8d14a1e2790e04a3434806eea98e7a33fe5c2c5e74c44dba77c7b1da3e475b9fd6345eb5ad557002f95f36f771a40736de379b4cfe741d351d4121963f35abbd4b4c1a2c1c25e234ab30cb2b786857cf5e6002120ff98ad8e80d2b6d6d2f3e3ab73a5447afe9b79cb9e143aaac613b671b1910d58

This occurs if RealPSKResumption is true or false

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Comments: 30 (30 by maintainers)

Commits related to this issue

Most upvoted comments

There appears to be a bug/typo, which I’ll clarify. In short, the bug is in newSessionController, where we should set these to nil: https://github.com/refraction-networking/utls/blob/df6e4c827aa5895583d53e9ccc5139077af27786/u_session_controller.go#L57-L58


The SessionController was designed to manage all session-related operations. Initially, it had ownership over extensions.

But this posed a challenge. The ClientHelloSpec might have a different set of PSK/Session extensions. For clarity, the original design had the SessionController take charge of the extensions, overwriting those from the spec. This leads to the design that newSessionController produces a non-nil session and a psk extension. A downside of this approach was that it altered the signature of a public method, and we didn’t have a clear vision for v2.0 yet.

To circumvent this, I pivoted to a strategy where the Spec owns the extensions, ensuring the method signature remains unchanged. However, users should be able to override psk/session extensions before invoking ApplyPreset. These overriding extensions should exclusively reside in the SessionController. In such cases, the SessionController have ownership over the extensions.

This necessitated the creation of the syncSessionExts method to discern which extension takes precedence. The guiding principle is straightforward:

  1. If both the user and the spec offer an extension, prioritize the user’s.
  2. If the spec lacks an extension and the user attempts to provide one, it’s deemed an error.
  3. In the absence of a user-provided extension, revert to the spec’s version, if available.

Yet, during this overhaul, I overlooked the newSessionController method. As illustrated in the comments, deep in my mind I believe that the extension would default to nil unless overridden by the user: https://github.com/refraction-networking/utls/blob/df6e4c827aa5895583d53e9ccc5139077af27786/u_session_controller.go#L271-L273

Sorry about the delay in getting a PR sorted… life and all that 😄

@VeNoMouS No problem! Thanks for your efforts!

Ok, i’ll dive deeper into FakePreSharedKeyExtension and see if i can fix what ever is broken with it (if a PSK is present) … 🤞

To clarify on the intended behaviors:

  • If RealPSKResumption is set to true: parsing from raw bytes will discard the ticket and binders from the raw bytes, but use the available ones from ClientSessionCache instead.
  • If RealPSKResumption is false: parsing from raw bytes with legitimate PSK tickets and binders IS GUARANTEED TO FAIL, given that:
    • the server will check the ticket and find it is legitimate, and proceed with verifying the binder. If we are sending binders from the original raw bytes (that’s the intended behavior), the binder will not match the expected value which is calculated from all previous bytes in ClientHello.
    • If we were to re-calculated binders, it will be a different challenge for us to find the shared secret for that specific ticket.

Interesting observation. I don’t believe we are ready to parse raw byte ClientHello into PSK yet. On the other hand it is theoretically not possible to resume the connection with just the PSK ticket and binder from raw bytes.

That said, it might worth debugging and fixing to make at least FakePreSharedKeyExtension work with raw bytes. But note that in the end, unexpected things would happen, such as server rejecting you for submitting a valid ticket with an incorrect binder, etc.

Tagging @3andne for visibility.