Skip to main content

uselesskey_jsonwebtoken/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Integration between uselesskey test fixtures and the `jsonwebtoken` crate.
4//!
5//! This crate provides extension traits that add `.encoding_key()` and `.decoding_key()`
6//! methods to uselesskey keypair types, making it easy to sign and verify JWTs in tests.
7//!
8//! # Features
9//!
10//! Enable the key types you need:
11//!
12//! - `rsa` - RSA keypairs (RS256, RS384, RS512)
13//! - `ecdsa` - ECDSA keypairs (ES256, ES384)
14//! - `ed25519` - Ed25519 keypairs (EdDSA)
15//! - `hmac` - HMAC secrets (HS256, HS384, HS512)
16//! - `all` - All of the above
17//!
18//! # Example: Sign and verify a JWT with RSA
19//!
20#![cfg_attr(feature = "rsa", doc = "```")]
21#![cfg_attr(not(feature = "rsa"), doc = "```ignore")]
22//! use uselesskey_core::Factory;
23//! use uselesskey_rsa::{RsaFactoryExt, RsaSpec};
24//! use uselesskey_jsonwebtoken::JwtKeyExt;
25//! use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
26//! use serde::{Serialize, Deserialize};
27//!
28//! #[derive(Debug, Serialize, Deserialize)]
29//! struct Claims {
30//!     sub: String,
31//!     exp: usize,
32//! }
33//!
34//! let fx = Factory::random();
35//! let keypair = fx.rsa("my-issuer", RsaSpec::rs256());
36//!
37//! // Sign a JWT
38//! let claims = Claims { sub: "user123".to_string(), exp: 2_000_000_000 };
39//! let header = Header::new(Algorithm::RS256);
40//! let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
41//!
42//! // Verify the JWT
43//! let validation = Validation::new(Algorithm::RS256);
44//! let decoded = decode::<Claims>(&token, &keypair.decoding_key(), &validation).unwrap();
45//! assert_eq!(decoded.claims.sub, "user123");
46//! ```
47//!
48//! # Example: Sign and verify with ECDSA
49//!
50#![cfg_attr(feature = "ecdsa", doc = "```")]
51#![cfg_attr(not(feature = "ecdsa"), doc = "```ignore")]
52//! use uselesskey_core::Factory;
53//! use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
54//! use uselesskey_jsonwebtoken::JwtKeyExt;
55//! use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
56//! use serde::{Serialize, Deserialize};
57//!
58//! #[derive(Debug, Serialize, Deserialize)]
59//! struct Claims {
60//!     sub: String,
61//!     exp: usize,
62//! }
63//!
64//! let fx = Factory::random();
65//! let keypair = fx.ecdsa("my-issuer", EcdsaSpec::es256());
66//!
67//! let claims = Claims { sub: "user123".to_string(), exp: 2_000_000_000 };
68//! let header = Header::new(Algorithm::ES256);
69//! let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
70//!
71//! let validation = Validation::new(Algorithm::ES256);
72//! let decoded = decode::<Claims>(&token, &keypair.decoding_key(), &validation).unwrap();
73//! assert_eq!(decoded.claims.sub, "user123");
74//! ```
75//!
76//! # Example: Sign and verify with Ed25519
77//!
78#![cfg_attr(feature = "ed25519", doc = "```")]
79#![cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
80//! use uselesskey_core::Factory;
81//! use uselesskey_ed25519::{Ed25519FactoryExt, Ed25519Spec};
82//! use uselesskey_jsonwebtoken::JwtKeyExt;
83//! use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
84//! use serde::{Serialize, Deserialize};
85//!
86//! #[derive(Debug, Serialize, Deserialize)]
87//! struct Claims {
88//!     sub: String,
89//!     exp: usize,
90//! }
91//!
92//! let fx = Factory::random();
93//! let keypair = fx.ed25519("my-issuer", Ed25519Spec::new());
94//!
95//! let claims = Claims { sub: "user123".to_string(), exp: 2_000_000_000 };
96//! let header = Header::new(Algorithm::EdDSA);
97//! let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
98//!
99//! let validation = Validation::new(Algorithm::EdDSA);
100//! let decoded = decode::<Claims>(&token, &keypair.decoding_key(), &validation).unwrap();
101//! assert_eq!(decoded.claims.sub, "user123");
102//! ```
103//!
104//! # Example: Sign and verify with HMAC
105//!
106#![cfg_attr(feature = "hmac", doc = "```")]
107#![cfg_attr(not(feature = "hmac"), doc = "```ignore")]
108//! use uselesskey_core::Factory;
109//! use uselesskey_hmac::{HmacFactoryExt, HmacSpec};
110//! use uselesskey_jsonwebtoken::JwtKeyExt;
111//! use jsonwebtoken::{encode, decode, Header, Algorithm, Validation};
112//! use serde::{Serialize, Deserialize};
113//!
114//! #[derive(Debug, Serialize, Deserialize)]
115//! struct Claims {
116//!     sub: String,
117//!     exp: usize,
118//! }
119//!
120//! let fx = Factory::random();
121//! let secret = fx.hmac("my-secret", HmacSpec::hs256());
122//!
123//! let claims = Claims { sub: "user123".to_string(), exp: 2_000_000_000 };
124//! let header = Header::new(Algorithm::HS256);
125//! let token = encode(&header, &claims, &secret.encoding_key()).unwrap();
126//!
127//! let validation = Validation::new(Algorithm::HS256);
128//! let decoded = decode::<Claims>(&token, &secret.decoding_key(), &validation).unwrap();
129//! assert_eq!(decoded.claims.sub, "user123");
130//! ```
131
132use jsonwebtoken::{DecodingKey, EncodingKey};
133
134/// Extension trait for uselesskey keypairs to produce jsonwebtoken keys.
135///
136/// This trait is implemented for RSA, ECDSA, Ed25519 keypairs, and HMAC secrets
137/// when the corresponding features are enabled.
138pub trait JwtKeyExt {
139    /// Create a `jsonwebtoken::EncodingKey` for signing JWTs.
140    ///
141    /// # Panics
142    ///
143    /// Panics if the key cannot be parsed (should not happen with valid uselesskey fixtures).
144    fn encoding_key(&self) -> EncodingKey;
145
146    /// Create a `jsonwebtoken::DecodingKey` for verifying JWTs.
147    ///
148    /// # Panics
149    ///
150    /// Panics if the key cannot be parsed (should not happen with valid uselesskey fixtures).
151    fn decoding_key(&self) -> DecodingKey;
152}
153
154#[cfg(feature = "rsa")]
155impl JwtKeyExt for uselesskey_rsa::RsaKeyPair {
156    fn encoding_key(&self) -> EncodingKey {
157        EncodingKey::from_rsa_pem(self.private_key_pkcs8_pem().as_bytes())
158            .expect("failed to create EncodingKey from RSA PEM")
159    }
160
161    fn decoding_key(&self) -> DecodingKey {
162        DecodingKey::from_rsa_pem(self.public_key_spki_pem().as_bytes())
163            .expect("failed to create DecodingKey from RSA PEM")
164    }
165}
166
167#[cfg(feature = "ecdsa")]
168impl JwtKeyExt for uselesskey_ecdsa::EcdsaKeyPair {
169    fn encoding_key(&self) -> EncodingKey {
170        EncodingKey::from_ec_pem(self.private_key_pkcs8_pem().as_bytes())
171            .expect("failed to create EncodingKey from EC PEM")
172    }
173
174    fn decoding_key(&self) -> DecodingKey {
175        DecodingKey::from_ec_pem(self.public_key_spki_pem().as_bytes())
176            .expect("failed to create DecodingKey from EC PEM")
177    }
178}
179
180#[cfg(feature = "ed25519")]
181impl JwtKeyExt for uselesskey_ed25519::Ed25519KeyPair {
182    fn encoding_key(&self) -> EncodingKey {
183        EncodingKey::from_ed_pem(self.private_key_pkcs8_pem().as_bytes())
184            .expect("failed to create EncodingKey from Ed25519 PEM")
185    }
186
187    fn decoding_key(&self) -> DecodingKey {
188        DecodingKey::from_ed_pem(self.public_key_spki_pem().as_bytes())
189            .expect("failed to create DecodingKey from Ed25519 PEM")
190    }
191}
192
193#[cfg(feature = "hmac")]
194impl JwtKeyExt for uselesskey_hmac::HmacSecret {
195    fn encoding_key(&self) -> EncodingKey {
196        EncodingKey::from_secret(self.secret_bytes())
197    }
198
199    fn decoding_key(&self) -> DecodingKey {
200        DecodingKey::from_secret(self.secret_bytes())
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use std::sync::OnceLock;
207    use uselesskey_core::{Factory, Seed};
208
209    static FX: OnceLock<Factory> = OnceLock::new();
210
211    fn fx() -> Factory {
212        FX.get_or_init(|| {
213            let seed = Seed::from_env_value("uselesskey-jsonwebtoken-inline-test-seed-v1")
214                .expect("test seed should always parse");
215            Factory::deterministic(seed)
216        })
217        .clone()
218    }
219
220    #[cfg(feature = "rsa")]
221    mod rsa_tests {
222        use crate::JwtKeyExt;
223        use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
224        use serde::{Deserialize, Serialize};
225        use uselesskey_core::Factory;
226        use uselesskey_rsa::{RsaFactoryExt, RsaSpec};
227
228        #[derive(Debug, Serialize, Deserialize, PartialEq)]
229        struct TestClaims {
230            sub: String,
231            exp: usize,
232        }
233
234        #[test]
235        fn test_rsa_sign_and_verify() {
236            let fx = super::fx();
237            let keypair = fx.rsa("test-issuer", RsaSpec::rs256());
238
239            let claims = TestClaims {
240                sub: "user123".to_string(),
241                exp: 2_000_000_000,
242            };
243
244            let header = Header::new(Algorithm::RS256);
245            let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
246
247            let validation = Validation::new(Algorithm::RS256);
248            let decoded =
249                decode::<TestClaims>(&token, &keypair.decoding_key(), &validation).unwrap();
250
251            assert_eq!(decoded.claims, claims);
252        }
253
254        #[test]
255        fn test_rsa_deterministic_keys_work() {
256            use uselesskey_core::Seed;
257
258            let seed = Seed::from_env_value("test-seed").unwrap();
259            let fx = Factory::deterministic(seed);
260            let keypair = fx.rsa("deterministic-issuer", RsaSpec::rs256());
261
262            let claims = TestClaims {
263                sub: "det-user".to_string(),
264                exp: 2_000_000_000,
265            };
266
267            let header = Header::new(Algorithm::RS256);
268            let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
269
270            let validation = Validation::new(Algorithm::RS256);
271            let decoded =
272                decode::<TestClaims>(&token, &keypair.decoding_key(), &validation).unwrap();
273
274            assert_eq!(decoded.claims, claims);
275        }
276    }
277
278    #[cfg(feature = "ecdsa")]
279    mod ecdsa_tests {
280        use crate::JwtKeyExt;
281        use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
282        use serde::{Deserialize, Serialize};
283        use uselesskey_core::Factory;
284        use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
285
286        #[derive(Debug, Serialize, Deserialize, PartialEq)]
287        struct TestClaims {
288            sub: String,
289            exp: usize,
290        }
291
292        #[test]
293        fn test_ecdsa_es256_sign_and_verify() {
294            let fx = Factory::random();
295            let keypair = fx.ecdsa("test-issuer", EcdsaSpec::es256());
296
297            let claims = TestClaims {
298                sub: "user123".to_string(),
299                exp: 2_000_000_000,
300            };
301
302            let header = Header::new(Algorithm::ES256);
303            let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
304
305            let validation = Validation::new(Algorithm::ES256);
306            let decoded =
307                decode::<TestClaims>(&token, &keypair.decoding_key(), &validation).unwrap();
308
309            assert_eq!(decoded.claims, claims);
310        }
311
312        #[test]
313        fn test_ecdsa_es384_sign_and_verify() {
314            let fx = Factory::random();
315            let keypair = fx.ecdsa("test-issuer", EcdsaSpec::es384());
316
317            let claims = TestClaims {
318                sub: "user123".to_string(),
319                exp: 2_000_000_000,
320            };
321
322            let header = Header::new(Algorithm::ES384);
323            let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
324
325            let validation = Validation::new(Algorithm::ES384);
326            let decoded =
327                decode::<TestClaims>(&token, &keypair.decoding_key(), &validation).unwrap();
328
329            assert_eq!(decoded.claims, claims);
330        }
331    }
332
333    #[cfg(feature = "ed25519")]
334    mod ed25519_tests {
335        use crate::JwtKeyExt;
336        use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
337        use serde::{Deserialize, Serialize};
338        use uselesskey_core::Factory;
339        use uselesskey_ed25519::{Ed25519FactoryExt, Ed25519Spec};
340
341        #[derive(Debug, Serialize, Deserialize, PartialEq)]
342        struct TestClaims {
343            sub: String,
344            exp: usize,
345        }
346
347        #[test]
348        fn test_ed25519_sign_and_verify() {
349            let fx = Factory::random();
350            let keypair = fx.ed25519("test-issuer", Ed25519Spec::new());
351
352            let claims = TestClaims {
353                sub: "user123".to_string(),
354                exp: 2_000_000_000,
355            };
356
357            let header = Header::new(Algorithm::EdDSA);
358            let token = encode(&header, &claims, &keypair.encoding_key()).unwrap();
359
360            let validation = Validation::new(Algorithm::EdDSA);
361            let decoded =
362                decode::<TestClaims>(&token, &keypair.decoding_key(), &validation).unwrap();
363
364            assert_eq!(decoded.claims, claims);
365        }
366    }
367
368    #[cfg(feature = "ecdsa")]
369    mod cross_key_tests {
370        use crate::JwtKeyExt;
371        use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
372        use serde::{Deserialize, Serialize};
373        use uselesskey_core::Factory;
374        use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
375
376        #[derive(Debug, Serialize, Deserialize, PartialEq)]
377        struct TestClaims {
378            sub: String,
379            exp: usize,
380        }
381
382        #[test]
383        fn test_cross_key_decode_fails() {
384            let fx = Factory::random();
385            let key_a = fx.ecdsa("issuer-a", EcdsaSpec::es256());
386            let key_b = fx.ecdsa("issuer-b", EcdsaSpec::es256());
387
388            let claims = TestClaims {
389                sub: "user".to_string(),
390                exp: 2_000_000_000,
391            };
392
393            let token = encode(
394                &Header::new(Algorithm::ES256),
395                &claims,
396                &key_a.encoding_key(),
397            )
398            .unwrap();
399
400            let result = decode::<TestClaims>(
401                &token,
402                &key_b.decoding_key(),
403                &Validation::new(Algorithm::ES256),
404            );
405            assert!(result.is_err(), "decoding with wrong key should fail");
406        }
407    }
408
409    #[cfg(feature = "hmac")]
410    mod hmac_tests {
411        use crate::JwtKeyExt;
412        use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
413        use serde::{Deserialize, Serialize};
414        use uselesskey_core::Factory;
415        use uselesskey_hmac::{HmacFactoryExt, HmacSpec};
416
417        #[derive(Debug, Serialize, Deserialize, PartialEq)]
418        struct TestClaims {
419            sub: String,
420            exp: usize,
421        }
422
423        #[test]
424        fn test_hmac_hs256_sign_and_verify() {
425            let fx = Factory::random();
426            let secret = fx.hmac("test-secret", HmacSpec::hs256());
427
428            let claims = TestClaims {
429                sub: "user123".to_string(),
430                exp: 2_000_000_000,
431            };
432
433            let header = Header::new(Algorithm::HS256);
434            let token = encode(&header, &claims, &secret.encoding_key()).unwrap();
435
436            let validation = Validation::new(Algorithm::HS256);
437            let decoded =
438                decode::<TestClaims>(&token, &secret.decoding_key(), &validation).unwrap();
439
440            assert_eq!(decoded.claims, claims);
441        }
442
443        #[test]
444        fn test_hmac_hs384_sign_and_verify() {
445            let fx = Factory::random();
446            let secret = fx.hmac("test-secret", HmacSpec::hs384());
447
448            let claims = TestClaims {
449                sub: "user123".to_string(),
450                exp: 2_000_000_000,
451            };
452
453            let header = Header::new(Algorithm::HS384);
454            let token = encode(&header, &claims, &secret.encoding_key()).unwrap();
455
456            let validation = Validation::new(Algorithm::HS384);
457            let decoded =
458                decode::<TestClaims>(&token, &secret.decoding_key(), &validation).unwrap();
459
460            assert_eq!(decoded.claims, claims);
461        }
462
463        #[test]
464        fn test_hmac_hs512_sign_and_verify() {
465            let fx = Factory::random();
466            let secret = fx.hmac("test-secret", HmacSpec::hs512());
467
468            let claims = TestClaims {
469                sub: "user123".to_string(),
470                exp: 2_000_000_000,
471            };
472
473            let header = Header::new(Algorithm::HS512);
474            let token = encode(&header, &claims, &secret.encoding_key()).unwrap();
475
476            let validation = Validation::new(Algorithm::HS512);
477            let decoded =
478                decode::<TestClaims>(&token, &secret.decoding_key(), &validation).unwrap();
479
480            assert_eq!(decoded.claims, claims);
481        }
482    }
483}