golang/go · error

tls: malformed encrypted_client_hello extension

Error message

tls: malformed encrypted_client_hello extension

What it means

Thrown by parseECHExt when the encrypted_client_hello extension in the outer ClientHello is structurally malformed. The extension must contain: a type byte (0 for outer, 1 for inner), and for outer type: KDF ID (uint16), AEAD ID (uint16), config ID (uint8), uint16-length-prefixed encapsulated key, and uint16-length-prefixed encrypted payload. For inner type, no additional data is allowed. Any truncated field, incorrect length prefix, or unexpected trailing data triggers this error. Note: processECHClientHello wraps this into errInvalidECHExt and sends a decode error alert before returning to the caller.

Source

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

	}
	return true
}

// ECHRejectionError is the error type returned when ECH is rejected by a remote
// server. If the server offered a ECHConfigList to use for retries, the
// RetryConfigList field will contain this list.
//
// The client may treat an ECHRejectionError with an empty set of RetryConfigs
// as a secure signal from the server.
type ECHRejectionError struct {
	RetryConfigList []byte
}

func (e *ECHRejectionError) Error() string {
	return "tls: server rejected ECH"
}

var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension")
var errInvalidECHExt = errors.New("tls: client sent invalid encrypted_client_hello extension")

type echExtType uint8

const (
	innerECHExt echExtType = 1
	outerECHExt echExtType = 0
)

func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) {
	data := make([]byte, len(ext))
	copy(data, ext)
	s := cryptobyte.String(data)
	var echInt uint8
	if !s.ReadUint8(&echInt) {
		err = errMalformedECHExt
		return
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure the client sends a properly formatted ECH extension following the RFC 9460 wire format: type(1) + KDF(2) + AEAD(2) + configID(1) + enc(2-length-prefixed) + payload(2-length-prefixed)
  2. Verify no TLS-intermediating device (proxy, WAF, load balancer) is modifying handshake packets
  3. Confirm client and server use compatible ECH specification versions (RFC 9460 final vs. older drafts)
  4. Use network packet capture (tcpdump/Wireshark) to verify the extension bytes on the wire
Defensive patterns

Strategy: validation

Try / catch

// parseECHExt errors are wrapped by processECHClientHello:
//   - errMalformedECHExt → alertDecodeError → returns errInvalidECHExt
//   - errInvalidECHExt  → alertIllegalParameter → returns errInvalidECHExt
//
// if errors.Is(err, errInvalidECHExt) {
//     // client sent malformed or invalid ECH extension
// }

Prevention

When it happens

Trigger: The outer ClientHello's encrypted_client_hello extension is too short to contain the required fields, has incorrect length prefixes for the encapsulated key or encrypted payload, the inner-type variant has unexpected trailing bytes, or the outer-type variant is missing the KDF ID, AEAD ID, config ID, enc, or payload fields.

Common situations: A non-compliant or buggy client sends a malformed ECH extension with truncated fields. A middlebox or TLS-terminating proxy corrupts the extension bytes. The client uses an incompatible ECH draft version with a different extension wire format than RFC 9460. A deliberate fuzzing or attack attempt sending garbage in the ECH extension.

Understand the failure class

Related errors


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