Skip to main content

gcloud_sdk/
id_token_verify.rs

1//! Verification of Google-signed ID tokens, for the receiving side of a
2//! service-to-service call.
3//!
4//! Signatures are checked with the crypto provider of the `jwt-aws-lc-rs` or
5//! `jwt-rust-crypto` feature. Without either, the application installs a `jsonwebtoken`
6//! crypto provider itself.
7
8use std::collections::HashMap;
9use std::fmt;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use async_trait::async_trait;
14use jiff::{SignedDuration, Timestamp};
15use jsonwebtoken::{Algorithm, DecodingKey, Validation};
16use tokio::sync::{Mutex, RwLock};
17use tracing::*;
18
19use crate::error::ErrorKind;
20use crate::IdTokenAudience;
21
22#[cfg(feature = "axum")]
23pub use crate::axum_layer::{VerifyIdToken, VerifyIdTokenLayer};
24/// The `jsonwebtoken` crate of [`JwkSet`] and of [`InvalidIdToken::Malformed`]'s error, so
25/// that an [`IdTokenKeysSource`] needs no `jsonwebtoken` dependency of its own.
26pub use jsonwebtoken;
27pub use jsonwebtoken::jwk::JwkSet;
28
29/// Google's ID token signing keys, the `jwks_uri` of
30/// <https://accounts.google.com/.well-known/openid-configuration>.
31const GOOGLE_KEYS_URL: &str = "https://www.googleapis.com/oauth2/v3/certs";
32
33/// <https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken>
34const GOOGLE_ISSUERS: [&str; 2] = ["https://accounts.google.com", "accounts.google.com"];
35
36/// Clock skew allowed on `exp`, `nbf` and `iat`.
37const LEEWAY: SignedDuration = SignedDuration::from_secs(30);
38
39/// How long keys are used when their response carries no `Cache-Control: max-age`.
40const KEYS_DEFAULT_MAX_AGE: Duration = Duration::from_secs(5 * 60);
41
42/// The longest keys are used without a fetch, whatever their `max-age`.
43const KEYS_MAX_AGE_LIMIT: Duration = Duration::from_secs(24 * 60 * 60);
44
45/// The shortest time between two fetches caused by unknown key IDs or by failures, so
46/// that tokens with made-up key IDs cannot make the verifier hammer the keys endpoint.
47const REFETCH_INTERVAL: Duration = Duration::from_secs(30);
48
49const KEYS_FETCH_TIMEOUT: Duration = Duration::from_secs(10);
50
51/// The email of the principal an ID token was minted for: a service account, or a user
52/// for tokens minted with user credentials.
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct PrincipalEmail(String);
55
56impl PrincipalEmail {
57    pub fn new(email: impl Into<String>) -> Self {
58        Self(email.into())
59    }
60}
61
62impl fmt::Display for PrincipalEmail {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.write_str(&self.0)
65    }
66}
67
68/// The claims of an ID token whose signature, issuer, audience and lifetime
69/// [`IdTokenVerifier::verify`] checked. Only the verifier creates it.
70#[derive(Debug, Clone)]
71pub struct VerifiedIdToken {
72    audience: IdTokenAudience,
73    subject: String,
74    email: Option<PrincipalEmail>,
75    email_verified: Option<bool>,
76    hosted_domain: Option<String>,
77    expires_at: Timestamp,
78}
79
80impl VerifiedIdToken {
81    /// The `aud` claim, which is the verifier's audience.
82    pub fn audience(&self) -> &IdTokenAudience {
83        &self.audience
84    }
85
86    /// The `sub` claim: the principal's unique, stable ID.
87    pub fn subject(&self) -> &str {
88        &self.subject
89    }
90
91    /// The `email` claim, present when the token was minted with the principal's email,
92    /// whether or not Google verified it.
93    pub fn email(&self) -> Option<&PrincipalEmail> {
94        self.email.as_ref()
95    }
96
97    /// The `email_verified` claim.
98    pub fn email_verified(&self) -> Option<bool> {
99        self.email_verified
100    }
101
102    /// The email when the token says Google verified it: the principal to authorize.
103    pub fn verified_email(&self) -> Option<&PrincipalEmail> {
104        match self.email_verified {
105            Some(true) => self.email.as_ref(),
106            Some(false) | None => None,
107        }
108    }
109
110    /// The `hd` claim: the Google Workspace or Cloud Identity domain of a user.
111    pub fn hosted_domain(&self) -> Option<&str> {
112        self.hosted_domain.as_deref()
113    }
114
115    /// The `exp` claim.
116    pub fn expires_at(&self) -> Timestamp {
117        self.expires_at
118    }
119}
120
121/// Why [`IdTokenVerifier::verify`] refused a token.
122#[derive(Debug)]
123pub enum IdTokenVerifyError {
124    /// The token is not a valid Google ID token for the audience; the caller is not
125    /// authenticated.
126    InvalidToken(InvalidIdToken),
127    /// The signing keys could not be fetched, so the token could not be checked; it may
128    /// be valid. A failed fetch is
129    /// reported to every verification for a short interval before the next fetch, hence
130    /// the shared error.
131    KeysUnavailable(Arc<crate::error::Error>),
132}
133
134impl fmt::Display for IdTokenVerifyError {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::InvalidToken(reason) => write!(f, "invalid ID token: {reason}"),
138            Self::KeysUnavailable(error) => {
139                write!(f, "ID token signing keys are unavailable: {error}")
140            }
141        }
142    }
143}
144
145impl std::error::Error for IdTokenVerifyError {
146    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147        match self {
148            Self::InvalidToken(reason) => Some(reason),
149            Self::KeysUnavailable(error) => Some(error.as_ref()),
150        }
151    }
152}
153
154impl From<InvalidIdToken> for IdTokenVerifyError {
155    fn from(reason: InvalidIdToken) -> Self {
156        Self::InvalidToken(reason)
157    }
158}
159
160/// What is wrong with an invalid ID token.
161#[derive(Debug)]
162#[non_exhaustive]
163pub enum InvalidIdToken {
164    /// Not an RS256 JWT, or its claims are missing or of the wrong type.
165    Malformed(jsonwebtoken::errors::Error),
166    /// The key ID in the header, if any, is not among Google's signing keys.
167    UnknownKey(Option<String>),
168    /// The signature does not match the key.
169    BadSignature,
170    /// `exp` has passed.
171    Expired,
172    /// `nbf` or `iat` is in the future.
173    NotYetValid,
174    /// `aud` is not the verifier's audience.
175    WrongAudience,
176    /// `iss` is not Google.
177    WrongIssuer,
178}
179
180impl fmt::Display for InvalidIdToken {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match self {
183            Self::Malformed(error) => write!(f, "malformed token: {error}"),
184            Self::UnknownKey(Some(kid)) => write!(f, "unknown signing key {kid}"),
185            Self::UnknownKey(None) => write!(f, "no signing key ID"),
186            Self::BadSignature => write!(f, "bad signature"),
187            Self::Expired => write!(f, "expired"),
188            Self::NotYetValid => write!(f, "not yet valid"),
189            Self::WrongAudience => write!(f, "wrong audience"),
190            Self::WrongIssuer => write!(f, "wrong issuer"),
191        }
192    }
193}
194
195impl std::error::Error for InvalidIdToken {
196    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
197        match self {
198            Self::Malformed(error) => Some(error),
199            _ => None,
200        }
201    }
202}
203
204impl From<jsonwebtoken::errors::Error> for InvalidIdToken {
205    fn from(error: jsonwebtoken::errors::Error) -> Self {
206        use jsonwebtoken::errors::ErrorKind as JwtErrorKind;
207        match error.kind() {
208            JwtErrorKind::InvalidSignature => Self::BadSignature,
209            JwtErrorKind::ExpiredSignature => Self::Expired,
210            JwtErrorKind::ImmatureSignature => Self::NotYetValid,
211            JwtErrorKind::InvalidAudience => Self::WrongAudience,
212            JwtErrorKind::InvalidIssuer => Self::WrongIssuer,
213            _ => Self::Malformed(error),
214        }
215    }
216}
217
218/// Signing keys, and how long they may be used before they are fetched again.
219pub struct IdTokenKeys {
220    keys: JwkSet,
221    max_age: Duration,
222}
223
224impl IdTokenKeys {
225    /// `keys` may be used for `max_age` after they are fetched, up to a day. Only keys
226    /// with a key ID (`kid`) are used.
227    pub fn new(keys: JwkSet, max_age: Duration) -> Self {
228        Self { keys, max_age }
229    }
230}
231
232/// Where an [`IdTokenVerifier`] gets its signing keys: Google's published keys by
233/// default, or a local key in tests.
234#[async_trait]
235pub trait IdTokenKeysSource: Send + Sync {
236    /// The signing keys as of now. An error means the keys are unavailable.
237    async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys>;
238}
239
240/// Google's published signing keys, fetched over HTTPS.
241struct GoogleKeys {
242    client: reqwest::Client,
243    url: String,
244}
245
246#[async_trait]
247impl IdTokenKeysSource for GoogleKeys {
248    async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys> {
249        let response = self.client.get(&self.url).send().await?;
250        let status = response.status();
251        if !status.is_success() {
252            return Err(ErrorKind::HttpStatus(status).into());
253        }
254        let max_age = cache_max_age(response.headers()).unwrap_or(KEYS_DEFAULT_MAX_AGE);
255        let keys = response.json::<JwkSet>().await?;
256        Ok(IdTokenKeys::new(keys, max_age))
257    }
258}
259
260/// The `max-age` directive of a response's `Cache-Control` header.
261fn cache_max_age(headers: &reqwest::header::HeaderMap) -> Option<Duration> {
262    let cache_control = headers.get(reqwest::header::CACHE_CONTROL)?.to_str().ok()?;
263    cache_control
264        .split(',')
265        .find_map(|directive| {
266            let (name, seconds) = directive.split_once('=')?;
267            name.trim()
268                .eq_ignore_ascii_case("max-age")
269                .then_some(seconds)
270        })
271        .and_then(|seconds| seconds.trim().parse().ok())
272        .map(Duration::from_secs)
273}
274
275/// Verifies Google-signed ID tokens for one audience: the RS256 signature against
276/// Google's signing keys, `iss`, `aud`, and `exp`, `nbf` and `iat` with 30 seconds of
277/// leeway.
278///
279/// Keys are fetched on first use and cached for the `max-age` of their response. A
280/// token signed with a key that is not cached causes a refetch, at most once every 30
281/// seconds. When a fetch fails, cached keys stay in use until their `max-age` passes.
282/// Share one verifier, behind an `Arc`, across requests.
283pub struct IdTokenVerifier {
284    validation: Validation,
285    keys_source: Box<dyn IdTokenKeysSource>,
286    cache: RwLock<KeyCache>,
287    /// Held while fetching, so that concurrent verifications fetch once.
288    fetching: Mutex<()>,
289    refetch_interval: Duration,
290}
291
292impl IdTokenVerifier {
293    /// A verifier that fetches Google's signing keys from
294    /// <https://www.googleapis.com/oauth2/v3/certs>. Fails only when the HTTP client
295    /// cannot be built.
296    pub fn new(audience: IdTokenAudience) -> crate::error::Result<Self> {
297        let client = reqwest::Client::builder()
298            .user_agent(crate::GCLOUD_SDK_USER_AGENT)
299            .timeout(KEYS_FETCH_TIMEOUT)
300            .https_only(true)
301            .build()?;
302        Ok(Self::with_keys_source(
303            audience,
304            GoogleKeys {
305                client,
306                url: GOOGLE_KEYS_URL.to_string(),
307            },
308        ))
309    }
310
311    /// A verifier that gets its signing keys from `keys_source`, such as a fixed local
312    /// key in tests. The other checks are the same as [`new`](Self::new)'s.
313    pub fn with_keys_source(
314        audience: IdTokenAudience,
315        keys_source: impl IdTokenKeysSource + 'static,
316    ) -> Self {
317        crate::jwt_crypto::ensure_provider();
318        let mut validation = Validation::new(Algorithm::RS256);
319        validation.set_audience(&[audience.as_str()]);
320        validation.set_issuer(&GOOGLE_ISSUERS);
321        validation.set_required_spec_claims(&["exp", "iss", "aud", "sub"]);
322        validation.leeway = LEEWAY.as_secs().unsigned_abs();
323        validation.validate_nbf = true;
324        Self {
325            validation,
326            keys_source: Box::new(keys_source),
327            cache: RwLock::new(KeyCache::default()),
328            fetching: Mutex::new(()),
329            refetch_interval: REFETCH_INTERVAL,
330        }
331    }
332
333    /// Verifies `token`, the bearer token of an `authorization` header, and returns its
334    /// claims.
335    pub async fn verify(&self, token: &str) -> Result<VerifiedIdToken, IdTokenVerifyError> {
336        let header = jsonwebtoken::decode_header(token).map_err(InvalidIdToken::Malformed)?;
337        if header.alg != Algorithm::RS256 {
338            return Err(InvalidIdToken::Malformed(
339                jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into(),
340            )
341            .into());
342        }
343        let kid = header.kid.ok_or(InvalidIdToken::UnknownKey(None))?;
344        let key = self.key(kid).await?;
345        let claims = jsonwebtoken::decode::<GoogleIdTokenClaims>(token, &key, &self.validation)
346            .map_err(InvalidIdToken::from)?
347            .claims;
348        // jsonwebtoken checks `exp` and `nbf` but not `iat`.
349        if claims.iat > Timestamp::now() + LEEWAY {
350            return Err(InvalidIdToken::NotYetValid.into());
351        }
352        Ok(claims.into())
353    }
354
355    async fn key(&self, kid: String) -> Result<Arc<DecodingKey>, IdTokenVerifyError> {
356        if let Some(found) = self.cache.read().await.lookup(&kid, self.refetch_interval) {
357            return found;
358        }
359        let _fetching = self.fetching.lock().await;
360        // Another verification may have fetched while this one waited for the lock.
361        if let Some(found) = self.cache.read().await.lookup(&kid, self.refetch_interval) {
362            return found;
363        }
364        let fetched = self.keys_source.fetch_keys().await;
365        let mut cache = self.cache.write().await;
366        // Set only once the fetch is over: verifications arriving during it must wait on
367        // `fetching` for its keys, not be refused as inside the refetch interval.
368        cache.last_fetch = Some(Instant::now());
369        match fetched {
370            Ok(keys) => {
371                cache.store(keys);
372                match cache.keys.get(&kid) {
373                    Some(key) => Ok(Arc::clone(key)),
374                    None => Err(InvalidIdToken::UnknownKey(Some(kid)).into()),
375                }
376            }
377            Err(error) => {
378                warn!(%error, "Fetching ID token signing keys failed");
379                let error = Arc::new(error);
380                cache.last_failure = Some(Arc::clone(&error));
381                Err(IdTokenVerifyError::KeysUnavailable(error))
382            }
383        }
384    }
385}
386
387#[derive(serde::Deserialize)]
388struct GoogleIdTokenClaims {
389    aud: String,
390    sub: String,
391    #[serde(with = "jiff::fmt::serde::timestamp::second::required")]
392    exp: Timestamp,
393    #[serde(with = "jiff::fmt::serde::timestamp::second::required")]
394    iat: Timestamp,
395    email: Option<String>,
396    email_verified: Option<bool>,
397    hd: Option<String>,
398}
399
400impl From<GoogleIdTokenClaims> for VerifiedIdToken {
401    fn from(claims: GoogleIdTokenClaims) -> Self {
402        Self {
403            audience: IdTokenAudience::new(claims.aud),
404            subject: claims.sub,
405            email: claims.email.map(PrincipalEmail::new),
406            email_verified: claims.email_verified,
407            hosted_domain: claims.hd,
408            expires_at: claims.exp,
409        }
410    }
411}
412
413/// The signing keys by key ID, and what the verifier knows about fetching them.
414#[derive(Default)]
415struct KeyCache {
416    keys: HashMap<String, Arc<DecodingKey>>,
417    /// When the keys stop being fresh; `None` until a fetch succeeds.
418    fresh_until: Option<Instant>,
419    /// When the last fetch finished, whether it succeeded or not.
420    last_fetch: Option<Instant>,
421    /// The error of the last fetch, when it failed.
422    last_failure: Option<Arc<crate::error::Error>>,
423}
424
425impl KeyCache {
426    /// The outcome for `kid` without fetching, or `None` when the keys must be fetched.
427    fn lookup(
428        &self,
429        kid: &str,
430        refetch_interval: Duration,
431    ) -> Option<Result<Arc<DecodingKey>, IdTokenVerifyError>> {
432        let now = Instant::now();
433        let fresh = self.fresh_until.is_some_and(|until| now < until);
434        if fresh {
435            if let Some(key) = self.keys.get(kid) {
436                return Some(Ok(Arc::clone(key)));
437            }
438        }
439        let fetched_recently = self
440            .last_fetch
441            .is_some_and(|at| now.duration_since(at) < refetch_interval);
442        if !fetched_recently {
443            return None;
444        }
445        match &self.last_failure {
446            Some(error) => Some(Err(IdTokenVerifyError::KeysUnavailable(Arc::clone(error)))),
447            None if fresh => Some(Err(InvalidIdToken::UnknownKey(Some(kid.to_string())).into())),
448            // Keys whose max-age is shorter than the refetch interval.
449            None => None,
450        }
451    }
452
453    fn store(&mut self, fetched: IdTokenKeys) {
454        self.keys = fetched
455            .keys
456            .keys
457            .iter()
458            .filter_map(|jwk| {
459                let kid = jwk.common.key_id.clone()?;
460                match DecodingKey::from_jwk(jwk) {
461                    Ok(key) => Some((kid, Arc::new(key))),
462                    Err(error) => {
463                        warn!(%kid, %error, "Skipping an unusable ID token signing key");
464                        None
465                    }
466                }
467            })
468            .collect();
469        self.fresh_until = Some(Instant::now() + fetched.max_age.min(KEYS_MAX_AGE_LIMIT));
470        self.last_failure = None;
471    }
472}
473
474// The tests sign tokens, which needs a crypto provider.
475#[cfg(all(test, any(feature = "jwt-aws-lc-rs", feature = "jwt-rust-crypto")))]
476mod tests {
477    use super::*;
478    use crate::test_support::{signed_jwt, test_jwk_set, StubResponse, StubServer};
479    use std::collections::VecDeque;
480    use std::sync::atomic::{AtomicUsize, Ordering};
481
482    const AUDIENCE: &str = "https://orders-abc123-ew.a.run.app";
483    const CALLER: &str = "caller@my-project.iam.gserviceaccount.com";
484    const CURRENT_KEY: &str = "current-key";
485    const ROTATED_KEY: &str = "rotated-key";
486
487    fn claims() -> serde_json::Value {
488        let now = Timestamp::now();
489        serde_json::json!({
490            "iss": "https://accounts.google.com",
491            "aud": AUDIENCE,
492            "sub": "112233445566778899",
493            "email": CALLER,
494            "email_verified": true,
495            "iat": now.as_second(),
496            "exp": (now + SignedDuration::from_hours(1)).as_second(),
497        })
498    }
499
500    /// Answers each fetch with the next scripted result and counts the fetches.
501    struct ScriptedKeys {
502        results: std::sync::Mutex<VecDeque<crate::error::Result<IdTokenKeys>>>,
503        fetches: Arc<AtomicUsize>,
504        fetch_duration: Duration,
505    }
506
507    impl ScriptedKeys {
508        fn new(results: Vec<crate::error::Result<IdTokenKeys>>) -> (Self, Arc<AtomicUsize>) {
509            let fetches = Arc::new(AtomicUsize::new(0));
510            let source = Self {
511                results: std::sync::Mutex::new(results.into()),
512                fetches: Arc::clone(&fetches),
513                fetch_duration: Duration::ZERO,
514            };
515            (source, fetches)
516        }
517    }
518
519    #[async_trait]
520    impl IdTokenKeysSource for ScriptedKeys {
521        async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys> {
522            self.fetches.fetch_add(1, Ordering::SeqCst);
523            tokio::time::sleep(self.fetch_duration).await;
524            self.results
525                .lock()
526                .unwrap()
527                .pop_front()
528                .expect("a scripted result for every fetch")
529        }
530    }
531
532    fn keys(key_ids: &[&str]) -> crate::error::Result<IdTokenKeys> {
533        Ok(IdTokenKeys::new(
534            test_jwk_set(key_ids),
535            Duration::from_secs(3600),
536        ))
537    }
538
539    fn unavailable() -> crate::error::Result<IdTokenKeys> {
540        Err(ErrorKind::HttpStatus(reqwest::StatusCode::SERVICE_UNAVAILABLE).into())
541    }
542
543    fn verifier(
544        results: Vec<crate::error::Result<IdTokenKeys>>,
545    ) -> (IdTokenVerifier, Arc<AtomicUsize>) {
546        let (source, fetches) = ScriptedKeys::new(results);
547        (
548            IdTokenVerifier::with_keys_source(IdTokenAudience::new(AUDIENCE), source),
549            fetches,
550        )
551    }
552
553    #[tokio::test]
554    async fn valid_token_yields_its_claims() {
555        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
556        let claims = claims();
557
558        let verified = verifier
559            .verify(&signed_jwt(CURRENT_KEY, &claims))
560            .await
561            .unwrap();
562
563        assert_eq!(verified.audience(), &IdTokenAudience::new(AUDIENCE));
564        assert_eq!(verified.subject(), "112233445566778899");
565        assert_eq!(
566            verified.verified_email(),
567            Some(&PrincipalEmail::new(CALLER))
568        );
569        assert_eq!(verified.email_verified(), Some(true));
570        assert_eq!(verified.hosted_domain(), None);
571        assert_eq!(
572            verified.expires_at().as_second(),
573            claims["exp"].as_i64().unwrap()
574        );
575    }
576
577    #[tokio::test]
578    async fn expired_token_is_invalid() {
579        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
580        let mut expired = claims();
581        expired["exp"] = (Timestamp::now() - SignedDuration::from_mins(5))
582            .as_second()
583            .into();
584
585        let err = verifier
586            .verify(&signed_jwt(CURRENT_KEY, &expired))
587            .await
588            .unwrap_err();
589
590        assert!(matches!(
591            err,
592            IdTokenVerifyError::InvalidToken(InvalidIdToken::Expired)
593        ));
594    }
595
596    #[tokio::test]
597    async fn token_for_another_audience_is_invalid() {
598        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
599        let mut other_audience = claims();
600        other_audience["aud"] = "https://billing-abc123-ew.a.run.app".into();
601
602        let err = verifier
603            .verify(&signed_jwt(CURRENT_KEY, &other_audience))
604            .await
605            .unwrap_err();
606
607        assert!(matches!(
608            err,
609            IdTokenVerifyError::InvalidToken(InvalidIdToken::WrongAudience)
610        ));
611    }
612
613    #[tokio::test]
614    async fn token_from_another_issuer_is_invalid() {
615        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
616        let mut other_issuer = claims();
617        other_issuer["iss"] = "https://issuer.example.com".into();
618
619        let err = verifier
620            .verify(&signed_jwt(CURRENT_KEY, &other_issuer))
621            .await
622            .unwrap_err();
623
624        assert!(matches!(
625            err,
626            IdTokenVerifyError::InvalidToken(InvalidIdToken::WrongIssuer)
627        ));
628    }
629
630    fn assert_invalid_algorithm(err: IdTokenVerifyError) {
631        assert!(matches!(
632            err,
633            IdTokenVerifyError::InvalidToken(InvalidIdToken::Malformed(ref error))
634                if matches!(error.kind(), jsonwebtoken::errors::ErrorKind::InvalidAlgorithm)
635        ));
636    }
637
638    #[tokio::test]
639    async fn token_signed_with_the_public_key_as_hmac_secret_is_invalid() {
640        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
641        let public_key = serde_json::to_vec(&test_jwk_set(&[CURRENT_KEY]).keys[0]).unwrap();
642        let header = jsonwebtoken::Header {
643            kid: Some(CURRENT_KEY.to_string()),
644            ..jsonwebtoken::Header::new(Algorithm::HS256)
645        };
646        let token = jsonwebtoken::encode(
647            &header,
648            &claims(),
649            &jsonwebtoken::EncodingKey::from_secret(&public_key),
650        )
651        .unwrap();
652
653        assert_invalid_algorithm(verifier.verify(&token).await.unwrap_err());
654    }
655
656    #[tokio::test]
657    async fn token_signed_with_another_rsa_algorithm_is_invalid() {
658        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
659        let header = jsonwebtoken::Header {
660            kid: Some(CURRENT_KEY.to_string()),
661            ..jsonwebtoken::Header::new(Algorithm::RS512)
662        };
663        let key = jsonwebtoken::EncodingKey::from_rsa_pem(
664            crate::test_support::TEST_RSA_PRIVATE_KEY.as_bytes(),
665        )
666        .unwrap();
667        let token = jsonwebtoken::encode(&header, &claims(), &key).unwrap();
668
669        assert_invalid_algorithm(verifier.verify(&token).await.unwrap_err());
670    }
671
672    #[tokio::test]
673    async fn token_without_a_key_id_is_invalid() {
674        let (verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY])]);
675        let key = jsonwebtoken::EncodingKey::from_rsa_pem(
676            crate::test_support::TEST_RSA_PRIVATE_KEY.as_bytes(),
677        )
678        .unwrap();
679        let token = jsonwebtoken::encode(
680            &jsonwebtoken::Header::new(Algorithm::RS256),
681            &claims(),
682            &key,
683        )
684        .unwrap();
685
686        let err = verifier.verify(&token).await.unwrap_err();
687
688        assert!(matches!(
689            err,
690            IdTokenVerifyError::InvalidToken(InvalidIdToken::UnknownKey(None))
691        ));
692        assert_eq!(fetches.load(Ordering::SeqCst), 0);
693    }
694
695    #[tokio::test]
696    async fn token_issued_in_the_future_is_not_yet_valid() {
697        let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
698        let mut issued_later = claims();
699        issued_later["iat"] = (Timestamp::now() + SignedDuration::from_mins(5))
700            .as_second()
701            .into();
702
703        let err = verifier
704            .verify(&signed_jwt(CURRENT_KEY, &issued_later))
705            .await
706            .unwrap_err();
707
708        assert!(matches!(
709            err,
710            IdTokenVerifyError::InvalidToken(InvalidIdToken::NotYetValid)
711        ));
712    }
713
714    #[tokio::test]
715    async fn unknown_key_refetches_the_keys() {
716        let (mut verifier, fetches) = verifier(vec![
717            keys(&[CURRENT_KEY]),
718            keys(&[CURRENT_KEY, ROTATED_KEY]),
719        ]);
720        verifier.refetch_interval = Duration::ZERO;
721
722        verifier
723            .verify(&signed_jwt(CURRENT_KEY, &claims()))
724            .await
725            .unwrap();
726        verifier
727            .verify(&signed_jwt(ROTATED_KEY, &claims()))
728            .await
729            .unwrap();
730
731        assert_eq!(fetches.load(Ordering::SeqCst), 2);
732    }
733
734    #[tokio::test]
735    async fn unknown_key_inside_the_refetch_interval_is_invalid() {
736        let (verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY])]);
737
738        verifier
739            .verify(&signed_jwt(CURRENT_KEY, &claims()))
740            .await
741            .unwrap();
742        let err = verifier
743            .verify(&signed_jwt(ROTATED_KEY, &claims()))
744            .await
745            .unwrap_err();
746
747        assert!(matches!(
748            err,
749            IdTokenVerifyError::InvalidToken(InvalidIdToken::UnknownKey(Some(ref kid))) if kid == ROTATED_KEY
750        ));
751        assert_eq!(fetches.load(Ordering::SeqCst), 1);
752    }
753
754    #[tokio::test]
755    async fn verification_during_a_refetch_waits_for_its_keys() {
756        let (mut source, fetches) = ScriptedKeys::new(vec![
757            keys(&[CURRENT_KEY]),
758            keys(&[CURRENT_KEY, ROTATED_KEY]),
759        ]);
760        source.fetch_duration = Duration::from_millis(300);
761        let mut verifier =
762            IdTokenVerifier::with_keys_source(IdTokenAudience::new(AUDIENCE), source);
763        verifier.refetch_interval = Duration::from_millis(100);
764        let verifier = Arc::new(verifier);
765        verifier
766            .verify(&signed_jwt(CURRENT_KEY, &claims()))
767            .await
768            .unwrap();
769        tokio::time::sleep(Duration::from_millis(150)).await;
770
771        let refetching = tokio::spawn({
772            let verifier = Arc::clone(&verifier);
773            async move { verifier.verify(&signed_jwt(ROTATED_KEY, &claims())).await }
774        });
775        tokio::time::sleep(Duration::from_millis(50)).await;
776        let waiting = verifier.verify(&signed_jwt(ROTATED_KEY, &claims())).await;
777
778        assert!(refetching.await.unwrap().is_ok());
779        assert!(waiting.is_ok(), "{waiting:?}");
780        assert_eq!(fetches.load(Ordering::SeqCst), 2);
781    }
782
783    #[tokio::test]
784    async fn cached_keys_stay_in_use_after_a_failed_refetch() {
785        let (mut verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY]), unavailable()]);
786        verifier.refetch_interval = Duration::ZERO;
787
788        verifier
789            .verify(&signed_jwt(CURRENT_KEY, &claims()))
790            .await
791            .unwrap();
792        let err = verifier
793            .verify(&signed_jwt(ROTATED_KEY, &claims()))
794            .await
795            .unwrap_err();
796        verifier
797            .verify(&signed_jwt(CURRENT_KEY, &claims()))
798            .await
799            .unwrap();
800
801        assert!(matches!(err, IdTokenVerifyError::KeysUnavailable(_)));
802        assert_eq!(fetches.load(Ordering::SeqCst), 2);
803    }
804
805    #[tokio::test]
806    async fn keys_are_fetched_again_after_their_max_age() {
807        let (verifier, fetches) = verifier(vec![
808            Ok(IdTokenKeys::new(
809                test_jwk_set(&[CURRENT_KEY]),
810                Duration::ZERO,
811            )),
812            keys(&[CURRENT_KEY]),
813        ]);
814
815        verifier
816            .verify(&signed_jwt(CURRENT_KEY, &claims()))
817            .await
818            .unwrap();
819        verifier
820            .verify(&signed_jwt(CURRENT_KEY, &claims()))
821            .await
822            .unwrap();
823
824        assert_eq!(fetches.load(Ordering::SeqCst), 2);
825    }
826
827    #[test]
828    fn cache_max_age_directive_is_case_insensitive() {
829        let mut headers = reqwest::header::HeaderMap::new();
830        headers.insert(
831            reqwest::header::CACHE_CONTROL,
832            "public, MAX-AGE=600".parse().unwrap(),
833        );
834
835        assert_eq!(cache_max_age(&headers), Some(Duration::from_secs(600)));
836    }
837
838    #[tokio::test]
839    async fn google_keys_last_for_the_cache_max_age() {
840        let keys_endpoint = StubServer::start(vec![StubResponse::json(
841            "200 OK",
842            serde_json::to_string(&test_jwk_set(&[CURRENT_KEY])).unwrap(),
843        )
844        .with_header(
845            "cache-control",
846            "public, max-age=24948, must-revalidate, no-transform",
847        )])
848        .await;
849        let google_keys = GoogleKeys {
850            client: reqwest::Client::new(),
851            url: format!("{}/oauth2/v3/certs", keys_endpoint.url),
852        };
853
854        let fetched = google_keys.fetch_keys().await.unwrap();
855
856        assert_eq!(fetched.max_age, Duration::from_secs(24948));
857        assert!(fetched.keys.find(CURRENT_KEY).is_some());
858        assert_eq!(
859            keys_endpoint.received()[0].request_line,
860            "GET /oauth2/v3/certs HTTP/1.1"
861        );
862    }
863}