Skip to content

[Bug] fingerprint=randomized can advertise X25519MLKEM768 without an initial key share, causing uTLS to abort after a valid HRR #6714

Description

@T1nyBear

This appears related to #4974 and #5031, which report the same tls: CurvePreferences includes unsupported curve symptom.

This report provides a packet-level reproduction of one concrete mechanism that triggers this error on Xray-core v26.7.28 with REALITY.

Environment

  • Client: Xray-core v26.7.28
  • Server: Xray-core v26.7.28
  • Protocol/transport/security: VLESS + XHTTP + REALITY
  • Client fingerprint: randomized

Only the client fingerprint was changed during the comparison.

The same client profile and server work consistently with fingerprint: firefox, including dozens of consecutive successful connections.

Reproduction

  1. Configure an Xray-core v26.7.28 client to use VLESS + XHTTP + REALITY with:

    "fingerprint": "randomized"
  2. Start the client process and attempt to connect.

  3. Restart the client process between test runs so that Xray generates different randomized seeds.

  4. With some generated randomized specifications, REALITY handshakes repeatedly fail for the lifetime of that client process.

  5. Change only the fingerprint to:

    "fingerprint": "firefox"
  6. Connections then complete successfully using the same server and the same remaining client settings.

Observed behavior

A failing randomized ClientHello advertises X25519MLKEM768 in supported_groups, but does not provide an initial key share for that group:

supported_groups:
  X25519MLKEM768 (0x11ec)
  X25519 (0x001d)
  secp256r1 (0x0017)
  secp384r1 (0x0018)

key_share:
  X25519 (0x001d)

0x11ec is the registered X25519MLKEM768 TLS Supported Group:

https://www.rfc-editor.org/rfc/rfc10024.html#section-7.1

The REALITY server responds with a TLS 1.3 HelloRetryRequest.

The ServerHello Random contains the standard TLS 1.3 HRR value:

cf 21 ad 74 e5 9a 61 11
be 1d 8c 02 1e 65 b8 91
c2 a2 11 16 7a bb 8c 5e
07 9e 09 e2 c8 a8 33 9c

The HRR selects X25519MLKEM768:

key_share selected_group:
00 33 00 02 11 ec

The client then sends:

14 03 03 00 01 01
15 03 03 00 02 02 50

These records decode as:

ChangeCipherSpec
fatal internal_error (80)

The TLS connection is then closed before any VLESS or XHTTP application data is exchanged. At the application level, this appears as a connected VPN tunnel with traffic stalling or pages loading indefinitely.

Protocol analysis

The initial ClientHello is not invalid merely because it advertises X25519MLKEM768 without providing an initial key share for that group.

The current TLS 1.3 specification states that key_share may contain shares for some or all groups listed in supported_groups.

If the server selects a mutually supported group for which the initial ClientHello does not contain a compatible key share, the server must respond with a HelloRetryRequest:

https://www.rfc-editor.org/rfc/rfc9846.html#section-4.2.1

After receiving an HRR containing a key_share selection, the client must send a second ClientHello whose key-share list contains a single entry for the selected group:

https://www.rfc-editor.org/rfc/rfc9846.html#section-4.2.2

Therefore, the server's HRR selecting X25519MLKEM768 is valid for this ClientHello.

Root cause

Xray-core v26.7.28 pins the following uTLS revision:

github.com/refraction-networking/utls
v1.8.3-0.20260301010127-aa6edf4b11af

In this revision, the randomized ClientHello generator independently decides whether to:

  1. include X25519MLKEM768 in supported_groups;
  2. include an initial X25519MLKEM768 key share.

The two independent generator paths are shown here:

The generator can therefore produce a ClientHello that advertises X25519MLKEM768, while its initial key_share contains only X25519.

In the same uTLS revision, the TLS 1.3 HRR handler explicitly does not support selecting X25519MLKEM768 through an HRR:

// Note: we don't support selecting X25519MLKEM768 in a HRR, because it
// is currently first in preference order, so if it's enabled we'll
// always send a key share for it.

However, this assumption does not hold for every ClientHello produced by the randomized generator.

When the HRR selects X25519MLKEM768, the client executes:

c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve")

Relevant HRR handler code:

https://github.com/refraction-networking/utls/blob/aa6edf4b11af82e110eea845bb2983d30138d651/handshake_client_tls13.go#L325-L346

The observed fatal internal_error (80) therefore matches this source-code path.

REALITY server behavior

The REALITY server prioritizes a mutually supported post-quantum key-exchange group.

When the selected group has no corresponding initial client key share, the server calls doHelloRetryRequest(selectedGroup):

https://github.com/XTLS/REALITY/blob/9234c772ba8f181f31c3e81dc2b4177322e5a9a9/handshake_server_tls13.go#L319-L335

Consequently, when the randomized ClientHello advertises X25519MLKEM768 without its key share, REALITY selects that group and sends the HRR that exposes the unsupported uTLS client path.

Xray seed persistence

Xray creates one PRNG seed for the randomized fingerprint during package initialization and reuses that seeded ClientHelloID:

weights := utls.DefaultWeights
weights.TLSVersMax_Set_VersionTLS13 = 1
weights.FirstKeyShare_Set_CurveP256 = 0
randomized := utls.HelloRandomizedALPN
randomized.Seed, _ = utls.NewPRNGSeed()
randomized.Weights = &weights
randomizednoalpn := utls.HelloRandomizedNoALPN
randomizednoalpn.Seed, _ = utls.NewPRNGSeed()
randomizednoalpn.Weights = &weights
PresetFingerprints["randomized"] = &randomized
PresetFingerprints["randomizednoalpn"] = &randomizednoalpn

Xray reuses the same seed for the lifetime of the client process, deterministically producing the same randomized specification. Therefore, if that specification advertises X25519MLKEM768 without the corresponding initial key share, connections to a server that selects this group through HRR continue to fail until the client process is restarted.

Restarting the client process generates a fresh randomized seed and may temporarily avoid the problem.

Expected behavior

fingerprint: randomized should not produce a ClientHello that advertises a key-exchange group which the embedded TLS implementation cannot provide after a valid HelloRetryRequest.

The handshake should not abort with fatal internal_error in response to a valid HRR selecting a group advertised by the client.

Possible implementation directions

Possible solutions include:

  • ensure that randomized always includes an initial X25519MLKEM768 key share whenever it advertises that group;
  • exclude X25519MLKEM768 from randomized supported_groups until its HRR path is supported;
  • implement X25519MLKEM768 HRR support in uTLS.

Related issues

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions