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
- Note the reported KEM ID and compare with 0x20 (X25519) which Caddy writes.
- Delete the malformed ECH config entries under ech/configs/ in storage; Caddy regenerates with X25519 automatically.
- 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
- Only import ECH configs created by the same Caddy/hpke stack.
- Keep backups of storage clean; avoid partial-copy restores.
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
- parsing public_key: %w
- invalid KDF ID: %d
- invalid AEAD ID: %d
- marshaling ECH private key: %v
- marshaling ECH config: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/d26e7c9860b54a35.
Report an issue: GitHub.