jwt_simple/lib.rs
1//! [](https://github.com/jedisct1/rust-jwt-simple/actions)
2//! [](https://docs.rs/jwt-simple/)
3//! [](https://crates.io/crates/jwt-simple)
4//!
5//! <!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
6//!
7//! <!-- code_chunk_output -->
8//!
9//! - [JWT-Simple](#jwt-simple)
10//! - [Usage](#usage)
11//! - [Authentication (symmetric, `HS*` JWT algorithms) example](#authentication-symmetric-hs-jwt-algorithms-example)
12//! - [Keys and tokens creation](#keys-and-tokens-creation)
13//! - [Token verification](#token-verification)
14//! - [Signatures (asymmetric, `RS*`, `PS*`, `ES*`, `EdDSA` and `ML-DSA` algorithms) example](#signatures-asymmetric-rs-ps-es-eddsa-and-ml-dsa-algorithms-example)
15//! - [Key pairs and tokens creation](#key-pairs-and-tokens-creation)
16//! - [ES256](#es256)
17//! - [ES384](#es384)
18//! - [ML-DSA](#ml-dsa)
19//! - [JWE (Encrypted tokens)](#jwe-encrypted-tokens)
20//! - [RSA-OAEP key management](#rsa-oaep-key-management)
21//! - [AES Key Wrap](#aes-key-wrap)
22//! - [ECDH-ES key agreement](#ecdh-es-key-agreement)
23//! - [Advanced usage](#advanced-usage)
24//! - [Custom claims](#custom-claims)
25//! - [Peeking at metadata before verification](#peeking-at-metadata-before-verification)
26//! - [Creating and attaching key identifiers](#creating-and-attaching-key-identifiers)
27//! - [Mitigations against replay attacks](#mitigations-against-replay-attacks)
28//! - [Salted keys](#salted-keys)
29//! - [CWT (CBOR) support](#cwt-cbor-support)
30//! - [Specifying header options](#specifying-header-options)
31//! - [Validating content and signature types](#validating-content-and-signature-types)
32//! - [Working around compilation issues with the `boring` crate](#working-around-compilation-issues-with-the-boring-crate)
33//! - [Usage in Web browsers](#usage-in-web-browsers)
34//! - [Why yet another JWT crate](#why-yet-another-jwt-crate)
35//!
36//! <!-- /code_chunk_output -->
37//!
38//! # JWT-Simple
39//!
40//! A new JWT (JSON Web Tokens) implementation for Rust that focuses on simplicity, while avoiding common JWT security pitfalls.
41//!
42//! `jwt-simple` is unopinionated and supports all commonly deployed authentication and signature algorithms:
43//!
44//! | JWT algorithm name | Description |
45//! | ------------------ | ------------------------------------- |
46//! | `HS256` | HMAC-SHA-256 |
47//! | `HS384` | HMAC-SHA-384 |
48//! | `HS512` | HMAC-SHA-512 |
49//! | `BLAKE2B` | BLAKE2B-256 |
50//! | `RS256` | RSA with PKCS#1v1.5 padding / SHA-256 |
51//! | `RS384` | RSA with PKCS#1v1.5 padding / SHA-384 |
52//! | `RS512` | RSA with PKCS#1v1.5 padding / SHA-512 |
53//! | `PS256` | RSA with PSS padding / SHA-256 |
54//! | `PS384` | RSA with PSS padding / SHA-384 |
55//! | `PS512` | RSA with PSS padding / SHA-512 |
56//! | `ES256` | ECDSA over p256 / SHA-256 |
57//! | `ES384` | ECDSA over p384 / SHA-384 |
58//! | `ES256K` | ECDSA over secp256k1 / SHA-256 |
59//! | `EdDSA` | Ed25519 |
60//! | `ML-DSA-44` | ML-DSA-44 (FIPS 204, post-quantum) |
61//! | `ML-DSA-65` | ML-DSA-65 (FIPS 204, post-quantum) |
62//! | `ML-DSA-87` | ML-DSA-87 (FIPS 204, post-quantum) |
63//!
64//! JWE (JSON Web Encryption) is also supported with the following key management algorithms:
65//!
66//! | JWE algorithm name | Description |
67//! | ------------------ | ------------------------------------------- |
68//! | `RSA-OAEP` | RSA with OAEP using SHA-1 (not recommended) |
69//! | `A256KW` | AES-256 Key Wrap |
70//! | `A128KW` | AES-128 Key Wrap |
71//! | `ECDH-ES+A256KW` | ECDH with AES-256 Key Wrap |
72//! | `ECDH-ES+A128KW` | ECDH with AES-128 Key Wrap |
73//!
74//! Content encryption uses AES-GCM (A256GCM or A128GCM).
75//!
76//! `jwt-simple` can be compiled out of the box to WebAssembly/WASI. It is fully compatible with Fastly _Compute_ service.
77//!
78//! Important: JWT's purpose is to verify that data has been created by a party knowing a secret key. It does not provide any kind of confidentiality: JWT data is simply encoded as Base64, and is not encrypted.
79//!
80//! ## Usage
81//!
82//! `cargo.toml`:
83//!
84//! ```toml
85//! [dependencies]
86//! jwt-simple = "0.13"
87//! ```
88//!
89//! Rust:
90//!
91//! ```rust
92//! use jwt_simple::prelude::*;
93//! ```
94//!
95//! Errors are returned as `jwt_simple::Error` values (alias for the `Error` type of the `anyhow` crate).
96//!
97//! ## Authentication (symmetric, `HS*` JWT algorithms) example
98//!
99//! Authentication schemes use the same key for creating and verifying tokens. In other words, both parties need to ultimately trust each other, or else the verifier could also create arbitrary tokens.
100//!
101//! ### Keys and tokens creation
102//!
103//! Key creation:
104//!
105//! ```rust
106//! use jwt_simple::prelude::*;
107//!
108//! // create a new key for the `HS256` JWT algorithm
109//! let key = HS256Key::generate();
110//! ```
111//!
112//! A key can be exported as bytes with `key.to_bytes()`, and restored with `HS256Key::from_bytes()`.
113//!
114//! Token creation:
115//!
116//! ```rust,ignore
117//! // create claims valid for 2 hours
118//! let claims = Claims::create(Duration::from_hours(2));
119//! let token = key.authenticate(claims)?;
120//! ```
121//!
122//! -> Done!
123//!
124//! ### Token verification
125//!
126//! ```rust,ignore
127//! let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
128//! ```
129//!
130//! -> Done! No additional steps required.
131//!
132//! Key expiration, start time, authentication tags, etc. are automatically verified. The function fails with `JWTError::InvalidAuthenticationTag` if the authentication tag is invalid for the given key.
133//!
134//! The full set of claims can be inspected in the `claims` object if necessary. `NoCustomClaims` means that only the standard set of claims is used by the application, but application-defined claims can also be supported.
135//!
136//! Extra verification steps can optionally be enabled via the `VerificationOptions` structure:
137//!
138//! ```rust,ignore
139//! let mut options = VerificationOptions::default();
140//! // Accept tokens that will only be valid in the future
141//! options.accept_future = true;
142//! // Accept tokens even if they have expired up to 15 minutes after the deadline,
143//! // and/or they will be valid within 15 minutes.
144//! // Note that 15 minutes is the default, since it is very common for clocks to be slightly off.
145//! options.time_tolerance = Some(Duration::from_mins(15));
146//! // Reject tokens if they were issued more than 1 hour ago
147//! options.max_validity = Some(Duration::from_hours(1));
148//! // Reject tokens if they don't include an issuer from that set
149//! options.allowed_issuers = Some(HashSet::from_strings(&["example app"]));
150//!
151//! // see the documentation for the full list of available options
152//!
153//! let claims = key.verify_token::<NoCustomClaims>(&token, Some(options))?;
154//! ```
155//!
156//! Note that `allowed_issuers` and `allowed_audiences` are not strings, but sets of strings (using the `HashSet` type from the Rust standard library), as the application can allow multiple values.
157//!
158//! ## Signatures (asymmetric, `RS*`, `PS*`, `ES*`, `EdDSA` and `ML-DSA` algorithms) example
159//!
160//! A signature requires a key pair: a secret key used to create tokens, and a public key, that can only verify them.
161//!
162//! Always use a signature scheme if both parties do not ultimately trust each other, such as tokens exchanged between clients and API providers.
163//!
164//! ### Key pairs and tokens creation
165//!
166//! Key creation:
167//!
168//! #### ES256
169//!
170//! ```rust
171//! use jwt_simple::prelude::*;
172//!
173//! // create a new key pair for the `ES256` JWT algorithm
174//! let key_pair = ES256KeyPair::generate();
175//!
176//! // a public key can be extracted from a key pair:
177//! let public_key = key_pair.public_key();
178//! ```
179//!
180//! #### ES384
181//!
182//! ```rust
183//! use jwt_simple::prelude::*;
184//!
185//! // create a new key pair for the `ES384` JWT algorithm
186//! let key_pair = ES384KeyPair::generate();
187//!
188//! // a public key can be extracted from a key pair:
189//! let public_key = key_pair.public_key();
190//! ```
191//!
192//! #### ML-DSA
193//!
194//! ML-DSA is a post-quantum signature scheme (FIPS 204). Signing and verification are fast, but public keys and signatures are much larger than their classical counterparts.
195//!
196//! ```rust
197//! use jwt_simple::prelude::*;
198//!
199//! // create a new key pair for the `ML-DSA-44` JWT algorithm
200//! let key_pair = MLDSA44KeyPair::generate();
201//!
202//! // a public key can be extracted from a key pair:
203//! let public_key = key_pair.public_key();
204//! ```
205//!
206//! `MLDSA65KeyPair` and `MLDSA87KeyPair` are also available, for the `ML-DSA-65` and `ML-DSA-87` algorithms. ML-DSA-44 is the recommended choice: its security level is good enough for all practical purposes, and it is much faster than the other variants. A key pair is serialized as its 32-byte seed (`to_bytes()`/`from_bytes()`), and a public key as its raw byte representation.
207//!
208//! Keys can be exported as bytes for later reuse, and imported from bytes or, for RSA, from individual parameters, DER-encoded data or PEM-encoded data.
209//!
210//! RSA key pair creation, using OpenSSL and PEM importation of the secret key:
211//!
212//! ```sh
213//! openssl genrsa -out private.pem 2048
214//! openssl rsa -in private.pem -outform PEM -pubout -out public.pem
215//! ```
216//!
217//! ```rust,ignore
218//! let key_pair = RS384KeyPair::from_pem(private_pem_file_content)?;
219//! let public_key = RS384PublicKey::from_pem(public_pem_file_content)?;
220//! ```
221//!
222//! Token creation and verification work the same way as with `HS*` algorithms, except that tokens are created with a key pair, and verified using the corresponding public key.
223//!
224//! Token creation:
225//!
226//! ```rust,ignore
227//! // create claims valid for 2 hours
228//! let claims = Claims::create(Duration::from_hours(2));
229//! let token = key_pair.sign(claims)?;
230//! ```
231//!
232//! Token verification:
233//!
234//! ```rust,ignore
235//! let claims = public_key.verify_token::<NoCustomClaims>(&token, None)?;
236//! ```
237//!
238//! Available verification options are identical to the ones used with symmetric algorithms.
239//!
240//! ## JWE (Encrypted tokens)
241//!
242//! While JWT signatures provide authenticity (verifying who created the token), JWE provides confidentiality by encrypting the token content. Use JWE when the claims contain sensitive data that should not be visible to intermediaries.
243//!
244//! ### RSA-OAEP key management
245//!
246//! RSA-OAEP uses asymmetric encryption: anyone with the public key can encrypt tokens, but only the private key holder can decrypt them.
247//!
248//! ```rust,ignore
249//! use jwt_simple::prelude::*;
250//!
251//! // Generate a key pair (2048 bits minimum, 4096 recommended for high security)
252//! let decryption_key = RsaOaepDecryptionKey::generate(2048)?;
253//! let encryption_key = decryption_key.encryption_key();
254//!
255//! // Encrypt a token
256//! let claims = Claims::create(Duration::from_hours(1))
257//! .with_subject("user@example.com");
258//! let token = encryption_key.encrypt(claims)?;
259//!
260//! // Decrypt the token
261//! let claims: JWTClaims<NoCustomClaims> = decryption_key.decrypt_token(&token, None)?;
262//! ```
263//!
264//! Keys can be exported and imported using PEM or DER formats, similar to RSA signature keys.
265//!
266//! ### AES Key Wrap
267//!
268//! For symmetric encryption where the same key is used for both encryption and decryption:
269//!
270//! ```rust,ignore
271//! use jwt_simple::prelude::*;
272//!
273//! // Generate a 256-bit key
274//! let key = A256KWKey::generate();
275//!
276//! // Or create from existing bytes
277//! let key = A256KWKey::from_bytes(&raw_key_bytes)?;
278//!
279//! // Encrypt
280//! let claims = Claims::create(Duration::from_hours(1));
281//! let token = key.encrypt(claims)?;
282//!
283//! // Decrypt
284//! let claims: JWTClaims<NoCustomClaims> = key.decrypt_token(&token, None)?;
285//! ```
286//!
287//! `A128KWKey` is also available for 128-bit keys.
288//!
289//! ### ECDH-ES key agreement
290//!
291//! ECDH-ES uses elliptic curve Diffie-Hellman for key agreement. Like RSA-OAEP, it uses asymmetric keys but is more efficient:
292//!
293//! ```rust,ignore
294//! use jwt_simple::prelude::*;
295//!
296//! // Generate a key pair
297//! let decryption_key = EcdhEsA256KWDecryptionKey::generate();
298//! let encryption_key = decryption_key.encryption_key();
299//!
300//! // Encrypt
301//! let claims = Claims::create(Duration::from_hours(1));
302//! let token = encryption_key.encrypt(claims)?;
303//!
304//! // Decrypt
305//! let claims: JWTClaims<NoCustomClaims> = decryption_key.decrypt_token(&token, None)?;
306//! ```
307//!
308//! JWE tokens support the same claim types and custom claims as JWT signatures. Decryption options allow validating claims, requiring specific key IDs, and limiting token size.
309//!
310//! ## Advanced usage
311//!
312//! ### Custom claims
313//!
314//! Claim objects support all the standard claims by default, and they can be set directly or via convenient helpers:
315//!
316//! ```rust,ignore
317//! let claims = Claims::create(Duration::from_hours(2)).
318//! with_issuer("Example issuer").with_subject("Example subject");
319//! ```
320//!
321//! But application-defined claims can also be used. These simply have to be present in a serializable type (this requires the `serde` crate):
322//!
323//! ```rust,ignore
324//! #[derive(Serialize, Deserialize)]
325//! struct MyAdditionalData {
326//! user_is_admin: bool,
327//! user_country: String,
328//! }
329//! let my_additional_data = MyAdditionalData {
330//! user_is_admin: false,
331//! user_country: "FR".to_string(),
332//! };
333//! ```
334//!
335//! Claim creation with custom data:
336//!
337//! ```rust,ignore
338//! let claims = Claims::with_custom_claims(my_additional_data, Duration::from_secs(30));
339//! ```
340//!
341//! Claim verification with custom data. Note the presence of the custom data type:
342//!
343//! ```rust,ignore
344//! let claims = public_key.verify_token::<MyAdditionalData>(&token, None)?;
345//! let user_is_admin = claims.custom.user_is_admin;
346//! ```
347//!
348//! ### Peeking at metadata before verification
349//!
350//! Properties such as the key identifier can be useful prior to tag or signature verification in order to pick the right key out of a set.
351//!
352//! ```rust,ignore
353//! let metadata = Token::decode_metadata(&token)?;
354//! let key_id = metadata.key_id();
355//! let algorithm = metadata.algorithm();
356//! // all other standard properties are also accessible
357//! ```
358//!
359//! **IMPORTANT:** neither the key ID nor the algorithm can be trusted. This is an unfixable design flaw of the JWT standard.
360//!
361//! As a result, `algorithm` should be used only for debugging purposes, and never to select a key type.
362//! Similarly, `key_id` should be used only to select a key in a set of keys made for the same algorithm.
363//!
364//! At the bare minimum, verification using `HS*` must be prohibited if a signature scheme was originally used to create the token.
365//!
366//! ### Creating and attaching key identifiers
367//!
368//! Key identifiers indicate to verifiers what public key (or shared key) should be used for verification.
369//! They can be attached at any time to existing shared keys, key pairs and public keys:
370//!
371//! ```rust,ignore
372//! let public_key_with_id = public_key.with_key_id(&"unique key identifier");
373//! ```
374//!
375//! Instead of delegating this to applications, `jwt-simple` can also create such an identifier for an existing key:
376//!
377//! ```rust,ignore
378//! let key_id = public_key.create_key_id();
379//! ```
380//!
381//! This creates a text-encoded identifier for the key, attaches it, and returns it.
382//!
383//! If an identifier has been attached to a shared key or a key pair, tokens created with them will include it.
384//!
385//! ### Mitigations against replay attacks
386//!
387//! `jwt-simple` includes mechanisms to mitigate replay attacks:
388//!
389//! - Nonces can be created and attached to new tokens using the `create_nonce()` claim function. The verification procedure can later reject any token that doesn't include the expected nonce (`required_nonce` verification option).
390//! - The verification procedure can reject tokens created too long ago, no matter what their expiration date is. This prevents tokens from malicious (or compromised) signers from being used for too long.
391//! - The verification procedure can reject tokens created before a date. For a given user, the date of the last successful authentication can be stored in a database, and used later along with this option to reject older (replayed) tokens.
392//!
393//! ### Salted keys
394//!
395//! Symmetric keys, such as the ones used with the `HS256`, `HS384`, `HS512` and `BLAKE2B` algorithms, are simple and fast, but have a major downside: signature and verification use the exact same key. Therefore, an adversary having access to the verifier key can forge arbitrary, valid tokens.
396//!
397//! Salted keys mitigate this issue in the following way:
398//!
399//! - A random signer salt is created and attached to the shared key. This salt is meant to be known only by the signer.
400//! - Another salt is computed from the signer salt and is meant to be used for verification.
401//! - The verifier salt is used to verify the signer salt, which is included in tokens in the `salt` JWT header.
402//!
403//! If the verifier has access to tokens, it can forge arbitrary tokens. But given only the verification code and keys, this is impossible. This greatly improves the security of symmetric keys used for verification on 3rd party servers, such as CDNs.
404//!
405//! A salt binds to a key, and can be of any length. The `generate_with_salt()` function generates both a random symmetric key, and a 32-byte salt.
406//!
407//! Example usage:
408//!
409//! ```rust,ignore
410//! // Create a random key and a signer salt
411//! let key = HS256Key::generate_with_salt();
412//! let claims = Claims::create(Duration::from_secs(86400));
413//! let token = key.authenticate(claims).unwrap();
414//! ```
415//!
416//! A salt is a `Salt` enum, because it can be either a salt for signing, or a salt for verification.
417//! It can be saved and restored:
418//!
419//! ```rust,ignore
420//! // Get the salt
421//! let salt = key.salt();
422//! // Attach an existing salt to a key
423//! key.attach_salt(salt)?;
424//! ```
425//!
426//! Given a signer salt, the corresponding verifier salt can be computed:
427//!
428//! ```rust,ignore
429//! // Compute the verifier salt, given a signer salt
430//! let verifier_salt = key.verifier_salt()?;
431//! ```
432//!
433//! The verifier salt doesn't have to be secret, and can even be hard-coded in the verification code.
434//!
435//! Verification:
436//!
437//! ```rust,ignore
438//! let verifier_salt = Salt::Verifier(verifier_salt_bytes);
439//! key.attach_salt(verifier_salt)?;
440//! let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
441//! ```
442//!
443//! ### CWT (CBOR) support
444//!
445//! The development code includes a `cwt` cargo feature that enables experimental parsing and validation of CWT tokens.
446//!
447//! Please note that CWT doesn't support custom claims. The required identifiers [haven't been standardized yet](https://www.iana.org/assignments/cwt/cwt.xhtml).
448//!
449//! Also, the existing Rust crates for JSON and CBOR deserialization are not safe. An untrusted party can send a serialized object that requires a lot of memory and CPU to deserialize. Band-aids have been added for JSON, but with the current Rust tooling, it would be tricky to implement for CBOR.
450//!
451//! As a mitigation, we highly recommend rejecting tokens that would be too large in the context of your application. That can be done with the `max_token_length` verification option.
452//!
453//! ### Specifying header options
454//!
455//! It is possible to change the content type (`cty`) and signature type (`typ`) fields of a signed JWT by using the `sign_with_options`/`authenticate_with_options` functions and passing in a `HeaderOptions` struct:
456//!
457//! ``` rust,ignore
458//! let options = HeaderOptions {
459//! content_type: Some("foo".into()),
460//! signature_type: Some("foo+JWT".into()),
461//! ..Default::default()
462//! };
463//! key_pair.sign_with_options(claims, &options).unwrap();
464//! ```
465//!
466//! By default, generated JWTs will have a signature type field containing the string "JWT", and the content type field will not be present.
467//!
468//! ### Validating content and signature types
469//!
470//! By default, `jwt_simple` ignores the `content_type` field when doing validation, and checks `signature_type` to ensure it is either exactly `JWT` or ends in `+JWT`, case insensitive, if it is present. Both fields may instead be case-insensitively compared against an expected string:
471//!
472//! ```rust,ignore
473//! options.required_signature_type = Some("JWT".into());
474//! options.required_content_type = Some("foo+jwt".into());
475//! ```
476//!
477//! When validating CWTs, note that CWTs do not have a `content_type` field in their header, and therefore attempting to match a specific one by setting `required_content_type` during validation will **always result in an error**.
478//!
479//!
480//! ## Working around compilation issues with the `boring` crate
481//!
482//! As a temporary workaround for portability issues with one of the dependencies (the `boring` crate), this library can be compiled to use only Rust implementations.
483//!
484//! In order to do so, import the crate with `default-features = false, features = ["pure-rust"]` in your Cargo configuration.
485//!
486//! Do not do it unconditionally. This is only required for very specific setups and targets, and only until issues with the `boring` crate have been solved. The way to configure this in Cargo may also change in future versions.
487//!
488//! Static builds targeting the `musl` library don't require that workaround. Just use [`cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild) to build your project.
489//!
490//! ## Usage in Web browsers
491//!
492//! The `wasm32-freestanding` target (still sometimes called `wasm32-unknown-unknown` in Rust) is supported (as in "it compiles").
493//!
494//! However, using a native JavaScript implementation is highly recommended instead. There are high-quality JWT implementations in JavaScript, leveraging the WebCrypto API, that provide better performance and security guarantees than a WebAssembly module.
495//!
496//! ## Why yet another JWT crate
497//!
498//! This crate is not an endorsement of JWT. JWT is [an awful design](https://tools.ietf.org/html/rfc8725), and one of the many examples that "but this is a standard" doesn't necessarily mean that it is good.
499//!
500//! I would highly recommend [PASETO](https://github.com/paragonie/paseto) or [Biscuit](https://github.com/CleverCloud/biscuit) instead if you control both token creation and verification.
501//!
502//! However, JWT is still widely used in the industry, and remains absolutely mandatory to communicate with popular APIs.
503//!
504//! This crate was designed to:
505//!
506//! - Be simple to use, even for people who are new to Rust
507//! - Avoid common JWT API pitfalls
508//! - Support features widely in use. I'd love to limit the algorithm choices to Ed25519, but other methods are required to connect to existing APIs, so we provide them (with the exception of the `None` signature method for obvious reasons).
509//! - Minimize code complexity and external dependencies
510//! - Automatically perform common tasks to prevent misuse. Signature verification and claims validation happen automatically instead of relying on applications.
511//! - Still allow power users to access everything JWT tokens include if they really need to
512//! - Work out of the box in a WebAssembly environment, so that it can be used in function-as-a-service platforms.
513#![forbid(unsafe_code)]
514
515#[cfg(all(feature = "pure-rust", feature = "optimal"))]
516compile_error!("jwt-simple: the `optimal` feature is only available when the `pure-rust` feature is disabled - Consider disabling default Cargo features.");
517
518#[cfg(all(not(feature = "pure-rust"), not(feature = "optimal")))]
519compile_error!("jwt-simple: the `optimal` feature is required when the `pure-rust` feature is disabled - Consider enabling default Cargo features.");
520
521pub mod algorithms;
522pub mod claims;
523pub mod common;
524#[cfg(feature = "cwt")]
525pub mod cwt_token;
526#[cfg(feature = "jwe")]
527pub mod jwe_header;
528#[cfg(feature = "jwe")]
529pub mod jwe_token;
530pub mod token;
531
532mod jwt_header;
533mod serde_additions;
534
535pub mod reexports {
536 pub use anyhow;
537 pub use coarsetime;
538 pub use ct_codecs;
539 pub use rand;
540 pub use serde;
541 pub use serde_json;
542 pub use thiserror;
543 pub use zeroize;
544}
545
546mod error;
547pub use error::{Error, JWTError};
548
549pub mod prelude {
550 pub use std::collections::HashSet;
551
552 pub use coarsetime::{self, Clock, Duration, UnixTimeStamp};
553 pub use ct_codecs::{
554 Base64, Base64NoPadding, Base64UrlSafe, Base64UrlSafeNoPadding, Decoder as _, Encoder as _,
555 };
556 pub use serde::{Deserialize, Serialize};
557
558 pub use crate::algorithms::*;
559 pub use crate::claims::*;
560 pub use crate::common::*;
561 #[cfg(feature = "cwt")]
562 pub use crate::cwt_token::*;
563 #[cfg(feature = "jwe")]
564 pub use crate::jwe_token::{DecryptionOptions, EncryptionOptions, JWEToken, JWETokenMetadata};
565 pub use crate::token::*;
566
567 mod hashset_from_strings {
568 use std::collections::HashSet;
569
570 pub trait HashSetFromStringsT {
571 /// Create a set from a list of strings
572 fn from_strings(strings: &[impl ToString]) -> HashSet<String> {
573 strings.iter().map(|x| x.to_string()).collect()
574 }
575 }
576
577 impl HashSetFromStringsT for HashSet<String> {}
578 }
579
580 pub use hashset_from_strings::HashSetFromStringsT as _;
581}
582
583#[cfg(test)]
584mod tests {
585 use crate::prelude::*;
586
587 const RSA_KP_PEM: &str = r"
588-----BEGIN RSA PRIVATE KEY-----
589MIIEpAIBAAKCAQEAyqq0N5u8Jvl+BLH2VMP/NAv/zY9T8mSq0V2Gk5Ql5H1a+4qi
5903viorUXG3AvIEEccpLsW85ps5+I9itp74jllRjA5HG5smbb+Oym0m2Hovfj6qP/1
591m1drQg8oth6tNmupNqVzlGGWZLsSCBLuMa3pFaPhoxl9lGU3XJIQ1/evMkOb98I3
592hHb4ELn3WGtNlAVkbP20R8sSii/zFjPqrG/NbSPLyAl1ctbG2d8RllQF1uRIqYQj
59385yx73hqQCMpYWU3d9QzpkLf/C35/79qNnSKa3t0cyDKinOY7JGIwh8DWAa4pfEz
594gg56yLcilYSSohXeaQV0nR8+rm9J8GUYXjPK7wIDAQABAoIBAQCpeRPYyHcPFGTH
5954lU9zuQSjtIq/+bP9FRPXWkS8bi6GAVEAUtvLvpGYuoGyidTTVPrgLORo5ncUnjq
596KwebRimlBuBLIR/Zboery5VGthoc+h4JwniMnQ6JIAoIOSDZODA5DSPYeb58n15V
597uBbNHkOiH/eoHsG/nOAtnctN/cXYPenkCfeLXa3se9EzkcmpNGhqCBL/awtLU17P
598Iw7XxsJsRMBOst4Aqiri1GQI8wqjtXWLyfjMpPR8Sqb4UpTDmU1wHhE/w/+2lahC
599Tu0/+sCWj7TlafYkT28+4pAMyMqUT6MjqdmGw8lD7/vXv8TF15NU1cUv3QSKpVGe
60050vlB1QpAoGBAO1BU1evrNvA91q1bliFjxrH3MzkTQAJRMn9PBX29XwxVG7/HlhX
6010tZRSR92ZimT2bAu7tH0Tcl3Bc3NwEQrmqKlIMqiW+1AVYtNjuipIuB7INb/TUM3
602smEh+fn3yhMoVxbbh/klR1FapPUFXlpNv3DJHYM+STqLMhl9tEc/I7bLAoGBANqt
603zR6Kovf2rh7VK/Qyb2w0rLJE7Zh/WI+r9ubCba46sorqkJclE5cocxWuTy8HWyQp
604spxzLP1FQlsI+MESgRLueoH3HtB9lu/pv6/8JlNjU6SzovfUZ0KztVUyUeB4vAcH
605pGcf2CkUtoYc8YL22Ybck3s8ThIdnY5zphCF55PtAoGAf46Go3c05XVKx78R05AD
606D2/y+0mnSGSzUjHPMzPyadIPxhltlCurlERhnwPGC4aNHFcvWTwS8kUGns6HF1+m
607JNnI1okSCW10UI/jTJ1avfwU/OKIBKKWSfi9cDJTt5cRs51V7pKnVEr6sy0uvDhe
608u+G091HuhwY9ak0WNtPwfJ8CgYEAuRdoyZQQso7x/Bj0tiHGW7EOB2n+LRiErj6g
609odspmNIH8zrtHXF9bnEHT++VCDpSs34ztuZpywnHS2SBoHH4HD0MJlszksbqbbDM
6101bk3+1bUIlEF/Hyk1jljn3QTB0tJ4y1dwweaH9NvVn7DENW9cr/aePGnJwA4Lq3G
611fq/IPlUCgYAuqgJQ4ztOq0EaB75xgqtErBM57A/+lMWS9eD/euzCEO5UzWVaiIJ+
612nNDmx/jvSrxA1Ih8TEHjzv4ezLFYpaJrTst4Mjhtx+csXRJU9a2W6HMXJ4Kdn8rk
613PBziuVURslNyLdlFsFlm/kfvX+4Cxrbb+pAGETtRTgmAoCDbvuDGRQ==
614-----END RSA PRIVATE KEY-----
615 ";
616
617 const RSA_PK_PEM: &str = r"
618-----BEGIN PUBLIC KEY-----
619MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyqq0N5u8Jvl+BLH2VMP/
620NAv/zY9T8mSq0V2Gk5Ql5H1a+4qi3viorUXG3AvIEEccpLsW85ps5+I9itp74jll
621RjA5HG5smbb+Oym0m2Hovfj6qP/1m1drQg8oth6tNmupNqVzlGGWZLsSCBLuMa3p
622FaPhoxl9lGU3XJIQ1/evMkOb98I3hHb4ELn3WGtNlAVkbP20R8sSii/zFjPqrG/N
623bSPLyAl1ctbG2d8RllQF1uRIqYQj85yx73hqQCMpYWU3d9QzpkLf/C35/79qNnSK
624a3t0cyDKinOY7JGIwh8DWAa4pfEzgg56yLcilYSSohXeaQV0nR8+rm9J8GUYXjPK
6257wIDAQAB
626-----END PUBLIC KEY-----
627 ";
628
629 #[test]
630 fn hs384() {
631 let key = HS384Key::from_bytes(b"your-256-bit-secret").with_key_id("my-key-id");
632 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
633 let token = key.authenticate(claims).unwrap();
634 let options = VerificationOptions {
635 allowed_issuers: Some(HashSet::from_strings(&["test issuer"])),
636 ..Default::default()
637 };
638 let _claims = key
639 .verify_token::<NoCustomClaims>(&token, Some(options))
640 .unwrap();
641 }
642
643 #[test]
644 fn blake2b() {
645 let key = Blake2bKey::from_bytes(b"your-256-bit-secret").with_key_id("my-key-id");
646 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
647 let token = key.authenticate(claims).unwrap();
648 let options = VerificationOptions {
649 allowed_issuers: Some(HashSet::from_strings(&["test issuer"])),
650 ..Default::default()
651 };
652 let _claims = key
653 .verify_token::<NoCustomClaims>(&token, Some(options))
654 .unwrap();
655 }
656
657 #[test]
658 fn rs256() {
659 let key_pair = RS256KeyPair::from_pem(RSA_KP_PEM).unwrap();
660 let claims = Claims::create(Duration::from_secs(86400));
661 let token = key_pair.sign(claims).unwrap();
662 let pk = RS256PublicKey::from_pem(RSA_PK_PEM).unwrap();
663 let _claims = pk.verify_token::<NoCustomClaims>(&token, None).unwrap();
664 let components = pk.to_components();
665 let hex_e = Base64::encode_to_string(components.e).unwrap();
666 let _e = Base64::decode_to_vec(hex_e, None).unwrap();
667 }
668
669 #[test]
670 fn ps384() {
671 let key_pair = PS384KeyPair::generate(2048).unwrap();
672 let claims = Claims::create(Duration::from_secs(86400));
673 let token = key_pair.sign(claims).unwrap();
674 let _claims = key_pair
675 .public_key()
676 .verify_token::<NoCustomClaims>(&token, None)
677 .unwrap();
678 }
679
680 #[test]
681 fn es256() {
682 let key_pair = ES256KeyPair::generate();
683 let claims = Claims::create(Duration::from_secs(86400));
684 let token = key_pair.sign(claims).unwrap();
685 let _claims = key_pair
686 .public_key()
687 .verify_token::<NoCustomClaims>(&token, None)
688 .unwrap();
689 }
690
691 #[test]
692 fn es384() {
693 let key_pair = ES384KeyPair::generate();
694 let claims = Claims::create(Duration::from_secs(86400));
695 let token = key_pair.sign(claims).unwrap();
696 let _claims = key_pair
697 .public_key()
698 .verify_token::<NoCustomClaims>(&token, None)
699 .unwrap();
700 }
701
702 #[test]
703 fn es256k() {
704 let key_pair = ES256kKeyPair::generate();
705 let claims = Claims::create(Duration::from_secs(86400));
706 let token = key_pair.sign(claims).unwrap();
707 let _claims = key_pair
708 .public_key()
709 .verify_token::<NoCustomClaims>(&token, None)
710 .unwrap();
711 }
712
713 #[test]
714 fn ed25519() {
715 #[derive(Serialize, Deserialize)]
716 struct CustomClaims {
717 is_custom: bool,
718 }
719
720 let key_pair = Ed25519KeyPair::generate();
721 let mut pk = key_pair.public_key();
722 let key_id = pk.create_key_id();
723 let key_pair = key_pair.with_key_id(key_id);
724 let public_key = key_pair.public_key(); // Get public key after setting key_id
725 let custom_claims = CustomClaims { is_custom: true };
726 let claims = Claims::with_custom_claims(custom_claims, Duration::from_secs(86400));
727 let token = key_pair.sign(claims).unwrap();
728 let options = VerificationOptions {
729 required_key_id: Some(key_id.to_string()),
730 ..Default::default()
731 };
732 let claims: JWTClaims<CustomClaims> = public_key
733 .verify_token::<CustomClaims>(&token, Some(options))
734 .unwrap();
735 assert!(claims.custom.is_custom);
736 }
737
738 #[test]
739 fn ed25519_der() {
740 let key_pair = Ed25519KeyPair::generate();
741 let der = key_pair.to_der();
742 let key_pair2 = Ed25519KeyPair::from_der(&der).unwrap();
743 assert_eq!(key_pair.to_bytes(), key_pair2.to_bytes());
744 }
745
746 #[test]
747 fn mldsa65() {
748 #[derive(Serialize, Deserialize)]
749 struct CustomClaims {
750 is_custom: bool,
751 }
752
753 let key_pair = MLDSA65KeyPair::generate();
754 let mut pk = key_pair.public_key();
755 let key_id = pk.create_key_id();
756 let key_pair = key_pair.with_key_id(key_id);
757 let public_key = key_pair.public_key();
758 let custom_claims = CustomClaims { is_custom: true };
759 let claims = Claims::with_custom_claims(custom_claims, Duration::from_secs(86400));
760 let token = key_pair.sign(claims).unwrap();
761 let options = VerificationOptions {
762 required_key_id: Some(key_id.to_string()),
763 ..Default::default()
764 };
765 let claims: JWTClaims<CustomClaims> = public_key
766 .verify_token::<CustomClaims>(&token, Some(options))
767 .unwrap();
768 assert!(claims.custom.is_custom);
769 }
770
771 #[test]
772 fn mldsa_key_round_trip() {
773 let key_pair = MLDSA44KeyPair::generate();
774 let key_pair2 = MLDSA44KeyPair::from_bytes(&key_pair.to_bytes()).unwrap();
775 assert_eq!(key_pair.to_bytes(), key_pair2.to_bytes());
776
777 let public_key = key_pair.public_key();
778 let public_key2 = MLDSA44PublicKey::from_bytes(&public_key.to_bytes()).unwrap();
779 assert_eq!(public_key.to_bytes(), public_key2.to_bytes());
780
781 let claims = Claims::create(Duration::from_secs(86400));
782 let token = key_pair2.sign(claims).unwrap();
783 let _claims = public_key2
784 .verify_token::<NoCustomClaims>(&token, None)
785 .unwrap();
786 }
787
788 #[test]
789 fn mldsa_wrong_key_rejected() {
790 let key_pair = MLDSA87KeyPair::generate();
791 let claims = Claims::create(Duration::from_secs(86400));
792 let token = key_pair.sign(claims).unwrap();
793
794 let other_public_key = MLDSA87KeyPair::generate().public_key();
795 let res = other_public_key.verify_token::<NoCustomClaims>(&token, None);
796 assert!(res.is_err());
797
798 let eddsa_public_key = Ed25519KeyPair::generate().public_key();
799 let res = eddsa_public_key.verify_token::<NoCustomClaims>(&token, None);
800 assert!(res.is_err());
801 }
802
803 #[test]
804 fn require_nonce() {
805 let key = HS256Key::generate();
806 let mut claims = Claims::create(Duration::from_hours(1));
807 let nonce = claims.create_nonce();
808 let token = key.authenticate(claims).unwrap();
809
810 let options = VerificationOptions {
811 required_nonce: Some(nonce),
812 ..Default::default()
813 };
814 key.verify_token::<NoCustomClaims>(&token, Some(options))
815 .unwrap();
816 }
817
818 #[test]
819 fn eddsa_pem() {
820 let sk_pem = "-----BEGIN PRIVATE KEY-----
821MC4CAQAwBQYDK2VwBCIEIMXY1NUbUe/3dW2YUoKW5evsnCJPMfj60/q0RzGne3gg
822-----END PRIVATE KEY-----
823";
824 let pk_pem = "-----BEGIN PUBLIC KEY-----
825MCowBQYDK2VwAyEAyrRjJfTnhMcW5igzYvPirFW5eUgMdKeClGzQhd4qw+Y=
826-----END PUBLIC KEY-----
827";
828 let kp = Ed25519KeyPair::from_pem(sk_pem).unwrap();
829 assert_eq!(kp.public_key().to_pem(), pk_pem);
830 }
831
832 #[test]
833 fn key_metadata() {
834 let mut key_pair = Ed25519KeyPair::generate();
835 let thumbprint = key_pair.public_key().sha1_thumbprint();
836 let key_metadata = KeyMetadata::default()
837 .with_certificate_sha1_thumbprint(&thumbprint)
838 .unwrap();
839 key_pair.attach_metadata(key_metadata).unwrap();
840
841 let claims = Claims::create(Duration::from_secs(86400));
842 let token = key_pair.sign(claims).unwrap();
843
844 let decoded_metadata = Token::decode_metadata(&token).unwrap();
845 assert_eq!(
846 decoded_metadata.certificate_sha1_thumbprint(),
847 Some(thumbprint.as_ref())
848 );
849 let _ = key_pair
850 .public_key()
851 .verify_token::<NoCustomClaims>(&token, None)
852 .unwrap();
853 }
854
855 #[test]
856 fn set_header_content_type() {
857 let key_pair = Ed25519KeyPair::generate();
858 let claims = Claims::create(Duration::from_secs(86400));
859 let token = key_pair
860 .sign_with_options(
861 claims,
862 &HeaderOptions {
863 content_type: Some("foo".into()),
864 ..Default::default()
865 },
866 )
867 .unwrap();
868 let decoded_metadata = Token::decode_metadata(&token).unwrap();
869 assert_eq!(
870 decoded_metadata.jwt_header.content_type.as_deref(),
871 Some("foo")
872 );
873 let _ = key_pair
874 .public_key()
875 .verify_token::<NoCustomClaims>(&token, None)
876 .unwrap();
877 }
878
879 #[test]
880 fn set_header_signature_type() {
881 let key_pair = Ed25519KeyPair::generate();
882 let claims = Claims::create(Duration::from_secs(86400));
883 let token = key_pair
884 .sign_with_options(
885 claims,
886 &HeaderOptions {
887 signature_type: Some("etc+jwt".into()),
888 ..Default::default()
889 },
890 )
891 .unwrap();
892 let decoded_metadata = Token::decode_metadata(&token).unwrap();
893 assert_eq!(
894 decoded_metadata.jwt_header.signature_type.as_deref(),
895 Some("etc+jwt")
896 );
897 let _ = key_pair
898 .public_key()
899 .verify_token::<NoCustomClaims>(&token, None)
900 .unwrap();
901 }
902
903 #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
904 #[test]
905 fn expired_token() {
906 let key = HS256Key::generate();
907 let claims = Claims::create(Duration::from_secs(1));
908 let token = key.authenticate(claims).unwrap();
909 std::thread::sleep(std::time::Duration::from_secs(2));
910 let options = VerificationOptions {
911 time_tolerance: None,
912 ..Default::default()
913 };
914 let claims = key.verify_token::<NoCustomClaims>(&token, None);
915 assert!(claims.is_ok());
916 let claims = key.verify_token::<NoCustomClaims>(&token, Some(options));
917 assert!(claims.is_err());
918 }
919
920 #[test]
921 fn salt() {
922 let mut key = HS256Key::generate_with_salt();
923 let claims = Claims::create(Duration::from_secs(86400));
924 let token = key.authenticate(claims).unwrap();
925
926 let res = key.verify_token::<NoCustomClaims>(&token, None);
927 assert!(res.is_err());
928
929 let verifier_salt = key.verifier_salt().unwrap();
930 key.attach_salt(verifier_salt).unwrap();
931 key.verify_token::<NoCustomClaims>(&token, None).unwrap();
932 }
933
934 #[test]
935 fn salt2() {
936 let mut key = HS256Key::generate();
937 let claims = Claims::create(Duration::from_secs(86400));
938 let token = key.authenticate(claims).unwrap();
939
940 key.verify_token::<NoCustomClaims>(&token, None).unwrap();
941
942 let verifier_salt = Salt::Verifier(b"salt".to_vec());
943 key.attach_salt(verifier_salt).unwrap();
944 let res = key.verify_token::<NoCustomClaims>(&token, None);
945 assert!(res.is_err());
946 }
947
948 #[test]
949 fn weak_key_rejected_on_authenticate() {
950 let key = HS256Key::from_bytes(b"short");
951 let claims = Claims::create(Duration::from_secs(86400));
952 let res = key.authenticate(claims);
953 assert!(res.is_err());
954 assert!(res.unwrap_err().to_string().contains("Weak key"));
955 }
956
957 #[test]
958 fn weak_key_rejected_on_verify() {
959 let valid_key = HS256Key::generate();
960 let claims = Claims::create(Duration::from_secs(86400));
961 let token = valid_key.authenticate(claims).unwrap();
962
963 let weak_key = HS256Key::from_bytes(b"11-bytes..");
964 let res = weak_key.verify_token::<NoCustomClaims>(&token, None);
965 assert!(res.is_err());
966 assert!(res.unwrap_err().to_string().contains("Weak key"));
967 }
968
969 #[test]
970 fn min_key_length_accepted() {
971 let key = HS256Key::from_bytes(b"12-bytes...!");
972 let claims = Claims::create(Duration::from_secs(86400));
973 let token = key.authenticate(claims).unwrap();
974 key.verify_token::<NoCustomClaims>(&token, None).unwrap();
975 }
976
977 #[test]
978 fn weak_key_rejected_hs384() {
979 let key = HS384Key::from_bytes(b"short");
980 let claims = Claims::create(Duration::from_secs(86400));
981 assert!(key.authenticate(claims).is_err());
982 }
983
984 #[test]
985 fn weak_key_rejected_hs512() {
986 let key = HS512Key::from_bytes(b"short");
987 let claims = Claims::create(Duration::from_secs(86400));
988 assert!(key.authenticate(claims).is_err());
989 }
990
991 #[test]
992 fn weak_key_rejected_blake2b() {
993 let key = Blake2bKey::from_bytes(b"short");
994 let claims = Claims::create(Duration::from_secs(86400));
995 assert!(key.authenticate(claims).is_err());
996 }
997
998 #[cfg(feature = "jwe")]
999 #[test]
1000 fn jwe_rsa_oaep() {
1001 let decryption_key = RsaOaepDecryptionKey::generate(2048).unwrap();
1002 let encryption_key = decryption_key.encryption_key();
1003
1004 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
1005 let token = encryption_key.encrypt(claims).unwrap();
1006
1007 let claims: JWTClaims<NoCustomClaims> = decryption_key.decrypt_token(&token, None).unwrap();
1008 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1009 }
1010
1011 #[cfg(feature = "jwe")]
1012 #[test]
1013 fn jwe_a256kw() {
1014 let key = A256KWKey::generate();
1015
1016 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
1017 let token = key.encrypt(claims).unwrap();
1018
1019 let claims: JWTClaims<NoCustomClaims> = key.decrypt_token(&token, None).unwrap();
1020 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1021 }
1022
1023 #[cfg(feature = "jwe")]
1024 #[test]
1025 fn jwe_a256kw_from_bytes() {
1026 let raw_key = [0u8; 32];
1027 let key = A256KWKey::from_bytes(&raw_key).unwrap();
1028
1029 let claims = Claims::create(Duration::from_secs(86400));
1030 let token = key.encrypt(claims).unwrap();
1031
1032 let key2 = A256KWKey::from_bytes(&raw_key).unwrap();
1033 let _claims: JWTClaims<NoCustomClaims> = key2.decrypt_token(&token, None).unwrap();
1034 }
1035
1036 #[cfg(feature = "jwe")]
1037 #[test]
1038 fn jwe_a128kw() {
1039 let key = A128KWKey::generate();
1040
1041 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
1042 let token = key.encrypt(claims).unwrap();
1043
1044 let claims: JWTClaims<NoCustomClaims> = key.decrypt_token(&token, None).unwrap();
1045 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1046 }
1047
1048 #[cfg(feature = "jwe")]
1049 #[test]
1050 fn jwe_a128kw_from_bytes() {
1051 let raw_key = [0u8; 16];
1052 let key = A128KWKey::from_bytes(&raw_key).unwrap();
1053
1054 let claims = Claims::create(Duration::from_secs(86400));
1055 let token = key.encrypt(claims).unwrap();
1056
1057 let key2 = A128KWKey::from_bytes(&raw_key).unwrap();
1058 let _claims: JWTClaims<NoCustomClaims> = key2.decrypt_token(&token, None).unwrap();
1059 }
1060
1061 #[cfg(feature = "jwe")]
1062 #[test]
1063 fn jwe_ecdh_es_a256kw() {
1064 let decryption_key = EcdhEsA256KWDecryptionKey::generate();
1065 let encryption_key = decryption_key.encryption_key();
1066
1067 let claims = Claims::create(Duration::from_secs(86400))
1068 .with_issuer("test issuer")
1069 .with_audience("test audience");
1070 let token = encryption_key.encrypt(claims).unwrap();
1071
1072 let claims: JWTClaims<NoCustomClaims> = decryption_key.decrypt_token(&token, None).unwrap();
1073 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1074 }
1075
1076 #[cfg(feature = "jwe")]
1077 #[test]
1078 fn jwe_ecdh_es_a128kw() {
1079 let decryption_key = EcdhEsA128KWDecryptionKey::generate();
1080 let encryption_key = decryption_key.encryption_key();
1081
1082 let claims = Claims::create(Duration::from_secs(86400)).with_issuer("test issuer");
1083 let token = encryption_key.encrypt(claims).unwrap();
1084
1085 let claims: JWTClaims<NoCustomClaims> = decryption_key.decrypt_token(&token, None).unwrap();
1086 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1087 }
1088
1089 #[cfg(feature = "jwe")]
1090 #[test]
1091 fn jwe_custom_claims() {
1092 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1093 struct CustomClaims {
1094 user_id: u64,
1095 role: String,
1096 }
1097
1098 let key = A256KWKey::generate();
1099
1100 let custom = CustomClaims {
1101 user_id: 12345,
1102 role: "admin".to_string(),
1103 };
1104 let claims = Claims::with_custom_claims(custom, Duration::from_secs(86400))
1105 .with_issuer("test issuer");
1106 let token = key.encrypt(claims).unwrap();
1107
1108 let claims: JWTClaims<CustomClaims> = key.decrypt_token(&token, None).unwrap();
1109 assert_eq!(claims.issuer, Some("test issuer".to_string()));
1110 assert_eq!(claims.custom.user_id, 12345);
1111 assert_eq!(claims.custom.role, "admin");
1112 }
1113
1114 #[cfg(feature = "jwe")]
1115 #[test]
1116 fn jwe_with_content_encryption_a128gcm() {
1117 let key = A256KWKey::generate();
1118
1119 let claims = Claims::create(Duration::from_secs(86400));
1120 let options = EncryptionOptions {
1121 content_encryption: ContentEncryption::A128GCM,
1122 ..Default::default()
1123 };
1124 let token = key.encrypt_with_options(claims, &options).unwrap();
1125
1126 let metadata = A256KWKey::decode_metadata(&token).unwrap();
1127 assert_eq!(metadata.encryption(), "A128GCM");
1128
1129 let _claims: JWTClaims<NoCustomClaims> = key.decrypt_token(&token, None).unwrap();
1130 }
1131
1132 #[cfg(feature = "jwe")]
1133 #[test]
1134 fn jwe_metadata_decode() {
1135 let key = A256KWKey::generate().with_key_id("my-key");
1136
1137 let claims = Claims::create(Duration::from_secs(86400));
1138 let options = EncryptionOptions {
1139 content_type: Some("JWT".to_string()),
1140 ..Default::default()
1141 };
1142 let token = key.encrypt_with_options(claims, &options).unwrap();
1143
1144 let metadata = A256KWKey::decode_metadata(&token).unwrap();
1145 assert_eq!(metadata.algorithm(), "A256KW");
1146 assert_eq!(metadata.encryption(), "A256GCM");
1147 assert_eq!(metadata.key_id(), Some("my-key"));
1148 assert_eq!(metadata.content_type(), Some("JWT"));
1149 }
1150
1151 #[cfg(feature = "jwe")]
1152 #[test]
1153 fn jwe_wrong_key_fails() {
1154 let key1 = A256KWKey::generate();
1155 let key2 = A256KWKey::generate();
1156
1157 let claims = Claims::create(Duration::from_secs(86400));
1158 let token = key1.encrypt(claims).unwrap();
1159
1160 let result: Result<JWTClaims<NoCustomClaims>, _> = key2.decrypt_token(&token, None);
1161 assert!(result.is_err());
1162 }
1163
1164 #[cfg(feature = "jwe")]
1165 #[test]
1166 fn jwe_invalid_key_size_a256kw() {
1167 let result = A256KWKey::from_bytes(&[0u8; 16]);
1168 assert!(result.is_err());
1169 }
1170
1171 #[cfg(feature = "jwe")]
1172 #[test]
1173 fn jwe_invalid_key_size_a128kw() {
1174 let result = A128KWKey::from_bytes(&[0u8; 32]);
1175 assert!(result.is_err());
1176 }
1177
1178 #[cfg(feature = "jwe")]
1179 #[test]
1180 fn jwe_rsa_key_too_small() {
1181 let result = RsaOaepDecryptionKey::generate(1024);
1182 assert!(result.is_err());
1183 }
1184
1185 #[cfg(feature = "jwe")]
1186 #[test]
1187 fn jwe_critical_header_rejected() {
1188 use ct_codecs::{Base64UrlSafeNoPadding, Encoder};
1189
1190 let key = A256KWKey::generate();
1191 let claims = Claims::create(Duration::from_secs(86400));
1192 let token = key.encrypt(claims).unwrap();
1193
1194 let parts: Vec<&str> = token.split('.').collect();
1195 let header_bytes =
1196 ct_codecs::Base64UrlSafeNoPadding::decode_to_vec(parts[0], None).unwrap();
1197 let mut header: serde_json::Value = serde_json::from_slice(&header_bytes).unwrap();
1198 header["crit"] = serde_json::json!(["unknown-extension"]);
1199 let modified_header = serde_json::to_string(&header).unwrap();
1200 let modified_header_b64 =
1201 Base64UrlSafeNoPadding::encode_to_string(&modified_header).unwrap();
1202
1203 let modified_token = format!(
1204 "{}.{}.{}.{}.{}",
1205 modified_header_b64, parts[1], parts[2], parts[3], parts[4]
1206 );
1207
1208 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(&modified_token, None);
1209 assert!(result.is_err());
1210 }
1211
1212 #[cfg(feature = "jwe")]
1213 #[test]
1214 fn jwe_malformed_inputs_no_panic() {
1215 let key = A256KWKey::generate();
1216
1217 let malformed_inputs = [
1218 "",
1219 ".",
1220 "..",
1221 "...",
1222 "....",
1223 ".....",
1224 "a]]]]]",
1225 "a.b.c.d.e",
1226 "?????.?????.?????.?????.?????",
1227 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0",
1228 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.",
1229 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0..",
1230 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0...",
1231 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0....",
1232 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.a.b.c.d",
1233 "not-base64!@#$.valid.valid.valid.valid",
1234 "eyJhbGciOiJBMjU2S1cifQ.a.b.c.d",
1235 "eyJ9.a.b.c.d",
1236 "e30.a.b.c.d",
1237 &"a".repeat(100000),
1238 &format!("{}.{}.{}.{}.{}", "a".repeat(10000), "b", "c", "d", "e"),
1239 "eyJhbGciOiJXUk9ORyIsImVuYyI6IkEyNTZHQ00ifQ.a.b.c.d",
1240 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJXUk9ORyJ9.a.b.c.d",
1241 " . . . . ",
1242 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.AAAA.AAAA.AAAA.AAAA",
1243 "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.....AAAA",
1244 ];
1245
1246 for input in &malformed_inputs {
1247 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(input, None);
1248 assert!(result.is_err(), "Expected error for input: {}", input);
1249 }
1250
1251 // Also test decode_metadata with malformed inputs
1252 for input in &malformed_inputs {
1253 let _ = A256KWKey::decode_metadata(input);
1254 }
1255 }
1256
1257 #[cfg(feature = "jwe")]
1258 #[test]
1259 fn jwe_truncated_token_no_panic() {
1260 let key = A256KWKey::generate();
1261 let claims = Claims::create(Duration::from_secs(86400));
1262 let token = key.encrypt(claims).unwrap();
1263
1264 // Test progressively truncated tokens
1265 for i in 0..token.len() {
1266 let truncated = &token[..i];
1267 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(truncated, None);
1268 assert!(result.is_err());
1269 }
1270 }
1271
1272 #[cfg(feature = "jwe")]
1273 #[test]
1274 fn jwe_corrupted_parts_no_panic() {
1275 let key = A256KWKey::generate();
1276 let claims = Claims::create(Duration::from_secs(86400));
1277 let token = key.encrypt(claims).unwrap();
1278 let parts: Vec<&str> = token.split('.').collect();
1279
1280 // Corrupt each part individually
1281 for i in 0..5 {
1282 let mut modified_parts: Vec<String> = parts.iter().map(|s| s.to_string()).collect();
1283 modified_parts[i] = "AAAA".to_string();
1284 let modified = modified_parts.join(".");
1285 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(&modified, None);
1286 assert!(result.is_err());
1287 }
1288
1289 // Empty each part individually
1290 for i in 0..5 {
1291 let mut modified_parts: Vec<String> = parts.iter().map(|s| s.to_string()).collect();
1292 modified_parts[i] = "".to_string();
1293 let modified = modified_parts.join(".");
1294 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(&modified, None);
1295 assert!(result.is_err());
1296 }
1297 }
1298
1299 #[cfg(feature = "jwe")]
1300 #[test]
1301 fn jwe_rsa_malformed_inputs_no_panic() {
1302 let key = RsaOaepDecryptionKey::generate(2048).unwrap();
1303
1304 let malformed_inputs = [
1305 "",
1306 ".....",
1307 "a.b.c.d.e",
1308 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.a.b.c.d",
1309 ];
1310
1311 for input in &malformed_inputs {
1312 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(input, None);
1313 assert!(result.is_err());
1314 }
1315 }
1316
1317 #[cfg(feature = "jwe")]
1318 #[test]
1319 fn jwe_ecdh_malformed_inputs_no_panic() {
1320 let key = EcdhEsA256KWDecryptionKey::generate();
1321
1322 let malformed_inputs = [
1323 "",
1324 ".....",
1325 "a.b.c.d.e",
1326 "eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZHQ00ifQ.a.b.c.d",
1327 // Valid header but missing epk
1328 "eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZHQ00ifQ.AAAA.AAAA.AAAA.AAAA",
1329 ];
1330
1331 for input in &malformed_inputs {
1332 let result: Result<JWTClaims<NoCustomClaims>, _> = key.decrypt_token(input, None);
1333 assert!(result.is_err());
1334 }
1335 }
1336}