golang/go · error
tls: client sent unexpected key share in second ClientHello
Error message
tls: client sent unexpected key share in second ClientHello
What it means
After a HelloRetryRequest the server expects the single key share in the client's second ClientHello to use the exact group the server selected in the HRR's selected_group field. This error fires when ks.group != selectedGroup. RFC 8446 Section 4.1.2 requires the client to generate a fresh key share for the server-requested group.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:640
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")
}
c.didHRR = true
hs.clientHello = clientHello
return ks, nil
}
// illegalClientHelloChange reports whether the two ClientHello messages areView on GitHub (pinned to b6b368adc5)
Solutions
- Confirm the client reads the HelloRetryRequest's key_share extension selected_group and generates a matching key share.
- Check that no intermediary (load balancer, TLS terminator) modifies the HRR selected_group value.
- Test with a reference TLS 1.3 implementation to confirm the server's HRR is well-formed.
- Inspect the packet capture: compare HRR selected_group vs. the key share group in the second ClientHello.
Example fix
// No server-side fix; this is a client protocol-conformance issue. // Client must generate key share for the group the server requested in HRR: // before (buggy): keyShare.group = clientOriginalGroup // after (correct): keyShare.group = hrrSelectedGroup
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: validate key share group matches HRR before sending
func validateKeyShareGroup(group CurveID, hrrSelectedGroup CurveID) error {
if group != hrrSelectedGroup {
return fmt.Errorf("key share group %v != HRR group %v", group, hrrSelectedGroup)
}
return nil
} Try / catch
// Server-side: handle during Handshake()
err := conn.Handshake()
if err != nil && strings.Contains(err.Error(), "unexpected key share") {
log.Printf("client sent wrong key share group after HRR: %v", err)
conn.Close()
} Prevention
- Verify client reads HRR selected_group correctly.
- Test HRR flows in your interop suite.
- Log which group the server requested vs. what the client sent.
When it happens
Trigger: The server sent a HelloRetryRequest asking for group X (e.g. X25519), but the client's retry ClientHello contains a key share for a different group Y (e.g. P-256), or it resent the original group. The server's selectedGroup variable was set during the initial ClientHello processing.
Common situations: Client misreads or ignores the HRR selected_group field; a proxy alters the HRR or the retry ClientHello; interoperability mismatch between client and server curve preferences; a client bug where it generates a key share for the wrong curve after HRR.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: client didn't send one 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/cf8d6834970e5c84.
Report an issue: GitHub.