golang/go · error
tls: server sent an unnecessary HelloRetryRequest key_share
Error message
tls: server sent an unnecessary HelloRetryRequest key_share
What it means
Raised while processing a TLS 1.3 HelloRetryRequest. RFC 8446 §4.1.4 forbids the server from selecting (via the key_share extension) a group for which the client already sent a key share in its first ClientHello. Go enforces this and sends an `illegal_parameter` alert before aborting. It almost always indicates a non-conformant server, middlebox, or active attacker.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:321
if hs.serverHello.serverShare.group != 0 {
c.sendAlert(alertDecodeError)
return errors.New("tls: received malformed key_share extension")
}
// If the server sent a key_share extension selecting a group, ensure it's
// a group we advertised but did not send a key share for, and send a key
// share for it this time.
if curveID := hs.serverHello.selectedGroup; curveID != 0 {
if !slices.Contains(hello.supportedCurves, curveID) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server selected unsupported group")
}
if slices.ContainsFunc(hs.hello.keyShares, func(ks keyShare) bool {
return ks.group == curveID
}) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
}
ke, err := keyExchangeForCurveID(curveID)
if err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: internal error: supportsCurve accepted unimplemented curve")
}
hs.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand())
if err != nil {
c.sendAlert(alertInternalError)
return err
}
// Do not send the fallback ECDH key share in a HRR response.
hello.keyShares = hello.keyShares[:1]
}
if len(hello.pskIdentities) > 0 {
pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
if pskSuite == nil {View on GitHub (pinned to b6b368adc5)
Solutions
- Report the bug to the server/middlebox operator; the server must select a group the client did NOT offer a share for.
- Pin CurvePreferences to a single group (e.g. tls.CurveP256) so the server never has reason to send HRR for a different group.
- Reproduce against a known-good peer (Go tls.Server) to confirm the fault is on the remote side, not your config.
- If you control the server, fix its HelloRetryRequest logic to select an unadvertised-share group only.
Example fix
// before
cfg := &tls.Config{} // default curves; server may HRR-pick one you shared
conn, err := tls.Dial("tcp", addr, cfg)
// after: pin one curve so HRR is never needed
cfg := &tls.Config{CurvePreferences: []tls.CurveID{tls.X25519}}
conn, err := tls.Dial("tcp", addr, cfg) Defensive patterns
Strategy: try-catch
Try / catch
// HelloRetryRequest violations are server-driven and cannot be prevented pre-flight.
// Treat as a fatal handshake error; do not retry blindly.
dialer := &net.Dialer{Timeout: 10 * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", addr, cfg)
if err != nil {
if strings.Contains(err.Error(), "unnecessary HelloRetryRequest key_share") {
// server is non-conformant; log and route to an alternate peer or surface to user
log.Printf("peer %s sent a non-conformant HRR: %v", addr, err)
}
return err
} Prevention
- Pin Config.CurvePreferences to a single widely-supported group so the server has no reason to send HelloRetryRequest.
- Log every handshake error with the peer address to spot chronically broken servers.
- Run integration tests against Go's own tls.Server peer to localise HRR faults.
When it happens
Trigger: Server returns HelloRetryRequest whose selectedGroup equals one of the groups in hs.hello.keyShares (i.e. the client already offered a share for it). Reachable via crypto/tls Dial/DialTLS/tls.Client.Handshake when the remote picks a curve the client pre-shared.
Common situations: Buggy in-house TLS 1.3 server, TLS-intercepting proxy/load-balancer, fuzz test against the Go client, or a malicious MITM. Rarely seen against mainstream servers (BoringSSL, OpenSSL, NSS, Go's own server).
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: server sent two HelloRetryRequest messages
- tls: server sent a cookie in a normal ServerHello
- tls: malformed key_share extension
- tls: server did not send a key share
- tls: server selected an invalid PSK
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/ff473c3d46aa9de3.
Report an issue: GitHub.