{"record":{"id":"ff473c3d46aa9de3","repo":"golang/go","slug":"tls-server-sent-an-unnecessary-helloretryrequest-ff473c","errorCode":null,"errorMessage":"tls: server sent an unnecessary HelloRetryRequest key_share","messagePattern":"tls: server sent an unnecessary HelloRetryRequest key_share","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client_tls13.go","lineNumber":321,"sourceCode":"\n\tif hs.serverHello.serverShare.group != 0 {\n\t\tc.sendAlert(alertDecodeError)\n\t\treturn errors.New(\"tls: received malformed key_share extension\")\n\t}\n\n\t// If the server sent a key_share extension selecting a group, ensure it's\n\t// a group we advertised but did not send a key share for, and send a key\n\t// share for it this time.\n\tif curveID := hs.serverHello.selectedGroup; curveID != 0 {\n\t\tif !slices.Contains(hello.supportedCurves, curveID) {\n\t\t\tc.sendAlert(alertIllegalParameter)\n\t\t\treturn errors.New(\"tls: server selected unsupported group\")\n\t\t}\n\t\tif slices.ContainsFunc(hs.hello.keyShares, func(ks keyShare) bool {\n\t\t\treturn ks.group == curveID\n\t\t}) {\n\t\t\tc.sendAlert(alertIllegalParameter)\n\t\t\treturn errors.New(\"tls: server sent an unnecessary HelloRetryRequest key_share\")\n\t\t}\n\t\tke, err := keyExchangeForCurveID(curveID)\n\t\tif err != nil {\n\t\t\tc.sendAlert(alertInternalError)\n\t\t\treturn errors.New(\"tls: internal error: supportsCurve accepted unimplemented curve\")\n\t\t}\n\t\ths.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand())\n\t\tif err != nil {\n\t\t\tc.sendAlert(alertInternalError)\n\t\t\treturn err\n\t\t}\n\t\t// Do not send the fallback ECDH key share in a HRR response.\n\t\thello.keyShares = hello.keyShares[:1]\n\t}\n\n\tif len(hello.pskIdentities) > 0 {\n\t\tpskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)\n\t\tif pskSuite == nil {","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client_tls13.go#L303-L339","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\ncfg := &tls.Config{} // default curves; server may HRR-pick one you shared\nconn, err := tls.Dial(\"tcp\", addr, cfg)\n\n// after: pin one curve so HRR is never needed\ncfg := &tls.Config{CurvePreferences: []tls.CurveID{tls.X25519}}\nconn, err := tls.Dial(\"tcp\", addr, cfg)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// HelloRetryRequest violations are server-driven and cannot be prevented pre-flight.\n// Treat as a fatal handshake error; do not retry blindly.\ndialer := &net.Dialer{Timeout: 10 * time.Second}\nconn, err := tls.DialWithDialer(dialer, \"tcp\", addr, cfg)\nif err != nil {\n    if strings.Contains(err.Error(), \"unnecessary HelloRetryRequest key_share\") {\n        // server is non-conformant; log and route to an alternate peer or surface to user\n        log.Printf(\"peer %s sent a non-conformant HRR: %v\", addr, err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["tls","tls13","handshake","helloretryrequest","crypto","network","go"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}