Skip to main content

pubhubs/misc/
jwt.rs

1//! Implements limited JSON Web Token functionality for our purposes.
2//!
3//! Created to reduce dependencies, and more immediately
4//! fix for this bug in rust's ring crate was not yet stable at
5//! the time of writing:  <https://github.com/briansmith/ring/issues/1299>
6
7use std::borrow::{Borrow as _, Cow};
8use std::fmt;
9
10use base64ct::{Base64UrlUnpadded, Encoding as _};
11use hmac::Mac as _;
12use rsa::{
13    pkcs8::{
14        DecodePrivateKey as _, DecodePublicKey as _, EncodePrivateKey as _, EncodePublicKey as _,
15    },
16    signature::{SignatureEncoding as _, Signer as _, Verifier as _},
17    traits::PublicKeyParts as _,
18};
19use serde::{
20    Deserialize, Deserializer, Serialize,
21    de::{DeserializeOwned, Visitor},
22};
23
24use crate::id;
25use crate::misc::time_ext;
26use crate::phcrypto;
27
28/// Wrapper around [`String`] to indicate it should be interpretted as a JWT.
29///
30/// Use [`JWT::from`] turn a [`String`] into a [`JWT`]..
31#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
32#[serde(transparent)]
33pub struct JWT {
34    inner: String,
35}
36
37/// Represents a set of claims made by a JWT.
38#[derive(Debug, Clone, Default, serde::Serialize)]
39#[serde(transparent)]
40pub struct Claims {
41    inner: serde_json::Map<String, serde_json::Value>,
42}
43
44impl Claims {
45    pub fn new() -> Self {
46        Default::default()
47    }
48
49    /// Checks that the claim with `name` meets the given `expectation`, and removes it from the
50    /// set.  The [Deserializer] is given ownership of the claim's contents, so for `V` you
51    /// probably want to use an owned type like [String] instead of [&str].
52    pub fn check<'s, V: Deserialize<'s>>(
53        mut self,
54        name: &'static str,
55        expectation: impl FnOnce(&'static str, Option<V>) -> Result<(), Error>,
56    ) -> Result<Self, Error> {
57        let value: Option<V> = self
58            .inner
59            .remove(name)
60            .map(V::deserialize)
61            .transpose()
62            .map_err(|err| Error::DeserializingClaim {
63                claim_name: name,
64                source: err,
65            })?;
66
67        expectation(name, value)?;
68
69        Ok(self)
70    }
71
72    /// Check that the claim with `name` exists and meets the given `expectation`,
73    /// removing the claim from the set afterwards.  Variation on [Claims::check].
74    pub fn check_present_and<'s, V: Deserialize<'s>>(
75        self,
76        name: &'static str,
77        expectation: impl FnOnce(&'static str, V) -> Result<(), Error>,
78    ) -> Result<Self, Error> {
79        self.check(
80            name,
81            |claim_name: &'static str, v: Option<V>| -> Result<(), Error> {
82                if v.is_none() {
83                    return Err(Error::MissingClaim(claim_name));
84                }
85                expectation(name, v.unwrap())
86            },
87        )
88    }
89
90    /// Checks that there is no claim with `name`.
91    pub fn check_no(self, name: &'static str) -> Result<Self, Error> {
92        if self.inner.contains_key(name) {
93            return Err(Error::UnexpectedClaim(name));
94        }
95
96        Ok(self)
97    }
98
99    /// Returns the contents of claim with `name`, if it exists, enabling manual checking.
100    pub fn extract<V: DeserializeOwned>(&mut self, name: &'static str) -> Result<Option<V>, Error> {
101        let Some(json_value) = self.inner.remove(name) else {
102            return Ok(None);
103        };
104
105        let deserialized_value =
106            V::deserialize(json_value).map_err(|err| Error::DeserializingClaim {
107                claim_name: name,
108                source: err,
109            })?;
110
111        Ok(Some(deserialized_value))
112    }
113
114    /// Removes named claim from this set, if present, effectively ignoring it.
115    pub fn ignore(mut self, name: &'static str) -> Self {
116        self.inner.remove(name);
117        self
118    }
119
120    /// [Self::check] for `iss` claim.
121    pub fn check_iss(
122        self,
123        expectation: impl FnOnce(&'static str, Option<String>) -> Result<(), Error>,
124    ) -> Result<Self, Error> {
125        self.check("iss", expectation)
126    }
127
128    /// [Self::check] for `sub` claim.
129    pub fn check_sub(
130        self,
131        expectation: impl FnOnce(&'static str, Option<String>) -> Result<(), Error>,
132    ) -> Result<Self, Error> {
133        self.check("sub", expectation)
134    }
135
136    /// Checks timestamps `iat`, `exp` and `nbf`. When present they should be a valid
137    /// [NumericDate], and the current moment should be between `nbf` and `exp`.
138    pub fn default_check_timestamps(self) -> Result<Self, Error> {
139        let now = NumericDate::now();
140
141        self.check(
142            "iat",
143            |_claim_name: &'static str, _iat: Option<NumericDate>| -> Result<(), Error> {
144                // When it is present, iat should be a valid NumericData, but it is otherwise ignored.
145                Ok(())
146            },
147        )?
148        .check(
149            "exp",
150            |_claim_name: &'static str, exp: Option<NumericDate>| -> Result<(), Error> {
151                // When `exp` is present, it should not be expired.
152                if let Some(exp) = exp
153                    && exp < now
154                {
155                    return Err(Error::Expired { when: exp });
156                }
157
158                Ok(())
159            },
160        )?
161        .check(
162            "nbf",
163            |_claim_name: &'static str, nbf: Option<NumericDate>| -> Result<(), Error> {
164                // When `nbf` is present, it should be in the past.
165                if let Some(nbf) = nbf
166                    && now < nbf
167                {
168                    return Err(Error::NotYetValid { valid_from: nbf });
169                }
170
171                Ok(())
172            },
173        )
174    }
175
176    /// Checks and removes `iat`, `exp`, and `nbf`, and makes sure
177    /// `sub` and `iss` have already been checked (i.e. are no longer present).
178    pub fn default_check_common_claims(self) -> Result<Self, Error> {
179        self.default_check_timestamps()?
180            .check_no("iss")?
181            .check_no("sub")
182    }
183
184    /// Deserializes remaining, custom, claims into a type `C` and calls the given `visitor` function on it.
185    ///
186    /// Will call [Claims::default_check_common_claims] first, checking `iat`, etc..
187    ///
188    /// We can, in general, not return `C` directly, because `C` might borrow from the
189    /// [Deserializer].
190    ///
191    /// For the caller's convenience, we pass along anything that's returned by the visitor.
192    ///
193    /// Consumes `self` to prevent deserializing to `C` twice.
194    pub fn visit_custom<C: DeserializeOwned, R>(
195        self,
196        visitor: impl FnOnce(C) -> R,
197    ) -> Result<R, Error> {
198        // we check common claims to ensure they are not ignored
199        let self_ = self.default_check_common_claims()?;
200
201        let jso = serde_json::Value::Object(self_.inner);
202        let claims: C = C::deserialize(&jso).map_err(|err| {
203            let jso_str = serde_json::to_string_pretty(&jso).unwrap();
204
205            if let Err(better_err) = serde_json::from_str::<C>(&jso_str) {
206                return Error::DeserializingClaims {
207                    source: better_err,
208                    claims: jso_str,
209                };
210            }
211
212            log::error!("something fishy is going on here with this faulty json");
213            Error::DeserializingClaims {
214                source: err,
215                claims: "".to_string(),
216            }
217        })?;
218
219        Ok(visitor(claims))
220    }
221
222    /// Like [Self::visit_custom], but returns `C` (which is not possible when `C` borrows from its
223    /// [Deserializer].)
224    pub fn into_custom<C: DeserializeOwned>(self) -> Result<C, Error> {
225        self.visit_custom(|c| c)
226    }
227
228    /// Creates new [Claims] from an object that serializes to a json map.
229    pub fn from_custom<C: Serialize>(claims: C) -> Result<Self, Error> {
230        let json_value = serde_json::to_value(claims).map_err(Error::SerializingClaims)?;
231
232        Ok(Self {
233            inner: match json_value {
234                serde_json::Value::Object(inner) => inner,
235                serde_json::Value::Null => {
236                    return Err(Error::ClaimsDontSerializeToMapButNull {
237                        claims_type: std::any::type_name::<C>(),
238                    });
239                }
240                _ => {
241                    return Err(Error::ClaimsDontSerializeToMap {
242                        claims_type: std::any::type_name::<C>(),
243                    });
244                }
245            },
246        })
247    }
248
249    /// Adds the named claim with the given value.  Returns an error when a claim with the same
250    /// name was already present.
251    pub fn claim<V: Serialize>(mut self, name: &'static str, value: V) -> Result<Self, Error> {
252        let old_value = self.inner.insert(
253            name.to_string(),
254            serde_json::to_value(value).map_err(|err| Error::SerializingClaim {
255                claim_name: name,
256                source: err,
257            })?,
258        );
259
260        if old_value.is_some() {
261            return Err(Error::ClaimAlreadyPresent(name));
262        }
263
264        Ok(self)
265    }
266
267    /// Adds the `iat` claim using the current timestamp
268    pub fn iat_now(self) -> Result<Self, Error> {
269        self.claim("iat", NumericDate::now())
270    }
271
272    /// Sets `exp` claim such that the jwt is valid for the given `duration`.
273    pub fn exp_after(self, duration: std::time::Duration) -> Result<Self, Error> {
274        self.claim("exp", NumericDate::now().add_clamp(duration.as_secs()))
275    }
276
277    /// Sets `nbf` to the current timestamp, minus 30 seconds leeway.
278    pub fn nbf(self) -> Result<Self, Error> {
279        self.claim("nbf", NumericDate::now().sub_clamp(30))
280    }
281
282    /// Signs these claims, returning a [`JWT`].
283    pub fn sign<SK: SigningKey>(&self, sk: &SK) -> Result<JWT, Error> {
284        JWT::create(&self.inner, sk)
285    }
286}
287
288/// Represents the value of the `iat`, `exp`, `nbf` claims.
289///
290/// According to RFC7519, it should be:
291///
292/// "A JSON numeric value representing the number of seconds from
293/// 1970-01-01T00:00:00Z UTC until the specified UTC date/time,
294/// ignoring leap seconds.  This is equivalent to the IEEE Std 1003.1,
295/// 2013 Edition [POSIX.1] definition "Seconds Since the Epoch", in
296/// which each day is accounted for by exactly 86400 seconds, other
297/// than that non-integer values can be represented."
298///
299/// But contrary to this, we reject negative timestamps — and timestamps beyond the invariant upper
300/// bound below (the last second of year 9999) — with an error, and silently round down
301/// non-negative decimals to the nearest u64.
302///
303/// # Invariant
304///
305/// `timestamp <= MAX_TIMESTAMP_SECS`, the last second of year 9999 and the largest instant
306/// representable in RFC3339.  Construction and arithmetic go through [`new_clamp`](Self::new_clamp),
307/// [`add_clamp`](Self::add_clamp) and [`sub_clamp`](Self::sub_clamp), which saturate into range; the
308/// `Deserialize` and `TryFrom<SystemTime>` impls instead *reject* an out-of-range value with an
309/// error.
310///
311/// The bound is a safety property, not just tidiness.  The `exp`/`nbf`/`iat` claims are
312/// attacker-controlled, and a value near `u64::MAX` overflows [`std::time::SystemTime`] — whose
313/// addition panics above `i64::MAX` seconds — the moment the date is formatted into a log or error
314/// message (e.g. the "not yet valid" message built from a bogus far-future `nbf`).  Staying in
315/// range also keeps `humantime` rendering the year correctly.  Because the invariant holds, the
316/// `From<&NumericDate> for SystemTime` conversion is total and lossless, so serializing and then
317/// deserializing a `NumericDate` round-trips faithfully.
318///
319/// The range operations are named (`*_clamp`) rather than `+`/`-`/`new` so the saturating behaviour
320/// is explicit at the call site and never silently surprises the reader.
321#[derive(Serialize, Default, Clone, Copy, Eq, PartialEq, Debug, PartialOrd, Ord)]
322#[serde(transparent)]
323pub struct NumericDate {
324    timestamp: u64,
325}
326
327/// Invariant upper bound of [`NumericDate`] (see its docs): `9999-12-31T23:59:59Z`.
328pub(crate) const MAX_TIMESTAMP_SECS: u64 = 253_402_300_799;
329
330impl NumericDate {
331    /// Creates a numeric date from `timestamp`, the number of seconds since the unix epoch
332    /// (ignoring leap seconds), saturating at `MAX_TIMESTAMP_SECS` to uphold the invariant.
333    pub fn new_clamp(timestamp: u64) -> Self {
334        Self {
335            timestamp: timestamp.min(MAX_TIMESTAMP_SECS),
336        }
337    }
338
339    /// Adds `secs` seconds, saturating at `MAX_TIMESTAMP_SECS`.
340    pub fn add_clamp(self, secs: u64) -> Self {
341        Self::new_clamp(self.timestamp.saturating_add(secs))
342    }
343
344    /// Subtracts `secs` seconds, saturating at zero.
345    pub fn sub_clamp(self, secs: u64) -> Self {
346        Self::new_clamp(self.timestamp.saturating_sub(secs))
347    }
348
349    /// Creates a numeric date representing the current moment
350    pub fn now() -> Self {
351        std::time::SystemTime::now()
352            .try_into()
353            .expect("system clock not between 1970 and 9999")
354    }
355
356    /// Returns the number of seconds this date is after the unix epoch.
357    pub fn timestamp(&self) -> u64 {
358        self.timestamp
359    }
360
361    /// Returns the UTC date for this timestamp
362    pub fn date(&self) -> String {
363        // Maybe not the most efficient implementation using humantime,
364        // but it saves an additional direct dependency
365        let mut datetime = humantime::format_rfc3339(self.into()).to_string();
366
367        let Some(idx) = datetime.find('T') else {
368            panic!("bug: expected date returned by humantime to contain a 'T'");
369        };
370
371        datetime.truncate(idx);
372
373        datetime
374    }
375}
376
377/// A value falls outside the [`NumericDate`] invariant range (before the unix epoch, or after the
378/// last second of year 9999).  Returned by fallible, non-clamping operations such as the
379/// `TryFrom<SystemTime>` conversion.
380#[derive(Debug, thiserror::Error)]
381#[error("value is out of NumericDate range (before 1970, or after year 9999)")]
382pub struct OutOfRange;
383
384impl TryFrom<std::time::SystemTime> for NumericDate {
385    type Error = OutOfRange;
386
387    /// Fallible (not [`From`] with a clamp) so a far-future `SystemTime` can never be silently
388    /// rewritten — even if a future caller feeds in a time that isn't the (sane) system clock.
389    fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
390        let secs = st
391            .duration_since(std::time::UNIX_EPOCH)
392            .map_err(|_| OutOfRange)?
393            .as_secs();
394
395        if secs > MAX_TIMESTAMP_SECS {
396            return Err(OutOfRange);
397        }
398
399        Ok(Self { timestamp: secs })
400    }
401}
402
403impl From<&NumericDate> for std::time::SystemTime {
404    fn from(nd: &NumericDate) -> Self {
405        // Total by the NumericDate invariant (`timestamp <= MAX_TIMESTAMP_SECS`, far below the
406        // SystemTime overflow point), so this never panics.
407        std::time::UNIX_EPOCH + std::time::Duration::from_secs(nd.timestamp)
408    }
409}
410
411impl fmt::Display for NumericDate {
412    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
413        write!(f, "{}", time_ext::format_time(self.into()))
414    }
415}
416
417impl<'de> Deserialize<'de> for NumericDate {
418    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
419        d.deserialize_u64(NumericDateVisitor {})
420    }
421}
422
423/// [Visitor] for the implementation of [Deserialize] for [NumericDate].
424struct NumericDateVisitor {}
425
426impl Visitor<'_> for NumericDateVisitor {
427    type Value = NumericDate;
428
429    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
430        write!(
431            f,
432            "a non-negative number no greater than {MAX_TIMESTAMP_SECS}"
433        )
434    }
435
436    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
437        // Reject out-of-range timestamps at this untrusted boundary (rather than clamping) so a
438        // bogus far-future claim is treated as invalid, not silently rewritten.
439        if v > MAX_TIMESTAMP_SECS {
440            return Err(E::invalid_value(serde::de::Unexpected::Unsigned(v), &self));
441        }
442        Ok(NumericDate::new_clamp(v))
443    }
444
445    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
446        if v < 0 {
447            return Err(E::invalid_value(serde::de::Unexpected::Signed(v), &self));
448        }
449
450        self.visit_u64(v as u64)
451    }
452
453    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> {
454        if v < 0.0 {
455            return Err(E::invalid_value(serde::de::Unexpected::Float(v), &self));
456        }
457
458        self.visit_u64(v as u64)
459    }
460
461    // NOTE: u/i128 are not supported by serde_json by default
462}
463
464impl From<String> for JWT {
465    fn from(s: String) -> Self {
466        Self { inner: s }
467    }
468}
469
470impl From<JWT> for String {
471    fn from(jwt: JWT) -> String {
472        jwt.inner
473    }
474}
475
476impl JWT {
477    /// Creates JWT from `claims` and [SigningKey] `key`.
478    ///
479    /// You can also use [`Claims::sign`]
480    pub fn create<C: Serialize, SK: SigningKey>(claims: &C, key: &SK) -> Result<JWT, Error> {
481        let to_be_signed: String = format!(
482            "{}.{}",
483            Base64UrlUnpadded::encode_string(
484                &serde_json::to_vec(&serde_json::json!({
485                    "alg": SK::ALG,
486                }))
487                .map_err(Error::SerializingHeader)?
488            ),
489            &Base64UrlUnpadded::encode_string(
490                &serde_json::to_vec(claims).map_err(Error::SerializingClaims)?
491            )
492        );
493        Ok(JWT::from(format!(
494            "{}.{}",
495            to_be_signed,
496            Base64UrlUnpadded::encode_string(
497                key.sign(to_be_signed.as_bytes())
498                    .map_err(Error::Signing)?
499                    .as_ref()
500            )
501        )))
502    }
503
504    /// Checks the validity of this jwt against the given [`VerifyingKey`] `key`, and the JSON syntax
505    /// of the claims.  Does not check the validity of the claims itself.
506    ///
507    /// If only the signature is invalid, the claims can be extracted from
508    /// [`Error::InvalidSignature::claims`].
509    pub fn open<VK: VerifyingKey>(&self, key: &VK) -> Result<Claims, Error> {
510        let s = &self.inner;
511
512        let last_dot_pos: usize = s.rfind('.').ok_or(Error::MissingDot)?;
513        let signed: &str = &s[..last_dot_pos];
514        let first_dot_pos: usize = signed.find('.').ok_or(Error::MissingDot)?;
515
516        // check header
517        let header_vec: Vec<u8> =
518            Base64UrlUnpadded::decode_vec(&s[..first_dot_pos]).map_err(Error::InvalidBase64)?;
519
520        let header: Header =
521            serde_json::from_slice(&header_vec).map_err(Error::DeserializingHeader)?;
522
523        VK::check_alg(&header.alg)?;
524
525        let signature: Vec<u8> =
526            Base64UrlUnpadded::decode_vec(&s[last_dot_pos + 1..]).map_err(Error::InvalidBase64)?;
527
528        // decode claims
529        let claims_vec: Vec<u8> = Base64UrlUnpadded::decode_vec(&signed[first_dot_pos + 1..])
530            .map_err(Error::InvalidBase64)?;
531
532        let mut d = serde_json::Deserializer::from_slice(&claims_vec);
533
534        let claims = Claims {
535            inner: serde_json::Map::<String, serde_json::Value>::deserialize(&mut d)
536                .map_err(Error::ClaimsNotJsonMap)?,
537        };
538
539        // check signature
540        if !key.is_valid_signature(signed.as_bytes(), signature) {
541            return Err(Error::InvalidSignature {
542                key: key.describe(),
543                claims,
544            });
545        }
546
547        Ok(claims)
548    }
549
550    pub fn as_str(&self) -> &str {
551        &self.inner
552    }
553
554    pub fn sha256(&self) -> sha2::Sha256 {
555        use sha2::Digest as _;
556        sha2::Sha256::new().chain_update(&self.inner)
557    }
558
559    pub fn id(&self) -> id::Id {
560        phcrypto::jwt_id(self)
561    }
562}
563
564impl fmt::Display for JWT {
565    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
566        write!(f, "{}", self.inner)
567    }
568}
569
570#[derive(thiserror::Error, Debug)]
571pub enum Error {
572    #[error("failed to serialize jwt header")]
573    SerializingHeader(#[source] serde_json::Error),
574
575    #[error("invalid jwt header")]
576    DeserializingHeader(#[source] serde_json::Error),
577
578    #[error("failed to serialize jwt claims")]
579    SerializingClaims(#[source] serde_json::Error),
580
581    #[error("failed to serialize claim {claim_name}")]
582    SerializingClaim {
583        claim_name: &'static str,
584        source: serde_json::Error,
585    },
586
587    #[error("claim {0} already present")]
588    ClaimAlreadyPresent(&'static str),
589
590    #[error("claims are not a valid json map")]
591    ClaimsNotJsonMap(#[source] serde_json::Error),
592
593    #[error("the given custom claims (of type {claims_type}) do not serialize to a json map")]
594    ClaimsDontSerializeToMap { claims_type: &'static str },
595
596    #[error(
597        "the given custom claims (of type {claims_type}) do not serialize to a json map, but to null. Hint: 'type Unit;' -> 'type Unit {{}}'"
598    )]
599    ClaimsDontSerializeToMapButNull { claims_type: &'static str },
600
601    #[error("invalid jwt claims: {source} in {claims}")]
602    DeserializingClaims {
603        source: serde_json::Error,
604        claims: String,
605    },
606
607    #[error("failed to deserialize claim {claim_name}")]
608    DeserializingClaim {
609        claim_name: &'static str,
610        source: serde_json::Error,
611    },
612
613    #[error("jwt contains unexpected/unhandled claim `{0}`")]
614    UnexpectedClaim(&'static str),
615
616    #[error("jwt is missing the claim `{0}'")]
617    MissingClaim(&'static str),
618
619    #[error("the claim `{claim_name}` is invalid")]
620    InvalidClaim {
621        claim_name: &'static str,
622        source: anyhow::Error,
623    },
624
625    #[error("expired at {when}")]
626    Expired { when: NumericDate },
627
628    #[error("only valid after {valid_from}")]
629    NotYetValid { valid_from: NumericDate },
630
631    #[error("signing jwt failed")]
632    Signing(#[source] anyhow::Error),
633
634    #[error("missing dot (.) in jwt (there should be two dots)")]
635    MissingDot,
636
637    #[error("jwt contains invalid unpadded urlsafe base64")]
638    InvalidBase64(#[source] base64ct::Error),
639
640    #[error("jwt signature is not valid (for this key, {key})")]
641    InvalidSignature { key: String, claims: Claims },
642
643    #[error("unexpected algorithm; got {got}, but expected {expected}")]
644    UnexpectedAlgorithm { got: String, expected: &'static str },
645}
646
647/// Signs `claims` using `key` yielding a JWT.
648//#[deprecated(note = "use JWT::create instead")] // TODO: reinstate
649pub fn sign<SK: SigningKey>(claims: &impl Serialize, key: &SK) -> anyhow::Result<String> {
650    Ok(JWT::create(claims, key)?.inner)
651}
652
653/// Gets the number of seconds since the Unix epoch,
654/// which is an appropriate value for the "iat" field.
655pub fn get_current_timestamp() -> u64 {
656    std::time::SystemTime::now()
657        .duration_since(std::time::UNIX_EPOCH)
658        .expect("system clock reports a time before the Unix epoch")
659        .as_secs()
660}
661
662/// Represents a JWT header.
663#[derive(Serialize, Deserialize, Debug)]
664#[serde(deny_unknown_fields)]
665struct Header<'a> {
666    #[serde(
667        rename = "typ",
668        skip_serializing, // don't include 'typ' when serializing
669        default // so 'typ' is optional
670    )]
671    _typ: HeaderType,
672
673    #[serde(borrow)]
674    alg: Cow<'a, str>,
675    // Add fields here when needed
676}
677
678/// Used to check that the `typ` field of the jwt header equals `JWT` (modulo case).
679#[derive(Default, Debug)]
680struct HeaderType {}
681
682impl<'de> Deserialize<'de> for HeaderType {
683    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
684        d.deserialize_str(HeaderType {})
685    }
686}
687
688impl Visitor<'_> for HeaderType {
689    type Value = Self;
690
691    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
692        write!(f, "the string \"JWT\" as \"typ\"")
693    }
694
695    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
696        if "JWT".eq_ignore_ascii_case(v) {
697            return Ok(self);
698        }
699
700        Err(E::invalid_value(serde::de::Unexpected::Str(v), &self))
701    }
702}
703
704/// Represents a key that can be used to sign a JWT.
705pub trait SigningKey: Key {
706    /// The result of signing, e.g. `[u8; 32]`.
707    type Signature: AsRef<[u8]>;
708
709    /// Returns a (non-base64-encoded) signature on `s`.
710    fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature>;
711
712    /// Returns JSON Web Key description of the associated public key.
713    fn jwk(&self) -> serde_json::Value;
714}
715
716/// Represents a key that can be used to verify the signature on a JWT.
717pub trait VerifyingKey: Key {
718    /// Verifies signature.
719    fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool;
720
721    /// Describe the key for use in errors
722    fn describe(&self) -> String;
723}
724
725/// What [SigningKey] and [VerifyingKey] have in common.
726pub trait Key {
727    /// value for `alg` in the JWT header
728    const ALG: &'static str;
729
730    /// Checks that `alg` equals `Self::ALG`.
731    ///
732    /// This method is overriden by [`IgnoreSignature`].
733    fn check_alg(alg: &str) -> Result<(), Error> {
734        if alg == Self::ALG {
735            return Ok(());
736        }
737        Err(Error::UnexpectedAlgorithm {
738            got: alg.to_string(),
739            expected: Self::ALG,
740        })
741    }
742}
743
744/// A [VerifyingKey] that neglects to check the signature and `alg` header.
745///
746/// Useful when the signature on a [JWT] can only be checked later on.
747pub struct IgnoreSignature;
748
749impl Key for IgnoreSignature {
750    const ALG: &'static str = "WARNING! This should never appear in the 'alg' field of a JWT.";
751
752    fn check_alg(_alg: &str) -> Result<(), Error> {
753        Ok(())
754    }
755}
756
757impl VerifyingKey for IgnoreSignature {
758    fn is_valid_signature(&self, _message: &[u8], _signature: Vec<u8>) -> bool {
759        true
760    }
761
762    fn describe(&self) -> String {
763        "n/a".into()
764    }
765}
766
767/// Implements signing JWTs using ed25519, See RFC8037.
768///
769/// ```
770/// use pubhubs::misc::jwt::SigningKey;
771/// use base64ct::{Base64UrlUnpadded, Encoding as _};
772///
773/// let mut privhex : String = r#"9d 61 b1 9d ef fd 5a 60 ba 84 4a f4 92 ec 2c c4
774///                44 49 c5 69 7b 32 69 19 70 3b ac 03 1c ae 7f 60"#.into();
775/// let mut pubhex : String = r#"d7 5a 98 01 82 b1 0a b7 d5 4b fe d3 c9 64 07 3a
776///               0e e1 72 f3 da a6 23 25 af 02 1a 68 f7 07 51 1a"#.into();
777/// privhex.retain(|c| !c.is_whitespace());
778/// pubhex.retain(|c| !c.is_whitespace());
779/// let privkey = base16ct::lower::decode_vec(privhex.as_bytes()).unwrap();
780/// let pubkey = base16ct::lower::decode_vec(pubhex.as_bytes()).unwrap();
781/// let sk = ed25519_dalek::SigningKey::try_from(privkey.as_slice()).unwrap();
782///
783/// assert_eq!(sk.verifying_key().to_bytes().as_slice(), pubkey.as_slice());
784///
785/// // See "A.4 Ed25519 Signing" from RFC8037.
786/// assert_eq!(
787///     Base64UrlUnpadded::encode_string(&SigningKey::sign(&sk,
788///             "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc".as_bytes()
789///         ).unwrap().as_ref()
790///     ),
791///     "hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg");
792///
793/// // See "A.3 JWK Thumbprint Canonicalization" from RFC8037.
794/// assert_eq!(sk.jwk(), serde_json::json!({
795///     "kty": "OKP",
796///     "alg": "EdDSA",
797///     "crv": "Ed25519",
798///     "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
799///     "use": "sig",
800/// }));
801/// ```
802impl SigningKey for ed25519_dalek::SigningKey {
803    type Signature = [u8; 64]; // ed25519_dalek::Signature no longer implements AsRef<[u8]>
804
805    fn sign(&self, s: &[u8]) -> anyhow::Result<[u8; 64]> {
806        Ok(ed25519_dalek::Signer::sign(self, s).to_bytes())
807    }
808
809    fn jwk(&self) -> serde_json::Value {
810        serde_json::json!({
811            "kty": "OKP", // not "EC", see RFC8037, Section 2.
812            "alg": Self::ALG,
813            "crv": "Ed25519",
814            "x": Base64UrlUnpadded::encode_string(AsRef::<ed25519_dalek::VerifyingKey>::as_ref(self).as_bytes()),
815            // parameter "d" must NOT be included, being the private key
816            "use": "sig",
817        })
818    }
819}
820
821impl Key for ed25519_dalek::SigningKey {
822    const ALG: &'static str = "EdDSA";
823}
824
825impl Key for ed25519_dalek::VerifyingKey {
826    const ALG: &'static str = "EdDSA";
827}
828
829impl VerifyingKey for ed25519_dalek::VerifyingKey {
830    fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
831        if let Ok(signature) = ed25519_dalek::Signature::from_slice(&signature) {
832            return ed25519_dalek::Verifier::verify(self, message, &signature).is_ok();
833        }
834        false
835    }
836
837    fn describe(&self) -> String {
838        base16ct::lower::encode_string(self.as_bytes().as_slice())
839    }
840}
841
842/// Key for SHA256 based HMAC
843#[derive(
844    serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, zeroize::ZeroizeOnDrop,
845)]
846#[serde(transparent)]
847pub struct HS256(#[serde(with = "serde_bytes")] pub Vec<u8>);
848
849/// Implements signing of JWTs using the sha256-hmac.
850/// ```
851/// use pubhubs::misc::jwt::{HS256, SigningKey};
852/// use base64ct::{Base64UrlUnpadded, Encoding as _};
853///
854/// // Example A.1.1 of RFC 7515
855/// let key = HS256(Base64UrlUnpadded::decode_vec("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow").unwrap().into());
856/// let result = key.sign("eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ".as_bytes()).unwrap();
857/// assert_eq!(Base64UrlUnpadded::encode_string(&result),
858///     "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk");
859/// ```
860impl SigningKey for HS256 {
861    type Signature = sha2::digest::generic_array::GenericArray<
862        u8,
863        <sha2::Sha256 as sha2::digest::OutputSizeUser>::OutputSize,
864    >;
865
866    fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature> {
867        let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&self.0)?;
868        mac.update(s);
869        Ok(mac.finalize().into_bytes())
870    }
871
872    fn jwk(&self) -> serde_json::Value {
873        panic!("HS256 has no public key to describe using JWK");
874    }
875}
876
877/// ```
878/// use pubhubs::misc::jwt::{HS256, VerifyingKey};
879/// use base64ct::{Base64UrlUnpadded, Encoding as _};
880///
881/// // Example A.1.1 of RFC 7515
882/// let key = HS256(Base64UrlUnpadded::decode_vec("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow").unwrap().into());
883/// assert!(key.is_valid_signature("eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ".as_bytes(), Base64UrlUnpadded::decode_vec("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk").unwrap()));
884/// ```
885impl VerifyingKey for HS256 {
886    fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
887        let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&self.0)
888            .expect("expect a sha256 mac to accept a key of any size");
889        mac.update(message);
890        mac.verify_slice(&signature).is_ok()
891    }
892
893    fn describe(&self) -> String {
894        base16ct::lower::encode_string(&self.0)
895    }
896}
897
898impl Key for HS256 {
899    const ALG: &'static str = "HS256";
900}
901
902/// RS256 public key  
903///
904/// **Note:** When a [`rsa::RsaPublicKey`] is used for signing under [`rsa::pkcs1v15`], a `prefix` is added
905/// to identify the hash used.  This additional information is encapsulated
906/// in a [`rsa::pkcs1v15::VerifyingKey`], which is just a [`rsa::RsaPublicKey`] plus `prefix`.
907#[derive(Clone, Debug)]
908pub struct RS256Vk(rsa::pkcs1v15::VerifyingKey<sha2::Sha256>);
909
910impl RS256Vk {
911    pub fn new(pk: rsa::RsaPublicKey) -> Self {
912        // NOTE: `pk.into()` does not work here:  https://github.com/RustCrypto/RSA/issues/234
913        Self(rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::new(pk))
914    }
915
916    pub fn from_public_key_pem(pem: &str) -> anyhow::Result<Self> {
917        Ok(Self(
918            rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::from_public_key_pem(pem)?,
919        ))
920    }
921
922    pub fn to_public_key_pem(&self) -> anyhow::Result<String> {
923        Ok(self.0.to_public_key_pem(Default::default())?)
924    }
925
926    /// Returns the underlying [`rsa::RsaPublicKey`], which completely determines this [`RS256Vk`].
927    pub fn as_rsa_pk(&self) -> &rsa::RsaPublicKey {
928        AsRef::<rsa::RsaPublicKey>::as_ref(&self.0)
929    }
930}
931
932/// For some reason [`PartialEq`] is not implemented for [`rsa::pkcs1v15::VerifyingKey`].
933///
934/// Maybe because checking equality of the `prefix` is often redundant, as it is in this case.
935impl PartialEq for RS256Vk {
936    fn eq(&self, other: &Self) -> bool {
937        // note: the `prefix` is fixed by our choice for `sha2::Sha256`
938        self.as_rsa_pk() == other.as_rsa_pk()
939    }
940}
941
942/// [`rsa::RsaPublicKey`] implements [`Eq`].`
943impl Eq for RS256Vk {}
944
945impl Key for RS256Vk {
946    const ALG: &'static str = "RS256";
947}
948
949impl VerifyingKey for RS256Vk {
950    fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
951        let signature: rsa::pkcs1v15::Signature = match signature.as_slice().try_into() {
952            Ok(signature) => signature,
953            Err(_) => return false,
954        };
955
956        self.0.verify(message, &signature).is_ok()
957    }
958
959    fn describe(&self) -> String {
960        format!("{self:?}")
961    }
962}
963
964/// RS256 private key
965#[derive(Clone, Debug)]
966pub struct RS256Sk(rsa::pkcs1v15::SigningKey<sha2::Sha256>);
967
968impl PartialEq for RS256Sk {
969    fn eq(&self, other: &Self) -> bool {
970        self.as_rsa_priv() == other.as_rsa_priv()
971    }
972}
973
974impl Eq for RS256Sk {}
975
976impl Key for RS256Sk {
977    const ALG: &'static str = RS256Vk::ALG;
978}
979
980impl SigningKey for RS256Sk {
981    type Signature = Box<[u8]>;
982
983    fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature> {
984        Ok(self.0.sign(s).to_bytes())
985    }
986
987    fn jwk(&self) -> serde_json::Value {
988        let rsa_pub: &rsa::RsaPublicKey = self.as_rsa_pub();
989
990        serde_json::json!({
991            "kty": "RSA",
992            "alg": Self::ALG,
993            "mod": Base64UrlUnpadded::encode_string(&rsa_pub.n().to_bytes_be()),
994            "exp": Base64UrlUnpadded::encode_string(&rsa_pub.e().to_bytes_be()),
995        })
996    }
997}
998
999impl RS256Sk {
1000    pub fn new(pk: rsa::RsaPrivateKey) -> Self {
1001        Self(rsa::pkcs1v15::SigningKey::<sha2::Sha256>::new(pk))
1002    }
1003
1004    pub fn random(bit_size: usize) -> anyhow::Result<Self> {
1005        Ok(Self::new(rsa::RsaPrivateKey::new(
1006            &mut rsa::rand_core::OsRng,
1007            bit_size,
1008        )?))
1009    }
1010
1011    pub fn from_pkcs8_pem(pem: &str) -> anyhow::Result<Self> {
1012        Ok(Self(
1013            rsa::pkcs1v15::SigningKey::<sha2::Sha256>::from_pkcs8_pem(pem)?,
1014        ))
1015    }
1016
1017    pub fn to_pkcs8_pem(&self) -> anyhow::Result<zeroize::Zeroizing<String>> {
1018        Ok(self.0.to_pkcs8_pem(Default::default())?)
1019    }
1020
1021    pub fn as_rsa_priv(&self) -> &rsa::RsaPrivateKey {
1022        AsRef::<rsa::RsaPrivateKey>::as_ref(&self.0)
1023    }
1024
1025    pub fn as_rsa_pub(&self) -> &rsa::RsaPublicKey {
1026        AsRef::<rsa::RsaPublicKey>::as_ref(self.as_rsa_priv())
1027    }
1028}
1029
1030/// Some common `FnOnce(Some(T))->Result<(),jwt::Error>`s for calling [`Claims::check`] and co.
1031pub mod expecting {
1032    use super::*;
1033
1034    /// Expectation that the claim is present and has the given value.
1035    pub fn exactly<T>(
1036        what: &T,
1037    ) -> impl (FnOnce(&'static str, Option<T::Owned>) -> Result<(), Error>) + use<'_, T>
1038    where
1039        T: std::fmt::Debug + PartialEq + ToOwned + ?Sized,
1040    {
1041        move |claim_name: &'static str, val_maybe: Option<T::Owned>| {
1042            if let Some(val) = val_maybe {
1043                if *what == *val.borrow() {
1044                    return Ok(());
1045                }
1046                return Err(Error::InvalidClaim {
1047                    claim_name,
1048                    source: anyhow::anyhow!("expected {:?}; got {:?}", what, val.borrow()),
1049                });
1050            }
1051            Err(Error::MissingClaim(claim_name))
1052        }
1053    }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use super::*;
1059
1060    #[test]
1061    fn test_jwt() {
1062        let jwt: JWT = serde_json::from_str("\"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\"").unwrap();
1063
1064        let key = HS256(
1065            base64ct::Base64UrlUnpadded::decode_vec("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow").unwrap(),
1066        );
1067
1068        let claims = jwt.open(&key).unwrap();
1069
1070        assert!(
1071            claims
1072                .clone()
1073                .into_custom::<serde_json::Value>()
1074                .unwrap_err()
1075                .to_string()
1076                .starts_with("expired at 2011-03-22T18:43:00Z (")
1077        );
1078
1079        assert_eq!(
1080            &claims
1081                .clone()
1082                .ignore("exp")
1083                .into_custom::<serde_json::Value>()
1084                .unwrap_err()
1085                .to_string(),
1086            "jwt contains unexpected/unhandled claim `iss`"
1087        );
1088
1089        #[derive(Deserialize, PartialEq, Eq, Debug)]
1090        #[serde(deny_unknown_fields)]
1091        struct Custom {
1092            #[serde(rename = "http://example.com/is_root")]
1093            is_root: bool,
1094        }
1095
1096        assert_eq!(
1097            claims
1098                .clone()
1099                .ignore("exp")
1100                .check_iss(
1101                    |_claim_name: &'static str, iss: Option<String>| -> Result<(), Error> {
1102                        assert_eq!(iss, Some("joe".to_string()));
1103                        Ok(())
1104                    }
1105                )
1106                .unwrap()
1107                .into_custom::<Custom>()
1108                .unwrap(),
1109            Custom { is_root: true }
1110        );
1111    }
1112
1113    #[test]
1114    fn test_header() {
1115        // no "alg"
1116        assert_eq!(
1117            serde_json::from_str::<Header>(r#"{}"#)
1118                .unwrap_err()
1119                .to_string(),
1120            "missing field `alg` at line 1 column 2".to_string()
1121        );
1122
1123        // invalid "typ"s
1124        assert_eq!(
1125            serde_json::from_str::<Header>(r#"{"typ": "not JWT", "alg": ""}"#)
1126                .unwrap_err()
1127                .to_string(),
1128            "invalid value: string \"not JWT\", expected the string \"JWT\" as \"typ\" at line 1 column 17".to_string()
1129        );
1130
1131        assert_eq!(
1132            serde_json::from_str::<Header>(r#"{"typ": 12,"alg":""}"#)
1133                .unwrap_err()
1134                .to_string(),
1135            "invalid type: integer `12`, expected the string \"JWT\" as \"typ\" at line 1 column 10".to_string()
1136        );
1137
1138        // JWT is accepted as "typ", whatever its case
1139        assert!(serde_json::from_str::<Header>(r#"{"typ": "jWT","alg":""}"#).is_ok());
1140
1141        // borrowing of "alg" value
1142        let header_a: Header = serde_json::from_str(r#"{"alg":"borrowed"}"#).unwrap();
1143        let header_b: Header = serde_json::from_str(r#"{"alg":"owned\u0020"}"#).unwrap();
1144
1145        assert!(matches!(header_a.alg, Cow::Borrowed(_)));
1146        assert!(matches!(header_b.alg, Cow::Owned(_)));
1147
1148        // unknown field
1149        assert_eq!(
1150            serde_json::from_str::<Header>(r#"{"alg":"", "unknown_field": ""}"#)
1151                .unwrap_err()
1152                .to_string(),
1153            "unknown field `unknown_field`, expected `typ` or `alg` at line 1 column 26"
1154                .to_string()
1155        );
1156    }
1157
1158    #[test]
1159    fn test_numericdate() {
1160        assert!(NumericDate::deserialize(serde_json::json!(0u64)).is_ok());
1161        assert!(NumericDate::deserialize(serde_json::json!(0f64)).is_ok());
1162        assert!(NumericDate::deserialize(serde_json::json!(0f32)).is_ok());
1163        assert!(NumericDate::deserialize(serde_json::json!(i64::MIN)).is_err());
1164        assert!(NumericDate::deserialize(serde_json::json!(f32::MIN)).is_err());
1165        assert!(NumericDate::deserialize(serde_json::json!(f64::MIN)).is_err());
1166        assert_eq!(
1167            NumericDate::deserialize(serde_json::json!(1.9))
1168                .unwrap()
1169                .timestamp,
1170            1
1171        );
1172
1173        // The invariant upper bound (year 9999) deserializes; anything past it is rejected rather
1174        // than accepted and later panicking when formatted (a bogus far-future `nbf` was a DoS).
1175        assert!(NumericDate::deserialize(serde_json::json!(MAX_TIMESTAMP_SECS)).is_ok());
1176        assert!(NumericDate::deserialize(serde_json::json!(MAX_TIMESTAMP_SECS + 1)).is_err());
1177        assert!(NumericDate::deserialize(serde_json::json!(u64::MAX)).is_err());
1178
1179        // The boundary value still converts to a SystemTime and renders — no panic.
1180        assert_eq!(
1181            NumericDate::new_clamp(MAX_TIMESTAMP_SECS).date(),
1182            "9999-12-31"
1183        );
1184    }
1185
1186    #[test]
1187    fn numericdate_clamps_and_saturates() {
1188        assert_eq!(
1189            NumericDate::new_clamp(u64::MAX).timestamp(),
1190            MAX_TIMESTAMP_SECS
1191        );
1192        assert_eq!(
1193            NumericDate::new_clamp(0).add_clamp(u64::MAX).timestamp(),
1194            MAX_TIMESTAMP_SECS
1195        );
1196        assert_eq!(NumericDate::new_clamp(5).sub_clamp(10).timestamp(), 0);
1197    }
1198
1199    #[test]
1200    fn test_rs256() {
1201        // from appendix A.2 of RFC 7515
1202        let sk = RS256Sk::new(
1203            rsa::RsaPrivateKey::from_components(
1204                // n
1205                rsa::BigUint::from_bytes_be(
1206                    &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1207                        "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx",
1208                        "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs",
1209                        "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH",
1210                        "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV",
1211                        "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8",
1212                        "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
1213                    ))
1214                    .unwrap(),
1215                ),
1216                // e
1217                rsa::BigUint::from_bytes_be(
1218                    &base64ct::Base64UrlUnpadded::decode_vec("AQAB").unwrap(),
1219                ),
1220                // d
1221                rsa::BigUint::from_bytes_be(
1222                    &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1223                        "Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I",
1224                        "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0",
1225                        "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn",
1226                        "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT",
1227                        "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh",
1228                        "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",
1229                    ))
1230                    .unwrap(),
1231                ),
1232                // primes
1233                vec![
1234                    // p
1235                    rsa::BigUint::from_bytes_be(
1236                        &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1237                            "4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi",
1238                            "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG",
1239                            "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",
1240                        ))
1241                        .unwrap(),
1242                    ),
1243                    // q
1244                    rsa::BigUint::from_bytes_be(
1245                        &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1246                            "uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa",
1247                            "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA",
1248                            "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",
1249                        ))
1250                        .unwrap(),
1251                    ),
1252                ],
1253            )
1254            .unwrap(),
1255        );
1256
1257        let to_sign: &str = concat!(
1258            "eyJhbGciOiJSUzI1NiJ9",
1259            ".",
1260            "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
1261            "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
1262        );
1263
1264        let signature = sk.sign(to_sign.as_bytes()).unwrap();
1265
1266        assert_eq!(
1267            signature.as_ref(),
1268            &Base64UrlUnpadded::decode_vec(concat!(
1269                "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7",
1270                "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4",
1271                "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K",
1272                "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv",
1273                "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB",
1274                "p0igcN_IoypGlUPQGe77Rw",
1275            ))
1276            .unwrap()
1277        );
1278
1279        let jwt: JWT = concat!(
1280            "eyJhbGciOiJSUzI1NiJ9",
1281            ".",
1282            "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
1283            "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
1284            ".",
1285            "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7",
1286            "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4",
1287            "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K",
1288            "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv",
1289            "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB",
1290            "p0igcN_IoypGlUPQGe77Rw",
1291        )
1292        .to_string()
1293        .into();
1294
1295        let pk = RS256Vk::new(sk.as_rsa_pub().clone());
1296
1297        let _ = jwt.open(&pk).unwrap();
1298    }
1299}