From 34ea1941791ec2f01f8ac36268f8e016dfa6c679 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 8 Nov 2019 19:00:19 +0000 Subject: [PATCH] Move crypto to a dir --- src/crypto/ecdsa.rs | 18 +++ src/{crypto.rs => crypto/mod.rs} | 132 +++++++++------------- src/crypto/rsa.rs | 25 ++++ src/header.rs | 4 +- src/jwk.rs | 31 +++++ src/lib.rs | 1 + src/serialization.rs | 18 ++- tests/{ec => ecdsa}/mod.rs | 0 tests/{ec => ecdsa}/private_ecdsa_key.pem | 0 tests/{ec => ecdsa}/private_ecdsa_key.pk8 | Bin tests/{ec => ecdsa}/public_ecdsa_key.pem | 0 tests/{ec => ecdsa}/public_ecdsa_key.pk8 | 0 tests/lib.rs | 2 +- 13 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 src/crypto/ecdsa.rs rename src/{crypto.rs => crypto/mod.rs} (62%) create mode 100644 src/crypto/rsa.rs create mode 100644 src/jwk.rs rename tests/{ec => ecdsa}/mod.rs (100%) rename tests/{ec => ecdsa}/private_ecdsa_key.pem (100%) rename tests/{ec => ecdsa}/private_ecdsa_key.pk8 (100%) rename tests/{ec => ecdsa}/public_ecdsa_key.pem (100%) rename tests/{ec => ecdsa}/public_ecdsa_key.pk8 (100%) diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs new file mode 100644 index 0000000..b1f00c9 --- /dev/null +++ b/src/crypto/ecdsa.rs @@ -0,0 +1,18 @@ +use ring::{rand, signature}; + +use crate::errors::{Result}; +use crate::pem_decoder::PemEncodedKey; +use crate::serialization::encode; + +/// The actual ECDSA signing + encoding +pub fn sign( + alg: &'static signature::EcdsaSigningAlgorithm, + key: &[u8], + signing_input: &str, +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; + let rng = rand::SystemRandom::new(); + let out = signing_key.sign(&rng, signing_input.as_bytes())?; + Ok(encode(out.as_ref())) +} diff --git a/src/crypto.rs b/src/crypto/mod.rs similarity index 62% rename from src/crypto.rs rename to src/crypto/mod.rs index ad95dc1..3ab788a 100644 --- a/src/crypto.rs +++ b/src/crypto/mod.rs @@ -1,54 +1,26 @@ -use base64; use ring::constant_time::verify_slices_are_equal; -use ring::{hmac, rand, signature}; +use ring::{hmac, signature}; use crate::algorithms::Algorithm; -use crate::errors::{ErrorKind, Result}; +use crate::errors::{Result}; use crate::pem_decoder::PemEncodedKey; +use crate::serialization::{encode, decode}; + +pub(crate) mod rsa; +pub(crate) mod ecdsa; /// The actual HS signing + encoding -fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { +pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { let digest = hmac::sign(&hmac::Key::new(alg, key), signing_input.as_bytes()); - Ok(base64::encode_config::(&digest, base64::URL_SAFE_NO_PAD)) + Ok(encode(digest.as_ref())) } -/// The actual ECDSA signing + encoding -fn sign_ecdsa( - alg: &'static signature::EcdsaSigningAlgorithm, - key: &[u8], - signing_input: &str, -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; - let rng = rand::SystemRandom::new(); - let sig = signing_key.sign(&rng, signing_input.as_bytes())?; - Ok(base64::encode_config(&sig, base64::URL_SAFE_NO_PAD)) -} - -/// The actual RSA signing + encoding -/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html -fn sign_rsa( - alg: &'static dyn signature::RsaEncoding, - key: &[u8], - signing_input: &str, -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) - .map_err(|_| ErrorKind::InvalidRsaKey)?; - - let mut signature = vec![0; key_pair.public_modulus_len()]; - let rng = rand::SystemRandom::new(); - key_pair - .sign(alg, &rng, signing_input.as_bytes(), &mut signature) - .map_err(|_| ErrorKind::InvalidRsaKey)?; - - Ok(base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD)) -} /// Take the payload of a JWT, sign it using the algorithm given and return /// the base64 url safe encoded of the result. /// /// Only use this function if you want to do something other than JWT. +/// `key` is the secret for HMAC and a pem encoded string otherwise pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, signing_input), @@ -56,56 +28,22 @@ pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result sign_hmac(hmac::HMAC_SHA512, key, signing_input), Algorithm::ES256 => { - sign_ecdsa(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input) + ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input) } Algorithm::ES384 => { - sign_ecdsa(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input) + ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input) } - Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input), - Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input), - Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input), + Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, signing_input), + Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, signing_input), + Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, signing_input), - Algorithm::PS256 => sign_rsa(&signature::RSA_PSS_SHA256, key, signing_input), - Algorithm::PS384 => sign_rsa(&signature::RSA_PSS_SHA384, key, signing_input), - Algorithm::PS512 => sign_rsa(&signature::RSA_PSS_SHA512, key, signing_input), + Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, signing_input), + Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, signing_input), + Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, signing_input), } } -/// See Ring docs for more details -fn verify_ring( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; - let public_key = signature::UnparsedPublicKey::new(alg, key); - let res = public_key.verify(signing_input.as_bytes(), &signature_bytes); - - Ok(res.is_ok()) -} - -fn verify_ring_es( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) -} - -fn verify_ring_rsa( - alg: &'static signature::RsaParameters, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) -} - /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA. /// @@ -152,3 +90,39 @@ pub fn verify( } } } + +// TODO: see if we can remove stuff? + +/// See Ring docs for more details +fn verify_ring( + alg: &'static dyn signature::VerificationAlgorithm, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let signature_bytes = decode(signature)?; + let public_key = signature::UnparsedPublicKey::new(alg, key); + let res = public_key.verify(signing_input.as_bytes(), &signature_bytes); + + Ok(res.is_ok()) +} + +fn verify_ring_es( + alg: &'static dyn signature::VerificationAlgorithm, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) +} + +fn verify_ring_rsa( + alg: &'static signature::RsaParameters, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) +} diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs new file mode 100644 index 0000000..bc4a78c --- /dev/null +++ b/src/crypto/rsa.rs @@ -0,0 +1,25 @@ +use ring::{rand, signature}; + +use crate::errors::{ErrorKind, Result}; +use crate::pem_decoder::PemEncodedKey; +use crate::serialization::encode; + +/// The actual RSA signing + encoding +/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html +pub fn sign( + alg: &'static dyn signature::RsaEncoding, + key: &[u8], + signing_input: &str, +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) + .map_err(|_| ErrorKind::InvalidRsaKey)?; + + let mut signature = vec![0; key_pair.public_modulus_len()]; + let rng = rand::SystemRandom::new(); + key_pair + .sign(alg, &rng, signing_input.as_bytes(), &mut signature) + .map_err(|_| ErrorKind::InvalidRsaKey)?; + + Ok(encode(&signature)) +} diff --git a/src/header.rs b/src/header.rs index 73a2a1d..553dbb3 100644 --- a/src/header.rs +++ b/src/header.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::algorithms::Algorithm; use crate::errors::Result; +use crate::serialization::decode; + /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. @@ -59,7 +61,7 @@ impl Header { /// Converts an encoded part into the Header struct if possible pub(crate) fn from_encoded(encoded_part: &str) -> Result { - let decoded = base64::decode_config(encoded_part, base64::URL_SAFE_NO_PAD)?; + let decoded = decode(encoded_part)?; let s = String::from_utf8(decoded)?; Ok(serde_json::from_str(&s)?) diff --git a/src/jwk.rs b/src/jwk.rs new file mode 100644 index 0000000..bc331f9 --- /dev/null +++ b/src/jwk.rs @@ -0,0 +1,31 @@ +use serde::de::DeserializeOwned; + +use crate::validation::{Validation, validate}; +use crate::header::Header; +use crate::errors::{new_error, ErrorKind, Result}; +use crate::serialization::{from_jwt_part_claims, TokenData}; + + +pub fn decode_rsa_jwk( + token: &str, + modulus: &[u8], + exponent: &[u8], + validation: &Validation, +) -> Result> { + let (signature, signing_input) = expect_two!(token.rsplitn(2, '.')); + let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; + + if !verify(signature, signing_input, key, header.alg)? { + return Err(new_error(ErrorKind::InvalidSignature)); + } + + if !validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; + validate(&claims_map, validation)?; + + Ok(TokenData { header, claims: decoded_claims }) +} diff --git a/src/lib.rs b/src/lib.rs index c11b6a6..604f6fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod pem_decoder; mod pem_encoder; mod serialization; mod validation; +// mod jwk; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; diff --git a/src/serialization.rs b/src/serialization.rs index 6c6df1f..0f814d4 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -1,4 +1,3 @@ -use base64; use serde::de::DeserializeOwned; use serde::ser::Serialize; use serde_json::map::Map; @@ -16,19 +15,26 @@ pub struct TokenData { pub claims: T, } +pub(crate) fn encode(input: &[u8]) -> String { + base64::encode_config(input, base64::URL_SAFE_NO_PAD) +} + +pub(crate) fn decode(input: &str) -> Result> { + base64::decode_config(input, base64::URL_SAFE_NO_PAD).map_err(|e| e.into()) +} + /// Serializes a struct to JSON and encodes it in base64 -pub fn encode_part(input: &T) -> Result { +pub(crate) fn encode_part(input: &T) -> Result { let json = to_string(input)?; - Ok(base64::encode_config(json.as_bytes(), base64::URL_SAFE_NO_PAD)) + Ok(encode(json.as_bytes())) } /// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can /// run validation on it -pub fn from_jwt_part_claims, T: DeserializeOwned>( +pub(crate) fn from_jwt_part_claims, T: DeserializeOwned>( encoded: B, ) -> Result<(T, Map)> { - let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; - let s = String::from_utf8(decoded)?; + let s = String::from_utf8(decode(encoded.as_ref())?)?; let claims: T = from_str(&s)?; let map: Map<_, _> = from_str(&s)?; diff --git a/tests/ec/mod.rs b/tests/ecdsa/mod.rs similarity index 100% rename from tests/ec/mod.rs rename to tests/ecdsa/mod.rs diff --git a/tests/ec/private_ecdsa_key.pem b/tests/ecdsa/private_ecdsa_key.pem similarity index 100% rename from tests/ec/private_ecdsa_key.pem rename to tests/ecdsa/private_ecdsa_key.pem diff --git a/tests/ec/private_ecdsa_key.pk8 b/tests/ecdsa/private_ecdsa_key.pk8 similarity index 100% rename from tests/ec/private_ecdsa_key.pk8 rename to tests/ecdsa/private_ecdsa_key.pk8 diff --git a/tests/ec/public_ecdsa_key.pem b/tests/ecdsa/public_ecdsa_key.pem similarity index 100% rename from tests/ec/public_ecdsa_key.pem rename to tests/ecdsa/public_ecdsa_key.pem diff --git a/tests/ec/public_ecdsa_key.pk8 b/tests/ecdsa/public_ecdsa_key.pk8 similarity index 100% rename from tests/ec/public_ecdsa_key.pk8 rename to tests/ecdsa/public_ecdsa_key.pk8 diff --git a/tests/lib.rs b/tests/lib.rs index 30241b4..3760c89 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,2 +1,2 @@ -mod ec; +mod ecdsa; mod rsa;