{"record":{"id":"8a922e3ae7faf548","repo":"golang/go","slug":"tls-second-client-hello-encrypted-client-hello-ex","errorCode":null,"errorMessage":"tls: second client hello encrypted client hello extension does not match","messagePattern":"tls: second client hello encrypted client hello extension does not match","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_server_tls13.go","lineNumber":613,"sourceCode":"\t\t\tc.sendAlert(alertMissingExtension)\n\t\t\treturn nil, errors.New(\"tls: second client hello missing encrypted client hello extension\")\n\t\t}\n\n\t\techType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello)\n\t\tif err != nil {\n\t\t\tc.sendAlert(alertDecodeError)\n\t\t\treturn nil, errors.New(\"tls: client sent invalid encrypted client hello extension\")\n\t\t}\n\n\t\tif echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner {\n\t\t\tc.sendAlert(alertDecodeError)\n\t\t\treturn nil, errors.New(\"tls: unexpected switch in encrypted client hello extension type\")\n\t\t}\n\n\t\tif echType == outerECHExt {\n\t\t\tif echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 {\n\t\t\t\tc.sendAlert(alertIllegalParameter)\n\t\t\t\treturn nil, errors.New(\"tls: second client hello encrypted client hello extension does not match\")\n\t\t\t}\n\n\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","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_server_tls13.go#L595-L631","documentation":"For outer ECH in ClientHello2, the ciphersuite and config_id must match what was in ClientHello1, and encap must be empty (the HPKE context is already established). Any mismatch triggers illegal_parameter per the ECH draft.","triggerScenarios":"ClientHello2 changes the ECH ciphersuite or config_id versus ClientHello1, or includes unexpected non-empty encap bytes.","commonSituations":"ECH client bug; a replayed ClientHello with a different ECH config; fuzzers.","solutions":["Keep the ECH ciphersuite and config_id identical between ClientHello1 and ClientHello2","Send an empty encap in ClientHello2's outer ECH extension as the draft requires"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Client-side: assert outer ECH is consistent across both ClientHellos.\nif echType1 != echType2 || suite1 != suite2 || configID1 != configID2 || len(encap2) != 0 {\n    return errors.New(\"ClientHello2 ECH must match ClientHello1 (and empty encap)\")\n}","typeGuard":null,"tryCatchPattern":"if err := tlsConn.Handshake(); err != nil {\n    if strings.Contains(err.Error(), \"encrypted client hello extension does not match\") {\n        log.Printf(\"ECH fields changed on retry from %v\", remote)\n    }\n    c.Close()\n    return\n}","preventionTips":["Keep ECH ciphersuite and config_id identical between ClientHello1 and ClientHello2","Send an empty encap in ClientHello2's outer ECH"],"tags":["tls","go","ech","encrypted-client-hello","consistency","handshake"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T11:17:21.771Z"}