JOSE implementation in OCaml.
This package aims to implement the JOSE specification. The main usecase for JOSE is probably JWT signing and verification via JWKs.
Install jose using opam:
opam install joseMake sure to initialize the random number generator (required by mirage-crypto) before performing cryptographic operations:
let () = Mirage_crypto_rng_unix.use_default ()Sign and verify arbitrary payload with a key (symmetric oct or asymmetric RSA, EC, OKP):
(* 1. Create or load a key *)
let jwk = Jose.Jwk.make_oct "a-secret-key-that-is-at-least-32-bytes"
(* 2. Sign a payload *)
let jws = Jose.Jws.sign ~payload:"Hello, JWS!" jwk |> Result.get_ok
(* 3. Serialize to compact representation ("<header>.<payload>.<signature>") *)
let token = Jose.Jws.to_string jws
(* 4. Parse and validate signature *)
let parsed_jws = Jose.Jws.of_string token |> Result.get_ok
let validated_jws = Jose.Jws.validate ~jwk parsed_jws |> Result.get_ok
let payload = validated_jws.payload (* "Hello, JWS!" *)Encrypt and decrypt plaintext payloads:
(* 1. Create or load an encryption key *)
let jwk = Jose.Jwk.make_oct ~use:`Enc "a-secret-key-that-is-at-least-32-bytes"
(* 2. Create a JWE header with key management and content encryption algorithms *)
let header = Jose.Header.make_header ~alg:`Dir ~enc:`A256GCM jwk
(* 3. Encrypt the plaintext into compact representation *)
let jwe = Jose.Jwe.make ~header "Secret payload data" |> Result.get_ok
let encrypted_token = Jose.Jwe.encrypt ~jwk jwe |> Result.get_ok
(* 4. Decrypt using the key *)
let decrypted_jwe = Jose.Jwe.decrypt ~jwk encrypted_token |> Result.get_ok
let payload = decrypted_jwe.payload (* "Secret payload data" *)Create, sign, and validate tokens with claim and expiration verification:
(* 1. Create or load a key *)
let jwk = Jose.Jwk.make_oct "a-secret-key-that-is-at-least-32-bytes"
(* 2. Build payload with claims *)
let now = Ptime.of_float_s (Unix.time ()) |> Option.get
let exp =
Ptime.add_span now (Ptime.Span.v (0, 3600L * 1_000_000_000_000L))
|> Option.get
let payload =
Jose.Jwt.empty_payload
|> Jose.Jwt.add_claim "sub" (`String "user_123")
|> Jose.Jwt.add_claim "exp" (`Int (Ptime.to_span exp |> Ptime.Span.to_int_s |> Option.get))
(* 3. Sign the token *)
let jwt = Jose.Jwt.sign ~payload jwk |> Result.get_ok
let token_string = Jose.Jwt.to_string jwt
(* 4. Parse and validate signature and expiration *)
let validated_jwt = Jose.Jwt.of_string ~jwk ~now token_string |> Result.get_ok
let user_id = Jose.Jwt.get_string_claim validated_jwt "sub" (* Some "user_123" *)The compatibility tables below are automatically extracted from the codebase using scripts/extract_compatibility.py.
| Algorithm | Description | Requirement | RFC Reference | Supported |
|---|---|---|---|---|
HS256 |
HMAC using SHA-256 | Required | RFC 7518 §3.1 | Yes |
HS384 |
HMAC using SHA-384 | Optional | RFC 7518 §3.1 | No |
HS512 |
HMAC using SHA-512 | Optional | RFC 7518 §3.1 | No |
RS256 |
RSASSA-PKCS1-v1_5 using SHA-256 | Recommended | RFC 7518 §3.1 | Yes |
RS384 |
RSASSA-PKCS1-v1_5 using SHA-384 | Optional | RFC 7518 §3.1 | No |
RS512 |
RSASSA-PKCS1-v1_5 using SHA-512 | Optional | RFC 7518 §3.1 | No |
ES256 |
ECDSA using P-256 and SHA-256 | Recommended+ | RFC 7518 §3.1 | Yes |
ES384 |
ECDSA using P-384 and SHA-384 | Optional | RFC 7518 §3.1 | Yes |
ES512 |
ECDSA using P-521 and SHA-512 | Optional | RFC 7518 §3.1 | Yes |
PS256 |
RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | Optional | RFC 7518 §3.1 | No |
PS384 |
RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | Optional | RFC 7518 §3.1 | No |
PS512 |
RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | Optional | RFC 7518 §3.1 | No |
EdDSA |
EdDSA signature algorithm (deprecated by RFC 9864) | Optional | RFC 8037 §3.1 | Yes |
Ed25519 |
Ed25519 signature algorithm | Optional | RFC 9864 §3.1 | Yes |
Ed448 |
Ed448 signature algorithm | Optional | RFC 9864 §3.1 | No |
none |
No digital signature or MAC performed | Optional | RFC 7518 §3.1 | Yes |
| Algorithm | Key Management Algorithm | Requirement | RFC Reference | Supported |
|---|---|---|---|---|
RSA1_5 |
RSAES-PKCS1-v1_5 | Recommended- | RFC 7518 §4.1 | Yes |
RSA-OAEP |
RSAES OAEP using default parameters | Recommended+ | RFC 7518 §4.1 | Yes |
RSA-OAEP-256 |
RSAES OAEP using SHA-256 and MGF1 with SHA-256 | Optional | RFC 7518 §4.1 | No |
A128KW |
AES Key Wrap using 128-bit key | Recommended | RFC 7518 §4.1, RFC 3394 | Yes |
A192KW |
AES Key Wrap using 192-bit key | Optional | RFC 7518 §4.1, RFC 3394 | No |
A256KW |
AES Key Wrap using 256-bit key | Recommended | RFC 7518 §4.1, RFC 3394 | Yes |
dir |
Direct use of a shared symmetric key | Recommended | RFC 7518 §4.1 | Yes |
ECDH-ES |
Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF | Recommended+ | RFC 7518 §4.1, §4.6 | Yes |
ECDH-ES+A128KW |
ECDH-ES using Concat KDF and CEK wrapped with "A128KW" | Recommended | RFC 7518 §4.1, §4.6 | Yes |
ECDH-ES+A192KW |
ECDH-ES using Concat KDF and CEK wrapped with "A192KW" | Optional | RFC 7518 §4.1, §4.6 | No |
ECDH-ES+A256KW |
ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | Recommended | RFC 7518 §4.1, §4.6 | Yes |
A128GCMKW |
Key wrapping with AES GCM using 128-bit key | Optional | RFC 7518 §4.1 | No |
A192GCMKW |
Key wrapping with AES GCM using 192-bit key | Optional | RFC 7518 §4.1 | No |
A256GCMKW |
Key wrapping with AES GCM using 256-bit key | Optional | RFC 7518 §4.1 | No |
PBES2-HS256+A128KW |
PBES2 with HMAC SHA-256 and "A128KW" wrapping | Optional | RFC 7518 §4.1 | No |
PBES2-HS384+A192KW |
PBES2 with HMAC SHA-384 and "A192KW" wrapping | Optional | RFC 7518 §4.1 | No |
PBES2-HS512+A256KW |
PBES2 with HMAC SHA-512 and "A256KW" wrapping | Optional | RFC 7518 §4.1 | No |
| Algorithm | Content Encryption Algorithm | Requirement | RFC Reference | Supported |
|---|---|---|---|---|
A128CBC-HS256 |
AES_128_CBC_HMAC_SHA_256 authenticated encryption | Required | RFC 7518 §5.1, §5.2.3 | Yes |
A192CBC-HS384 |
AES_192_CBC_HMAC_SHA_384 authenticated encryption | Optional | RFC 7518 §5.1, §5.2.4 | No |
A256CBC-HS512 |
AES_256_CBC_HMAC_SHA_512 authenticated encryption | Required | RFC 7518 §5.1, §5.2.5 | Yes |
A128GCM |
AES GCM using 128-bit key | Recommended | RFC 7518 §5.1, §5.3 | Yes |
A192GCM |
AES GCM using 192-bit key | Optional | RFC 7518 §5.1, §5.3 | No |
A256GCM |
AES GCM using 256-bit key | Recommended | RFC 7518 §5.1, §5.3 | Yes |
Key Type (kty) |
Description | Requirement | RFC Reference | Supported |
|---|---|---|---|---|
EC |
Elliptic Curve | Recommended+ | RFC 7518 §6.1 | Yes |
RSA |
RSA | Required | RFC 7518 §6.1 | Yes |
oct |
Octet sequence (used to represent symmetric keys) | Required | RFC 7518 §6.1 | Yes |
OKP |
Octet Key Pair | Optional | RFC 8037 §2 | Yes |
To update or check the compatibility tables:
# Update README.md in-place
python3 scripts/extract_compatibility.py --update-readme
# Check if README.md is in sync (e.g. in CI)
python3 scripts/extract_compatibility.py --checkExpect breaking changes on minor releases but patch should not be breaking.
I want to get feedback on both the API and implementation. Issues and PRs are more than welcome.