{"record":{"id":"b2506cadea27771e","repo":"golang/go","slug":"tls-minversion-must-be-versiontls13-if-encrypt","errorCode":null,"errorMessage":"tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated","messagePattern":"tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client.go","lineNumber":177,"sourceCode":"\t\t\thello.keyShares = hello.keyShares[:1]\n\t\t}\n\t}\n\n\tif c.quic != nil {\n\t\tp, err := c.quicGetTransportParameters()\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\tif p == nil {\n\t\t\tp = []byte{}\n\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","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client.go#L159-L195","documentation":"Thrown by makeClientHello when config.EncryptedClientHelloConfigList is non-nil (ECH is enabled on the client) AND config.MinVersion is explicitly set to a value below VersionTLS13 (0x0304). ECH is a TLS 1.3-only extension per RFC 9460, so requiring a minimum version below 1.3 while enabling ECH is contradictory and rejected at configuration time.","triggerScenarios":"Setting both EncryptedClientHelloConfigList (to a non-nil byte slice) and MinVersion to VersionTLS12 (0x0303), VersionTLS11 (0x0302), or VersionTLS10 (0x0301) simultaneously.","commonSituations":"Adding ECH to an existing tls.Config that had MinVersion=VersionTLS12 for legacy compatibility. Copying a production config that restricted MinVersion and adding ECH without updating the version floor. Misunderstanding that ECH requires TLS 1.3.","solutions":["Set MinVersion to tls.VersionTLS13 (or higher) when using ECH","Leave MinVersion as 0 (zero value) when using ECH — the zero value allows TLS 1.3 and the ECH code path works correctly","If you need TLS 1.2 fallback, do not use ECH — ECH and TLS 1.2 are mutually exclusive"],"exampleFix":"// before\nconfig := &tls.Config{\n    EncryptedClientHelloConfigList: echConfigList,\n    MinVersion:                    tls.VersionTLS12,\n}\n// after\nconfig := &tls.Config{\n    EncryptedClientHelloConfigList: echConfigList,\n    MinVersion:                    tls.VersionTLS13,\n}","handlingStrategy":"validation","validationCode":"func validateECHClientConfig(config *tls.Config) error {\n    if config.EncryptedClientHelloConfigList == nil {\n        return nil\n    }\n    if config.MinVersion != 0 && config.MinVersion < tls.VersionTLS13 {\n        return errors.New(\"MinVersion must be >= VersionTLS13 when ECH is enabled\")\n    }\n    if config.MaxVersion != 0 && config.MaxVersion <= tls.VersionTLS12 {\n        return errors.New(\"MaxVersion must be >= VersionTLS13 when ECH is enabled\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Pre-validate before dial:\n//\n//   if err := validateECHClientConfig(config); err != nil {\n//       log.Fatal(err)\n//   }\n//   conn, err := tls.Dial(\"tcp\", addr, config)","preventionTips":["Always set MinVersion >= tls.VersionTLS13 when EncryptedClientHelloConfigList is populated","Build a Config constructor that validates ECH + version constraints together","Write unit tests asserting ECH-enabled configs reject TLS 1.2 MinVersion"],"tags":["tls","client-side","config","ech","version-negotiation","tls13"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}