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 jose
Make 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.
alg)| 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 |
alg)| 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 | No |
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 |
enc)| 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 |
kty)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 --check
Expect 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.
OCaml
87.4%
Python
8.3%
Standard ML
3.0%
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 jose
Make 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.
alg)| 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 |
alg)| 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 | No |
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 |
enc)| 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 |
kty)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 --check
Expect 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.
OCaml
87.4%
Python
8.3%
Standard ML
3.0%