From 63721aedbe64ce02bf826d04415e8fbea49ae111 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 10 Nov 2023 23:04:05 +0000 Subject: [PATCH 1/6] fix: update deps + openssl vendor --- Cargo.toml | 10 ++++++---- examples/custom_claims.rs | 2 +- examples/hs256.rs | 2 +- src/algorithm/mod.rs | 13 ++++++------- src/algorithm/openssl.rs | 15 ++++++++------- src/algorithm/rust_crypto.rs | 3 ++- src/lib.rs | 6 ++++-- src/token/signed.rs | 2 +- 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4865ea2f..e9795f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,15 @@ repository = "http://github.com/mikkyang/rust-jwt" readme = "README.md" keywords = ["JWT", "token", "web"] license = "MIT" -edition = "2018" +edition = "2021" -[package.metadata.docs.rs] -features = ["openssl"] +[features] +openssl = ["dep:openssl"] +openssl-vendored = ["openssl", "openssl/vendored"] +default = [] [dependencies] -base64 = "0.13" +base64 = "0.21" crypto-common = "0.1" digest = "0.10" hmac = { version = "0.12", features = ["reset"] } diff --git a/examples/custom_claims.rs b/examples/custom_claims.rs index 1495265c..8d5a1eba 100644 --- a/examples/custom_claims.rs +++ b/examples/custom_claims.rs @@ -43,7 +43,7 @@ fn login(token: &str) -> Result { fn main() -> Result<(), &'static str> { let token = new_token("Michael Yang", "password")?; - let logged_in_user = login(&*token)?; + let logged_in_user = login(&token)?; assert_eq!(logged_in_user, "Michael Yang"); Ok(()) diff --git a/examples/hs256.rs b/examples/hs256.rs index d27730a8..6af1c78d 100644 --- a/examples/hs256.rs +++ b/examples/hs256.rs @@ -32,7 +32,7 @@ fn login(token: &str) -> Result { fn main() -> Result<(), &'static str> { let token = new_token("Michael Yang", "password")?; - let logged_in_user = login(&*token)?; + let logged_in_user = login(&token)?; assert_eq!(logged_in_user, "Michael Yang"); Ok(()) diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index 3e98a832..e2390b05 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -10,6 +10,7 @@ //! let hs256_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); //! ``` +use base64::Engine; use serde::{Deserialize, Serialize}; use crate::error::Error; @@ -23,7 +24,9 @@ pub mod store; /// [JWA](https://tools.ietf.org/html/rfc7518) specification. #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] +#[derive(Default)] pub enum AlgorithmType { + #[default] Hs256, Hs384, Hs512, @@ -40,11 +43,7 @@ pub enum AlgorithmType { None, } -impl Default for AlgorithmType { - fn default() -> Self { - AlgorithmType::Hs256 - } -} + /// An algorithm capable of signing base64 encoded header and claims strings. /// strings. @@ -61,8 +60,8 @@ pub trait VerifyingAlgorithm { fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result; fn verify(&self, header: &str, claims: &str, signature: &str) -> Result { - let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; - self.verify_bytes(header, claims, &*signature_bytes) + let signature_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(signature)?; + self.verify_bytes(header, claims, &signature_bytes) } } diff --git a/src/algorithm/openssl.rs b/src/algorithm/openssl.rs index fce0daaa..e4230bb0 100644 --- a/src/algorithm/openssl.rs +++ b/src/algorithm/openssl.rs @@ -17,6 +17,7 @@ use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; use crate::error::Error; use crate::SEPARATOR; +use base64::Engine; use openssl::bn::BigNum; use openssl::ecdsa::EcdsaSig; use openssl::hash::MessageDigest; @@ -52,7 +53,7 @@ impl SigningAlgorithm for PKeyWithDigest { } fn sign(&self, header: &str, claims: &str) -> Result { - let mut signer = Signer::new(self.digest.clone(), &self.key)?; + let mut signer = Signer::new(self.digest, &self.key)?; signer.update(header.as_bytes())?; signer.update(SEPARATOR.as_bytes())?; signer.update(claims.as_bytes())?; @@ -64,7 +65,7 @@ impl SigningAlgorithm for PKeyWithDigest { signer_signature }; - Ok(base64::encode_config(&signature, base64::URL_SAFE_NO_PAD)) + Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(signature)) } } @@ -74,7 +75,7 @@ impl VerifyingAlgorithm for PKeyWithDigest { } fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { - let mut verifier = Verifier::new(self.digest.clone(), &self.key)?; + let mut verifier = Verifier::new(self.digest, &self.key)?; verifier.update(header.as_bytes())?; verifier.update(SEPARATOR.as_bytes())?; verifier.update(claims.as_bytes())?; @@ -92,7 +93,7 @@ impl VerifyingAlgorithm for PKeyWithDigest { /// OpenSSL by default signs ECDSA in DER, but JOSE expects them in a concatenated (R, S) format fn der_to_jose(der: &[u8]) -> Result, Error> { - let signature = EcdsaSig::from_der(&der)?; + let signature = EcdsaSig::from_der(der)?; let r = signature.r().to_vec(); let s = signature.s().to_vec(); Ok([r, s].concat()) @@ -119,10 +120,10 @@ mod tests { use openssl::pkey::PKey; // {"sub":"1234567890","name":"John Doe","admin":true} - const CLAIMS: &'static str = + const CLAIMS: &str = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; - const RS256_SIGNATURE: &'static str = + const RS256_SIGNATURE: &str = "cQsAHF2jHvPGFP5zTD8BgoJrnzEx6JNQCpupebWLFnOc2r_punDDTylI6Ia4JZNkvy2dQP-7W-DEbFQ3oaarHsDndqUgwf9iYlDQxz4Rr2nEZX1FX0-FMEgFPeQpdwveCgjtTYUbVy37ijUySN_rW-xZTrsh_Ug-ica8t-zHRIw"; #[test] @@ -172,7 +173,7 @@ mod tests { }; let verification_result = - public_key.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &*signature)?; + public_key.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &signature)?; assert!(verification_result); Ok(()) } diff --git a/src/algorithm/rust_crypto.rs b/src/algorithm/rust_crypto.rs index ed7cc53d..f0ff3b5e 100644 --- a/src/algorithm/rust_crypto.rs +++ b/src/algorithm/rust_crypto.rs @@ -2,6 +2,7 @@ //! According to that organization, only hmac is safely implemented at the //! moment. +use base64::Engine; use digest::{ block_buffer::Eager, consts::U256, @@ -56,7 +57,7 @@ where let hmac = get_hmac_with_data(self, header, claims); let mac_result = hmac.finalize(); let code = mac_result.into_bytes(); - Ok(base64::encode_config(&code, base64::URL_SAFE_NO_PAD)) + Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(code)) } } diff --git a/src/lib.rs b/src/lib.rs index 85a99cd6..46dc2410 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ doctest!("../README.md"); use std::borrow::Cow; +use base64::Engine; #[cfg(doctest)] use doc_comment::doctest; use serde::{Deserialize, Serialize}; @@ -163,7 +164,8 @@ pub trait ToBase64 { impl ToBase64 for T { fn to_base64(&self) -> Result, Error> { let json_bytes = serde_json::to_vec(&self)?; - let encoded_json_bytes = base64::encode_config(&json_bytes, base64::URL_SAFE_NO_PAD); + let encoded_json_bytes = + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(json_bytes); Ok(Cow::Owned(encoded_json_bytes)) } } @@ -180,7 +182,7 @@ pub trait FromBase64: Sized { impl Deserialize<'de> + Sized> FromBase64 for T { fn from_base64>(raw: &Input) -> Result { - let json_bytes = base64::decode_config(raw, base64::URL_SAFE_NO_PAD)?; + let json_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(raw)?; Ok(serde_json::from_slice(&json_bytes)?) } } diff --git a/src/token/signed.rs b/src/token/signed.rs index 6cda0123..014f86fb 100644 --- a/src/token/signed.rs +++ b/src/token/signed.rs @@ -124,7 +124,7 @@ where } } -impl<'a, H, C> Token { +impl Token { /// Get the string representation of the token. pub fn as_str(&self) -> &str { &self.signature.token_string From 312e1276a4511ebfaa57a612168fc8576c08d04c Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 20 Nov 2023 23:06:27 +0000 Subject: [PATCH 2/6] feat: pkey impl without openssl --- Cargo.toml | 7 +- src/algorithm/mod.rs | 3 +- src/algorithm/openssl.rs | 29 ++- src/algorithm/rust_crypto.rs | 5 + src/algorithm/rust_crypto/pkey.rs | 373 ++++++++++++++++++++++++++++++ src/error.rs | 5 + test/es256-private.pem | 10 +- test/rs256-private.pem | 19 +- test/rs256-public.pem | 5 +- 9 files changed, 439 insertions(+), 17 deletions(-) create mode 100644 src/algorithm/rust_crypto/pkey.rs diff --git a/Cargo.toml b/Cargo.toml index e9795f83..4ed57212 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,16 @@ default = [] [dependencies] base64 = "0.21" crypto-common = "0.1" -digest = "0.10" +digest = { version = "0.10", features = ["oid"] } +signature = { version = "2.2", features = ["digest"] } hmac = { version = "0.12", features = ["reset"] } sha2 = "0.10" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +ecdsa = { version = "0.16", features = ["verifying", "signing"] } +rsa = { version = "0.9", features = ["sha2"] } +p256 = { version = "0.13", features = ["pem"] } +pem = "3.0" [dependencies.openssl] version = "0.10" diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index e2390b05..f59a17f4 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -17,6 +17,7 @@ use crate::error::Error; #[cfg(feature = "openssl")] pub mod openssl; + pub mod rust_crypto; pub mod store; @@ -43,8 +44,6 @@ pub enum AlgorithmType { None, } - - /// An algorithm capable of signing base64 encoded header and claims strings. /// strings. pub trait SigningAlgorithm { diff --git a/src/algorithm/openssl.rs b/src/algorithm/openssl.rs index e4230bb0..ca61b8e3 100644 --- a/src/algorithm/openssl.rs +++ b/src/algorithm/openssl.rs @@ -120,12 +120,14 @@ mod tests { use openssl::pkey::PKey; // {"sub":"1234567890","name":"John Doe","admin":true} - const CLAIMS: &str = - "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; + const CLAIMS: &str = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; const RS256_SIGNATURE: &str = "cQsAHF2jHvPGFP5zTD8BgoJrnzEx6JNQCpupebWLFnOc2r_punDDTylI6Ia4JZNkvy2dQP-7W-DEbFQ3oaarHsDndqUgwf9iYlDQxz4Rr2nEZX1FX0-FMEgFPeQpdwveCgjtTYUbVy37ijUySN_rW-xZTrsh_Ug-ica8t-zHRIw"; + const PREGENERATED_ES256_SIGNATURE: &str = + "6SgeIURSNz_qFcxsKQOWZmi_ALiBctj_ZINvce4AOa-OQn9QI6lh8P78FTZx5LQtOleF3XeBlGIAdYms_VPecA"; + #[test] fn rs256_sign() -> Result<(), Error> { let pem = include_bytes!("../../test/rs256-private.pem"); @@ -156,7 +158,7 @@ mod tests { } #[test] - fn es256() -> Result<(), Error> { + fn es256_sign() -> Result<(), Error> { let private_pem = include_bytes!("../../test/es256-private.pem"); let private_key = PKeyWithDigest { digest: MessageDigest::sha256(), @@ -164,9 +166,7 @@ mod tests { }; let signature = private_key.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?; - let public_pem = include_bytes!("../../test/es256-public.pem"); - let public_key = PKeyWithDigest { digest: MessageDigest::sha256(), key: PKey::public_key_from_pem(public_pem)?, @@ -174,7 +174,26 @@ mod tests { let verification_result = public_key.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &signature)?; + + assert!(verification_result); + Ok(()) + } + + #[test] + fn es256_verify() -> Result<(), Error> { + let public_pem = include_bytes!("../../test/es256-public.pem"); + let public_key = PKeyWithDigest { + digest: MessageDigest::sha256(), + key: PKey::public_key_from_pem(public_pem)?, + }; + + let verification_result = public_key.verify( + &AlgOnly(Es256).to_base64()?, + CLAIMS, + PREGENERATED_ES256_SIGNATURE, + )?; assert!(verification_result); + Ok(()) } } diff --git a/src/algorithm/rust_crypto.rs b/src/algorithm/rust_crypto.rs index f0ff3b5e..5385e8da 100644 --- a/src/algorithm/rust_crypto.rs +++ b/src/algorithm/rust_crypto.rs @@ -15,6 +15,11 @@ use hmac::{Hmac, Mac}; use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; use crate::error::Error; use crate::SEPARATOR; + +mod pkey; + +pub use pkey::*; + /// A trait used to make the implementation of `SigningAlgorithm` and /// `VerifyingAlgorithm` easier. /// RustCrypto crates tend to have algorithm types defined at the type level, diff --git a/src/algorithm/rust_crypto/pkey.rs b/src/algorithm/rust_crypto/pkey.rs new file mode 100644 index 00000000..be841033 --- /dev/null +++ b/src/algorithm/rust_crypto/pkey.rs @@ -0,0 +1,373 @@ +use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; +use crate::error::Error; +use crate::SEPARATOR; + +use base64::Engine; +use crypto_common::generic_array::ArrayLength; +use digest::{Digest, FixedOutput}; +use ecdsa::elliptic_curve::ops::Invert; +use ecdsa::elliptic_curve::subtle::CtOption; +use ecdsa::elliptic_curve::{CurveArithmetic, FieldBytesSize, Scalar}; +use ecdsa::hazmat::SignPrimitive; +use ecdsa::{PrimeCurve, SignatureSize}; +use p256::pkcs8::{DecodePrivateKey, DecodePublicKey}; +use p256::NistP256; +use rsa::pkcs1::DecodeRsaPrivateKey; +use signature::{DigestSigner, DigestVerifier, SignatureEncoding}; + +#[derive(Clone, Debug)] +pub enum PublicKey { + RSA(rsa::pkcs1v15::VerifyingKey), + EC(p256::ecdsa::VerifyingKey), +} + +impl PublicKey { + pub fn from_pem_bytes(encoded: &[u8]) -> Result { + Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) + } + + pub fn from_pem(encoded: &str) -> Result { + if let Ok(ec) = encoded.parse::() { + Ok(PublicKey::EC(ecdsa::VerifyingKey::from(ec))) + } else if let Ok(rsa) = rsa::RsaPublicKey::from_public_key_pem(encoded) { + Ok(PublicKey::RSA(rsa::pkcs1v15::VerifyingKey::new(rsa))) + } else { + Err(Error::InvalidKey) + } + } +} + +impl DigestVerifier for PublicKey { + fn verify_digest( + &self, + digest: D, + signature: &rsa::pkcs1v15::Signature, + ) -> Result<(), signature::Error> { + match self { + PublicKey::RSA(key) => key.verify_digest(digest, signature), + PublicKey::EC(_) => Err(signature::Error::new()), + } + } +} + +impl DigestVerifier for PublicKey +where + D: FixedOutput>, +{ + fn verify_digest( + &self, + digest: D, + signature: &p256::ecdsa::Signature, + ) -> Result<(), signature::Error> { + match self { + PublicKey::RSA(_) => Err(signature::Error::new()), + PublicKey::EC(key) => key.verify_digest(digest, signature), + } + } +} + +#[derive(Clone, Debug)] +pub enum PrivateKey { + RSA(Box>), + EC(p256::ecdsa::SigningKey), +} + +impl PrivateKey { + pub fn from_pem_bytes(encoded: &[u8]) -> Result { + Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) + } + + pub fn from_pem(pem: &str) -> Result { + if let Ok(ec) = pem.parse::() { + Ok(PrivateKey::EC(ecdsa::SigningKey::from(ec))) + } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs8_pem(pem) { + Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new(rsa)))) + } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs1_pem(pem) { + Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new(rsa)))) + } else { + Err(Error::InvalidKey) + } + } +} + +impl DigestSigner for PrivateKey { + fn try_sign_digest(&self, digest: D) -> Result { + match self { + PrivateKey::RSA(key) => key.try_sign_digest(digest), + PrivateKey::EC(_) => Err(signature::Error::new()), + } + } +} + +impl DigestSigner for PrivateKey +where + D: FixedOutput>, +{ + fn try_sign_digest(&self, digest: D) -> Result { + match self { + PrivateKey::RSA(_) => Err(signature::Error::new()), + PrivateKey::EC(key) => key.try_sign_digest(digest), + } + } +} + +pub struct PKeyWithDigest { + key: K, + _marker: std::marker::PhantomData<(S, D)>, +} + +impl PKeyWithDigest { + pub fn new(key: K) -> Self { + Self { + key, + _marker: std::marker::PhantomData, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DigestType { + SHA256, + SHA384, + SHA512, +} + +pub trait SupportedPKeyDigest: Digest + digest::const_oid::AssociatedOid { + fn digest_type() -> DigestType; +} + +impl SupportedPKeyDigest for sha2::Sha256 { + fn digest_type() -> DigestType { + DigestType::SHA256 + } +} + +impl SupportedPKeyDigest for sha2::Sha384 { + fn digest_type() -> DigestType { + DigestType::SHA384 + } +} + +impl SupportedPKeyDigest for sha2::Sha512 { + fn digest_type() -> DigestType { + DigestType::SHA512 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PKeyType { + EC, + RSA, +} + +pub trait SupportedPKey { + fn key_type(&self) -> PKeyType; +} + +impl SupportedPKey for ecdsa::SigningKey +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + fn key_type(&self) -> PKeyType { + PKeyType::EC + } +} + +impl SupportedPKey for ecdsa::VerifyingKey +where + C: PrimeCurve + CurveArithmetic, +{ + fn key_type(&self) -> PKeyType { + PKeyType::EC + } +} + +impl SupportedPKey for rsa::pkcs1v15::SigningKey { + fn key_type(&self) -> PKeyType { + PKeyType::RSA + } +} + +impl SupportedPKey for rsa::pkcs1v15::VerifyingKey { + fn key_type(&self) -> PKeyType { + PKeyType::RSA + } +} + +impl SupportedPKey for PublicKey { + fn key_type(&self) -> PKeyType { + match self { + PublicKey::RSA(_) => PKeyType::RSA, + PublicKey::EC(_) => PKeyType::EC, + } + } +} + +impl SupportedPKey for PrivateKey { + fn key_type(&self) -> PKeyType { + match self { + PrivateKey::RSA(_) => PKeyType::RSA, + PrivateKey::EC(_) => PKeyType::EC, + } + } +} + +impl PKeyWithDigest { + fn algorithm_type(&self) -> AlgorithmType { + match (self.key.key_type(), D::digest_type()) { + (PKeyType::RSA, DigestType::SHA256) => AlgorithmType::Rs256, + (PKeyType::RSA, DigestType::SHA384) => AlgorithmType::Rs384, + (PKeyType::RSA, DigestType::SHA512) => AlgorithmType::Rs512, + (PKeyType::EC, DigestType::SHA256) => AlgorithmType::Es256, + (PKeyType::EC, DigestType::SHA384) => AlgorithmType::Es384, + (PKeyType::EC, DigestType::SHA512) => AlgorithmType::Es512, + } + } +} + +impl + SupportedPKey> + SigningAlgorithm for PKeyWithDigest +{ + fn algorithm_type(&self) -> AlgorithmType { + PKeyWithDigest::algorithm_type(self) + } + + fn sign(&self, header: &str, claims: &str) -> Result { + let mut digest = D::new(); + + digest.update(header.as_bytes()); + digest.update(SEPARATOR.as_bytes()); + digest.update(claims.as_bytes()); + + let signature = self.key.try_sign_digest(digest)?; + + Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(signature.to_bytes())) + } +} + +impl + SupportedPKey> + VerifyingAlgorithm for PKeyWithDigest +{ + fn algorithm_type(&self) -> AlgorithmType { + PKeyWithDigest::algorithm_type(self) + } + + fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { + let mut digest = D::new(); + + digest.update(header.as_bytes()); + digest.update(SEPARATOR.as_bytes()); + digest.update(claims.as_bytes()); + + let signature = S::try_from(signature).map_err(|_| Error::InvalidSignature)?; + + Ok(self.key.verify_digest(digest, &signature).is_ok()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::algorithm::AlgorithmType::*; + use crate::algorithm::{SigningAlgorithm, VerifyingAlgorithm}; + use crate::error::Error; + use crate::header::PrecomputedAlgorithmOnlyHeader as AlgOnly; + use crate::ToBase64; + + // {"sub":"1234567890","name":"John Doe","admin":true} + const CLAIMS: &str = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"; + + const RS256_SIGNATURE: &str = + "cQsAHF2jHvPGFP5zTD8BgoJrnzEx6JNQCpupebWLFnOc2r_punDDTylI6Ia4JZNkvy2dQP-7W-DEbFQ3oaarHsDndqUgwf9iYlDQxz4Rr2nEZX1FX0-FMEgFPeQpdwveCgjtTYUbVy37ijUySN_rW-xZTrsh_Ug-ica8t-zHRIw"; + + const PREGENERATED_ES256_SIGNATURE: &str = + "6SgeIURSNz_qFcxsKQOWZmi_ALiBctj_ZINvce4AOa-OQn9QI6lh8P78FTZx5LQtOleF3XeBlGIAdYms_VPecA"; + + #[test] + fn rs256_sign() -> Result<(), Error> { + let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/rs256-private.pem"))?; + let algorithm = PKeyWithDigest::::new(key); + let result = algorithm.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?; + assert_eq!(result, RS256_SIGNATURE); + Ok(()) + } + + #[test] + fn rs256_verify() -> Result<(), Error> { + let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/rs256-public.pem"))?; + let verifier = PKeyWithDigest::::new(key); + assert!( + verifier.verify(&AlgOnly(Rs256).to_base64()?, CLAIMS, RS256_SIGNATURE)?, + "signature should be valid" + ); + Ok(()) + } + + #[test] + fn es256_sign() -> Result<(), Error> { + let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/es256-private.pem"))?; + let signer = PKeyWithDigest::::new(key); + let signature = signer.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?; + + let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; + let verifier = PKeyWithDigest::::new(key); + assert!( + verifier.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &signature)?, + "signature should be valid" + ); + Ok(()) + } + + #[test] + fn es256_verify() -> Result<(), Error> { + let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; + let verifier = PKeyWithDigest::::new(key); + assert!( + verifier.verify( + &AlgOnly(Es256).to_base64()?, + CLAIMS, + PREGENERATED_ES256_SIGNATURE + )?, + "signature should be valid" + ); + + Ok(()) + } + + #[test] + fn genric_public_key_parse() -> Result<(), Error> { + let pem = include_bytes!("../../../test/rs256-public.pem"); + super::PublicKey::::from_pem( + std::str::from_utf8(pem).expect("invalid utf8 rsa key"), + ) + .expect("invalid rsa key"); + + let pem = include_bytes!("../../../test/es256-public.pem"); + super::PublicKey::::from_pem( + std::str::from_utf8(pem).expect("invalid utf8 ec key"), + ) + .expect("invalid ec key"); + + Ok(()) + } + + #[test] + fn genric_private_key_parse() -> Result<(), Error> { + let pem = include_bytes!("../../../test/rs256-private.pem"); + super::PrivateKey::::from_pem( + std::str::from_utf8(pem).expect("invalid utf8 rsa key"), + ) + .expect("invalid rsa key"); + + let pem = include_bytes!("../../../test/es256-private.pem"); + super::PrivateKey::::from_pem( + std::str::from_utf8(pem).expect("invalid utf8 ec key"), + ) + .expect("invalid ec key"); + + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 1e9f3762..5912fdff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,8 @@ pub enum Error { RustCryptoMacKeyLength(InvalidLength), TooManyComponents, Utf8(FromUtf8Error), + Signature(signature::Error), + InvalidKey, #[cfg(feature = "openssl")] OpenSsl(openssl::error::ErrorStack), } @@ -48,6 +50,8 @@ impl fmt::Display for Error { Utf8(ref x) => write!(f, "{}", x), RustCryptoMac(ref x) => write!(f, "{}", x), RustCryptoMacKeyLength(ref x) => write!(f, "{}", x), + Signature(ref x) => write!(f, "{}", x), + InvalidKey => write!(f, "Invalid key"), #[cfg(feature = "openssl")] OpenSsl(ref x) => write!(f, "{}", x), } @@ -71,5 +75,6 @@ error_wrap!(JsonError, Json); error_wrap!(FromUtf8Error, Utf8); error_wrap!(MacError, RustCryptoMac); error_wrap!(InvalidLength, RustCryptoMacKeyLength); +error_wrap!(signature::Error, Signature); #[cfg(feature = "openssl")] error_wrap!(openssl::error::ErrorStack, Error::OpenSsl); diff --git a/test/es256-private.pem b/test/es256-private.pem index 8548ca88..4d834eaf 100644 --- a/test/es256-private.pem +++ b/test/es256-private.pem @@ -1,5 +1,5 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIDaCNjB5eSARlkILmSrpnfoI8TYi7tJ7xzpy4Bb1D6HVoAoGCCqGSM49 -AwEHoUQDQgAEU9PK2chNDTNdJygt1lkhLwuhLOkcuG90J5kHbnHNC/PG9ww1D7q0 -d9dWWzfRfZfx3Duw+3j8NnBt6zBgnUG5Uw== ------END EC PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNoI2MHl5IBGWQguZ +Kumd+gjxNiLu0nvHOnLgFvUPodWhRANCAART08rZyE0NM10nKC3WWSEvC6Es6Ry4 +b3QnmQducc0L88b3DDUPurR311ZbN9F9l/HcO7D7ePw2cG3rMGCdQblT +-----END PRIVATE KEY----- diff --git a/test/rs256-private.pem b/test/rs256-private.pem index 3b15b704..5202dfbc 100644 --- a/test/rs256-private.pem +++ b/test/rs256-private.pem @@ -1,3 +1,16 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQABAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5CpuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0KSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAN2Vq1GNGOiCjdai +OAYcUdgu6B1RYBj2JHd/LhqtY0DUqhLyRXDfdwmJtevxu/BQBSlqsLCW91sfp28Q +5+i7T+AIVCwdR9CtIO/4y5JQwB7yPMoTipb6Mr7FBT1rTcZScoeSSV75DSlf+DqN +dnuvX/EArkOjaRD5fnEr1yKlGAQrAgMBAAECgYAP6icC1XJ7iJztVHtXnQMX1s6f +BHA1esBFwOGr0tY1GtEYSURJhhNDoRtq1dLcwLKONfZ4yG79oXliFcHCSmnDGD4Q +6Zd6H4zMvtmHbzg649uSCUBoaFX1tvr/kKm4ZvV6zI9thb5PLY8LQZJl8heRezvq +NcC71M4G8PTJtzZiWQJBAPLCDZH/u3dDY4Tb7KjfzYIsl2uVItVE5YrBvi1vY+OF +jhcDBXx3W/LRF6fFMH4rky7nu5VJMe2swrQYC0WvzfcCQQDpq/HbQ4ejrVh6Vr/i +TmRAO2MR/U3Qt8j4FxZm3GxLeMMzeLUenQpK7muwI9vxpwmjlE7bqAZWt8CwNwhc +oL5tAkBlnZcimyK0vI+m6Iw68FvM9q93iBjpnwpat9jMmgj9D0W4GjqsrCXgEWhO +gPtYhRL6GmRqDBaLP7rMuhfV1s5nAkAKYfl9JKMCQtGLng8onxMVR44/XmH2xDPJ +0jzMlT66m8MQpmxlz9SFP9LJIM7FDgb/nbdjSzP85m1JZyiX9QyBAkAt6zL74Z2+ +ggMt8lfkYFCm4WAV6dGJENpvGsT0w1RnZo8VoWn4PIB75vicPC4lpbYtRFuvwARm +BruhCPS5jFxj +-----END PRIVATE KEY----- diff --git a/test/rs256-public.pem b/test/rs256-public.pem index 4f194663..c6bbd08e 100644 --- a/test/rs256-public.pem +++ b/test/rs256-public.pem @@ -1,3 +1,6 @@ -----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd +UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs +HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D +o2kQ+X5xK9cipRgEKwIDAQAB -----END PUBLIC KEY----- From 1ee11142d0ced741c869beafb32035f571aad574 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 20 Nov 2023 23:20:44 +0000 Subject: [PATCH 3/6] chore: rename pkey --- src/algorithm/rust_crypto.rs | 4 +- .../rust_crypto/{pkey.rs => asymmetric.rs} | 90 +++++++++++-------- src/lib.rs | 1 + 3 files changed, 57 insertions(+), 38 deletions(-) rename src/algorithm/rust_crypto/{pkey.rs => asymmetric.rs} (77%) diff --git a/src/algorithm/rust_crypto.rs b/src/algorithm/rust_crypto.rs index 5385e8da..35c5b89d 100644 --- a/src/algorithm/rust_crypto.rs +++ b/src/algorithm/rust_crypto.rs @@ -16,9 +16,7 @@ use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; use crate::error::Error; use crate::SEPARATOR; -mod pkey; - -pub use pkey::*; +pub mod asymmetric; /// A trait used to make the implementation of `SigningAlgorithm` and /// `VerifyingAlgorithm` easier. diff --git a/src/algorithm/rust_crypto/pkey.rs b/src/algorithm/rust_crypto/asymmetric.rs similarity index 77% rename from src/algorithm/rust_crypto/pkey.rs rename to src/algorithm/rust_crypto/asymmetric.rs index be841033..9c64d1ac 100644 --- a/src/algorithm/rust_crypto/pkey.rs +++ b/src/algorithm/rust_crypto/asymmetric.rs @@ -16,12 +16,12 @@ use rsa::pkcs1::DecodeRsaPrivateKey; use signature::{DigestSigner, DigestVerifier, SignatureEncoding}; #[derive(Clone, Debug)] -pub enum PublicKey { +pub enum PublicKey { RSA(rsa::pkcs1v15::VerifyingKey), EC(p256::ecdsa::VerifyingKey), } -impl PublicKey { +impl PublicKey { pub fn from_pem_bytes(encoded: &[u8]) -> Result { Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) } @@ -37,7 +37,7 @@ impl PublicKey { } } -impl DigestVerifier for PublicKey { +impl DigestVerifier for PublicKey { fn verify_digest( &self, digest: D, @@ -50,7 +50,7 @@ impl DigestVerifier for Pub } } -impl DigestVerifier for PublicKey +impl DigestVerifier for PublicKey where D: FixedOutput>, { @@ -67,12 +67,12 @@ where } #[derive(Clone, Debug)] -pub enum PrivateKey { +pub enum PrivateKey { RSA(Box>), EC(p256::ecdsa::SigningKey), } -impl PrivateKey { +impl PrivateKey { pub fn from_pem_bytes(encoded: &[u8]) -> Result { Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) } @@ -81,16 +81,20 @@ impl PrivateKey { if let Ok(ec) = pem.parse::() { Ok(PrivateKey::EC(ecdsa::SigningKey::from(ec))) } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs8_pem(pem) { - Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new(rsa)))) + Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new( + rsa, + )))) } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs1_pem(pem) { - Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new(rsa)))) + Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new( + rsa, + )))) } else { Err(Error::InvalidKey) } } } -impl DigestSigner for PrivateKey { +impl DigestSigner for PrivateKey { fn try_sign_digest(&self, digest: D) -> Result { match self { PrivateKey::RSA(key) => key.try_sign_digest(digest), @@ -99,7 +103,7 @@ impl DigestSigner for Priva } } -impl DigestSigner for PrivateKey +impl DigestSigner for PrivateKey where D: FixedOutput>, { @@ -111,12 +115,18 @@ where } } -pub struct PKeyWithDigest { +pub struct AsymmetricKeyWithDigest< + D: SupportedAsymmetricDigest, + S: SignatureEncoding, + K: SupportedAsymmetricKey, +> { key: K, _marker: std::marker::PhantomData<(S, D)>, } -impl PKeyWithDigest { +impl + AsymmetricKeyWithDigest +{ pub fn new(key: K) -> Self { Self { key, @@ -132,23 +142,23 @@ pub enum DigestType { SHA512, } -pub trait SupportedPKeyDigest: Digest + digest::const_oid::AssociatedOid { +pub trait SupportedAsymmetricDigest: Digest + digest::const_oid::AssociatedOid { fn digest_type() -> DigestType; } -impl SupportedPKeyDigest for sha2::Sha256 { +impl SupportedAsymmetricDigest for sha2::Sha256 { fn digest_type() -> DigestType { DigestType::SHA256 } } -impl SupportedPKeyDigest for sha2::Sha384 { +impl SupportedAsymmetricDigest for sha2::Sha384 { fn digest_type() -> DigestType { DigestType::SHA384 } } -impl SupportedPKeyDigest for sha2::Sha512 { +impl SupportedAsymmetricDigest for sha2::Sha512 { fn digest_type() -> DigestType { DigestType::SHA512 } @@ -160,11 +170,11 @@ pub enum PKeyType { RSA, } -pub trait SupportedPKey { +pub trait SupportedAsymmetricKey { fn key_type(&self) -> PKeyType; } -impl SupportedPKey for ecdsa::SigningKey +impl SupportedAsymmetricKey for ecdsa::SigningKey where C: PrimeCurve + CurveArithmetic, Scalar: Invert>> + SignPrimitive, @@ -175,7 +185,7 @@ where } } -impl SupportedPKey for ecdsa::VerifyingKey +impl SupportedAsymmetricKey for ecdsa::VerifyingKey where C: PrimeCurve + CurveArithmetic, { @@ -184,19 +194,19 @@ where } } -impl SupportedPKey for rsa::pkcs1v15::SigningKey { +impl SupportedAsymmetricKey for rsa::pkcs1v15::SigningKey { fn key_type(&self) -> PKeyType { PKeyType::RSA } } -impl SupportedPKey for rsa::pkcs1v15::VerifyingKey { +impl SupportedAsymmetricKey for rsa::pkcs1v15::VerifyingKey { fn key_type(&self) -> PKeyType { PKeyType::RSA } } -impl SupportedPKey for PublicKey { +impl SupportedAsymmetricKey for PublicKey { fn key_type(&self) -> PKeyType { match self { PublicKey::RSA(_) => PKeyType::RSA, @@ -205,7 +215,7 @@ impl SupportedPKey for PublicKey { } } -impl SupportedPKey for PrivateKey { +impl SupportedAsymmetricKey for PrivateKey { fn key_type(&self) -> PKeyType { match self { PrivateKey::RSA(_) => PKeyType::RSA, @@ -214,7 +224,9 @@ impl SupportedPKey for PrivateKey { } } -impl PKeyWithDigest { +impl + AsymmetricKeyWithDigest +{ fn algorithm_type(&self) -> AlgorithmType { match (self.key.key_type(), D::digest_type()) { (PKeyType::RSA, DigestType::SHA256) => AlgorithmType::Rs256, @@ -227,11 +239,14 @@ impl PKeyWithDig } } -impl + SupportedPKey> - SigningAlgorithm for PKeyWithDigest +impl< + D: SupportedAsymmetricDigest, + S: SignatureEncoding, + K: DigestSigner + SupportedAsymmetricKey, + > SigningAlgorithm for AsymmetricKeyWithDigest { fn algorithm_type(&self) -> AlgorithmType { - PKeyWithDigest::algorithm_type(self) + AsymmetricKeyWithDigest::algorithm_type(self) } fn sign(&self, header: &str, claims: &str) -> Result { @@ -247,11 +262,14 @@ impl + Suppo } } -impl + SupportedPKey> - VerifyingAlgorithm for PKeyWithDigest +impl< + D: SupportedAsymmetricDigest, + S: SignatureEncoding, + K: DigestVerifier + SupportedAsymmetricKey, + > VerifyingAlgorithm for AsymmetricKeyWithDigest { fn algorithm_type(&self) -> AlgorithmType { - PKeyWithDigest::algorithm_type(self) + AsymmetricKeyWithDigest::algorithm_type(self) } fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { @@ -289,7 +307,8 @@ mod tests { #[test] fn rs256_sign() -> Result<(), Error> { let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/rs256-private.pem"))?; - let algorithm = PKeyWithDigest::::new(key); + let algorithm = + AsymmetricKeyWithDigest::::new(key); let result = algorithm.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?; assert_eq!(result, RS256_SIGNATURE); Ok(()) @@ -298,7 +317,8 @@ mod tests { #[test] fn rs256_verify() -> Result<(), Error> { let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/rs256-public.pem"))?; - let verifier = PKeyWithDigest::::new(key); + let verifier = + AsymmetricKeyWithDigest::::new(key); assert!( verifier.verify(&AlgOnly(Rs256).to_base64()?, CLAIMS, RS256_SIGNATURE)?, "signature should be valid" @@ -309,11 +329,11 @@ mod tests { #[test] fn es256_sign() -> Result<(), Error> { let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/es256-private.pem"))?; - let signer = PKeyWithDigest::::new(key); + let signer = AsymmetricKeyWithDigest::::new(key); let signature = signer.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?; let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; - let verifier = PKeyWithDigest::::new(key); + let verifier = AsymmetricKeyWithDigest::::new(key); assert!( verifier.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &signature)?, "signature should be valid" @@ -324,7 +344,7 @@ mod tests { #[test] fn es256_verify() -> Result<(), Error> { let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; - let verifier = PKeyWithDigest::::new(key); + let verifier = AsymmetricKeyWithDigest::::new(key); assert!( verifier.verify( &AlgOnly(Es256).to_base64()?, diff --git a/src/lib.rs b/src/lib.rs index 46dc2410..f9095ea9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "openssl")] pub use crate::algorithm::openssl::PKeyWithDigest; +pub use crate::algorithm::rust_crypto::asymmetric; pub use crate::algorithm::store::Store; pub use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm}; pub use crate::claims::Claims; From f45de03839a6a21a529ef7bbbf689141c27d860e Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Tue, 21 Nov 2023 00:31:34 +0000 Subject: [PATCH 4/6] fix: simplify api --- src/algorithm/rust_crypto/asymmetric.rs | 404 ++++++++++-------------- 1 file changed, 175 insertions(+), 229 deletions(-) diff --git a/src/algorithm/rust_crypto/asymmetric.rs b/src/algorithm/rust_crypto/asymmetric.rs index 9c64d1ac..672513f7 100644 --- a/src/algorithm/rust_crypto/asymmetric.rs +++ b/src/algorithm/rust_crypto/asymmetric.rs @@ -3,285 +3,233 @@ use crate::error::Error; use crate::SEPARATOR; use base64::Engine; -use crypto_common::generic_array::ArrayLength; -use digest::{Digest, FixedOutput}; -use ecdsa::elliptic_curve::ops::Invert; -use ecdsa::elliptic_curve::subtle::CtOption; -use ecdsa::elliptic_curve::{CurveArithmetic, FieldBytesSize, Scalar}; -use ecdsa::hazmat::SignPrimitive; -use ecdsa::{PrimeCurve, SignatureSize}; +use digest::Digest; use p256::pkcs8::{DecodePrivateKey, DecodePublicKey}; -use p256::NistP256; use rsa::pkcs1::DecodeRsaPrivateKey; +use rsa::{RsaPrivateKey, RsaPublicKey}; use signature::{DigestSigner, DigestVerifier, SignatureEncoding}; #[derive(Clone, Debug)] -pub enum PublicKey { - RSA(rsa::pkcs1v15::VerifyingKey), - EC(p256::ecdsa::VerifyingKey), +pub enum VerifyingKey { + RS256(Box>), + RS384(Box>), + RS512(Box>), + EC256(Box), } -impl PublicKey { +impl VerifyingKey { + pub fn from_ec256(key: p256::PublicKey) -> Self { + Self::EC256(p256::ecdsa::VerifyingKey::from(key).into()) + } + + pub fn from_rsa256(key: RsaPublicKey) -> Self { + Self::RS256(rsa::pkcs1v15::VerifyingKey::new(key).into()) + } + + pub fn from_rsa384(key: RsaPublicKey) -> Self { + Self::RS384(rsa::pkcs1v15::VerifyingKey::new(key).into()) + } + + pub fn from_rsa512(key: RsaPublicKey) -> Self { + Self::RS512(rsa::pkcs1v15::VerifyingKey::new(key).into()) + } +} + +#[derive(Clone, Debug)] +pub enum SigningKey { + RS256(Box>), + RS384(Box>), + RS512(Box>), + EC256(Box), +} + +impl SigningKey { + pub fn from_ec256(key: p256::SecretKey) -> Self { + Self::EC256(p256::ecdsa::SigningKey::from(key).into()) + } + + pub fn from_rsa256(key: RsaPrivateKey) -> Self { + Self::RS256(rsa::pkcs1v15::SigningKey::new(key).into()) + } + + pub fn from_rsa384(key: RsaPrivateKey) -> Self { + Self::RS384(rsa::pkcs1v15::SigningKey::new(key).into()) + } + + pub fn from_rsa512(key: RsaPrivateKey) -> Self { + Self::RS512(rsa::pkcs1v15::SigningKey::new(key).into()) + } +} + +pub use ::{digest, ecdsa, p256, rsa, signature}; + +#[derive(Clone, Debug)] +pub enum PublicKey { + RSA(Box), + EC256(Box), +} + +impl PublicKey { pub fn from_pem_bytes(encoded: &[u8]) -> Result { Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) } pub fn from_pem(encoded: &str) -> Result { if let Ok(ec) = encoded.parse::() { - Ok(PublicKey::EC(ecdsa::VerifyingKey::from(ec))) + Ok(PublicKey::EC256(ec.into())) } else if let Ok(rsa) = rsa::RsaPublicKey::from_public_key_pem(encoded) { - Ok(PublicKey::RSA(rsa::pkcs1v15::VerifyingKey::new(rsa))) + Ok(PublicKey::RSA(rsa.into())) } else { Err(Error::InvalidKey) } } -} -impl DigestVerifier for PublicKey { - fn verify_digest( - &self, - digest: D, - signature: &rsa::pkcs1v15::Signature, - ) -> Result<(), signature::Error> { + pub fn into_rsa(self) -> Result { match self { - PublicKey::RSA(key) => key.verify_digest(digest, signature), - PublicKey::EC(_) => Err(signature::Error::new()), + PublicKey::RSA(rsa) => Ok(*rsa), + _ => Err(self), } } -} -impl DigestVerifier for PublicKey -where - D: FixedOutput>, -{ - fn verify_digest( - &self, - digest: D, - signature: &p256::ecdsa::Signature, - ) -> Result<(), signature::Error> { + pub fn into_ec256(self) -> Result { match self { - PublicKey::RSA(_) => Err(signature::Error::new()), - PublicKey::EC(key) => key.verify_digest(digest, signature), + PublicKey::EC256(ec) => Ok(*ec), + _ => Err(self), } } } #[derive(Clone, Debug)] -pub enum PrivateKey { - RSA(Box>), - EC(p256::ecdsa::SigningKey), +pub enum PrivateKey { + RSA(Box), + EC256(Box), } -impl PrivateKey { +impl PrivateKey { pub fn from_pem_bytes(encoded: &[u8]) -> Result { Self::from_pem(std::str::from_utf8(encoded).map_err(|_| Error::InvalidKey)?) } pub fn from_pem(pem: &str) -> Result { if let Ok(ec) = pem.parse::() { - Ok(PrivateKey::EC(ecdsa::SigningKey::from(ec))) + Ok(PrivateKey::EC256(ec.into())) } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs8_pem(pem) { - Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new( - rsa, - )))) + Ok(PrivateKey::RSA(rsa.into())) } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs1_pem(pem) { - Ok(PrivateKey::RSA(Box::new(rsa::pkcs1v15::SigningKey::new( - rsa, - )))) + Ok(PrivateKey::RSA(rsa.into())) } else { Err(Error::InvalidKey) } } -} -impl DigestSigner for PrivateKey { - fn try_sign_digest(&self, digest: D) -> Result { + pub fn into_rsa(self) -> Result { match self { - PrivateKey::RSA(key) => key.try_sign_digest(digest), - PrivateKey::EC(_) => Err(signature::Error::new()), + PrivateKey::RSA(rsa) => Ok(*rsa), + _ => Err(self), } } -} -impl DigestSigner for PrivateKey -where - D: FixedOutput>, -{ - fn try_sign_digest(&self, digest: D) -> Result { + pub fn into_ec256(self) -> Result { match self { - PrivateKey::RSA(_) => Err(signature::Error::new()), - PrivateKey::EC(key) => key.try_sign_digest(digest), + PrivateKey::EC256(ec) => Ok(*ec), + _ => Err(self), } } } -pub struct AsymmetricKeyWithDigest< - D: SupportedAsymmetricDigest, - S: SignatureEncoding, - K: SupportedAsymmetricKey, -> { +pub struct AsymmetricKeyWithDigest { key: K, - _marker: std::marker::PhantomData<(S, D)>, } -impl - AsymmetricKeyWithDigest -{ +impl AsymmetricKeyWithDigest { pub fn new(key: K) -> Self { - Self { - key, - _marker: std::marker::PhantomData, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum DigestType { - SHA256, - SHA384, - SHA512, -} - -pub trait SupportedAsymmetricDigest: Digest + digest::const_oid::AssociatedOid { - fn digest_type() -> DigestType; -} - -impl SupportedAsymmetricDigest for sha2::Sha256 { - fn digest_type() -> DigestType { - DigestType::SHA256 - } -} - -impl SupportedAsymmetricDigest for sha2::Sha384 { - fn digest_type() -> DigestType { - DigestType::SHA384 - } -} - -impl SupportedAsymmetricDigest for sha2::Sha512 { - fn digest_type() -> DigestType { - DigestType::SHA512 + Self { key } } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PKeyType { - EC, - RSA, -} - -pub trait SupportedAsymmetricKey { - fn key_type(&self) -> PKeyType; -} - -impl SupportedAsymmetricKey for ecdsa::SigningKey -where - C: PrimeCurve + CurveArithmetic, - Scalar: Invert>> + SignPrimitive, - SignatureSize: ArrayLength, -{ - fn key_type(&self) -> PKeyType { - PKeyType::EC +impl SigningAlgorithm for AsymmetricKeyWithDigest { + fn algorithm_type(&self) -> AlgorithmType { + match self.key { + SigningKey::RS256(_) => AlgorithmType::Rs256, + SigningKey::RS384(_) => AlgorithmType::Rs384, + SigningKey::RS512(_) => AlgorithmType::Rs512, + SigningKey::EC256(_) => AlgorithmType::Es256, + } } -} -impl SupportedAsymmetricKey for ecdsa::VerifyingKey -where - C: PrimeCurve + CurveArithmetic, -{ - fn key_type(&self) -> PKeyType { - PKeyType::EC - } -} + fn sign(&self, header: &str, claims: &str) -> Result { + macro_rules! short_hand { + ($key:ident, $hash:ty, $sig:ty) => { + let mut digest = <$hash>::new(); -impl SupportedAsymmetricKey for rsa::pkcs1v15::SigningKey { - fn key_type(&self) -> PKeyType { - PKeyType::RSA - } -} + digest.update(header.as_bytes()); + digest.update(SEPARATOR.as_bytes()); + digest.update(claims.as_bytes()); -impl SupportedAsymmetricKey for rsa::pkcs1v15::VerifyingKey { - fn key_type(&self) -> PKeyType { - PKeyType::RSA - } -} + let signed: $sig = $key.try_sign_digest(digest)?; -impl SupportedAsymmetricKey for PublicKey { - fn key_type(&self) -> PKeyType { - match self { - PublicKey::RSA(_) => PKeyType::RSA, - PublicKey::EC(_) => PKeyType::EC, + return Ok( + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(signed.to_bytes()) + ); + }; } - } -} -impl SupportedAsymmetricKey for PrivateKey { - fn key_type(&self) -> PKeyType { - match self { - PrivateKey::RSA(_) => PKeyType::RSA, - PrivateKey::EC(_) => PKeyType::EC, + match &self.key { + SigningKey::RS256(key) => { + short_hand!(key, sha2::Sha256, rsa::pkcs1v15::Signature); + } + SigningKey::RS384(key) => { + short_hand!(key, sha2::Sha384, rsa::pkcs1v15::Signature); + } + SigningKey::RS512(key) => { + short_hand!(key, sha2::Sha512, rsa::pkcs1v15::Signature); + } + SigningKey::EC256(key) => { + short_hand!(key, sha2::Sha256, p256::ecdsa::Signature); + } } } } -impl - AsymmetricKeyWithDigest -{ +impl VerifyingAlgorithm for AsymmetricKeyWithDigest { fn algorithm_type(&self) -> AlgorithmType { - match (self.key.key_type(), D::digest_type()) { - (PKeyType::RSA, DigestType::SHA256) => AlgorithmType::Rs256, - (PKeyType::RSA, DigestType::SHA384) => AlgorithmType::Rs384, - (PKeyType::RSA, DigestType::SHA512) => AlgorithmType::Rs512, - (PKeyType::EC, DigestType::SHA256) => AlgorithmType::Es256, - (PKeyType::EC, DigestType::SHA384) => AlgorithmType::Es384, - (PKeyType::EC, DigestType::SHA512) => AlgorithmType::Es512, + match self.key { + VerifyingKey::RS256(_) => AlgorithmType::Rs256, + VerifyingKey::RS384(_) => AlgorithmType::Rs384, + VerifyingKey::RS512(_) => AlgorithmType::Rs512, + VerifyingKey::EC256(_) => AlgorithmType::Es256, } } -} - -impl< - D: SupportedAsymmetricDigest, - S: SignatureEncoding, - K: DigestSigner + SupportedAsymmetricKey, - > SigningAlgorithm for AsymmetricKeyWithDigest -{ - fn algorithm_type(&self) -> AlgorithmType { - AsymmetricKeyWithDigest::algorithm_type(self) - } - - fn sign(&self, header: &str, claims: &str) -> Result { - let mut digest = D::new(); - - digest.update(header.as_bytes()); - digest.update(SEPARATOR.as_bytes()); - digest.update(claims.as_bytes()); - - let signature = self.key.try_sign_digest(digest)?; - - Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(signature.to_bytes())) - } -} - -impl< - D: SupportedAsymmetricDigest, - S: SignatureEncoding, - K: DigestVerifier + SupportedAsymmetricKey, - > VerifyingAlgorithm for AsymmetricKeyWithDigest -{ - fn algorithm_type(&self) -> AlgorithmType { - AsymmetricKeyWithDigest::algorithm_type(self) - } fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result { - let mut digest = D::new(); + macro_rules! short_hand { + ($key:ident, $hash:ty, $sig:ty) => { + let mut digest = <$hash>::new(); - digest.update(header.as_bytes()); - digest.update(SEPARATOR.as_bytes()); - digest.update(claims.as_bytes()); + digest.update(header.as_bytes()); + digest.update(SEPARATOR.as_bytes()); + digest.update(claims.as_bytes()); - let signature = S::try_from(signature).map_err(|_| Error::InvalidSignature)?; + let signature = <$sig>::try_from(signature).map_err(|_| Error::InvalidSignature)?; - Ok(self.key.verify_digest(digest, &signature).is_ok()) + return Ok($key.verify_digest(digest, &signature).is_ok()); + }; + } + + match &self.key { + VerifyingKey::RS256(key) => { + short_hand!(key, sha2::Sha256, rsa::pkcs1v15::Signature); + } + VerifyingKey::RS384(key) => { + short_hand!(key, sha2::Sha384, rsa::pkcs1v15::Signature); + } + VerifyingKey::RS512(key) => { + short_hand!(key, sha2::Sha512, rsa::pkcs1v15::Signature); + } + VerifyingKey::EC256(key) => { + short_hand!(key, sha2::Sha256, p256::ecdsa::Signature); + } + } } } @@ -307,9 +255,8 @@ mod tests { #[test] fn rs256_sign() -> Result<(), Error> { let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/rs256-private.pem"))?; - let algorithm = - AsymmetricKeyWithDigest::::new(key); - let result = algorithm.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?; + let signer = AsymmetricKeyWithDigest::new(SigningKey::from_rsa256(key.into_rsa().unwrap())); + let result = signer.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?; assert_eq!(result, RS256_SIGNATURE); Ok(()) } @@ -318,7 +265,7 @@ mod tests { fn rs256_verify() -> Result<(), Error> { let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/rs256-public.pem"))?; let verifier = - AsymmetricKeyWithDigest::::new(key); + AsymmetricKeyWithDigest::new(VerifyingKey::from_rsa256(key.into_rsa().unwrap())); assert!( verifier.verify(&AlgOnly(Rs256).to_base64()?, CLAIMS, RS256_SIGNATURE)?, "signature should be valid" @@ -329,11 +276,13 @@ mod tests { #[test] fn es256_sign() -> Result<(), Error> { let key = PrivateKey::from_pem_bytes(include_bytes!("../../../test/es256-private.pem"))?; - let signer = AsymmetricKeyWithDigest::::new(key); + let signer = + AsymmetricKeyWithDigest::new(SigningKey::from_ec256(key.into_ec256().unwrap())); let signature = signer.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?; let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; - let verifier = AsymmetricKeyWithDigest::::new(key); + let verifier = + AsymmetricKeyWithDigest::new(VerifyingKey::from_ec256(key.into_ec256().unwrap())); assert!( verifier.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &signature)?, "signature should be valid" @@ -344,7 +293,8 @@ mod tests { #[test] fn es256_verify() -> Result<(), Error> { let key = PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem"))?; - let verifier = AsymmetricKeyWithDigest::::new(key); + let verifier = + AsymmetricKeyWithDigest::new(VerifyingKey::from_ec256(key.into_ec256().unwrap())); assert!( verifier.verify( &AlgOnly(Es256).to_base64()?, @@ -359,34 +309,30 @@ mod tests { #[test] fn genric_public_key_parse() -> Result<(), Error> { - let pem = include_bytes!("../../../test/rs256-public.pem"); - super::PublicKey::::from_pem( - std::str::from_utf8(pem).expect("invalid utf8 rsa key"), - ) - .expect("invalid rsa key"); - - let pem = include_bytes!("../../../test/es256-public.pem"); - super::PublicKey::::from_pem( - std::str::from_utf8(pem).expect("invalid utf8 ec key"), - ) - .expect("invalid ec key"); + match PublicKey::from_pem_bytes(include_bytes!("../../../test/rs256-public.pem")) { + Ok(PublicKey::RSA(_)) => {} + _ => panic!("invalid rsa key"), + } + + match PublicKey::from_pem_bytes(include_bytes!("../../../test/es256-public.pem")) { + Ok(PublicKey::EC256(_)) => {} + _ => panic!("invalid ec key"), + } Ok(()) } #[test] fn genric_private_key_parse() -> Result<(), Error> { - let pem = include_bytes!("../../../test/rs256-private.pem"); - super::PrivateKey::::from_pem( - std::str::from_utf8(pem).expect("invalid utf8 rsa key"), - ) - .expect("invalid rsa key"); - - let pem = include_bytes!("../../../test/es256-private.pem"); - super::PrivateKey::::from_pem( - std::str::from_utf8(pem).expect("invalid utf8 ec key"), - ) - .expect("invalid ec key"); + match PrivateKey::from_pem_bytes(include_bytes!("../../../test/rs256-private.pem")) { + Ok(PrivateKey::RSA(_)) => {} + _ => panic!("invalid rsa key"), + } + + match PrivateKey::from_pem_bytes(include_bytes!("../../../test/es256-private.pem")) { + Ok(PrivateKey::EC256(_)) => {} + _ => panic!("invalid ec key"), + } Ok(()) } From dfad15da0e47ca564a3ea931fa8983e06d291586 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Tue, 21 Nov 2023 00:37:02 +0000 Subject: [PATCH 5/6] feat: add es384 support --- Cargo.toml | 1 + src/algorithm/rust_crypto/asymmetric.rs | 35 +++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ed57212..b1cd6434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ serde_json = "1.0" ecdsa = { version = "0.16", features = ["verifying", "signing"] } rsa = { version = "0.9", features = ["sha2"] } p256 = { version = "0.13", features = ["pem"] } +p384 = { version = "0.13", features = ["pem"] } pem = "3.0" [dependencies.openssl] diff --git a/src/algorithm/rust_crypto/asymmetric.rs b/src/algorithm/rust_crypto/asymmetric.rs index 672513f7..e678f388 100644 --- a/src/algorithm/rust_crypto/asymmetric.rs +++ b/src/algorithm/rust_crypto/asymmetric.rs @@ -15,6 +15,7 @@ pub enum VerifyingKey { RS384(Box>), RS512(Box>), EC256(Box), + EC384(Box), } impl VerifyingKey { @@ -22,6 +23,10 @@ impl VerifyingKey { Self::EC256(p256::ecdsa::VerifyingKey::from(key).into()) } + pub fn from_ec384(key: p384::PublicKey) -> Self { + Self::EC384(p384::ecdsa::VerifyingKey::from(key).into()) + } + pub fn from_rsa256(key: RsaPublicKey) -> Self { Self::RS256(rsa::pkcs1v15::VerifyingKey::new(key).into()) } @@ -41,6 +46,7 @@ pub enum SigningKey { RS384(Box>), RS512(Box>), EC256(Box), + EC384(Box), } impl SigningKey { @@ -48,6 +54,10 @@ impl SigningKey { Self::EC256(p256::ecdsa::SigningKey::from(key).into()) } + pub fn from_ec384(key: p384::SecretKey) -> Self { + Self::EC384(p384::ecdsa::SigningKey::from(key).into()) + } + pub fn from_rsa256(key: RsaPrivateKey) -> Self { Self::RS256(rsa::pkcs1v15::SigningKey::new(key).into()) } @@ -67,6 +77,7 @@ pub use ::{digest, ecdsa, p256, rsa, signature}; pub enum PublicKey { RSA(Box), EC256(Box), + EC384(Box), } impl PublicKey { @@ -75,7 +86,9 @@ impl PublicKey { } pub fn from_pem(encoded: &str) -> Result { - if let Ok(ec) = encoded.parse::() { + if let Ok(ec) = encoded.parse::() { + Ok(PublicKey::EC384(ec.into())) + } else if let Ok(ec) = encoded.parse::() { Ok(PublicKey::EC256(ec.into())) } else if let Ok(rsa) = rsa::RsaPublicKey::from_public_key_pem(encoded) { Ok(PublicKey::RSA(rsa.into())) @@ -97,12 +110,20 @@ impl PublicKey { _ => Err(self), } } + + pub fn into_ec384(self) -> Result { + match self { + PublicKey::EC384(ec) => Ok(*ec), + _ => Err(self), + } + } } #[derive(Clone, Debug)] pub enum PrivateKey { RSA(Box), EC256(Box), + EC384(Box), } impl PrivateKey { @@ -111,7 +132,9 @@ impl PrivateKey { } pub fn from_pem(pem: &str) -> Result { - if let Ok(ec) = pem.parse::() { + if let Ok(ec) = pem.parse::() { + Ok(PrivateKey::EC384(ec.into())) + } else if let Ok(ec) = pem.parse::() { Ok(PrivateKey::EC256(ec.into())) } else if let Ok(rsa) = rsa::RsaPrivateKey::from_pkcs8_pem(pem) { Ok(PrivateKey::RSA(rsa.into())) @@ -154,6 +177,7 @@ impl SigningAlgorithm for AsymmetricKeyWithDigest { SigningKey::RS384(_) => AlgorithmType::Rs384, SigningKey::RS512(_) => AlgorithmType::Rs512, SigningKey::EC256(_) => AlgorithmType::Es256, + SigningKey::EC384(_) => AlgorithmType::Es384, } } @@ -187,6 +211,9 @@ impl SigningAlgorithm for AsymmetricKeyWithDigest { SigningKey::EC256(key) => { short_hand!(key, sha2::Sha256, p256::ecdsa::Signature); } + SigningKey::EC384(key) => { + short_hand!(key, sha2::Sha384, p384::ecdsa::Signature); + } } } } @@ -198,6 +225,7 @@ impl VerifyingAlgorithm for AsymmetricKeyWithDigest { VerifyingKey::RS384(_) => AlgorithmType::Rs384, VerifyingKey::RS512(_) => AlgorithmType::Rs512, VerifyingKey::EC256(_) => AlgorithmType::Es256, + VerifyingKey::EC384(_) => AlgorithmType::Es384, } } @@ -229,6 +257,9 @@ impl VerifyingAlgorithm for AsymmetricKeyWithDigest { VerifyingKey::EC256(key) => { short_hand!(key, sha2::Sha256, p256::ecdsa::Signature); } + VerifyingKey::EC384(key) => { + short_hand!(key, sha2::Sha384, p384::ecdsa::Signature); + } } } } From ad7ed4bdb88207e1cdeda47674a42c91214e2617 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Tue, 21 Nov 2023 00:39:42 +0000 Subject: [PATCH 6/6] fix: add into_ec384 for PrivateKey --- src/algorithm/rust_crypto/asymmetric.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/algorithm/rust_crypto/asymmetric.rs b/src/algorithm/rust_crypto/asymmetric.rs index e678f388..e5af4de4 100644 --- a/src/algorithm/rust_crypto/asymmetric.rs +++ b/src/algorithm/rust_crypto/asymmetric.rs @@ -158,6 +158,13 @@ impl PrivateKey { _ => Err(self), } } + + pub fn into_ec384(self) -> Result { + match self { + PrivateKey::EC384(ec) => Ok(*ec), + _ => Err(self), + } + } } pub struct AsymmetricKeyWithDigest {