mbedtls: migrate the native backend to the PSA Crypto API (closes #267)

MbedTLS 4.x (TF-PSA-Crypto) split the crypto core out, made the classic
low-level API (mbedtls_rsa_*, mbedtls_ecp_*, mbedtls_gcm_*, ...) private
under mbedtls/private/, removed the low-level mbedtls_ecdh_* primitives
entirely, and reworked even some public APIs (mbedtls_md HMAC, nist_kw)
toward PSA. Reaching into the private headers would just re-break on the
next release, so move the whole native backend onto the public PSA Crypto
API (psa/crypto.h). PSA is also stable in MbedTLS 3.6 LTS, so this is a
single code path for both lines with no version #ifdefs; the CMake floor
stays mbedcrypto >= 3.6.0.

Key model: a parsed JWK now holds PSA-importable material (RSA PKCS#1 DER
built via the public asn1write.h; EC/OKP-X curve family + raw point/scalar)
rather than a native key object. Each operation imports a short-lived
volatile PSA key with exactly the policy it needs, then destroys it. This
sidesteps PSA's single-policy-per-key model and keeps RSA parse-time
leniency.

  - sign/verify: psa_sign_message/psa_verify_message (RSA PSS/PKCS1v15,
    ECDSA); HMAC via PSA MAC. Strict signature-size checks preserved.
  - JWE: GCM via PSA AEAD, CBC via PSA cipher + truncated-MAC HMAC,
    RSA-OAEP via PSA, RNG via psa_generate_random, ECDH via
    psa_raw_key_agreement, and RFC 3394 AES-KW hand-rolled on PSA AES-ECB
    (nist_kw changed shape in 4.x). EdDSA stays unsupported.
  - EC keys are validated at parse via a throwaway PSA import; item->bits
    is computed at parse.

Two PSA constraints handled: psa_generate_key cannot produce Montgomery
pairs (X25519/X448 ephemerals generate a random scalar and import it), and
secp256k1 is rejected for ECDH-ES (PSA would otherwise allow it).

PSA caps RSA at PSA_VENDOR_RSA_MAX_KEY_BITS (4096 by default), which the
classic API did not. Oversize RSA keys are now rejected at parse with a
clear message; the keyring and flip-flop tests skip the 8192-bit key on
the MbedTLS backend, mirroring the existing EdDSA skip.

Verified against MbedTLS 4.1: full suite passes (mbedtls-only and the
OpenSSL+GnuTLS+MbedTLS+libcurl combo) with no new uncovered lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Ben Collins <bcollins@libjwt.io>
6 files changed
tree: b78c9fc8a26a07058c61e428690e8445ee94fbc2
  1. .github/
  2. cmake/
  3. doxygen/
  4. images/
  5. include/
  6. libjwt/
  7. tests/
  8. tools/
  9. .gitignore
  10. CLAUDE.md
  11. CMakeLists.txt
  12. codecov.yml
  13. LICENSE
  14. README.md
  15. SECURITY.md
README.md

LibJWT - The C JWT Library

codecov

maClara

:bulb: Supported Standards

StandardRFCDescription
JWS:page_facing_up: RFC-7515JSON Web Signature
JWE:page_facing_up: RFC-7516JSON Web Encryption
JWK:page_facing_up: RFC-7517JSON Web Keys and Sets
JWA:page_facing_up: RFC-7518JSON Web Algorithms
JWT:page_facing_up: RFC-7519JSON 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.

:construction: Build Prerequisites

Required

Crypto support

  • OpenSSL (>= 3.0.0)
  • GnuTLS (>= 3.6.0)
  • MbedTLS (>= 3.6.0)

[!NOTE] OpenSSL is required and used for JWK(S) operations.

Algorithm support matrix

JWS Algorithm algOpenSSLGnuTLSMbedTLS
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: >= 3.8.8:x:
PS256 PS384 PS512:white_check_mark::white_check_mark::white_check_mark:
ES256K:white_check_mark::x::white_check_mark:

JWE

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 serializationRecipientsSupported
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  ·  :large_blue_circle: supported, using OpenSSL as a fallback  ·  :x: not supported

JWE key management algOpenSSLGnuTLSMbedTLS
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::large_blue_circle::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 encOpenSSLGnuTLSMbedTLS
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-ES supports both Direct Key Agreement and the +A*KW key wrapping modes, on the EC curves P-256/384/521 and the OKP curves X25519/X448, with optional apu/apv PartyInfo. RSA1_5 and zip (compression) are intentionally not supported. Each backend implements JWE natively, with one exception: GnuTLS/Nettle cannot perform RSA-OAEP with SHA-1, so plain RSA-OAEP falls back to OpenSSL under the GnuTLS backend (RSA-OAEP-256 is native).

Optional

:books: Docs and Source

:link: Current Docs

:link: Legacy Docs v2.1.1

:link: GitHub Repo

:package: Pre-built Packages

LibJWT is available in most Linux distributions as well as through Homebrew for Linux, macOS, and Windows.

:hammer: Build Instructions

With CMake:

$ mkdir build
$ cd build
$ cmake ..
$ make