LCOV - code coverage report
Current view: top level - proxy/src/auth/backend - jwt.rs (source / functions) Coverage Total Hit
Test: a43a77853355b937a79c57b07a8f05607cf29e6c.info Lines: 78.6 % 412 324
Test Date: 2024-09-19 12:04:32 Functions: 42.3 % 97 41

            Line data    Source code
       1              : use std::{
       2              :     future::Future,
       3              :     sync::Arc,
       4              :     time::{Duration, SystemTime},
       5              : };
       6              : 
       7              : use anyhow::{bail, ensure, Context};
       8              : use arc_swap::ArcSwapOption;
       9              : use dashmap::DashMap;
      10              : use jose_jwk::crypto::KeyInfo;
      11              : use serde::{Deserialize, Deserializer};
      12              : use signature::Verifier;
      13              : use tokio::time::Instant;
      14              : 
      15              : use crate::{context::RequestMonitoring, http::parse_json_body_with_limit, EndpointId, RoleName};
      16              : 
      17              : // TODO(conrad): make these configurable.
      18              : const CLOCK_SKEW_LEEWAY: Duration = Duration::from_secs(30);
      19              : const MIN_RENEW: Duration = Duration::from_secs(30);
      20              : const AUTO_RENEW: Duration = Duration::from_secs(300);
      21              : const MAX_RENEW: Duration = Duration::from_secs(3600);
      22              : const MAX_JWK_BODY_SIZE: usize = 64 * 1024;
      23              : 
      24              : /// How to get the JWT auth rules
      25              : pub(crate) trait FetchAuthRules: Clone + Send + Sync + 'static {
      26              :     fn fetch_auth_rules(
      27              :         &self,
      28              :         role_name: RoleName,
      29              :     ) -> impl Future<Output = anyhow::Result<Vec<AuthRule>>> + Send;
      30              : }
      31              : 
      32              : pub(crate) struct AuthRule {
      33              :     pub(crate) id: String,
      34              :     pub(crate) jwks_url: url::Url,
      35              :     pub(crate) audience: Option<String>,
      36              : }
      37              : 
      38              : #[derive(Default)]
      39              : pub(crate) struct JwkCache {
      40              :     client: reqwest::Client,
      41              : 
      42              :     map: DashMap<(EndpointId, RoleName), Arc<JwkCacheEntryLock>>,
      43              : }
      44              : 
      45              : pub(crate) struct JwkCacheEntry {
      46              :     /// Should refetch at least every hour to verify when old keys have been removed.
      47              :     /// Should refetch when new key IDs are seen only every 5 minutes or so
      48              :     last_retrieved: Instant,
      49              : 
      50              :     /// cplane will return multiple JWKs urls that we need to scrape.
      51              :     key_sets: ahash::HashMap<String, KeySet>,
      52              : }
      53              : 
      54              : impl JwkCacheEntry {
      55            4 :     fn find_jwk_and_audience(&self, key_id: &str) -> Option<(&jose_jwk::Jwk, Option<&str>)> {
      56            6 :         self.key_sets.values().find_map(|key_set| {
      57            6 :             key_set
      58            6 :                 .find_key(key_id)
      59            6 :                 .map(|jwk| (jwk, key_set.audience.as_deref()))
      60            6 :         })
      61            4 :     }
      62              : }
      63              : 
      64              : struct KeySet {
      65              :     jwks: jose_jwk::JwkSet,
      66              :     audience: Option<String>,
      67              : }
      68              : 
      69              : impl KeySet {
      70            6 :     fn find_key(&self, key_id: &str) -> Option<&jose_jwk::Jwk> {
      71            6 :         self.jwks
      72            6 :             .keys
      73            6 :             .iter()
      74           10 :             .find(|jwk| jwk.prm.kid.as_deref() == Some(key_id))
      75            6 :     }
      76              : }
      77              : 
      78              : pub(crate) struct JwkCacheEntryLock {
      79              :     cached: ArcSwapOption<JwkCacheEntry>,
      80              :     lookup: tokio::sync::Semaphore,
      81              : }
      82              : 
      83              : impl Default for JwkCacheEntryLock {
      84            1 :     fn default() -> Self {
      85            1 :         JwkCacheEntryLock {
      86            1 :             cached: ArcSwapOption::empty(),
      87            1 :             lookup: tokio::sync::Semaphore::new(1),
      88            1 :         }
      89            1 :     }
      90              : }
      91              : 
      92              : impl JwkCacheEntryLock {
      93            1 :     async fn acquire_permit<'a>(self: &'a Arc<Self>) -> JwkRenewalPermit<'a> {
      94            1 :         JwkRenewalPermit::acquire_permit(self).await
      95            1 :     }
      96              : 
      97            0 :     fn try_acquire_permit<'a>(self: &'a Arc<Self>) -> Option<JwkRenewalPermit<'a>> {
      98            0 :         JwkRenewalPermit::try_acquire_permit(self)
      99            0 :     }
     100              : 
     101            1 :     async fn renew_jwks<F: FetchAuthRules>(
     102            1 :         &self,
     103            1 :         _permit: JwkRenewalPermit<'_>,
     104            1 :         client: &reqwest::Client,
     105            1 :         role_name: RoleName,
     106            1 :         auth_rules: &F,
     107            1 :     ) -> anyhow::Result<Arc<JwkCacheEntry>> {
     108            1 :         // double check that no one beat us to updating the cache.
     109            1 :         let now = Instant::now();
     110            1 :         let guard = self.cached.load_full();
     111            1 :         if let Some(cached) = guard {
     112            0 :             let last_update = now.duration_since(cached.last_retrieved);
     113            0 :             if last_update < Duration::from_secs(300) {
     114            0 :                 return Ok(cached);
     115            0 :             }
     116            1 :         }
     117              : 
     118            1 :         let rules = auth_rules.fetch_auth_rules(role_name).await?;
     119            1 :         let mut key_sets =
     120            1 :             ahash::HashMap::with_capacity_and_hasher(rules.len(), ahash::RandomState::new());
     121              :         // TODO(conrad): run concurrently
     122              :         // TODO(conrad): strip the JWKs urls (should be checked by cplane as well - cloud#16284)
     123            3 :         for rule in rules {
     124            2 :             let req = client.get(rule.jwks_url.clone());
     125            2 :             // TODO(conrad): eventually switch to using reqwest_middleware/`new_client_with_timeout`.
     126            2 :             // TODO(conrad): We need to filter out URLs that point to local resources. Public internet only.
     127            4 :             match req.send().await.and_then(|r| r.error_for_status()) {
     128              :                 // todo: should we re-insert JWKs if we want to keep this JWKs URL?
     129              :                 // I expect these failures would be quite sparse.
     130            0 :                 Err(e) => tracing::warn!(url=?rule.jwks_url, error=?e, "could not fetch JWKs"),
     131            2 :                 Ok(r) => {
     132            2 :                     let resp: http::Response<reqwest::Body> = r.into();
     133            2 :                     match parse_json_body_with_limit::<jose_jwk::JwkSet>(
     134            2 :                         resp.into_body(),
     135            2 :                         MAX_JWK_BODY_SIZE,
     136            2 :                     )
     137            0 :                     .await
     138              :                     {
     139            0 :                         Err(e) => {
     140            0 :                             tracing::warn!(url=?rule.jwks_url, error=?e, "could not decode JWKs");
     141              :                         }
     142            2 :                         Ok(jwks) => {
     143            2 :                             key_sets.insert(
     144            2 :                                 rule.id,
     145            2 :                                 KeySet {
     146            2 :                                     jwks,
     147            2 :                                     audience: rule.audience,
     148            2 :                                 },
     149            2 :                             );
     150            2 :                         }
     151              :                     }
     152              :                 }
     153              :             }
     154              :         }
     155              : 
     156            1 :         let entry = Arc::new(JwkCacheEntry {
     157            1 :             last_retrieved: now,
     158            1 :             key_sets,
     159            1 :         });
     160            1 :         self.cached.swap(Some(Arc::clone(&entry)));
     161            1 : 
     162            1 :         Ok(entry)
     163            1 :     }
     164              : 
     165            4 :     async fn get_or_update_jwk_cache<F: FetchAuthRules>(
     166            4 :         self: &Arc<Self>,
     167            4 :         ctx: &RequestMonitoring,
     168            4 :         client: &reqwest::Client,
     169            4 :         role_name: RoleName,
     170            4 :         fetch: &F,
     171            4 :     ) -> Result<Arc<JwkCacheEntry>, anyhow::Error> {
     172            4 :         let now = Instant::now();
     173            4 :         let guard = self.cached.load_full();
     174              : 
     175              :         // if we have no cached JWKs, try and get some
     176            4 :         let Some(cached) = guard else {
     177            1 :             let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
     178            1 :             let permit = self.acquire_permit().await;
     179            4 :             return self.renew_jwks(permit, client, role_name, fetch).await;
     180              :         };
     181              : 
     182            3 :         let last_update = now.duration_since(cached.last_retrieved);
     183            3 : 
     184            3 :         // check if the cached JWKs need updating.
     185            3 :         if last_update > MAX_RENEW {
     186            0 :             let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
     187            0 :             let permit = self.acquire_permit().await;
     188              : 
     189              :             // it's been too long since we checked the keys. wait for them to update.
     190            0 :             return self.renew_jwks(permit, client, role_name, fetch).await;
     191            3 :         }
     192            3 : 
     193            3 :         // every 5 minutes we should spawn a job to eagerly update the token.
     194            3 :         if last_update > AUTO_RENEW {
     195            0 :             if let Some(permit) = self.try_acquire_permit() {
     196            0 :                 tracing::debug!("JWKs should be renewed. Renewal permit acquired");
     197            0 :                 let permit = permit.into_owned();
     198            0 :                 let entry = self.clone();
     199            0 :                 let client = client.clone();
     200            0 :                 let fetch = fetch.clone();
     201            0 :                 tokio::spawn(async move {
     202            0 :                     if let Err(e) = entry.renew_jwks(permit, &client, role_name, &fetch).await {
     203            0 :                         tracing::warn!(error=?e, "could not fetch JWKs in background job");
     204            0 :                     }
     205            0 :                 });
     206            0 :             } else {
     207            0 :                 tracing::debug!("JWKs should be renewed. Renewal permit already taken, skipping");
     208              :             }
     209            3 :         }
     210              : 
     211            3 :         Ok(cached)
     212            4 :     }
     213              : 
     214            4 :     async fn check_jwt<F: FetchAuthRules>(
     215            4 :         self: &Arc<Self>,
     216            4 :         ctx: &RequestMonitoring,
     217            4 :         jwt: &str,
     218            4 :         client: &reqwest::Client,
     219            4 :         role_name: RoleName,
     220            4 :         fetch: &F,
     221            4 :     ) -> Result<(), anyhow::Error> {
     222              :         // JWT compact form is defined to be
     223              :         // <B64(Header)> || . || <B64(Payload)> || . || <B64(Signature)>
     224              :         // where Signature = alg(<B64(Header)> || . || <B64(Payload)>);
     225              : 
     226            4 :         let (header_payload, signature) = jwt
     227            4 :             .rsplit_once('.')
     228            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     229            4 :         let (header, payload) = header_payload
     230            4 :             .split_once('.')
     231            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     232              : 
     233            4 :         let header = base64::decode_config(header, base64::URL_SAFE_NO_PAD)
     234            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     235            4 :         let header = serde_json::from_slice::<JwtHeader<'_>>(&header)
     236            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     237              : 
     238            4 :         let sig = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)
     239            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     240              : 
     241            4 :         ensure!(header.typ == "JWT");
     242            4 :         let kid = header.key_id.context("missing key id")?;
     243              : 
     244            4 :         let mut guard = self
     245            4 :             .get_or_update_jwk_cache(ctx, client, role_name.clone(), fetch)
     246            4 :             .await?;
     247              : 
     248              :         // get the key from the JWKs if possible. If not, wait for the keys to update.
     249            4 :         let (jwk, expected_audience) = loop {
     250            4 :             match guard.find_jwk_and_audience(kid) {
     251            4 :                 Some(jwk) => break jwk,
     252            0 :                 None if guard.last_retrieved.elapsed() > MIN_RENEW => {
     253            0 :                     let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
     254              : 
     255            0 :                     let permit = self.acquire_permit().await;
     256            0 :                     guard = self
     257            0 :                         .renew_jwks(permit, client, role_name.clone(), fetch)
     258            0 :                         .await?;
     259              :                 }
     260              :                 _ => {
     261            0 :                     bail!("jwk not found");
     262              :                 }
     263              :             }
     264              :         };
     265              : 
     266            4 :         ensure!(
     267            4 :             jwk.is_supported(&header.algorithm),
     268            0 :             "signature algorithm not supported"
     269              :         );
     270              : 
     271            4 :         match &jwk.key {
     272            2 :             jose_jwk::Key::Ec(key) => {
     273            2 :                 verify_ec_signature(header_payload.as_bytes(), &sig, key)?;
     274              :             }
     275            2 :             jose_jwk::Key::Rsa(key) => {
     276            2 :                 verify_rsa_signature(header_payload.as_bytes(), &sig, key, &jwk.prm.alg)?;
     277              :             }
     278            0 :             key => bail!("unsupported key type {key:?}"),
     279              :         };
     280              : 
     281            4 :         let payload = base64::decode_config(payload, base64::URL_SAFE_NO_PAD)
     282            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     283            4 :         let payload = serde_json::from_slice::<JwtPayload<'_>>(&payload)
     284            4 :             .context("Provided authentication token is not a valid JWT encoding")?;
     285              : 
     286            4 :         tracing::debug!(?payload, "JWT signature valid with claims");
     287              : 
     288            4 :         match (expected_audience, payload.audience) {
     289              :             // check the audience matches
     290            0 :             (Some(aud1), Some(aud2)) => ensure!(aud1 == aud2, "invalid JWT token audience"),
     291              :             // the audience is expected but is missing
     292            0 :             (Some(_), None) => bail!("invalid JWT token audience"),
     293              :             // we don't care for the audience field
     294            4 :             (None, _) => {}
     295              :         }
     296              : 
     297            4 :         let now = SystemTime::now();
     298              : 
     299            4 :         if let Some(exp) = payload.expiration {
     300            4 :             ensure!(now < exp + CLOCK_SKEW_LEEWAY);
     301            0 :         }
     302              : 
     303            4 :         if let Some(nbf) = payload.not_before {
     304            0 :             ensure!(nbf < now + CLOCK_SKEW_LEEWAY);
     305            4 :         }
     306              : 
     307            4 :         Ok(())
     308            4 :     }
     309              : }
     310              : 
     311              : impl JwkCache {
     312            0 :     pub(crate) async fn check_jwt<F: FetchAuthRules>(
     313            0 :         &self,
     314            0 :         ctx: &RequestMonitoring,
     315            0 :         endpoint: EndpointId,
     316            0 :         role_name: RoleName,
     317            0 :         fetch: &F,
     318            0 :         jwt: &str,
     319            0 :     ) -> Result<(), anyhow::Error> {
     320            0 :         // try with just a read lock first
     321            0 :         let key = (endpoint, role_name.clone());
     322            0 :         let entry = self.map.get(&key).as_deref().map(Arc::clone);
     323            0 :         let entry = entry.unwrap_or_else(|| {
     324            0 :             // acquire a write lock after to insert.
     325            0 :             let entry = self.map.entry(key).or_default();
     326            0 :             Arc::clone(&*entry)
     327            0 :         });
     328            0 : 
     329            0 :         entry
     330            0 :             .check_jwt(ctx, jwt, &self.client, role_name, fetch)
     331            0 :             .await
     332            0 :     }
     333              : }
     334              : 
     335            2 : fn verify_ec_signature(data: &[u8], sig: &[u8], key: &jose_jwk::Ec) -> anyhow::Result<()> {
     336              :     use ecdsa::Signature;
     337              :     use signature::Verifier;
     338              : 
     339            2 :     match key.crv {
     340              :         jose_jwk::EcCurves::P256 => {
     341            2 :             let pk =
     342            2 :                 p256::PublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid P256 key"))?;
     343            2 :             let key = p256::ecdsa::VerifyingKey::from(&pk);
     344            2 :             let sig = Signature::from_slice(sig)?;
     345            2 :             key.verify(data, &sig)?;
     346              :         }
     347            0 :         key => bail!("unsupported ec key type {key:?}"),
     348              :     }
     349              : 
     350            2 :     Ok(())
     351            2 : }
     352              : 
     353            2 : fn verify_rsa_signature(
     354            2 :     data: &[u8],
     355            2 :     sig: &[u8],
     356            2 :     key: &jose_jwk::Rsa,
     357            2 :     alg: &Option<jose_jwa::Algorithm>,
     358            2 : ) -> anyhow::Result<()> {
     359              :     use jose_jwa::{Algorithm, Signing};
     360              :     use rsa::{
     361              :         pkcs1v15::{Signature, VerifyingKey},
     362              :         RsaPublicKey,
     363              :     };
     364              : 
     365            2 :     let key = RsaPublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid RSA key"))?;
     366              : 
     367            2 :     match alg {
     368              :         Some(Algorithm::Signing(Signing::Rs256)) => {
     369            2 :             let key = VerifyingKey::<sha2::Sha256>::new(key);
     370            2 :             let sig = Signature::try_from(sig)?;
     371            2 :             key.verify(data, &sig)?;
     372              :         }
     373            0 :         _ => bail!("invalid RSA signing algorithm"),
     374              :     };
     375              : 
     376            2 :     Ok(())
     377            2 : }
     378              : 
     379              : /// <https://datatracker.ietf.org/doc/html/rfc7515#section-4.1>
     380           16 : #[derive(serde::Deserialize, serde::Serialize)]
     381              : struct JwtHeader<'a> {
     382              :     /// must be "JWT"
     383              :     #[serde(rename = "typ")]
     384              :     typ: &'a str,
     385              :     /// must be a supported alg
     386              :     #[serde(rename = "alg")]
     387              :     algorithm: jose_jwa::Algorithm,
     388              :     /// key id, must be provided for our usecase
     389              :     #[serde(rename = "kid")]
     390              :     key_id: Option<&'a str>,
     391              : }
     392              : 
     393              : /// <https://datatracker.ietf.org/doc/html/rfc7519#section-4.1>
     394           12 : #[derive(serde::Deserialize, serde::Serialize, Debug)]
     395              : struct JwtPayload<'a> {
     396              :     /// Audience - Recipient for which the JWT is intended
     397              :     #[serde(rename = "aud")]
     398              :     audience: Option<&'a str>,
     399              :     /// Expiration - Time after which the JWT expires
     400              :     #[serde(deserialize_with = "numeric_date_opt", rename = "exp", default)]
     401              :     expiration: Option<SystemTime>,
     402              :     /// Not before - Time after which the JWT expires
     403              :     #[serde(deserialize_with = "numeric_date_opt", rename = "nbf", default)]
     404              :     not_before: Option<SystemTime>,
     405              : 
     406              :     // the following entries are only extracted for the sake of debug logging.
     407              :     /// Issuer of the JWT
     408              :     #[serde(rename = "iss")]
     409              :     issuer: Option<&'a str>,
     410              :     /// Subject of the JWT (the user)
     411              :     #[serde(rename = "sub")]
     412              :     subject: Option<&'a str>,
     413              :     /// Unique token identifier
     414              :     #[serde(rename = "jti")]
     415              :     jwt_id: Option<&'a str>,
     416              :     /// Unique session identifier
     417              :     #[serde(rename = "sid")]
     418              :     session_id: Option<&'a str>,
     419              : }
     420              : 
     421            4 : fn numeric_date_opt<'de, D: Deserializer<'de>>(d: D) -> Result<Option<SystemTime>, D::Error> {
     422            4 :     let d = <Option<u64>>::deserialize(d)?;
     423            4 :     Ok(d.map(|n| SystemTime::UNIX_EPOCH + Duration::from_secs(n)))
     424            4 : }
     425              : 
     426              : struct JwkRenewalPermit<'a> {
     427              :     inner: Option<JwkRenewalPermitInner<'a>>,
     428              : }
     429              : 
     430              : enum JwkRenewalPermitInner<'a> {
     431              :     Owned(Arc<JwkCacheEntryLock>),
     432              :     Borrowed(&'a Arc<JwkCacheEntryLock>),
     433              : }
     434              : 
     435              : impl JwkRenewalPermit<'_> {
     436            0 :     fn into_owned(mut self) -> JwkRenewalPermit<'static> {
     437            0 :         JwkRenewalPermit {
     438            0 :             inner: self.inner.take().map(JwkRenewalPermitInner::into_owned),
     439            0 :         }
     440            0 :     }
     441              : 
     442            1 :     async fn acquire_permit(from: &Arc<JwkCacheEntryLock>) -> JwkRenewalPermit<'_> {
     443            1 :         match from.lookup.acquire().await {
     444            1 :             Ok(permit) => {
     445            1 :                 permit.forget();
     446            1 :                 JwkRenewalPermit {
     447            1 :                     inner: Some(JwkRenewalPermitInner::Borrowed(from)),
     448            1 :                 }
     449              :             }
     450            0 :             Err(_) => panic!("semaphore should not be closed"),
     451              :         }
     452            1 :     }
     453              : 
     454            0 :     fn try_acquire_permit(from: &Arc<JwkCacheEntryLock>) -> Option<JwkRenewalPermit<'_>> {
     455            0 :         match from.lookup.try_acquire() {
     456            0 :             Ok(permit) => {
     457            0 :                 permit.forget();
     458            0 :                 Some(JwkRenewalPermit {
     459            0 :                     inner: Some(JwkRenewalPermitInner::Borrowed(from)),
     460            0 :                 })
     461              :             }
     462            0 :             Err(tokio::sync::TryAcquireError::NoPermits) => None,
     463            0 :             Err(tokio::sync::TryAcquireError::Closed) => panic!("semaphore should not be closed"),
     464              :         }
     465            0 :     }
     466              : }
     467              : 
     468              : impl JwkRenewalPermitInner<'_> {
     469            0 :     fn into_owned(self) -> JwkRenewalPermitInner<'static> {
     470            0 :         match self {
     471            0 :             JwkRenewalPermitInner::Owned(p) => JwkRenewalPermitInner::Owned(p),
     472            0 :             JwkRenewalPermitInner::Borrowed(p) => JwkRenewalPermitInner::Owned(Arc::clone(p)),
     473              :         }
     474            0 :     }
     475              : }
     476              : 
     477              : impl Drop for JwkRenewalPermit<'_> {
     478            1 :     fn drop(&mut self) {
     479            1 :         let entry = match &self.inner {
     480            0 :             None => return,
     481            0 :             Some(JwkRenewalPermitInner::Owned(p)) => p,
     482            1 :             Some(JwkRenewalPermitInner::Borrowed(p)) => *p,
     483              :         };
     484            1 :         entry.lookup.add_permits(1);
     485            1 :     }
     486              : }
     487              : 
     488              : #[cfg(test)]
     489              : mod tests {
     490              :     use crate::RoleName;
     491              : 
     492              :     use super::*;
     493              : 
     494              :     use std::{future::IntoFuture, net::SocketAddr, time::SystemTime};
     495              : 
     496              :     use base64::URL_SAFE_NO_PAD;
     497              :     use bytes::Bytes;
     498              :     use http::Response;
     499              :     use http_body_util::Full;
     500              :     use hyper1::service::service_fn;
     501              :     use hyper_util::rt::TokioIo;
     502              :     use rand::rngs::OsRng;
     503              :     use rsa::pkcs8::DecodePrivateKey;
     504              :     use signature::Signer;
     505              :     use tokio::net::TcpListener;
     506              : 
     507            2 :     fn new_ec_jwk(kid: String) -> (p256::SecretKey, jose_jwk::Jwk) {
     508            2 :         let sk = p256::SecretKey::random(&mut OsRng);
     509            2 :         let pk = sk.public_key().into();
     510            2 :         let jwk = jose_jwk::Jwk {
     511            2 :             key: jose_jwk::Key::Ec(pk),
     512            2 :             prm: jose_jwk::Parameters {
     513            2 :                 kid: Some(kid),
     514            2 :                 alg: Some(jose_jwa::Algorithm::Signing(jose_jwa::Signing::Es256)),
     515            2 :                 ..Default::default()
     516            2 :             },
     517            2 :         };
     518            2 :         (sk, jwk)
     519            2 :     }
     520              : 
     521            2 :     fn new_rsa_jwk(key: &str, kid: String) -> (rsa::RsaPrivateKey, jose_jwk::Jwk) {
     522            2 :         let sk = rsa::RsaPrivateKey::from_pkcs8_pem(key).unwrap();
     523            2 :         let pk = sk.to_public_key().into();
     524            2 :         let jwk = jose_jwk::Jwk {
     525            2 :             key: jose_jwk::Key::Rsa(pk),
     526            2 :             prm: jose_jwk::Parameters {
     527            2 :                 kid: Some(kid),
     528            2 :                 alg: Some(jose_jwa::Algorithm::Signing(jose_jwa::Signing::Rs256)),
     529            2 :                 ..Default::default()
     530            2 :             },
     531            2 :         };
     532            2 :         (sk, jwk)
     533            2 :     }
     534              : 
     535            4 :     fn build_jwt_payload(kid: String, sig: jose_jwa::Signing) -> String {
     536            4 :         let header = JwtHeader {
     537            4 :             typ: "JWT",
     538            4 :             algorithm: jose_jwa::Algorithm::Signing(sig),
     539            4 :             key_id: Some(&kid),
     540            4 :         };
     541            4 :         let body = typed_json::json! {{
     542            4 :             "exp": SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + 3600,
     543            4 :         }};
     544            4 : 
     545            4 :         let header =
     546            4 :             base64::encode_config(serde_json::to_string(&header).unwrap(), URL_SAFE_NO_PAD);
     547            4 :         let body = base64::encode_config(body.to_string(), URL_SAFE_NO_PAD);
     548            4 : 
     549            4 :         format!("{header}.{body}")
     550            4 :     }
     551              : 
     552            2 :     fn new_ec_jwt(kid: String, key: p256::SecretKey) -> String {
     553              :         use p256::ecdsa::{Signature, SigningKey};
     554              : 
     555            2 :         let payload = build_jwt_payload(kid, jose_jwa::Signing::Es256);
     556            2 :         let sig: Signature = SigningKey::from(key).sign(payload.as_bytes());
     557            2 :         let sig = base64::encode_config(sig.to_bytes(), URL_SAFE_NO_PAD);
     558            2 : 
     559            2 :         format!("{payload}.{sig}")
     560            2 :     }
     561              : 
     562            2 :     fn new_rsa_jwt(kid: String, key: rsa::RsaPrivateKey) -> String {
     563              :         use rsa::pkcs1v15::SigningKey;
     564              :         use rsa::signature::SignatureEncoding;
     565              : 
     566            2 :         let payload = build_jwt_payload(kid, jose_jwa::Signing::Rs256);
     567            2 :         let sig = SigningKey::<sha2::Sha256>::new(key).sign(payload.as_bytes());
     568            2 :         let sig = base64::encode_config(sig.to_bytes(), URL_SAFE_NO_PAD);
     569            2 : 
     570            2 :         format!("{payload}.{sig}")
     571            2 :     }
     572              : 
     573              :     // RSA key gen is slow....
     574              :     const RS1: &str = "-----BEGIN PRIVATE KEY-----
     575              : MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNuWBIWTlo+54Y
     576              : aifpGInIrpv6LlsbI/2/2CC81Arlx4RsABORklgA9XSGwaCbHTshHsfd1S916JwA
     577              : SpjyPQYWfqo6iAV8a4MhjIeJIkRr74prDCSzOGZvIc6VaGeCIb9clf3HSrPHm3hA
     578              : cfLMB8/p5MgoxERPDOIn3XYoS9SEEuP7l0LkmEZMerg6W6lDjQRDny0Lb50Jky9X
     579              : mDqnYXBhs99ranbwL5vjy0ba6OIeCWFJme5u+rv5C/P0BOYrJfGxIcEoKa8Ukw5s
     580              : PlM+qrz9ope1eOuXMNNdyFDReNBUyaM1AwBAayU5rz57crer7K/UIofaJ42T4cMM
     581              : nx/SWfBNAgMBAAECggEACqdpBxYn1PoC6/zDaFzu9celKEWyTiuE/qRwvZa1ocS9
     582              : ZOJ0IPvVNud/S2NHsADJiSOQ8joSJScQvSsf1Ju4bv3MTw+wSQtAVUJz2nQ92uEi
     583              : 5/xPAkEPfP3hNvebNLAOuvrBk8qYmOPCTIQaMNrOt6wzeXkAmJ9wLuRXNCsJLHW+
     584              : KLpf2WdgTYxqK06ZiJERFgJ2r1MsC2IgTydzjOAdEIrtMarerTLqqCpwFrk/l0cz
     585              : 1O2OAb17ZxmhuzMhjNMin81c8F2fZAGMeOjn92Jl5kUsYw/pG+0S8QKlbveR/fdP
     586              : We2tJsgXw2zD0q7OJpp8NXS2yddrZGyysYsof983wQKBgQD2McqNJqo+eWL5zony
     587              : UbL19loYw0M15EjhzIuzW1Jk0rPj65yQyzpJ6pqicRuWr34MvzCx+ZHM2b3jSiNu
     588              : GES2fnC7xLIKyeRxfqsXF71xz+6UStEGRQX27r1YWEtyQVuBhvlqB+AGWP3PYAC+
     589              : HecZecnZ+vcihJ2K3+l5O3paVQKBgQDV6vKH5h2SY9vgO8obx0P7XSS+djHhmPuU
     590              : f8C/Fq6AuRbIA1g04pzuLU2WS9T26eIjgM173uVNg2TuqJveWzz+CAAp6nCR6l24
     591              : DBg49lMGCWrMo4FqPG46QkUqvK8uSj42GkX/e5Rut1Gyu0209emeM6h2d2K15SvY
     592              : 9563tYSmGQKBgQDwcH5WTi20KA7e07TroJi8GKWzS3gneNUpGQBS4VxdtV4UuXXF
     593              : /4TkzafJ/9cm2iurvUmMd6XKP9lw0mY5zp/E70WgTCBp4vUlVsU3H2tYbO+filYL
     594              : 3ntNx6nKTykX4/a/UJfj0t8as+zli+gNxNx/h+734V9dKdFG4Rl+2fTLpQKBgQCE
     595              : qJkTEe+Q0wCOBEYICADupwqcWqwAXWDW7IrZdfVtulqYWwqecVIkmk+dPxWosc4d
     596              : ekjz4nyNH0i+gC15LVebqdaAJ/T7aD4KXuW+nXNLMRfcJCGjgipRUruWD0EMEdqW
     597              : rqBuGXMpXeH6VxGPgVkJVLvKC6tZZe9VM+pnvteuMQKBgQC8GaL+Lz+al4biyZBf
     598              : JE8ekWrIotq/gfUBLP7x70+PB9bNtXtlgmTvjgYg4jiu3KR/ZIYYQ8vfVgkb6tDI
     599              : rWGZw86Pzuoi1ppg/pYhKk9qrmCIT4HPEXbHl7ATahu2BOCIU3hybjTh2lB6LbX9
     600              : 8LMFlz1QPqSZYN/A/kOcLBfa3A==
     601              : -----END PRIVATE KEY-----
     602              : ";
     603              :     const RS2: &str = "-----BEGIN PRIVATE KEY-----
     604              : MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDipm6FIKSRab3J
     605              : HwmK18t7hp+pohllxIDUSPi7S5mIhN/JG2Plq2Lp746E/fuT8dcBF2R4sJlG2L0J
     606              : zmxOvBU/i/sQF9s1i4CEfg05k2//gKENIEsF3pMMmrH+mcZi0TTD6rezHpdVxPHk
     607              : qWxSyOCtIJV29X+wxPwAB59kQFHzy2ooPB1isZcpE8tO0KthAM+oZ3KuCwE0++cO
     608              : IWLeq9aPwyKhtip/xjTMxd1kzdKh592mGSyzr9D0QSWOYFGvgJXANDdiPdhSSOLt
     609              : ECWPNPlm2FQvGGvYYBafUqz7VumKHE6x8J6lKdYa2J0ZdDzCIo2IHzlxe+RZNgwy
     610              : uAD2jhVxAgMBAAECggEAbsZHWBu3MzcKQiVARbLoygvnN0J5xUqAaMDtiKUPejDv
     611              : K1yOu67DXnDuKEP2VL2rhuYG/hHaKE1AP227c9PrUq6424m9YvM2sgrlrdFIuQkG
     612              : LeMtp8W7+zoUasp/ssZrUqICfLIj5xCl5UuFHQT/Ar7dLlIYwa3VOLKBDb9+Dnfe
     613              : QH5/So4uMXG6vw34JN9jf+eAc8Yt0PeIz62ycvRwdpTJQ0MxZN9ZKpCAQp+VTuXT
     614              : zlzNvDMilabEdqUvAyGyz8lBLNl0wdaVrqPqAEWM5U45QXsdFZknWammP7/tijeX
     615              : 0z+Bi0J0uSEU5X502zm7GArj/NNIiWMcjmDjwUUhwQKBgQD9C2GoqxOxuVPYqwYR
     616              : +Jz7f2qMjlSP8adA5Lzuh8UKXDp8JCEQC8ryweLzaOKS9C5MAw+W4W2wd4nJoQI1
     617              : P1dgGvBlfvEeRHMgqWtq7FuTsjSe7e0uSEkC4ngDb4sc0QOpv15cMuEz+4+aFLPL
     618              : x29EcHWAaBX+rkid3zpQHFU4eQKBgQDlTCEqRuXwwa3V+Sq+mNWzD9QIGtD87TH/
     619              : FPO/Ij/cK2+GISgFDqhetiGTH4qrvPL0psPT+iH5zGFYcoFmTtwLdWQJdxhxz0bg
     620              : iX/AceyX5e1Bm+ThT36sU83NrxKPkrdk6jNmr2iUF1OTzTwUKOYdHOPZqdMPfF4M
     621              : 4XAaWVT2uQKBgQD4nKcNdU+7LE9Rr+4d1/o8Klp/0BMK/ayK2HE7lc8kt6qKb2DA
     622              : iCWUTqPw7Fq3cQrPia5WWhNP7pJEtFkcAaiR9sW7onW5fBz0uR+dhK0QtmR2xWJj
     623              : N4fsOp8ZGQ0/eae0rh1CTobucLkM9EwV6VLLlgYL67e4anlUCo8bSEr+WQKBgQCB
     624              : uf6RgqcY/RqyklPCnYlZ0zyskS9nyXKd1GbK3j+u+swP4LZZlh9f5j88k33LCA2U
     625              : qLzmMwAB6cWxWqcnELqhqPq9+ClWSmTZKDGk2U936NfAZMirSGRsbsVi9wfTPriP
     626              : WYlXMSpDjqb0WgsBhNob4npubQxCGKTFOM5Jufy90QKBgB0Lte1jX144uaXx6dtB
     627              : rjXNuWNir0Jy31wHnQuCA+XnfUgPcrKmRLm8taMbXgZwxkNvgFkpUWU8aPEK08Ne
     628              : X0n5X2/pBLJzxZc62ccvZYVnctBiFs6HbSnxpuMQCfkt/BcR/ttIepBQQIW86wHL
     629              : 5JiconnI5aLek0QVPoFaVXFa
     630              : -----END PRIVATE KEY-----
     631              : ";
     632              : 
     633              :     #[tokio::test]
     634            1 :     async fn renew() {
     635            1 :         let (rs1, jwk1) = new_rsa_jwk(RS1, "1".into());
     636            1 :         let (rs2, jwk2) = new_rsa_jwk(RS2, "2".into());
     637            1 :         let (ec1, jwk3) = new_ec_jwk("3".into());
     638            1 :         let (ec2, jwk4) = new_ec_jwk("4".into());
     639            1 : 
     640            1 :         let jwt1 = new_rsa_jwt("1".into(), rs1);
     641            1 :         let jwt2 = new_rsa_jwt("2".into(), rs2);
     642            1 :         let jwt3 = new_ec_jwt("3".into(), ec1);
     643            1 :         let jwt4 = new_ec_jwt("4".into(), ec2);
     644            1 : 
     645            1 :         let foo_jwks = jose_jwk::JwkSet {
     646            1 :             keys: vec![jwk1, jwk3],
     647            1 :         };
     648            1 :         let bar_jwks = jose_jwk::JwkSet {
     649            1 :             keys: vec![jwk2, jwk4],
     650            1 :         };
     651            1 : 
     652            2 :         let service = service_fn(move |req| {
     653            2 :             let foo_jwks = foo_jwks.clone();
     654            2 :             let bar_jwks = bar_jwks.clone();
     655            2 :             async move {
     656            2 :                 let jwks = match req.uri().path() {
     657            2 :                     "/foo" => &foo_jwks,
     658            1 :                     "/bar" => &bar_jwks,
     659            1 :                     _ => {
     660            1 :                         return Response::builder()
     661            0 :                             .status(404)
     662            0 :                             .body(Full::new(Bytes::new()));
     663            1 :                     }
     664            1 :                 };
     665            2 :                 let body = serde_json::to_vec(jwks).unwrap();
     666            2 :                 Response::builder()
     667            2 :                     .status(200)
     668            2 :                     .body(Full::new(Bytes::from(body)))
     669            2 :             }
     670            2 :         });
     671            1 : 
     672            1 :         let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
     673            1 :         let server = hyper1::server::conn::http1::Builder::new();
     674            1 :         let addr = listener.local_addr().unwrap();
     675            1 :         tokio::spawn(async move {
     676            1 :             loop {
     677            2 :                 let (s, _) = listener.accept().await.unwrap();
     678            1 :                 let serve = server.serve_connection(TokioIo::new(s), service.clone());
     679            1 :                 tokio::spawn(serve.into_future());
     680            1 :             }
     681            1 :         });
     682            1 : 
     683            1 :         let client = reqwest::Client::new();
     684            1 : 
     685            1 :         #[derive(Clone)]
     686            1 :         struct Fetch(SocketAddr);
     687            1 : 
     688            1 :         impl FetchAuthRules for Fetch {
     689            1 :             async fn fetch_auth_rules(
     690            1 :                 &self,
     691            1 :                 _role_name: RoleName,
     692            1 :             ) -> anyhow::Result<Vec<AuthRule>> {
     693            1 :                 Ok(vec![
     694            1 :                     AuthRule {
     695            1 :                         id: "foo".to_owned(),
     696            1 :                         jwks_url: format!("http://{}/foo", self.0).parse().unwrap(),
     697            1 :                         audience: None,
     698            1 :                     },
     699            1 :                     AuthRule {
     700            1 :                         id: "bar".to_owned(),
     701            1 :                         jwks_url: format!("http://{}/bar", self.0).parse().unwrap(),
     702            1 :                         audience: None,
     703            1 :                     },
     704            1 :                 ])
     705            1 :             }
     706            1 :         }
     707            1 : 
     708            1 :         let role_name = RoleName::from("user");
     709            1 : 
     710            1 :         let jwk_cache = Arc::new(JwkCacheEntryLock::default());
     711            1 : 
     712            4 :         for token in [jwt1, jwt2, jwt3, jwt4] {
     713            4 :             jwk_cache
     714            4 :                 .check_jwt(
     715            4 :                     &RequestMonitoring::test(),
     716            4 :                     &token,
     717            4 :                     &client,
     718            4 :                     role_name.clone(),
     719            4 :                     &Fetch(addr),
     720            4 :                 )
     721            4 :                 .await
     722            4 :                 .unwrap();
     723            1 :         }
     724            1 :     }
     725              : }
        

Generated by: LCOV version 2.1-beta