Summary

  • TLS 1.3 HelloRetryRequest is not a fresh negotiation. The second ClientHello must repeat the first except for a narrow set of authorized changes, including one requested key share, removal of early data, a supplied cookie and explicitly defined extension behavior.
  • The protocol replaces the bytes of ClientHello1 with a synthetic message_hash carrying its digest, then authenticates that commitment together with the retry and later messages. Stateless retry compresses history; it does not erase it.
  • Operational evidence must retain both ClientHello messages, the retry reason, the permitted delta, cookie policy, selected group, alert outcome and added latency. A completed handshake alone cannot prove that the retry was valid or that the final group was the client's original preference.

The trace that started too late

The diagnosis looked straightforward. A service team had a successful TLS 1.3 connection and a packet capture beginning at ClientHello2. The client sent a single key share for the group the server eventually used. The server then sent ServerHello, its authentication messages and Finished. No alert appeared. The natural conclusion was that the client had offered the final group voluntarily.

That conclusion converted an outcome into a history.

The first ClientHello had advertised several supported groups but predicted a key share for another one. The server did not accept that prediction. It sent HelloRetryRequest, naming a group that the client had said it supported but for which it had not yet sent a share. Only then did the client construct the single-share second hello visible in the capture.

The distinction matters whenever algorithm policy, latency, post-quantum migration or incident attribution is under review. supported_groups says which groups the client can use. key_share says which expensive exchange material it chose to send immediately. HelloRetryRequest records what correction the server requested. ServerHello records what was finally selected. None of these facts can substitute for the others.

A monitoring system that retains only the successful second flight will systematically assign the server's request to the client. It may report a group as the client's first preference, declare that no retry occurred and charge the added round trip to the network. Its bytes can be accurate while its account of authority is false.

A correction with an allowlist

The current TLS 1.3 specification gives the retry a narrow grammar. After HelloRetryRequest, the client sends the same ClientHello again, except for changes the protocol authorizes. If the retry contains key_share, the client replaces its earlier list with exactly one newly generated share for the selected group. If the first hello offered early data, the client removes early_data. If the server supplied a cookie, the client copies it. Pre-shared-key binders are recomputed over the retry transcript. A future extension may define another change only when that extension is actually present and supplies the rule.

This is not a suggestion to make the messages roughly similar. It is an allowlist. The protected decision is not only the final group; it is the boundary around what may change while every other offer remains stable.

The group request is bounded twice. The selected group must have appeared in the original supported_groups list, so the server cannot introduce a capability the client never claimed. It must not be a group for which the client already supplied a key share, because such a retry would make no useful correction. A retry that produces no change is rejected. A second retry is rejected. The cipher suite cannot drift between the retry and ServerHello, and the eventual ServerHello key share must match the group requested.

Those checks turn a conversational word—retry—into a deterministic state transition. The server may ask for one permitted correction. It may not reopen every field, invent a new offer or keep asking until the client accepts a different policy.

Why the first hello becomes message_hash

Most TLS handshake computations authenticate a transcript hash built from the handshake messages in order. HelloRetryRequest creates a practical problem for a stateless server. If the server wants to issue a cookie and discard per-client state, it cannot keep an arbitrary running hash state or the full first ClientHello for every requester.

TLS solves that problem by replacing ClientHello1 in the transcript with a synthetic handshake message. Its handshake type is message_hash, value 254, and its body is Hash(ClientHello1). The transcript then continues with HelloRetryRequest, ClientHello2, ServerHello and the later handshake messages.

The construction is a compact commitment to the first offer. It lets a server carry sufficient context in an integrity-protected cookie or equivalent state without pretending that the second hello began a new negotiation. CertificateVerify and Finished are computed over a history that includes the commitment and the retry. A PSK binder after retry also incorporates the first hello, the retry and the truncated second hello under its defined calculation.

The first message is no longer available from the digest alone. That is why operational capture still matters. Cryptography can make later tampering detectable to the endpoints while an observability system remains unable to explain which extension, group or key share appeared in the original bytes. The transcript proves continuity; a retained trace explains the delta.

Prediction is not support, and support is not selection

TLS 1.3 clients try to save a round trip by predicting useful key shares in the first message. Generating and sending a share for every supported group would consume computation and enlarge ClientHello. Sending only one share saves bytes but risks a retry when the server prefers or requires another supported group.

The tradeoff is increasingly visible as implementations add larger hybrid post-quantum groups. OpenSSL exposes group tuples and explicit predicted-share markers. A client can send selected shares immediately, while a server can choose whether to accept an offered share or request a more preferred one. Its documentation also notes the other edge of the tradeoff: a larger first ClientHello can cross a TCP-segment boundary and encounter defective firewall behavior, while deferring a large share can reserve it for servers willing to pay the retry.

GnuTLS exposes group policy through priority strings and lets its client send a single TLS 1.3 key share. These controls show why an inventory table is insufficient. The implementation may know a group, policy may enable it, the first hello may list it, the first hello may or may not carry its share, the server may request it, and the connection may finally negotiate it. Six distinct states are often collapsed into one word: supported.

The safe question is not “Do both sides support group X?” It is “Which groups were enabled, which were advertised, which shares were predicted, what did the server request, what was returned, and what was finally authenticated?”

Early data does not survive the detour

HelloRetryRequest also closes the attempted 0-RTT path. A client that included early_data in the first hello removes the extension from the second. The server's retry is therefore evidence that early data was not accepted on that handshake.

This boundary belongs in application telemetry. The early bytes may have been sent before the client received the retry. The application still owns the question of whether an operation is safe to repeat, whether it observed a response and how it reconciles a rejected attempt. TLS does not transform a replay-sensitive request into an idempotent one merely because the handshake later succeeds at 1-RTT.

A dashboard that labels the final connection “resumed” or “successful” without the early-data outcome hides the consequential state change. Record at least attempted, accepted, rejected-by-retry, retransmission decision and application result as separate fields.

A cookie proves only what it was built to prove

The cookie extension lets a server place opaque state in HelloRetryRequest and require the client to return it unchanged in ClientHello2. The server decides the contents: it may bind the first hello's hash, selected parameters, time, routing context or other data. Integrity protection can prove that the cookie came from the server and was not altered, subject to key custody and validation rules.

DTLS 1.3 adds a clear operational use. A server can bind the cookie to the apparent client address and require return traffic before sending an amplified response. The DTLS specification discusses secret rotation, overlapping acceptance windows and timestamps because a stateless token still has a lifecycle.

Reachability is not identity. Returning an address-bound cookie can show that someone received traffic at that address during the token's validity. It does not identify a person, establish a durable device role or grant an application permission. Nor does a valid cookie excuse the server from checking the second ClientHello against the first commitment. A token can carry state; it cannot expand the authority of the state transition it represents.

Extensions must join the same history

Encrypted Client Hello supplies a useful composition example. HelloRetryRequest uses a fixed random value, so ECH cannot place its retry acceptance signal there in the way it uses ServerHello random. The ECH specification instead defines an encrypted_client_hello extension carrying a confirmation derived from the inner ClientHello and the modified retry transcript.

The lesson is architectural, not an invitation to turn every retry investigation into an ECH investigation. A later extension does not receive a private negotiation universe. It must define what may change, where its signal appears and how that signal joins the authenticated history. The base transcript remains the authority boundary around the retry.

Evidence that can survive an incident

A defensible retry record starts before the event most dashboards call the connection. It retains a hash and, where policy permits, the parsed fields of ClientHello1; the exact HelloRetryRequest; the cookie hash rather than its secret contents; the parsed fields of ClientHello2; and the final ServerHello. It records the implementation and configuration revisions that produced each policy decision.

For group selection, record enabled groups, advertised order, predicted shares, server preference mode, requested group, returned group, negotiated group and whether the selected group was already present as a first-flight share. For the retry itself, record the reason, whether the delta matched the allowlist, the alert on rejection and whether a second retry was attempted.

For effects, record the added round-trip time, ClientHello byte sizes, fragmentation or segmentation observations, handshake completion, early-data outcome and client population. Aggregate HRR rate is useful only when broken down by those dimensions. A higher rate may reflect a policy migration, a new client prediction, a post-quantum rollout, an address-validation defense or a configuration mistake.

Finally, retain negative evidence. Test that the client rejects an unadvertised group, a group already shared, a retry with no effective change, a changed cipher suite, a second retry and a ServerHello inconsistent with the request. BoringSSL's runner models many malformed variants because rejection is part of interoperability, not merely a security footnote.

The completed handshake is the end of the story, not the proof of every preceding decision. The proof is the chain of constrained changes that made completion legitimate.

Sources