{"record":{"id":"a53525b8baca3261","repo":"golang/go","slug":"tls-client-didn-t-send-one-key-share-in-second-cl","errorCode":null,"errorMessage":"tls: client didn't send one key share in second ClientHello","messagePattern":"tls: client didn't send one key share in second ClientHello","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_server_tls13.go","lineNumber":634,"sourceCode":"\t\t\tencodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload)\n\t\t\tif err != nil {\n\t\t\t\tc.sendAlert(alertDecryptError)\n\t\t\t\treturn nil, errors.New(\"tls: failed to decrypt second client hello encrypted client hello extension payload\")\n\t\t\t}\n\n\t\t\techInner, err := decodeInnerClientHello(clientHello, encodedInner)\n\t\t\tif err != nil {\n\t\t\t\tc.sendAlert(alertIllegalParameter)\n\t\t\t\treturn nil, errors.New(\"tls: client sent invalid encrypted client hello extension\")\n\t\t\t}\n\n\t\t\tclientHello = echInner\n\t\t}\n\t}\n\n\tif len(clientHello.keyShares) != 1 {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn nil, errors.New(\"tls: client didn't send one key share in second ClientHello\")\n\t}\n\tks := &clientHello.keyShares[0]\n\n\tif ks.group != selectedGroup {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn nil, errors.New(\"tls: client sent unexpected key share in second ClientHello\")\n\t}\n\n\tif clientHello.earlyData {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn nil, errors.New(\"tls: client indicated early data in second ClientHello\")\n\t}\n\n\tif illegalClientHelloChange(clientHello, hs.clientHello) {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn nil, errors.New(\"tls: client illegally modified second ClientHello\")\n\t}\n","sourceCodeStart":616,"sourceCodeEnd":652,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_server_tls13.go#L616-L652","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// No application-level fix; this is a protocol-conformance error.\n// On the client side, after receiving HelloRetryRequest:\n//   - Remove all existing key shares\n//   - Generate exactly one new key share for the server-selected group\n//   - Send only that single key share in the retry ClientHello\n// Example (conceptual):\n// before (buggy): resend all original keyShares\n// after (correct): keyShares = [{group: hrrSelectedGroup, data: newKeyShare}]","handlingStrategy":"validation","validationCode":"// This is a server-side protocol check; callers cannot prevent it.\n// If implementing a TLS 1.3 client, validate before sending:\nfunc validateRetryKeyShares(keyShares []keyShare, hrrGroup CurveID) error {\n    if len(keyShares) != 1 {\n        return errors.New(\"must send exactly one key share after HRR\")\n    }\n    if keyShares[0].group != hrrGroup {\n        return errors.New(\"key share group must match HRR selected_group\")\n    }\n    return nil\n}","typeGuard":"// Type guard for server-side: check keyShares slice before processing\nfunc hasExactlyOneKeyShare(ch *clientHelloMsg) bool {\n    return len(ch.keyShares) == 1\n}","tryCatchPattern":"// Server-side: these errors surface via tls.Conn.Handshake() error\n// Handle by logging and closing the connection:\nerr := conn.Handshake()\nif err != nil {\n    if strings.Contains(err.Error(), \"key share\") {\n        log.Printf(\"client key share protocol violation: %v\", err)\n    }\n    conn.Close()\n}","preventionTips":["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."],"tags":["tls","tls13","handshake","helloretryrequest","key-share","server-side"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}