{"record":{"id":"2fab0f092da443bf","repo":"golang/go","slug":"tls-encryptedclienthelloconfiglist-contains-no-va","errorCode":null,"errorMessage":"tls: EncryptedClientHelloConfigList contains no valid configs","messagePattern":"tls: EncryptedClientHelloConfigList contains no valid configs","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client.go","lineNumber":188,"sourceCode":"\t\t}\n\t\thello.quicTransportParameters = p\n\t}\n\n\tvar ech *echClientContext\n\tif c.config.EncryptedClientHelloConfigList != nil {\n\t\tif c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {\n\t\t\treturn nil, nil, nil, errors.New(\"tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated\")\n\t\t}\n\t\tif c.config.MaxVersion != 0 && c.config.MaxVersion <= VersionTLS12 {\n\t\t\treturn nil, nil, nil, errors.New(\"tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated\")\n\t\t}\n\t\techConfigs, err := parseECHConfigList(c.config.EncryptedClientHelloConfigList)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\techConfig, echPK, kdf, aead := pickECHConfig(echConfigs)\n\t\tif echConfig == nil {\n\t\t\treturn nil, nil, nil, errors.New(\"tls: EncryptedClientHelloConfigList contains no valid configs\")\n\t\t}\n\t\tech = &echClientContext{config: echConfig, kdfID: kdf.ID(), aeadID: aead.ID()}\n\t\thello.encryptedClientHello = []byte{1} // indicate inner hello\n\t\t// We need to explicitly set these 1.2 fields to nil, as we do not\n\t\t// marshal them when encoding the inner hello, otherwise transcripts\n\t\t// will later mismatch.\n\t\thello.supportedPoints = nil\n\t\thello.ticketSupported = false\n\t\thello.secureRenegotiationSupported = false\n\t\thello.extendedMasterSecret = false\n\n\t\tinfo := append([]byte(\"tls ech\\x00\"), ech.config.raw...)\n\t\tech.encapsulatedKey, ech.hpkeContext, err = hpke.NewSender(echPK, kdf, aead, info)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t}\n","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client.go#L170-L206","documentation":"Thrown after parsing Config.EncryptedClientHelloConfigList when pickECHConfig(echConfigs) returns a nil config. The list parsed successfully (parseECHConfigList did not error) but no entry had a usable combination of HPKE KEM/KDF/AEAD that this Go build supports. ECH (Encrypted Client Hello, draft-ietf-tls-esni) wraps the real ClientHello inside an HPKE-sealed inner hello to hide the SNI.","triggerScenarios":"Calling tls.Dial / tls.DialTLS / http transport with Config.EncryptedClientHelloConfigList set to bytes where every entry uses an unsupported KEM (e.g. a post-quantum KEM this Go version lacks), uses an ECHConfigVersion other than 0xfffd, or has a public_name/key_config that pickECHConfig rejects. Common when the caller passes the whole DNS HTTPS RR instead of just the ech= base64 payload.","commonSituations":"Stale ECH config cached locally after the server rotated its HPKE key; fetching the ECH config from DNS HTTPS/SVCB records on a Go version older than the suite the server advertises; passing raw bytes that contain non-ECH SvcParamKeys; empty list after filtering malformed entries.","solutions":["Refetch the ECH config at runtime from the authoritative DNS HTTPS/SVCB ech= parameter (base64-decoded) rather than caching it long-term.","Verify you are passing only the ech= value (base64-decoded bytes), not the entire HTTPS RR or the quoted string.","Upgrade Go to a version whose crypto/internal/hpke supports the KEM/KDF/AEAD in the config (X25519 + HKDF-SHA256 + AES-128-GCM is the baseline).","If ECH cannot be made reliable, set Config.EncryptedClientHelloConfigList to nil and fall back to plaintext SNI rather than failing every handshake."],"exampleFix":"// before: stale hardcoded list\ncfg := &tls.Config{EncryptedClientHelloConfigList: cachedECHBytes}\n// after: fetch fresh at connection time and tolerate absence\nechBytes, err := dnshttps.ECHConfig(serverName) // resolves type 65\ncfg := &tls.Config{}\nif err == nil && len(echBytes) > 0 {\n    cfg.EncryptedClientHelloConfigList = echBytes\n}","handlingStrategy":"validation","validationCode":"// Parse the ECH config list the same way the library will, and check that\n// at least one config is acceptable before dialing.\nimport \"crypto/internal/fips140/hpke\" // not publicly exposed; mirror the supported suites\n\nfunc hasUsableECHConfig(list []byte) bool {\n    // Walk 4-byte version + 1-byte length-prefixed ECHConfig entries (draft-ietf-tls-esni).\n    for len(list) > 0 {\n        if len(list) < 4 { return false }\n        // version is uint16 at list[0:2]; config_id length follows\n        n := int(list[3])\n        if 4+n > len(list) { return false }\n        cfg := list[4 : 4+n]\n        if echSupported(cfg) { return true }\n        list = list[4+n:]\n    }\n    return false\n}\n// echSupported should check the HPKE KEM/KDF/AEAD triple is one your Go supports.\n// In practice: fetch a fresh config list at runtime and rely on Go's own parsing.","typeGuard":"// No type guard: errors are untyped strings. Match on the message prefix.\nfunc isECHNoValidConfigs(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"tls: EncryptedClientHelloConfigList contains no valid configs\")\n}","tryCatchPattern":"echBytes, err := resolveECHConfig(host)\nif err == nil { cfg.EncryptedClientHelloConfigList = echBytes }\nif _, derr := tls.Dial(\"tcp\", addr, cfg); derr != nil {\n    if isECHNoValidConfigs(derr) {\n        // retry once with ECH disabled rather than failing permanently\n        cfg.EncryptedClientHelloConfigList = nil\n        _, derr = tls.Dial(\"tcp\", addr, cfg)\n    }\n}","preventionTips":["Fetch ECH configs at runtime from DNS HTTPS records; never hardcode them.","Pass only the base64-decoded ech= value, not the whole HTTPS RR.","Upgrade Go alongside ECH draft revisions."],"tags":["tls","ech","client-handshake","crypto","config"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}