From 6a146fbeb1545a8678a958e807c04b06c8d235f3 Mon Sep 17 00:00:00 2001 From: Rob Sliwa Date: Fri, 18 Oct 2024 16:16:05 -0400 Subject: [PATCH 1/2] Replaced JWT crate with jwt-rustcrypto crate. --- Cargo.toml | 16 +--- src/algorithm/algorithm.rs | 3 +- src/decoding.rs | 148 ++++++++++---------------------- src/encoding.rs | 125 ++++++--------------------- src/error.rs | 7 +- src/header.rs | 2 + src/holder.rs | 4 +- src/jwk.rs | 7 +- src/test_utils.rs | 22 +++-- src/validation.rs | 79 ++++++++++------- src/verifier.rs | 19 ++-- tests/common.rs | 22 +++-- tests/encoding_decoding_test.rs | 4 +- 13 files changed, 170 insertions(+), 288 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbddece..1e649a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,19 +17,9 @@ serde_yaml = "0.9.30" thiserror = "1.0.51" rand = "0.8.5" base64 = "0.21.5" -sha2 = "0.10.8" chrono = "0.4.31" -rsa = "0.8" -getrandom = { version = "0.2.12", features = ["js"] } - -jsonwebtoken-rustcrypto = { version = "1.2.0", optional = true } -serde_plain = { version = "1.0.2", optional = true } - -jsonwebtoken = { version = "9.2.0", optional = true } - -[features] -default = ["ring"] -ring = ["jsonwebtoken"] -noring = ["jsonwebtoken-rustcrypto", "serde_plain"] +sha2 = "0.10.8" +jwt-rustcrypto = "0.2.0" +rsa = "0.9.6" [dev-dependencies] diff --git a/src/algorithm/algorithm.rs b/src/algorithm/algorithm.rs index c4398db..6422e91 100644 --- a/src/algorithm/algorithm.rs +++ b/src/algorithm/algorithm.rs @@ -6,7 +6,9 @@ pub enum Algorithm { HS384, HS512, ES256, + ES256K, ES384, + ES512, #[default] RS256, RS384, @@ -14,5 +16,4 @@ pub enum Algorithm { PS256, PS384, PS512, - EdDSA, } diff --git a/src/decoding.rs b/src/decoding.rs index 170f26e..4edfeb4 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -1,104 +1,70 @@ use crate::{Algorithm, Error, Validation}; -#[cfg(feature = "noring")] -use base64::Engine; -#[cfg(feature = "ring")] -use jsonwebtoken::{ - decode as jwt_decode, Algorithm as JwtAlgorithm, DecodingKey, Validation as JwtValidation, +use jwt_rustcrypto::{ + decode as jwt_decode, Algorithm as JwtAlgorithm, ValidationOptions as JwtValidation, + VerifyingKey, }; - -#[cfg(feature = "noring")] -use jsonwebtoken_rustcrypto::{ - decode as jwt_decode, Algorithm as JwtAlgorithm, DecodingKey, Validation as JwtValidation, -}; -#[cfg(feature = "noring")] -use rsa::PublicKeyParts; -#[cfg(feature = "noring")] -use rsa::{pkcs1::DecodeRsaPublicKey, RsaPublicKey}; - use serde_json::Value; -#[derive(Clone)] pub struct KeyForDecoding { - key: DecodingKey, + key: VerifyingKey, } impl KeyForDecoding { - #[cfg(feature = "ring")] pub fn from_secret(secret: &[u8]) -> Self { KeyForDecoding { - key: DecodingKey::from_secret(secret), + key: VerifyingKey::from_secret(secret), } } - #[cfg(feature = "ring")] pub fn from_base64_secret(secret: &str) -> Result { Ok(KeyForDecoding { - key: DecodingKey::from_base64_secret(secret)?, + key: VerifyingKey::from_base64_secret(secret)?, }) } - #[cfg(feature = "ring")] pub fn from_rsa_pem(key: &[u8]) -> Result { Ok(KeyForDecoding { - key: DecodingKey::from_rsa_pem(key)?, + key: VerifyingKey::from_rsa_pem(key)?, }) } - #[cfg(feature = "noring")] - pub fn from_rsa_pem(key: &[u8]) -> Result { - let rsa_key = RsaPublicKey::from_pkcs1_pem(std::str::from_utf8(key)?)?; - - let modulus = - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(rsa_key.n().to_bytes_be()); - let exponent = - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(rsa_key.e().to_bytes_be()); - - Self::from_rsa_components(&modulus, &exponent) - } - - pub fn from_rsa_components(modulus: &str, exponent: &str) -> Result { + pub fn from_rsa_components(modulus: &[u8], exponent: &[u8]) -> Result { Ok(KeyForDecoding { - key: DecodingKey::from_rsa_components(modulus, exponent)?, + key: VerifyingKey::from_rsa_components(modulus, exponent)?, }) } - #[cfg(feature = "ring")] pub fn from_ec_pem(key: &[u8]) -> Result { Ok(KeyForDecoding { - key: DecodingKey::from_ec_pem(key)?, + key: VerifyingKey::from_ec_pem(key)?, }) } - #[cfg(feature = "ring")] - pub fn from_ed_pem(key: &[u8]) -> Result { + // pub fn from_ed_pem(key: &[u8]) -> Result { + // Ok(KeyForDecoding { + // key: DecodingKey::from_ed_pem(key)?, + // }) + // } + + pub fn from_rsa_der(der: &[u8]) -> Result { Ok(KeyForDecoding { - key: DecodingKey::from_ed_pem(key)?, + key: VerifyingKey::from_rsa_der(der)?, }) } - #[cfg(feature = "ring")] - pub fn from_rsa_der(der: &[u8]) -> Self { - KeyForDecoding { - key: DecodingKey::from_rsa_der(der), - } - } - - #[cfg(feature = "ring")] - pub fn from_ec_der(der: &[u8]) -> Self { - KeyForDecoding { - key: DecodingKey::from_ec_der(der), - } + pub fn from_ec_der(der: &[u8]) -> Result { + Ok(KeyForDecoding { + key: VerifyingKey::from_ec_der(der)?, + }) } - #[cfg(feature = "ring")] - pub fn from_ed_der(der: &[u8]) -> Self { - KeyForDecoding { - key: DecodingKey::from_ed_der(der), - } + pub fn from_ed_der(der: &[u8]) -> Result { + Ok(KeyForDecoding { + key: VerifyingKey::from_ed_der(der)?, + }) } } -#[cfg(feature = "ring")] fn build_validation(validation: &Validation) -> JwtValidation { let mut valid = JwtValidation::new(match validation.algorithms { Algorithm::HS256 => JwtAlgorithm::HS256, @@ -108,48 +74,20 @@ fn build_validation(validation: &Validation) -> JwtValidation { Algorithm::RS384 => JwtAlgorithm::RS384, Algorithm::RS512 => JwtAlgorithm::RS512, Algorithm::ES256 => JwtAlgorithm::ES256, + Algorithm::ES256K => JwtAlgorithm::ES256K, Algorithm::ES384 => JwtAlgorithm::ES384, + Algorithm::ES512 => JwtAlgorithm::ES512, Algorithm::PS256 => JwtAlgorithm::PS256, Algorithm::PS384 => JwtAlgorithm::PS384, Algorithm::PS512 => JwtAlgorithm::PS512, - Algorithm::EdDSA => JwtAlgorithm::EdDSA, }); - valid.required_spec_claims = validation.required_spec_claims.clone(); + valid.required_claims = validation.required_spec_claims.clone(); valid.leeway = validation.leeway; valid.validate_exp = validation.validate_exp; valid.validate_nbf = validation.validate_nbf; - valid.validate_aud = validation.validate_aud; - valid.aud = validation.aud.clone(); - valid.iss = validation.iss.clone(); - valid.sub = validation.sub.clone(); - valid -} - -#[cfg(feature = "noring")] -fn build_validation(validation: &Validation) -> JwtValidation { - let mut valid = JwtValidation::new(match validation.algorithms { - Algorithm::HS256 => JwtAlgorithm::HS256, - Algorithm::HS384 => JwtAlgorithm::HS384, - Algorithm::HS512 => JwtAlgorithm::HS512, - Algorithm::RS256 => JwtAlgorithm::RS256, - Algorithm::RS384 => JwtAlgorithm::RS384, - Algorithm::RS512 => JwtAlgorithm::RS512, - Algorithm::ES256 => JwtAlgorithm::ES256, - Algorithm::ES384 => JwtAlgorithm::ES384, - Algorithm::PS256 => JwtAlgorithm::PS256, - Algorithm::PS384 => JwtAlgorithm::PS384, - Algorithm::PS512 => JwtAlgorithm::PS512, - // Algorithm::EdDSA => JwtAlgorithm::EdDSA, - _ => unimplemented!(), - }); - - valid.leeway = validation.leeway; - valid.validate_exp = validation.validate_exp; - valid.validate_nbf = validation.validate_nbf; - valid.aud = validation.aud.clone(); - valid.iss = validation.iss.clone(); - valid.sub = validation.sub.clone(); + valid.audiences = validation.aud.clone(); + valid.issuer = validation.iss.clone(); valid } @@ -161,7 +99,7 @@ pub fn decode( let validation = build_validation(validation); let token_data = jwt_decode(token, &key.key, &validation)?; let header: Value = serde_json::from_str(&serde_json::to_string(&token_data.header)?)?; - Ok((header, token_data.claims)) + Ok((header, token_data.payload)) } pub fn sd_jwt_parts(serialized_jwt: &str) -> (String, Vec, Option) { @@ -187,10 +125,12 @@ pub fn sd_jwt_parts(serialized_jwt: &str) -> (String, Vec, Option (RsaPrivateKey, RsaPublicKey) { - let mut rng = OsRng; + let mut rng = rand::thread_rng(); + let bits = 2048; - let private_key = RsaPrivateKey::new(&mut rng, bits).unwrap(); - let public_key = RsaPublicKey::from(&private_key); + let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let signing_key = SigningKey::::new(private_key.clone()); + let public_key = signing_key.verifying_key().into(); (private_key, public_key) } @@ -225,10 +167,12 @@ mod tests { fn convert_to_pem(private_key: RsaPrivateKey, public_key: RsaPublicKey) -> (String, String) { ( private_key - .to_pkcs8_pem(rsa::pkcs8::LineEnding::CR) + .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF) .unwrap() .to_string(), - public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::CR).unwrap(), + public_key + .to_public_key_pem(rsa::pkcs1::LineEnding::LF) + .unwrap(), ) } @@ -236,6 +180,8 @@ mod tests { fn test_basic_decode() -> Result<(), Error> { let (priv_key, pub_key) = keys(); let (issuer_private_key, issuer_public_key) = convert_to_pem(priv_key, pub_key); + println!("issuer_private_key: {:?}", issuer_private_key); + println!("issuer_public_key: {:?}", issuer_public_key); let mut claims: Value = serde_json::from_str(TEST_CLAIMS).unwrap(); let now = Utc::now(); let expiration = now + Duration::minutes(5); diff --git a/src/encoding.rs b/src/encoding.rs index 763924a..54fbb3b 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,97 +1,67 @@ use crate::Algorithm; use crate::Error; use crate::Header; -#[cfg(feature = "ring")] -use jsonwebtoken::{ - encode as jwt_encode, Algorithm as JwtAlgorithm, EncodingKey, Header as JwtHeader, +use jwt_rustcrypto::{ + encode as jwt_encode, Algorithm as JwtAlgorithm, Header as JwtHeader, SigningKey, }; -#[cfg(feature = "noring")] -use jsonwebtoken_rustcrypto::{ - encode as jwt_encode, - // headers::{JwtHeader, X509Headers}, - Algorithm as JwtAlgorithm, - EncodingKey, - Header as JwtHeader, -}; -#[cfg(feature = "noring")] -use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; - use serde::Serialize; #[derive(Clone)] pub struct KeyForEncoding { - key: EncodingKey, + key: SigningKey, } impl KeyForEncoding { - #[cfg(feature = "ring")] pub fn from_secret(secret: &[u8]) -> Self { KeyForEncoding { - key: EncodingKey::from_secret(secret), + key: SigningKey::from_secret(secret), } } - #[cfg(feature = "ring")] pub fn from_base64_secret(secret: &str) -> Result { Ok(KeyForEncoding { - key: EncodingKey::from_base64_secret(secret)?, - }) - } - - #[cfg(feature = "ring")] - pub fn from_rsa_pem(key: &[u8]) -> Result { - Ok(KeyForEncoding { - key: EncodingKey::from_rsa_pem(key)?, + key: SigningKey::from_base64_secret(secret)?, }) } - #[cfg(feature = "noring")] pub fn from_rsa_pem(key: &[u8]) -> Result { - let rsa_key = RsaPrivateKey::from_pkcs8_pem(std::str::from_utf8(key)?)?; - Ok(KeyForEncoding { - key: EncodingKey::from_rsa(rsa_key)?, + key: SigningKey::from_rsa_pem(key)?, }) } - #[cfg(feature = "ring")] pub fn from_ec_pem(key: &[u8]) -> Result { Ok(KeyForEncoding { - key: EncodingKey::from_ec_pem(key)?, + key: SigningKey::from_ec_pem(key)?, }) } - #[cfg(feature = "ring")] pub fn from_ed_pem(key: &[u8]) -> Result { Ok(KeyForEncoding { - key: EncodingKey::from_ed_pem(key)?, + key: SigningKey::from_ed_pem(key)?, }) } - #[cfg(feature = "ring")] - pub fn from_rsa_der(der: &[u8]) -> Self { - KeyForEncoding { - key: EncodingKey::from_rsa_der(der), - } + pub fn from_rsa_der(der: &[u8]) -> Result { + Ok(KeyForEncoding { + key: SigningKey::from_rsa_der(der)?, + }) } - #[cfg(feature = "ring")] - pub fn from_ec_der(der: &[u8]) -> Self { - KeyForEncoding { - key: EncodingKey::from_ec_der(der), - } + pub fn from_ec_der(der: &[u8]) -> Result { + Ok(KeyForEncoding { + key: SigningKey::from_ec_der(der)?, + }) } - #[cfg(feature = "ring")] - pub fn from_ed_der(der: &[u8]) -> Self { - KeyForEncoding { - key: EncodingKey::from_ed_der(der), - } + pub fn from_ed_der(der: &[u8]) -> Result { + Ok(KeyForEncoding { + key: SigningKey::from_ed_der(der)?, + }) } } -#[cfg(feature = "ring")] fn build_header(header: &Header) -> Result { let jwk = match &header.jwk { Some(jwk) => Some(serde_json::from_value(jwk.clone())?), @@ -107,11 +77,13 @@ fn build_header(header: &Header) -> Result { Algorithm::RS384 => JwtAlgorithm::RS384, Algorithm::RS512 => JwtAlgorithm::RS512, Algorithm::ES256 => JwtAlgorithm::ES256, + Algorithm::ES256K => JwtAlgorithm::ES256K, Algorithm::ES384 => JwtAlgorithm::ES384, + Algorithm::ES512 => JwtAlgorithm::ES512, Algorithm::PS256 => JwtAlgorithm::PS256, Algorithm::PS384 => JwtAlgorithm::PS384, Algorithm::PS512 => JwtAlgorithm::PS512, - Algorithm::EdDSA => JwtAlgorithm::EdDSA, + // Algorithm::EdDSA => JwtAlgorithm::EdDSA, }, cty: header.cty.clone(), jku: header.jku.clone(), @@ -121,61 +93,14 @@ fn build_header(header: &Header) -> Result { x5c: header.x5c.clone(), x5t: header.x5t.clone(), x5t_s256: header.x5t_s256.clone(), + crit: header.crit.clone(), }) } -#[cfg(feature = "noring")] -fn build_header(header: &Header) -> Result { - // let jwk = match &header.jwk { - // Some(jwk) => Some(serde_json::from_value(jwk.clone())?), - // None => None, - // }; - - let alg = match header.alg { - Algorithm::HS256 => JwtAlgorithm::HS256, - Algorithm::HS384 => JwtAlgorithm::HS384, - Algorithm::HS512 => JwtAlgorithm::HS512, - Algorithm::RS256 => JwtAlgorithm::RS256, - Algorithm::RS384 => JwtAlgorithm::RS384, - Algorithm::RS512 => JwtAlgorithm::RS512, - Algorithm::ES256 => JwtAlgorithm::ES256, - Algorithm::ES384 => JwtAlgorithm::ES384, - Algorithm::PS256 => JwtAlgorithm::PS256, - Algorithm::PS384 => JwtAlgorithm::PS384, - Algorithm::PS512 => JwtAlgorithm::PS512, - // Algorithm::EdDSA => JwtAlgorithm::EdDSA, - _ => unimplemented!(), - }; - - let mut jwt_header = JwtHeader::new(alg); - jwt_header.typ = header.typ.clone(); - jwt_header.jku = header.jku.clone(); - jwt_header.kid = header.kid.clone(); - jwt_header.cty = header.cty.clone(); - // jwt_header.jwk_set_headers.jwk = jwk; - - // let mut x509_headers = None; - // if header.x5u.is_some() - // || header.x5c.is_some() - // || header.x5t.is_some() - // || header.x5t_s256.is_some() - // { - // x509_headers = Some(Box::new(X509Headers { - // x5u: header.x5u.clone(), - // x5c: header.x5c.clone(), - // x5t: header.x5t.clone(), - // x5t_s256: header.x5t_s256.clone(), - // })); - // } - // jwt_header.x509_headers = x509_headers; - - Ok(jwt_header) -} - pub fn encode( header: &Header, claims: &T, key: &KeyForEncoding, ) -> Result { - Ok(jwt_encode(&build_header(header)?, claims, &key.key)?) + Ok(jwt_encode(&build_header(header)?, &key.key, claims)?) } diff --git a/src/error.rs b/src/error.rs index a29c8a3..55251f5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,7 @@ +use jwt_rustcrypto::Error as JwtError; use serde_json::Error as SerdeError; use thiserror::Error; -#[cfg(feature = "ring")] -use jsonwebtoken::errors::Error as JwtError; - -#[cfg(feature = "noring")] -use jsonwebtoken_rustcrypto::errors::Error as JwtError; - #[derive(Error, Debug)] pub enum Error { #[error("failed to form disclosuer")] diff --git a/src/header.rs b/src/header.rs index 66776d1..30abec5 100644 --- a/src/header.rs +++ b/src/header.rs @@ -14,6 +14,7 @@ pub struct Header { pub x5c: Option>, pub x5t: Option, pub x5t_s256: Option, + pub crit: Option>, } impl Header { @@ -29,6 +30,7 @@ impl Header { x5c: None, x5t: None, x5t_s256: None, + crit: None, } } } diff --git a/src/holder.rs b/src/holder.rs index 687fdaa..472464c 100644 --- a/src/holder.rs +++ b/src/holder.rs @@ -26,7 +26,7 @@ use serde_json::Value; /// const ISSUER_SD_JWT: &str = "eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJSUzI1NiJ9.eyJfc2QiOlsiVFhsUEt1RjM1cDQ3ZW9XTlpEcklxS0w0R0JFaDBFWXJEQnBjNmFCWjUyQSIsIkdYWlpyVUlsdnBtaDB4b0h4WURadzFOZ211WXJrd1VVS09rNG1XTHZKYUEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJhZGRyZXNzIjp7Il9zZCI6WyJiUjVKM21ULXQ0a05pZ0V0dDJ5RVd1MU92b0hVMzBmSTZ1RVdJd2ozZWJBIiwiczhicTVKeUtJaFFwcVR1Vl9hcVNtd090UVN5UHV1TUlUU2xINXg1UWI5RSJdLCJjb3VudHJ5IjoiVVMiLCJyZWdpb24iOiJBbnlzdGF0ZSJ9LCJiaXJ0aGRhdGUiOiIxOTQwLTAxLTAxIiwiY25mIjp7ImFsZyI6IlJTMjU2IiwiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJuIjoiNS1EZDU0WHNNQU5UWm9KMllCcHVpWmFfYXpyMzJIcEJ3MUZjanA1d1UwWFBqbW9NQTdKVllDSk4wU05maDZ0dFhyWHhhYWhFNXdmUzd4S1E0N1ZvWXhYTjlLa3kxMzdDSUx0Q0xPWUJDZkdULWFRRXJKS0FJWUVORWtzbVNpU3k0VnVWRk1yTzlMOV9KTzViZk02QjZ6X3pickJYX2MxU2s0UFRLTnBqRTcxcTJHenU4ak5GdTR0c0JaOFFSdmtJVldxNGdxVklQNTFQQmZEcmNfTm53dk1aallGN2pfc0Z5eGg2ZExTVV96QkRrZjJOVWo4VXQ0M25vcW9YMGJoaE96aGdyTlpadGpFMTlrZGFlZTJYbjBweG0td3QzRjBxUjZxd2F2TFRJT21LVHE0OFdXSGxvUk5QWXpGbEo4OHNOaVNLeW9Ta0hXMG9SVDlscUhGX3ZRIiwidXNlIjoic2lnIn0sImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsIm5hdGlvbmFsaXRpZXMiOlt7Ii4uLiI6InhnU2FMYS1CNk03OWpwVWZtaE9Hb0pkSHdNS0RNR0s3eUVKdC0tX0xScDAifSx7Ii4uLiI6Im5vNWxNSkVJSmRWdHozS3lDMVRXVkk2T2tsQnZIMjFCOExOOVEzWkxWRmMifV0sInBob25lX251bWJlciI6IisxLTIwMi01NTUtMDEwMSIsInBob25lX251bWJlcl92ZXJpZmllZCI6dHJ1ZSwic3ViIjoidXNlcl80MiIsInVwZGF0ZWRfYXQiOjE1NzAwMDAwMDB9.K2h-DNDgnq6q61tSxm1Gv-Hfo46SD8rEcP7yLFxcAlQNKBY-l1-bpXCJcqVZ7jugs2lqng0Cf9e34tM1OPkU3R6Pi5kUMGSyJ2y2ifsaZhGLCgxzNKk5W2ZxdkehzZQ6nHy6iu4flbT92Szv0eBR0hmS3hYTCtHlE4xib9G2dKWTQigB4ylPMkoRzbiKjgkucGkxSLN5ZQRXdxkez19bk5Q9BwuNLQMKG0lanq4ZJWq1C4LPt_K0WhEntyTL6SxVxGfR5HaUSxeYPCCOWSz9AVyZ46DWZGRx48PbuXGgLDH1UJYIsMej2F89CU-3QkWUrFq9b-DCYCQMxbBBekeLog~WyJoV2xxekkxY3piQzhCMnF2Mm5vN3pBIiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ~WyJ4NXdpQVg1Qks3MFNfYzhXX2Vybm5nIiwiZmFtaWx5X25hbWUiLCJEb2UiXQ~WyI4Q1BKSmNKV2tiOGVwT09yZkl5YUNRIiwic3RyZWV0X2FkZHJlc3MiLCIxMjMgTWFpbiBTdCJd~WyJDTGo2S0tjblA1M2taOG5kOWFueWxnIiwibG9jYWxpdHkiLCJBbnl0b3duIl0~WyI4UEVqT3FlY245cjhGY0llWThhRjh3IiwiVVMiXQ~WyJMR2hVZmV2Y0FkTGVUUEVzRnlCNi1BIiwiREUiXQ~"; /// /// fn main() -> Result<(), Error> { -/// let mut validation = Validation::default().no_exp(); +/// let mut validation = Validation::default().without_expiry(); /// let decoding_key = KeyForDecoding::from_rsa_pem(ISSUER_PUBKEY.as_bytes())?; /// let (header, decoded_claims, disclosure_paths) = /// Holder::verify(ISSUER_SD_JWT, &decoding_key, &validation)?; @@ -271,7 +271,7 @@ impl Holder { /// const ISSUER_SD_JWT: &str = "eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJSUzI1NiJ9.eyJfc2QiOlsiVFhsUEt1RjM1cDQ3ZW9XTlpEcklxS0w0R0JFaDBFWXJEQnBjNmFCWjUyQSIsIkdYWlpyVUlsdnBtaDB4b0h4WURadzFOZ211WXJrd1VVS09rNG1XTHZKYUEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJhZGRyZXNzIjp7Il9zZCI6WyJiUjVKM21ULXQ0a05pZ0V0dDJ5RVd1MU92b0hVMzBmSTZ1RVdJd2ozZWJBIiwiczhicTVKeUtJaFFwcVR1Vl9hcVNtd090UVN5UHV1TUlUU2xINXg1UWI5RSJdLCJjb3VudHJ5IjoiVVMiLCJyZWdpb24iOiJBbnlzdGF0ZSJ9LCJiaXJ0aGRhdGUiOiIxOTQwLTAxLTAxIiwiY25mIjp7ImFsZyI6IlJTMjU2IiwiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJuIjoiNS1EZDU0WHNNQU5UWm9KMllCcHVpWmFfYXpyMzJIcEJ3MUZjanA1d1UwWFBqbW9NQTdKVllDSk4wU05maDZ0dFhyWHhhYWhFNXdmUzd4S1E0N1ZvWXhYTjlLa3kxMzdDSUx0Q0xPWUJDZkdULWFRRXJKS0FJWUVORWtzbVNpU3k0VnVWRk1yTzlMOV9KTzViZk02QjZ6X3pickJYX2MxU2s0UFRLTnBqRTcxcTJHenU4ak5GdTR0c0JaOFFSdmtJVldxNGdxVklQNTFQQmZEcmNfTm53dk1aallGN2pfc0Z5eGg2ZExTVV96QkRrZjJOVWo4VXQ0M25vcW9YMGJoaE96aGdyTlpadGpFMTlrZGFlZTJYbjBweG0td3QzRjBxUjZxd2F2TFRJT21LVHE0OFdXSGxvUk5QWXpGbEo4OHNOaVNLeW9Ta0hXMG9SVDlscUhGX3ZRIiwidXNlIjoic2lnIn0sImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsIm5hdGlvbmFsaXRpZXMiOlt7Ii4uLiI6InhnU2FMYS1CNk03OWpwVWZtaE9Hb0pkSHdNS0RNR0s3eUVKdC0tX0xScDAifSx7Ii4uLiI6Im5vNWxNSkVJSmRWdHozS3lDMVRXVkk2T2tsQnZIMjFCOExOOVEzWkxWRmMifV0sInBob25lX251bWJlciI6IisxLTIwMi01NTUtMDEwMSIsInBob25lX251bWJlcl92ZXJpZmllZCI6dHJ1ZSwic3ViIjoidXNlcl80MiIsInVwZGF0ZWRfYXQiOjE1NzAwMDAwMDB9.K2h-DNDgnq6q61tSxm1Gv-Hfo46SD8rEcP7yLFxcAlQNKBY-l1-bpXCJcqVZ7jugs2lqng0Cf9e34tM1OPkU3R6Pi5kUMGSyJ2y2ifsaZhGLCgxzNKk5W2ZxdkehzZQ6nHy6iu4flbT92Szv0eBR0hmS3hYTCtHlE4xib9G2dKWTQigB4ylPMkoRzbiKjgkucGkxSLN5ZQRXdxkez19bk5Q9BwuNLQMKG0lanq4ZJWq1C4LPt_K0WhEntyTL6SxVxGfR5HaUSxeYPCCOWSz9AVyZ46DWZGRx48PbuXGgLDH1UJYIsMej2F89CU-3QkWUrFq9b-DCYCQMxbBBekeLog~WyJoV2xxekkxY3piQzhCMnF2Mm5vN3pBIiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ~WyJ4NXdpQVg1Qks3MFNfYzhXX2Vybm5nIiwiZmFtaWx5X25hbWUiLCJEb2UiXQ~WyI4Q1BKSmNKV2tiOGVwT09yZkl5YUNRIiwic3RyZWV0X2FkZHJlc3MiLCIxMjMgTWFpbiBTdCJd~WyJDTGo2S0tjblA1M2taOG5kOWFueWxnIiwibG9jYWxpdHkiLCJBbnl0b3duIl0~WyI4UEVqT3FlY245cjhGY0llWThhRjh3IiwiVVMiXQ~WyJMR2hVZmV2Y0FkTGVUUEVzRnlCNi1BIiwiREUiXQ~"; /// /// fn main() -> Result<(), Error> { - /// let mut validation = Validation::default().no_exp(); + /// let mut validation = Validation::default().without_expiry(); /// let decoding_key = KeyForDecoding::from_rsa_pem(ISSUER_PUBKEY.as_bytes())?; /// let (header, decoded_claims, disclosure_paths) = /// Holder::verify(ISSUER_SD_JWT, &decoding_key, &validation)?; diff --git a/src/jwk.rs b/src/jwk.rs index a461493..8bcde9e 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -1,12 +1,7 @@ use crate::Error; +use jwt_rustcrypto::Jwk as JwtJwk; use std::ops::{Deref, DerefMut}; -#[cfg(feature = "ring")] -use jsonwebtoken::jwk::Jwk as JwtJwk; - -#[cfg(feature = "noring")] -use crate::registries::Jwk as JwtJwk; - #[derive(Debug, Clone)] pub struct Jwk { jwk: JwtJwk, diff --git a/src/test_utils.rs b/src/test_utils.rs index 2c801ed..b24af99 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,19 +1,23 @@ #[cfg(test)] pub mod common_test_utils { use base64::Engine; - use rand::rngs::OsRng; - use rsa::pkcs1::EncodeRsaPublicKey; + use rsa::pkcs1v15::SigningKey; use rsa::pkcs8::EncodePrivateKey; - use rsa::PublicKeyParts; + use rsa::pkcs8::EncodePublicKey; + use rsa::signature::Keypair; + use rsa::traits::PublicKeyParts; use rsa::{RsaPrivateKey, RsaPublicKey}; use serde_json::value::{Map, Value}; + use sha2::Sha256; use std::collections::HashSet; pub fn keys() -> (RsaPrivateKey, RsaPublicKey) { - let mut rng = OsRng; + let mut rng = rand::thread_rng(); + let bits = 2048; - let private_key = RsaPrivateKey::new(&mut rng, bits).unwrap(); - let public_key = RsaPublicKey::from(&private_key); + let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let signing_key = SigningKey::::new(private_key.clone()); + let public_key = signing_key.verifying_key().into(); (private_key, public_key) } @@ -24,10 +28,12 @@ pub mod common_test_utils { ) -> (String, String) { ( private_key - .to_pkcs8_pem(rsa::pkcs8::LineEnding::CR) + .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF) .unwrap() .to_string(), - public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::CR).unwrap(), + public_key + .to_public_key_pem(rsa::pkcs1::LineEnding::LF) + .unwrap(), ) } diff --git a/src/validation.rs b/src/validation.rs index 91b78b4..08ec057 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -6,32 +6,23 @@ use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Validation { - #[cfg(feature = "ring")] - pub required_spec_claims: HashSet, + pub required_spec_claims: Option>, pub leeway: u64, pub validate_exp: bool, pub validate_nbf: bool, - #[cfg(feature = "ring")] pub validate_aud: bool, pub aud: Option>, - #[cfg(feature = "ring")] - pub iss: Option>, - #[cfg(feature = "noring")] pub iss: Option, pub sub: Option, pub algorithms: Algorithm, } impl Validation { - #[cfg(feature = "ring")] pub fn new(alg: Algorithm) -> Validation { - let mut required_claims = HashSet::with_capacity(1); - required_claims.insert("exp".to_owned()); - Validation { - required_spec_claims: required_claims, + required_spec_claims: None, algorithms: alg, - leeway: 60, + leeway: 0, validate_exp: true, validate_nbf: false, @@ -43,34 +34,56 @@ impl Validation { } } - #[cfg(feature = "ring")] - pub fn no_exp(mut self) -> Self { - self.validate_exp = false; - self.required_spec_claims.remove("exp"); - self + /// Disable expiration (`exp`) validation. + pub fn without_expiry(self) -> Self { + Self { + validate_exp: false, + ..Self::default() + } } - #[cfg(feature = "noring")] - pub fn new(alg: Algorithm) -> Validation { - let mut required_claims = HashSet::with_capacity(1); - required_claims.insert("exp".to_owned()); - - Validation { - algorithms: alg, - leeway: 60, + /// Set a single audience member as the only acceptable value. + pub fn with_audience(self, audience: T) -> Self { + Self { + aud: Some(HashSet::from([audience.to_string()])), + ..self + } + } - validate_exp: true, - validate_nbf: false, + /// Set the issuer claim to validate. + pub fn with_issuer(self, issuer: T) -> Self { + Self { + iss: Some(issuer.to_string()), + ..self + } + } - iss: None, - sub: None, - aud: None, + /// Set the subject claim to validate. + pub fn with_subject(self, subject: T) -> Self { + Self { + sub: Some(subject.to_string()), + ..self } } - #[cfg(feature = "noring")] - pub fn no_exp(mut self) -> Self { - self.validate_exp = false; + /// Set leeway for time-related claims (`exp`, `nbf`, `iat`). + pub fn with_leeway(self, leeway: u64) -> Self { + Self { leeway, ..self } + } + + /// Add an allowed signing algorithm. + pub fn with_algorithm(mut self, alg: Algorithm) -> Self { + self.algorithms = alg; + self + } + + /// Add a required claim. + pub fn with_required_claim(mut self, claim: T) -> Self { + if let Some(ref mut required_claims) = self.required_spec_claims { + required_claims.insert(claim.to_string()); + } else { + self.required_spec_claims = Some(HashSet::from([claim.to_string()])); + } self } } diff --git a/src/verifier.rs b/src/verifier.rs index b511af7..5dc9a77 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -3,6 +3,7 @@ use crate::{ utils::{drop_kb, remove_digests, restore_disclosures}, Error, HashAlgorithm, Jwk, KeyForDecoding, Validation, }; +use base64::Engine; use serde_json::Value; /// # Verifier Module @@ -23,8 +24,8 @@ use serde_json::Value; /// const PRESENTATION: &str = "eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJSUzI1NiJ9.eyJfc2QiOlsiYlQzVnNrcVBwc0F1RWJ5VXBVb0o1UVVZaFp6TkZWSWw5TUhkN0hiWjNOSSIsInRWam9RWW1iT2FUOEt6YmRTMFpmUTdUTlU2UFlmV1RxQU1nNVlOUVJ1OUEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJhZGRyZXNzIjp7Il9zZCI6WyJ5WC13SXRkMmk1M2pCaV9jeHk3TE5Wd1J6Mm84ajlyd1IxQVJnVVFtVm9vIiwiQi14a3FHNzRvQzFCOUdheDlqQWZTWlVtQlBrVldhVmR1QVBSYlJkWHIyYyJdLCJjb3VudHJ5IjoiVVMiLCJyZWdpb24iOiJBbnlzdGF0ZSJ9LCJiaXJ0aGRhdGUiOiIxOTQwLTAxLTAxIiwiY25mIjp7ImFsZyI6IlJTMjU2IiwiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJuIjoiMFEta0s0aGZQbzZsMmFvVzlWUHR6S2hTaV9iN2t6ZTZ6eTlfVThTZjFsRmdxUGIwVXBvRTNuTW4zRUpyc0Jfb1hhb1RmY0RxaG4zTi1EblRFUFFmSTBfRTdnaHc3M0g1TWxiREdZM2VyajdzamE0enFIbmUyX1BZRnJvTFd3V0tjZDMzbUQ3VzhVYTdVSGV1a21GekFreXFEZlp1b0ZRcFdYLTFaVVdnalc0LUpoUUtYSXB4NVF6U1ZDX1hwaUFibzN3Zk5jQlFaaE8xSGxlTDV3VnFyMVZrUTgxcXl6Tlo3UFVRTWd0VlJGdkIyX3lPTlBDZ3piVzQ0TGNVQUFzYk5HNkdyX095WlBvblhuQml3b085LUxnNXdoQVc1TnlkU2ZwVi05UzE0NjV3Nm9IenpxdU1DX0JhcUQ5WVFTZ2pPVXpJb21fc3lYZG5GSTNyWWRZaG93IiwidXNlIjoic2lnIn0sImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMzg2NDkxMSwibmF0aW9uYWxpdGllcyI6W3siLi4uIjoiRDVSLXVQVEhMaTVFNVJqWEJwaW5Ia0VfV1Jxckl0UVFndnFyYWpEZ3ZPTSJ9LHsiLi4uIjoiNTJwZGc4enYtQ1RLT3U1bDhnVUpRalNKQ0I2dHF0NVJ1dUk5WkRESTJCdyJ9XSwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwicGhvbmVfbnVtYmVyX3ZlcmlmaWVkIjp0cnVlLCJzdWIiOiJ1c2VyXzQyIiwidXBkYXRlZF9hdCI6MTU3MDAwMDAwMH0.aziX_zt4VylvCt4b_ILZacHQYWGFGsMUd0KEVgg4qtj8JwljDoL8845eHjV1ldpBp7hyWnkrV1X7ZtM7WK1F987ntNv5hK9o-5C2H18UpYKI9YZz5f8yETkWBmu9sH5HKtPv0lstJFc-kQB-jKRyidMxhwO_MU_oR_UtjpIjVd6atRLrwlud4ZM-un8R2R209au8TIE4JIAyzJA1IC5NTR4FdCcwGJiodj62lGRVpmvWhQspxtA9aGKSrnx0x8rL82_dE0hBrRkq5cfbiPR5GM1BN7FtA68OrWK9STHCAaH3VQxe0htOg3o8wlQ6rPMIP5B1Oc0932K56bGwXDZPCg~WyJGSjNhS2JyaWNONUdZRGQtdVk2dGVnIiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ~WyItQkFxQ2VJN0kzVUdaREJQR1RNcUpRIiwibG9jYWxpdHkiLCJBbnl0b3duIl0~WyI2RF8zUFpoSlQxTHVDR3o2WTVOMjVBIiwiREUiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20iLCJpYXQiOjE3MDM4NjQ4NTEsIm5vbmNlIjoiODEzOWN2ZUdVTjFKQW1QTllGeWg5eTdqWmZab2VMZXIiLCJzZF9oYXNoIjoidUU1MTY0eTVqZ1NFNWg1V2FiUFpnU0lLWDFOX015Ti1qMlJhNnE3NDJ0ayJ9.BtYvadr-iT6poH9DQV5xAJxAxIFFsNRJ6AQ1rrGySpCVZ-1Dg7a9mvkP3Tf7dJ-r8O-cndJEaUaiKXSFZW7H8j-wO3hp0hrEqlp9OpCNON2EnwUrSm_XLFUFe-MinJZDMZ3qJeCLk7-AMvOgEHXHautwA3Sj2W_G4oDtH05tEHdy50lTVSblqINOLTdy8Vkz82Hs1WW7CVeUOQbsGbKNNAPczTDf00fQg18n6nGmpkHp7rgMV-Sq4qV2qxDeuXE00AkgPAzcMRyCx3Gk7NSWn9NtkTPK9Bporf58r_p5hf4lp-RoqRT0Uza1d5FcaoONl9GtLnhYURLKlCo9yhCbOA"; /// /// fn main() -> Result<(), Error> { -/// let validation = Validation::default().no_exp(); -/// let mut kb_validation = Validation::default().no_exp(); +/// let validation = Validation::default().without_expiry(); +/// let mut kb_validation = Validation::default().without_expiry(); /// let mut audience = HashSet::new(); /// audience.insert("https://someone.example.com".to_string()); /// kb_validation.aud = Some(audience); @@ -110,8 +111,8 @@ impl Verifier { /// const PRESENTATION: &str = "eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJSUzI1NiJ9.eyJfc2QiOlsiYlQzVnNrcVBwc0F1RWJ5VXBVb0o1UVVZaFp6TkZWSWw5TUhkN0hiWjNOSSIsInRWam9RWW1iT2FUOEt6YmRTMFpmUTdUTlU2UFlmV1RxQU1nNVlOUVJ1OUEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJhZGRyZXNzIjp7Il9zZCI6WyJ5WC13SXRkMmk1M2pCaV9jeHk3TE5Wd1J6Mm84ajlyd1IxQVJnVVFtVm9vIiwiQi14a3FHNzRvQzFCOUdheDlqQWZTWlVtQlBrVldhVmR1QVBSYlJkWHIyYyJdLCJjb3VudHJ5IjoiVVMiLCJyZWdpb24iOiJBbnlzdGF0ZSJ9LCJiaXJ0aGRhdGUiOiIxOTQwLTAxLTAxIiwiY25mIjp7ImFsZyI6IlJTMjU2IiwiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJuIjoiMFEta0s0aGZQbzZsMmFvVzlWUHR6S2hTaV9iN2t6ZTZ6eTlfVThTZjFsRmdxUGIwVXBvRTNuTW4zRUpyc0Jfb1hhb1RmY0RxaG4zTi1EblRFUFFmSTBfRTdnaHc3M0g1TWxiREdZM2VyajdzamE0enFIbmUyX1BZRnJvTFd3V0tjZDMzbUQ3VzhVYTdVSGV1a21GekFreXFEZlp1b0ZRcFdYLTFaVVdnalc0LUpoUUtYSXB4NVF6U1ZDX1hwaUFibzN3Zk5jQlFaaE8xSGxlTDV3VnFyMVZrUTgxcXl6Tlo3UFVRTWd0VlJGdkIyX3lPTlBDZ3piVzQ0TGNVQUFzYk5HNkdyX095WlBvblhuQml3b085LUxnNXdoQVc1TnlkU2ZwVi05UzE0NjV3Nm9IenpxdU1DX0JhcUQ5WVFTZ2pPVXpJb21fc3lYZG5GSTNyWWRZaG93IiwidXNlIjoic2lnIn0sImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMzg2NDkxMSwibmF0aW9uYWxpdGllcyI6W3siLi4uIjoiRDVSLXVQVEhMaTVFNVJqWEJwaW5Ia0VfV1Jxckl0UVFndnFyYWpEZ3ZPTSJ9LHsiLi4uIjoiNTJwZGc4enYtQ1RLT3U1bDhnVUpRalNKQ0I2dHF0NVJ1dUk5WkRESTJCdyJ9XSwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwicGhvbmVfbnVtYmVyX3ZlcmlmaWVkIjp0cnVlLCJzdWIiOiJ1c2VyXzQyIiwidXBkYXRlZF9hdCI6MTU3MDAwMDAwMH0.aziX_zt4VylvCt4b_ILZacHQYWGFGsMUd0KEVgg4qtj8JwljDoL8845eHjV1ldpBp7hyWnkrV1X7ZtM7WK1F987ntNv5hK9o-5C2H18UpYKI9YZz5f8yETkWBmu9sH5HKtPv0lstJFc-kQB-jKRyidMxhwO_MU_oR_UtjpIjVd6atRLrwlud4ZM-un8R2R209au8TIE4JIAyzJA1IC5NTR4FdCcwGJiodj62lGRVpmvWhQspxtA9aGKSrnx0x8rL82_dE0hBrRkq5cfbiPR5GM1BN7FtA68OrWK9STHCAaH3VQxe0htOg3o8wlQ6rPMIP5B1Oc0932K56bGwXDZPCg~WyJGSjNhS2JyaWNONUdZRGQtdVk2dGVnIiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ~WyItQkFxQ2VJN0kzVUdaREJQR1RNcUpRIiwibG9jYWxpdHkiLCJBbnl0b3duIl0~WyI2RF8zUFpoSlQxTHVDR3o2WTVOMjVBIiwiREUiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20iLCJpYXQiOjE3MDM4NjQ4NTEsIm5vbmNlIjoiODEzOWN2ZUdVTjFKQW1QTllGeWg5eTdqWmZab2VMZXIiLCJzZF9oYXNoIjoidUU1MTY0eTVqZ1NFNWg1V2FiUFpnU0lLWDFOX015Ti1qMlJhNnE3NDJ0ayJ9.BtYvadr-iT6poH9DQV5xAJxAxIFFsNRJ6AQ1rrGySpCVZ-1Dg7a9mvkP3Tf7dJ-r8O-cndJEaUaiKXSFZW7H8j-wO3hp0hrEqlp9OpCNON2EnwUrSm_XLFUFe-MinJZDMZ3qJeCLk7-AMvOgEHXHautwA3Sj2W_G4oDtH05tEHdy50lTVSblqINOLTdy8Vkz82Hs1WW7CVeUOQbsGbKNNAPczTDf00fQg18n6nGmpkHp7rgMV-Sq4qV2qxDeuXE00AkgPAzcMRyCx3Gk7NSWn9NtkTPK9Bporf58r_p5hf4lp-RoqRT0Uza1d5FcaoONl9GtLnhYURLKlCo9yhCbOA"; /// /// fn main() -> Result<(), Error> { - /// let validation = Validation::default().no_exp(); - /// let mut kb_validation = Validation::default().no_exp(); + /// let validation = Validation::default().without_expiry(); + /// let mut kb_validation = Validation::default().without_expiry(); /// let mut audience = HashSet::new(); /// audience.insert("https://someone.example.com".to_string()); /// kb_validation.aud = Some(audience); @@ -159,15 +160,17 @@ pub fn verify_kb( "Issuer SD JWT cnf claim must contain RSA key".to_string(), )); } - let e = kb_jwk["e"].as_str().ok_or(Error::SDJWTRejected( + let e_str = kb_jwk["e"].as_str().ok_or(Error::SDJWTRejected( "Issuer SD JWT cnf claim must contain RSA key, invalid exponent".to_string(), ))?; - let n = kb_jwk["n"].as_str().ok_or(Error::SDJWTRejected( + let n_str = kb_jwk["n"].as_str().ok_or(Error::SDJWTRejected( "Issuer SD JWT cnf claim must contain RSA key, invalid modulus".to_string(), ))?; + let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(n_str)?; + let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(e_str)?; let (header, claims) = decode( kb_jwt, - &KeyForDecoding::from_rsa_components(n, e)?, + &KeyForDecoding::from_rsa_components(&n, &e)?, validation, )?; @@ -331,7 +334,7 @@ mod tests { // Verifier verifies presentation let validation = Validation::default(); - let mut kb_validation = Validation::default().no_exp(); + let mut kb_validation = Validation::default().without_expiry(); let mut audience = HashSet::new(); audience.insert("https://someone.example.com".to_string()); kb_validation.aud = Some(audience); diff --git a/tests/common.rs b/tests/common.rs index c3c9a56..9716a2b 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,17 +1,21 @@ use base64::Engine; -use rand::rngs::OsRng; -use rsa::pkcs1::EncodeRsaPublicKey; +use rsa::pkcs1v15::SigningKey; use rsa::pkcs8::EncodePrivateKey; -use rsa::PublicKeyParts; +use rsa::pkcs8::EncodePublicKey; +use rsa::signature::Keypair; +use rsa::traits::PublicKeyParts; use rsa::{RsaPrivateKey, RsaPublicKey}; use serde_json::value::{Map, Value}; +use sha2::Sha256; use std::collections::HashSet; pub fn keys() -> (RsaPrivateKey, RsaPublicKey) { - let mut rng = OsRng; + let mut rng = rand::thread_rng(); + let bits = 2048; - let private_key = RsaPrivateKey::new(&mut rng, bits).unwrap(); - let public_key = RsaPublicKey::from(&private_key); + let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let signing_key = SigningKey::::new(private_key.clone()); + let public_key = signing_key.verifying_key().into(); (private_key, public_key) } @@ -19,10 +23,12 @@ pub fn keys() -> (RsaPrivateKey, RsaPublicKey) { pub fn convert_to_pem(private_key: RsaPrivateKey, public_key: RsaPublicKey) -> (String, String) { ( private_key - .to_pkcs8_pem(rsa::pkcs8::LineEnding::CR) + .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF) .unwrap() .to_string(), - public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::CR).unwrap(), + public_key + .to_public_key_pem(rsa::pkcs1::LineEnding::LF) + .unwrap(), ) } diff --git a/tests/encoding_decoding_test.rs b/tests/encoding_decoding_test.rs index 255febd..56a99ee 100644 --- a/tests/encoding_decoding_test.rs +++ b/tests/encoding_decoding_test.rs @@ -192,7 +192,7 @@ fn test_presentation_verification_with_kb() -> Result<(), Error> { // Verifier verifies presentation let validation = Validation::default(); - let mut kb_validation = Validation::default().no_exp(); + let mut kb_validation = Validation::default().without_expiry(); let mut audience = HashSet::new(); audience.insert("https://someone.example.com".to_string()); kb_validation.aud = Some(audience); @@ -289,7 +289,7 @@ fn test_issue_claims_with_yaml() -> Result<(), Error> { // Verifier verifies presentation let validation = Validation::default(); - let mut kb_validation = Validation::default().no_exp(); + let mut kb_validation = Validation::default().without_expiry(); let mut audience = HashSet::new(); audience.insert("https://someone.example.com".to_string()); kb_validation.aud = Some(audience); From 2516a824129e5cc59193f6e484dcc38ab4152c2f Mon Sep 17 00:00:00 2001 From: Rob Sliwa Date: Mon, 21 Oct 2024 08:51:48 -0400 Subject: [PATCH 2/2] Updated README and fixed linter errors. --- .github/workflows/ci.yml | 35 ++++++++++++++++--- Cargo.toml | 2 +- README.md | 8 +++++ src/algorithm/{algorithm.rs => algorithms.rs} | 0 src/algorithm/mod.rs | 4 +-- src/decoy.rs | 7 ++-- src/issuer.rs | 12 +++---- src/lib.rs | 4 +-- 8 files changed, 52 insertions(+), 20 deletions(-) rename src/algorithm/{algorithm.rs => algorithms.rs} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 981393f..eda8941 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ name: Rust on: push: branches: [ "main" ] + tags: + - 'release_v*' pull_request: branches: [ "main" ] @@ -16,11 +18,36 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install wasm32 target + run: rustup target add wasm32-unknown-unknown + - name: Check code formatting + run: cargo fmt + - name: Run linter + run: cargo clippy - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose - - name: Build without Ring - run: cargo build --no-default-features --features "noring" - - name: Run tests without Ring - run: cargo test --no-default-features --features "noring" + - name: Build WASM + run: cargo build --target wasm32-unknown-unknown --release + + publish: + + if: startsWith(github.ref, 'refs/tags/release_v') + + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v4 + + - name: Install stable Rust + run: rustup install stable + + - name: Verify cargo package + run: cargo package + + - name: Publish to crates.io + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + run: cargo publish diff --git a/Cargo.toml b/Cargo.toml index 1e649a6..4b3acc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sdjwt" -version = "0.7.3" +version = "0.8.0" authors = ["Rob Sliwa "] license = "MIT" readme = "README.md" diff --git a/README.md b/README.md index 4a0114b..9dfe2a1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ Add the following to Cargo.toml: sdjwt = "0.7.0" ``` +# Supported Algorithms + +The library supports the following signing and verifying algorithms: + +- **HMAC**: `HS256`, `HS384`, `HS512` +- **RSA**: `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512` +- **ECDSA**: `ES256`, `ES256K`, `ES384`, `ES512` + # How to use There are three use cases: Issuer, Holder, and Verifier. diff --git a/src/algorithm/algorithm.rs b/src/algorithm/algorithms.rs similarity index 100% rename from src/algorithm/algorithm.rs rename to src/algorithm/algorithms.rs diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index 8a06859..a990615 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -1,5 +1,5 @@ -pub mod algorithm; +pub mod algorithms; pub mod hashalgorithm; -pub use algorithm::*; +pub use algorithms::*; pub use hashalgorithm::*; diff --git a/src/decoy.rs b/src/decoy.rs index 500a8d2..e006354 100644 --- a/src/decoy.rs +++ b/src/decoy.rs @@ -1,6 +1,6 @@ use crate::algorithm::{base64_hash, HashAlgorithm}; use crate::Error; -use rand::{Rng, SeedableRng, rngs::StdRng}; +use rand::{rngs::StdRng, Rng, SeedableRng}; #[derive(Debug, Clone)] pub struct Decoy { @@ -18,7 +18,7 @@ impl Decoy { } } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let seed: [u8; 32] = rand::random(); let mut rng = StdRng::from_seed(seed); let random_number: u32 = rng.gen(); @@ -58,5 +58,4 @@ mod tests { let decoy2 = Decoy::new().build().unwrap(); assert_ne!(decoy1.digest(), decoy2.digest()); } - -} \ No newline at end of file +} diff --git a/src/issuer.rs b/src/issuer.rs index bca0805..372b4c9 100644 --- a/src/issuer.rs +++ b/src/issuer.rs @@ -1,13 +1,13 @@ +use crate::decoy::Decoy; use crate::Disclosure; use crate::Error; use crate::Header; use crate::Jwk; -use crate::decoy::Decoy; use crate::{encode, KeyForEncoding}; use chrono::{Duration, Utc}; -use rand::Rng; -use rand::seq::SliceRandom; use core::slice::Iter; +use rand::seq::SliceRandom; +use rand::Rng; use serde::Serialize; use serde_json::Value; use std::ops::Deref; @@ -399,10 +399,10 @@ impl Issuer { .iter() .map(|disclosable_claim| build_disclosure(&mut updated_claims, disclosable_claim)) .collect(); - let disclosures = disclosures?; - + let disclosures = disclosures?; + if let Some(max_decoys) = self.max_decoys { - let decoy_count = rand::thread_rng().gen_range(1..max_decoys+1); + let decoy_count = rand::thread_rng().gen_range(1..max_decoys + 1); build_decoys(&mut updated_claims, decoy_count)?; } diff --git a/src/lib.rs b/src/lib.rs index 374baf2..3bfb04a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod algorithm; pub mod decoding; +pub(crate) mod decoy; pub mod disclosure; pub mod disclosure_path; pub mod encoding; @@ -9,9 +10,6 @@ pub mod holder; pub mod issuer; pub mod jwk; mod parser; -#[cfg(feature = "noring")] -pub(crate) mod registries; -pub(crate) mod decoy; mod utils; pub mod validation; pub mod verifier;