jwks_client_update/
lib.rs

1pub mod error;
2pub mod jwt;
3pub mod keyset;
4
5///JWKS client library [![Build Status](https://travis-ci.com/jfbilodeau/jwks-client.svg?branch=master)](https://travis-ci.com/jfbilodeau/jwks-client) [![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6///===
7///JWKS-Client is a library written in Rust to decode and validate JWT tokens using a JSON Web Key Store.
8///
9///I created this library specifically to decode GCP/Firebase JWT but should be useable with little to no modification. Contact me to propose support for different JWKS key store.
10///
11///TODO:
12///* More documentation :P
13///* Extract expiration time of keys from HTTP request
14///* Automatically refresh keys in background
15#[cfg(test)]
16mod tests {
17    use std::time::{Duration, SystemTime};
18
19    use serde::{Deserialize, Serialize};
20
21    use crate::error::{Error, Type};
22    use crate::keyset::{JwtKey, KeyStore};
23
24    //    const IAT: u64 = 200;
25    const TIME_NBF: u64 = 300;
26    const TIME_SAFE: u64 = 400;
27    const TIME_EXP: u64 = 500;
28
29    fn time_nbf() -> SystemTime {
30        SystemTime::UNIX_EPOCH + Duration::new(TIME_NBF - 1, 0)
31    }
32
33    fn time_safe() -> SystemTime {
34        SystemTime::UNIX_EPOCH + Duration::new(TIME_SAFE, 0)
35    }
36
37    fn time_exp() -> SystemTime {
38        SystemTime::UNIX_EPOCH + Duration::new(TIME_EXP + 1, 0)
39    }
40
41    pub const KEY_URL: &str = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
42    pub const E: &str = "AQAB";
43    pub const N: &str = "t5N44H1mpb5Wlx_0e7CdoKTY8xt-3yMby8BgNdagVNkeCkZ4pRbmQXRWNC7qn__Zaxx9dnzHbzGCul5W0RLfd3oB3PESwsrQh-oiXVEPTYhvUPQkX0vBfCXJtg_zY2mY1DxKOIiXnZ8PaK_7Sx0aMmvR__0Yy2a5dIAWCmjPsxn-PcGZOkVUm-D5bH1-ZStcA_68r4ZSPix7Szhgl1RoHb9Q6JSekyZqM0Qfwhgb7srZVXC_9_m5PEx9wMVNYpYJBrXhD5IQm9RzE9oJS8T-Ai-4_5mNTNXI8f1rrYgffWS4wf9cvsEihrvEg9867B2f98L7ux9Llle7jsHCtwgV1w";
44    pub const N_INVALID: &str = "xt5N44H1mpb5Wlx_0e7CdoKTY8xt-3yMby8BgNdagVNkeCkZ4pRbmQXRWNC7qn__Zaxx9dnzHbzGCul5W0RLfd3oB3PESwsrQh-oiXVEPTYhvUPQkX0vBfCXJtg_zY2mY1DxKOIiXnZ8PaK_7Sx0aMmvR__0Yy2a5dIAWCmjPsxn-PcGZOkVUm-D5bH1-ZStcA_68r4ZSPix7Szhgl1RoHb9Q6JSekyZqM0Qfwhgb7srZVXC_9_m5PEx9wMVNYpYJBrXhD5IQm9RzE9oJS8T-Ai-4_5mNTNXI8f1rrYgffWS4wf9cvsEihrvEg9867B2f98L7ux9Llle7jsHCtwgV1w==";
45    pub const TOKEN: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
46    pub const TOKEN_INV_CERT: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.XXXeTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
47
48    #[derive(Debug, Serialize, Deserialize)]
49    pub struct TestPayload {
50        pub iss: String,
51        pub name: String,
52        pub email: String,
53    }
54
55    #[test]
56    fn test_new_with_url() {
57        let key_set = tokio_test::block_on(KeyStore::new_from(KEY_URL.to_owned())).unwrap();
58
59        assert_eq!(KEY_URL, key_set.key_set_url());
60    }
61
62    #[test]
63    fn test_refresh_keys() {
64        let key_set = tokio_test::block_on(KeyStore::new_from(KEY_URL.to_owned())).unwrap();
65
66        assert_eq!(KEY_URL, key_set.key_set_url());
67        assert!(key_set.keys_len() > 0);
68
69        assert!(key_set.key_by_id("1").is_some());
70        assert!(key_set.key_by_id("2").is_none());
71
72        let result = key_set.verify_time(TOKEN, time_safe());
73
74        let jwt = result.unwrap();
75
76        assert_eq!("https://chronogears.com/test", jwt.payload().iss().unwrap());
77        assert_eq!("Ada Lovelace", jwt.payload().get_str("name").unwrap());
78        assert_eq!("alovelace@chronogears.com", jwt.payload().get_str("email").unwrap());
79    }
80
81    #[test]
82    fn test_add_key() {
83        let key = JwtKey::new("1", N, E);
84
85        let mut key_set = KeyStore::new();
86
87        assert_eq!(0usize, key_set.keys_len());
88
89        key_set.add_key(&key);
90
91        assert_eq!(1usize, key_set.keys_len());
92
93        let result = key_set.key_by_id("1");
94
95        assert!(result.is_some());
96
97        let key = result.unwrap();
98
99        assert_eq!(N, key.n);
100        assert_eq!(E, key.e);
101        assert_eq!("1", key.kid);
102    }
103
104    #[test]
105    fn test_get_key() {
106        let key = JwtKey::new("1", N, E);
107
108        let mut key_set = KeyStore::new();
109
110        assert_eq!(0usize, key_set.keys_len());
111
112        key_set.add_key(&key);
113
114        assert_eq!(1usize, key_set.keys_len());
115
116        let result = key_set.key_by_id("1");
117
118        assert!(result.is_some());
119
120        let result = key_set.key_by_id("2");
121
122        assert!(result.is_none());
123    }
124
125    #[test]
126    fn test_decode_custom_payload() {
127        let key = JwtKey::new("1", N, E);
128
129        let mut key_set = KeyStore::new();
130
131        key_set.add_key(&key);
132
133        let result = key_set.decode(TOKEN);
134
135        assert!(result.is_ok());
136
137        let jwt = result.unwrap();
138
139        let payload = jwt.payload().into::<TestPayload>().unwrap();
140
141        assert_eq!("https://chronogears.com/test", payload.iss);
142        assert_eq!("Ada Lovelace", payload.name);
143        assert_eq!("alovelace@chronogears.com", payload.email);
144    }
145
146    #[test]
147    fn test_decode_json_payload() {
148        let key = JwtKey::new("1", N, E);
149
150        let mut key_set = KeyStore::new();
151
152        key_set.add_key(&key);
153
154        let result = key_set.decode(TOKEN);
155
156        assert!(result.is_ok());
157
158        let jwt = result.unwrap();
159
160        assert_eq!("https://chronogears.com/test", jwt.payload().iss().unwrap());
161        assert_eq!("Ada Lovelace", jwt.payload().get_str("name").unwrap());
162        assert_eq!("alovelace@chronogears.com", jwt.payload().get_str("email").unwrap());
163    }
164
165    #[test]
166    fn test_verify() {
167        let key = JwtKey::new("1", N, E);
168
169        let mut key_set = KeyStore::new();
170
171        key_set.add_key(&key);
172
173        let result = key_set.verify_time(TOKEN, time_safe());
174
175        assert!(result.is_ok());
176
177        let jwt = result.unwrap();
178
179        assert_eq!("https://chronogears.com/test", jwt.payload().iss().unwrap());
180        assert_eq!("Ada Lovelace", jwt.payload().get_str("name").unwrap());
181        assert_eq!("alovelace@chronogears.com", jwt.payload().get_str("email").unwrap());
182
183        let result = key_set.verify_time(TOKEN, time_nbf());
184
185        match result {
186            Ok(_) => panic!(),
187            Err(Error { msg: _, typ }) => {
188                assert_eq!(Type::Early, typ);
189            }
190        }
191
192        let result = key_set.verify_time(TOKEN, time_exp());
193
194        match result {
195            Ok(_) => panic!(),
196            Err(Error { msg: _, typ }) => {
197                assert_eq!(Type::Expired, typ);
198            }
199        }
200    }
201
202    #[test]
203    fn test_verify_invalid_certificate() {
204        let key = JwtKey::new("1", N_INVALID, E);
205
206        let mut key_set = KeyStore::new();
207
208        key_set.add_key(&key);
209
210        let result = key_set.verify(TOKEN);
211
212        assert!(result.is_err());
213    }
214
215    #[test]
216    fn test_verify_invalid_signature() {
217        let key = JwtKey::new("1", N, E);
218
219        let mut key_set = KeyStore::new();
220
221        key_set.add_key(&key);
222
223        let result = key_set.verify(TOKEN_INV_CERT);
224
225        assert!(result.is_err());
226
227        // Should still be able to decode:
228        let result = key_set.decode(TOKEN_INV_CERT);
229
230        let jwt = result.unwrap();
231
232        assert_eq!("https://chronogears.com/test", jwt.payload().iss().unwrap());
233        assert_eq!("Ada Lovelace", jwt.payload().get_str("name").unwrap());
234        assert_eq!("alovelace@chronogears.com", jwt.payload().get_str("email").unwrap());
235    }
236
237    #[test]
238    fn test_expired() {
239        let key_set = KeyStore::new();
240
241        let jwk = key_set.decode(TOKEN).unwrap();
242
243        let time = SystemTime::UNIX_EPOCH + Duration::new(TIME_EXP + 1, 0);
244
245        assert!(jwk.expired_time(time).unwrap());
246    }
247
248    #[test]
249    fn test_not_expired() {
250        let key_set = KeyStore::new();
251
252        let jwk = key_set.decode(TOKEN).unwrap();
253
254        let time = SystemTime::UNIX_EPOCH + Duration::new(TIME_EXP - 1, 0);
255
256        assert!(!jwk.expired_time(time).unwrap());
257    }
258
259    #[test]
260    fn test_nbf() {
261        let key_set = KeyStore::new();
262
263        let jwk = key_set.decode(TOKEN).unwrap();
264
265        let time = SystemTime::UNIX_EPOCH + Duration::new(TIME_NBF - 1, 0);
266
267        assert!(jwk.early_time(time).unwrap());
268    }
269
270    #[test]
271    fn test_not_nbf() {
272        let key_set = KeyStore::new();
273
274        let jwk = key_set.decode(TOKEN).unwrap();
275
276        let time = SystemTime::UNIX_EPOCH + Duration::new(TIME_NBF + 1, 0);
277
278        assert!(!jwk.early_time(time).unwrap());
279    }
280
281    #[test]
282    fn test_valid_exp() {
283        let key_set = KeyStore::new();
284
285        let jwk = key_set.decode(TOKEN).unwrap();
286
287        let time = SystemTime::UNIX_EPOCH + Duration::new(TIME_NBF - 1, 0);
288
289        assert!(jwk.early_time(time).unwrap());
290    }
291
292    #[test]
293    fn test_keys_expired() {
294        let key_store = KeyStore::new();
295
296        assert_eq!(None, key_store.last_load_time());
297        assert_eq!(None, key_store.keys_expired());
298
299        let key_store = tokio_test::block_on(KeyStore::new_from(KEY_URL.to_owned())).unwrap();
300
301        assert!(key_store.last_load_time().is_some());
302        assert!(key_store.keys_expired().is_some());
303        assert_eq!(false, key_store.keys_expired().unwrap());
304    }
305
306    #[test]
307    fn test_should_refresh() {
308        let mut key_store = KeyStore::new();
309
310        assert_eq!(0.5, key_store.refresh_interval());
311        assert_eq!(None, key_store.expire_time());
312        assert_eq!(None, key_store.keys_expired());
313        assert_eq!(None, key_store.last_load_time());
314        assert_eq!(None, key_store.should_refresh());
315
316        key_store.set_refresh_interval(0.75);
317        assert_eq!(0.75, key_store.refresh_interval());
318
319        key_store.set_refresh_interval(0.5);
320
321        tokio_test::block_on(key_store.load_keys_from(KEY_URL.to_owned())).unwrap();
322
323        assert_eq!(0.5, key_store.refresh_interval());
324        assert_ne!(None, key_store.expire_time());
325        assert_ne!(None, key_store.keys_expired());
326        assert_ne!(None, key_store.last_load_time());
327        assert_eq!(Some(false), key_store.should_refresh());
328
329        let key_duration = key_store.expire_time().unwrap().duration_since(key_store.load_time().unwrap());
330        let key_duration = key_duration.unwrap();
331
332        let refresh_time = key_store.load_time().unwrap() + (key_duration / 2);
333
334        assert_eq!(Some(refresh_time), key_store.refresh_time());
335
336        // Boundary test
337        let just_before = refresh_time - Duration::new(1, 0);
338        assert_eq!(Some(false), key_store.should_refresh_time(just_before));
339
340        let just_after = refresh_time + Duration::new(1, 0);
341        assert_eq!(Some(true), key_store.should_refresh_time(just_after));
342    }
343}