golang/go · error

tls: unexpected encrypted client hello extension in server h

Error message

tls: unexpected encrypted client hello extension in server hello despite ECH being accepted

What it means

Thrown during TLS 1.3 ECH (Encrypted Client Hello) processing when the server's accept_confirmation value matches (ECH was accepted) but the ServerHello still contains an encrypted_client_hello extension. Per the ECH specification, once acceptance is confirmed, the server must not include the ECH extension in ServerHello.

Source

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

		confTranscript.Write(hs.serverHello.original[:30])
		confTranscript.Write(make([]byte, 8))
		confTranscript.Write(hs.serverHello.original[38:])
		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, "ech accept confirmation", confTranscript.Sum(nil), 8)
		if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.random[len(hs.serverHello.random)-8:]) == 1 {
			hs.hello = hs.echContext.innerHello
			c.serverName = c.config.ServerName
			hs.transcript = hs.echContext.innerTranscript
			c.echAccepted = true

			if hs.serverHello.encryptedClientHello != nil {
				c.sendAlert(alertUnsupportedExtension)
				return errors.New("tls: unexpected encrypted client hello extension in server hello despite ECH being accepted")
			}

			if hs.hello.serverName == "" && hs.serverHello.serverNameAck {
				c.sendAlert(alertUnsupportedExtension)
				return errors.New("tls: unexpected server_name extension in server hello")
			}
		} else {
			hs.echContext.echRejected = true
		}
	}

	if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil {
		return err
	}

	c.buffering = true
	if err := hs.processServerHello(); err != nil {
		return err

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the ECH draft version on client and server match — ECH is still evolving through IETF drafts.
  2. If you control the server, ensure it omits encrypted_client_hello from ServerHello after ECH acceptance.
  3. If ECH is not required, remove config.EncryptedClientHelloConfigList from the client config to disable ECH entirely.
  4. Report the issue 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(), "ech being accepted") {
        // Server bug in ECH handling — disable ECH and retry
        config.EncryptedClientHelloConfigList = nil
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered inside checkServerHelloOrHRR() when hs.serverHello.encryptedClientHello is non-nil after ECH acceptance was confirmed via constant-time comparison of the accept_confirmation value. The client sends alertUnsupportedExtension.

Common situations: Server bug in ECH implementation that includes the extension after acceptance. Mismatched ECH draft versions between client and server causing divergent handling. Experimental or non-standard ECH server implementation.

Understand the failure class

Related errors


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