golang/go · error

tls: client sent encrypted_client_hello extension with unsup

Error message

tls: client sent encrypted_client_hello extension with unsupported versions

What it means

Thrown when the reconstructed ECH inner ClientHello's supported_versions extension contains a non-GREASE version below TLS 1.3 (0x0304). ECH is defined exclusively for TLS 1.3 and later per RFC 9460, so advertising TLS 1.2 (0x0303), TLS 1.1 (0x0302), TLS 1.0 (0x0301), or SSL 3.0 (0x0300) in the inner hello is a protocol violation. GREASE values (0x?A0A pattern) are skipped before this check.

Source

Thrown at src/crypto/tls/ech.go:393

	}

	hasTLS13 := false
	for _, v := range inner.supportedVersions {
		// Skip GREASE values (values of the form 0x?A0A).
		// GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism used by
		// browsers like Chrome to ensure TLS implementations correctly ignore unknown values.
		// GREASE values follow a specific pattern: 0x?A0A, where ? can be any hex digit.
		// These values should be ignored when processing supported TLS versions.
		if v&0x0F0F == 0x0A0A && v&0xff == v>>8 {
			continue
		}

		// Ensure at least TLS 1.3 is offered.
		if v == VersionTLS13 {
			hasTLS13 = true
		} else if v < VersionTLS13 {
			// Reject if any non-GREASE value is below TLS 1.3, as ECH requires TLS 1.3+.
			return nil, errors.New("tls: client sent encrypted_client_hello extension with unsupported versions")
		}
	}

	if !hasTLS13 {
		return nil, errors.New("tls: client sent encrypted_client_hello extension but did not offer TLS 1.3")
	}

	return inner, nil
}

func decryptECHPayload(context *hpke.Recipient, hello, payload []byte) ([]byte, error) {
	outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1)
	return context.Open(outerAAD, payload)
}

func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
	var b cryptobyte.Builder
	b.AddUint8(0) // outer

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Set the client's MinVersion to at least tls.VersionTLS13 when using ECH
  2. Ensure the inner ClientHello's supported_versions extension only includes TLS 1.3 (0x0304) and optionally GREASE values
  3. If using a non-Go ECH client, verify it enforces the TLS 1.3 minimum for ECH inner hellos
  4. Remove any explicit version configuration that allows TLS 1.2 or earlier when ECH is enabled

Example fix

// before
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MinVersion: tls.VersionTLS12, // allows TLS 1.2 — invalid with ECH
}
// after
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MinVersion: tls.VersionTLS13,
}
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: verify MinVersion before enabling ECH
func validateECHVersionConfig(config *tls.Config) error {
    if config.EncryptedClientHelloConfigList != nil {
        if config.MinVersion != 0 && config.MinVersion < tls.VersionTLS13 {
            return fmt.Errorf("MinVersion must be >= TLS 1.3 when ECH is enabled")
        }
    }
    return nil
}

Try / catch

// Server-side: wrapped into errInvalidECHExt.
// Client-side: prevent by validating config before dial:
//
//   if err := validateECHVersionConfig(config); err != nil {
//       log.Fatal(err)
//   }

Prevention

When it happens

Trigger: The inner ClientHello's supported_versions extension includes at least one non-GREASE version ID with a numeric value less than 0x0304 (VersionTLS13).

Common situations: Client configured with MinVersion below TLS 1.3 while using ECH — though the client-side makeClientHello also checks this, a custom or non-Go client might not. An ECH client library that does not enforce the TLS 1.3 minimum for the inner hello. A misconfigured client that includes legacy versions in the inner hello's supported_versions.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/ade8d9249f198a9c. Report an issue: GitHub.