feat: add experimental ML-DSA (FIPS 204 / RFC 9964) signing Add support for the post-quantum ML-DSA signature algorithms (ML-DSA-44, ML-DSA-65, ML-DSA-87) registered for JOSE by RFC 9964. ML-DSA keys use the new "AKP" (Algorithm Key Pair) JWK key type with a "pub" member and a "priv" member holding the 32-byte FIPS-204 seed; "alg" is required on every AKP key. The feature is experimental and off by default: build with -DWITH_ML_DSA=ON. It is only enabled when a backend that implements ML-DSA is present (OpenSSL >= 3.5 today), and the public header then defines LIBJWT_HAVE_ML_DSA so downstream code can detect it. The JWT_ALG_ML_DSA_* and JWK_KEY_TYPE_AKP enum values are always present (ABI), but all behavior is gated on the macro. - OpenSSL: real sign/verify (one-shot, like EdDSA), AKP JWK parse via the seed, and native-key -> JWK export. A seedless private key (one with no retained FIPS-204 seed) downgrades to a clean public export rather than a private JWK with no private material. - GnuTLS / MbedTLS: cleanly reject AKP keys at parse time via a null-guard on jwt_ops->process_mldsa (a real GnuTLS implementation is tracked in #301). - Anti algorithm-confusion: ML-DSA maps to JWK_KEY_TYPE_AKP only, and the variant is pinned to the key, so a 44 key cannot be used for an 87 token. Tests cover sign/verify and the emitted header alg for all three variants, AKP parse-error branches, the non-OpenSSL reject path, native PEM/DER -> JWK export (including the seedless downgrade), public-export priv-stripping, and the CLI tools, under both Jansson and json-c. closes #227 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Ben Collins <bcollins@libjwt.io>
| Standard | RFC | Description |
|---|---|---|
JWS | :page_facing_up: RFC-7515 | JSON Web Signature |
JWE | :page_facing_up: RFC-7516 | JSON Web Encryption |
JWK | :page_facing_up: RFC-7517 | JSON Web Keys and Sets |
JWA | :page_facing_up: RFC-7518 | JSON Web Algorithms |
JWT | :page_facing_up: RFC-7519 | JSON Web Token |
[!NOTE] Throughout this documentation you will see links such as the ones above to RFC documents. These are relevant to that particular part of the library and are helpful to understand some of the specific standards that shaped the development of LibJWT.
-DWITH_JSON_C=ON). The two are interchangeable.[!NOTE] At least one crypto backend is required, but any non-empty combination works. OpenSSL is enabled by default and can be disabled with
-DWITH_OPENSSL=OFF. Each backend parses and converts JWK(S) natively.
JWS Algorithm alg | OpenSSL | GnuTLS | MbedTLS |
|---|---|---|---|
HS256 HS384 HS512 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
ES256 ES384 ES512 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
RS256 RS384 RS512 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
EdDSA using ED25519 | :white_check_mark: | :white_check_mark: | :x: |
EdDSA using ED448 | :white_check_mark: | :white_check_mark: | :x: |
PS256 PS384 PS512 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
ES256K | :white_check_mark: | :x: | :white_check_mark: |
ML-DSA-44/65/87 [^mldsa] | :white_check_mark: | :x: | :x: |
[^mldsa]: ML-DSA (FIPS 204, registered for JOSE by RFC 9964) is experimental and off by default. Build with -DWITH_ML_DSA=ON; it is only enabled when a backend with ML-DSA support is present (OpenSSL >= 3.5 today). When built in, the public header defines LIBJWT_HAVE_ML_DSA. ML-DSA keys use the "AKP" key type with a "pub" member and a "priv" member holding the 32-byte FIPS-204 seed. GnuTLS and MbedTLS support is not yet implemented.
LibJWT supports JWE (RFC 7516) in both the Compact Serialization and the JSON Serialization (the Flattened form and the General form with one or more recipients). A JWE uses two algorithms: a key management algorithm (alg) and a content encryption algorithm (enc).
| JWE serialization | Recipients | Supported |
|---|---|---|
| Compact (RFC 7516 §7.1) | one | :white_check_mark: |
| JSON Flattened (RFC 7516 §7.2.2) | one | :white_check_mark: |
| JSON General (RFC 7516 §7.2.1) | one or more | :white_check_mark: |
With the JSON serializations the plaintext is encrypted once with a single CEK; each recipient wraps that CEK independently, so any recipient's key can decrypt the token. They also carry an optional shared unprotected header, per-recipient headers, and an application AAD member.
Legend: :white_check_mark: native implementation · :x: not supported
JWE key management alg | OpenSSL | GnuTLS | MbedTLS |
|---|---|---|---|
dir (Direct Encryption) | :white_check_mark: | :white_check_mark: | :white_check_mark: |
A128KW A192KW A256KW | :white_check_mark: | :white_check_mark: | :white_check_mark: |
RSA-OAEP (SHA-1) | :white_check_mark: | :x: | :white_check_mark: |
RSA-OAEP-256 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
ECDH-ES (+ +A128KW/+A192KW/+A256KW) | :white_check_mark: | :white_check_mark: | :white_check_mark: |
JWE content encryption enc | OpenSSL | GnuTLS | MbedTLS |
|---|---|---|---|
A128GCM A192GCM A256GCM | :white_check_mark: | :white_check_mark: | :white_check_mark: |
A128CBC-HS256 A192CBC-HS384 A256CBC-HS512 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
[!NOTE]
ECDH-ESsupports both Direct Key Agreement and the+A*KWkey wrapping modes, on the EC curves P-256/384/521 and the OKP curves X25519/X448, with optionalapu/apvPartyInfo.RSA1_5andzip(compression) are intentionally not supported. Each backend implements JWE natively. GnuTLS/Nettle cannot perform RSA-OAEP with SHA-1, so the GnuTLS backend does not support plainRSA-OAEP(RSA-OAEP-256is native).
:link: Current Docs
:link: Legacy Docs v2.1.1
:link: GitHub Repo
LibJWT is available in most Linux distributions as well as through Homebrew for Linux, macOS, and Windows.
$ mkdir build $ cd build $ cmake .. $ make