From 502faa491099c8ed5d8806dae9e31c6618e94f9b Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Mon, 30 Sep 2024 08:26:43 -0300 Subject: [PATCH 1/9] feat(encoder): Add encoder builder --- Cargo.toml | 1 + src/builder.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/crypto/hmac.rs | 28 +++++++++++++++ src/crypto/mod.rs | 18 ++++++++++ src/lib.rs | 1 + 5 files changed, 138 insertions(+) create mode 100644 src/builder.rs diff --git a/Cargo.toml b/Cargo.toml index 7d438c0f..b31f72ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ ed25519-dalek = { version = "2.1.1" } p256 = { version = "0.13.2", features = ["ecdsa"] } p384 = { version = "0.13.0", features = ["ecdsa"] } rand_core = "0.6.4" +signature = "2.2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 00000000..456a3232 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,90 @@ +//! # Todo +//! +//! - Documentation + +use serde::Serialize; + +use crate::{ + crypto::JwtSigner, + errors::{new_error, Result}, + serialization::{b64_encode, b64_encode_part}, + Header, +}; + +/// # Todo +/// +/// - Documentation +pub struct JwtEncoder { + signing_provider: C, + header: Header, +} + +impl JwtEncoder { + /// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait. + pub fn new(signing_provider: C) -> Self { + // Determine a default header + let mut header = Header::new(signing_provider.algorithm()); + header.typ = Some("JWT".to_owned()); + + Self { signing_provider, header } + } + + /// Provide a custom header. + /// + /// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated. + /// + /// # Todo + /// + /// - Test the the error checking works + pub fn with_header(mut self, header: Header) -> Result { + // Check that the header makes use of the correct algorithm + if header.alg != self.signing_provider.algorithm() { + return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); + } + + self.header = header; + Ok(self) + } + + /// Encode and sign the `claims` as a JWT. + /// + /// # Todo + /// + /// - Put in example usage. + pub fn encode(&self, claims: &T) -> Result { + let encoded_header = b64_encode_part(&self.header)?; + let encoded_claims = b64_encode_part(claims)?; + let message = [encoded_header, encoded_claims].join("."); + + let signature = b64_encode(&self.signing_provider.sign(message.as_bytes())); + + Ok([message, signature].join(".")) + } +} + +#[cfg(test)] +mod builder_tests { + use crate::crypto::hmac::HmacSha256Trait; + + use super::*; + + #[derive(Debug, Serialize)] + struct Claims { + sub: String, + age: u32, + } + + #[test] + fn test_builder() { + // Arrange + let claims = Claims { sub: "123345".to_owned(), age: 25 }; + let signer = HmacSha256Trait::new("k3XTGsWiuO0stzhwPkuF2R6FdFY2crfyAVDjSBX34bW41ektItjp340PNXz1UvLkaq4CcT6ZMl7GXzfTvCvpkFXJbMni1wj40g423FbUxI7ZclVyzIrVFywrB5trt94Rv9AkTpShXzpnEWKGhZdD0MIOrQlg".as_ref()).unwrap(); + + // Act + let jwt = JwtEncoder::new(signer).encode(&claims).unwrap(); + + dbg!(&jwt); + + // Assert + } +} diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs index 7e4e2b21..e9d6c526 100644 --- a/src/crypto/hmac.rs +++ b/src/crypto/hmac.rs @@ -1,14 +1,42 @@ use hmac::{Hmac, Mac}; use sha2::{Sha256, Sha384, Sha512}; +use signature::Signer; use crate::errors::Result; use crate::serialization::{b64_decode, b64_encode}; use crate::Algorithm; +use super::JwtSigner; + type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; +pub(crate) struct HmacSha256Trait(HmacSha256); + +impl HmacSha256Trait { + pub(crate) fn new(key: &[u8]) -> Result { + let inner = HmacSha256::new_from_slice(key) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Signer> for HmacSha256Trait { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + + Ok(signer.sign(msg)) + } +} + +impl JwtSigner for HmacSha256Trait { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + pub(crate) fn sign_hmac(alg: Algorithm, key: &[u8], message: &[u8]) -> Result { let mut hmac = create_hmac(alg, key)?; let digest = hmac.sign(message); diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 4f5328eb..da347600 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -8,6 +8,24 @@ pub(crate) mod eddsa; pub(crate) mod hmac; pub(crate) mod rsa; +use signature::{Signer, Verifier}; + +/// Trait providing the functionality to sign a JWT. +/// +/// Allows an arbitrary crypto backend to be provided. +pub trait JwtSigner: Signer> { + /// Return the [`Algorithm`] corresponding to the signing module. + fn algorithm(&self) -> Algorithm; +} + +/// Trait providing the functionality to verify a JWT. +/// +/// Allows an arbitrary crypto backend to be provided. +pub trait JwtVerifier: Verifier> { + /// Return the [`Algorithm`] corresponding to the signing module. + fn algorithm(&self) -> Algorithm; +} + /// Take the payload of a JWT, sign it using the algorithm given and return /// the base64 url safe encoded of the result. /// diff --git a/src/lib.rs b/src/lib.rs index 2f936c8d..0eb22777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub use header::Header; pub use validation::{get_current_timestamp, Validation}; mod algorithms; +pub mod builder; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; mod decoding; From a701547b39fea62c4a32a00319bb3d41a1144c71 Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:25:34 -0300 Subject: [PATCH 2/9] feat(encoder): Convert to dynamic dispatch --- src/builder.rs | 37 ++++++++--- src/crypto/hmac.rs | 149 +++++++++++++++++++-------------------------- src/crypto/mod.rs | 118 +++++++++++++++++------------------ src/encoding.rs | 32 ++++++++-- src/lib.rs | 4 +- 5 files changed, 180 insertions(+), 160 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 456a3232..d6b7ad94 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,7 +5,10 @@ use serde::Serialize; use crate::{ - crypto::JwtSigner, + crypto::{ + hmac::{HmacSecret, Hs256, Hs384}, + JwtSigner, + }, errors::{new_error, Result}, serialization::{b64_encode, b64_encode_part}, Header, @@ -14,14 +17,19 @@ use crate::{ /// # Todo /// /// - Documentation -pub struct JwtEncoder { - signing_provider: C, +pub struct JwtEncoder { + signing_provider: Box, header: Header, } -impl JwtEncoder { +impl JwtEncoder { + /// Todo + pub fn from_signer(signing_provider: S) -> Self { + Self::from_boxed_signer(Box::new(signing_provider)) + } + /// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait. - pub fn new(signing_provider: C) -> Self { + pub fn from_boxed_signer(signing_provider: Box) -> Self { // Determine a default header let mut header = Header::new(signing_provider.algorithm()); header.typ = Some("JWT".to_owned()); @@ -60,11 +68,24 @@ impl JwtEncoder { Ok([message, signature].join(".")) } + + /// Create new [`JwtEncoder`] with the `HS256` algorithm. + pub fn hs_256(secret: HmacSecret) -> Result { + let signing_provider = Box::new(Hs256::new(secret)?); + + Ok(JwtEncoder::from_boxed_signer(signing_provider)) + } + + /// Create new [`JwtEncoder`] with the `HS384` algorithm. + pub fn hs_384(secret: HmacSecret) -> Result { + let signing_provider = Box::new(Hs384::new(secret)?); + + Ok(JwtEncoder::from_boxed_signer(signing_provider)) + } } #[cfg(test)] mod builder_tests { - use crate::crypto::hmac::HmacSha256Trait; use super::*; @@ -78,10 +99,10 @@ mod builder_tests { fn test_builder() { // Arrange let claims = Claims { sub: "123345".to_owned(), age: 25 }; - let signer = HmacSha256Trait::new("k3XTGsWiuO0stzhwPkuF2R6FdFY2crfyAVDjSBX34bW41ektItjp340PNXz1UvLkaq4CcT6ZMl7GXzfTvCvpkFXJbMni1wj40g423FbUxI7ZclVyzIrVFywrB5trt94Rv9AkTpShXzpnEWKGhZdD0MIOrQlg".as_ref()).unwrap(); + let secret = HmacSecret::from_secret("test".as_ref()); // Act - let jwt = JwtEncoder::new(signer).encode(&claims).unwrap(); + let jwt = JwtEncoder::hs_256(secret).unwrap().encode(&claims).unwrap(); dbg!(&jwt); diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs index e9d6c526..11a5144a 100644 --- a/src/crypto/hmac.rs +++ b/src/crypto/hmac.rs @@ -1,134 +1,113 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; use hmac::{Hmac, Mac}; use sha2::{Sha256, Sha384, Sha512}; -use signature::Signer; +use signature::{Signer, Verifier}; use crate::errors::Result; -use crate::serialization::{b64_decode, b64_encode}; use crate::Algorithm; -use super::JwtSigner; +use super::{JwtSigner, JwtVerifier}; type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; -pub(crate) struct HmacSha256Trait(HmacSha256); +pub(crate) struct HmacSecret(Vec); -impl HmacSha256Trait { - pub(crate) fn new(key: &[u8]) -> Result { - let inner = HmacSha256::new_from_slice(key) +impl HmacSecret { + /// If you're using an HMAC secret that is not base64, use that. + pub fn from_secret(secret: &[u8]) -> Self { + Self(secret.to_vec()) + } + + /// If you have a base64 HMAC secret, use that. + pub fn from_base64_secret(secret: &str) -> Result { + Ok(Self(STANDARD.decode(secret)?)) + } +} + +pub struct Hs256(HmacSha256); + +impl Hs256 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha256::new_from_slice(&secret.0) .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } -impl Signer> for HmacSha256Trait { +impl Signer> for Hs256 { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); - Ok(signer.sign(msg)) + Ok(signer.finalize().into_bytes().to_vec()) } } -impl JwtSigner for HmacSha256Trait { +impl JwtSigner for Hs256 { fn algorithm(&self) -> Algorithm { Algorithm::HS256 } } -pub(crate) fn sign_hmac(alg: Algorithm, key: &[u8], message: &[u8]) -> Result { - let mut hmac = create_hmac(alg, key)?; - let digest = hmac.sign(message); - Ok(b64_encode(digest)) -} +impl Verifier> for Hs256 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); -pub(crate) fn hmac_verify( - alg: Algorithm, - signature: &str, - key: &[u8], - message: &[u8], -) -> Result { - let mut hmac = create_hmac(alg, key)?; - let signature = b64_decode(signature)?; - Ok(hmac.verify(&signature, message)) + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) + } } -fn create_hmac(alg: Algorithm, key: &[u8]) -> Result> { - let hmac: Box = match alg { - Algorithm::HS256 => { - let sha256 = HmacSha256::new_from_slice(key) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - Box::new(sha256) - } - Algorithm::HS384 => { - let sha384 = HmacSha384::new_from_slice(key) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - Box::new(sha384) - } - Algorithm::HS512 => { - let sha512 = HmacSha512::new_from_slice(key) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - Box::new(sha512) - } - _ => { - return Err(crate::errors::new_error(crate::errors::ErrorKind::InvalidAlgorithm)); - } - }; - Ok(hmac) +impl JwtVerifier for Hs256 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } } -trait HmacAlgorithm { - fn sign(&mut self, message: &[u8]) -> Vec; - fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool; -} +pub(crate) struct Hs384(HmacSha384); -impl HmacAlgorithm for Box { - fn sign(&mut self, message: &[u8]) -> Vec { - (**self).sign(message) - } +impl Hs384 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha384::new_from_slice(&secret.0) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool { - (**self).verify(signature, message) + Ok(Self(inner)) } } -impl HmacAlgorithm for HmacSha256 { - fn sign(&mut self, message: &[u8]) -> Vec { - self.reset(); - self.update(message); - self.clone().finalize().into_bytes().to_vec() - } - fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool { - self.reset(); - self.update(message); - self.clone().verify_slice(signature).is_ok() +impl Signer> for Hs384 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); + + Ok(signer.finalize().into_bytes().to_vec()) } } -impl HmacAlgorithm for HmacSha384 { - fn sign(&mut self, message: &[u8]) -> Vec { - self.reset(); - self.update(message); - self.clone().finalize().into_bytes().to_vec() - } - fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool { - self.reset(); - self.update(message); - self.clone().verify_slice(signature).is_ok() +impl JwtSigner for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 } } -impl HmacAlgorithm for HmacSha512 { - fn sign(&mut self, message: &[u8]) -> Vec { - self.reset(); - self.update(message); - self.clone().finalize().into_bytes().to_vec() +impl Verifier> for Hs384 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); + + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) } +} - fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool { - self.reset(); - self.update(message); - self.clone().verify_slice(signature).is_ok() +impl JwtVerifier for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 } } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index da347600..4317a780 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,7 +1,7 @@ use crate::algorithms::Algorithm; -use crate::decoding::{DecodingKey, DecodingKeyKind}; -use crate::encoding::EncodingKey; -use crate::errors::Result; +// use crate::decoding::{DecodingKey, DecodingKeyKind}; +// use crate::encoding::EncodingKey; +// use crate::errors::Result; pub(crate) mod ecdsa; pub(crate) mod eddsa; @@ -26,60 +26,60 @@ pub trait JwtVerifier: Verifier> { fn algorithm(&self) -> Algorithm; } -/// Take the payload of a JWT, sign it using the algorithm given and return -/// the base64 url safe encoded of the result. -/// -/// If you just want to encode a JWT, use `encode` instead. -pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result { - match algorithm { - Algorithm::ES256 | Algorithm::ES384 => ecdsa::sign(algorithm, key.inner(), message), - Algorithm::EdDSA => eddsa::sign(key.inner(), message), - Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { - hmac::sign_hmac(algorithm, key.inner(), message) - } - Algorithm::RS256 - | Algorithm::RS384 - | Algorithm::RS512 - | Algorithm::PS256 - | Algorithm::PS384 - | Algorithm::PS512 => rsa::sign(algorithm, key.inner(), message), - } -} +// /// Take the payload of a JWT, sign it using the algorithm given and return +// /// the base64 url safe encoded of the result. +// /// +// /// If you just want to encode a JWT, use `encode` instead. +// pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result { +// match algorithm { +// Algorithm::ES256 | Algorithm::ES384 => ecdsa::sign(algorithm, key.inner(), message), +// Algorithm::EdDSA => eddsa::sign(key.inner(), message), +// Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { +// hmac::sign_hmac(algorithm, key.inner(), message) +// } +// Algorithm::RS256 +// | Algorithm::RS384 +// | Algorithm::RS512 +// | Algorithm::PS256 +// | Algorithm::PS384 +// | Algorithm::PS512 => rsa::sign(algorithm, key.inner(), message), +// } +// } -/// Compares the signature given with a re-computed signature for HMAC or using the public key -/// for RSA/EC. -/// -/// If you just want to decode a JWT, use `decode` instead. -/// -/// `signature` is the signature part of a jwt (text after the second '.') -/// -/// `message` is base64(header) + "." + base64(claims) -pub fn verify( - signature: &str, - message: &[u8], - key: &DecodingKey, - algorithm: Algorithm, -) -> Result { - match algorithm { - Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { - hmac::hmac_verify(algorithm, signature, key.as_bytes(), message) - } - Algorithm::ES256 | Algorithm::ES384 => { - ecdsa::verify(algorithm, signature, message, key.as_bytes()) - } - Algorithm::EdDSA => eddsa::verify(signature, message, key.as_bytes()), - Algorithm::RS256 - | Algorithm::RS384 - | Algorithm::RS512 - | Algorithm::PS256 - | Algorithm::PS384 - | Algorithm::PS512 => match &key.kind { - DecodingKeyKind::SecretOrDer(bytes) => { - rsa::verify_der(algorithm, signature, message, bytes) - } - DecodingKeyKind::RsaModulusExponent { n, e } => { - rsa::verify_from_components(algorithm, signature, message, (n, e)) - } - }, - } -} +// /// Compares the signature given with a re-computed signature for HMAC or using the public key +// /// for RSA/EC. +// /// +// /// If you just want to decode a JWT, use `decode` instead. +// /// +// /// `signature` is the signature part of a jwt (text after the second '.') +// /// +// /// `message` is base64(header) + "." + base64(claims) +// pub fn verify( +// signature: &str, +// message: &[u8], +// key: &DecodingKey, +// algorithm: Algorithm, +// ) -> Result { +// match algorithm { +// Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { +// hmac::hmac_verify(algorithm, signature, key.as_bytes(), message) +// } +// Algorithm::ES256 | Algorithm::ES384 => { +// ecdsa::verify(algorithm, signature, message, key.as_bytes()) +// } +// Algorithm::EdDSA => eddsa::verify(signature, message, key.as_bytes()), +// Algorithm::RS256 +// | Algorithm::RS384 +// | Algorithm::RS512 +// | Algorithm::PS256 +// | Algorithm::PS384 +// | Algorithm::PS512 => match &key.kind { +// DecodingKeyKind::SecretOrDer(bytes) => { +// rsa::verify_der(algorithm, signature, message, bytes) +// } +// DecodingKeyKind::RsaModulusExponent { n, e } => { +// rsa::verify_from_components(algorithm, signature, message, (n, e)) +// } +// }, +// } +// } diff --git a/src/encoding.rs b/src/encoding.rs index 26f5c4c3..626cc138 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,12 +2,15 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; -use crate::crypto; +use crate::builder::JwtEncoder; +use crate::crypto::hmac::{HmacSecret, Hs256, Hs384}; +use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; use crate::serialization::b64_encode_part; +use crate::{crypto, Algorithm}; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance. @@ -122,10 +125,27 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R if key.family != header.alg.family() { return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let encoded_header = b64_encode_part(header)?; - let encoded_claims = b64_encode_part(claims)?; - let message = [encoded_header, encoded_claims].join("."); - let signature = crypto::sign(message.as_bytes(), key, header.alg)?; - Ok([message, signature].join(".")) + let jwt_encoder = encoder_factory(&header.alg, key)?; + + jwt_encoder.encode(claims) +} + +fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result { + let jwt_encoder = match algorithm { + Algorithm::HS256 => JwtEncoder::hs_256(HmacSecret::from_secret(&key.content))?, + Algorithm::HS384 => JwtEncoder::hs_384(HmacSecret::from_secret(&key.content))?, + Algorithm::HS512 => todo!(), + Algorithm::ES256 => todo!(), + Algorithm::ES384 => todo!(), + Algorithm::RS256 => todo!(), + Algorithm::RS384 => todo!(), + Algorithm::RS512 => todo!(), + Algorithm::PS256 => todo!(), + Algorithm::PS384 => todo!(), + Algorithm::PS512 => todo!(), + Algorithm::EdDSA => todo!(), + }; + + Ok(jwt_encoder) } diff --git a/src/lib.rs b/src/lib.rs index 0eb22777..34daafee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ #![deny(missing_docs)] pub use algorithms::Algorithm; -pub use decoding::{decode, decode_header, DecodingKey, TokenData}; +// pub use decoding::{decode, decode_header, DecodingKey, TokenData}; pub use encoding::{encode, EncodingKey}; pub use header::Header; pub use validation::{get_current_timestamp, Validation}; @@ -13,7 +13,7 @@ mod algorithms; pub mod builder; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; -mod decoding; +// mod decoding; mod encoding; /// All the errors that can be encountered while encoding/decoding JWTs pub mod errors; From 6a41ba749bc8dee7921838dfa917fd0629d516ff Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:56:41 -0300 Subject: [PATCH 3/9] feat(decoder): Create decoder --- src/builder.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++--- src/decoding.rs | 20 ++++++++ src/encoding.rs | 2 +- src/lib.rs | 4 +- 4 files changed, 136 insertions(+), 10 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index d6b7ad94..da2c8c4a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2,16 +2,17 @@ //! //! - Documentation -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use crate::{ crypto::{ hmac::{HmacSecret, Hs256, Hs384}, - JwtSigner, + JwtSigner, JwtVerifier, }, - errors::{new_error, Result}, - serialization::{b64_encode, b64_encode_part}, - Header, + errors::{new_error, ErrorKind, Result}, + serialization::{b64_encode, b64_encode_part, DecodedJwtPartClaims}, + validation::validate, + Header, TokenData, Validation, }; /// # Todo @@ -44,13 +45,13 @@ impl JwtEncoder { /// # Todo /// /// - Test the the error checking works - pub fn with_header(mut self, header: Header) -> Result { + pub fn with_header(mut self, header: &Header) -> Result { // Check that the header makes use of the correct algorithm if header.alg != self.signing_provider.algorithm() { return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); } - self.header = header; + self.header = header.clone(); Ok(self) } @@ -84,6 +85,111 @@ impl JwtEncoder { } } +/// Takes the result of a rsplit and ensure we only get 2 parts +/// Errors if we don't +macro_rules! expect_two { + ($iter:expr) => {{ + let mut i = $iter; + match (i.next(), i.next(), i.next()) { + (Some(first), Some(second), None) => (first, second), + _ => return Err(new_error(ErrorKind::InvalidToken)), + } + }}; +} + +/// Todo +pub struct JwtDecoder { + verifying_provider: Box, + validation: Validation, +} + +impl JwtDecoder { + /// Todo + pub fn from_verifier(verifying_provider: V) -> Self { + Self::from_boxed_verifiyer(Box::new(verifying_provider)) + } + + /// Todo + pub fn from_boxed_verifiyer(verifying_provider: Box) -> Self { + let validation = Validation::new(verifying_provider.algorithm()); + + Self { verifying_provider, validation } + } + + /// Todo + pub fn with_validation(mut self, validation: &Validation) -> Result { + // Check that the validation contains the correct algorithm + if !validation.algorithms.contains(&self.verifying_provider.algorithm()) { + return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); + } + + self.validation = validation.clone(); + Ok(self) + } + + /// Todo + pub fn decode(&self, token: &str) -> Result> { + let (header, claims) = self.verify_signature(token)?; + + let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; + let claims = decoded_claims.deserialize()?; + validate(decoded_claims.deserialize()?, &self.validation)?; + + Ok(TokenData { header, claims }) + } + + /// Verify signature of a JWT, and return header object and raw payload + /// + /// If the token or its signature is invalid, it will return an error. + fn verify_signature<'a>(&self, token: &'a str) -> Result<(Header, &'a str)> { + if self.validation.validate_signature && self.validation.algorithms.is_empty() { + return Err(new_error(ErrorKind::MissingAlgorithm)); + } + + // Todo: This behaviour is currently not captured anywhere. + // if validation.validate_signature { + // for alg in &validation.algorithms { + // if key.family != alg.family() { + // return Err(new_error(ErrorKind::InvalidAlgorithm)); + // } + // } + // } + + let (signature, message) = expect_two!(token.rsplitn(2, '.')); + let (payload, header) = expect_two!(message.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; + + if self.validation.validate_signature && !self.validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + if self.validation.validate_signature + && self + .verifying_provider + .verify(message.as_bytes(), &signature.as_bytes().to_vec()) + .is_err() + { + return Err(new_error(ErrorKind::InvalidSignature)); + } + + Ok((header, payload)) + } + + /// Create new [`JwtDecoder`] with the `HS256` algorithm. + pub fn hs_256(secret: HmacSecret) -> Result { + let verifying_provider = Box::new(Hs256::new(secret)?); + + Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + } + + /// Create new [`JwtDecoder`] with the `HS384` algorithm. + pub fn hs_384(secret: HmacSecret) -> Result { + let verifying_provider = Box::new(Hs384::new(secret)?); + + Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + } +} + #[cfg(test)] mod builder_tests { diff --git a/src/decoding.rs b/src/decoding.rs index 8d87f03d..3b041201 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,6 +2,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; +use crate::builder::JwtDecoder; use crate::crypto::verify; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; @@ -271,6 +272,25 @@ pub fn decode( } } +fn decoder_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result { + let jwt_encoder = match algorithm { + Algorithm::HS256 => JwtDecoder::hs_256(HmacSecret::from_secret(&key.content))?, + Algorithm::HS384 => JwtDecoder::hs_384(HmacSecret::from_secret(&key.content))?, + Algorithm::HS512 => todo!(), + Algorithm::ES256 => todo!(), + Algorithm::ES384 => todo!(), + Algorithm::RS256 => todo!(), + Algorithm::RS384 => todo!(), + Algorithm::RS512 => todo!(), + Algorithm::PS256 => todo!(), + Algorithm::PS384 => todo!(), + Algorithm::PS512 => todo!(), + Algorithm::EdDSA => todo!(), + }; + + Ok(jwt_encoder) +} + /// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html). /// /// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error. diff --git a/src/encoding.rs b/src/encoding.rs index 626cc138..9443c5e1 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -126,7 +126,7 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let jwt_encoder = encoder_factory(&header.alg, key)?; + let jwt_encoder = encoder_factory(&header.alg, key)?.with_header(header)?; jwt_encoder.encode(claims) } diff --git a/src/lib.rs b/src/lib.rs index 34daafee..0eb22777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ #![deny(missing_docs)] pub use algorithms::Algorithm; -// pub use decoding::{decode, decode_header, DecodingKey, TokenData}; +pub use decoding::{decode, decode_header, DecodingKey, TokenData}; pub use encoding::{encode, EncodingKey}; pub use header::Header; pub use validation::{get_current_timestamp, Validation}; @@ -13,7 +13,7 @@ mod algorithms; pub mod builder; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; -// mod decoding; +mod decoding; mod encoding; /// All the errors that can be encountered while encoding/decoding JWTs pub mod errors; From 66d54be7aa277612db6fef741871ff39a5d4cf9f Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:26:12 -0300 Subject: [PATCH 4/9] test: Get HMAC tests passing --- src/builder.rs | 217 --------------------------------------------- src/crypto/hmac.rs | 44 +++++++++ src/decoding.rs | 179 ++++++++++++++++++++++++++----------- src/encoding.rs | 76 +++++++++++++++- src/lib.rs | 1 - tests/hmac.rs | 44 +++++---- tests/lib.rs | 8 +- 7 files changed, 276 insertions(+), 293 deletions(-) delete mode 100644 src/builder.rs diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index da2c8c4a..00000000 --- a/src/builder.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! # Todo -//! -//! - Documentation - -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{ - crypto::{ - hmac::{HmacSecret, Hs256, Hs384}, - JwtSigner, JwtVerifier, - }, - errors::{new_error, ErrorKind, Result}, - serialization::{b64_encode, b64_encode_part, DecodedJwtPartClaims}, - validation::validate, - Header, TokenData, Validation, -}; - -/// # Todo -/// -/// - Documentation -pub struct JwtEncoder { - signing_provider: Box, - header: Header, -} - -impl JwtEncoder { - /// Todo - pub fn from_signer(signing_provider: S) -> Self { - Self::from_boxed_signer(Box::new(signing_provider)) - } - - /// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait. - pub fn from_boxed_signer(signing_provider: Box) -> Self { - // Determine a default header - let mut header = Header::new(signing_provider.algorithm()); - header.typ = Some("JWT".to_owned()); - - Self { signing_provider, header } - } - - /// Provide a custom header. - /// - /// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated. - /// - /// # Todo - /// - /// - Test the the error checking works - pub fn with_header(mut self, header: &Header) -> Result { - // Check that the header makes use of the correct algorithm - if header.alg != self.signing_provider.algorithm() { - return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); - } - - self.header = header.clone(); - Ok(self) - } - - /// Encode and sign the `claims` as a JWT. - /// - /// # Todo - /// - /// - Put in example usage. - pub fn encode(&self, claims: &T) -> Result { - let encoded_header = b64_encode_part(&self.header)?; - let encoded_claims = b64_encode_part(claims)?; - let message = [encoded_header, encoded_claims].join("."); - - let signature = b64_encode(&self.signing_provider.sign(message.as_bytes())); - - Ok([message, signature].join(".")) - } - - /// Create new [`JwtEncoder`] with the `HS256` algorithm. - pub fn hs_256(secret: HmacSecret) -> Result { - let signing_provider = Box::new(Hs256::new(secret)?); - - Ok(JwtEncoder::from_boxed_signer(signing_provider)) - } - - /// Create new [`JwtEncoder`] with the `HS384` algorithm. - pub fn hs_384(secret: HmacSecret) -> Result { - let signing_provider = Box::new(Hs384::new(secret)?); - - Ok(JwtEncoder::from_boxed_signer(signing_provider)) - } -} - -/// Takes the result of a rsplit and ensure we only get 2 parts -/// Errors if we don't -macro_rules! expect_two { - ($iter:expr) => {{ - let mut i = $iter; - match (i.next(), i.next(), i.next()) { - (Some(first), Some(second), None) => (first, second), - _ => return Err(new_error(ErrorKind::InvalidToken)), - } - }}; -} - -/// Todo -pub struct JwtDecoder { - verifying_provider: Box, - validation: Validation, -} - -impl JwtDecoder { - /// Todo - pub fn from_verifier(verifying_provider: V) -> Self { - Self::from_boxed_verifiyer(Box::new(verifying_provider)) - } - - /// Todo - pub fn from_boxed_verifiyer(verifying_provider: Box) -> Self { - let validation = Validation::new(verifying_provider.algorithm()); - - Self { verifying_provider, validation } - } - - /// Todo - pub fn with_validation(mut self, validation: &Validation) -> Result { - // Check that the validation contains the correct algorithm - if !validation.algorithms.contains(&self.verifying_provider.algorithm()) { - return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); - } - - self.validation = validation.clone(); - Ok(self) - } - - /// Todo - pub fn decode(&self, token: &str) -> Result> { - let (header, claims) = self.verify_signature(token)?; - - let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; - let claims = decoded_claims.deserialize()?; - validate(decoded_claims.deserialize()?, &self.validation)?; - - Ok(TokenData { header, claims }) - } - - /// Verify signature of a JWT, and return header object and raw payload - /// - /// If the token or its signature is invalid, it will return an error. - fn verify_signature<'a>(&self, token: &'a str) -> Result<(Header, &'a str)> { - if self.validation.validate_signature && self.validation.algorithms.is_empty() { - return Err(new_error(ErrorKind::MissingAlgorithm)); - } - - // Todo: This behaviour is currently not captured anywhere. - // if validation.validate_signature { - // for alg in &validation.algorithms { - // if key.family != alg.family() { - // return Err(new_error(ErrorKind::InvalidAlgorithm)); - // } - // } - // } - - let (signature, message) = expect_two!(token.rsplitn(2, '.')); - let (payload, header) = expect_two!(message.rsplitn(2, '.')); - let header = Header::from_encoded(header)?; - - if self.validation.validate_signature && !self.validation.algorithms.contains(&header.alg) { - return Err(new_error(ErrorKind::InvalidAlgorithm)); - } - - if self.validation.validate_signature - && self - .verifying_provider - .verify(message.as_bytes(), &signature.as_bytes().to_vec()) - .is_err() - { - return Err(new_error(ErrorKind::InvalidSignature)); - } - - Ok((header, payload)) - } - - /// Create new [`JwtDecoder`] with the `HS256` algorithm. - pub fn hs_256(secret: HmacSecret) -> Result { - let verifying_provider = Box::new(Hs256::new(secret)?); - - Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) - } - - /// Create new [`JwtDecoder`] with the `HS384` algorithm. - pub fn hs_384(secret: HmacSecret) -> Result { - let verifying_provider = Box::new(Hs384::new(secret)?); - - Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) - } -} - -#[cfg(test)] -mod builder_tests { - - use super::*; - - #[derive(Debug, Serialize)] - struct Claims { - sub: String, - age: u32, - } - - #[test] - fn test_builder() { - // Arrange - let claims = Claims { sub: "123345".to_owned(), age: 25 }; - let secret = HmacSecret::from_secret("test".as_ref()); - - // Act - let jwt = JwtEncoder::hs_256(secret).unwrap().encode(&claims).unwrap(); - - dbg!(&jwt); - - // Assert - } -} diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs index 11a5144a..9ad3046a 100644 --- a/src/crypto/hmac.rs +++ b/src/crypto/hmac.rs @@ -12,6 +12,7 @@ type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; +#[derive(Debug)] pub(crate) struct HmacSecret(Vec); impl HmacSecret { @@ -111,3 +112,46 @@ impl JwtVerifier for Hs384 { Algorithm::HS384 } } + +pub struct Hs512(HmacSha512); + +impl Hs512 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha512::new_from_slice(&secret.0) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Signer> for Hs512 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); + + Ok(signer.finalize().into_bytes().to_vec()) + } +} + +impl JwtSigner for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} + +impl Verifier> for Hs512 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); + + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) + } +} + +impl JwtVerifier for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} diff --git a/src/decoding.rs b/src/decoding.rs index 3b041201..90c19b24 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,15 +2,16 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; -use crate::builder::JwtDecoder; -use crate::crypto::verify; -use crate::errors::{new_error, ErrorKind, Result}; +use crate::crypto::hmac::{HmacSecret, Hs256, Hs384, Hs512}; +use crate::crypto::JwtVerifier; +use crate::errors::{new_error, Error, ErrorKind, Result}; use crate::header::Header; use crate::jwk::{AlgorithmParameters, Jwk}; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, DecodedJwtPartClaims}; use crate::validation::{validate, Validation}; +use crate::Algorithm; /// The return type of a successful call to [decode](fn.decode.html). #[derive(Debug)] @@ -202,41 +203,6 @@ impl DecodingKey { } } -/// Verify signature of a JWT, and return header object and raw payload -/// -/// If the token or its signature is invalid, it will return an error. -fn verify_signature<'a>( - token: &'a str, - key: &DecodingKey, - validation: &Validation, -) -> Result<(Header, &'a str)> { - if validation.validate_signature && validation.algorithms.is_empty() { - return Err(new_error(ErrorKind::MissingAlgorithm)); - } - - if validation.validate_signature { - for alg in &validation.algorithms { - if key.family != alg.family() { - return Err(new_error(ErrorKind::InvalidAlgorithm)); - } - } - } - - let (signature, message) = expect_two!(token.rsplitn(2, '.')); - let (payload, header) = expect_two!(message.rsplitn(2, '.')); - let header = Header::from_encoded(header)?; - - if validation.validate_signature && !validation.algorithms.contains(&header.alg) { - return Err(new_error(ErrorKind::InvalidAlgorithm)); - } - - if validation.validate_signature && !verify(signature, message.as_bytes(), key, header.alg)? { - return Err(new_error(ErrorKind::InvalidSignature)); - } - - Ok((header, payload)) -} - /// Decode and validate a JWT /// /// If the token or its signature is invalid or the claims fail validation, it will return an error. @@ -260,23 +226,23 @@ pub fn decode( key: &DecodingKey, validation: &Validation, ) -> Result> { - match verify_signature(token, key, validation) { - Err(e) => Err(e), - Ok((header, claims)) => { - let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; - let claims = decoded_claims.deserialize()?; - validate(decoded_claims.deserialize()?, validation)?; - - Ok(TokenData { header, claims }) - } + // The decoding step is unfortunately a little clunky but that seems to be how I need to do it to fit it into the `decode` function + let header = decode_header(token)?; + + if validation.validate_signature && !(validation.algorithms.contains(&header.alg)) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); } + + let decoder = decoder_factory(&header.alg, key)?.with_validation(validation)?; + + decoder.decode(token) } fn decoder_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result { let jwt_encoder = match algorithm { - Algorithm::HS256 => JwtDecoder::hs_256(HmacSecret::from_secret(&key.content))?, - Algorithm::HS384 => JwtDecoder::hs_384(HmacSecret::from_secret(&key.content))?, - Algorithm::HS512 => todo!(), + Algorithm::HS256 => JwtDecoder::hs_256(key.try_into()?)?, + Algorithm::HS384 => JwtDecoder::hs_384(key.try_into()?)?, + Algorithm::HS512 => JwtDecoder::hs_512(key.try_into()?)?, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -291,6 +257,20 @@ fn decoder_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result for &DecodingKey { + type Error = Error; + + fn try_into(self) -> std::result::Result { + match self.kind.clone() { + DecodingKeyKind::SecretOrDer(vec) => Ok(HmacSecret::from_secret(&vec)), + DecodingKeyKind::RsaModulusExponent { .. } => { + Err(new_error(crate::errors::ErrorKind::InvalidKeyFormat)) + } + } + } +} + /// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html). /// /// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error. @@ -306,3 +286,102 @@ pub fn decode_header(token: &str) -> Result
{ let (_, header) = expect_two!(message.rsplitn(2, '.')); Header::from_encoded(header) } + +/// Todo +pub struct JwtDecoder { + verifying_provider: Box, + validation: Validation, +} + +impl JwtDecoder { + /// Todo + pub fn from_verifier(verifying_provider: V) -> Self { + Self::from_boxed_verifiyer(Box::new(verifying_provider)) + } + + /// Todo + pub fn from_boxed_verifiyer(verifying_provider: Box) -> Self { + let validation = Validation::new(verifying_provider.algorithm()); + + Self { verifying_provider, validation } + } + + /// Todo + pub fn with_validation(mut self, validation: &Validation) -> Result { + // Check that the validation contains the correct algorithm + if validation.validate_signature + && !validation.algorithms.contains(&self.verifying_provider.algorithm()) + { + return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); + } + + self.validation = validation.clone(); + Ok(self) + } + + /// Todo + pub fn decode(&self, token: &str) -> Result> { + let (header, claims) = self.verify_signature(token)?; + + let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; + let claims = decoded_claims.deserialize()?; + validate(decoded_claims.deserialize()?, &self.validation)?; + + Ok(TokenData { header, claims }) + } + + /// Verify signature of a JWT, and return header object and raw payload + /// + /// If the token or its signature is invalid, it will return an error. + fn verify_signature<'a>(&self, token: &'a str) -> Result<(Header, &'a str)> { + if self.validation.validate_signature && self.validation.algorithms.is_empty() { + return Err(new_error(ErrorKind::MissingAlgorithm)); + } + + // Todo: This behaviour is currently not captured anywhere. + // if validation.validate_signature { + // for alg in &validation.algorithms { + // if key.family != alg.family() { + // return Err(new_error(ErrorKind::InvalidAlgorithm)); + // } + // } + // } + + let (signature, message) = expect_two!(token.rsplitn(2, '.')); + let (payload, header) = expect_two!(message.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; + + if self.validation.validate_signature && !self.validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + if self.validation.validate_signature + && self.verifying_provider.verify(message.as_bytes(), &b64_decode(signature)?).is_err() + { + return Err(new_error(ErrorKind::InvalidSignature)); + } + + Ok((header, payload)) + } + + /// Create new [`JwtDecoder`] with the `HS256` algorithm. + pub fn hs_256(secret: HmacSecret) -> Result { + let verifying_provider = Box::new(Hs256::new(secret)?); + + Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + } + + /// Create new [`JwtDecoder`] with the `HS384` algorithm. + pub fn hs_384(secret: HmacSecret) -> Result { + let verifying_provider = Box::new(Hs384::new(secret)?); + + Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + } + + /// Create new [`JwtDecoder`] with the `HS512` algorithm. + pub fn hs_512(secret: HmacSecret) -> Result { + let verifying_provider = Box::new(Hs512::new(secret)?); + + Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + } +} diff --git a/src/encoding.rs b/src/encoding.rs index 9443c5e1..41f21488 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,15 +2,14 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; -use crate::builder::JwtEncoder; use crate::crypto::hmac::{HmacSecret, Hs256, Hs384}; use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; -use crate::serialization::b64_encode_part; -use crate::{crypto, Algorithm}; +use crate::serialization::{b64_encode, b64_encode_part}; +use crate::Algorithm; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance. @@ -133,6 +132,7 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result { let jwt_encoder = match algorithm { + // Todo: Need to implement `TryInto for &EncodingKey` Algorithm::HS256 => JwtEncoder::hs_256(HmacSecret::from_secret(&key.content))?, Algorithm::HS384 => JwtEncoder::hs_384(HmacSecret::from_secret(&key.content))?, Algorithm::HS512 => todo!(), @@ -149,3 +149,73 @@ fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result, + header: Header, +} + +impl JwtEncoder { + /// Todo + pub fn from_signer(signing_provider: S) -> Self { + Self::from_boxed_signer(Box::new(signing_provider)) + } + + /// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait. + pub fn from_boxed_signer(signing_provider: Box) -> Self { + // Determine a default header + let mut header = Header::new(signing_provider.algorithm()); + header.typ = Some("JWT".to_owned()); + + Self { signing_provider, header } + } + + /// Provide a custom header. + /// + /// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated. + /// + /// # Todo + /// + /// - Test the the error checking works + pub fn with_header(mut self, header: &Header) -> Result { + // Check that the header makes use of the correct algorithm + if header.alg != self.signing_provider.algorithm() { + return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); + } + + self.header = header.clone(); + Ok(self) + } + + /// Encode and sign the `claims` as a JWT. + /// + /// # Todo + /// + /// - Put in example usage. + pub fn encode(&self, claims: &T) -> Result { + let encoded_header = b64_encode_part(&self.header)?; + let encoded_claims = b64_encode_part(claims)?; + let message = [encoded_header, encoded_claims].join("."); + + let signature = b64_encode(&self.signing_provider.sign(message.as_bytes())); + + Ok([message, signature].join(".")) + } + + /// Create new [`JwtEncoder`] with the `HS256` algorithm. + pub fn hs_256(secret: HmacSecret) -> Result { + let signing_provider = Box::new(Hs256::new(secret)?); + + Ok(JwtEncoder::from_boxed_signer(signing_provider)) + } + + /// Create new [`JwtEncoder`] with the `HS384` algorithm. + pub fn hs_384(secret: HmacSecret) -> Result { + let signing_provider = Box::new(Hs384::new(secret)?); + + Ok(JwtEncoder::from_boxed_signer(signing_provider)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0eb22777..2f936c8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ pub use header::Header; pub use validation::{get_current_timestamp, Validation}; mod algorithms; -pub mod builder; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; mod decoding; diff --git a/tests/hmac.rs b/tests/hmac.rs index ec24e6e7..8d02c997 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -5,8 +5,15 @@ use wasm_bindgen_test::wasm_bindgen_test; use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::jwk::Jwk; use jsonwebtoken::{ - crypto::{sign, verify}, - decode, decode_header, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, + // crypto::{sign, verify}, + decode, + decode_header, + encode, + Algorithm, + DecodingKey, + EncodingKey, + Header, + Validation, }; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -16,23 +23,23 @@ pub struct Claims { exp: i64, } -#[test] -#[wasm_bindgen_test] -fn sign_hs256() { - let result = - sign(b"hello world", &EncodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap(); - let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - assert_eq!(result, expected); -} +// #[test] +// #[wasm_bindgen_test] +// fn sign_hs256() { +// let result = +// sign(b"hello world", &EncodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap(); +// let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; +// assert_eq!(result, expected); +// } -#[test] -#[wasm_bindgen_test] -fn verify_hs256() { - let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - let valid = verify(sig, b"hello world", &DecodingKey::from_secret(b"secret"), Algorithm::HS256) - .unwrap(); - assert!(valid); -} +// #[test] +// #[wasm_bindgen_test] +// fn verify_hs256() { +// let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; +// let valid = verify(sig, b"hello world", &DecodingKey::from_secret(b"secret"), Algorithm::HS256) +// .unwrap(); +// assert!(valid); +// } #[test] #[wasm_bindgen_test] @@ -44,6 +51,7 @@ fn encode_with_custom_header() { }; let header = Header { kid: Some("kid".to_string()), ..Default::default() }; let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); + let token_data = decode::( &token, &DecodingKey::from_secret(b"secret"), diff --git a/tests/lib.rs b/tests/lib.rs index 89661df9..c5374c72 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,4 @@ -mod ecdsa; -mod eddsa; -mod header; -mod rsa; +// mod ecdsa; +// mod eddsa; +// mod header; +// mod rsa; From 088606440e6c346c4ce4a3b1f00fb4ff4997c847 Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Wed, 2 Oct 2024 22:14:22 -0300 Subject: [PATCH 5/9] docs: Neaten up docstrings --- src/crypto/hmac.rs | 6 +++- src/crypto/mod.rs | 71 ++++-------------------------------- src/decoding.rs | 56 ++++++++++++++++++++++------- src/encoding.rs | 90 +++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 5 +-- 5 files changed, 133 insertions(+), 95 deletions(-) diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs index 9ad3046a..824c5b41 100644 --- a/src/crypto/hmac.rs +++ b/src/crypto/hmac.rs @@ -1,3 +1,6 @@ +//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the +//! HMAC family of algorithms. + use base64::{engine::general_purpose::STANDARD, Engine}; use hmac::{Hmac, Mac}; use sha2::{Sha256, Sha384, Sha512}; @@ -12,8 +15,9 @@ type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; +/// The shared secret used for the HMAC family of algorithms. #[derive(Debug)] -pub(crate) struct HmacSecret(Vec); +pub struct HmacSecret(Vec); impl HmacSecret { /// If you're using an HMAC secret that is not base64, use that. diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 4317a780..f4b456b4 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,12 +1,13 @@ +//! The cryptography of the `jsonwebtoken` crate is decoupled behind +//! [`JwtSigner`] and [`JwtVerifier`] traits. These make use of `RustCrypto`'s +//! [`Signer`] and [`Verifier`] traits respectively. + use crate::algorithms::Algorithm; -// use crate::decoding::{DecodingKey, DecodingKeyKind}; -// use crate::encoding::EncodingKey; -// use crate::errors::Result; -pub(crate) mod ecdsa; -pub(crate) mod eddsa; +// pub(crate) mod ecdsa; +// pub(crate) mod eddsa; pub(crate) mod hmac; -pub(crate) mod rsa; +// pub(crate) mod rsa; use signature::{Signer, Verifier}; @@ -25,61 +26,3 @@ pub trait JwtVerifier: Verifier> { /// Return the [`Algorithm`] corresponding to the signing module. fn algorithm(&self) -> Algorithm; } - -// /// Take the payload of a JWT, sign it using the algorithm given and return -// /// the base64 url safe encoded of the result. -// /// -// /// If you just want to encode a JWT, use `encode` instead. -// pub fn sign(message: &[u8], key: &EncodingKey, algorithm: Algorithm) -> Result { -// match algorithm { -// Algorithm::ES256 | Algorithm::ES384 => ecdsa::sign(algorithm, key.inner(), message), -// Algorithm::EdDSA => eddsa::sign(key.inner(), message), -// Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { -// hmac::sign_hmac(algorithm, key.inner(), message) -// } -// Algorithm::RS256 -// | Algorithm::RS384 -// | Algorithm::RS512 -// | Algorithm::PS256 -// | Algorithm::PS384 -// | Algorithm::PS512 => rsa::sign(algorithm, key.inner(), message), -// } -// } - -// /// Compares the signature given with a re-computed signature for HMAC or using the public key -// /// for RSA/EC. -// /// -// /// If you just want to decode a JWT, use `decode` instead. -// /// -// /// `signature` is the signature part of a jwt (text after the second '.') -// /// -// /// `message` is base64(header) + "." + base64(claims) -// pub fn verify( -// signature: &str, -// message: &[u8], -// key: &DecodingKey, -// algorithm: Algorithm, -// ) -> Result { -// match algorithm { -// Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { -// hmac::hmac_verify(algorithm, signature, key.as_bytes(), message) -// } -// Algorithm::ES256 | Algorithm::ES384 => { -// ecdsa::verify(algorithm, signature, message, key.as_bytes()) -// } -// Algorithm::EdDSA => eddsa::verify(signature, message, key.as_bytes()), -// Algorithm::RS256 -// | Algorithm::RS384 -// | Algorithm::RS512 -// | Algorithm::PS256 -// | Algorithm::PS384 -// | Algorithm::PS512 => match &key.kind { -// DecodingKeyKind::SecretOrDer(bytes) => { -// rsa::verify_der(algorithm, signature, message, bytes) -// } -// DecodingKeyKind::RsaModulusExponent { n, e } => { -// rsa::verify_from_components(algorithm, signature, message, (n, e)) -// } -// }, -// } -// } diff --git a/src/decoding.rs b/src/decoding.rs index 90c19b24..9e9d7648 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -194,13 +194,6 @@ impl DecodingKey { } } } - - pub(crate) fn as_bytes(&self) -> &[u8] { - match &self.kind { - DecodingKeyKind::SecretOrDer(b) => b, - DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), - } - } } /// Decode and validate a JWT @@ -238,6 +231,7 @@ pub fn decode( decoder.decode(token) } +/// Return the correct decoder based on the `algorithm`. fn decoder_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result { let jwt_encoder = match algorithm { Algorithm::HS256 => JwtDecoder::hs_256(key.try_into()?)?, @@ -287,26 +281,64 @@ pub fn decode_header(token: &str) -> Result
{ Header::from_encoded(header) } -/// Todo +/// A builder style JWT decoder +/// +/// # Examples +/// +/// ``` +/// use jsonwebtoken::{JwtDecoder, HmacSecret}; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String, +/// exp: usize, +/// } +/// +/// let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; +/// +/// let hmac_secret = HmacSecret::from_secret(b"secret"); +/// +/// let claims = JwtDecoder::hs_256(hmac_secret) +/// .unwrap() +/// .decode::(&token) +/// .unwrap(); +/// ``` pub struct JwtDecoder { verifying_provider: Box, validation: Validation, } impl JwtDecoder { - /// Todo + /// Create a new [`JwtDecoder`] with any `verifying_provider` that implements the [`JwtVerifier`] trait. pub fn from_verifier(verifying_provider: V) -> Self { Self::from_boxed_verifiyer(Box::new(verifying_provider)) } - /// Todo + /// Create a new [`JwtDecoder`] with any `verifying_provider` implements the [`JwtVerifier`] trait. pub fn from_boxed_verifiyer(verifying_provider: Box) -> Self { let validation = Validation::new(verifying_provider.algorithm()); Self { verifying_provider, validation } } - /// Todo + /// Provide custom a custom validation configuration. + /// + /// # Examples + /// + /// ``` + /// use jsonwebtoken::{JwtDecoder, HmacSecret, Validation, Algorithm}; + /// + /// let hmac_secret = HmacSecret::from_secret(b"secret"); + /// let mut validation = Validation::new(Algorithm::HS256); + /// validation.leeway = 5; + /// + /// let jwt_decoder = JwtDecoder::hs_256(hmac_secret) + /// .unwrap() + /// .with_validation(&validation) + /// .unwrap(); + /// ``` pub fn with_validation(mut self, validation: &Validation) -> Result { // Check that the validation contains the correct algorithm if validation.validate_signature @@ -319,7 +351,7 @@ impl JwtDecoder { Ok(self) } - /// Todo + /// Decode and verify a JWT `token` using the `verifying_provider` and `validation` of the [`JwtDecoder`] pub fn decode(&self, token: &str) -> Result> { let (header, claims) = self.verify_signature(token)?; diff --git a/src/encoding.rs b/src/encoding.rs index 41f21488..9e7558d7 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,7 +2,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; -use crate::crypto::hmac::{HmacSecret, Hs256, Hs384}; +use crate::crypto::hmac::{HmacSecret, Hs256, Hs384, Hs512}; use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; @@ -130,12 +130,12 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R jwt_encoder.encode(claims) } +/// Return the correct [`JwtEncoder`] based on the `algorithm`. fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result { let jwt_encoder = match algorithm { - // Todo: Need to implement `TryInto for &EncodingKey` - Algorithm::HS256 => JwtEncoder::hs_256(HmacSecret::from_secret(&key.content))?, - Algorithm::HS384 => JwtEncoder::hs_384(HmacSecret::from_secret(&key.content))?, - Algorithm::HS512 => todo!(), + Algorithm::HS256 => JwtEncoder::hs_256(key.into())?, + Algorithm::HS384 => JwtEncoder::hs_384(key.into())?, + Algorithm::HS512 => JwtEncoder::hs_512(key.into())?, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -150,21 +150,51 @@ fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result for HmacSecret { + fn from(key: &EncodingKey) -> Self { + HmacSecret::from_secret(&key.content) + } +} + +/// A builder style JWT encoder. +/// +/// # Examples +/// +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use jsonwebtoken::{JwtEncoder, HmacSecret}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String +/// } +/// +/// let my_claims = Claims { +/// sub: "b@b.com".to_owned(), +/// company: "ACME".to_owned() +/// }; /// -/// - Documentation +/// let hmac_secret = HmacSecret::from_secret(b"secret"); +/// +/// let token = JwtEncoder::hs_256(hmac_secret) +/// .unwrap() +/// .encode(&my_claims) +/// .unwrap(); +/// ``` pub struct JwtEncoder { signing_provider: Box, header: Header, } impl JwtEncoder { - /// Todo + /// Create a new [`JwtEncoder`] with any `signing_provider` that implements the [`JwtSigner`] trait. pub fn from_signer(signing_provider: S) -> Self { Self::from_boxed_signer(Box::new(signing_provider)) } - /// Create a new [`JwtEncoder`] with any crypto provider that implements the [`CryptoProvider`] trait. + /// Create a new [`JwtEncoder`] with any `signing_provider` that implements the [`JwtSigner`] trait. pub fn from_boxed_signer(signing_provider: Box) -> Self { // Determine a default header let mut header = Header::new(signing_provider.algorithm()); @@ -177,9 +207,34 @@ impl JwtEncoder { /// /// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated. /// - /// # Todo + /// # Examples + /// + /// ``` + /// use serde::{Deserialize, Serialize}; + /// use jsonwebtoken::{JwtEncoder, HmacSecret, Header, Algorithm}; + /// + /// #[derive(Debug, Serialize, Deserialize)] + /// struct Claims { + /// sub: String, + /// company: String + /// } + /// + /// let my_claims = Claims { + /// sub: "b@b.com".to_owned(), + /// company: "ACME".to_owned() + /// }; /// - /// - Test the the error checking works + /// let hmac_secret = HmacSecret::from_secret(b"secret"); + /// let mut header = Header::new(Algorithm::HS256); + /// header.cty = Some("content-type".to_owned()); + /// + /// let token = JwtEncoder::hs_256(hmac_secret) + /// .unwrap() + /// .with_header(&header) + /// .unwrap() + /// .encode(&my_claims) + /// .unwrap(); + /// ``` pub fn with_header(mut self, header: &Header) -> Result { // Check that the header makes use of the correct algorithm if header.alg != self.signing_provider.algorithm() { @@ -190,11 +245,7 @@ impl JwtEncoder { Ok(self) } - /// Encode and sign the `claims` as a JWT. - /// - /// # Todo - /// - /// - Put in example usage. + /// Encode and sign the `claims` as a JWT using the `signing_provider` of the [`JwtEncoder`]. pub fn encode(&self, claims: &T) -> Result { let encoded_header = b64_encode_part(&self.header)?; let encoded_claims = b64_encode_part(claims)?; @@ -218,4 +269,11 @@ impl JwtEncoder { Ok(JwtEncoder::from_boxed_signer(signing_provider)) } + + /// Create new [`JwtEncoder`] with the `HS512` algorithm. + pub fn hs_512(secret: HmacSecret) -> Result { + let signing_provider = Box::new(Hs512::new(secret)?); + + Ok(JwtEncoder::from_boxed_signer(signing_provider)) + } } diff --git a/src/lib.rs b/src/lib.rs index 2f936c8d..0d6b3be7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,9 @@ #![deny(missing_docs)] pub use algorithms::Algorithm; -pub use decoding::{decode, decode_header, DecodingKey, TokenData}; -pub use encoding::{encode, EncodingKey}; +pub use crypto::hmac::HmacSecret; +pub use decoding::{decode, decode_header, DecodingKey, JwtDecoder, TokenData}; +pub use encoding::{encode, EncodingKey, JwtEncoder}; pub use header::Header; pub use validation::{get_current_timestamp, Validation}; From 337f9ed5d6cf1e0e2f3d059aa34c9f6a3b55a609 Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:04:05 -0300 Subject: [PATCH 6/9] feat(crypto): Implement JwtSigner and JwtVerifier for aws-lc-rs --- Cargo.toml | 12 ++- src/crypto/aws_lc/hmac.rs | 105 ++++++++++++++++++++++++ src/crypto/aws_lc/mod.rs | 1 + src/crypto/ecdsa.rs | 74 ----------------- src/crypto/eddsa.rs | 29 ------- src/crypto/hmac.rs | 144 ++------------------------------- src/crypto/mod.rs | 7 +- src/crypto/rsa.rs | 115 -------------------------- src/crypto/rust_crypto/hmac.rs | 143 ++++++++++++++++++++++++++++++++ src/crypto/rust_crypto/mod.rs | 1 + src/decoding.rs | 8 +- src/encoding.rs | 8 +- src/lib.rs | 5 ++ 13 files changed, 290 insertions(+), 362 deletions(-) create mode 100644 src/crypto/aws_lc/hmac.rs create mode 100644 src/crypto/aws_lc/mod.rs delete mode 100644 src/crypto/ecdsa.rs delete mode 100644 src/crypto/eddsa.rs delete mode 100644 src/crypto/rsa.rs create mode 100644 src/crypto/rust_crypto/hmac.rs create mode 100644 src/crypto/rust_crypto/mod.rs diff --git a/Cargo.toml b/Cargo.toml index b31f72ff..7d228aec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ base64 = "0.22" pem = { version = "3", optional = true } simple_asn1 = { version = "0.6", optional = true } -hmac = "0.12.1" rsa = "0.9.6" sha2 = { version = "0.10.7", features = ["oid"] } getrandom = { version = "0.2.10", features = ["js"] } @@ -37,6 +36,13 @@ p256 = { version = "0.13.2", features = ["ecdsa"] } p384 = { version = "0.13.0", features = ["ecdsa"] } rand_core = "0.6.4" signature = "2.2.0" + +# "rust_crypto" feature +hmac = { version = "0.12.1", optional = true } + +# "aws_lc_rs" feature +aws-lc-rs = { version = "1.10.0", optional = true } + [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" @@ -54,8 +60,10 @@ time = { version = "0.3", features = ["wasm-bindgen"] } criterion = { version = "0.4", default-features = false } [features] -default = ["use_pem"] +default = ["use_pem", "rust_crypto"] use_pem = ["pem", "simple_asn1", 'p256/pem', 'p384/pem'] +rust_crypto = ["hmac"] +aws_lc_rs = ["aws-lc-rs"] [[bench]] name = "jwt" diff --git a/src/crypto/aws_lc/hmac.rs b/src/crypto/aws_lc/hmac.rs new file mode 100644 index 00000000..00b3570b --- /dev/null +++ b/src/crypto/aws_lc/hmac.rs @@ -0,0 +1,105 @@ +//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the +//! HMAC family of algorithms using [`aws_lc_rs`] + +use aws_lc_rs::hmac; +use signature::{Signer, Verifier}; + +use crate::crypto::{JwtSigner, JwtVerifier}; +use crate::errors::Result; +use crate::{Algorithm, HmacSecret}; + +pub struct Hs256(hmac::Key); + +impl Hs256 { + pub(crate) fn new(secret: HmacSecret) -> Result { + Ok(Self(hmac::Key::new(hmac::HMAC_SHA256, &secret))) + } +} + +impl Signer> for Hs256 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) + } +} + +impl JwtSigner for Hs256 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +impl Verifier> for Hs256 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) + } +} + +impl JwtVerifier for Hs256 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +pub struct Hs384(hmac::Key); + +impl Hs384 { + pub(crate) fn new(secret: HmacSecret) -> Result { + Ok(Self(hmac::Key::new(hmac::HMAC_SHA384, &secret))) + } +} + +impl Signer> for Hs384 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) + } +} + +impl JwtSigner for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 + } +} + +impl Verifier> for Hs384 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) + } +} + +impl JwtVerifier for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 + } +} + +pub struct Hs512(hmac::Key); + +impl Hs512 { + pub(crate) fn new(secret: HmacSecret) -> Result { + Ok(Self(hmac::Key::new(hmac::HMAC_SHA512, &secret))) + } +} + +impl Signer> for Hs512 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) + } +} + +impl JwtSigner for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} + +impl Verifier> for Hs512 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) + } +} + +impl JwtVerifier for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} diff --git a/src/crypto/aws_lc/mod.rs b/src/crypto/aws_lc/mod.rs new file mode 100644 index 00000000..3a812e97 --- /dev/null +++ b/src/crypto/aws_lc/mod.rs @@ -0,0 +1 @@ +pub(crate) mod hmac; diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs deleted file mode 100644 index df7190c1..00000000 --- a/src/crypto/ecdsa.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::algorithms::Algorithm; -use crate::errors::Result; -use crate::serialization::{b64_decode, b64_encode}; - -/// The actual ECDSA signing + encoding -/// The key needs to be in PKCS8 format -pub(crate) fn sign(alg: Algorithm, key: &[u8], message: &[u8]) -> Result { - match alg { - Algorithm::ES256 => es256_sign(key, message), - Algorithm::ES384 => es384_sign(key, message), - - _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), - } -} - -fn es256_sign(key: &[u8], message: &[u8]) -> Result { - use p256::ecdsa::signature::Signer; - use p256::ecdsa::{Signature, SigningKey}; - use p256::pkcs8::DecodePrivateKey; - use p256::SecretKey; - let secret_key = - SecretKey::from_pkcs8_der(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?; - let signing_key: SigningKey = secret_key.into(); - - let signature: Signature = signing_key.sign(message); - let bytes = signature.to_bytes(); - Ok(b64_encode(bytes)) -} - -fn es384_sign(key: &[u8], message: &[u8]) -> Result { - use p384::ecdsa::signature::Signer; - use p384::ecdsa::{Signature, SigningKey}; - use p384::pkcs8::DecodePrivateKey; - use p384::SecretKey; - let secret_key = - SecretKey::from_pkcs8_der(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?; - let signing_key: SigningKey = secret_key.into(); - let signature: Signature = signing_key.sign(message); - let bytes = signature.to_bytes(); - Ok(b64_encode(bytes)) -} - -pub(crate) fn verify(alg: Algorithm, signature: &str, message: &[u8], key: &[u8]) -> Result { - match alg { - Algorithm::ES256 => es256_verify(signature, message, key), - Algorithm::ES384 => es384_verify(signature, message, key), - _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), - } -} - -fn es384_verify(signature: &str, message: &[u8], key: &[u8]) -> Result { - use p384::ecdsa::signature::Verifier; - use p384::ecdsa::{Signature, VerifyingKey}; - use p384::PublicKey; - - let public_key = - PublicKey::from_sec1_bytes(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?; - let verifying_key: VerifyingKey = public_key.into(); - let signature = Signature::from_slice(&b64_decode(signature)?) - .map_err(|_e| crate::errors::ErrorKind::InvalidSignature)?; - Ok(verifying_key.verify(message, &signature).is_ok()) -} - -fn es256_verify(signature: &str, message: &[u8], key: &[u8]) -> Result { - use p256::ecdsa::signature::Verifier; - use p256::ecdsa::{Signature, VerifyingKey}; - use p256::PublicKey; - let public_key = - PublicKey::from_sec1_bytes(key).map_err(|_e| crate::errors::ErrorKind::InvalidEcdsaKey)?; - let verifying_key: VerifyingKey = public_key.into(); - let signature = Signature::from_slice(&b64_decode(signature)?) - .map_err(|_e| crate::errors::ErrorKind::InvalidSignature)?; - Ok(verifying_key.verify(message, &signature).is_ok()) -} diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs deleted file mode 100644 index 3da4fb5a..00000000 --- a/src/crypto/eddsa.rs +++ /dev/null @@ -1,29 +0,0 @@ -use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; - -use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{b64_decode, b64_encode}; - -fn parse_key(key: &[u8]) -> Result { - let key = key.try_into().map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?; - let signing_key = SigningKey::from_bytes(key); - Ok(signing_key) -} - -pub(crate) fn verify(signature: &str, message: &[u8], key: &[u8]) -> Result { - let signature = b64_decode(signature)?; - let signature = - Signature::from_slice(&signature).map_err(|_e| new_error(ErrorKind::InvalidSignature))?; - let key = key.try_into().map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?; - let verifying_key = - VerifyingKey::from_bytes(key).map_err(|_| new_error(ErrorKind::InvalidEddsaKey))?; - Ok(verifying_key.verify(message, &signature).is_ok()) -} - -/// The actual EdDSA signing + encoding -/// The key needs to be in PKCS8 format -pub fn sign(key: &[u8], message: &[u8]) -> Result { - let key = key[16..].into(); - let signing_key = parse_key(key)?; - let out = signing_key.sign(message); - Ok(b64_encode(out.to_bytes())) -} diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs index 824c5b41..8467f2dd 100644 --- a/src/crypto/hmac.rs +++ b/src/crypto/hmac.rs @@ -1,19 +1,10 @@ -//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the -//! HMAC family of algorithms. +//! Common HMAC related functionality. + +use std::ops::Deref; use base64::{engine::general_purpose::STANDARD, Engine}; -use hmac::{Hmac, Mac}; -use sha2::{Sha256, Sha384, Sha512}; -use signature::{Signer, Verifier}; use crate::errors::Result; -use crate::Algorithm; - -use super::{JwtSigner, JwtVerifier}; - -type HmacSha256 = Hmac; -type HmacSha384 = Hmac; -type HmacSha512 = Hmac; /// The shared secret used for the HMAC family of algorithms. #[derive(Debug)] @@ -31,131 +22,10 @@ impl HmacSecret { } } -pub struct Hs256(HmacSha256); - -impl Hs256 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha256::new_from_slice(&secret.0) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - - Ok(Self(inner)) - } -} - -impl Signer> for Hs256 { - fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { - let mut signer = self.0.clone(); - signer.reset(); - signer.update(msg); - - Ok(signer.finalize().into_bytes().to_vec()) - } -} - -impl JwtSigner for Hs256 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS256 - } -} - -impl Verifier> for Hs256 { - fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { - let mut verifier = self.0.clone(); - verifier.reset(); - verifier.update(msg); - - verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) - } -} - -impl JwtVerifier for Hs256 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS256 - } -} - -pub(crate) struct Hs384(HmacSha384); - -impl Hs384 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha384::new_from_slice(&secret.0) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - - Ok(Self(inner)) - } -} - -impl Signer> for Hs384 { - fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { - let mut signer = self.0.clone(); - signer.reset(); - signer.update(msg); - - Ok(signer.finalize().into_bytes().to_vec()) - } -} - -impl JwtSigner for Hs384 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS384 - } -} - -impl Verifier> for Hs384 { - fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { - let mut verifier = self.0.clone(); - verifier.reset(); - verifier.update(msg); - - verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) - } -} - -impl JwtVerifier for Hs384 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS384 - } -} - -pub struct Hs512(HmacSha512); - -impl Hs512 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha512::new_from_slice(&secret.0) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; - - Ok(Self(inner)) - } -} - -impl Signer> for Hs512 { - fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { - let mut signer = self.0.clone(); - signer.reset(); - signer.update(msg); - - Ok(signer.finalize().into_bytes().to_vec()) - } -} - -impl JwtSigner for Hs512 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS512 - } -} - -impl Verifier> for Hs512 { - fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { - let mut verifier = self.0.clone(); - verifier.reset(); - verifier.update(msg); - - verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) - } -} +impl Deref for HmacSecret { + type Target = Vec; -impl JwtVerifier for Hs512 { - fn algorithm(&self) -> Algorithm { - Algorithm::HS512 + fn deref(&self) -> &Self::Target { + &self.0 } } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index f4b456b4..15d9f479 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -4,10 +4,11 @@ use crate::algorithms::Algorithm; -// pub(crate) mod ecdsa; -// pub(crate) mod eddsa; +#[cfg(feature = "aws_lc_rs")] +pub(crate) mod aws_lc; pub(crate) mod hmac; -// pub(crate) mod rsa; +#[cfg(feature = "rust_crypto")] +pub(crate) mod rust_crypto; use signature::{Signer, Verifier}; diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs deleted file mode 100644 index 7d13411e..00000000 --- a/src/crypto/rsa.rs +++ /dev/null @@ -1,115 +0,0 @@ -use rsa::{ - pkcs1::DecodeRsaPrivateKey, pkcs1::DecodeRsaPublicKey, pss::Pss, traits::SignatureScheme, - BigUint, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, -}; -use sha2::{Digest, Sha256, Sha384, Sha512}; - -use crate::algorithms::Algorithm; -use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{b64_decode, b64_encode}; - -fn alg_to_pss(alg: Algorithm, digest_len: usize) -> Option { - match alg { - Algorithm::PS256 => Some(Pss::new_with_salt::(digest_len)), - Algorithm::PS384 => Some(Pss::new_with_salt::(digest_len)), - Algorithm::PS512 => Some(Pss::new_with_salt::(digest_len)), - _ => None, - } -} - -fn alg_to_pkcs1_v15(alg: Algorithm) -> Option { - match alg { - Algorithm::RS256 => Some(Pkcs1v15Sign::new::()), - Algorithm::RS384 => Some(Pkcs1v15Sign::new::()), - Algorithm::RS512 => Some(Pkcs1v15Sign::new::()), - _ => None, - } -} - -fn message_digest(alg: Algorithm, message: &[u8]) -> Result> { - match alg { - Algorithm::RS256 | Algorithm::PS256 => { - let mut hasher = Sha256::new(); - hasher.update(message); - let d = hasher.finalize(); - Ok(d.as_slice().to_vec()) - } - Algorithm::RS384 | Algorithm::PS384 => { - let mut hasher = Sha384::new(); - hasher.update(message); - let d = hasher.finalize(); - Ok(d.as_slice().to_vec()) - } - Algorithm::RS512 | Algorithm::PS512 => { - let mut hasher = Sha512::new(); - hasher.update(message); - let d = hasher.finalize(); - Ok(d.as_slice().to_vec()) - } - _ => Err(new_error(ErrorKind::InvalidAlgorithm)), - } -} - -pub(crate) fn sign(alg: Algorithm, key: &[u8], message: &[u8]) -> Result { - let digest = message_digest(alg, message)?; - let signatures_scheme_pkcs = alg_to_pkcs1_v15(alg); - let signatures_scheme_pss = alg_to_pss(alg, digest.len()); - let private_key = - RsaPrivateKey::from_pkcs1_der(key).map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - let mut rng = rand::thread_rng(); - let signature = if let Some(signatures_scheme) = signatures_scheme_pkcs { - signatures_scheme - .sign(Some(&mut rng), &private_key, &digest) - .map_err(|_e| ErrorKind::RsaFailedSigning)? - } else if let Some(signatures_scheme) = signatures_scheme_pss { - signatures_scheme - .sign(Some(&mut rng), &private_key, &digest) - .map_err(|_e| ErrorKind::RsaFailedSigning)? - } else { - return Err(new_error(ErrorKind::InvalidAlgorithmName)); - }; - Ok(b64_encode(signature)) -} - -pub(crate) fn verify_from_components( - alg: Algorithm, - signature: &str, - message: &[u8], - components: (&[u8], &[u8]), -) -> Result { - let n = BigUint::from_bytes_be(components.0); - let e = BigUint::from_bytes_be(components.1); - let pub_key = RsaPublicKey::new(n, e).map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - - verify(alg, signature, message, &pub_key) -} - -fn verify(alg: Algorithm, signature: &str, message: &[u8], pub_key: &RsaPublicKey) -> Result { - let signature_bytes = b64_decode(signature)?; - let digest = message_digest(alg, message)?; - let signatures_scheme_pkcs = alg_to_pkcs1_v15(alg); - let signatures_scheme_pss = alg_to_pss(alg, digest.len()); - if let Some(signatures_scheme) = signatures_scheme_pkcs { - signatures_scheme - .verify(pub_key, &digest, &signature_bytes) - .map_err(|_e| ErrorKind::InvalidSignature)?; - } else if let Some(signatures_scheme) = signatures_scheme_pss { - signatures_scheme - .verify(pub_key, &digest, &signature_bytes) - .map_err(|_e| ErrorKind::InvalidSignature)?; - } else { - return Err(new_error(ErrorKind::InvalidAlgorithmName)); - }; - Ok(true) -} - -pub(crate) fn verify_der( - alg: Algorithm, - signature: &str, - message: &[u8], - bytes: &[u8], -) -> Result { - let pub_key = - RsaPublicKey::from_pkcs1_der(bytes).map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - verify(alg, signature, message, &pub_key) -} diff --git a/src/crypto/rust_crypto/hmac.rs b/src/crypto/rust_crypto/hmac.rs new file mode 100644 index 00000000..a29f5c68 --- /dev/null +++ b/src/crypto/rust_crypto/hmac.rs @@ -0,0 +1,143 @@ +//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the +//! HMAC family of algorithms using `RustCtypto`'s [`hmac`]. + +use hmac::{Hmac, Mac}; +use sha2::{Sha256, Sha384, Sha512}; +use signature::{Signer, Verifier}; + +use crate::crypto::{JwtSigner, JwtVerifier}; +use crate::errors::Result; +use crate::{Algorithm, HmacSecret}; + +type HmacSha256 = Hmac; +type HmacSha384 = Hmac; +type HmacSha512 = Hmac; + +pub struct Hs256(HmacSha256); + +impl Hs256 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha256::new_from_slice(&secret) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Signer> for Hs256 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); + + Ok(signer.finalize().into_bytes().to_vec()) + } +} + +impl JwtSigner for Hs256 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +impl Verifier> for Hs256 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); + + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) + } +} + +impl JwtVerifier for Hs256 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS256 + } +} + +pub(crate) struct Hs384(HmacSha384); + +impl Hs384 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha384::new_from_slice(&secret) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Signer> for Hs384 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); + + Ok(signer.finalize().into_bytes().to_vec()) + } +} + +impl JwtSigner for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 + } +} + +impl Verifier> for Hs384 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); + + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) + } +} + +impl JwtVerifier for Hs384 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS384 + } +} + +pub struct Hs512(HmacSha512); + +impl Hs512 { + pub(crate) fn new(secret: HmacSecret) -> Result { + let inner = HmacSha512::new_from_slice(&secret) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Signer> for Hs512 { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + let mut signer = self.0.clone(); + signer.reset(); + signer.update(msg); + + Ok(signer.finalize().into_bytes().to_vec()) + } +} + +impl JwtSigner for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} + +impl Verifier> for Hs512 { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + let mut verifier = self.0.clone(); + verifier.reset(); + verifier.update(msg); + + verifier.verify_slice(signature).map_err(|e| signature::Error::from_source(e)) + } +} + +impl JwtVerifier for Hs512 { + fn algorithm(&self) -> Algorithm { + Algorithm::HS512 + } +} diff --git a/src/crypto/rust_crypto/mod.rs b/src/crypto/rust_crypto/mod.rs new file mode 100644 index 00000000..3a812e97 --- /dev/null +++ b/src/crypto/rust_crypto/mod.rs @@ -0,0 +1 @@ +pub(crate) mod hmac; diff --git a/src/decoding.rs b/src/decoding.rs index 9e9d7648..5f62d520 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,7 +2,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; -use crate::crypto::hmac::{HmacSecret, Hs256, Hs384, Hs512}; +use crate::crypto::hmac::HmacSecret; use crate::crypto::JwtVerifier; use crate::errors::{new_error, Error, ErrorKind, Result}; use crate::header::Header; @@ -13,6 +13,12 @@ use crate::serialization::{b64_decode, DecodedJwtPartClaims}; use crate::validation::{validate, Validation}; use crate::Algorithm; +// Crypto +#[cfg(feature = "aws_lc_rs")] +use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; +#[cfg(feature = "rust_crypto")] +use crate::crypto::rust_crypto::hmac::{Hs256, Hs384, Hs512}; + /// The return type of a successful call to [decode](fn.decode.html). #[derive(Debug)] pub struct TokenData { diff --git a/src/encoding.rs b/src/encoding.rs index 9e7558d7..96dc6dce 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,7 +2,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; -use crate::crypto::hmac::{HmacSecret, Hs256, Hs384, Hs512}; +use crate::crypto::hmac::HmacSecret; use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; @@ -11,6 +11,12 @@ use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_encode, b64_encode_part}; use crate::Algorithm; +// Crypto +#[cfg(feature = "aws_lc_rs")] +use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; +#[cfg(feature = "rust_crypto")] +use crate::crypto::rust_crypto::hmac::{Hs256, Hs384, Hs512}; + /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance. #[derive(Clone)] diff --git a/src/lib.rs b/src/lib.rs index 0d6b3be7..4d18c704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,11 @@ //! Documentation: [stable](https://docs.rs/jsonwebtoken/) #![deny(missing_docs)] +#[cfg(all(feature = "rust_crypto", feature = "aws_lc_rs"))] +compile_error!( + "feature \"rust_crypto\" and feature \"aws_lc_rs\" cannot be enabled at the same time" +); + pub use algorithms::Algorithm; pub use crypto::hmac::HmacSecret; pub use decoding::{decode, decode_header, DecodingKey, JwtDecoder, TokenData}; From a0431d898d32a629743393c74222b1f7b5a57d9b Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Sat, 12 Oct 2024 12:12:48 -0300 Subject: [PATCH 7/9] feat: Remove builder style implementation --- src/decoding.rs | 182 +++++++++++++----------------------------------- src/encoding.rs | 160 ++++++++---------------------------------- src/lib.rs | 4 +- 3 files changed, 82 insertions(+), 264 deletions(-) diff --git a/src/decoding.rs b/src/decoding.rs index 5f62d520..debec3b8 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -232,17 +232,34 @@ pub fn decode( return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let decoder = decoder_factory(&header.alg, key)?.with_validation(validation)?; + let verifying_provider = jwt_verifier_factory(&header.alg, key)?; - decoder.decode(token) + _decode(token, validation, verifying_provider) } -/// Return the correct decoder based on the `algorithm`. -fn decoder_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result { +/// # Todo +/// +/// - Documentation +pub fn _decode( + token: &str, + validation: &Validation, + verifying_provider: Box, +) -> Result> { + let (header, claims) = verify_signature(token, validation, verifying_provider)?; + + let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; + let claims = decoded_claims.deserialize()?; + validate(decoded_claims.deserialize()?, validation)?; + + Ok(TokenData { header, claims }) +} + +/// Return the correct [`JwtVerifier`] based on the `algorithm`. +fn jwt_verifier_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result> { let jwt_encoder = match algorithm { - Algorithm::HS256 => JwtDecoder::hs_256(key.try_into()?)?, - Algorithm::HS384 => JwtDecoder::hs_384(key.try_into()?)?, - Algorithm::HS512 => JwtDecoder::hs_512(key.try_into()?)?, + Algorithm::HS256 => Box::new(Hs256::new(key.try_into()?)?) as Box, + Algorithm::HS384 => Box::new(Hs384::new(key.try_into()?)?) as Box, + Algorithm::HS512 => Box::new(Hs512::new(key.try_into()?)?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -287,139 +304,40 @@ pub fn decode_header(token: &str) -> Result
{ Header::from_encoded(header) } -/// A builder style JWT decoder -/// -/// # Examples -/// -/// ``` -/// use jsonwebtoken::{JwtDecoder, HmacSecret}; -/// use serde::{Serialize, Deserialize}; +/// Verify signature of a JWT, and return header object and raw payload /// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Claims { -/// sub: String, -/// company: String, -/// exp: usize, -/// } -/// -/// let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; -/// -/// let hmac_secret = HmacSecret::from_secret(b"secret"); -/// -/// let claims = JwtDecoder::hs_256(hmac_secret) -/// .unwrap() -/// .decode::(&token) -/// .unwrap(); -/// ``` -pub struct JwtDecoder { +/// If the token or its signature is invalid, it will return an error. +fn verify_signature<'a>( + token: &'a str, + validation: &Validation, verifying_provider: Box, - validation: Validation, -} - -impl JwtDecoder { - /// Create a new [`JwtDecoder`] with any `verifying_provider` that implements the [`JwtVerifier`] trait. - pub fn from_verifier(verifying_provider: V) -> Self { - Self::from_boxed_verifiyer(Box::new(verifying_provider)) +) -> Result<(Header, &'a str)> { + if validation.validate_signature && validation.algorithms.is_empty() { + return Err(new_error(ErrorKind::MissingAlgorithm)); } - /// Create a new [`JwtDecoder`] with any `verifying_provider` implements the [`JwtVerifier`] trait. - pub fn from_boxed_verifiyer(verifying_provider: Box) -> Self { - let validation = Validation::new(verifying_provider.algorithm()); + // Todo: This behaviour is currently not captured anywhere. + // if validation.validate_signature { + // for alg in &validation.algorithms { + // if key.family != alg.family() { + // return Err(new_error(ErrorKind::InvalidAlgorithm)); + // } + // } + // } - Self { verifying_provider, validation } - } - - /// Provide custom a custom validation configuration. - /// - /// # Examples - /// - /// ``` - /// use jsonwebtoken::{JwtDecoder, HmacSecret, Validation, Algorithm}; - /// - /// let hmac_secret = HmacSecret::from_secret(b"secret"); - /// let mut validation = Validation::new(Algorithm::HS256); - /// validation.leeway = 5; - /// - /// let jwt_decoder = JwtDecoder::hs_256(hmac_secret) - /// .unwrap() - /// .with_validation(&validation) - /// .unwrap(); - /// ``` - pub fn with_validation(mut self, validation: &Validation) -> Result { - // Check that the validation contains the correct algorithm - if validation.validate_signature - && !validation.algorithms.contains(&self.verifying_provider.algorithm()) - { - return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); - } - - self.validation = validation.clone(); - Ok(self) - } - - /// Decode and verify a JWT `token` using the `verifying_provider` and `validation` of the [`JwtDecoder`] - pub fn decode(&self, token: &str) -> Result> { - let (header, claims) = self.verify_signature(token)?; - - let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?; - let claims = decoded_claims.deserialize()?; - validate(decoded_claims.deserialize()?, &self.validation)?; - - Ok(TokenData { header, claims }) - } - - /// Verify signature of a JWT, and return header object and raw payload - /// - /// If the token or its signature is invalid, it will return an error. - fn verify_signature<'a>(&self, token: &'a str) -> Result<(Header, &'a str)> { - if self.validation.validate_signature && self.validation.algorithms.is_empty() { - return Err(new_error(ErrorKind::MissingAlgorithm)); - } + let (signature, message) = expect_two!(token.rsplitn(2, '.')); + let (payload, header) = expect_two!(message.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; - // Todo: This behaviour is currently not captured anywhere. - // if validation.validate_signature { - // for alg in &validation.algorithms { - // if key.family != alg.family() { - // return Err(new_error(ErrorKind::InvalidAlgorithm)); - // } - // } - // } - - let (signature, message) = expect_two!(token.rsplitn(2, '.')); - let (payload, header) = expect_two!(message.rsplitn(2, '.')); - let header = Header::from_encoded(header)?; - - if self.validation.validate_signature && !self.validation.algorithms.contains(&header.alg) { - return Err(new_error(ErrorKind::InvalidAlgorithm)); - } - - if self.validation.validate_signature - && self.verifying_provider.verify(message.as_bytes(), &b64_decode(signature)?).is_err() - { - return Err(new_error(ErrorKind::InvalidSignature)); - } - - Ok((header, payload)) - } - - /// Create new [`JwtDecoder`] with the `HS256` algorithm. - pub fn hs_256(secret: HmacSecret) -> Result { - let verifying_provider = Box::new(Hs256::new(secret)?); - - Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + if validation.validate_signature && !validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); } - /// Create new [`JwtDecoder`] with the `HS384` algorithm. - pub fn hs_384(secret: HmacSecret) -> Result { - let verifying_provider = Box::new(Hs384::new(secret)?); - - Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) + if validation.validate_signature + && verifying_provider.verify(message.as_bytes(), &b64_decode(signature)?).is_err() + { + return Err(new_error(ErrorKind::InvalidSignature)); } - /// Create new [`JwtDecoder`] with the `HS512` algorithm. - pub fn hs_512(secret: HmacSecret) -> Result { - let verifying_provider = Box::new(Hs512::new(secret)?); - - Ok(JwtDecoder::from_boxed_verifiyer(verifying_provider)) - } + Ok((header, payload)) } diff --git a/src/encoding.rs b/src/encoding.rs index 96dc6dce..e045d185 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -131,17 +131,38 @@ pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> R return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let jwt_encoder = encoder_factory(&header.alg, key)?.with_header(header)?; + let signing_provider = jwt_signer_factory(&header.alg, key)?; - jwt_encoder.encode(claims) + _encode(header, claims, signing_provider) } -/// Return the correct [`JwtEncoder`] based on the `algorithm`. -fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result { - let jwt_encoder = match algorithm { - Algorithm::HS256 => JwtEncoder::hs_256(key.into())?, - Algorithm::HS384 => JwtEncoder::hs_384(key.into())?, - Algorithm::HS512 => JwtEncoder::hs_512(key.into())?, +/// # Todo +/// +/// - Documentation +pub fn _encode( + header: &Header, + claims: &T, + signing_provider: Box, +) -> Result { + if signing_provider.algorithm() != header.alg { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + let encoded_header = b64_encode_part(&header)?; + let encoded_claims = b64_encode_part(claims)?; + let message = [encoded_header, encoded_claims].join("."); + + let signature = b64_encode(&signing_provider.sign(message.as_bytes())); + + Ok([message, signature].join(".")) +} + +/// Return the correct [`JwtSigner`] based on the `algorithm`. +fn jwt_signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result> { + let jwt_signer = match algorithm { + Algorithm::HS256 => Box::new(Hs256::new(key.into())?) as Box, + Algorithm::HS384 => Box::new(Hs384::new(key.into())?) as Box, + Algorithm::HS512 => Box::new(Hs512::new(key.into())?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -153,7 +174,7 @@ fn encoder_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result todo!(), }; - Ok(jwt_encoder) + Ok(jwt_signer) } /// Convert an [`&EncodingKey`] to an [`HmacSecret`]. @@ -162,124 +183,3 @@ impl From<&EncodingKey> for HmacSecret { HmacSecret::from_secret(&key.content) } } - -/// A builder style JWT encoder. -/// -/// # Examples -/// -/// ``` -/// use serde::{Deserialize, Serialize}; -/// use jsonwebtoken::{JwtEncoder, HmacSecret}; -/// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Claims { -/// sub: String, -/// company: String -/// } -/// -/// let my_claims = Claims { -/// sub: "b@b.com".to_owned(), -/// company: "ACME".to_owned() -/// }; -/// -/// let hmac_secret = HmacSecret::from_secret(b"secret"); -/// -/// let token = JwtEncoder::hs_256(hmac_secret) -/// .unwrap() -/// .encode(&my_claims) -/// .unwrap(); -/// ``` -pub struct JwtEncoder { - signing_provider: Box, - header: Header, -} - -impl JwtEncoder { - /// Create a new [`JwtEncoder`] with any `signing_provider` that implements the [`JwtSigner`] trait. - pub fn from_signer(signing_provider: S) -> Self { - Self::from_boxed_signer(Box::new(signing_provider)) - } - - /// Create a new [`JwtEncoder`] with any `signing_provider` that implements the [`JwtSigner`] trait. - pub fn from_boxed_signer(signing_provider: Box) -> Self { - // Determine a default header - let mut header = Header::new(signing_provider.algorithm()); - header.typ = Some("JWT".to_owned()); - - Self { signing_provider, header } - } - - /// Provide a custom header. - /// - /// This would be used in the rare cases that fields other than `algorithm` and `type` need to be populated. - /// - /// # Examples - /// - /// ``` - /// use serde::{Deserialize, Serialize}; - /// use jsonwebtoken::{JwtEncoder, HmacSecret, Header, Algorithm}; - /// - /// #[derive(Debug, Serialize, Deserialize)] - /// struct Claims { - /// sub: String, - /// company: String - /// } - /// - /// let my_claims = Claims { - /// sub: "b@b.com".to_owned(), - /// company: "ACME".to_owned() - /// }; - /// - /// let hmac_secret = HmacSecret::from_secret(b"secret"); - /// let mut header = Header::new(Algorithm::HS256); - /// header.cty = Some("content-type".to_owned()); - /// - /// let token = JwtEncoder::hs_256(hmac_secret) - /// .unwrap() - /// .with_header(&header) - /// .unwrap() - /// .encode(&my_claims) - /// .unwrap(); - /// ``` - pub fn with_header(mut self, header: &Header) -> Result { - // Check that the header makes use of the correct algorithm - if header.alg != self.signing_provider.algorithm() { - return Err(new_error(crate::errors::ErrorKind::InvalidAlgorithm)); - } - - self.header = header.clone(); - Ok(self) - } - - /// Encode and sign the `claims` as a JWT using the `signing_provider` of the [`JwtEncoder`]. - pub fn encode(&self, claims: &T) -> Result { - let encoded_header = b64_encode_part(&self.header)?; - let encoded_claims = b64_encode_part(claims)?; - let message = [encoded_header, encoded_claims].join("."); - - let signature = b64_encode(&self.signing_provider.sign(message.as_bytes())); - - Ok([message, signature].join(".")) - } - - /// Create new [`JwtEncoder`] with the `HS256` algorithm. - pub fn hs_256(secret: HmacSecret) -> Result { - let signing_provider = Box::new(Hs256::new(secret)?); - - Ok(JwtEncoder::from_boxed_signer(signing_provider)) - } - - /// Create new [`JwtEncoder`] with the `HS384` algorithm. - pub fn hs_384(secret: HmacSecret) -> Result { - let signing_provider = Box::new(Hs384::new(secret)?); - - Ok(JwtEncoder::from_boxed_signer(signing_provider)) - } - - /// Create new [`JwtEncoder`] with the `HS512` algorithm. - pub fn hs_512(secret: HmacSecret) -> Result { - let signing_provider = Box::new(Hs512::new(secret)?); - - Ok(JwtEncoder::from_boxed_signer(signing_provider)) - } -} diff --git a/src/lib.rs b/src/lib.rs index 4d18c704..3de6f9a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ compile_error!( pub use algorithms::Algorithm; pub use crypto::hmac::HmacSecret; -pub use decoding::{decode, decode_header, DecodingKey, JwtDecoder, TokenData}; -pub use encoding::{encode, EncodingKey, JwtEncoder}; +pub use decoding::{decode, decode_header, DecodingKey, TokenData, _decode}; +pub use encoding::{encode, EncodingKey, _encode}; pub use header::Header; pub use validation::{get_current_timestamp, Validation}; From 4225e1f1be29ca4867befb69ebde19be6cdf26b0 Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:22:06 -0300 Subject: [PATCH 8/9] feat: Use original encoding and decoding key structs --- src/crypto/aws_lc/mod.rs | 1 + src/crypto/hmac.rs | 31 ----------- src/crypto/mod.rs | 1 - src/crypto/rust_crypto/hmac.rs | 97 ++++++++++++++++++++++++---------- src/decoding.rs | 39 +++++++------- src/encoding.rs | 22 ++++---- src/lib.rs | 1 - 7 files changed, 102 insertions(+), 90 deletions(-) delete mode 100644 src/crypto/hmac.rs diff --git a/src/crypto/aws_lc/mod.rs b/src/crypto/aws_lc/mod.rs index 3a812e97..dccbe7b3 100644 --- a/src/crypto/aws_lc/mod.rs +++ b/src/crypto/aws_lc/mod.rs @@ -1 +1,2 @@ pub(crate) mod hmac; +// pub(crate) mod rsa; diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs deleted file mode 100644 index 8467f2dd..00000000 --- a/src/crypto/hmac.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Common HMAC related functionality. - -use std::ops::Deref; - -use base64::{engine::general_purpose::STANDARD, Engine}; - -use crate::errors::Result; - -/// The shared secret used for the HMAC family of algorithms. -#[derive(Debug)] -pub struct HmacSecret(Vec); - -impl HmacSecret { - /// If you're using an HMAC secret that is not base64, use that. - pub fn from_secret(secret: &[u8]) -> Self { - Self(secret.to_vec()) - } - - /// If you have a base64 HMAC secret, use that. - pub fn from_base64_secret(secret: &str) -> Result { - Ok(Self(STANDARD.decode(secret)?)) - } -} - -impl Deref for HmacSecret { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 15d9f479..0ee0fcb9 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -6,7 +6,6 @@ use crate::algorithms::Algorithm; #[cfg(feature = "aws_lc_rs")] pub(crate) mod aws_lc; -pub(crate) mod hmac; #[cfg(feature = "rust_crypto")] pub(crate) mod rust_crypto; diff --git a/src/crypto/rust_crypto/hmac.rs b/src/crypto/rust_crypto/hmac.rs index a29f5c68..5de983c6 100644 --- a/src/crypto/rust_crypto/hmac.rs +++ b/src/crypto/rust_crypto/hmac.rs @@ -6,25 +6,28 @@ use sha2::{Sha256, Sha384, Sha512}; use signature::{Signer, Verifier}; use crate::crypto::{JwtSigner, JwtVerifier}; +use crate::decoding::try_get_hmac_secret_from_decoding_key; +use crate::encoding::try_get_hmac_secret_from_encoding_key; use crate::errors::Result; -use crate::{Algorithm, HmacSecret}; +use crate::{Algorithm, DecodingKey, EncodingKey}; type HmacSha256 = Hmac; type HmacSha384 = Hmac; type HmacSha512 = Hmac; -pub struct Hs256(HmacSha256); +pub struct Hs256Signer(HmacSha256); -impl Hs256 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha256::new_from_slice(&secret) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; +impl Hs256Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + let inner = + HmacSha256::new_from_slice(try_get_hmac_secret_from_encoding_key(encoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } -impl Signer> for Hs256 { +impl Signer> for Hs256Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { let mut signer = self.0.clone(); signer.reset(); @@ -34,13 +37,25 @@ impl Signer> for Hs256 { } } -impl JwtSigner for Hs256 { +impl JwtSigner for Hs256Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS256 } } -impl Verifier> for Hs256 { +pub struct Hs256Verifier(HmacSha256); + +impl Hs256Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + let inner = + HmacSha256::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Verifier> for Hs256Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { let mut verifier = self.0.clone(); verifier.reset(); @@ -50,24 +65,25 @@ impl Verifier> for Hs256 { } } -impl JwtVerifier for Hs256 { +impl JwtVerifier for Hs256Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS256 } } -pub(crate) struct Hs384(HmacSha384); +pub struct Hs384Signer(HmacSha384); -impl Hs384 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha384::new_from_slice(&secret) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; +impl Hs384Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + let inner = + HmacSha384::new_from_slice(try_get_hmac_secret_from_encoding_key(encoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } -impl Signer> for Hs384 { +impl Signer> for Hs384Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { let mut signer = self.0.clone(); signer.reset(); @@ -77,13 +93,25 @@ impl Signer> for Hs384 { } } -impl JwtSigner for Hs384 { +impl JwtSigner for Hs384Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS384 } } -impl Verifier> for Hs384 { +pub struct Hs384Verifier(HmacSha384); + +impl Hs384Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + let inner = + HmacSha384::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Verifier> for Hs384Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { let mut verifier = self.0.clone(); verifier.reset(); @@ -93,24 +121,25 @@ impl Verifier> for Hs384 { } } -impl JwtVerifier for Hs384 { +impl JwtVerifier for Hs384Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS384 } } -pub struct Hs512(HmacSha512); +pub struct Hs512Signer(HmacSha512); -impl Hs512 { - pub(crate) fn new(secret: HmacSecret) -> Result { - let inner = HmacSha512::new_from_slice(&secret) - .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; +impl Hs512Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + let inner = + HmacSha512::new_from_slice(try_get_hmac_secret_from_encoding_key(encoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) } } -impl Signer> for Hs512 { +impl Signer> for Hs512Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { let mut signer = self.0.clone(); signer.reset(); @@ -120,13 +149,25 @@ impl Signer> for Hs512 { } } -impl JwtSigner for Hs512 { +impl JwtSigner for Hs512Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS512 } } -impl Verifier> for Hs512 { +pub struct Hs512Verifier(HmacSha512); + +impl Hs512Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + let inner = + HmacSha512::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; + + Ok(Self(inner)) + } +} + +impl Verifier> for Hs512Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { let mut verifier = self.0.clone(); verifier.reset(); @@ -136,7 +177,7 @@ impl Verifier> for Hs512 { } } -impl JwtVerifier for Hs512 { +impl JwtVerifier for Hs512Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS512 } diff --git a/src/decoding.rs b/src/decoding.rs index debec3b8..ba2780db 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,7 +2,6 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; -use crate::crypto::hmac::HmacSecret; use crate::crypto::JwtVerifier; use crate::errors::{new_error, Error, ErrorKind, Result}; use crate::header::Header; @@ -17,7 +16,7 @@ use crate::Algorithm; #[cfg(feature = "aws_lc_rs")] use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; #[cfg(feature = "rust_crypto")] -use crate::crypto::rust_crypto::hmac::{Hs256, Hs384, Hs512}; +use crate::crypto::rust_crypto::hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier}; /// The return type of a successful call to [decode](fn.decode.html). #[derive(Debug)] @@ -257,9 +256,9 @@ pub fn _decode( /// Return the correct [`JwtVerifier`] based on the `algorithm`. fn jwt_verifier_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result> { let jwt_encoder = match algorithm { - Algorithm::HS256 => Box::new(Hs256::new(key.try_into()?)?) as Box, - Algorithm::HS384 => Box::new(Hs384::new(key.try_into()?)?) as Box, - Algorithm::HS512 => Box::new(Hs512::new(key.try_into()?)?) as Box, + Algorithm::HS256 => Box::new(Hs256Verifier::new(key)?) as Box, + Algorithm::HS384 => Box::new(Hs384Verifier::new(key)?) as Box, + Algorithm::HS512 => Box::new(Hs512Verifier::new(key)?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -274,20 +273,6 @@ fn jwt_verifier_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result for &DecodingKey { - type Error = Error; - - fn try_into(self) -> std::result::Result { - match self.kind.clone() { - DecodingKeyKind::SecretOrDer(vec) => Ok(HmacSecret::from_secret(&vec)), - DecodingKeyKind::RsaModulusExponent { .. } => { - Err(new_error(crate::errors::ErrorKind::InvalidKeyFormat)) - } - } - } -} - /// Decode a JWT without any signature verification/validations and return its [Header](struct.Header.html). /// /// If the token has an invalid format (ie 3 parts separated by a `.`), it will return an error. @@ -341,3 +326,19 @@ fn verify_signature<'a>( Ok((header, payload)) } + +/// # Todo +/// +/// - Try return a reference. +pub(crate) fn try_get_hmac_secret_from_decoding_key(decoding_key: &DecodingKey) -> Result> { + if decoding_key.family != AlgorithmFamily::Hmac { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + match decoding_key.kind.clone() { + DecodingKeyKind::SecretOrDer(vec) => Ok(vec), + DecodingKeyKind::RsaModulusExponent { .. } => { + Err(new_error(crate::errors::ErrorKind::InvalidKeyFormat)) + } + } +} diff --git a/src/encoding.rs b/src/encoding.rs index e045d185..3de6c11c 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,20 +2,19 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; -use crate::crypto::hmac::HmacSecret; use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_encode, b64_encode_part}; -use crate::Algorithm; +use crate::{Algorithm, DecodingKey}; // Crypto #[cfg(feature = "aws_lc_rs")] use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; #[cfg(feature = "rust_crypto")] -use crate::crypto::rust_crypto::hmac::{Hs256, Hs384, Hs512}; +use crate::crypto::rust_crypto::hmac::{Hs256Signer, Hs384Signer, Hs512Signer}; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance. @@ -160,9 +159,9 @@ pub fn _encode( /// Return the correct [`JwtSigner`] based on the `algorithm`. fn jwt_signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result> { let jwt_signer = match algorithm { - Algorithm::HS256 => Box::new(Hs256::new(key.into())?) as Box, - Algorithm::HS384 => Box::new(Hs384::new(key.into())?) as Box, - Algorithm::HS512 => Box::new(Hs512::new(key.into())?) as Box, + Algorithm::HS256 => Box::new(Hs256Signer::new(key)?) as Box, + Algorithm::HS384 => Box::new(Hs384Signer::new(key)?) as Box, + Algorithm::HS512 => Box::new(Hs512Signer::new(key)?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), Algorithm::RS256 => todo!(), @@ -177,9 +176,12 @@ fn jwt_signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result for HmacSecret { - fn from(key: &EncodingKey) -> Self { - HmacSecret::from_secret(&key.content) +pub(crate) fn try_get_hmac_secret_from_encoding_key( + encoding_key: &EncodingKey, +) -> Result<&Vec> { + if encoding_key.family == AlgorithmFamily::Hmac { + Ok(&encoding_key.content) + } else { + Err(new_error(ErrorKind::InvalidKeyFormat)) } } diff --git a/src/lib.rs b/src/lib.rs index 3de6f9a3..0fc4404e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,6 @@ compile_error!( ); pub use algorithms::Algorithm; -pub use crypto::hmac::HmacSecret; pub use decoding::{decode, decode_header, DecodingKey, TokenData, _decode}; pub use encoding::{encode, EncodingKey, _encode}; pub use header::Header; From 78e84c1f57a25ed1000d455cf22da955a01e05d8 Mon Sep 17 00:00:00 2001 From: sidrubs <52378223+sidrubs@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:15:56 -0300 Subject: [PATCH 9/9] feat(crypto): Add RSA family --- Cargo.toml | 2 +- src/crypto/aws_lc/hmac.rs | 95 +++++++++++----- src/crypto/aws_lc/mod.rs | 2 +- src/crypto/aws_lc/rsa.rs | 200 +++++++++++++++++++++++++++++++++ src/crypto/mod.rs | 1 + src/crypto/rust_crypto/hmac.rs | 11 +- src/crypto/utils.rs | 26 +++++ src/decoding.rs | 32 ++---- src/encoding.rs | 23 ++-- tests/lib.rs | 2 +- tests/rsa/mod.rs | 168 +++++++++++++-------------- 11 files changed, 412 insertions(+), 150 deletions(-) create mode 100644 src/crypto/aws_lc/rsa.rs create mode 100644 src/crypto/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 7d228aec..ab274b0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ time = { version = "0.3", features = ["wasm-bindgen"] } criterion = { version = "0.4", default-features = false } [features] -default = ["use_pem", "rust_crypto"] +default = ["use_pem", "aws_lc_rs"] use_pem = ["pem", "simple_asn1", 'p256/pem', 'p384/pem'] rust_crypto = ["hmac"] aws_lc_rs = ["aws-lc-rs"] diff --git a/src/crypto/aws_lc/hmac.rs b/src/crypto/aws_lc/hmac.rs index 00b3570b..73413e2d 100644 --- a/src/crypto/aws_lc/hmac.rs +++ b/src/crypto/aws_lc/hmac.rs @@ -4,101 +4,146 @@ use aws_lc_rs::hmac; use signature::{Signer, Verifier}; +use crate::crypto::utils::{ + try_get_hmac_secret_from_decoding_key, try_get_hmac_secret_from_encoding_key, +}; use crate::crypto::{JwtSigner, JwtVerifier}; use crate::errors::Result; -use crate::{Algorithm, HmacSecret}; +use crate::{Algorithm, DecodingKey, EncodingKey}; -pub struct Hs256(hmac::Key); +pub struct Hs256Signer(hmac::Key); -impl Hs256 { - pub(crate) fn new(secret: HmacSecret) -> Result { - Ok(Self(hmac::Key::new(hmac::HMAC_SHA256, &secret))) +impl Hs256Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA256, + try_get_hmac_secret_from_encoding_key(encoding_key)?, + ))) } } -impl Signer> for Hs256 { +impl Signer> for Hs256Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) } } -impl JwtSigner for Hs256 { +impl JwtSigner for Hs256Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS256 } } -impl Verifier> for Hs256 { +pub struct Hs256Verifier(hmac::Key); + +impl Hs256Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA256, + try_get_hmac_secret_from_decoding_key(decoding_key)?, + ))) + } +} + +impl Verifier> for Hs256Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) } } -impl JwtVerifier for Hs256 { +impl JwtVerifier for Hs256Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS256 } } -pub struct Hs384(hmac::Key); +pub struct Hs384Signer(hmac::Key); -impl Hs384 { - pub(crate) fn new(secret: HmacSecret) -> Result { - Ok(Self(hmac::Key::new(hmac::HMAC_SHA384, &secret))) +impl Hs384Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA384, + try_get_hmac_secret_from_encoding_key(encoding_key)?, + ))) } } -impl Signer> for Hs384 { +impl Signer> for Hs384Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) } } -impl JwtSigner for Hs384 { +impl JwtSigner for Hs384Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS384 } } -impl Verifier> for Hs384 { +pub struct Hs384Verifier(hmac::Key); + +impl Hs384Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA384, + try_get_hmac_secret_from_decoding_key(decoding_key)?, + ))) + } +} + +impl Verifier> for Hs384Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) } } -impl JwtVerifier for Hs384 { +impl JwtVerifier for Hs384Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS384 } } -pub struct Hs512(hmac::Key); +pub struct Hs512Signer(hmac::Key); -impl Hs512 { - pub(crate) fn new(secret: HmacSecret) -> Result { - Ok(Self(hmac::Key::new(hmac::HMAC_SHA512, &secret))) +impl Hs512Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA512, + try_get_hmac_secret_from_encoding_key(encoding_key)?, + ))) } } -impl Signer> for Hs512 { +impl Signer> for Hs512Signer { fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { Ok(hmac::sign(&self.0, msg).as_ref().to_vec()) } } -impl JwtSigner for Hs512 { +impl JwtSigner for Hs512Signer { fn algorithm(&self) -> Algorithm { Algorithm::HS512 } } -impl Verifier> for Hs512 { +pub struct Hs512Verifier(hmac::Key); + +impl Hs512Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + Ok(Self(hmac::Key::new( + hmac::HMAC_SHA512, + try_get_hmac_secret_from_decoding_key(decoding_key)?, + ))) + } +} + +impl Verifier> for Hs512Verifier { fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { hmac::verify(&self.0, msg, &signature).map_err(|err| signature::Error::from_source(err)) } } -impl JwtVerifier for Hs512 { +impl JwtVerifier for Hs512Verifier { fn algorithm(&self) -> Algorithm { Algorithm::HS512 } diff --git a/src/crypto/aws_lc/mod.rs b/src/crypto/aws_lc/mod.rs index dccbe7b3..5461dfa2 100644 --- a/src/crypto/aws_lc/mod.rs +++ b/src/crypto/aws_lc/mod.rs @@ -1,2 +1,2 @@ pub(crate) mod hmac; -// pub(crate) mod rsa; +pub(crate) mod rsa; diff --git a/src/crypto/aws_lc/rsa.rs b/src/crypto/aws_lc/rsa.rs new file mode 100644 index 00000000..2d9ece56 --- /dev/null +++ b/src/crypto/aws_lc/rsa.rs @@ -0,0 +1,200 @@ +//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the +//! RSA family of algorithms using [`aws_lc_rs`] + +use aws_lc_rs::{rand, signature as crypto_sig}; +use signature::{Signer, Verifier}; + +use crate::algorithms::AlgorithmFamily; +use crate::crypto::{JwtSigner, JwtVerifier}; +use crate::decoding::DecodingKeyKind; +use crate::errors::{new_error, ErrorKind, Result}; +use crate::{Algorithm, DecodingKey, EncodingKey}; + +/// Try to sign the `message` using an `RSA` `algorithm`. +fn try_sign_rsa( + algorithm: &'static dyn crypto_sig::RsaEncoding, + encoding_key: &EncodingKey, + msg: &[u8], +) -> std::result::Result, signature::Error> { + let key_pair = crypto_sig::RsaKeyPair::from_der(encoding_key.inner()) + .map_err(|err| signature::Error::from_source(err))?; + + let mut signature = vec![0; key_pair.public_modulus_len()]; + let rng = rand::SystemRandom::new(); + key_pair + .sign(algorithm, &rng, msg, &mut signature) + .map_err(|err| signature::Error::from_source(err))?; + + Ok(signature) +} + +/// Return a `aws_lc_rs` RSA public key from a [`DecodingKey`] +/// +/// # Errors +/// +/// - If `decoding_key` is not from the RSA family. +fn verify_rsa( + algorithm: &'static crypto_sig::RsaParameters, + decoding_key: &DecodingKey, + msg: &[u8], + signature: &[u8], +) -> std::result::Result<(), signature::Error> { + match &decoding_key.kind { + DecodingKeyKind::SecretOrDer(bytes) => { + let public_key = crypto_sig::UnparsedPublicKey::new(algorithm, bytes); + public_key.verify(msg, signature).map_err(|err| signature::Error::from_source(err))?; + } + DecodingKeyKind::RsaModulusExponent { n, e } => { + let public_key = crypto_sig::RsaPublicKeyComponents { n, e }; + public_key + .verify(algorithm, msg, &signature) + .map_err(|err| signature::Error::from_source(err))?; + } + }; + + Ok(()) +} + +pub struct Rsa256Signer(EncodingKey); + +impl Rsa256Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + if encoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(encoding_key.clone())) + } +} + +impl Signer> for Rsa256Signer { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + try_sign_rsa(&crypto_sig::RSA_PKCS1_SHA256, &self.0, msg) + } +} + +impl JwtSigner for Rsa256Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::RS256 + } +} + +pub struct Rsa256Verifier(DecodingKey); + +impl Rsa256Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + if decoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(decoding_key.clone())) + } +} + +impl Verifier> for Rsa256Verifier { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + verify_rsa(&crypto_sig::RSA_PKCS1_2048_8192_SHA256, &self.0, msg, signature) + } +} + +impl JwtVerifier for Rsa256Verifier { + fn algorithm(&self) -> Algorithm { + Algorithm::RS256 + } +} + +pub struct Rsa384Signer(EncodingKey); + +impl Rsa384Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + if encoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(encoding_key.clone())) + } +} + +impl Signer> for Rsa384Signer { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + try_sign_rsa(&crypto_sig::RSA_PKCS1_SHA384, &self.0, msg) + } +} + +impl JwtSigner for Rsa384Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::RS384 + } +} + +pub struct Rsa384Verifier(DecodingKey); + +impl Rsa384Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + if decoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(decoding_key.clone())) + } +} + +impl Verifier> for Rsa384Verifier { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + verify_rsa(&crypto_sig::RSA_PKCS1_2048_8192_SHA384, &self.0, msg, signature) + } +} + +impl JwtVerifier for Rsa384Verifier { + fn algorithm(&self) -> Algorithm { + Algorithm::RS384 + } +} + +pub struct Rsa512Signer(EncodingKey); + +impl Rsa512Signer { + pub(crate) fn new(encoding_key: &EncodingKey) -> Result { + if encoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(encoding_key.clone())) + } +} + +impl Signer> for Rsa512Signer { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, signature::Error> { + try_sign_rsa(&crypto_sig::RSA_PKCS1_SHA512, &self.0, msg) + } +} + +impl JwtSigner for Rsa512Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::RS512 + } +} + +pub struct Rsa512Verifier(DecodingKey); + +impl Rsa512Verifier { + pub(crate) fn new(decoding_key: &DecodingKey) -> Result { + if decoding_key.family != AlgorithmFamily::Rsa { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(Self(decoding_key.clone())) + } +} + +impl Verifier> for Rsa512Verifier { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), signature::Error> { + verify_rsa(&crypto_sig::RSA_PKCS1_2048_8192_SHA512, &self.0, msg, signature) + } +} + +impl JwtVerifier for Rsa512Verifier { + fn algorithm(&self) -> Algorithm { + Algorithm::RS512 + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0ee0fcb9..5acca716 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -8,6 +8,7 @@ use crate::algorithms::Algorithm; pub(crate) mod aws_lc; #[cfg(feature = "rust_crypto")] pub(crate) mod rust_crypto; +pub(crate) mod utils; use signature::{Signer, Verifier}; diff --git a/src/crypto/rust_crypto/hmac.rs b/src/crypto/rust_crypto/hmac.rs index 5de983c6..374bfa68 100644 --- a/src/crypto/rust_crypto/hmac.rs +++ b/src/crypto/rust_crypto/hmac.rs @@ -5,9 +5,10 @@ use hmac::{Hmac, Mac}; use sha2::{Sha256, Sha384, Sha512}; use signature::{Signer, Verifier}; +use crate::crypto::utils::{ + try_get_hmac_secret_from_decoding_key, try_get_hmac_secret_from_encoding_key, +}; use crate::crypto::{JwtSigner, JwtVerifier}; -use crate::decoding::try_get_hmac_secret_from_decoding_key; -use crate::encoding::try_get_hmac_secret_from_encoding_key; use crate::errors::Result; use crate::{Algorithm, DecodingKey, EncodingKey}; @@ -48,7 +49,7 @@ pub struct Hs256Verifier(HmacSha256); impl Hs256Verifier { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { let inner = - HmacSha256::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + HmacSha256::new_from_slice(try_get_hmac_secret_from_decoding_key(decoding_key)?) .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) @@ -104,7 +105,7 @@ pub struct Hs384Verifier(HmacSha384); impl Hs384Verifier { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { let inner = - HmacSha384::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + HmacSha384::new_from_slice(try_get_hmac_secret_from_decoding_key(decoding_key)?) .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) @@ -160,7 +161,7 @@ pub struct Hs512Verifier(HmacSha512); impl Hs512Verifier { pub(crate) fn new(decoding_key: &DecodingKey) -> Result { let inner = - HmacSha512::new_from_slice(&try_get_hmac_secret_from_decoding_key(decoding_key)?) + HmacSha512::new_from_slice(try_get_hmac_secret_from_decoding_key(decoding_key)?) .map_err(|_e| crate::errors::ErrorKind::InvalidKeyFormat)?; Ok(Self(inner)) diff --git a/src/crypto/utils.rs b/src/crypto/utils.rs new file mode 100644 index 00000000..2044a7a4 --- /dev/null +++ b/src/crypto/utils.rs @@ -0,0 +1,26 @@ +//! # Todo +//! +//! - Put in documentation + +use crate::{ + algorithms::AlgorithmFamily, + decoding::DecodingKeyKind, + errors::{self, new_error, ErrorKind, Result}, + DecodingKey, EncodingKey, +}; + +pub(crate) fn try_get_hmac_secret_from_encoding_key(encoding_key: &EncodingKey) -> Result<&[u8]> { + if encoding_key.family == AlgorithmFamily::Hmac { + Ok(encoding_key.inner()) + } else { + Err(new_error(ErrorKind::InvalidKeyFormat)) + } +} + +pub(crate) fn try_get_hmac_secret_from_decoding_key(decoding_key: &DecodingKey) -> Result<&[u8]> { + if decoding_key.family != AlgorithmFamily::Hmac { + return Err(new_error(ErrorKind::InvalidKeyFormat)); + } + + Ok(decoding_key.as_bytes()) +} diff --git a/src/decoding.rs b/src/decoding.rs index ba2780db..ac0e6314 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,6 +2,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::de::DeserializeOwned; use crate::algorithms::AlgorithmFamily; +use crate::crypto::aws_lc::rsa::{Rsa256Verifier, Rsa384Verifier, Rsa512Verifier}; use crate::crypto::JwtVerifier; use crate::errors::{new_error, Error, ErrorKind, Result}; use crate::header::Header; @@ -14,7 +15,7 @@ use crate::Algorithm; // Crypto #[cfg(feature = "aws_lc_rs")] -use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; +use crate::crypto::aws_lc::hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier}; #[cfg(feature = "rust_crypto")] use crate::crypto::rust_crypto::hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier}; @@ -199,6 +200,13 @@ impl DecodingKey { } } } + + pub(crate) fn as_bytes(&self) -> &[u8] { + match &self.kind { + DecodingKeyKind::SecretOrDer(b) => b, + DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), + } + } } /// Decode and validate a JWT @@ -261,9 +269,9 @@ fn jwt_verifier_factory(algorithm: &Algorithm, key: &DecodingKey) -> Result Box::new(Hs512Verifier::new(key)?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), - Algorithm::RS256 => todo!(), - Algorithm::RS384 => todo!(), - Algorithm::RS512 => todo!(), + Algorithm::RS256 => Box::new(Rsa256Verifier::new(key)?) as Box, + Algorithm::RS384 => Box::new(Rsa384Verifier::new(key)?) as Box, + Algorithm::RS512 => Box::new(Rsa512Verifier::new(key)?) as Box, Algorithm::PS256 => todo!(), Algorithm::PS384 => todo!(), Algorithm::PS512 => todo!(), @@ -326,19 +334,3 @@ fn verify_signature<'a>( Ok((header, payload)) } - -/// # Todo -/// -/// - Try return a reference. -pub(crate) fn try_get_hmac_secret_from_decoding_key(decoding_key: &DecodingKey) -> Result> { - if decoding_key.family != AlgorithmFamily::Hmac { - return Err(new_error(ErrorKind::InvalidKeyFormat)); - } - - match decoding_key.kind.clone() { - DecodingKeyKind::SecretOrDer(vec) => Ok(vec), - DecodingKeyKind::RsaModulusExponent { .. } => { - Err(new_error(crate::errors::ErrorKind::InvalidKeyFormat)) - } - } -} diff --git a/src/encoding.rs b/src/encoding.rs index 3de6c11c..7b744a42 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -2,17 +2,20 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::ser::Serialize; use crate::algorithms::AlgorithmFamily; +use crate::crypto::aws_lc::rsa::Rsa512Signer; use crate::crypto::JwtSigner; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; #[cfg(feature = "use_pem")] use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_encode, b64_encode_part}; -use crate::{Algorithm, DecodingKey}; +use crate::Algorithm; // Crypto #[cfg(feature = "aws_lc_rs")] -use crate::crypto::aws_lc::hmac::{Hs256, Hs384, Hs512}; +use crate::crypto::aws_lc::hmac::{Hs256Signer, Hs384Signer, Hs512Signer}; +#[cfg(feature = "aws_lc_rs")] +use crate::crypto::aws_lc::rsa::{Rsa256Signer, Rsa384Signer}; #[cfg(feature = "rust_crypto")] use crate::crypto::rust_crypto::hmac::{Hs256Signer, Hs384Signer, Hs512Signer}; @@ -164,9 +167,9 @@ fn jwt_signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result Box::new(Hs512Signer::new(key)?) as Box, Algorithm::ES256 => todo!(), Algorithm::ES384 => todo!(), - Algorithm::RS256 => todo!(), - Algorithm::RS384 => todo!(), - Algorithm::RS512 => todo!(), + Algorithm::RS256 => Box::new(Rsa256Signer::new(key)?) as Box, + Algorithm::RS384 => Box::new(Rsa384Signer::new(key)?) as Box, + Algorithm::RS512 => Box::new(Rsa512Signer::new(key)?) as Box, Algorithm::PS256 => todo!(), Algorithm::PS384 => todo!(), Algorithm::PS512 => todo!(), @@ -175,13 +178,3 @@ fn jwt_signer_factory(algorithm: &Algorithm, key: &EncodingKey) -> Result Result<&Vec> { - if encoding_key.family == AlgorithmFamily::Hmac { - Ok(&encoding_key.content) - } else { - Err(new_error(ErrorKind::InvalidKeyFormat)) - } -} diff --git a/tests/lib.rs b/tests/lib.rs index c5374c72..cd8729e3 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,4 @@ // mod ecdsa; // mod eddsa; // mod header; -// mod rsa; +mod rsa; diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 9c679e88..74e03291 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -3,12 +3,14 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use wasm_bindgen_test::wasm_bindgen_test; -use jsonwebtoken::{ - crypto::{sign, verify}, - Algorithm, DecodingKey, EncodingKey, -}; #[cfg(feature = "use_pem")] use jsonwebtoken::{decode, encode, Header, Validation}; +use jsonwebtoken::{ + // crypto::{sign, verify}, + Algorithm, + DecodingKey, + EncodingKey, +}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, @@ -26,84 +28,86 @@ pub struct Claims { exp: i64, } -#[cfg(feature = "use_pem")] -#[test] -#[wasm_bindgen_test] -fn round_trip_sign_verification_pem_pkcs1() { - let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); - let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); - let certificate_pem = include_bytes!("certificate_rsa_key_pkcs1.crt"); - - for &alg in RSA_ALGORITHMS { - let encrypted = - sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); - - let is_valid = verify( - &encrypted, - b"hello world", - &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), - alg, - ) - .unwrap(); - assert!(is_valid); - - let cert_is_valid = verify( - &encrypted, - b"hello world", - &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), - alg, - ) - .unwrap(); - assert!(cert_is_valid); - } -} - -#[cfg(feature = "use_pem")] -#[test] -#[wasm_bindgen_test] -fn round_trip_sign_verification_pem_pkcs8() { - let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); - let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); - let certificate_pem = include_bytes!("certificate_rsa_key_pkcs8.crt"); - - for &alg in RSA_ALGORITHMS { - let encrypted = - sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); - - let is_valid = verify( - &encrypted, - b"hello world", - &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), - alg, - ) - .unwrap(); - assert!(is_valid); - - let cert_is_valid = verify( - &encrypted, - b"hello world", - &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), - alg, - ) - .unwrap(); - assert!(cert_is_valid); - } -} - -#[test] -#[wasm_bindgen_test] -fn round_trip_sign_verification_der() { - let privkey_der = include_bytes!("private_rsa_key.der"); - let pubkey_der = include_bytes!("public_rsa_key.der"); - - for &alg in RSA_ALGORITHMS { - let encrypted = sign(b"hello world", &EncodingKey::from_rsa_der(privkey_der), alg).unwrap(); - let is_valid = - verify(&encrypted, b"hello world", &DecodingKey::from_rsa_der(pubkey_der), alg) - .unwrap(); - assert!(is_valid); - } -} +// Todo: These no longer apply because `verify` does not exist, would probably need to convert it to test the factory for getting signers and verifiers. But I would rather this not be part of the public facing API. + +// #[cfg(feature = "use_pem")] +// #[test] +// #[wasm_bindgen_test] +// fn round_trip_sign_verification_pem_pkcs1() { +// let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); +// let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); +// let certificate_pem = include_bytes!("certificate_rsa_key_pkcs1.crt"); + +// for &alg in RSA_ALGORITHMS { +// let encrypted = +// sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); + +// let is_valid = verify( +// &encrypted, +// b"hello world", +// &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), +// alg, +// ) +// .unwrap(); +// assert!(is_valid); + +// let cert_is_valid = verify( +// &encrypted, +// b"hello world", +// &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), +// alg, +// ) +// .unwrap(); +// assert!(cert_is_valid); +// } +// } + +// #[cfg(feature = "use_pem")] +// #[test] +// #[wasm_bindgen_test] +// fn round_trip_sign_verification_pem_pkcs8() { +// let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); +// let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); +// let certificate_pem = include_bytes!("certificate_rsa_key_pkcs8.crt"); + +// for &alg in RSA_ALGORITHMS { +// let encrypted = +// sign(b"hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); + +// let is_valid = verify( +// &encrypted, +// b"hello world", +// &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), +// alg, +// ) +// .unwrap(); +// assert!(is_valid); + +// let cert_is_valid = verify( +// &encrypted, +// b"hello world", +// &DecodingKey::from_rsa_pem(certificate_pem).unwrap(), +// alg, +// ) +// .unwrap(); +// assert!(cert_is_valid); +// } +// } + +// #[test] +// #[wasm_bindgen_test] +// fn round_trip_sign_verification_der() { +// let privkey_der = include_bytes!("private_rsa_key.der"); +// let pubkey_der = include_bytes!("public_rsa_key.der"); + +// for &alg in RSA_ALGORITHMS { +// let encrypted = sign(b"hello world", &EncodingKey::from_rsa_der(privkey_der), alg).unwrap(); +// let is_valid = +// verify(&encrypted, b"hello world", &DecodingKey::from_rsa_der(pubkey_der), alg) +// .unwrap(); +// assert!(is_valid); +// } +// } #[cfg(feature = "use_pem")] #[test]