diff --git a/README.md b/README.md index 527625d..ef5b6fc 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,15 @@ a limited set of use cases. When should I use this library? ------------------------------- -If you wish to create ECDSA signed JWT tokens, or verify ECDSA signed JWT tokens, this library is for you. +If you are: -If you are implementing OIDC as a relying party or authorisation server, this library is for you. +* creating ECDSA signed JWT tokens, or verify ECDSA signed JWT tokens +* implementing OIDC as a relying party or authorisation server +* wanting to use HMAC signatures +* needing a minimal secure JWS implementation, this library is for you +* using TPM bound keys for signing JWTs -If you want to use HMAC signatures, have a full JWS implementation, or have the non-compact (JSON) -serialisation support, this library is not what you want. +If you need non-compact JWS, or other complex use cases, this library is not for you. Why another JWT library? ------------------------ @@ -30,7 +33,7 @@ or design that conflicts with the project goals in Kanidm. Examples are: * Ring as the sole cryptographic provider - we need to use OpenSSL * Only supporting RSA/Weak cryptographic algos - We want to use ECDSA * Full JWS implementation - As mentioned, JWS has a number of sharp edges like alg=none +* No library supports pkcs11 or TPMS - We aim to allow hardware security modules to store private keys As a result, nothing "fit" what we wanted, so we are making another library. - diff --git a/src/compact.rs b/src/compact.rs index df32201..8c595b6 100644 --- a/src/compact.rs +++ b/src/compact.rs @@ -8,7 +8,7 @@ use url::Url; use crate::error::JwtError; use crate::jws::Jws; -use crate::traits::JwsVerifier; +use crate::traits::JwsVerifiable; use base64urlsafedata::Base64UrlSafeData; // https://datatracker.ietf.org/doc/html/rfc7515 @@ -165,26 +165,6 @@ impl JwsCompact { pub fn get_jwk_pubkey(&self) -> Option<&Jwk> { self.header.jwk.as_ref() } - - /// Using this JwsVerifier, assert the correct signature of the data contained in - /// this token. - pub fn verify(&self, verifier: &mut K) -> Result { - if verifier.verify_signature(self)? { - general_purpose::URL_SAFE_NO_PAD - .decode(&self.payload_b64) - .map_err(|_| { - debug!("invalid base64 while decoding payload"); - JwtError::InvalidBase64 - }) - .map(|payload| Jws { - header: self.header.clone(), - payload, - }) - } else { - debug!("invalid signature"); - Err(JwtError::InvalidSignature) - } - } } impl FromStr for JwsCompact { @@ -264,3 +244,48 @@ impl fmt::Display for JwsCompact { write!(f, "{}.{}.{}", self.hdr_b64, self.payload_b64, sig) } } + +impl JwsVerifiable for JwsCompact { + type Verified = Jws; + + fn data(&self) -> JwsCompactVerifyData { + JwsCompactVerifyData { + header: &self.header, + hdr_bytes: self.hdr_b64.as_bytes(), + payload_bytes: self.payload_b64.as_bytes(), + signature_bytes: self.signature.as_slice(), + } + } + + fn post_process(&self, value: Jws) -> Result { + Ok(value) + } +} + +/// Data that will be verified +pub struct JwsCompactVerifyData<'a> { + #[allow(dead_code)] + pub(crate) header: &'a ProtectedHeader, + #[allow(dead_code)] + pub(crate) hdr_bytes: &'a [u8], + #[allow(dead_code)] + pub(crate) payload_bytes: &'a [u8], + #[allow(dead_code)] + pub(crate) signature_bytes: &'a [u8], +} + +#[cfg(any(feature = "unsafe_release_without_verify", feature = "openssl"))] +impl<'a> JwsCompactVerifyData<'a> { + pub(crate) fn release(&self) -> Result { + general_purpose::URL_SAFE_NO_PAD + .decode(self.payload_bytes) + .map_err(|_| { + debug!("invalid base64 while decoding payload"); + JwtError::InvalidBase64 + }) + .map(|payload| Jws { + header: self.header.clone(), + payload, + }) + } +} diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs new file mode 100644 index 0000000..0eaaeee --- /dev/null +++ b/src/crypto/es256.rs @@ -0,0 +1,574 @@ +//! JWS Signing and Verification Structures + +use openssl::{bn, ec, ecdsa, hash, nid, pkey}; +use std::convert::TryFrom; + +use crate::error::JwtError; + +use base64::{engine::general_purpose, Engine as _}; +use base64urlsafedata::Base64UrlSafeData; + +use crate::compact::{EcCurve, JwaAlg, Jwk, JwkUse, JwsCompact, ProtectedHeader}; +use crate::traits::*; + +/// A JWS signer that creates ECDSA P-256 signatures. +pub struct JwsEs256Signer { + /// If the public jwk should be embeded during signing + sign_option_embed_jwk: bool, + /// The KID of this validator + kid: String, + /// Private Key + skey: ec::EcKey, + /// The matching digest. + digest: hash::MessageDigest, +} + +impl JwsEs256Signer { + #[cfg(test)] + pub fn from_es256_jwk_components(x: &str, y: &str, d: &str) -> Result { + let x = general_purpose::URL_SAFE_NO_PAD.decode(x).map_err(|e| { + debug!(?e); + JwtError::InvalidBase64 + })?; + let y = general_purpose::URL_SAFE_NO_PAD.decode(y).map_err(|e| { + debug!(?e); + JwtError::InvalidBase64 + })?; + + let d = general_purpose::URL_SAFE_NO_PAD.decode(d).map_err(|e| { + debug!(?e); + JwtError::InvalidBase64 + })?; + + let xbn = bn::BigNum::from_slice(&x).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let ybn = bn::BigNum::from_slice(&y).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let dbn = bn::BigNum::from_slice(&d).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let ec_group = ec::EcGroup::from_curve_name(nid::Nid::X9_62_PRIME256V1).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let pkey = + ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let digest = hash::MessageDigest::sha256(); + + let skey = ec::EcKey::from_private_components(&ec_group, &dbn, pkey.public_key()).map_err( + |e| { + debug!(?e); + JwtError::OpenSSLError + }, + )?; + + skey.check_key().map_err(|_| JwtError::OpenSSLError)?; + + let kid = skey + .private_key_to_der() + .and_then(|der| hash::hash(digest, &der)) + .map(|hashout| hex::encode(hashout)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + Ok(JwsEs256Signer { + kid, + skey, + digest, + sign_option_embed_jwk: false, + }) + } + + /// Enable or disable embedding of the public jwk into the Jws that are signed + /// by this signer + pub fn set_sign_option_embed_jwk(&mut self, value: bool) { + self.sign_option_embed_jwk = value; + } + + /// Create a new secure private key for signing + pub fn generate_es256() -> Result { + let digest = hash::MessageDigest::sha256(); + let ec_group = ec::EcGroup::from_curve_name(nid::Nid::X9_62_PRIME256V1) + .map_err(|_| JwtError::OpenSSLError)?; + + let skey = ec::EcKey::generate(&ec_group).map_err(|_| JwtError::OpenSSLError)?; + + skey.check_key().map_err(|_| JwtError::OpenSSLError)?; + + let kid = skey + .private_key_to_der() + .and_then(|der| hash::hash(digest, &der)) + .map(hex::encode) + .map_err(|_| JwtError::OpenSSLError)?; + + Ok(JwsEs256Signer { + kid, + skey, + digest, + sign_option_embed_jwk: false, + }) + } + + /// Restore this JwsSignerEnum from a DER private key. + pub fn from_es256_der(der: &[u8]) -> Result { + let digest = hash::MessageDigest::sha256(); + + let kid = hash::hash(digest, der).map(hex::encode).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let skey = ec::EcKey::private_key_from_der(der).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + Ok(JwsEs256Signer { + kid, + skey, + digest, + sign_option_embed_jwk: false, + }) + } + + /// Export this signer to a DER private key. + pub fn private_key_to_der(&self) -> Result, JwtError> { + self.skey.private_key_to_der().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + }) + } + + /// Get the public Jwk from this signer + pub fn public_key_as_jwk(&self) -> Result { + let pkey = self.skey.public_key(); + let ec_group = self.skey.group(); + + let mut bnctx = bn::BigNumContext::new().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut xbn = bn::BigNum::new().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut ybn = bn::BigNum::new().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + pkey.affine_coordinates_gfp(ec_group, &mut xbn, &mut ybn, &mut bnctx) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut public_key_x = Vec::with_capacity(32); + let mut public_key_y = Vec::with_capacity(32); + + public_key_x.resize(32, 0); + public_key_y.resize(32, 0); + + let xbnv = xbn.to_vec(); + let ybnv = ybn.to_vec(); + + let (_pad, x_fill) = public_key_x.split_at_mut(32 - xbnv.len()); + x_fill.copy_from_slice(&xbnv); + + let (_pad, y_fill) = public_key_y.split_at_mut(32 - ybnv.len()); + y_fill.copy_from_slice(&ybnv); + + Ok(Jwk::EC { + crv: EcCurve::P256, + x: Base64UrlSafeData(public_key_x), + y: Base64UrlSafeData(public_key_y), + alg: Some(JwaAlg::ES256), + use_: Some(JwkUse::Sig), + kid: Some(self.kid.clone()), + }) + } +} + +impl JwsSignerToVerifier for JwsEs256Signer { + type Verifier = JwsEs256Verifier; + + fn get_verifier(&self) -> Result { + ec::EcKey::from_public_key(self.skey.group(), self.skey.public_key()) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + }) + .map_err(|_| JwtError::OpenSSLError) + .map(|pkey| JwsEs256Verifier { + kid: Some(self.kid.clone()), + pkey, + digest: self.digest, + }) + } +} + +impl JwsSigner for JwsEs256Signer { + fn get_kid(&self) -> &str { + self.kid.as_str() + } + + fn update_header(&self, header: &mut ProtectedHeader) -> Result<(), JwtError> { + // Update the alg to match. + header.alg = JwaAlg::ES256; + + header.kid = Some(self.kid.clone()); + + // if were were asked to ember the jwk, do so now. + if self.sign_option_embed_jwk { + header.jwk = self.public_key_as_jwk().map(Some)?; + } + + Ok(()) + } + + fn sign(&self, jws: &V) -> Result { + let mut sign_data = jws.data()?; + + // Let the signer update the header as required. + self.update_header(&mut sign_data.header)?; + + let hdr_b64 = serde_json::to_vec(&sign_data.header) + .map_err(|e| { + debug!(?e); + JwtError::InvalidHeaderFormat + }) + .map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?; + + let mut hasher = hash::Hasher::new(self.digest).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + hasher + .update(hdr_b64.as_bytes()) + .and_then(|_| hasher.update(".".as_bytes())) + .and_then(|_| hasher.update(sign_data.payload_b64.as_bytes())) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let hashout = hasher.finish().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let ec_sig = ecdsa::EcdsaSig::sign(&hashout, &self.skey).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut r = [0; 32]; + let r_vec = ec_sig.r().to_vec(); + let (_left, right) = r.split_at_mut(32 - r_vec.len()); + right.copy_from_slice(r_vec.as_slice()); + let mut s = [0; 32]; + let s_vec = ec_sig.s().to_vec(); + let (_left, right) = s.split_at_mut(32 - s_vec.len()); + right.copy_from_slice(s_vec.as_slice()); + + // trace!("r {:?}", r); + // trace!("s {:?}", s); + + let mut signature = Vec::with_capacity(64); + signature.extend_from_slice(&r); + signature.extend_from_slice(&s); + + let jwsc = JwsCompact { + header: sign_data.header, + hdr_b64, + payload_b64: sign_data.payload_b64, + signature, + }; + + jws.post_process(jwsc) + } +} + +/// A JWS verifier that creates ECDSA P-256 signatures. +pub struct JwsEs256Verifier { + /// The KID of this validator + kid: Option, + /// Public Key + pkey: ec::EcKey, + /// The matching digest. + digest: hash::MessageDigest, +} + +impl TryFrom<&Jwk> for JwsEs256Verifier { + type Error = JwtError; + + fn try_from(value: &Jwk) -> Result { + match value { + Jwk::EC { + crv: EcCurve::P256, + x, + y, + alg: _, + use_: _, + kid, + } => { + let curve = nid::Nid::X9_62_PRIME256V1; + let digest = hash::MessageDigest::sha256(); + + let ec_group = ec::EcGroup::from_curve_name(curve).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let xbn = bn::BigNum::from_slice(&x.0).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let ybn = bn::BigNum::from_slice(&y.0).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let pkey = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + pkey.check_key().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let kid = kid.clone(); + + Ok(JwsEs256Verifier { kid, pkey, digest }) + } + alg_request => { + debug!(?alg_request, "validator algorithm mismatch"); + Err(JwtError::ValidatorAlgMismatch) + } + } + } +} + +impl JwsVerifier for JwsEs256Verifier { + fn get_kid(&self) -> Option<&str> { + self.kid.as_deref() + } + + fn verify(&self, jwsc: &V) -> Result { + let signed_data = jwsc.data(); + + if signed_data.header.alg != JwaAlg::ES256 { + debug!(jwsc_alg = ?signed_data.header.alg, "validator algorithm mismatch"); + return Err(JwtError::ValidatorAlgMismatch); + } + + if signed_data.signature_bytes.len() != 64 { + return Err(JwtError::InvalidSignature); + } + + let r = bn::BigNum::from_slice(&signed_data.signature_bytes[..32]).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let s = bn::BigNum::from_slice(&signed_data.signature_bytes[32..64]).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let sig = ecdsa::EcdsaSig::from_private_components(r, s).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut hasher = hash::Hasher::new(self.digest).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + hasher + .update(signed_data.hdr_bytes) + .and_then(|_| hasher.update(".".as_bytes())) + .and_then(|_| hasher.update(signed_data.payload_bytes)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let hashout = hasher.finish().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let valid = sig.verify(&hashout, &self.pkey).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + if valid { + signed_data.release().and_then(|d| jwsc.post_process(d)) + } else { + debug!("invalid signature"); + Err(JwtError::InvalidSignature) + } + } +} + +#[cfg(test)] +mod tests { + use super::{JwsEs256Signer, JwsEs256Verifier}; + use crate::compact::{Jwk, JwsCompact}; + use crate::jws::JwsBuilder; + use crate::traits::*; + use std::convert::TryFrom; + use std::str::FromStr; + + #[test] + fn rfc7515_es256_validation_example() { + let _ = tracing_subscriber::fmt::try_init(); + let test_jws = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"; + + let jwsc = JwsCompact::from_str(test_jws).unwrap(); + + assert!(jwsc.to_string() == test_jws); + + assert!(jwsc.check_vectors( + &[ + 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 70, 85, 122, 73, 49, 78, 105, 74, + 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, + 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, + 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, + 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, + 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, + 81 + ], + &[ + 14, 209, 33, 83, 121, 99, 108, 72, 60, 47, 127, 21, 88, 7, 212, 2, 163, 178, 40, 3, + 58, 249, 124, 126, 23, 129, 154, 195, 22, 158, 166, 101, 197, 10, 7, 211, 140, 60, + 112, 229, 216, 241, 45, 175, 8, 74, 84, 128, 166, 101, 144, 197, 242, 147, 80, 154, + 143, 63, 127, 138, 131, 163, 84, 213 + ] + )); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + assert!(jwsc.get_jwk_pubkey().is_none()); + + let pkey = r#"{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}"#; + + let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); + trace!("jwk -> {:?}", pkey); + + let jwk_es256_verifier = + JwsEs256Verifier::try_from(&pkey).expect("Unable to create validator"); + + let released = jwk_es256_verifier + .verify(&jwsc) + .expect("Unable to verify jws"); + trace!("rel -> {:?}", released); + } + + #[test] + fn rfc7515_es256_signature_example() { + let _ = tracing_subscriber::fmt::try_init(); + // https://docs.rs/openssl/0.10.36/openssl/ec/struct.EcKey.html#method.from_private_components + let jws_es256_signer = JwsEs256Signer::from_es256_jwk_components( + "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", + ) + .expect("failed to construct signer"); + + let jws = JwsBuilder::from(vec![ + 123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, + 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, + 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, + 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125, + ]) + .build(); + + let jwsc = jws_es256_signer.sign(&jws).expect("Failed to sign"); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + assert!(jwsc.get_jwk_pubkey().is_none()); + + let pkey = r#"{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}"#; + + let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); + trace!("jwk -> {:?}", pkey); + + let jwk_es256_verifier = + JwsEs256Verifier::try_from(&pkey).expect("Unable to create validator"); + + let released = jwk_es256_verifier + .verify(&jwsc) + .expect("Unable to verify jws"); + + trace!("rel -> {:?}", released); + + // Test the verifier from the signer also works. + let jwk_es256_verifier = jws_es256_signer + .get_verifier() + .expect("failed to get verifier from signer"); + + let released = jwk_es256_verifier + .verify(&jwsc) + .expect("Unable to verify jws"); + + trace!("rel -> {:?}", released); + } + + #[test] + fn es256_key_generate_cycle() { + let _ = tracing_subscriber::fmt::try_init(); + let jws_es256_signer = + JwsEs256Signer::generate_es256().expect("failed to construct signer."); + + let der = jws_es256_signer + .private_key_to_der() + .expect("Failed to extract DER"); + + let mut jws_es256_signer = + JwsEs256Signer::from_es256_der(&der).expect("Failed to restore signer"); + + // This time we'll add the jwk pubkey and show it being used with the validator. + let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) + .set_kid(Some("abcd")) + .set_typ(Some("abcd")) + .set_cty(Some("abcd")) + .build(); + + jws_es256_signer.set_sign_option_embed_jwk(true); + + let jwsc = jws_es256_signer.sign(&jws).expect("Failed to sign"); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + let pub_jwk = jwsc.get_jwk_pubkey().expect("No embeded public jwk!"); + assert!(*pub_jwk == jws_es256_signer.public_key_as_jwk().unwrap()); + + let jwk_es256_verifier = + JwsEs256Verifier::try_from(pub_jwk).expect("Unable to create validator"); + + let released = jwk_es256_verifier + .verify(&jwsc) + .expect("Unable to validate jws"); + assert!(released.payload() == &[0, 1, 2, 3, 4]); + } +} diff --git a/src/crypto/hs256.rs b/src/crypto/hs256.rs new file mode 100644 index 0000000..24ba023 --- /dev/null +++ b/src/crypto/hs256.rs @@ -0,0 +1,257 @@ +//! JWS Signing and Verification Structures + +use crate::error::JwtError; +use openssl::{hash, pkey, rand, sign}; + +use crate::compact::{JwaAlg, JwsCompact, ProtectedHeader}; +use crate::traits::*; +use base64::{engine::general_purpose, Engine as _}; + +/// A JWS signer that creates HMAC SHA256 signatures. +pub struct JwsHs256Signer { + /// The KID of this signer. This is the sha256 digest of the key. + kid: String, + /// Private Key + skey: pkey::PKey, + /// The matching digest + digest: hash::MessageDigest, +} + +impl JwsHs256Signer { + /// Create a new secure private key for signing + pub fn generate_hs256() -> Result { + let digest = hash::MessageDigest::sha256(); + + let mut buf = [0; 32]; + rand::rand_bytes(&mut buf).map_err(|e| { + error!("{:?}", e); + JwtError::OpenSSLError + })?; + + // Can it become a pkey? + let skey = pkey::PKey::hmac(&buf).map_err(|e| { + error!("{:?}", e); + JwtError::OpenSSLError + })?; + + let mut kid = [0; 32]; + rand::rand_bytes(&mut kid).map_err(|e| { + error!("{:?}", e); + JwtError::OpenSSLError + })?; + + let kid = hash::hash(digest, &kid) + .map(hex::encode) + .map_err(|_| JwtError::OpenSSLError)?; + + Ok(JwsHs256Signer { kid, skey, digest }) + } +} + +#[cfg(test)] +impl TryFrom<&[u8]> for JwsHs256Signer { + type Error = JwtError; + + fn try_from(buf: &[u8]) -> Result { + if buf.len() < 32 { + return Err(JwtError::OpenSSLError); + } + + let digest = hash::MessageDigest::sha256(); + + let kid = hash::hash(digest, buf) + .map(|hashout| hex::encode(hashout)) + .map_err(|_| JwtError::OpenSSLError)?; + + let skey = pkey::PKey::hmac(buf).map_err(|e| { + error!("{:?}", e); + JwtError::OpenSSLError + })?; + + Ok(JwsHs256Signer { kid, skey, digest }) + } +} + +impl JwsSigner for JwsHs256Signer { + fn get_kid(&self) -> &str { + self.kid.as_str() + } + + fn update_header(&self, header: &mut ProtectedHeader) -> Result<(), JwtError> { + // Update the alg to match. + header.alg = JwaAlg::HS256; + + header.kid = Some(self.kid.clone()); + + Ok(()) + } + + fn sign(&self, jws: &V) -> Result { + let mut sign_data = jws.data()?; + + // Let the signer update the header as required. + self.update_header(&mut sign_data.header)?; + + let hdr_b64 = serde_json::to_vec(&sign_data.header) + .map_err(|e| { + debug!(?e); + JwtError::InvalidHeaderFormat + }) + .map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?; + + let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + signer + .update(hdr_b64.as_bytes()) + .and_then(|_| signer.update(".".as_bytes())) + .and_then(|_| signer.update(sign_data.payload_b64.as_bytes())) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let signature = signer.sign_to_vec().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let jwsc = JwsCompact { + header: sign_data.header, + hdr_b64, + payload_b64: sign_data.payload_b64, + signature, + }; + + jws.post_process(jwsc) + } +} + +impl JwsVerifier for JwsHs256Signer { + fn get_kid(&self) -> Option<&str> { + Some(self.kid.as_str()) + } + + fn verify(&self, jwsc: &V) -> Result { + let signed_data = jwsc.data(); + + if signed_data.header.alg != JwaAlg::HS256 { + debug!(jwsc_alg = ?signed_data.header.alg, "validator algorithm mismatch"); + return Err(JwtError::ValidatorAlgMismatch); + } + + let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + signer + .update(signed_data.hdr_bytes) + .and_then(|_| signer.update(".".as_bytes())) + .and_then(|_| signer.update(signed_data.payload_bytes)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let ver_sig = signer.sign_to_vec().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + if signed_data.signature_bytes == ver_sig.as_slice() { + signed_data.release().and_then(|d| jwsc.post_process(d)) + } else { + debug!("invalid signature"); + Err(JwtError::InvalidSignature) + } + } +} + +#[cfg(test)] +mod tests { + use super::JwsHs256Signer; + use crate::compact::JwsCompact; + use crate::traits::*; + use base64::{engine::general_purpose, Engine as _}; + use std::convert::TryFrom; + use std::str::FromStr; + + #[test] + fn rfc7519_hs256_validation_example_legacy() { + let _ = tracing_subscriber::fmt::try_init(); + let test_jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + + let jwsc = JwsCompact::from_str(test_jws).unwrap(); + + assert!(jwsc.check_vectors( + &[ + 101, 121, 74, 48, 101, 88, 65, 105, 79, 105, 74, 75, 86, 49, 81, 105, 76, 65, 48, + 75, 73, 67, 74, 104, 98, 71, 99, 105, 79, 105, 74, 73, 85, 122, 73, 49, 78, 105, + 74, 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, + 76, 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, + 52, 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, + 65, 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, + 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, + 102, 81 + ], + &[ + 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, + 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 + ] + )); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + assert!(jwsc.get_jwk_pubkey().is_none()); + + let skey = general_purpose::URL_SAFE_NO_PAD.decode( + "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" + ).expect("Invalid key"); + + let jws_signer = + JwsHs256Signer::try_from(skey.as_slice()).expect("Unable to create validator"); + + let released = jws_signer.verify(&jwsc).expect("Unable to validate jws"); + trace!("rel -> {:?}", released); + } + + #[test] + fn rfc7519_hs256_validation_example() { + let _ = tracing_subscriber::fmt::try_init(); + let test_jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + + let jwsc = JwsCompact::from_str(test_jws).unwrap(); + + assert!(jwsc.check_vectors( + &[ + 101, 121, 74, 48, 101, 88, 65, 105, 79, 105, 74, 75, 86, 49, 81, 105, 76, 65, 48, + 75, 73, 67, 74, 104, 98, 71, 99, 105, 79, 105, 74, 73, 85, 122, 73, 49, 78, 105, + 74, 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, + 76, 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, + 52, 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, + 65, 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, + 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, + 102, 81 + ], + &[ + 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, + 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 + ] + )); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + assert!(jwsc.get_jwk_pubkey().is_none()); + + let skey = general_purpose::URL_SAFE_NO_PAD.decode( + "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" + ).expect("Invalid key"); + + let jws_signer = + JwsHs256Signer::try_from(skey.as_slice()).expect("Unable to create validator"); + + let released = jws_signer.verify(&jwsc).expect("Unable to validate jws"); + trace!("rel -> {:?}", released); + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index e45d0bc..f41d5e7 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,18 +1,20 @@ //! JWS Signing and Verification Structures -use openssl::{bn, ec, ecdsa, hash, nid, pkey, rand, rsa, sign, x509}; -use std::convert::TryFrom; - use crate::error::JwtError; use base64::{engine::general_purpose, Engine as _}; -use base64urlsafedata::Base64UrlSafeData; +use openssl::x509::X509; + +use crate::compact::JwsCompact; -use crate::compact::{EcCurve, JwaAlg, Jwk, JwkUse, JwsCompact, ProtectedHeader}; -use crate::jws::Jws; -use crate::traits::*; +mod es256; +mod hs256; +mod rs256; +mod x509; -const RSA_MIN_SIZE: u32 = 3072; -const RSA_SIG_SIZE: i32 = 384; +pub use es256::{JwsEs256Signer, JwsEs256Verifier}; +pub use hs256::JwsHs256Signer; +pub use rs256::{JwsRs256Signer, JwsRs256Verifier}; +pub use x509::{JwsX509Verifier, JwsX509VerifierBuilder}; impl JwsCompact { #[cfg(test)] @@ -25,10 +27,9 @@ impl JwsCompact { /// toward the root. /// /// return [Ok(None)] if the jws object's header's x5c field isn't populated - pub fn get_x5c_chain(&self) -> Result>, JwtError> { - let fullchain = match &self.header.x5c { - Some(chain) => chain, - None => return Ok(None), + pub fn get_x5c_chain(&self) -> Result>, JwtError> { + let Some(fullchain) = &self.header.x5c else { + return Ok(None); }; let fullchain: Result, _> = fullchain @@ -38,7 +39,7 @@ impl JwsCompact { .decode(value) .map_err(|_| JwtError::InvalidBase64) .and_then(|bytes| { - x509::X509::from_der(&bytes).map_err(|e| { + X509::from_der(&bytes).map_err(|e| { debug!(?e); JwtError::OpenSSLError }) @@ -51,1328 +52,3 @@ impl JwsCompact { Ok(Some(fullchain)) } } - -impl Jws { - /// Sign the content of this JWS with the provided signer, yielding a compact - /// signed string. - pub fn sign(&self, signer: &mut S) -> Result { - let mut header = self.header.clone(); - - // Let the signer update the header as required. - signer.update_header(&mut header)?; - - let hdr_b64 = serde_json::to_vec(&header) - .map_err(|e| { - debug!(?e); - JwtError::InvalidHeaderFormat - }) - .map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(&bytes))?; - let payload_b64 = general_purpose::URL_SAFE_NO_PAD.encode(&self.payload); - - let data = JwsCompactSignData { - hdr_bytes: hdr_b64.as_bytes(), - payload_bytes: payload_b64.as_bytes(), - }; - - let signature = signer.sign(data)?; - - Ok(JwsCompact { - header, - hdr_b64, - payload_b64, - signature, - }) - } -} - -/// A JWS signer that creates ECDSA P-256 signatures. -pub struct JwsEs256Signer { - /// If the public jwk should be embeded during signing - sign_option_embed_jwk: bool, - /// The KID of this validator - kid: String, - /// Private Key - skey: ec::EcKey, - /// The matching digest. - digest: hash::MessageDigest, -} - -impl JwsEs256Signer { - #[cfg(test)] - pub fn from_es256_jwk_components(x: &str, y: &str, d: &str) -> Result { - let x = general_purpose::URL_SAFE_NO_PAD.decode(x).map_err(|e| { - debug!(?e); - JwtError::InvalidBase64 - })?; - let y = general_purpose::URL_SAFE_NO_PAD.decode(y).map_err(|e| { - debug!(?e); - JwtError::InvalidBase64 - })?; - - let d = general_purpose::URL_SAFE_NO_PAD.decode(d).map_err(|e| { - debug!(?e); - JwtError::InvalidBase64 - })?; - - let xbn = bn::BigNum::from_slice(&x).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let ybn = bn::BigNum::from_slice(&y).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let dbn = bn::BigNum::from_slice(&d).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let ec_group = ec::EcGroup::from_curve_name(nid::Nid::X9_62_PRIME256V1).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let pkey = - ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let digest = hash::MessageDigest::sha256(); - - let skey = ec::EcKey::from_private_components(&ec_group, &dbn, pkey.public_key()).map_err( - |e| { - debug!(?e); - JwtError::OpenSSLError - }, - )?; - - skey.check_key().map_err(|_| JwtError::OpenSSLError)?; - - let kid = skey - .private_key_to_der() - .and_then(|der| hash::hash(digest, &der)) - .map(|hashout| hex::encode(hashout)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - Ok(JwsEs256Signer { - kid, - skey, - digest, - sign_option_embed_jwk: false, - }) - } - - /// Enable or disable embedding of the public jwk into the Jws that are signed - /// by this signer - pub fn set_sign_option_embed_jwk(&mut self, value: bool) { - self.sign_option_embed_jwk = value; - } - - /// Create a new secure private key for signing - pub fn generate_es256() -> Result { - let digest = hash::MessageDigest::sha256(); - let ec_group = ec::EcGroup::from_curve_name(nid::Nid::X9_62_PRIME256V1) - .map_err(|_| JwtError::OpenSSLError)?; - - let skey = ec::EcKey::generate(&ec_group).map_err(|_| JwtError::OpenSSLError)?; - - skey.check_key().map_err(|_| JwtError::OpenSSLError)?; - - let kid = skey - .private_key_to_der() - .and_then(|der| hash::hash(digest, &der)) - .map(|hashout| hex::encode(hashout)) - .map_err(|_| JwtError::OpenSSLError)?; - - Ok(JwsEs256Signer { - kid, - skey, - digest, - sign_option_embed_jwk: false, - }) - } - - /// Restore this JwsSignerEnum from a DER private key. - pub fn from_es256_der(der: &[u8]) -> Result { - let digest = hash::MessageDigest::sha256(); - - let kid = hash::hash(digest, der) - .map(|hashout| hex::encode(hashout)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let skey = ec::EcKey::private_key_from_der(der).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - Ok(JwsEs256Signer { - kid, - skey, - digest, - sign_option_embed_jwk: false, - }) - } - - /// Export this signer to a DER private key. - pub fn private_key_to_der(&self) -> Result, JwtError> { - self.skey.private_key_to_der().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } - - /// Get the public Jwk from this signer - pub fn public_key_as_jwk(&self) -> Result { - let pkey = self.skey.public_key(); - let ec_group = self.skey.group(); - - let mut bnctx = bn::BigNumContext::new().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut xbn = bn::BigNum::new().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut ybn = bn::BigNum::new().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - pkey.affine_coordinates_gfp(ec_group, &mut xbn, &mut ybn, &mut bnctx) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut public_key_x = Vec::with_capacity(32); - let mut public_key_y = Vec::with_capacity(32); - - public_key_x.resize(32, 0); - public_key_y.resize(32, 0); - - let xbnv = xbn.to_vec(); - let ybnv = ybn.to_vec(); - - let (_pad, x_fill) = public_key_x.split_at_mut(32 - xbnv.len()); - x_fill.copy_from_slice(&xbnv); - - let (_pad, y_fill) = public_key_y.split_at_mut(32 - ybnv.len()); - y_fill.copy_from_slice(&ybnv); - - Ok(Jwk::EC { - crv: EcCurve::P256, - x: Base64UrlSafeData(public_key_x), - y: Base64UrlSafeData(public_key_y), - alg: Some(JwaAlg::ES256), - use_: Some(JwkUse::Sig), - kid: Some(self.kid.clone()), - }) - } -} - -impl JwsSignerToVerifier for JwsEs256Signer { - type Verifier = JwsEs256Verifier; - - fn get_verifier(&mut self) -> Result { - ec::EcKey::from_public_key(self.skey.group(), self.skey.public_key()) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - .map_err(|_| JwtError::OpenSSLError) - .map(|pkey| JwsEs256Verifier { - kid: Some(self.kid.clone()), - pkey, - digest: self.digest, - }) - } -} - -impl JwsSigner for JwsEs256Signer { - fn get_kid(&mut self) -> &str { - self.kid.as_str() - } - - fn update_header(&mut self, header: &mut ProtectedHeader) -> Result<(), JwtError> { - // Update the alg to match. - header.alg = JwaAlg::ES256; - - header.kid = Some(self.kid.clone()); - - // if were were asked to ember the jwk, do so now. - if self.sign_option_embed_jwk { - header.jwk = self.public_key_as_jwk().map(Some)?; - } - - Ok(()) - } - - fn sign(&mut self, jwsc: JwsCompactSignData<'_>) -> Result, JwtError> { - let mut hasher = hash::Hasher::new(self.digest).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - hasher - .update(jwsc.hdr_bytes) - .and_then(|_| hasher.update(".".as_bytes())) - .and_then(|_| hasher.update(jwsc.payload_bytes)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let hashout = hasher.finish().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let ec_sig = ecdsa::EcdsaSig::sign(&hashout, &self.skey).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut r = [0; 32]; - let r_vec = ec_sig.r().to_vec(); - let (_left, right) = r.split_at_mut(32 - r_vec.len()); - right.copy_from_slice(r_vec.as_slice()); - let mut s = [0; 32]; - let s_vec = ec_sig.s().to_vec(); - let (_left, right) = s.split_at_mut(32 - s_vec.len()); - right.copy_from_slice(s_vec.as_slice()); - - // trace!("r {:?}", r); - // trace!("s {:?}", s); - - let mut signature = Vec::with_capacity(64); - signature.extend_from_slice(&r); - signature.extend_from_slice(&s); - Ok(signature) - } -} - -/// A JWS verifier that creates ECDSA P-256 signatures. -pub struct JwsEs256Verifier { - /// The KID of this validator - kid: Option, - /// Public Key - pkey: ec::EcKey, - /// The matching digest. - digest: hash::MessageDigest, -} - -impl TryFrom<&Jwk> for JwsEs256Verifier { - type Error = JwtError; - - fn try_from(value: &Jwk) -> Result { - match value { - Jwk::EC { - crv: EcCurve::P256, - x, - y, - alg: _, - use_: _, - kid, - } => { - let curve = nid::Nid::X9_62_PRIME256V1; - let digest = hash::MessageDigest::sha256(); - - let ec_group = ec::EcGroup::from_curve_name(curve).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let xbn = bn::BigNum::from_slice(&x.0).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let ybn = bn::BigNum::from_slice(&y.0).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let pkey = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - pkey.check_key().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let kid = kid.clone(); - - Ok(JwsEs256Verifier { kid, pkey, digest }) - } - alg_request => { - debug!(?alg_request, "validator algorithm mismatch"); - Err(JwtError::ValidatorAlgMismatch) - } - } - } -} - -impl JwsVerifier for JwsEs256Verifier { - fn get_kid(&mut self) -> Option<&str> { - self.kid.as_deref() - } - - fn verify_signature(&mut self, jwsc: &JwsCompact) -> Result { - if jwsc.header.alg != JwaAlg::ES256 { - debug!(jwsc_alg = ?jwsc.header.alg, "validator algorithm mismatch"); - return Err(JwtError::ValidatorAlgMismatch); - } - - if jwsc.signature.len() != 64 { - return Err(JwtError::InvalidSignature); - } - - let r = bn::BigNum::from_slice(&jwsc.signature[..32]).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let s = bn::BigNum::from_slice(&jwsc.signature[32..64]).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let sig = ecdsa::EcdsaSig::from_private_components(r, s).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut hasher = hash::Hasher::new(self.digest).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - hasher - .update(jwsc.hdr_b64.as_bytes()) - .and_then(|_| hasher.update(".".as_bytes())) - .and_then(|_| hasher.update(jwsc.payload_b64.as_bytes())) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let hashout = hasher.finish().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - sig.verify(&hashout, &self.pkey).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} - -/// A JWS signer that creates RSA SHA256 signatures. -pub struct JwsRs256Signer { - /// If the public jwk should be embeded during signing - sign_option_embed_jwk: bool, - /// The KID of this validator - kid: String, - /// Private Key - skey: rsa::Rsa, - /// The matching digest. - digest: hash::MessageDigest, -} - -impl JwsRs256Signer { - /// Enable or disable embedding of the public jwk into the Jws that are signed - /// by this signer - pub fn set_sign_option_embed_jwk(&mut self, value: bool) { - self.sign_option_embed_jwk = value; - } - - /// Restore this JwsSignerEnum from a DER private key. - pub fn from_rs256_der(der: &[u8]) -> Result { - let digest = hash::MessageDigest::sha256(); - - let kid = hash::hash(digest, der) - .map(|hashout| hex::encode(hashout)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let skey = rsa::Rsa::private_key_from_der(der).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - Ok(JwsRs256Signer { - kid, - skey, - digest, - sign_option_embed_jwk: false, - }) - } - - /// Export this signer to a DER private key. - pub fn private_key_to_der(&self) -> Result, JwtError> { - self.skey.private_key_to_der().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } - - /// Create a new legacy (RSA) private key for signing - pub fn generate_legacy_rs256() -> Result { - let digest = hash::MessageDigest::sha256(); - - let skey = rsa::Rsa::generate(RSA_MIN_SIZE).map_err(|_| JwtError::OpenSSLError)?; - - skey.check_key().map_err(|_| JwtError::OpenSSLError)?; - - let kid = skey - .private_key_to_der() - .and_then(|der| hash::hash(digest, &der)) - .map(|hashout| hex::encode(hashout)) - .map_err(|_| JwtError::OpenSSLError)?; - - Ok(JwsRs256Signer { - kid, - skey, - digest, - sign_option_embed_jwk: false, - }) - } - - /// Get the public Jwk from this signer - pub fn public_key_as_jwk(&self) -> Result { - let public_key_n = self.skey.n().to_vec_padded(RSA_SIG_SIZE).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let public_key_e = self.skey.e().to_vec_padded(3).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - Ok(Jwk::RSA { - n: Base64UrlSafeData(public_key_n), - e: Base64UrlSafeData(public_key_e), - alg: Some(JwaAlg::RS256), - use_: Some(JwkUse::Sig), - kid: Some(self.kid.clone()), - }) - } -} - -impl JwsSignerToVerifier for JwsRs256Signer { - type Verifier = JwsRs256Verifier; - - fn get_verifier(&mut self) -> Result { - todo!(); - } -} - -impl JwsSigner for JwsRs256Signer { - fn get_kid(&mut self) -> &str { - self.kid.as_str() - } - - fn update_header(&mut self, header: &mut ProtectedHeader) -> Result<(), JwtError> { - // Update the alg to match. - header.alg = JwaAlg::RS256; - - header.kid = Some(self.kid.clone()); - - // if were were asked to ember the jwk, do so now. - if self.sign_option_embed_jwk { - header.jwk = self.public_key_as_jwk().map(Some)?; - } - - Ok(()) - } - - fn sign(&mut self, jwsc: JwsCompactSignData<'_>) -> Result, JwtError> { - let key = pkey::PKey::from_rsa(self.skey.clone()).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut signer = sign::Signer::new(self.digest, &key).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer - .update(jwsc.hdr_bytes) - .and_then(|_| signer.update(".".as_bytes())) - .and_then(|_| signer.update(jwsc.payload_bytes)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer.sign_to_vec().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} - -/// A JWS verifier that creates RSA SHA256 signatures. -pub struct JwsRs256Verifier { - /// The KID of this validator - kid: Option, - /// Public Key - pkey: rsa::Rsa, - /// The matching digest. - digest: hash::MessageDigest, -} - -/* -impl TryFrom for JwsRs256Verifier { - type Error = JwtError; - - fn try_from(value: x509::X509) -> Result { - let pkey = value.public_key().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let digest = hash::MessageDigest::sha256(); - pkey.rsa() - .map(|pkey| JwsRs256Verifier { - kid: None, - pkey, - digest, - }) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} -*/ - -impl TryFrom<&Jwk> for JwsRs256Verifier { - type Error = JwtError; - - fn try_from(value: &Jwk) -> Result { - match value { - Jwk::RSA { - n, - e, - alg: _, - use_: _, - kid, - } => { - let digest = hash::MessageDigest::sha256(); - - let nbn = bn::BigNum::from_slice(&n.0).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - let ebn = bn::BigNum::from_slice(&e.0).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let pkey = rsa::Rsa::from_public_components(nbn, ebn).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let kid = kid.clone(); - - Ok(JwsRs256Verifier { kid, pkey, digest }) - } - alg_request => { - debug!(?alg_request, "validator algorithm mismatch"); - Err(JwtError::ValidatorAlgMismatch) - } - } - } -} - -impl JwsVerifier for JwsRs256Verifier { - fn get_kid(&mut self) -> Option<&str> { - self.kid.as_deref() - } - - fn verify_signature(&mut self, jwsc: &JwsCompact) -> Result { - if jwsc.header.alg != JwaAlg::RS256 { - debug!(jwsc_alg = ?jwsc.header.alg, "validator algorithm mismatch"); - return Err(JwtError::ValidatorAlgMismatch); - } - - if jwsc.signature.len() < 256 { - debug!("invalid signature length"); - return Err(JwtError::InvalidSignature); - } - - let p = pkey::PKey::from_rsa(self.pkey.clone()).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let mut verifier = sign::Verifier::new(self.digest, &p).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - verifier.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - verifier - .update(jwsc.hdr_b64.as_bytes()) - .and_then(|_| verifier.update(".".as_bytes())) - .and_then(|_| verifier.update(jwsc.payload_b64.as_bytes())) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - verifier.verify(&jwsc.signature).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} - -/// A JWS signer that creates HMAC SHA256 signatures. -pub struct JwsHs256Signer { - /// The KID of this signer. This is the sha256 digest of the key. - kid: String, - /// Private Key - skey: pkey::PKey, - /// The matching digest - digest: hash::MessageDigest, -} - -impl JwsHs256Signer { - /// Create a new secure private key for signing - pub fn generate_hs256() -> Result { - let digest = hash::MessageDigest::sha256(); - - let mut buf = [0; 32]; - rand::rand_bytes(&mut buf).map_err(|e| { - error!("{:?}", e); - JwtError::OpenSSLError - })?; - - // Can it become a pkey? - let skey = pkey::PKey::hmac(&buf).map_err(|e| { - error!("{:?}", e); - JwtError::OpenSSLError - })?; - - let mut kid = [0; 32]; - rand::rand_bytes(&mut kid).map_err(|e| { - error!("{:?}", e); - JwtError::OpenSSLError - })?; - - let kid = hash::hash(digest, &kid) - .map(|hashout| hex::encode(hashout)) - .map_err(|_| JwtError::OpenSSLError)?; - - Ok(JwsHs256Signer { kid, skey, digest }) - } -} - -#[cfg(test)] -impl TryFrom<&[u8]> for JwsHs256Signer { - type Error = JwtError; - - fn try_from(buf: &[u8]) -> Result { - if buf.len() < 32 { - return Err(JwtError::OpenSSLError); - } - - let digest = hash::MessageDigest::sha256(); - - let kid = hash::hash(digest, buf) - .map(|hashout| hex::encode(hashout)) - .map_err(|_| JwtError::OpenSSLError)?; - - let skey = pkey::PKey::hmac(buf).map_err(|e| { - error!("{:?}", e); - JwtError::OpenSSLError - })?; - - Ok(JwsHs256Signer { kid, skey, digest }) - } -} - -impl JwsSigner for JwsHs256Signer { - fn get_kid(&mut self) -> &str { - self.kid.as_str() - } - - fn update_header(&mut self, header: &mut ProtectedHeader) -> Result<(), JwtError> { - // Update the alg to match. - header.alg = JwaAlg::HS256; - - header.kid = Some(self.kid.clone()); - - Ok(()) - } - - fn sign(&mut self, jwsc: JwsCompactSignData<'_>) -> Result, JwtError> { - let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer - .update(jwsc.hdr_bytes) - .and_then(|_| signer.update(".".as_bytes())) - .and_then(|_| signer.update(jwsc.payload_bytes)) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer.sign_to_vec().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} - -impl JwsVerifier for JwsHs256Signer { - fn get_kid(&mut self) -> Option<&str> { - Some(self.kid.as_str()) - } - - fn verify_signature(&mut self, jwsc: &JwsCompact) -> Result { - if jwsc.header.alg != JwaAlg::HS256 { - debug!(jwsc_alg = ?jwsc.header.alg, "validator algorithm mismatch"); - return Err(JwtError::ValidatorAlgMismatch); - } - - let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - signer - .update(jwsc.hdr_b64.as_bytes()) - .and_then(|_| signer.update(".".as_bytes())) - .and_then(|_| signer.update(jwsc.payload_b64.as_bytes())) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - let ver_sig = signer.sign_to_vec().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - Ok(jwsc.signature == ver_sig) - } -} - -/// A builder for a verifier that will be rooted in a trusted ca chain. -#[derive(Default)] -pub struct JwsX509VerifierBuilder { - kid: Option, - leaf: Option, - chain: Vec, - trust_roots: Vec, - #[cfg(test)] - disable_time_checks: bool, -} - -impl JwsX509VerifierBuilder { - /// Create a new X509 Verifier Builder - pub fn new() -> Self { - JwsX509VerifierBuilder::default() - } - - #[cfg(test)] - pub fn set_kid(mut self, kid: Option<&str>) -> Self { - self.kid = kid.map(|s| s.to_string()); - self - } - - /// Add the CA trust roots that you trust to anchor signature chains. - pub fn add_trust_root(mut self, root: x509::X509) -> Self { - self.trust_roots.push(root); - self - } - - #[cfg(test)] - pub(crate) fn yolo(mut self) -> Self { - self.disable_time_checks = true; - self - } - - /// Add the full chain of certificates to this verifier. The expected - /// Vec should start with the leaf certificate, and end with the root. - /// - /// By default, the x5c content of a Jws should have this in the correct - /// order. - pub fn add_fullchain(mut self, mut chain: Vec) -> Self { - // Normally the chains are leaf -> root. We need to reverse it - // so we can pop from the right. - chain.reverse(); - - // Now we can pop() which gives us the leaf - // If there is no leaf, we'll error in the build phase. - self.leaf = chain.pop(); - self.chain = chain; - self - } - - /// Build this X509 Verifier. - pub fn build(self) -> Result { - use openssl::stack; - use openssl::x509::store; - - let JwsX509VerifierBuilder { - kid, - leaf, - mut chain, - mut trust_roots, - #[cfg(test)] - disable_time_checks, - } = self; - - let leaf = leaf.ok_or_else(|| { - error!("No leaf certificate available in chain"); - JwtError::X5cChainMissingLeaf - })?; - - // Now verify the whole thing back to the trust roots. - - // Convert the chain to a stackref for openssl. - let mut chain_stack = stack::Stack::new().map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - - while let Some(crt) = chain.pop() { - chain_stack.push(crt).map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - } - - // Setup a CA store we plan to verify against. - let mut ca_store = store::X509StoreBuilder::new().map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - - while let Some(ca_crt) = trust_roots.pop() { - ca_store.add_cert(ca_crt).map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - } - - #[cfg(test)] - if disable_time_checks { - ca_store - .set_flags(x509::verify::X509VerifyFlags::NO_CHECK_TIME) - .map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - } - - let ca_store = ca_store.build(); - - let mut ca_ctx = x509::X509StoreContext::new().map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - - let out = ca_ctx - .init(&ca_store, &leaf, &chain_stack, |ca_ctx_ref| { - ca_ctx_ref.verify_cert().map(|_| { - let verify_cert_result = ca_ctx_ref.error(); - trace!(?verify_cert_result); - if verify_cert_result == x509::X509VerifyResult::OK { - Ok(()) - } else { - error!( - "ca_ctx_ref verify cert - error depth={}, sn={:?}", - ca_ctx_ref.error_depth(), - ca_ctx_ref.current_cert().map(|crt| crt.subject_name()) - ); - Err(JwtError::X5cChainNotTrusted) - } - }) - }) - .map_err(|ossl_err| { - error!(?ossl_err); - JwtError::OpenSSLError - })?; - - trace!(?out); - - out.map(|()| JwsX509Verifier { kid, pkey: leaf }) - } -} - -/// A verifier for a Jws that is trusted by a certificate chain. This verifier represents the leaf -/// certificate that will be used to verify a Jws. -/// -/// If you have multiple trust roots and chains, you will need to build this verifier for each -/// Jws that you need to validate since this type verifies a single leaf. -pub struct JwsX509Verifier { - /// The KID of this validator - kid: Option, - /// Public Key - pkey: x509::X509, -} - -impl JwsVerifier for JwsX509Verifier { - fn get_kid(&mut self) -> Option<&str> { - self.kid.as_deref() - } - - fn verify_signature(&mut self, jwsc: &JwsCompact) -> Result { - let pkey = self.pkey.public_key().map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - // Okay, the cert is valid, lets do this. - let digest = match (jwsc.header.alg, pkey.id()) { - (JwaAlg::RS256, pkey::Id::RSA) | (JwaAlg::ES256, pkey::Id::EC) => { - Ok(hash::MessageDigest::sha256()) - } - _ => { - debug!(jwsc_alg = ?jwsc.header.alg, "validator algorithm mismatch"); - return Err(JwtError::ValidatorAlgMismatch); - } - }?; - - let mut verifier = sign::Verifier::new(digest, &pkey).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - if jwsc.header.alg == JwaAlg::RS256 { - verifier.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - } - - verifier - .update(jwsc.hdr_b64.as_bytes()) - .and_then(|_| verifier.update(".".as_bytes())) - .and_then(|_| verifier.update(jwsc.payload_b64.as_bytes())) - .map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - })?; - - verifier.verify(&jwsc.signature).map_err(|e| { - debug!(?e); - JwtError::OpenSSLError - }) - } -} - -#[cfg(all(feature = "openssl", test))] -mod tests { - use super::{ - JwsEs256Signer, JwsEs256Verifier, JwsHs256Signer, JwsRs256Signer, JwsRs256Verifier, - JwsSignerToVerifier, - }; - use crate::compact::{Jwk, JwsCompact}; - use crate::jws::JwsBuilder; - use base64::{engine::general_purpose, Engine as _}; - use std::convert::TryFrom; - use std::str::FromStr; - - #[test] - fn rfc7515_es256_validation_example() { - let _ = tracing_subscriber::fmt::try_init(); - let test_jws = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"; - - let jwsc = JwsCompact::from_str(test_jws).unwrap(); - - assert!(jwsc.to_string() == test_jws); - - assert!(jwsc.check_vectors( - &[ - 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 70, 85, 122, 73, 49, 78, 105, 74, - 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, - 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, - 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, - 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, - 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, - 81 - ], - &[ - 14, 209, 33, 83, 121, 99, 108, 72, 60, 47, 127, 21, 88, 7, 212, 2, 163, 178, 40, 3, - 58, 249, 124, 126, 23, 129, 154, 195, 22, 158, 166, 101, 197, 10, 7, 211, 140, 60, - 112, 229, 216, 241, 45, 175, 8, 74, 84, 128, 166, 101, 144, 197, 242, 147, 80, 154, - 143, 63, 127, 138, 131, 163, 84, 213 - ] - )); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - assert!(jwsc.get_jwk_pubkey().is_none()); - - let pkey = r#"{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}"#; - - let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); - trace!("jwk -> {:?}", pkey); - - let mut jwk_es256_verifier = - JwsEs256Verifier::try_from(&pkey).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jwk_es256_verifier) - .expect("Unable to verify jws"); - trace!("rel -> {:?}", released); - } - - #[test] - fn rfc7515_es256_signature_example() { - let _ = tracing_subscriber::fmt::try_init(); - // https://docs.rs/openssl/0.10.36/openssl/ec/struct.EcKey.html#method.from_private_components - let mut jws_es256_signer = JwsEs256Signer::from_es256_jwk_components( - "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", - "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", - "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", - ) - .expect("failed to construct signer"); - - let jws = JwsBuilder::from(vec![ - 123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, - 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, - 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, - 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125, - ]) - .build(); - - let jwsc = jws.sign(&mut jws_es256_signer).expect("Failed to sign"); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - assert!(jwsc.get_jwk_pubkey().is_none()); - - let pkey = r#"{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}"#; - - let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); - trace!("jwk -> {:?}", pkey); - - let mut jwk_es256_verifier = - JwsEs256Verifier::try_from(&pkey).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jwk_es256_verifier) - .expect("Unable to verify jws"); - - trace!("rel -> {:?}", released); - - // Test the verifier from the signer also works. - let mut jwk_es256_verifier = jws_es256_signer - .get_verifier() - .expect("failed to get verifier from signer"); - - let released = jwsc - .verify(&mut jwk_es256_verifier) - .expect("Unable to verify jws"); - - trace!("rel -> {:?}", released); - } - - #[test] - fn es256_key_generate_cycle() { - let _ = tracing_subscriber::fmt::try_init(); - let jws_es256_signer = - JwsEs256Signer::generate_es256().expect("failed to construct signer."); - - let der = jws_es256_signer - .private_key_to_der() - .expect("Failed to extract DER"); - - let mut jws_es256_signer = - JwsEs256Signer::from_es256_der(&der).expect("Failed to restore signer"); - - // This time we'll add the jwk pubkey and show it being used with the validator. - let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) - .set_kid(Some("abcd")) - .set_typ(Some("abcd")) - .set_cty(Some("abcd")) - .build(); - - jws_es256_signer.set_sign_option_embed_jwk(true); - - let jwsc = jws.sign(&mut jws_es256_signer).expect("Failed to sign"); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - let pub_jwk = jwsc.get_jwk_pubkey().expect("No embeded public jwk!"); - assert!(*pub_jwk == jws_es256_signer.public_key_as_jwk().unwrap()); - - let mut jwk_es256_verifier = - JwsEs256Verifier::try_from(pub_jwk).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jwk_es256_verifier) - .expect("Unable to validate jws"); - assert!(released.payload() == &[0, 1, 2, 3, 4]); - } - - // RSA3072 - // https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2 - #[test] - fn rfc7515_rs256_validation_example() { - let _ = tracing_subscriber::fmt::try_init(); - let test_jws = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"; - - let jwsc = JwsCompact::from_str(test_jws).unwrap(); - - assert!(jwsc.to_string() == test_jws); - - assert!(jwsc.check_vectors( - &[ - 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 122, 73, 49, 78, 105, 74, - 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, - 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, - 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, - 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, - 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, - 81 - ], - &[ - 112, 46, 33, 137, 67, 232, 143, 209, 30, 181, 216, 45, 191, 120, 69, 243, 65, 6, - 174, 27, 129, 255, 247, 115, 17, 22, 173, 209, 113, 125, 131, 101, 109, 66, 10, - 253, 60, 150, 238, 221, 115, 162, 102, 62, 81, 102, 104, 123, 0, 11, 135, 34, 110, - 1, 135, 237, 16, 115, 249, 69, 229, 130, 173, 252, 239, 22, 216, 90, 121, 142, 232, - 198, 109, 219, 61, 184, 151, 91, 23, 208, 148, 2, 190, 237, 213, 217, 217, 112, 7, - 16, 141, 178, 129, 96, 213, 248, 4, 12, 167, 68, 87, 98, 184, 31, 190, 127, 249, - 217, 46, 10, 231, 111, 36, 242, 91, 51, 187, 230, 244, 74, 230, 30, 177, 4, 10, - 203, 32, 4, 77, 62, 249, 18, 142, 212, 1, 48, 121, 91, 212, 189, 59, 65, 238, 202, - 208, 102, 171, 101, 25, 129, 253, 228, 141, 247, 127, 55, 45, 195, 139, 159, 175, - 221, 59, 239, 177, 139, 93, 163, 204, 60, 46, 176, 47, 158, 58, 65, 214, 18, 202, - 173, 21, 145, 18, 115, 160, 95, 35, 185, 232, 56, 250, 175, 132, 157, 105, 132, 41, - 239, 90, 30, 136, 121, 130, 54, 195, 212, 14, 96, 69, 34, 165, 68, 200, 242, 122, - 122, 45, 184, 6, 99, 209, 108, 247, 202, 234, 86, 222, 64, 92, 178, 33, 90, 69, - 178, 194, 85, 102, 181, 90, 193, 167, 72, 160, 112, 223, 200, 163, 42, 70, 149, 67, - 208, 25, 238, 251, 71 - ] - )); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - assert!(jwsc.get_jwk_pubkey().is_none()); - - let pkey = r#"{ - "kty":"RSA", - "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", - "e":"AQAB" - }"#; - - let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); - trace!("jwk -> {:?}", pkey); - - let mut jws_validator = - JwsRs256Verifier::try_from(&pkey).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jws_validator) - .expect("Unable to validate jws"); - trace!("rel -> {:?}", released); - } - - #[test] - fn rs256_key_generate_cycle() { - let _ = tracing_subscriber::fmt::try_init(); - let jws_rs256_signer = - JwsRs256Signer::generate_legacy_rs256().expect("failed to construct signer."); - - let der = jws_rs256_signer - .private_key_to_der() - .expect("Failed to extract DER"); - - let mut jws_rs256_signer = - JwsRs256Signer::from_rs256_der(&der).expect("Failed to restore signer"); - - // This time we'll add the jwk pubkey and show it being used with the validator. - let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) - .set_typ(Some("abcd")) - .set_cty(Some("abcd")) - .build(); - - jws_rs256_signer.set_sign_option_embed_jwk(true); - - let jwsc = jws.sign(&mut jws_rs256_signer).expect("Failed to sign"); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - - let pub_jwk = jwsc.get_jwk_pubkey().expect("No embeded public jwk!"); - assert!(*pub_jwk == jws_rs256_signer.public_key_as_jwk().unwrap()); - - let mut jws_validator = - JwsRs256Verifier::try_from(pub_jwk).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jws_validator) - .expect("Unable to validate jws"); - assert!(released.payload() == &[0, 1, 2, 3, 4]); - } - - #[test] - fn rfc7519_hs256_validation_example() { - let _ = tracing_subscriber::fmt::try_init(); - let test_jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - let jwsc = JwsCompact::from_str(test_jws).unwrap(); - - assert!(jwsc.check_vectors( - &[ - 101, 121, 74, 48, 101, 88, 65, 105, 79, 105, 74, 75, 86, 49, 81, 105, 76, 65, 48, - 75, 73, 67, 74, 104, 98, 71, 99, 105, 79, 105, 74, 73, 85, 122, 73, 49, 78, 105, - 74, 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, - 76, 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, - 52, 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, - 65, 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, - 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, - 102, 81 - ], - &[ - 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, - 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 - ] - )); - - assert!(jwsc.get_jwk_pubkey_url().is_none()); - assert!(jwsc.get_jwk_pubkey().is_none()); - - let skey = general_purpose::URL_SAFE_NO_PAD.decode( - "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" - ).expect("Invalid key"); - - let mut jws_signer = - JwsHs256Signer::try_from(skey.as_slice()).expect("Unable to create validator"); - - let released = jwsc - .verify(&mut jws_signer) - .expect("Unable to validate jws"); - trace!("rel -> {:?}", released); - } -} diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs new file mode 100644 index 0000000..09b0b8d --- /dev/null +++ b/src/crypto/rs256.rs @@ -0,0 +1,435 @@ +//! JWS Signing and Verification Structures + +use openssl::{bn, hash, pkey, rsa, sign}; +use std::convert::TryFrom; + +use crate::error::JwtError; +use base64::{engine::general_purpose, Engine as _}; +use base64urlsafedata::Base64UrlSafeData; + +use crate::compact::{JwaAlg, Jwk, JwkUse, JwsCompact, ProtectedHeader}; +use crate::traits::*; + +const RSA_MIN_SIZE: u32 = 3072; +const RSA_SIG_SIZE: i32 = 384; + +/// A JWS signer that creates RSA SHA256 signatures. +pub struct JwsRs256Signer { + /// If the public jwk should be embeded during signing + sign_option_embed_jwk: bool, + /// The KID of this validator + kid: String, + /// Private Key + skey: rsa::Rsa, + /// The matching digest. + digest: hash::MessageDigest, +} + +impl JwsRs256Signer { + /// Enable or disable embedding of the public jwk into the Jws that are signed + /// by this signer + pub fn set_sign_option_embed_jwk(&mut self, value: bool) { + self.sign_option_embed_jwk = value; + } + + /// Restore this JwsSignerEnum from a DER private key. + pub fn from_rs256_der(der: &[u8]) -> Result { + let digest = hash::MessageDigest::sha256(); + + let kid = hash::hash(digest, der).map(hex::encode).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let skey = rsa::Rsa::private_key_from_der(der).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + Ok(JwsRs256Signer { + kid, + skey, + digest, + sign_option_embed_jwk: false, + }) + } + + /// Export this signer to a DER private key. + pub fn private_key_to_der(&self) -> Result, JwtError> { + self.skey.private_key_to_der().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + }) + } + + /// Create a new legacy (RSA) private key for signing + pub fn generate_legacy_rs256() -> Result { + let digest = hash::MessageDigest::sha256(); + + let skey = rsa::Rsa::generate(RSA_MIN_SIZE).map_err(|_| JwtError::OpenSSLError)?; + + skey.check_key().map_err(|_| JwtError::OpenSSLError)?; + + let kid = skey + .private_key_to_der() + .and_then(|der| hash::hash(digest, &der)) + .map(hex::encode) + .map_err(|_| JwtError::OpenSSLError)?; + + Ok(JwsRs256Signer { + kid, + skey, + digest, + sign_option_embed_jwk: false, + }) + } + + /// Get the public Jwk from this signer + pub fn public_key_as_jwk(&self) -> Result { + let public_key_n = self.skey.n().to_vec_padded(RSA_SIG_SIZE).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let public_key_e = self.skey.e().to_vec_padded(3).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + Ok(Jwk::RSA { + n: Base64UrlSafeData(public_key_n), + e: Base64UrlSafeData(public_key_e), + alg: Some(JwaAlg::RS256), + use_: Some(JwkUse::Sig), + kid: Some(self.kid.clone()), + }) + } +} + +impl JwsSignerToVerifier for JwsRs256Signer { + type Verifier = JwsRs256Verifier; + + fn get_verifier(&self) -> Result { + self.skey + .n() + .to_owned() + .and_then(|n| self.skey.e().to_owned().map(|e| (n, e))) + .and_then(|(n, e)| rsa::Rsa::from_public_components(n, e)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + }) + .map_err(|_| JwtError::OpenSSLError) + .map(|pkey| JwsRs256Verifier { + kid: Some(self.kid.clone()), + pkey, + digest: self.digest, + }) + } +} + +impl JwsSigner for JwsRs256Signer { + fn get_kid(&self) -> &str { + self.kid.as_str() + } + + fn update_header(&self, header: &mut ProtectedHeader) -> Result<(), JwtError> { + // Update the alg to match. + header.alg = JwaAlg::RS256; + + header.kid = Some(self.kid.clone()); + + // if were were asked to ember the jwk, do so now. + if self.sign_option_embed_jwk { + header.jwk = self.public_key_as_jwk().map(Some)?; + } + + Ok(()) + } + + fn sign(&self, jws: &V) -> Result { + let mut sign_data = jws.data()?; + + // Let the signer update the header as required. + self.update_header(&mut sign_data.header)?; + + let hdr_b64 = serde_json::to_vec(&sign_data.header) + .map_err(|e| { + debug!(?e); + JwtError::InvalidHeaderFormat + }) + .map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?; + + let key = pkey::PKey::from_rsa(self.skey.clone()).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut signer = sign::Signer::new(self.digest, &key).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + signer.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + signer + .update(hdr_b64.as_bytes()) + .and_then(|_| signer.update(".".as_bytes())) + .and_then(|_| signer.update(sign_data.payload_b64.as_bytes())) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let signature = signer.sign_to_vec().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let jwsc = JwsCompact { + header: sign_data.header, + hdr_b64, + payload_b64: sign_data.payload_b64, + signature, + }; + + jws.post_process(jwsc) + } +} + +/// A JWS verifier that creates RSA SHA256 signatures. +pub struct JwsRs256Verifier { + /// The KID of this validator + kid: Option, + /// Public Key + pkey: rsa::Rsa, + /// The matching digest. + digest: hash::MessageDigest, +} + +/* +impl TryFrom for JwsRs256Verifier { + type Error = JwtError; + + fn try_from(value: x509::X509) -> Result { + let pkey = value.public_key().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let digest = hash::MessageDigest::sha256(); + pkey.rsa() + .map(|pkey| JwsRs256Verifier { + kid: None, + pkey, + digest, + }) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + }) + } +} +*/ + +impl TryFrom<&Jwk> for JwsRs256Verifier { + type Error = JwtError; + + fn try_from(value: &Jwk) -> Result { + match value { + Jwk::RSA { + n, + e, + alg: _, + use_: _, + kid, + } => { + let digest = hash::MessageDigest::sha256(); + + let nbn = bn::BigNum::from_slice(&n.0).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + let ebn = bn::BigNum::from_slice(&e.0).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let pkey = rsa::Rsa::from_public_components(nbn, ebn).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let kid = kid.clone(); + + Ok(JwsRs256Verifier { kid, pkey, digest }) + } + alg_request => { + debug!(?alg_request, "validator algorithm mismatch"); + Err(JwtError::ValidatorAlgMismatch) + } + } + } +} + +impl JwsVerifier for JwsRs256Verifier { + fn get_kid(&self) -> Option<&str> { + self.kid.as_deref() + } + + fn verify(&self, jwsc: &V) -> Result { + let signed_data = jwsc.data(); + + if signed_data.header.alg != JwaAlg::RS256 { + debug!(jwsc_alg = ?signed_data.header.alg, "validator algorithm mismatch"); + return Err(JwtError::ValidatorAlgMismatch); + } + + if signed_data.signature_bytes.len() < 256 { + debug!("invalid signature length"); + return Err(JwtError::InvalidSignature); + } + + let p = pkey::PKey::from_rsa(self.pkey.clone()).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let mut verifier = sign::Verifier::new(self.digest, &p).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + verifier.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + verifier + .update(signed_data.hdr_bytes) + .and_then(|_| verifier.update(".".as_bytes())) + .and_then(|_| verifier.update(signed_data.payload_bytes)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let valid = verifier.verify(signed_data.signature_bytes).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + if valid { + signed_data.release().and_then(|d| jwsc.post_process(d)) + } else { + debug!("invalid signature"); + Err(JwtError::InvalidSignature) + } + } +} + +#[cfg(test)] +mod tests { + use super::{JwsRs256Signer, JwsRs256Verifier}; + use crate::compact::{Jwk, JwsCompact}; + use crate::jws::JwsBuilder; + use crate::traits::*; + use std::convert::TryFrom; + use std::str::FromStr; + + // RSA3072 + // https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2 + #[test] + fn rfc7515_rs256_validation_example() { + let _ = tracing_subscriber::fmt::try_init(); + let test_jws = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"; + + let jwsc = JwsCompact::from_str(test_jws).unwrap(); + + assert!(jwsc.to_string() == test_jws); + + assert!(jwsc.check_vectors( + &[ + 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 122, 73, 49, 78, 105, 74, + 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, + 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, + 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, + 54, 76, 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, + 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, + 81 + ], + &[ + 112, 46, 33, 137, 67, 232, 143, 209, 30, 181, 216, 45, 191, 120, 69, 243, 65, 6, + 174, 27, 129, 255, 247, 115, 17, 22, 173, 209, 113, 125, 131, 101, 109, 66, 10, + 253, 60, 150, 238, 221, 115, 162, 102, 62, 81, 102, 104, 123, 0, 11, 135, 34, 110, + 1, 135, 237, 16, 115, 249, 69, 229, 130, 173, 252, 239, 22, 216, 90, 121, 142, 232, + 198, 109, 219, 61, 184, 151, 91, 23, 208, 148, 2, 190, 237, 213, 217, 217, 112, 7, + 16, 141, 178, 129, 96, 213, 248, 4, 12, 167, 68, 87, 98, 184, 31, 190, 127, 249, + 217, 46, 10, 231, 111, 36, 242, 91, 51, 187, 230, 244, 74, 230, 30, 177, 4, 10, + 203, 32, 4, 77, 62, 249, 18, 142, 212, 1, 48, 121, 91, 212, 189, 59, 65, 238, 202, + 208, 102, 171, 101, 25, 129, 253, 228, 141, 247, 127, 55, 45, 195, 139, 159, 175, + 221, 59, 239, 177, 139, 93, 163, 204, 60, 46, 176, 47, 158, 58, 65, 214, 18, 202, + 173, 21, 145, 18, 115, 160, 95, 35, 185, 232, 56, 250, 175, 132, 157, 105, 132, 41, + 239, 90, 30, 136, 121, 130, 54, 195, 212, 14, 96, 69, 34, 165, 68, 200, 242, 122, + 122, 45, 184, 6, 99, 209, 108, 247, 202, 234, 86, 222, 64, 92, 178, 33, 90, 69, + 178, 194, 85, 102, 181, 90, 193, 167, 72, 160, 112, 223, 200, 163, 42, 70, 149, 67, + 208, 25, 238, 251, 71 + ] + )); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + assert!(jwsc.get_jwk_pubkey().is_none()); + + let pkey = r#"{ + "kty":"RSA", + "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", + "e":"AQAB" + }"#; + + let pkey: Jwk = serde_json::from_str(pkey).expect("Invalid JWK"); + trace!("jwk -> {:?}", pkey); + + let jws_validator = JwsRs256Verifier::try_from(&pkey).expect("Unable to create validator"); + + let released = jws_validator.verify(&jwsc).expect("Unable to validate jws"); + trace!("rel -> {:?}", released); + } + + #[test] + fn rs256_key_generate_cycle() { + let _ = tracing_subscriber::fmt::try_init(); + let jws_rs256_signer = + JwsRs256Signer::generate_legacy_rs256().expect("failed to construct signer."); + + let der = jws_rs256_signer + .private_key_to_der() + .expect("Failed to extract DER"); + + let mut jws_rs256_signer = + JwsRs256Signer::from_rs256_der(&der).expect("Failed to restore signer"); + + // This time we'll add the jwk pubkey and show it being used with the validator. + let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) + .set_typ(Some("abcd")) + .set_cty(Some("abcd")) + .build(); + + jws_rs256_signer.set_sign_option_embed_jwk(true); + + let jwsc = jws_rs256_signer.sign(&jws).expect("Failed to sign"); + + assert!(jwsc.get_jwk_pubkey_url().is_none()); + + let pub_jwk = jwsc.get_jwk_pubkey().expect("No embeded public jwk!"); + assert!(*pub_jwk == jws_rs256_signer.public_key_as_jwk().unwrap()); + + let jws_validator = jws_rs256_signer + .get_verifier() + .expect("Unable to create validator"); + + let released = jws_validator.verify(&jwsc).expect("Unable to validate jws"); + assert!(released.payload() == &[0, 1, 2, 3, 4]); + } +} diff --git a/src/crypto/x509.rs b/src/crypto/x509.rs new file mode 100644 index 0000000..34eee15 --- /dev/null +++ b/src/crypto/x509.rs @@ -0,0 +1,221 @@ +//! JWS Signing and Verification Structures + +use crate::compact::JwaAlg; +use crate::error::JwtError; +use crate::traits::*; +use openssl::{hash, pkey, rsa, sign, x509}; + +/// A builder for a verifier that will be rooted in a trusted ca chain. +#[derive(Default)] +pub struct JwsX509VerifierBuilder { + kid: Option, + leaf: Option, + chain: Vec, + trust_roots: Vec, + #[cfg(test)] + disable_time_checks: bool, +} + +impl JwsX509VerifierBuilder { + /// Create a new X509 Verifier Builder + pub fn new() -> Self { + JwsX509VerifierBuilder::default() + } + + #[cfg(test)] + pub fn set_kid(mut self, kid: Option<&str>) -> Self { + self.kid = kid.map(|s| s.to_string()); + self + } + + /// Add the CA trust roots that you trust to anchor signature chains. + pub fn add_trust_root(mut self, root: x509::X509) -> Self { + self.trust_roots.push(root); + self + } + + #[cfg(test)] + pub(crate) fn yolo(mut self) -> Self { + self.disable_time_checks = true; + self + } + + /// Add the full chain of certificates to this verifier. The expected + /// Vec should start with the leaf certificate, and end with the root. + /// + /// By default, the x5c content of a Jws should have this in the correct + /// order. + pub fn add_fullchain(mut self, mut chain: Vec) -> Self { + // Normally the chains are leaf -> root. We need to reverse it + // so we can pop from the right. + chain.reverse(); + + // Now we can pop() which gives us the leaf + // If there is no leaf, we'll error in the build phase. + self.leaf = chain.pop(); + self.chain = chain; + self + } + + /// Build this X509 Verifier. + pub fn build(self) -> Result { + use openssl::stack; + use openssl::x509::store; + + let JwsX509VerifierBuilder { + kid, + leaf, + mut chain, + mut trust_roots, + #[cfg(test)] + disable_time_checks, + } = self; + + let leaf = leaf.ok_or_else(|| { + error!("No leaf certificate available in chain"); + JwtError::X5cChainMissingLeaf + })?; + + // Now verify the whole thing back to the trust roots. + + // Convert the chain to a stackref for openssl. + let mut chain_stack = stack::Stack::new().map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + + while let Some(crt) = chain.pop() { + chain_stack.push(crt).map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + } + + // Setup a CA store we plan to verify against. + let mut ca_store = store::X509StoreBuilder::new().map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + + while let Some(ca_crt) = trust_roots.pop() { + ca_store.add_cert(ca_crt).map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + } + + #[cfg(test)] + if disable_time_checks { + ca_store + .set_flags(x509::verify::X509VerifyFlags::NO_CHECK_TIME) + .map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + } + + let ca_store = ca_store.build(); + + let mut ca_ctx = x509::X509StoreContext::new().map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + + let out = ca_ctx + .init(&ca_store, &leaf, &chain_stack, |ca_ctx_ref| { + ca_ctx_ref.verify_cert().map(|_| { + let verify_cert_result = ca_ctx_ref.error(); + trace!(?verify_cert_result); + if verify_cert_result == x509::X509VerifyResult::OK { + Ok(()) + } else { + error!( + "ca_ctx_ref verify cert - error depth={}, sn={:?}", + ca_ctx_ref.error_depth(), + ca_ctx_ref.current_cert().map(|crt| crt.subject_name()) + ); + Err(JwtError::X5cChainNotTrusted) + } + }) + }) + .map_err(|ossl_err| { + error!(?ossl_err); + JwtError::OpenSSLError + })?; + + trace!(?out); + + out.map(|()| JwsX509Verifier { kid, pkey: leaf }) + } +} + +/// A verifier for a Jws that is trusted by a certificate chain. This verifier represents the leaf +/// certificate that will be used to verify a Jws. +/// +/// If you have multiple trust roots and chains, you will need to build this verifier for each +/// Jws that you need to validate since this type verifies a single leaf. +pub struct JwsX509Verifier { + /// The KID of this validator + kid: Option, + /// Public Key + pkey: x509::X509, +} + +impl JwsVerifier for JwsX509Verifier { + fn get_kid(&self) -> Option<&str> { + self.kid.as_deref() + } + + fn verify(&self, jwsc: &V) -> Result { + let signed_data = jwsc.data(); + + let pkey = self.pkey.public_key().map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + // Okay, the cert is valid, lets do this. + let digest = match (signed_data.header.alg, pkey.id()) { + (JwaAlg::RS256, pkey::Id::RSA) | (JwaAlg::ES256, pkey::Id::EC) => { + Ok(hash::MessageDigest::sha256()) + } + _ => { + debug!(jwsc_alg = ?signed_data.header.alg, "validator algorithm mismatch"); + return Err(JwtError::ValidatorAlgMismatch); + } + }?; + + let mut verifier = sign::Verifier::new(digest, &pkey).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + if signed_data.header.alg == JwaAlg::RS256 { + verifier.set_rsa_padding(rsa::Padding::PKCS1).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + } + + verifier + .update(signed_data.hdr_bytes) + .and_then(|_| verifier.update(".".as_bytes())) + .and_then(|_| verifier.update(signed_data.payload_bytes)) + .map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + let valid = verifier.verify(signed_data.signature_bytes).map_err(|e| { + debug!(?e); + JwtError::OpenSSLError + })?; + + if valid { + signed_data.release().and_then(|d| jwsc.post_process(d)) + } else { + debug!("invalid signature"); + Err(JwtError::InvalidSignature) + } + } +} diff --git a/src/dangernoverify.rs b/src/dangernoverify.rs index 5c133c2..74e991d 100644 --- a/src/dangernoverify.rs +++ b/src/dangernoverify.rs @@ -1,9 +1,8 @@ //! A dangerous verification type that allows bypassing cryptographic //! checking of the content of JWS tokens. -use crate::compact::JwsCompact; use crate::error::JwtError; -use crate::traits::JwsVerifier; +use crate::traits::{JwsVerifiable, JwsVerifier}; /// A dangerous verification type that allows bypassing cryptographic /// checking of the content of JWS tokens. @@ -11,21 +10,23 @@ use crate::traits::JwsVerifier; pub struct JwsDangerReleaseWithoutVerify {} impl JwsVerifier for JwsDangerReleaseWithoutVerify { - fn get_kid(&mut self) -> Option<&str> { + fn get_kid(&self) -> Option<&str> { None } - fn verify_signature(&mut self, _jwsc: &JwsCompact) -> Result { - warn!("releasing without signature check."); - Ok(true) + fn verify(&self, jwsc: &V) -> Result { + let signed_data = jwsc.data(); + + signed_data.release().and_then(|d| jwsc.post_process(d)) } } #[cfg(test)] mod tests { use super::JwsDangerReleaseWithoutVerify; - use crate::compact::JwaAlg; - use crate::jws::{JwsBuilder, JwsUnverified}; + use crate::compact::{JwaAlg, JwsCompact}; + use crate::jws::JwsBuilder; + use crate::traits::*; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -49,13 +50,13 @@ mod tests { .set_alg(JwaAlg::ES256) .build(); - let jwtu = JwsUnverified::from_str("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJteV9leHRlbiI6IkhlbGxvIn0.VNG9R9oitdzadh327cDo4Jcww7l_IGGVrsnRrKfdW-VzqNVjbrjLhyhZ6QmYT7uBBwcVxPuBKv5idyBapo_AlA") + let jwtu = JwsCompact::from_str("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJteV9leHRlbiI6IkhlbGxvIn0.VNG9R9oitdzadh327cDo4Jcww7l_IGGVrsnRrKfdW-VzqNVjbrjLhyhZ6QmYT7uBBwcVxPuBKv5idyBapo_AlA") .expect("Invalid jwtu"); - let mut jws_danger_no_verification = JwsDangerReleaseWithoutVerify::default(); + let jws_danger_no_verification = JwsDangerReleaseWithoutVerify::default(); - let released = jwtu - .verify(&mut jws_danger_no_verification) + let released = jws_danger_no_verification + .verify(&jwtu) .expect("Unable to validate jwt"); trace!(?released); diff --git a/src/jws.rs b/src/jws.rs index e94726e..02b7342 100644 --- a/src/jws.rs +++ b/src/jws.rs @@ -1,17 +1,11 @@ //! JWS Implementation -use crate::compact::{Jwk, JwsCompact, ProtectedHeader}; +use crate::compact::{JwsCompact, ProtectedHeader}; use crate::error::JwtError; -use crate::traits::JwsVerifier; +use crate::traits::JwsSignable; +use base64::{engine::general_purpose, Engine as _}; use serde::{Deserialize, Serialize}; use std::fmt; -use std::str::FromStr; - -/// An unverified jws input which is ready to validate -#[derive(Debug)] -pub struct JwsUnverified { - pub(crate) jwsc: JwsCompact, -} /// A signed jwt which can be converted to a string. pub struct JwsSigned { @@ -97,45 +91,35 @@ impl Jws { } } -#[cfg(feature = "openssl")] -impl JwsUnverified { - /// Get the embedded certificate chain (if any) in DER forms - pub fn get_x5c_chain(&self) -> Result>, JwtError> { - self.jwsc.get_x5c_chain() - } -} +impl JwsSignable for Jws { + type Signed = JwsCompact; -impl JwsUnverified { - /// Using this JwsVerifier, assert the correct signature of the data contained in - /// this token. - pub fn verify(&self, verifier: &mut K) -> Result { - self.jwsc.verify(verifier) - } - - /// Get the embedded public key used to sign this jwt, if present. - pub fn get_jwk_pubkey(&self) -> Option<&Jwk> { - self.jwsc.get_jwk_pubkey() + fn data(&self) -> Result { + let payload_b64 = general_purpose::URL_SAFE_NO_PAD.encode(&self.payload); + Ok(JwsCompactSign2Data { + header: self.header.clone(), + payload_b64, + }) } - /// Get the KID used to sign this Jws if present - pub fn get_jwk_kid(&self) -> Option<&str> { - self.jwsc.get_jwk_kid() + fn post_process(&self, value: JwsCompact) -> Result { + Ok(value) } } -impl FromStr for JwsUnverified { - type Err = JwtError; - - fn from_str(s: &str) -> Result { - JwsCompact::from_str(s).map(|jwsc| JwsUnverified { jwsc }) - } +/// Data that will be signed +pub struct JwsCompactSign2Data { + #[allow(dead_code)] + pub(crate) header: ProtectedHeader, + #[allow(dead_code)] + pub(crate) payload_b64: String, } impl JwsSigned { /// Invalidate this signed jwt, causing it to require validation before you can use it /// again. - pub fn invalidate(self) -> JwsUnverified { - JwsUnverified { jwsc: self.jwsc } + pub fn invalidate(self) -> JwsCompact { + self.jwsc } } @@ -150,7 +134,7 @@ mod tests { use super::JwsBuilder; use crate::compact::JwaAlg; use crate::crypto::{JwsEs256Signer, JwsEs256Verifier, JwsHs256Signer, JwsX509VerifierBuilder}; - use crate::traits::{JwsSigner, JwsSignerToVerifier}; + use crate::traits::*; use openssl::x509; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; @@ -163,9 +147,9 @@ mod tests { #[test] fn test_sign_and_validate_es256() { let _ = tracing_subscriber::fmt::try_init(); - let mut jws_es256_signer = + let jws_es256_signer = JwsEs256Signer::generate_es256().expect("failed to construct signer."); - let mut jwk_es256_verifier = jws_es256_signer + let jwk_es256_verifier = jws_es256_signer .get_verifier() .expect("failed to get verifier from signer"); @@ -181,13 +165,13 @@ mod tests { .set_alg(JwaAlg::ES256) .build(); - let jwts = jwt.sign(&mut jws_es256_signer).expect("failed to sign jwt"); + let jwts = jws_es256_signer.sign(&jwt).expect("failed to sign jwt"); let jwt_str = jwts.to_string(); trace!("{}", jwt_str); - let released = jwts - .verify(&mut jwk_es256_verifier) + let released = jwk_es256_verifier + .verify(&jwts) .expect("Unable to validate jwt"); trace!(?released); @@ -199,7 +183,7 @@ mod tests { #[test] fn test_sign_and_validate_hs256() { let _ = tracing_subscriber::fmt::try_init(); - let mut jws_hs256_verifier = + let jws_hs256_verifier = JwsHs256Signer::generate_hs256().expect("failed to construct signer."); let inner = CustomExtension { @@ -210,16 +194,14 @@ mod tests { let jwt = JwsBuilder::from(payload) .set_typ(Some("JWT")) - .set_kid(Some(jws_hs256_verifier.get_kid())) + .set_kid(Some(JwsSigner::get_kid(&jws_hs256_verifier))) .set_alg(JwaAlg::HS256) .build(); - let jwts = jwt - .sign(&mut jws_hs256_verifier) - .expect("failed to sign jwt"); + let jwts = jws_hs256_verifier.sign(&jwt).expect("failed to sign jwt"); - let released = jwts - .verify(&mut jws_hs256_verifier) + let released = jws_hs256_verifier + .verify(&jwts) .expect("Unable to validate jwt"); trace!(?released); @@ -256,7 +238,7 @@ HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- "#; - let jwsu = super::JwsUnverified::from_str("eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGYmpDQ0JGYWdBd0lCQWdJUUFhM09LT2RvVFk0UUFBQUFBQTNYWERBTkJna3Foa2lHOXcwQkFRc0ZBREJHTVFzd0NRWURWUVFHRXdKVlV6RWlNQ0FHQTFVRUNoTVpSMjl2WjJ4bElGUnlkWE4wSUZObGNuWnBZMlZ6SUV4TVF6RVRNQkVHQTFVRUF4TUtSMVJUSUVOQklERkVOREFlRncweU1qQXpNakF5TVRFMU1qRmFGdzB5TWpBMk1UZ3lNVEUxTWpCYU1CMHhHekFaQmdOVkJBTVRFbUYwZEdWemRDNWhibVJ5YjJsa0xtTnZiVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMbXFMQlhWNENaQTVzVDVjVGZ1WGN3MTFXRDJZVVczZUFKdmRxK1hJYkhEZ01OTUJyc3gvWER4NkxtOU9tSkNVNHZDcFdJTjRXQ0gyMFQ5T2ZlNkhkeU52RWVpM3pobHpOMFovWVR5b1RlcFdwNUgvbXJuR29zU3NtcEp1NDV3OVJYbm5KbElrRzU5dDN0V1JoYXNZZW5GY0hlY0ZobG1odm5UQnRHa01Vb0VGREZnanltZ2twUUdkMmxoaU9YWGJwMzE1SXlGbEdUVFpvNERBYTZiMHp2VGZQOXV6R1FJZHhma3N5TUlGZmJDYVd6TjNPanB1bVIwMHg2SVZjZDdyOUxvOVBlVWw5a296cjhFaDRDWS9PQitEOVEvVjZ4RVpiVHNHeXc0aUFxQ0tvMTRDRXpDRVFIMEZWWTQ1cFg3b2IrbWhmL1pKbWNzL014blZGbkx6bDBDQXdFQUFhT0NBbjh3Z2dKN01BNEdBMVVkRHdFQi93UUVBd0lGb0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFUQU1CZ05WSFJNQkFmOEVBakFBTUIwR0ExVWREZ1FXQkJUOVI3Z21PZUQxdlpRVkNIYzBUdGh2T1lpMzlEQWZCZ05WSFNNRUdEQVdnQlFsNGhnT3NsZVJsQ3JsMUYyR2tJUGVVN080a2pCN0JnZ3JCZ0VGQlFjQkFRUnZNRzB3T0FZSUt3WUJCUVVITUFHR0xHaDBkSEE2THk5dlkzTndMbkJyYVM1bmIyOW5MM012WjNSek1XUTBhVzUwTDNoT0xWOHdkRE4zV1Rrd01ERUdDQ3NHQVFVRkJ6QUNoaVZvZEhSd09pOHZjR3RwTG1kdmIyY3ZjbVZ3Ynk5alpYSjBjeTluZEhNeFpEUXVaR1Z5TUIwR0ExVWRFUVFXTUJTQ0VtRjBkR1Z6ZEM1aGJtUnliMmxrTG1OdmJUQWhCZ05WSFNBRUdqQVlNQWdHQm1lQkRBRUNBVEFNQmdvckJnRUVBZFo1QWdVRE1EOEdBMVVkSHdRNE1EWXdOS0F5b0RDR0xtaDBkSEE2THk5amNteHpMbkJyYVM1bmIyOW5MMmQwY3pGa05HbHVkQzlZTWtveVNISmZOMUJwVFM1amNtd3dnZ0VFQmdvckJnRUVBZFo1QWdRQ0JJSDFCSUh5QVBBQWRnQlJvN0QxL1FGNW5GWnR1RGQ0and5a2Vzd2JKOHYzbm9oQ21nMysxSXNGNVFBQUFYK3Baa0pyQUFBRUF3QkhNRVVDSVFDb2RWRnpPQ1VubHVRUzB0MG9HdUEzdlZFR0Zxb2I4SVJiQ3BZeTdVZmNBUUlnRi9NZVVSdG9EN1FraFhCTjB1cmlDdEwvTENsMW1zRE5oWjFtMUhKeEpRb0FkZ0FwZWI3d25qazVJZkJXYzU5anBYZmx2bGQ5bkdBSytQbE5YU1pjSlYzSGhBQUFBWCtwWmtKWUFBQUVBd0JITUVVQ0lRQ1pvRW1Bbzc0UitGT0pQeVJLYkkyRSs2S0NYNkF1WG1oZnNXa2h0aUFLYWdJZ1p1dmZIcUE2UE9sM0JkV3RlU1l4TzA2QmNwT3dUYTV6NjVqSkw0dExEckl3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQURJcC93blFsZnE3dVZ6dDU3MHlRRTJOQVA1ajh5OGFzWWhKTXcrUTBYZ3M2a3pqZnpGL2g3OVpmRlhLOTh3QVJhVnI2amVSQXo2Y3E4cUVIMU8yQkQ5eDVEQ09UZzJxclNnSldiTU5VWkR5TXV6RmVyQ2EyNzloQklQVXBqNzg0YUdsYWp4Y2M3VHRYSHpacnhmbGM0d1BzZ2JnQ2twd3VqNmowandDNjdRNGJrOVVYKzNxcGw3MmFKMnpWbzFmT2s3U0ZwSTU4RjNJL1c4bkkva2Nwb1BvcDJCNkoxR3RxTURIRnByc3RnZUpMbFkzQWVmZWoyeW9Fd3UyajIrYzEvSjZ3SDV4YWRES3hnM052aDIreGhaUkZab0FUYjJlNllzeDRSMEJ0eWVYNEhaTWc0OFFhQk40N2xBeEFjZzR1YVNqRy8vQkhXTjM0cE1FYWNJeEdOMD0iLCJNSUlGakRDQ0EzU2dBd0lCQWdJTkFnQ09zZ0l6Tm1XTFpNM2JtekFOQmdrcWhraUc5dzBCQVFzRkFEQkhNUXN3Q1FZRFZRUUdFd0pWVXpFaU1DQUdBMVVFQ2hNWlIyOXZaMnhsSUZSeWRYTjBJRk5sY25acFkyVnpJRXhNUXpFVU1CSUdBMVVFQXhNTFIxUlRJRkp2YjNRZ1VqRXdIaGNOTWpBd09ERXpNREF3TURReVdoY05NamN3T1RNd01EQXdNRFF5V2pCR01Rc3dDUVlEVlFRR0V3SlZVekVpTUNBR0ExVUVDaE1aUjI5dloyeGxJRlJ5ZFhOMElGTmxjblpwWTJWeklFeE1RekVUTUJFR0ExVUVBeE1LUjFSVElFTkJJREZFTkRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS3ZBcXFQQ0UyN2wwdzl6QzhkVFBJRTg5YkEreFRtRGFHN3k3VmZRNGMrbU9XaGxVZWJVUXBLMHl2MnI2NzhSSkV4SzBIV0RqZXErbkxJSE4xRW01ajZyQVJaaXhteVJTamhJUjBLT1FQR0JNVWxkc2F6dElJSjdPMGcvODJxai92R0RsLy8zdDR0VHF4aVJoTFFuVExYSmRlQisyRGhrZFU2SUlneDZ3TjdFNU5jVUgzUmNzZWpjcWo4cDVTajE5dkJtNmkxRmhxTEd5bWhNRnJvV1ZVR08zeHRJSDkxZHNneTRlRktjZktWTFdLM28yMTkwUTBMbS9TaUttTGJSSjVBdTR5MWV1RkptMkpNOWVCODRGa3FhM2l2clhXVWVWdHllMENRZEt2c1kyRmthenZ4dHh2dXNMSnpMV1lIazU1emNSQWFjREEyU2VFdEJiUWZEMXFzQ0F3RUFBYU9DQVhZd2dnRnlNQTRHQTFVZER3RUIvd1FFQXdJQmhqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUhBd0l3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVUplSVlEckpYa1pRcTVkUmRocENEM2xPenVKSXdId1lEVlIwakJCZ3dGb0FVNUs4ckpuRWFLMGduaFM5U1ppenY4SWtUY1Q0d2FBWUlLd1lCQlFVSEFRRUVYREJhTUNZR0NDc0dBUVVGQnpBQmhocG9kSFJ3T2k4dmIyTnpjQzV3YTJrdVoyOXZaeTluZEhOeU1UQXdCZ2dyQmdFRkJRY3dBb1lrYUhSMGNEb3ZMM0JyYVM1bmIyOW5MM0psY0c4dlkyVnlkSE12WjNSemNqRXVaR1Z5TURRR0ExVWRId1F0TUNzd0thQW5vQ1dHSTJoMGRIQTZMeTlqY213dWNHdHBMbWR2YjJjdlozUnpjakV2WjNSemNqRXVZM0pzTUUwR0ExVWRJQVJHTUVRd0NBWUdaNEVNQVFJQk1EZ0dDaXNHQVFRQjFua0NCUU13S2pBb0JnZ3JCZ0VGQlFjQ0FSWWNhSFIwY0hNNkx5OXdhMmt1WjI5dlp5OXlaWEJ2YzJsMGIzSjVMekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBSVZUb3kyNGp3WFVyMHJBUGM5MjR2dVNWYktRdVl3M25MZmxMZkxoNUFZV0VlVmwvRHUxOFFBV1VNZGNKNm8vcUZaYmhYa0JIMFBOY3c5N3RoYWYyQmVvRFlZOUNrL2IrVUdsdWh4MDZ6ZDRFQmY3SDlQODRubnJ3cFIrNEdCRFpLK1hoM0kwdHFKeTJyZ09xTkRmbHI1SU1ROFpUV0EzeWx0YWt6U0JLWjZYcEYwUHBxeUNSdnAvTkNHdjJLWDJUdVBDSnZzY3AxL20ycFZUdHlCallQUlErUXVDUUdBSktqdE43UjVERnJmVHFNV3ZZZ1ZscENKQmt3bHU3KzdLWTNjVElmekU3Y21BTHNrTUtOTHVEeitSekNjc1lUc1ZhVTdWcDN4TDYwT1locUZrdUFPT3hEWjZwSE9qOStPSm1ZZ1BtT1Q0WDMrN0w1MWZYSnlSSDlLZkxSUDZuVDMxRDVubXNHQU9nWjI2LzhUOWhzQlcxdW85anU1ZlpMWlhWVlM1SDBIeUlCTUVLeUdNSVBoRldybHQvaEZTMjhOMXphS0kwWkJHRDNnWWdETGJpRFQ5ZkdYc3RwaytGbWM0b2xWbFdQelhlODF2ZG9FbkZicjVNMjcySGRnSldvK1doVDlCWU0wSmkrd2RWbW5SZmZYZ2xvRW9sdVROY1d6YzQxZEZwZ0p1OGZGM0xHMGdsMmliU1lpQ2k5YTZodlUwVHBwakp5SVdYaGtKVGNNSmxQcld4MVZ5dEVVR3JYMmwwSkR3UmpXLzY1NnIwS1ZCMDJ4SFJLdm0yWktJMDNUZ2xMSXBtVkNLM2tCS2tLTnBCTmtGdDhyaGFmY0NLT2I5SngvOXRwTkZsUVRsN0IzOXJKbEpXa1IxN1FuWnFWcHRGZVBGT1JvWm1Gek09IiwiTUlJRllqQ0NCRXFnQXdJQkFnSVFkNzBOYk5zMitScnFJUS9FOEZqVERUQU5CZ2txaGtpRzl3MEJBUXNGQURCWE1Rc3dDUVlEVlFRR0V3SkNSVEVaTUJjR0ExVUVDaE1RUjJ4dlltRnNVMmxuYmlCdWRpMXpZVEVRTUE0R0ExVUVDeE1IVW05dmRDQkRRVEViTUJrR0ExVUVBeE1TUjJ4dlltRnNVMmxuYmlCU2IyOTBJRU5CTUI0WERUSXdNRFl4T1RBd01EQTBNbG9YRFRJNE1ERXlPREF3TURBME1sb3dSekVMTUFrR0ExVUVCaE1DVlZNeElqQWdCZ05WQkFvVEdVZHZiMmRzWlNCVWNuVnpkQ0JUWlhKMmFXTmxjeUJNVEVNeEZEQVNCZ05WQkFNVEMwZFVVeUJTYjI5MElGSXhNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQXRoRUNpeDdqb1hlYk85eS9sRDYzbGFkQVBLSDlndmw5TWdhQ2NmYjJqSC83Nk51OGFpNlhsNk9NUy9rcjlySDV6b1Fkc2ZuRmw5N3Z1ZktqNmJ3U2lWNm5xbEtyK0NNbnk2U3huR1BiMTVsKzhBcGU2MmltOU1aYVJ3MU5FRFBqVHJFVG84Z1liRXZzL0FtUTM1MWtLU1VqQjZHMDBqMHVZT0RQMGdtSHU4MUk4RTNDd25xSWlydTZ6MWtaMXErUHNBZXduakh4Z3NIQTN5Nm1iV3daRHJYWWZpWWFSUU05c0hta2xDaXREMzhtNWFnSS9wYm9QR2lVVSs2RE9vZ3JGWllKc3VCNmpDNTExcHpycDFaa2o1WlBhSzQ5bDhLRWo4QzhRTUFMWEwzMmg3TTFiS3dZVUgrRTRFek5rdE1nNlRPOFVwbXZNclVwc3lVcXRFajVjdUhLWlBmbWdoQ042SjNDaW9qNk9HYUsvR1A1QWZsNC9YdGNkL3AyaC9yczM3RU9lWlZYdEwwbTc5WUIwZXNXQ3J1T0M3WEZ4WXBWcTlPczZwRkxLY3dacERJbFRpcnhaVVRRQXM2cXprbTA2cDk4ZzdCQWUrZERxNmRzbzQ5OWlZSDZUS1gvMVk3RHprdmd0ZGl6amtYUGRzRHRRQ3Y5VXcrd3A5VTdEYkdLb2dQZU1hM01kK3B2ZXo3VzM1RWlFdWErK3RneS9CQmpGRkZ5M2wzV0ZwTzlLV2d6N3pwbTdBZUtKdDhUMTFkbGVDZmVYa2tVQUtJQWY1cW9JYmFwc1pXd3Bia05GaEhheDJ4SVBFRGdmZzFhelZZODBaY0Z1Y3RMN1RsTG5NUS8wbFVUYmlTdzFuSDY5TUc2ek8wYjlmNkJRZGdBbUQwNnlLNTZtRGNZQlpVQ0F3RUFBYU9DQVRnd2dnRTBNQTRHQTFVZER3RUIvd1FFQXdJQmhqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVGtyeXNtY1JvclNDZUZMMUptTE8vd2lSTnhQakFmQmdOVkhTTUVHREFXZ0JSZ2UyWWFSUTJYeW9sUUwzMEV6VFNvLy96OVN6QmdCZ2dyQmdFRkJRY0JBUVJVTUZJd0pRWUlLd1lCQlFVSE1BR0dHV2gwZEhBNkx5OXZZM053TG5CcmFTNW5iMjluTDJkemNqRXdLUVlJS3dZQkJRVUhNQUtHSFdoMGRIQTZMeTl3YTJrdVoyOXZaeTluYzNJeEwyZHpjakV1WTNKME1ESUdBMVVkSHdRck1Da3dKNkFsb0NPR0lXaDBkSEE2THk5amNtd3VjR3RwTG1kdmIyY3ZaM055TVM5bmMzSXhMbU55YkRBN0JnTlZIU0FFTkRBeU1BZ0dCbWVCREFFQ0FUQUlCZ1puZ1F3QkFnSXdEUVlMS3dZQkJBSFdlUUlGQXdJd0RRWUxLd1lCQkFIV2VRSUZBd013RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQURTa0hyRW9vOUMwZGhlbU1Yb2g2ZEZTUHNqYmRCWkJpTGc5TlIzdDVQK1Q0VnhmcTd2cWZNL2I1QTNSaTFmeUptOWJ2aGRHYUpRM2IydDZ5TUFZTi9vbFVhenNhTCt5eUVuOVdwcktBU09zaElBckFveVpsK3RKYW94MTE4ZmVzc21YbjFoSVZ3NDFvZVFhMXYxdmc0RnY3NHpQbDYvQWhTcnc5VTVwQ1pFdDRXaTR3U3R6NmRUWi9DTEFOeDhMWmgxSjdRSlZqMmZoTXRmVEpyOXc0ejMwWjIwOWZPVTBpT015K3FkdUJtcHZ2WXVSN2haTDZEdXBzemZudzBTa2Z0aHMxOGRHOVpLYjU5VWh2bWFTR1pSVmJOUXBzZzNCWmx2aWQwbElLTzJkMXhvemNsT3pnalhQWW92SkpJdWx0emtNdTM0cVFiOVN6L3lpbHJiQ2dqOD0iXX0.eyJub25jZSI6IkprblpTb1p1TnE4K09ydFB4MERmc0xVWEZWckozQ0M5U216RURjbGJHbms9IiwidGltZXN0YW1wTXMiOjE2NTQ1MjkzNTYwMTcsImFwa1BhY2thZ2VOYW1lIjoiY29tLmdvb2dsZS5hbmRyb2lkLmdtcyIsImFwa0RpZ2VzdFNoYTI1NiI6IlJZdkx3Vm1nRjJYYXMxMUREOTI1SXVzc0p1eEtEL3dCN2pjT01qbU1UR0E9IiwiY3RzUHJvZmlsZU1hdGNoIjp0cnVlLCJhcGtDZXJ0aWZpY2F0ZURpZ2VzdFNoYTI1NiI6WyI4UDFzVzBFUEpjc2x3N1V6UnNpWEw2NHcrTzUwRWQrUkJJQ3RheTFnMjRNPSJdLCJiYXNpY0ludGVncml0eSI6dHJ1ZSwiZXZhbHVhdGlvblR5cGUiOiJCQVNJQyJ9.QGm9B8pw6wwy0Zyly_lkLPw_56y9vzFggS7z6J0u9nLglFBc-VnDUgeZEBzYiSrU5bXsKFn9lF6MbjvmpVgnYgBFLEYAlNFDe-2CPf0UdNR1wS-cMep1IKsdkhCQGL7LVzucLvSMPJt4QvqEScQrsjw9X-zCKiKuEsrDfrBoVhvYEJjSNzMtIG8k1gAtJJ-QcdcoLU1ImJEvmU-5VdYfoiOuxyGULaBbjIQ7o190FEXtQuyHIxUUknVsADvDK9loQA0lp38sl1Ec4ddbsyNMFCctnNFdCrosp9PSmQLNMv1_bhIgctdYVTkr9CR59LJur4PWGmOGS_3bjot5IB5Qrg") + let jwsu = super::JwsCompact::from_str("eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGYmpDQ0JGYWdBd0lCQWdJUUFhM09LT2RvVFk0UUFBQUFBQTNYWERBTkJna3Foa2lHOXcwQkFRc0ZBREJHTVFzd0NRWURWUVFHRXdKVlV6RWlNQ0FHQTFVRUNoTVpSMjl2WjJ4bElGUnlkWE4wSUZObGNuWnBZMlZ6SUV4TVF6RVRNQkVHQTFVRUF4TUtSMVJUSUVOQklERkVOREFlRncweU1qQXpNakF5TVRFMU1qRmFGdzB5TWpBMk1UZ3lNVEUxTWpCYU1CMHhHekFaQmdOVkJBTVRFbUYwZEdWemRDNWhibVJ5YjJsa0xtTnZiVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMbXFMQlhWNENaQTVzVDVjVGZ1WGN3MTFXRDJZVVczZUFKdmRxK1hJYkhEZ01OTUJyc3gvWER4NkxtOU9tSkNVNHZDcFdJTjRXQ0gyMFQ5T2ZlNkhkeU52RWVpM3pobHpOMFovWVR5b1RlcFdwNUgvbXJuR29zU3NtcEp1NDV3OVJYbm5KbElrRzU5dDN0V1JoYXNZZW5GY0hlY0ZobG1odm5UQnRHa01Vb0VGREZnanltZ2twUUdkMmxoaU9YWGJwMzE1SXlGbEdUVFpvNERBYTZiMHp2VGZQOXV6R1FJZHhma3N5TUlGZmJDYVd6TjNPanB1bVIwMHg2SVZjZDdyOUxvOVBlVWw5a296cjhFaDRDWS9PQitEOVEvVjZ4RVpiVHNHeXc0aUFxQ0tvMTRDRXpDRVFIMEZWWTQ1cFg3b2IrbWhmL1pKbWNzL014blZGbkx6bDBDQXdFQUFhT0NBbjh3Z2dKN01BNEdBMVVkRHdFQi93UUVBd0lGb0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFUQU1CZ05WSFJNQkFmOEVBakFBTUIwR0ExVWREZ1FXQkJUOVI3Z21PZUQxdlpRVkNIYzBUdGh2T1lpMzlEQWZCZ05WSFNNRUdEQVdnQlFsNGhnT3NsZVJsQ3JsMUYyR2tJUGVVN080a2pCN0JnZ3JCZ0VGQlFjQkFRUnZNRzB3T0FZSUt3WUJCUVVITUFHR0xHaDBkSEE2THk5dlkzTndMbkJyYVM1bmIyOW5MM012WjNSek1XUTBhVzUwTDNoT0xWOHdkRE4zV1Rrd01ERUdDQ3NHQVFVRkJ6QUNoaVZvZEhSd09pOHZjR3RwTG1kdmIyY3ZjbVZ3Ynk5alpYSjBjeTluZEhNeFpEUXVaR1Z5TUIwR0ExVWRFUVFXTUJTQ0VtRjBkR1Z6ZEM1aGJtUnliMmxrTG1OdmJUQWhCZ05WSFNBRUdqQVlNQWdHQm1lQkRBRUNBVEFNQmdvckJnRUVBZFo1QWdVRE1EOEdBMVVkSHdRNE1EWXdOS0F5b0RDR0xtaDBkSEE2THk5amNteHpMbkJyYVM1bmIyOW5MMmQwY3pGa05HbHVkQzlZTWtveVNISmZOMUJwVFM1amNtd3dnZ0VFQmdvckJnRUVBZFo1QWdRQ0JJSDFCSUh5QVBBQWRnQlJvN0QxL1FGNW5GWnR1RGQ0and5a2Vzd2JKOHYzbm9oQ21nMysxSXNGNVFBQUFYK3Baa0pyQUFBRUF3QkhNRVVDSVFDb2RWRnpPQ1VubHVRUzB0MG9HdUEzdlZFR0Zxb2I4SVJiQ3BZeTdVZmNBUUlnRi9NZVVSdG9EN1FraFhCTjB1cmlDdEwvTENsMW1zRE5oWjFtMUhKeEpRb0FkZ0FwZWI3d25qazVJZkJXYzU5anBYZmx2bGQ5bkdBSytQbE5YU1pjSlYzSGhBQUFBWCtwWmtKWUFBQUVBd0JITUVVQ0lRQ1pvRW1Bbzc0UitGT0pQeVJLYkkyRSs2S0NYNkF1WG1oZnNXa2h0aUFLYWdJZ1p1dmZIcUE2UE9sM0JkV3RlU1l4TzA2QmNwT3dUYTV6NjVqSkw0dExEckl3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQURJcC93blFsZnE3dVZ6dDU3MHlRRTJOQVA1ajh5OGFzWWhKTXcrUTBYZ3M2a3pqZnpGL2g3OVpmRlhLOTh3QVJhVnI2amVSQXo2Y3E4cUVIMU8yQkQ5eDVEQ09UZzJxclNnSldiTU5VWkR5TXV6RmVyQ2EyNzloQklQVXBqNzg0YUdsYWp4Y2M3VHRYSHpacnhmbGM0d1BzZ2JnQ2twd3VqNmowandDNjdRNGJrOVVYKzNxcGw3MmFKMnpWbzFmT2s3U0ZwSTU4RjNJL1c4bkkva2Nwb1BvcDJCNkoxR3RxTURIRnByc3RnZUpMbFkzQWVmZWoyeW9Fd3UyajIrYzEvSjZ3SDV4YWRES3hnM052aDIreGhaUkZab0FUYjJlNllzeDRSMEJ0eWVYNEhaTWc0OFFhQk40N2xBeEFjZzR1YVNqRy8vQkhXTjM0cE1FYWNJeEdOMD0iLCJNSUlGakRDQ0EzU2dBd0lCQWdJTkFnQ09zZ0l6Tm1XTFpNM2JtekFOQmdrcWhraUc5dzBCQVFzRkFEQkhNUXN3Q1FZRFZRUUdFd0pWVXpFaU1DQUdBMVVFQ2hNWlIyOXZaMnhsSUZSeWRYTjBJRk5sY25acFkyVnpJRXhNUXpFVU1CSUdBMVVFQXhNTFIxUlRJRkp2YjNRZ1VqRXdIaGNOTWpBd09ERXpNREF3TURReVdoY05NamN3T1RNd01EQXdNRFF5V2pCR01Rc3dDUVlEVlFRR0V3SlZVekVpTUNBR0ExVUVDaE1aUjI5dloyeGxJRlJ5ZFhOMElGTmxjblpwWTJWeklFeE1RekVUTUJFR0ExVUVBeE1LUjFSVElFTkJJREZFTkRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS3ZBcXFQQ0UyN2wwdzl6QzhkVFBJRTg5YkEreFRtRGFHN3k3VmZRNGMrbU9XaGxVZWJVUXBLMHl2MnI2NzhSSkV4SzBIV0RqZXErbkxJSE4xRW01ajZyQVJaaXhteVJTamhJUjBLT1FQR0JNVWxkc2F6dElJSjdPMGcvODJxai92R0RsLy8zdDR0VHF4aVJoTFFuVExYSmRlQisyRGhrZFU2SUlneDZ3TjdFNU5jVUgzUmNzZWpjcWo4cDVTajE5dkJtNmkxRmhxTEd5bWhNRnJvV1ZVR08zeHRJSDkxZHNneTRlRktjZktWTFdLM28yMTkwUTBMbS9TaUttTGJSSjVBdTR5MWV1RkptMkpNOWVCODRGa3FhM2l2clhXVWVWdHllMENRZEt2c1kyRmthenZ4dHh2dXNMSnpMV1lIazU1emNSQWFjREEyU2VFdEJiUWZEMXFzQ0F3RUFBYU9DQVhZd2dnRnlNQTRHQTFVZER3RUIvd1FFQXdJQmhqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUhBd0l3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVUplSVlEckpYa1pRcTVkUmRocENEM2xPenVKSXdId1lEVlIwakJCZ3dGb0FVNUs4ckpuRWFLMGduaFM5U1ppenY4SWtUY1Q0d2FBWUlLd1lCQlFVSEFRRUVYREJhTUNZR0NDc0dBUVVGQnpBQmhocG9kSFJ3T2k4dmIyTnpjQzV3YTJrdVoyOXZaeTluZEhOeU1UQXdCZ2dyQmdFRkJRY3dBb1lrYUhSMGNEb3ZMM0JyYVM1bmIyOW5MM0psY0c4dlkyVnlkSE12WjNSemNqRXVaR1Z5TURRR0ExVWRId1F0TUNzd0thQW5vQ1dHSTJoMGRIQTZMeTlqY213dWNHdHBMbWR2YjJjdlozUnpjakV2WjNSemNqRXVZM0pzTUUwR0ExVWRJQVJHTUVRd0NBWUdaNEVNQVFJQk1EZ0dDaXNHQVFRQjFua0NCUU13S2pBb0JnZ3JCZ0VGQlFjQ0FSWWNhSFIwY0hNNkx5OXdhMmt1WjI5dlp5OXlaWEJ2YzJsMGIzSjVMekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBSVZUb3kyNGp3WFVyMHJBUGM5MjR2dVNWYktRdVl3M25MZmxMZkxoNUFZV0VlVmwvRHUxOFFBV1VNZGNKNm8vcUZaYmhYa0JIMFBOY3c5N3RoYWYyQmVvRFlZOUNrL2IrVUdsdWh4MDZ6ZDRFQmY3SDlQODRubnJ3cFIrNEdCRFpLK1hoM0kwdHFKeTJyZ09xTkRmbHI1SU1ROFpUV0EzeWx0YWt6U0JLWjZYcEYwUHBxeUNSdnAvTkNHdjJLWDJUdVBDSnZzY3AxL20ycFZUdHlCallQUlErUXVDUUdBSktqdE43UjVERnJmVHFNV3ZZZ1ZscENKQmt3bHU3KzdLWTNjVElmekU3Y21BTHNrTUtOTHVEeitSekNjc1lUc1ZhVTdWcDN4TDYwT1locUZrdUFPT3hEWjZwSE9qOStPSm1ZZ1BtT1Q0WDMrN0w1MWZYSnlSSDlLZkxSUDZuVDMxRDVubXNHQU9nWjI2LzhUOWhzQlcxdW85anU1ZlpMWlhWVlM1SDBIeUlCTUVLeUdNSVBoRldybHQvaEZTMjhOMXphS0kwWkJHRDNnWWdETGJpRFQ5ZkdYc3RwaytGbWM0b2xWbFdQelhlODF2ZG9FbkZicjVNMjcySGRnSldvK1doVDlCWU0wSmkrd2RWbW5SZmZYZ2xvRW9sdVROY1d6YzQxZEZwZ0p1OGZGM0xHMGdsMmliU1lpQ2k5YTZodlUwVHBwakp5SVdYaGtKVGNNSmxQcld4MVZ5dEVVR3JYMmwwSkR3UmpXLzY1NnIwS1ZCMDJ4SFJLdm0yWktJMDNUZ2xMSXBtVkNLM2tCS2tLTnBCTmtGdDhyaGFmY0NLT2I5SngvOXRwTkZsUVRsN0IzOXJKbEpXa1IxN1FuWnFWcHRGZVBGT1JvWm1Gek09IiwiTUlJRllqQ0NCRXFnQXdJQkFnSVFkNzBOYk5zMitScnFJUS9FOEZqVERUQU5CZ2txaGtpRzl3MEJBUXNGQURCWE1Rc3dDUVlEVlFRR0V3SkNSVEVaTUJjR0ExVUVDaE1RUjJ4dlltRnNVMmxuYmlCdWRpMXpZVEVRTUE0R0ExVUVDeE1IVW05dmRDQkRRVEViTUJrR0ExVUVBeE1TUjJ4dlltRnNVMmxuYmlCU2IyOTBJRU5CTUI0WERUSXdNRFl4T1RBd01EQTBNbG9YRFRJNE1ERXlPREF3TURBME1sb3dSekVMTUFrR0ExVUVCaE1DVlZNeElqQWdCZ05WQkFvVEdVZHZiMmRzWlNCVWNuVnpkQ0JUWlhKMmFXTmxjeUJNVEVNeEZEQVNCZ05WQkFNVEMwZFVVeUJTYjI5MElGSXhNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQXRoRUNpeDdqb1hlYk85eS9sRDYzbGFkQVBLSDlndmw5TWdhQ2NmYjJqSC83Nk51OGFpNlhsNk9NUy9rcjlySDV6b1Fkc2ZuRmw5N3Z1ZktqNmJ3U2lWNm5xbEtyK0NNbnk2U3huR1BiMTVsKzhBcGU2MmltOU1aYVJ3MU5FRFBqVHJFVG84Z1liRXZzL0FtUTM1MWtLU1VqQjZHMDBqMHVZT0RQMGdtSHU4MUk4RTNDd25xSWlydTZ6MWtaMXErUHNBZXduakh4Z3NIQTN5Nm1iV3daRHJYWWZpWWFSUU05c0hta2xDaXREMzhtNWFnSS9wYm9QR2lVVSs2RE9vZ3JGWllKc3VCNmpDNTExcHpycDFaa2o1WlBhSzQ5bDhLRWo4QzhRTUFMWEwzMmg3TTFiS3dZVUgrRTRFek5rdE1nNlRPOFVwbXZNclVwc3lVcXRFajVjdUhLWlBmbWdoQ042SjNDaW9qNk9HYUsvR1A1QWZsNC9YdGNkL3AyaC9yczM3RU9lWlZYdEwwbTc5WUIwZXNXQ3J1T0M3WEZ4WXBWcTlPczZwRkxLY3dacERJbFRpcnhaVVRRQXM2cXprbTA2cDk4ZzdCQWUrZERxNmRzbzQ5OWlZSDZUS1gvMVk3RHprdmd0ZGl6amtYUGRzRHRRQ3Y5VXcrd3A5VTdEYkdLb2dQZU1hM01kK3B2ZXo3VzM1RWlFdWErK3RneS9CQmpGRkZ5M2wzV0ZwTzlLV2d6N3pwbTdBZUtKdDhUMTFkbGVDZmVYa2tVQUtJQWY1cW9JYmFwc1pXd3Bia05GaEhheDJ4SVBFRGdmZzFhelZZODBaY0Z1Y3RMN1RsTG5NUS8wbFVUYmlTdzFuSDY5TUc2ek8wYjlmNkJRZGdBbUQwNnlLNTZtRGNZQlpVQ0F3RUFBYU9DQVRnd2dnRTBNQTRHQTFVZER3RUIvd1FFQXdJQmhqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVGtyeXNtY1JvclNDZUZMMUptTE8vd2lSTnhQakFmQmdOVkhTTUVHREFXZ0JSZ2UyWWFSUTJYeW9sUUwzMEV6VFNvLy96OVN6QmdCZ2dyQmdFRkJRY0JBUVJVTUZJd0pRWUlLd1lCQlFVSE1BR0dHV2gwZEhBNkx5OXZZM053TG5CcmFTNW5iMjluTDJkemNqRXdLUVlJS3dZQkJRVUhNQUtHSFdoMGRIQTZMeTl3YTJrdVoyOXZaeTluYzNJeEwyZHpjakV1WTNKME1ESUdBMVVkSHdRck1Da3dKNkFsb0NPR0lXaDBkSEE2THk5amNtd3VjR3RwTG1kdmIyY3ZaM055TVM5bmMzSXhMbU55YkRBN0JnTlZIU0FFTkRBeU1BZ0dCbWVCREFFQ0FUQUlCZ1puZ1F3QkFnSXdEUVlMS3dZQkJBSFdlUUlGQXdJd0RRWUxLd1lCQkFIV2VRSUZBd013RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQURTa0hyRW9vOUMwZGhlbU1Yb2g2ZEZTUHNqYmRCWkJpTGc5TlIzdDVQK1Q0VnhmcTd2cWZNL2I1QTNSaTFmeUptOWJ2aGRHYUpRM2IydDZ5TUFZTi9vbFVhenNhTCt5eUVuOVdwcktBU09zaElBckFveVpsK3RKYW94MTE4ZmVzc21YbjFoSVZ3NDFvZVFhMXYxdmc0RnY3NHpQbDYvQWhTcnc5VTVwQ1pFdDRXaTR3U3R6NmRUWi9DTEFOeDhMWmgxSjdRSlZqMmZoTXRmVEpyOXc0ejMwWjIwOWZPVTBpT015K3FkdUJtcHZ2WXVSN2haTDZEdXBzemZudzBTa2Z0aHMxOGRHOVpLYjU5VWh2bWFTR1pSVmJOUXBzZzNCWmx2aWQwbElLTzJkMXhvemNsT3pnalhQWW92SkpJdWx0emtNdTM0cVFiOVN6L3lpbHJiQ2dqOD0iXX0.eyJub25jZSI6IkprblpTb1p1TnE4K09ydFB4MERmc0xVWEZWckozQ0M5U216RURjbGJHbms9IiwidGltZXN0YW1wTXMiOjE2NTQ1MjkzNTYwMTcsImFwa1BhY2thZ2VOYW1lIjoiY29tLmdvb2dsZS5hbmRyb2lkLmdtcyIsImFwa0RpZ2VzdFNoYTI1NiI6IlJZdkx3Vm1nRjJYYXMxMUREOTI1SXVzc0p1eEtEL3dCN2pjT01qbU1UR0E9IiwiY3RzUHJvZmlsZU1hdGNoIjp0cnVlLCJhcGtDZXJ0aWZpY2F0ZURpZ2VzdFNoYTI1NiI6WyI4UDFzVzBFUEpjc2x3N1V6UnNpWEw2NHcrTzUwRWQrUkJJQ3RheTFnMjRNPSJdLCJiYXNpY0ludGVncml0eSI6dHJ1ZSwiZXZhbHVhdGlvblR5cGUiOiJCQVNJQyJ9.QGm9B8pw6wwy0Zyly_lkLPw_56y9vzFggS7z6J0u9nLglFBc-VnDUgeZEBzYiSrU5bXsKFn9lF6MbjvmpVgnYgBFLEYAlNFDe-2CPf0UdNR1wS-cMep1IKsdkhCQGL7LVzucLvSMPJt4QvqEScQrsjw9X-zCKiKuEsrDfrBoVhvYEJjSNzMtIG8k1gAtJJ-QcdcoLU1ImJEvmU-5VdYfoiOuxyGULaBbjIQ7o190FEXtQuyHIxUUknVsADvDK9loQA0lp38sl1Ec4ddbsyNMFCctnNFdCrosp9PSmQLNMv1_bhIgctdYVTkr9CR59LJur4PWGmOGS_3bjot5IB5Qrg") .expect("Invalid jwsu"); let certs = jwsu @@ -268,18 +250,19 @@ HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== let trust_root = x509::X509::from_pem(gsr1.as_bytes()).unwrap(); - let mut jws_x509_verifier = JwsX509VerifierBuilder::new() + let jws_x509_verifier = JwsX509VerifierBuilder::new() .add_trust_root(trust_root) .add_fullchain(certs) .yolo() .build() .unwrap(); - let _claims: std::collections::BTreeMap = jwsu - .verify(&mut jws_x509_verifier) - .expect("Failed to verify") - .from_json() - .expect("Failed to deserialise contents"); + let _claims: std::collections::BTreeMap = + jws_x509_verifier + .verify(&jwsu) + .expect("Failed to verify") + .from_json() + .expect("Failed to deserialise contents"); } #[test] @@ -288,19 +271,20 @@ HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== let _ = tracing_subscriber::fmt::try_init(); - let jwsu = super::JwsUnverified::from_str( + let jwsu = super::JwsCompact::from_str( "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjhyaFRhVElJMHRzY1MyX2QtWUNYRm92RGpRUkxEUTEzbWhHV3d5UTBibWMiLCJ5IjoiYmoyakNkSXkxU3lpcHBkU2lEWmxHZEhMUTR0TG40NjMzTFk2dUJHUWU1NCIsImFsZyI6IkVTMjU2IiwidXNlIjoic2lnIn0sInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiYTNkYjczYTctNzc3Zi00NzI2LTliZGUtNjBkMjEwOTJlNTFmIiwiYXV0aF90eXBlIjoiZ2VuZXJhdGVkcGFzc3dvcmQiLCJleHBpcnkiOlsyMDIyLDI2MCwyMTk5OCw2NTc4MDM0NjhdLCJ1dWlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiZGlzcGxheW5hbWUiOiJTeXN0ZW0gQWRtaW5pc3RyYXRvciIsInNwbiI6ImFkbWluQGlkbS5jb3JlZm9ybS5jb20iLCJtYWlsX3ByaW1hcnkiOm51bGwsImxpbV91aWR4IjpmYWxzZSwibGltX3JtYXgiOjEyOCwibGltX3BtYXgiOjI1NiwibGltX2ZtYXgiOjMyfQ.Y9CeMWwGX4xS4O2Yy9vlTjW-6dL_Ncoo-nWd2344O_SwWdBneDpUE35aA_kuLRg1ssVceyVvCDhlxYOyXwzAjQ" ) .expect("Invalid jwsu"); let jwk = jwsu.get_jwk_pubkey().unwrap(); - let mut jws_es256_verifier = JwsEs256Verifier::try_from(jwk).unwrap(); + let jws_es256_verifier = JwsEs256Verifier::try_from(jwk).unwrap(); - let _claims: std::collections::BTreeMap = jwsu - .verify(&mut jws_es256_verifier) - .expect("Failed to verify") - .from_json() - .expect("Failed to deserialise contents"); + let _claims: std::collections::BTreeMap = + jws_es256_verifier + .verify(&jwsu) + .expect("Failed to verify") + .from_json() + .expect("Failed to deserialise contents"); } } diff --git a/src/jwt.rs b/src/jwt.rs index c2c36b2..25850da 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -1,18 +1,20 @@ //! Jwt implementation use crate::btreemap_empty; -use crate::compact::Jwk; +use crate::compact::{Jwk, JwsCompact, JwsCompactVerifyData}; use crate::error::JwtError; -use crate::jws::{Jws, JwsSigned, JwsUnverified}; -use crate::traits::{JwsSigner, JwsVerifier}; +use crate::jws::{Jws, JwsCompactSign2Data, JwsSigned}; +use crate::traits::{JwsSignable, JwsVerifiable}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt; +use std::marker::PhantomData; use std::str::FromStr; /// An unverified jwt input which is ready to validate -pub struct JwtUnverified { - jws: JwsUnverified, +pub struct JwtUnverified { + jwsc: JwsCompact, + _v: PhantomData, } /// A signed jwt which can be converted to a string. @@ -112,61 +114,75 @@ where } } -#[cfg(feature = "openssl")] -impl Jwt +impl JwsSignable for Jwt where V: Clone + Serialize, { - /// Sign the content of this JWT token with the provided signer - pub fn sign(&self, signer: &mut S) -> Result { + type Signed = JwtSigned; + + fn data(&self) -> Result { let mut jwts = Jws::into_json(self).map_err(|_| JwtError::InvalidJwt)?; jwts.set_typ(Some("JWT")); - jwts.sign(signer).map(|jwsc| JwtSigned { + jwts.data() + } + + fn post_process(&self, jwsc: JwsCompact) -> Result { + Ok(JwtSigned { jws: JwsSigned { jwsc }, }) } } -impl JwtUnverified { - /// Using this JwsVerifier, assert the correct signature of the data contained in - /// this token. - pub fn verify(&self, verifier: &mut K) -> Result, JwtError> - where - K: JwsVerifier, - V: Clone + DeserializeOwned, - { - let jws = self.jws.verify(verifier)?; - - jws.from_json().map_err(|_| JwtError::InvalidJwt) +impl JwsVerifiable for JwtUnverified +where + V: Clone + DeserializeOwned, +{ + type Verified = Jwt; + + fn data(&self) -> JwsCompactVerifyData<'_> { + self.jwsc.data() } + fn post_process(&self, value: Jws) -> Result { + value.from_json().map_err(|_| JwtError::InvalidJwt) + } +} + +impl JwtUnverified +where + V: Clone + DeserializeOwned, +{ /// Get the embedded public key used to sign this jwt, if present. pub fn get_jwk_pubkey(&self) -> Option<&Jwk> { - self.jws.get_jwk_pubkey() + self.jwsc.get_jwk_pubkey() } /// Get the KID used to sign this Jws if present pub fn get_jwk_kid(&self) -> Option<&str> { - self.jws.get_jwk_kid() + self.jwsc.get_jwk_kid() } } -impl FromStr for JwtUnverified { +impl FromStr for JwtUnverified { type Err = JwtError; fn from_str(s: &str) -> Result { - JwsUnverified::from_str(s).map(|jws| JwtUnverified { jws }) + JwsCompact::from_str(s).map(|jwsc| JwtUnverified { + jwsc, + _v: PhantomData, + }) } } impl JwtSigned { /// Invalidate this signed jwt, causing it to require validation before you can use it /// again. - pub fn invalidate(self) -> JwtUnverified { + pub fn invalidate(self) -> JwtUnverified { JwtUnverified { - jws: self.jws.invalidate(), + jwsc: self.jws.jwsc, + _v: PhantomData, } } } @@ -180,8 +196,8 @@ impl fmt::Display for JwtSigned { #[cfg(all(feature = "openssl", test))] mod tests { use super::Jwt; - use crate::crypto::JwsEs256Signer; - use crate::traits::JwsSignerToVerifier; + use crate::crypto::JwsHs256Signer; + use crate::traits::*; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Serialize, Clone, Deserialize, PartialEq)] @@ -200,18 +216,15 @@ mod tests { ..Default::default() }; - let mut jws_es256_signer = - JwsEs256Signer::generate_es256().expect("failed to construct signer."); - let mut jwk_es256_verifier = jws_es256_signer - .get_verifier() - .expect("failed to get verifier from signer"); + let jws_hs256_signer = + JwsHs256Signer::generate_hs256().expect("failed to construct signer."); - let jwts = jwt.sign(&mut jws_es256_signer).expect("failed to sign jwt"); + let jwts = jws_hs256_signer.sign(&jwt).expect("failed to sign jwt"); let jwtu = jwts.invalidate(); - let released = jwtu - .verify(&mut jwk_es256_verifier) + let released = jws_hs256_signer + .verify(&jwtu) .expect("Unable to validate jwt"); assert!(released == jwt); diff --git a/src/lib.rs b/src/lib.rs index 511fb54..6e422c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ //! let mut jws_es256_signer = //! JwsEs256Signer::generate_es256().unwrap(); //! -//! let oidc_signed = oidc.sign(&mut jws_es256_signer) +//! let oidc_signed = jws_es256_signer.sign(&oidc) //! .unwrap(); //! //! // Get the signed formatted token string @@ -85,8 +85,9 @@ //! .expect("Failed to retrieve current time") //! .as_secs() as i64; //! -//! let oidc_validated = oidc_unverified -//! .verify(&mut jwk_es256_verifier, curtime) +//! let oidc_validated = jwk_es256_verifier +//! .verify(&oidc_unverified) +//! .and_then(|oidc_exp| oidc_exp.verify_exp(curtime)) //! .unwrap(); //! //! // Prove we got back the same content. @@ -116,9 +117,9 @@ pub mod oidc; #[cfg(feature = "openssl")] pub use crate::crypto::{JwsEs256Signer, JwsEs256Verifier, JwsHs256Signer}; -pub use crate::compact::{JwaAlg, Jwk}; +pub use crate::compact::{JwaAlg, Jwk, JwsCompact}; pub use crate::error::JwtError; -pub use crate::jws::{Jws, JwsSigned, JwsUnverified}; +pub use crate::jws::{Jws, JwsSigned}; pub use crate::jwt::{Jwt, JwtSigned, JwtUnverified}; pub use crate::oidc::{OidcClaims, OidcSigned, OidcSubject, OidcToken, OidcUnverified}; diff --git a/src/oidc.rs b/src/oidc.rs index 82f3814..b9e22e8 100644 --- a/src/oidc.rs +++ b/src/oidc.rs @@ -1,9 +1,9 @@ //! Oidc token implementation -use crate::compact::{Jwk, JwsCompact}; -use crate::jws::{Jws, JwsSigned, JwsUnverified}; +use crate::compact::{Jwk, JwsCompact, JwsCompactVerifyData}; +use crate::jws::{Jws, JwsCompactSign2Data, JwsSigned}; -use crate::traits::{JwsSigner, JwsVerifier}; +use crate::traits::{JwsSignable, JwsVerifiable}; use crate::error::JwtError; use crate::{btreemap_empty, vec_empty}; @@ -16,7 +16,12 @@ use uuid::Uuid; /// An unverified token input which is ready to validate pub struct OidcUnverified { - jws: JwsUnverified, + jwsc: JwsCompact, +} + +/// An verified token that is awaiting expiry verification +pub struct OidcExpUnverified { + oidc: OidcToken, } /// A signed oidc token which can be converted to a string. @@ -116,50 +121,33 @@ pub struct OidcToken { pub claims: BTreeMap, } -#[cfg(feature = "openssl")] -impl OidcToken { - /// Sign the content of this OIDC token with the provided signer - pub fn sign(&self, signer: &mut S) -> Result { +impl JwsSignable for OidcToken { + type Signed = OidcSigned; + + fn data(&self) -> Result { let mut jwts = Jws::into_json(self).map_err(|_| JwtError::InvalidJwt)?; jwts.set_typ(Some("JWT")); - jwts.sign(signer).map(|jwsc| OidcSigned { - jws: JwsSigned { jwsc }, - }) + jwts.data() } -} -impl OidcUnverified { - /// Using this JwsVerifier, assert the correct signature of the data contained in - /// this token. The current time is represented by seconds since the epoch. You may - /// choose to ignore exp validation by setting this to 0, but this is DANGEROUS. - pub fn verify(&self, verifier: &mut K, curtime: i64) -> Result - where - K: JwsVerifier, - { - let jws = self.jws.verify(verifier)?; - - let tok: OidcToken = jws.from_json().map_err(|_| JwtError::InvalidJwt)?; - - // Check the exp - if tok.exp != 0 && tok.exp < curtime { - Err(JwtError::OidcTokenExpired) - } else { - Ok(tok) - } + fn post_process(&self, jwsc: JwsCompact) -> Result { + Ok(OidcSigned { + jws: JwsSigned { jwsc }, + }) } } impl OidcUnverified { /// Get the embedded public key used to sign this jwt, if present. pub fn get_jwk_pubkey(&self) -> Option<&Jwk> { - self.jws.get_jwk_pubkey() + self.jwsc.get_jwk_pubkey() } /// Get the KID used to sign this Jws if present pub fn get_jwk_kid(&self) -> Option<&str> { - self.jws.get_jwk_kid() + self.jwsc.get_jwk_kid() } } @@ -167,9 +155,36 @@ impl FromStr for OidcUnverified { type Err = JwtError; fn from_str(s: &str) -> Result { - JwsCompact::from_str(s).map(|jwsc| OidcUnverified { - jws: JwsUnverified { jwsc }, - }) + JwsCompact::from_str(s).map(|jwsc| OidcUnverified { jwsc }) + } +} + +impl JwsVerifiable for OidcUnverified { + type Verified = OidcExpUnverified; + + fn data(&self) -> JwsCompactVerifyData<'_> { + self.jwsc.data() + } + + fn post_process(&self, value: Jws) -> Result { + let oidc: OidcToken = value.from_json().map_err(|_| JwtError::InvalidJwt)?; + Ok(OidcExpUnverified { oidc }) + } +} + +impl OidcExpUnverified { + /// Verify the expiry of this OIDC Token. The token at this point has passed cryptographic + /// verification, and should have it's expiry validated. + /// + /// curtime represents the current time in seconds since the unix epoch. + /// + /// A curtime of `0` means that the exp will not be checked. This is not recommended. + pub fn verify_exp(self, curtime: i64) -> Result { + if self.oidc.exp != 0 && self.oidc.exp < curtime { + Err(JwtError::OidcTokenExpired) + } else { + Ok(self.oidc) + } } } @@ -178,7 +193,7 @@ impl OidcSigned { /// again. pub fn invalidate(self) -> OidcUnverified { OidcUnverified { - jws: self.jws.invalidate(), + jwsc: self.jws.jwsc, } } } @@ -193,7 +208,7 @@ impl fmt::Display for OidcSigned { mod tests { use super::{OidcSubject, OidcToken}; use crate::crypto::JwsEs256Signer; - use crate::traits::JwsSignerToVerifier; + use crate::traits::{JwsSigner, JwsSignerToVerifier, JwsVerifier}; use url::Url; #[test] @@ -217,19 +232,21 @@ mod tests { claims: Default::default(), }; - let mut jws_es256_signer = + let jws_es256_signer = JwsEs256Signer::generate_es256().expect("failed to construct signer."); - let mut jwk_es256_verifier = jws_es256_signer + let jwk_es256_verifier = jws_es256_signer .get_verifier() .expect("failed to get verifier from signer"); - let jwts = jwt.sign(&mut jws_es256_signer).expect("failed to sign jwt"); + let jwts = jws_es256_signer.sign(&jwt).expect("failed to sign jwt"); let jwtu = jwts.invalidate(); - let released = jwtu - .verify(&mut jwk_es256_verifier, 0) - .expect("Unable to validate jwt"); + let released = jwk_es256_verifier + .verify(&jwtu) + .expect("Unable to validate jwt") + .verify_exp(0) + .expect("Unable to validate oidc exp"); assert!(released == jwt); } diff --git a/src/traits.rs b/src/traits.rs index 4e2769e..b9ab62f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,46 +1,65 @@ //! Traits that define behaviour of JWS signing and verification types. -use crate::compact::{JwsCompact, ProtectedHeader}; +use crate::compact::{JwsCompact, JwsCompactVerifyData, ProtectedHeader}; use crate::error::JwtError; - -/// Data that will be signed -pub struct JwsCompactSignData<'a> { - pub(crate) hdr_bytes: &'a [u8], - pub(crate) payload_bytes: &'a [u8], -} +use crate::jws::{Jws, JwsCompactSign2Data}; /// A trait defining how a JwsSigner will operate. /// -/// Note that due to the design of this api, you can NOT defined your own. +/// Note that due to the design of this api, you can NOT define your own signer. pub trait JwsSigner { /// Get the key id from this signer - fn get_kid(&mut self) -> &str; + fn get_kid(&self) -> &str; /// Update thee content of the header with signer specific data - fn update_header(&mut self, header: &mut ProtectedHeader) -> Result<(), JwtError>; + fn update_header(&self, header: &mut ProtectedHeader) -> Result<(), JwtError>; /// Perform the signature operation - fn sign(&mut self, jwsc: JwsCompactSignData<'_>) -> Result, JwtError>; + fn sign(&self, _jws: &V) -> Result; } /// A trait allowing a signer to create it's corresponding verifier. /// -/// Note that due to the design of this api, you can NOT defined your own. +/// Note that due to the design of this api, you can NOT define your own signer or verifier. pub trait JwsSignerToVerifier { /// The associated verifier type Verifier; /// Retrieve the verifier corresponding to this signer - fn get_verifier(&mut self) -> Result; + fn get_verifier(&self) -> Result; } /// A trait defining how a JwsVerifier will operate. /// -/// Note that due to the design of this api, you can NOT defined your own. +/// Note that due to the design of this api, you can NOT define your own verifier. pub trait JwsVerifier { /// Get the key id from this verifier - fn get_kid(&mut self) -> Option<&str>; + fn get_kid(&self) -> Option<&str>; /// Perform the signature verification - fn verify_signature(&mut self, jwsc: &JwsCompact) -> Result; + fn verify(&self, _jwsc: &V) -> Result; +} + +/// A trait defining types that can be verified by a [JwsVerifier] +pub trait JwsVerifiable { + /// The type that should be emitted when the verification is complete + type Verified; + + /// Retrieve the inner data from the JwsCompact that is to be verified + fn data(&self) -> JwsCompactVerifyData<'_>; + + /// After the verification is complete, allow post-processing of the released payload + fn post_process(&self, value: Jws) -> Result; +} + +/// A trait defining types that can be signed by a [JwsSigner] +pub trait JwsSignable { + /// The type that should be emitted when the signature is completed + type Signed; + + /// Retrieve the inner data from the Jws that is to be signed. + fn data(&self) -> Result; + + /// After the signature is complete, allow post-processing of the compact jws + fn post_process(&self, value: JwsCompact) -> Result; }