keygate_jwt/
lib.rs

1// [![GitHub CI](https://github.com/keygateio/keygate-jwt/workflows/Rust/badge.svg)](https://github.com/keygateio/keygate-jwt/actions)
2// [![Docs.rs](https://docs.rs/keygate-jwt/badge.svg)](https://docs.rs/keygate-jwt/)
3// [![crates.io](https://img.shields.io/crates/v/keygate-jwt.svg)](https://crates.io/crates/keygate-jwt)//!
4//! # keygate-jwt
5//!
6//! A new JWT implementation for Rust that focuses on simplicity, while avoiding
7//! common JWT security pitfalls.
8//!
9//! A new JWT (JSON Web Tokens) implementation for Rust that focuses on simplicity, while avoiding common JWT security pitfalls.
10//!
11//! * p256
12//!   * `ES256`
13//! * p384
14//!   * `ES384`
15//! * secp256k1
16//!   * `ES256K`
17//! * Ed25519
18//!   * `EdDSA`
19//!
20//! `keygate-jwt` uses only pure Rust implementations, and can be compiled out of the box to WebAssembly/WASI.
21//!
22//! Important: JWT's purpose is to verify that data has been created by a party
23//! knowing a secret key. It does not provide any kind of confidentiality: JWT
24//! data is simply encoded as BASE64, and is not encrypted.
25
26#![forbid(unsafe_code)]
27
28pub mod algorithms;
29pub mod claims;
30pub mod common;
31pub mod token;
32
33mod jwt_header;
34mod serde_additions;
35
36mod error;
37pub use error::{Error, JWTError};
38
39pub mod prelude {
40    pub use std::collections::HashSet;
41
42    pub use coarsetime::{self, Clock, Duration, UnixTimeStamp};
43    pub use serde::{Deserialize, Serialize};
44
45    pub use crate::algorithms::*;
46    pub use crate::claims::*;
47    pub use crate::common::*;
48    pub use crate::token::*;
49
50    mod hashset_from_strings {
51        use std::collections::HashSet;
52
53        pub trait HashSetFromStringsT {
54            /// Create a set from a list of strings
55            fn from_strings(strings: &[impl ToString]) -> HashSet<String> {
56                strings.iter().map(|x| x.to_string()).collect()
57            }
58        }
59
60        impl HashSetFromStringsT for HashSet<String> {}
61    }
62
63    pub use hashset_from_strings::HashSetFromStringsT as _;
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::prelude::*;
69
70    #[cfg(feature = "ecdsa")]
71    #[test]
72    fn es256() {
73        let key_pair = ES256KeyPair::generate();
74        let claims = Claims::create(Duration::from_secs(86400));
75        let token = key_pair.sign(claims).unwrap();
76        let _claims = key_pair
77            .public_key()
78            .verify_token::<NoCustomClaims>(&token, None)
79            .unwrap();
80    }
81
82    #[cfg(feature = "ecdsa")]
83    #[test]
84    fn es384() {
85        let key_pair = ES384KeyPair::generate();
86        let claims = Claims::create(Duration::from_secs(86400));
87        let token = key_pair.sign(claims).unwrap();
88        let _claims = key_pair
89            .public_key()
90            .verify_token::<NoCustomClaims>(&token, None)
91            .unwrap();
92    }
93
94    #[cfg(feature = "ecdsa")]
95    #[test]
96    fn es256k() {
97        let key_pair = ES256kKeyPair::generate();
98        let claims = Claims::create(Duration::from_secs(86400));
99        let token = key_pair.sign(claims).unwrap();
100        let _claims = key_pair
101            .public_key()
102            .verify_token::<NoCustomClaims>(&token, None)
103            .unwrap();
104    }
105
106    #[cfg(feature = "eddsa")]
107    #[test]
108    fn ed25519() {
109        #[derive(Serialize, Deserialize)]
110        struct CustomClaims {
111            is_custom: bool,
112        }
113
114        let key_pair = Ed25519KeyPair::generate();
115        let mut pk = key_pair.public_key();
116        let key_id = pk.create_key_id().unwrap();
117        let key_pair = key_pair.with_key_id(&key_id);
118        let custom_claims = CustomClaims { is_custom: true };
119        let claims = Claims::with_custom_claims(custom_claims, Duration::from_secs(86400));
120        let token = key_pair.sign(claims).unwrap();
121        let options = VerificationOptions {
122            required_key_id: Some(key_id),
123            ..Default::default()
124        };
125        let claims: JWTClaims<CustomClaims> = key_pair
126            .public_key()
127            .verify_token::<CustomClaims>(&token, Some(options))
128            .unwrap();
129        assert!(claims.custom.is_custom);
130    }
131
132    #[cfg(feature = "eddsa")]
133    #[test]
134    fn ed25519_der() {
135        let key_pair = Ed25519KeyPair::generate();
136        let der = key_pair.to_der();
137        let key_pair2 = Ed25519KeyPair::from_der(&der).unwrap();
138        assert_eq!(key_pair.to_bytes(), key_pair2.to_bytes());
139    }
140
141    #[test]
142    fn require_nonce() {
143        let key_pair = Ed25519KeyPair::generate();
144        let nonce = "some-nonce".to_string();
145        let claims = Claims::create(Duration::from_hours(1)).with_nonce(nonce.clone());
146        let token = key_pair.sign(claims).unwrap();
147
148        let options = VerificationOptions {
149            required_nonce: Some(nonce),
150            ..Default::default()
151        };
152        key_pair
153            .public_key()
154            .verify_token::<NoCustomClaims>(&token, Some(options))
155            .unwrap();
156    }
157
158    #[cfg(feature = "eddsa")]
159    #[test]
160    fn eddsa_pem() {
161        let sk_pem = "-----BEGIN PRIVATE KEY-----
162MC4CAQAwBQYDK2VwBCIEIMXY1NUbUe/3dW2YUoKW5evsnCJPMfj60/q0RzGne3gg
163-----END PRIVATE KEY-----\n";
164        let pk_pem = "-----BEGIN PUBLIC KEY-----
165MCowBQYDK2VwAyEAyrRjJfTnhMcW5igzYvPirFW5eUgMdKeClGzQhd4qw+Y=
166-----END PUBLIC KEY-----\n";
167        let kp = Ed25519KeyPair::from_pem(sk_pem).unwrap();
168        assert_eq!(kp.public_key().to_pem(), pk_pem);
169    }
170
171    #[cfg(feature = "eddsa")]
172    #[test]
173    fn key_metadata() {
174        let mut key_pair = Ed25519KeyPair::generate();
175        let thumbprint = key_pair.public_key().sha256_thumbprint().unwrap();
176        let key_metadata = KeyMetadata::default()
177            .with_certificate_sha256_thumbprint(&thumbprint)
178            .unwrap();
179        key_pair.attach_metadata(key_metadata).unwrap();
180
181        let claims = Claims::create(Duration::from_secs(86400));
182        let token = key_pair.sign(claims).unwrap();
183
184        let decoded_metadata = Token::decode_metadata(&token).unwrap();
185        assert_eq!(
186            decoded_metadata.certificate_sha256_thumbprint(),
187            Some(thumbprint.as_ref())
188        );
189        let _ = key_pair
190            .public_key()
191            .verify_token::<NoCustomClaims>(&token, None)
192            .unwrap();
193    }
194
195    #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
196    #[test]
197    fn expired_token() {
198        let key_pair = Ed25519KeyPair::generate();
199        let claims = Claims::create(Duration::from_secs(1));
200        let token = key_pair.sign(claims).unwrap();
201        std::thread::sleep(std::time::Duration::from_secs(2));
202        let options = VerificationOptions {
203            time_tolerance: None,
204            ..Default::default()
205        };
206        let claims = key_pair
207            .public_key()
208            .verify_token::<NoCustomClaims>(&token, None);
209        assert!(claims.is_ok());
210        let claims = key_pair
211            .public_key()
212            .verify_token::<NoCustomClaims>(&token, Some(options));
213        assert!(claims.is_err());
214    }
215}