jwk-simple 0.3.0

JWK/JWKS for Rust and WASM (RFC 7517, 7518, 7638, 8037, 9864) with jwt-simple integration
Documentation
# jwk-simple

[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/sagikazarmark/jwk-simple/ci.yaml?style=flat-square)](https://github.com/sagikazarmark/jwk-simple/actions/workflows/ci.yaml)
[![Crates.io](https://img.shields.io/crates/v/jwk-simple.svg?style=flat-square)](https://crates.io/crates/jwk-simple)
![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/sagikazarmark/jwk-simple/badge?style=flat-square)

A Rust library for working with JSON Web Keys (JWK) and JWK Sets (JWKS) as defined in RFC 7517, with WASM compatibility and optional [`jwt-simple`](https://github.com/jedisct1/rust-jwt-simple) integration.

## Features

- **RFC coverage (JOSE/JWK)**: Supports RFC 7517 (JWK), RFC 7518 (algorithms), RFC 8037 (OKP), RFC 9864 (Ed25519/Ed448 JOSE algorithms), and RFC 7638 (thumbprints)
- **Multiple key types**: RSA, EC (P-256, P-384, P-521, secp256k1), Symmetric (HMAC), and OKP (Ed25519, Ed448, X25519, X448)
- **WASM and WebCrypto compatible**: Core functionality works in WebAssembly environments, with optional WebCrypto integration on `wasm32`
- **Security-first**: Zeroize support for sensitive data, constant-time base64 encoding
- **jwt-simple integration**: Optional feature for converting JWKs to [`jwt-simple`]https://github.com/jedisct1/rust-jwt-simple key types
- **Remote fetching**: Load JWKS from HTTP endpoints with caching support
- **Caching**: Optional TTL-based caching of fetched JWKS (`KeySet`) data

## Quick Start

Add to your `Cargo.toml`:

```toml
[dependencies]
jwk-simple = "0.1"
serde_json = "1"
```

Parse a JWKS and strictly select a verification key:

```rust
use jwk_simple::{Algorithm, KeyMatcher, KeyOperation, KeySet};

let json = r#"{
    "keys": [{
        "kty": "RSA",
        "kid": "my-key-id",
        "use": "sig",
        "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
        "e": "AQAB"
    }]
}"#;

let jwks: KeySet = serde_json::from_str(json)?;
let key = jwks
    .selector(&[Algorithm::Rs256])
    .select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("my-key-id"))?;
assert!(key.is_public_key_only());
```

Note: JWKS parsing is permissive and may skip invalid keys.

## Feature Flags

Feature definitions live in `Cargo.toml` (`[features]`). This table documents
expected usage and platform constraints.

| Feature | Default | Description |
|---------|---------|-------------|
| `jwt-simple` || Integration with the jwt-simple crate (all targets; requires a `jwt-simple` backend feature such as `jwt-simple/pure-rust`) |
| `http` || Async HTTP fetching (reqwest, all targets) |
| `web-crypto` || WebCrypto API integration (`wasm32` only) |
| `cloudflare` || Cloudflare Workers support (KV cache + fetch, `wasm32` only) |
| `moka` || In-memory TTL-based key caching (non-`wasm32` only) |

Invalid platform/feature combinations intentionally fail at compile time with
clear `compile_error!` messages.

## Usage Examples

Note: snippets below are focused examples and may omit surrounding async/runtime scaffolding and input setup.

### Basic JWKS Parsing

```rust
use jwk_simple::{KeyFilter, KeySet, KeyType, KeyUse};

// Parse from JSON string
let jwks: KeySet = serde_json::from_str(json)?;

// Find keys by various criteria
let key = jwks.get_by_kid("key-id");
let rsa_keys = jwks.find(KeyFilter::for_kty(KeyType::Rsa));
let signing_keys = jwks.find(KeyFilter::for_use(KeyUse::Signature));

// Get the first signing key (common pattern)
let first_signing = jwks.first_signing_key();

// Iterate over all keys
for key in &jwks {
    println!("Key: {:?}", key.kid());
}
```

### Converting to jwt-simple Keys

With the `jwt-simple` feature enabled:

Note: `jwt-simple` is not re-exported. Add it to your dependencies when using its key types directly, and enable a backend feature (for example `pure-rust`):

```toml
[dependencies]
jwk-simple = { version = "0.1", features = ["jwt-simple"] }
jwt-simple = { version = "0.12", default-features = false, features = ["pure-rust"] }
```

```rust
use jwk_simple::{Algorithm, KeyMatcher, KeyOperation, KeySet};
use jwt_simple::prelude::*;

let jwks: KeySet = serde_json::from_str(json)?;
let jwk = jwks
    .selector(&[Algorithm::Rs256])
    .select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("my-key"))?;

// Convert to jwt-simple key type using TryFrom/TryInto
let key: RS256PublicKey = jwk.try_into()?;

// Verify a JWT
let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
```

### Fetching from HTTP

With the `http` feature enabled:

```rust
use jwk_simple::jwks::{HttpKeyStore, KeyStore};
use std::time::Duration;

// Create remote key store.
// Native targets use a default 30s timeout.
// On wasm32, reqwest uses the browser/Fetch backend where
// client-level timeout configuration is not available.
let remote = HttpKeyStore::new("https://example.com/.well-known/jwks.json")?;

// For custom timeout, use a custom client.
// Note: reqwest is not re-exported, so add it to your own Cargo.toml:
//   reqwest = "0.13"
let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(10))
    .build()?;
let remote = HttpKeyStore::new_with_client(
    "https://example.com/.well-known/jwks.json",
    client,
)?;

// Fetch the JWKS
let jwks = remote.get_keyset().await?;
```

### Caching Keys

With the `http` and `moka` features enabled:

```rust
use jwk_simple::jwks::{CachedKeyStore, HttpKeyStore, KeyCache, KeyStore, MokaKeyCache};
use std::time::Duration;

// For production, wrap a remote store with caching (5 minute TTL)
let cache = MokaKeyCache::new(Duration::from_secs(300));
let cached = CachedKeyStore::new(
    cache,
    HttpKeyStore::new("https://example.com/.well-known/jwks.json")?,
);

// Keys are automatically cached on first access
let key = cached.get_key("key-id").await?;

// Invalidate the entire cache when needed
cached.cache().clear().await?;
```

### JWK Thumbprints (RFC 7638)

```rust
use jwk_simple::KeySet;

let jwks: KeySet = serde_json::from_str(json)?;
let key = jwks.first().unwrap();

// Calculate thumbprint (base64url-encoded SHA-256)
let thumbprint = key.thumbprint();

// Find key by thumbprint
let key = jwks.get_by_thumbprint(&thumbprint);
```

## Supported Key Types

### RSA (kty: "RSA")
- Public keys: n, e
- Private keys: n, e, d, p, q, dp, dq, qi
- Algorithms: RS256, RS384, RS512, PS256, PS384, PS512

### Elliptic Curve (kty: "EC")
- Curves: P-256, P-384, P-521, secp256k1
- Public keys: crv, x, y
- Private keys: crv, x, y, d
- Algorithms: ES256, ES384, ES512, ES256K

### Symmetric (kty: "oct")
- Keys: k
- Algorithms: HS256, HS384, HS512, A128KW, A192KW, A256KW

### Octet Key Pair (kty: "OKP")
- Curves: Ed25519, Ed448, X25519, X448
- Public keys: crv, x
- Private keys: crv, x, d
- Algorithms: Ed25519, Ed448 (preferred), EdDSA (legacy/deprecated)

## Comparison to Other Libraries

Status below is based on current crates.io releases as of 2026-03.

| Feature | jwk-simple | jwks-client | jsonwebkey | jwt-simple | jsonwebtoken |
|---------|-------------|-------------|------------|------------|--------------|
| Full JWKS spec ||||||
| RSA keys ||||||
| EC keys (P-256/384/521) ||| ⚠️ | ⚠️ | ⚠️ |
| EdDSA (Ed25519) ||||||
| Symmetric keys ||||||
| OKP keys (X25519) ||||||
| WASM support ||| ⚠️ |||
| jwt-simple integration |||| N/A ||
| HTTP fetching ||||||
| Caching ||||||
| Zeroize support ||||||
| Panic-free APIs* ||||||
| JWK thumbprint (RFC 7638) ||| ⚠️ |||
| TryFrom/TryInto traits ||||| ⚠️ |
| Private key support ||||||

Legend for partial support (`⚠️`):
- `jsonwebkey` EC = P-256 only; thumbprints require the `thumbprint` feature
- `jwt-simple` EC = P-256/P-384/secp256k1 (not P-521)
- `jsonwebtoken` EC = P-256/P-384 (P-521 JWK exists but is not supported by the crypto backend)
- `jsonwebkey` WASM = core parsing works; behavior can depend on enabled optional features
- `jsonwebtoken` WASM = works on `wasm32` with `rust_crypto` (or a custom `CryptoProvider`)
- `jsonwebtoken` TryFrom/TryInto = `TryFrom<&Jwk>` is available for `DecodingKey`

*"Panic-free APIs" excludes standard panic-prone trait semantics such as out-of-bounds indexing via `Index`.

### When to use jwk-simple

- **You need jwt-simple integration** - Direct conversion to jwt-simple key types
- **You're building Cloudflare Workers** - Native KV cache support
- **You need WASM support** - Core parsing works in browser
- **You want broad JOSE/JWK RFC coverage** - All key types including OKP

### When to use alternatives

- **jwks-client** - If you only need RSA and want mature async HTTP
- **jsonwebkey** - If you need key generation and PEM/DER conversion
- **jwt-simple** - If you only need JWT operations, not JWKS
- **jsonwebtoken** - If you want the most widely-used JWT library

## Security Considerations

This crate prioritizes security:

- **Zeroize**: Private key parameters are zeroed from memory on drop via the `zeroize` crate
- **Constant-time base64**: Base64 encoding uses constant-time operations via `base64ct`
- **Debug redaction**: Debug output redacts sensitive key material
- **Panic-free APIs**: All fallible operations return `Result` types — standard trait implementations like `Index` follow normal Rust semantics and may panic on invalid input (e.g., out-of-bounds indexing)
- **Input validation**: Key parameters are validated for correct sizes
- **Trust boundary clarity**: `Key::validate` performs structural and metadata consistency checks only, while `Key::validate_for_use` adds algorithm suitability and operation intent checks; PKIX trust validation (chain path, trust anchors, validity, EKU/KU, revocation) must be provided by your application

When enabling the optional `jwt-simple` integration, note that some `jwt-simple`
dependency chains may pull in `rsa` versions affected by
[`RUSTSEC-2023-0071`](https://rustsec.org/advisories/RUSTSEC-2023-0071.html)
(Marvin timing side-channel). This primarily concerns RSA private-key
operations (for example, signing) in attacker-observable timing contexts. If
this matters for your deployment, prefer EdDSA/ECDSA or avoid RSA private-key
operations until an upstream patched chain is available.

## WASM Usage

Core JWKS parsing works in WebAssembly environments.

- `http` is available on `wasm32` via reqwest's Fetch backend
- `web-crypto` and `cloudflare` are `wasm32`-only features
- `moka` is not available on `wasm32`

Example for WASM:

```rust
use jwk_simple::KeySet;

// In WASM, fetch JWKS via browser APIs, then parse
let jwks: KeySet = serde_json::from_str(&json_string)?;
let key = jwks.get_by_kid("key-id").expect("key not found");
```

## License

The project is licensed under the [MIT License](LICENSE).