diff --git a/Cargo.lock b/Cargo.lock index bb909f675d..9c7718c9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1487,16 +1487,15 @@ version = "1.5.0" dependencies = [ "anyhow", "async-trait", - "aws-lc-rs", "base64", "bytes", "google-cloud-gax", "http", "httptest", - "jsonwebtoken", "mockall", "mutants", "p256", + "pkcs1", "regex", "reqwest 0.13.1", "rsa", @@ -1507,6 +1506,7 @@ dependencies = [ "serde", "serde_json", "serial_test", + "spki", "tempfile", "test-case", "thiserror 2.0.18", @@ -5985,9 +5985,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.2.0" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "aws-lc-rs", "base64", diff --git a/src/auth/Cargo.toml b/src/auth/Cargo.toml index 63e7c37d42..a58efb4824 100644 --- a/src/auth/Cargo.toml +++ b/src/auth/Cargo.toml @@ -43,13 +43,13 @@ rustls = { workspace = true, features = ["logging", "std", "tls12 rustls-pki-types = { workspace = true, features = ["std"] } serde.workspace = true serde_json.workspace = true +# TODO: move new deps to workspace +pkcs1 = { version = "0.7", optional = true, features = ["alloc"] } +spki = { version = "0.7", optional = true, features = ["alloc"] } +p256 = { workspace = true, optional = true, features = ["ecdsa"] } thiserror.workspace = true time = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["fs", "process"] } -jsonwebtoken = { workspace = true, optional = true } -# We do not use this directly, but without it the minimal-versions build breaks. -# See: https://github.com/Keats/jsonwebtoken/pull/481 -aws-lc-rs = { workspace = true, optional = true } # Local dependencies gax.workspace = true @@ -73,12 +73,12 @@ mutants.workspace = true default = ["default-idtoken-backend", "default-rustls-provider"] # The `idtoken` feature enables support to create and validate OIDC ID Tokens. # See the create top-level documentation for more information. -idtoken = ["dep:jsonwebtoken", "jsonwebtoken"] +idtoken = ["dep:pkcs1", "dep:spki", "dep:p256", "jsonwebtoken"] # By default this crate enables the `aws_lc_rs` backend. Applications can # link `google-cloud-auth` with `default-features = false, features = ["idtoken"] # and then directly configure the `jsonwebtoken` features to select the # `rust_crypto` backend. -default-idtoken-backend = ["dep:aws-lc-rs", "jsonwebtoken?/aws_lc_rs"] +default-idtoken-backend = [] # Enabled by default. Use the default rustls crypto provider ([aws-lc-rs]) for # TLS and authentication. Applications with specific requirements for # cryptography (such as exclusively using the [ring] crate) should disable this @@ -86,7 +86,7 @@ default-idtoken-backend = ["dep:aws-lc-rs", "jsonwebtoken?/aws_lc_rs"] default-rustls-provider = ["reqwest/default-tls", "rustls/aws_lc_rs"] # Do not use, this was a mistake in the 1.3 release. We accidentally introduced # this feature. The intent was to only introduce `idtoken`. -jsonwebtoken = ["dep:jsonwebtoken"] +jsonwebtoken = [] [lints] workspace = true diff --git a/src/auth/src/credentials/idtoken/verifier.rs b/src/auth/src/credentials/idtoken/verifier.rs index 6ea1e2f323..eb0d46ed71 100644 --- a/src/auth/src/credentials/idtoken/verifier.rs +++ b/src/auth/src/credentials/idtoken/verifier.rs @@ -37,12 +37,16 @@ //! [OIDC ID Tokens]: https://cloud.google.com/docs/authentication/token-types#identity-tokens use crate::credentials::internal::jwk_client::JwkClient; -use jsonwebtoken::Validation; +use crate::credentials::service_account::jws::JwsHeader; +use base64::Engine; +use p256::ecdsa::Signature; +use rustls::crypto::CryptoProvider; /// Represents the claims in an ID token. pub use serde_json::Map; /// Represents a claim value in an ID token. pub use serde_json::Value; use std::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; /// Builder is used construct a [Verifier] of id tokens. pub struct Builder { @@ -165,38 +169,138 @@ pub struct Verifier { impl Verifier { /// Verifies the ID token and returns the claims. pub async fn verify(&self, token: &str) -> std::result::Result, Error> { - let header = jsonwebtoken::decode_header(token).map_err(Error::decode)?; + let parts: Vec<&str> = token.split('.').collect(); + if parts.len() != 3 { + return Err(Error::decode("token must have 3 parts")); + } + + let header_json = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[0]) + .map_err(Error::decode)?; + let header: JwsHeader = serde_json::from_slice(&header_json).map_err(Error::decode)?; let key_id = header .kid .ok_or_else(|| Error::invalid_field("kid", "kid header is missing"))?; - let mut validation = Validation::new(header.alg); - validation.leeway = self.clock_skew.as_secs(); - // TODO(#3591): Support TPC/REP that can have different issuers - validation.set_issuer(&["https://accounts.google.com", "accounts.google.com"]); - validation.set_audience(&self.audiences); + let scheme = match header.alg { + "RS256" => rustls::SignatureScheme::RSA_PKCS1_SHA256, + "ES256" => rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + _ => { + return Err(Error::invalid_field( + "alg", + format!("Unsupported algorithm: {}", header.alg), + )); + } + }; - let expected_email = self.email.clone(); - let jwks_url = self.jwks_url.clone(); + let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[2]) + .map_err(Error::decode)?; + + let signature = if header.alg == "ES256" { + let sig = Signature::from_slice(&signature) + .map_err(|_| Error::invalid("invalid ECDSA signature format"))?; + sig.to_der().as_bytes().to_vec() + } else { + signature + }; - let cert = self + let jwks_url = self.jwks_url.clone(); + let spki = self .jwk_client - .get_or_load_cert(key_id, header.alg, jwks_url) + .get_or_load_cert(key_id.to_string(), header.alg, jwks_url) .await .map_err(Error::load_cert)?; - let token = jsonwebtoken::decode::>(&token, &cert, &validation) - .map_err(|e| match e.clone().into_kind() { - jsonwebtoken::errors::ErrorKind::InvalidIssuer => Error::invalid_field("iss", e), - jsonwebtoken::errors::ErrorKind::InvalidAudience => Error::invalid_field("aud", e), - jsonwebtoken::errors::ErrorKind::MissingRequiredClaim(field) => { - Error::invalid_field(field.as_str(), e) - } - _ => Error::invalid(e), + let message = format!("{}.{}", parts[0], parts[1]); + + let provider = CryptoProvider::get_default().cloned(); + #[cfg(feature = "default-rustls-provider")] + let provider = provider + .unwrap_or_else(|| std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider())); + #[cfg(not(feature = "default-rustls-provider"))] + let provider = provider.expect( + r###" +The default rustls::CryptoProvider should be configured by the application. The +`google-cloud-auth` crate was compiled without the `default-rustls-provider` +feature. Without this feature the crate expects the application to initialize +the rustls crypto provider using `rustls::CryptoProvider::install_default()`. + +Note that the application must use the exact same version of `rustls` as the +`google-cloud-auth` crate does. Otherwise `install_default()` has no effect."###, + ); + + let algs = provider.signature_verification_algorithms; + let mapping = algs.mapping; + let alg = mapping + .iter() + .find(|(candidate_scheme, _)| *candidate_scheme == scheme) + .map(|(_, candidates)| candidates[0]) + .ok_or_else(|| { + Error::invalid(format!( + "{:?} not supported by current crypto provider", + scheme + )) })?; - let claims = token.claims; + alg.verify_signature(&spki, message.as_bytes(), &signature) + .map_err(|e| Error::invalid(format!("Invalid signature: {:?}", e)))?; + + // claims validation + let expected_email = self.email.clone(); + let claims_json = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[1]) + .map_err(Error::decode)?; + let claims: Map = + serde_json::from_slice(&claims_json).map_err(Error::decode)?; + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let exp = claims + .get("exp") + .and_then(|v| v.as_u64()) + .ok_or_else(|| Error::invalid_field("exp", "exp claim is missing"))?; + + if now > exp + self.clock_skew.as_secs() { + return Err(Error::invalid_field("exp", "Token has expired")); + } + + let iss = claims + .get("iss") + .and_then(|v| v.as_str()) + .ok_or_else(|| Error::invalid_field("iss", "iss claim is missing"))?; + + // TODO(#3591): Support TPC/REP that can have different issuers + let valid_issuers = ["https://accounts.google.com", "accounts.google.com"]; + if !valid_issuers.contains(&iss) { + return Err(Error::invalid_field( + "iss", + format!("Invalid issuer: {}", iss), + )); + } + + let aud_val = claims + .get("aud") + .ok_or_else(|| Error::invalid_field("aud", "aud claim is missing"))?; + let aud_match = match aud_val { + Value::String(s) => self.audiences.contains(s), + Value::Array(arr) => arr.iter().any(|v| { + v.as_str() + .is_some_and(|s| self.audiences.contains(&s.to_string())) + }), + _ => return Err(Error::invalid_field("aud", "Invalid aud format")), + }; + + if !aud_match { + return Err(Error::invalid_field( + "aud", + format!("Invalid audience: {:?}", aud_val), + )); + } + if let Some(email) = expected_email { let email_verified = claims["email_verified"] .as_bool() @@ -300,11 +404,12 @@ pub(crate) mod tests { use crate::credentials::internal::jwk_client::tests::{ create_es256_jwk_set_response, create_rsa256_jwk_set_response, }; + use crate::credentials::service_account::jws::JwsHeader; + use base64::Engine; use httptest::matchers::{all_of, request}; use httptest::responders::{json_encoded, status_code}; use httptest::{Expectation, Server}; - use jsonwebtoken::{Algorithm, EncodingKey, Header}; - use rsa::pkcs1::EncodeRsaPrivateKey; + use serde_json::json; use std::collections::HashMap; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -566,17 +671,22 @@ pub(crate) mod tests { #[tokio::test] async fn test_verify_missing_kid() -> TestResult { - let header = Header::new(Algorithm::RS256); - let claims: HashMap<&str, Value> = HashMap::new(); - - let private_cert = crate::credentials::tests::RSA_PRIVATE_KEY - .to_pkcs1_der() - .expect("Failed to encode private key to PKCS#1 DER"); - - let private_key = EncodingKey::from_rsa_der(private_cert.as_bytes()); - - let token = - jsonwebtoken::encode(&header, &claims, &private_key).expect("failed to encode jwt"); + let header = JwsHeader { + alg: "RS256", + typ: "JWT", + kid: None, + }; + + let claims_json = json!({}); + let encoded_claims = + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims_json.to_string()); + + let token = format!( + "{}.{}.{}", + header.encode()?, + encoded_claims, + "signature_placeholder" + ); let verifier = Builder::new(["https://example.com"]).build(); diff --git a/src/auth/src/credentials/internal/jwk_client.rs b/src/auth/src/credentials/internal/jwk_client.rs index 1721c9ca58..e2d6879664 100644 --- a/src/auth/src/credentials/internal/jwk_client.rs +++ b/src/auth/src/credentials/internal/jwk_client.rs @@ -14,7 +14,15 @@ use crate::Result; use crate::errors::CredentialsError; -use jsonwebtoken::{Algorithm, DecodingKey, jwk::JwkSet}; +use base64::Engine; +use pkcs1::RsaPublicKey; +use rustls::pki_types::SubjectPublicKeyInfoDer; +use serde::Deserialize; +use spki::der::{ + Encode, + asn1::{BitString, Null, UintRef}, +}; +use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; use std::{ collections::HashMap, sync::Arc, @@ -26,9 +34,31 @@ const IAP_JWK_URL: &str = "https://www.gstatic.com/iap/verify/public_key-jwk"; const OAUTH2_JWK_URL: &str = "https://www.googleapis.com/oauth2/v3/certs"; const CACHE_TTL: Duration = Duration::from_secs(3600); +#[derive(Clone, Debug, Deserialize)] +struct Jwk { + pub n: Option, + pub e: Option, + pub x: Option, + pub y: Option, + pub crv: Option, + pub kty: String, + pub kid: String, +} + +#[derive(Clone, Debug, Deserialize)] +struct JwkSet { + keys: Vec, +} + +impl JwkSet { + fn find(&self, kid: &str) -> Option<&Jwk> { + self.keys.iter().find(|k| k.kid == kid) + } +} + #[derive(Clone, Debug)] struct CacheEntry { - key: DecodingKey, + key: SubjectPublicKeyInfoDer<'static>, expires_at: Instant, } @@ -57,9 +87,9 @@ impl JwkClient { pub async fn get_or_load_cert( &self, key_id: String, - alg: Algorithm, + alg: &str, jwks_url: Option, - ) -> Result { + ) -> Result> { let key_id_str = key_id.as_str(); let mut cache = self.cache.try_write().map_err(|_e| { CredentialsError::from_msg(false, "failed to obtain lock to read certificate cache") @@ -76,8 +106,7 @@ impl JwkClient { CredentialsError::from_msg(false, "JWKS did not contain a matching `kid`") })?; - let key = DecodingKey::from_jwk(jwk) - .map_err(|e| CredentialsError::new(false, "failed to parse JWK", e))?; + let key = create_der(jwk)?; let entry = CacheEntry { key: key.clone(), @@ -88,13 +117,13 @@ impl JwkClient { Ok(key) } - fn resolve_jwks_url(&self, alg: Algorithm, jwks_url: Option) -> Result { + fn resolve_jwks_url(&self, alg: &str, jwks_url: Option) -> Result { if let Some(jwks_url) = jwks_url { return Ok(jwks_url); } match alg { - Algorithm::RS256 => Ok(OAUTH2_JWK_URL.to_string()), - Algorithm::ES256 => Ok(IAP_JWK_URL.to_string()), + "RS256" => Ok(OAUTH2_JWK_URL.to_string()), + "ES256" => Ok(IAP_JWK_URL.to_string()), _ => Err(CredentialsError::from_msg( false, format!( @@ -127,6 +156,98 @@ impl JwkClient { } } +// Creates a DER-encoded SubjectPublicKeyInfo from a JWK. +fn create_der(jwk: &Jwk) -> Result> { + match jwk.kty.as_str() { + "RSA" => create_rsa_der(jwk), + "EC" => create_ec_der(jwk), + _ => Err(CredentialsError::from_msg( + false, + format!("unsupported key type: {}", jwk.kty), + )), + } +} + +fn create_rsa_der(jwk: &Jwk) -> Result> { + let n = jwk + .n + .as_ref() + .ok_or_else(|| CredentialsError::from_msg(false, "missing n in RSA JWK"))?; + let e = jwk + .e + .as_ref() + .ok_or_else(|| CredentialsError::from_msg(false, "missing e in RSA JWK"))?; + + let n_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(n) + .map_err(|e| CredentialsError::from_msg(false, format!("invalid n in JWK: {}", e)))?; + let e_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(e) + .map_err(|e| CredentialsError::from_msg(false, format!("invalid e in JWK: {}", e)))?; + + let rsa_pub_key = RsaPublicKey { + modulus: UintRef::new(&n_bytes).map_err(|e| { + CredentialsError::from_msg(false, format!("invalid modulus in JWK: {}", e)) + })?, + public_exponent: UintRef::new(&e_bytes).map_err(|e| { + CredentialsError::from_msg(false, format!("invalid public exponent in JWK: {}", e)) + })?, + }; + let rsa_pub_key_der = rsa_pub_key.to_der().map_err(|e| { + CredentialsError::from_msg(false, format!("failed to encode RSA public key: {}", e)) + })?; + + let spki = SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier:: { + oid: pkcs1::ALGORITHM_OID, + parameters: Some(Null), + }, + subject_public_key: BitString::from_bytes(&rsa_pub_key_der).map_err(|e| { + CredentialsError::from_msg( + false, + format!("failed to create public key bitstring: {}", e), + ) + })?, + }; + + let der_bytes = spki + .to_der() + .map_err(|e| CredentialsError::from_msg(false, format!("Failed to encode SPKI: {}", e)))?; + Ok(SubjectPublicKeyInfoDer::from(der_bytes)) +} + +fn create_ec_der(jwk: &Jwk) -> Result> { + let x = jwk + .x + .as_ref() + .ok_or_else(|| CredentialsError::from_msg(false, "missing x in EC JWK"))?; + let y = jwk + .y + .as_ref() + .ok_or_else(|| CredentialsError::from_msg(false, "missing y in EC JWK"))?; + if jwk.crv.as_deref() != Some("P-256") { + return Err(CredentialsError::from_msg( + false, + format!("unsupported curve: {:?}", jwk.crv), + )); + } + + let x_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(x) + .map_err(|e| CredentialsError::from_msg(false, format!("invalid x in JWK: {}", e)))?; + let y_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(y) + .map_err(|e| CredentialsError::from_msg(false, format!("invalid y in JWK: {}", e)))?; + + // Uncompressed point format: 0x04 || x || y + let mut public_key_bytes = Vec::with_capacity(1 + x_bytes.len() + y_bytes.len()); + public_key_bytes.push(0x04); + public_key_bytes.extend_from_slice(&x_bytes); + public_key_bytes.extend_from_slice(&y_bytes); + + Ok(SubjectPublicKeyInfoDer::from(public_key_bytes)) +} + #[cfg(test)] pub(crate) mod tests { use super::*; @@ -134,7 +255,6 @@ pub(crate) mod tests { use httptest::matchers::{all_of, request}; use httptest::responders::json_encoded; use httptest::{Expectation, Server}; - use jsonwebtoken::Algorithm; use p256::elliptic_curve::sec1::ToEncodedPoint; use rsa::traits::PublicKeyParts; use serial_test::parallel; @@ -193,16 +313,12 @@ pub(crate) mod tests { // First call, should fetch from URL let _key = client - .get_or_load_cert( - TEST_KEY_ID.to_string(), - Algorithm::RS256, - Some(jwks_url.clone()), - ) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url.clone())) .await?; // Second call, should use cache let _key = client - .get_or_load_cert(TEST_KEY_ID.to_string(), Algorithm::RS256, Some(jwks_url)) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url)) .await?; Ok(()) @@ -223,7 +339,7 @@ pub(crate) mod tests { let jwks_url = format!("http://{}/certs", server.addr()); let result = client - .get_or_load_cert("unknown-kid".to_string(), Algorithm::RS256, Some(jwks_url)) + .get_or_load_cert("unknown-kid".to_string(), "RS256", Some(jwks_url)) .await; assert!(result.is_err()); @@ -250,7 +366,7 @@ pub(crate) mod tests { let jwks_url = format!("http://{}/certs", server.addr()); let result = client - .get_or_load_cert(TEST_KEY_ID.to_string(), Algorithm::RS256, Some(jwks_url)) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url)) .await; assert!(result.is_err()); @@ -268,26 +384,21 @@ pub(crate) mod tests { // Custom URL let url = "https://example.com/jwks".to_string(); assert_eq!( - client - .resolve_jwks_url(Algorithm::RS256, Some(url.clone())) - .unwrap(), + client.resolve_jwks_url("RS256", Some(url.clone())).unwrap(), url ); // Default for RS256 assert_eq!( - client.resolve_jwks_url(Algorithm::RS256, None).unwrap(), + client.resolve_jwks_url("RS256", None).unwrap(), OAUTH2_JWK_URL ); // Default for ES256 - assert_eq!( - client.resolve_jwks_url(Algorithm::ES256, None).unwrap(), - IAP_JWK_URL - ); + assert_eq!(client.resolve_jwks_url("ES256", None).unwrap(), IAP_JWK_URL); // Unsupported algorithm - let result = client.resolve_jwks_url(Algorithm::HS256, None); + let result = client.resolve_jwks_url("HS256", None); assert!(result.is_err()); Ok(()) @@ -309,20 +420,12 @@ pub(crate) mod tests { // First call, should fetch from URL and cache it. let _key = client - .get_or_load_cert( - TEST_KEY_ID.to_string(), - Algorithm::RS256, - Some(jwks_url.clone()), - ) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url.clone())) .await?; // Second call, should still be cached. let _key = client - .get_or_load_cert( - TEST_KEY_ID.to_string(), - Algorithm::RS256, - Some(jwks_url.clone()), - ) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url.clone())) .await?; // Wait for the cache to expire. @@ -330,7 +433,7 @@ pub(crate) mod tests { // This call should fetch from URL again. let _key = client - .get_or_load_cert(TEST_KEY_ID.to_string(), Algorithm::RS256, Some(jwks_url)) + .get_or_load_cert(TEST_KEY_ID.to_string(), "RS256", Some(jwks_url)) .await?; Ok(()) @@ -352,18 +455,37 @@ pub(crate) mod tests { // First call, should fetch from URL let _key = client - .get_or_load_cert( - TEST_KEY_ID.to_string(), - Algorithm::ES256, - Some(jwks_url.clone()), - ) + .get_or_load_cert(TEST_KEY_ID.to_string(), "ES256", Some(jwks_url.clone())) .await?; // Second call, should use cache let _key = client - .get_or_load_cert(TEST_KEY_ID.to_string(), Algorithm::ES256, Some(jwks_url)) + .get_or_load_cert(TEST_KEY_ID.to_string(), "ES256", Some(jwks_url)) .await?; Ok(()) } + + // TODO: remove since this is probably gonna be flaky. + // Added just to make sure it works with real certs. + #[tokio::test] + async fn test_fetch_real_certs() -> TestResult { + let client = JwkClient::new(); + + println!("Fetching OAuth2 certs from {}", OAUTH2_JWK_URL); + let jwk_set = client.fetch_certs(OAUTH2_JWK_URL.to_string()).await?; + assert!(!jwk_set.keys.is_empty(), "OAuth2 keys should not be empty"); + for key in &jwk_set.keys { + create_der(key).expect(&format!("failed to create DER for key {}", key.kid)); + } + + println!("Fetching IAP certs from {}", IAP_JWK_URL); + let jwk_set = client.fetch_certs(IAP_JWK_URL.to_string()).await?; + assert!(!jwk_set.keys.is_empty(), "IAP keys should not be empty"); + for key in &jwk_set.keys { + create_der(key).expect(&format!("failed to create DER for key {}", key.kid)); + } + + Ok(()) + } } diff --git a/src/auth/src/lib.rs b/src/auth/src/lib.rs index c68433f4cf..b674035ab7 100644 --- a/src/auth/src/lib.rs +++ b/src/auth/src/lib.rs @@ -35,17 +35,13 @@ //! verify [OIDC ID Tokens]. //! - `default-idtoken-backend`: enabled by default, this feature enables a default //! backend for the `idtoken` feature. Currently the feature is implemented using -//! the [jsonwebtoken] crate and uses `aws-lc-rs` as its default backend. We may -//! change the default backend at any time, applications that have specific needs -//! for this backend should not rely on the current default. To control the -//! backend selection: +//! the [rsa] crate. //! - Configure this crate with `default-features = false`, and //! `features = ["idtoken"]` -//! - Select the desired backend for `jsonwebtoken`. //! //! [aws-lc-rs]: https://crates.io/crates/aws-lc-rs //! [ring]: https://crates.io/crates/ring -//! [jsonwebtoken]: https://crates.io/crates/jsonwebtoken +//! [rsa]: https://crates.io/crates/rsa //! [oidc id tokens]: https://cloud.google.com/docs/authentication/token-types#identity-tokens //! [Authentication methods at Google]: https://cloud.google.com/docs/authentication //! [Principals]: https://cloud.google.com/docs/authentication#principal diff --git a/tests/crypto-providers/test-metadata/src/lib.rs b/tests/crypto-providers/test-metadata/src/lib.rs index 3911e2802d..e368593974 100644 --- a/tests/crypto-providers/test-metadata/src/lib.rs +++ b/tests/crypto-providers/test-metadata/src/lib.rs @@ -220,8 +220,6 @@ mod tests { assert!(v.is_ok(), "{v:?}"); let v = super::find_version(&metadata, "rustls"); assert!(v.is_ok(), "{v:?}"); - let v = super::find_version(&metadata, "jsonwebtoken"); - assert!(v.is_ok(), "{v:?}"); Ok(()) } }