golang/go · error

tls: malformed encrypted client hello extension

Error message

tls: malformed encrypted client hello extension

What it means

Thrown during HelloRetryRequest ECH processing when the encrypted_client_hello extension in the ServerHello/HRR is present but not exactly 8 bytes long. The ECH accept confirmation value in an HRR must be exactly 8 bytes.

Source

Thrown at src/crypto/tls/handshake_client_tls13.go:260

	hs.transcript.Reset()
	hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
	hs.transcript.Write(chHash)
	if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil {
		return err
	}

	var isInnerHello bool
	hello := hs.hello
	if hs.echContext != nil {
		chHash = hs.echContext.innerTranscript.Sum(nil)
		hs.echContext.innerTranscript.Reset()
		hs.echContext.innerTranscript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
		hs.echContext.innerTranscript.Write(chHash)

		if hs.serverHello.encryptedClientHello != nil {
			if len(hs.serverHello.encryptedClientHello) != 8 {
				hs.c.sendAlert(alertDecodeError)
				return errors.New("tls: malformed encrypted client hello extension")
			}

			confTranscript := cloneHash(hs.echContext.innerTranscript, hs.suite.hash)
			hrrHello := make([]byte, len(hs.serverHello.original))
			copy(hrrHello, hs.serverHello.original)
			hrrHello = bytes.Replace(hrrHello, hs.serverHello.encryptedClientHello, make([]byte, 8), 1)
			confTranscript.Write(hrrHello)
			h := hs.suite.hash.New
			prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil)
			if err != nil {
				c.sendAlert(alertInternalError)
				return err
			}
			acceptConfirmation := tls13.ExpandLabel(h, prk, "hrr ech accept confirmation", confTranscript.Sum(nil), 8)
			if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.encryptedClientHello) == 1 {
				hello = hs.echContext.innerHello
				c.serverName = c.config.ServerName
				isInnerHello = true

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the server's ECH implementation produces exactly 8-byte accept confirmation in HelloRetryRequest.
  2. Ensure client and server implement compatible ECH draft versions.
  3. If ECH is not required, remove config.EncryptedClientHelloConfigList to disable ECH entirely.
  4. Report the malformed extension to the server's ECH implementation maintainer.
Defensive patterns

Strategy: try-catch

Try / catch

conn, err := tls.Dial("tcp", addr, config)
if err != nil {
    if strings.Contains(err.Error(), "malformed encrypted client hello extension") {
        // Server ECH HRR bug — disable ECH and retry
        config.EncryptedClientHelloConfigList = nil
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered when hs.echContext is not nil, hs.serverHello.encryptedClientHello is not nil, and len(hs.serverHello.encryptedClientHello) != 8 during HRR processing. The client sends alertDecodeError.

Common situations: Server bug in ECH HRR handling producing wrong-length confirmation. Mismatched ECH specification draft versions between client and server. Corrupted handshake message altering the extension length.

Understand the failure class

Related errors


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