Skip to content

Add ML-KEM key encapsulation support#184

Open
sergio-correia wants to merge 6 commits into
latchset:masterfrom
sergio-correia:ml-kem
Open

Add ML-KEM key encapsulation support#184
sergio-correia wants to merge 6 commits into
latchset:masterfrom
sergio-correia:ml-kem

Conversation

@sergio-correia

Copy link
Copy Markdown
Collaborator

Add post-quantum ML-KEM (FIPS 203) support to jose, implementing the encapsulate/decapsulate model per draft-ietf-jose-pqc-kem-06.

  • Introduce JOSE_HOOK_ALG_KIND_KEM with enc/dec callbacks and public API (jose_jwk_kem_enc, jose_jwk_kem_dec)
  • Register the AKP (Algorithm Key Pair) key type with alg, pub, and priv fields
  • Implement ML-KEM-512, ML-KEM-768, and ML-KEM-1024 via OpenSSL 3.5+ EVP API
  • Add jose jwk encap / jose jwk decap CLI commands
  • Add C and shell tests covering roundtrip, implicit rejection, cross-key rejection, and error cases
  • Fix pre-existing bugs in jws, aesgcm, jwk, and io

- jws: fix algorithm mismatch check using strcmp < 0 instead of != 0,
  which only rejected mismatches where halg sorted before kalg
- misc: use OPENSSL_cleanse() in zero() instead of bare memset(),
  which compilers may optimize away, leaving key material in memory
- aesgcm: fix AAD feed using protected header length (prtl) instead
  of actual AAD length (aadl), causing truncated or over-read AAD
- jwk: add missing check for private exponent decode failure in RSA
  key import, preventing silent loss of private key component
- io: add integer overflow check in malloc_feed() before computing
  the new buffer size

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Post-quantum KEMs (like ML-KEM / FIPS 203) use an asymmetric
encapsulate/decapsulate model that does not fit the existing
JOSE_HOOK_ALG_KIND_EXCH interface, which assumes both sides
can independently compute the same shared secret.

Add JOSE_HOOK_ALG_KIND_KEM with enc (encapsulate) and dec
(decapsulate) callbacks. Add the corresponding public API
functions jose_jwk_kem_enc() and jose_jwk_kem_dec(), export
them in the linker map, and register "kem" in the CLI algorithm
kind table.

No algorithm implementations are registered yet; that follows
in a subsequent commit with the ML-KEM OpenSSL integration.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Register the AKP (Algorithm Key Pair) key type per
the IETF PQC KEM specification (draft-06 as of now), with required fields "alg" and "pub",
public field "pub", and private field "priv". This key type is
shared across post-quantum algorithms (ML-KEM, ML-DSA) and uses
the mandatory "alg" field to distinguish between them.

Add a JOSE_HOOK_ALG_KIND_KEM case to the key_ops auto-assignment
switch in jose_jwk_gen(), setting "deriveKey" for generated KEM
keys.

Add an mlkem meson build option (auto/enabled/disabled) for
future ML-KEM conditional compilation.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add ML-KEM (FIPS 203) support using OpenSSL 3.5+ EVP API. The
entire file is guarded by OPENSSL_VERSION_NUMBER >= 0x30500000L
with a runtime probe that skips registration if ML-KEM is not
available in the OpenSSL provider.

Registers PREP, MAKE, and KEM hooks for ML-KEM-512, ML-KEM-768,
and ML-KEM-1024. Key material uses the AKP key type (per
the IETF PQC KEM specification (draft-06 as of now)) with base64url-encoded pub/priv
fields. The MAKE hook checks both kty and alg to coexist with
future AKP-based algorithms (ML-DSA).

All key material buffers are heap-allocated (ML-KEM keys exceed
the KEYMAX constant) and scrubbed with OPENSSL_cleanse before
free.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add CLI commands using FIPS 203 terminology to avoid collision
with jose jwe enc/dec:

- jose jwk encap -i pub.jwk: encapsulate against a public AKP
  key, output JSON with ciphertext ("ct") and shared secret
  ("ss" as an oct JWK)
- jose jwk decap -i prv.jwk -c <ct>: decapsulate using a private
  AKP key and base64url ciphertext, output shared secret as an
  oct JWK

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
C test (alg_kem.c) exercises all registered KEM algorithms:
- Roundtrip: encap with public key, decap with private key,
  verify shared secrets match
- Implicit rejection: corrupt ciphertext produces a different
  shared secret (not an error), per ML-KEM design
- Cross-key rejection: decap with wrong private key produces
  a different shared secret
- Public-only decap rejection: decap with a public-only key
  is rejected with a clear error
- key_ops validation: generated keys have ["deriveKey"]
- Thumbprint: SHA-256 thumbprint works for AKP keys

Shell tests (jose-jwk-encap, jose-jwk-decap) exercise the CLI
commands across all three ML-KEM variants, including error cases.
Both skip gracefully when ML-KEM is unavailable (OpenSSL < 3.5).

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
@sergio-correia sergio-correia requested a review from simo5 July 14, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant