{"record":{"id":"d26e7c9860b54a35","repo":"caddyserver/caddy","slug":"invalid-kem-id-d","errorCode":null,"errorMessage":"invalid KEM ID: %d","messagePattern":"invalid KEM ID: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/ech.go","lineNumber":996,"sourceCode":"\n\tif !b.ReadUint16LengthPrefixed(&content) || !b.Empty() {\n\t\treturn errInvalidLen\n\t}\n\n\tvar t cryptobyte.String\n\tvar pk []byte\n\n\tif !content.ReadUint8(&echCfg.ConfigID) ||\n\t\t!content.ReadUint16((*uint16)(&echCfg.KEMID)) ||\n\t\t!content.ReadUint16LengthPrefixed(&t) ||\n\t\t!t.ReadBytes(&pk, len(t)) ||\n\t\t!content.ReadUint16LengthPrefixed(&t) ||\n\t\tlen(t)%4 != 0 /* the length of (KDFs and AEADs) must be divisible by 4 */ {\n\t\treturn errInvalidLen\n\t}\n\n\tif !echCfg.KEMID.IsValid() {\n\t\treturn fmt.Errorf(\"invalid KEM ID: %d\", echCfg.KEMID)\n\t}\n\n\tvar err error\n\tif echCfg.PublicKey, err = echCfg.KEMID.Scheme().UnmarshalBinaryPublicKey(pk); err != nil {\n\t\treturn fmt.Errorf(\"parsing public_key: %w\", err)\n\t}\n\n\techCfg.CipherSuites = echCfg.CipherSuites[:0]\n\n\tfor !t.Empty() {\n\t\tvar hpkeKDF, hpkeAEAD uint16\n\t\tif !t.ReadUint16(&hpkeKDF) || !t.ReadUint16(&hpkeAEAD) {\n\t\t\t// we have already checked that the length is divisible by 4\n\t\t\tpanic(\"this must not happen\")\n\t\t}\n\t\tif !hpke.KDF(hpkeKDF).IsValid() {\n\t\t\treturn fmt.Errorf(\"invalid KDF ID: %d\", hpkeKDF)\n\t\t}","sourceCodeStart":978,"sourceCodeEnd":1014,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/ech.go#L978-L1014","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Expected KEM: X25519-HKDF-SHA256 = 0x0020 in the ECHConfig wire format (after config_id byte).\nfunc likelyValidKEM(configBin []byte) bool {\n    // version(2) + len(2) + id(1) then 2-byte KEM\n    return len(configBin) >= 7 && binary.BigEndian.Uint16(configBin[5:7]) == 0x0020\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"invalid KEM ID\") {\n    // prune malformed stored ECH configs; regenerate with X25519\n}","preventionTips":["Only import ECH configs created by the same Caddy/hpke stack.","Keep backups of storage clean; avoid partial-copy restores."],"tags":["tls","ech","crypto","hpke","serialization"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}