golang/go · error
tls: client didn't send one key share in second ClientHello
Error message
tls: client didn't send one key share in second ClientHello
What it means
During a TLS 1.3 HelloRetryRequest (HRR) round-trip, the server expects the client's second ClientHello to contain exactly one key share entry (the one for the group the server selected in HRR). This error fires when the server-side handshake processor sees len(clientHello.keyShares) != 1 after HRR. Per RFC 8446 Section 4.1.2, after HRR the client must drop all key shares except the one matching the server's selected group, yielding exactly one entry.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:634
encodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload)
if err != nil {
c.sendAlert(alertDecryptError)
return nil, errors.New("tls: failed to decrypt second client hello encrypted client hello extension payload")
}
echInner, err := decodeInnerClientHello(clientHello, encodedInner)
if err != nil {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client sent invalid encrypted client hello extension")
}
clientHello = echInner
}
}
if len(clientHello.keyShares) != 1 {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client didn't send one key share in second ClientHello")
}
ks := &clientHello.keyShares[0]
if ks.group != selectedGroup {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client sent unexpected key share in second ClientHello")
}
if clientHello.earlyData {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client indicated early data in second ClientHello")
}
if illegalClientHelloChange(clientHello, hs.clientHello) {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client illegally modified second ClientHello")
}
View on GitHub (pinned to b6b368adc5)
Solutions
- Verify the client TLS implementation correctly strips non-selected key shares after receiving HRR (RFC 8446 §4.1.2).
- Test with a known-good TLS 1.3 client (e.g. OpenSSL s_client, Firefox) to isolate whether the issue is client-side.
- If you control the client, ensure it sends exactly one keyShare extension entry whose group matches the server's HRR selected_group.
- Capture the handshake with Wireshark and inspect the second ClientHello's key_share extension.
Example fix
// No application-level fix; this is a protocol-conformance error.
// On the client side, after receiving HelloRetryRequest:
// - Remove all existing key shares
// - Generate exactly one new key share for the server-selected group
// - Send only that single key share in the retry ClientHello
// Example (conceptual):
// before (buggy): resend all original keyShares
// after (correct): keyShares = [{group: hrrSelectedGroup, data: newKeyShare}] Defensive patterns
Strategy: validation
Validate before calling
// This is a server-side protocol check; callers cannot prevent it.
// If implementing a TLS 1.3 client, validate before sending:
func validateRetryKeyShares(keyShares []keyShare, hrrGroup CurveID) error {
if len(keyShares) != 1 {
return errors.New("must send exactly one key share after HRR")
}
if keyShares[0].group != hrrGroup {
return errors.New("key share group must match HRR selected_group")
}
return nil
} Type guard
// Type guard for server-side: check keyShares slice before processing
func hasExactlyOneKeyShare(ch *clientHelloMsg) bool {
return len(ch.keyShares) == 1
} Try / catch
// Server-side: these errors surface via tls.Conn.Handshake() error
// Handle by logging and closing the connection:
err := conn.Handshake()
if err != nil {
if strings.Contains(err.Error(), "key share") {
log.Printf("client key share protocol violation: %v", err)
}
conn.Close()
} Prevention
- Test client TLS implementations against Go's TLS server for HRR compliance.
- Monitor for this error to detect non-conformant clients.
- Use Wireshark to verify client retry ClientHello structure during interop testing.
When it happens
Trigger: Server invokes processKeyShareClientHello after issuing a HelloRetryRequest; the returned second ClientHello contains zero key shares, or more than one. This typically arises from a buggy or non-conformant TLS client that either re-sends its original multi-share list, sends zero shares, or mis-implements the HRR response path.
Common situations: Interoperability testing against a custom or embedded TLS 1.3 client stack; a man-in-the-middle proxy that mangles key shares; a client library with an HRR bug (e.g. some older OpenSSL versions or custom IoT firmware); fuzzing the handshake with a test harness that doesn't faithfully replay HRR.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: client sent unexpected key share in second ClientHello
- tls: client illegally modified second ClientHello
- tls: server sent an unnecessary HelloRetryRequest key_share
- tls: server sent two HelloRetryRequest messages
- tls: malformed key_share extension
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/a53525b8baca3261.
Report an issue: GitHub.