caddyserver/caddy · error

invalid KEM ID: %d

Error message

invalid KEM ID: %d

What it means

During ECH config decoding, the 2-byte KEM identifier is validated against the hpke package's known KEMs. Caddy only ever generates KEM_X25519_HKDF_SHA256 (0x0020); any other value in stored data — corrupt file, hand-crafted config, or a KEM the linked hpke library doesn't know — fails here. The message prints the offending numeric ID.

Source

Thrown at modules/caddytls/ech.go:996

	if !b.ReadUint16LengthPrefixed(&content) || !b.Empty() {
		return errInvalidLen
	}

	var t cryptobyte.String
	var pk []byte

	if !content.ReadUint8(&echCfg.ConfigID) ||
		!content.ReadUint16((*uint16)(&echCfg.KEMID)) ||
		!content.ReadUint16LengthPrefixed(&t) ||
		!t.ReadBytes(&pk, len(t)) ||
		!content.ReadUint16LengthPrefixed(&t) ||
		len(t)%4 != 0 /* the length of (KDFs and AEADs) must be divisible by 4 */ {
		return errInvalidLen
	}

	if !echCfg.KEMID.IsValid() {
		return fmt.Errorf("invalid KEM ID: %d", echCfg.KEMID)
	}

	var err error
	if echCfg.PublicKey, err = echCfg.KEMID.Scheme().UnmarshalBinaryPublicKey(pk); err != nil {
		return fmt.Errorf("parsing public_key: %w", err)
	}

	echCfg.CipherSuites = echCfg.CipherSuites[:0]

	for !t.Empty() {
		var hpkeKDF, hpkeAEAD uint16
		if !t.ReadUint16(&hpkeKDF) || !t.ReadUint16(&hpkeAEAD) {
			// we have already checked that the length is divisible by 4
			panic("this must not happen")
		}
		if !hpke.KDF(hpkeKDF).IsValid() {
			return fmt.Errorf("invalid KDF ID: %d", hpkeKDF)
		}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Note the reported KEM ID and compare with 0x20 (X25519) which Caddy writes.
  2. Delete the malformed ECH config entries under ech/configs/ in storage; Caddy regenerates with X25519 automatically.
  3. If importing external ECH configs, re-create them with the X25519 KEM.
Defensive patterns

Strategy: validation

Validate before calling

// Expected KEM: X25519-HKDF-SHA256 = 0x0020 in the ECHConfig wire format (after config_id byte).
func likelyValidKEM(configBin []byte) bool {
    // version(2) + len(2) + id(1) then 2-byte KEM
    return len(configBin) >= 7 && binary.BigEndian.Uint16(configBin[5:7]) == 0x0020
}

Try / catch

if err != nil && strings.Contains(err.Error(), "invalid KEM ID") {
    // prune malformed stored ECH configs; regenerate with X25519
}

Prevention

When it happens

Trigger: UnmarshalBinary of an ECH config whose KEM ID field is not a registered hpke.KEM (e.g. P-256/0x0017 if the hpke build lacks it, or random bytes after corruption).

Common situations: Corrupted ech/configs/<id>/config.bin; importing an ECH config produced by another implementation using a KEM Caddy's hpke dependency doesn't support; partial writes from storage failures.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/d26e7c9860b54a35. Report an issue: GitHub.