diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56b656b5..5eeb854a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,12 @@ jobs: - name: Build System Info run: rustc --version + - name: Downgrade bumpalo + run: cargo update -p bumpalo@3.15.4 --precise 3.14.0 + + - name: Downgrade half + run: cargo update -p half@2.4.0 --precise 2.2.1 + - name: Run tests default features run: cargo test diff --git a/Cargo.toml b/Cargo.toml index 92178dd9..7d438c0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,16 +27,21 @@ base64 = "0.22" pem = { version = "3", optional = true } simple_asn1 = { version = "0.6", optional = true } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ring = { version = "0.17.4", features = ["std"] } - +hmac = "0.12.1" +rsa = "0.9.6" +sha2 = { version = "0.10.7", features = ["oid"] } +getrandom = { version = "0.2.10", features = ["js"] } +rand = { version = "0.8.5", features = ["std"], default-features = false } +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" [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" -ring = { version = "0.17.4", features = ["std", "wasm32_unknown_unknown_js"] } [dev-dependencies] wasm-bindgen-test = "0.3.1" - +ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] } [target.'cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))'.dev-dependencies] # For the custom time example time = "0.3" @@ -49,7 +54,7 @@ criterion = { version = "0.4", default-features = false } [features] default = ["use_pem"] -use_pem = ["pem", "simple_asn1"] +use_pem = ["pem", "simple_asn1", 'p256/pem', 'p384/pem'] [[bench]] name = "jwt" diff --git a/examples/custom_time.rs b/examples/custom_time.rs index d502a0f4..fbf92b96 100644 --- a/examples/custom_time.rs +++ b/examples/custom_time.rs @@ -1,7 +1,8 @@ -use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime}; +use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation}; + const SECRET: &str = "some-secret"; #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -60,13 +61,15 @@ mod jwt_numeric_date { #[cfg(test)] mod tests { - const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg"; + use time::{Duration, OffsetDateTime}; - use super::super::{Claims, SECRET}; use jsonwebtoken::{ decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, }; - use time::{Duration, OffsetDateTime}; + + use super::super::{Claims, SECRET}; + + const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gT2Zmc2V0RGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.BcPipupP9oIV6uFRI6Acn7FMLws_wA3oo6CrfeFF3Gg"; #[test] fn round_trip() { diff --git a/examples/ed25519.rs b/examples/ed25519.rs index 21a8f217..8ee8b3fa 100644 --- a/examples/ed25519.rs +++ b/examples/ed25519.rs @@ -1,8 +1,11 @@ +use ed25519_dalek::pkcs8::EncodePrivateKey; +use ed25519_dalek::SigningKey; +use rand_core::OsRng; +use serde::{Deserialize, Serialize}; + use jsonwebtoken::{ decode, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, Validation, }; -use ring::signature::{Ed25519KeyPair, KeyPair}; -use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { @@ -11,11 +14,16 @@ pub struct Claims { } fn main() { - let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap(); - let encoding_key = EncodingKey::from_ed_der(doc.as_ref()); + let signing_key = SigningKey::generate(&mut OsRng); + let pkcs8 = signing_key.to_pkcs8_der().unwrap(); + let pkcs8 = pkcs8.as_bytes(); + // The `to_pkcs8_der` includes the public key, the first 48 bits are the private key. + let pkcs8 = &pkcs8[..48]; + let encoding_key = EncodingKey::from_ed_der(pkcs8); - let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap(); - let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref()); + let verifying_key = signing_key.verifying_key(); + let public_key = verifying_key.as_bytes(); + let decoding_key = DecodingKey::from_ed_der(public_key); let claims = Claims { sub: "test".to_string(), exp: get_current_timestamp() }; @@ -37,11 +45,17 @@ mod tests { impl Jot { fn new() -> Jot { - let doc = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new()).unwrap(); - let encoding_key = EncodingKey::from_ed_der(doc.as_ref()); + let signing_key = SigningKey::generate(&mut OsRng); + let pkcs8 = signing_key.to_pkcs8_der().unwrap(); + let pkcs8 = pkcs8.as_bytes(); + // The `to_pkcs8_der` includes the public key, the first 48 bits are the private key. + let pkcs8 = &pkcs8[..48]; + let encoding_key = EncodingKey::from_ed_der(&pkcs8); + + let verifying_key = signing_key.verifying_key(); + let public_key = verifying_key.as_bytes(); + let decoding_key = DecodingKey::from_ed_der(public_key); - let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap(); - let decoding_key = DecodingKey::from_ed_der(pair.public_key().as_ref()); Jot { encoding_key, decoding_key } } } diff --git a/examples/validation.rs b/examples/validation.rs index ffabc6f1..ed2a9a8c 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,6 +1,7 @@ +use serde::{Deserialize, Serialize}; + use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; -use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct Claims { diff --git a/src/algorithms.rs b/src/algorithms.rs index e162bab2..ee17c54b 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,7 +1,9 @@ -use crate::errors::{Error, ErrorKind, Result}; -use serde::{Deserialize, Serialize}; use std::str::FromStr; +use serde::{Deserialize, Serialize}; + +use crate::errors::{Error, ErrorKind, Result}; + #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] pub(crate) enum AlgorithmFamily { Hmac, diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index 99b92fc6..df7190c1 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -1,38 +1,74 @@ -use ring::{rand, signature}; - use crate::algorithms::Algorithm; use crate::errors::Result; -use crate::serialization::b64_encode; +use crate::serialization::{b64_decode, b64_encode}; -/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. -pub(crate) fn alg_to_ec_verification( - alg: Algorithm, -) -> &'static signature::EcdsaVerificationAlgorithm { +/// 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 => &signature::ECDSA_P256_SHA256_FIXED, - Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED, + Algorithm::ES256 => es256_sign(key, message), + Algorithm::ES384 => es384_sign(key, message), + _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), } } -/// Only used internally when signing EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. -pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSigningAlgorithm { +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 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, - Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING, + 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"), } } -/// The actual ECDSA signing + encoding -/// The key needs to be in PKCS8 format -pub fn sign( - alg: &'static signature::EcdsaSigningAlgorithm, - key: &[u8], - message: &[u8], -) -> Result { - let rng = rand::SystemRandom::new(); - let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key, &rng)?; - let out = signing_key.sign(&rng, message)?; - Ok(b64_encode(out)) +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 index 7c185347..3da4fb5a 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -1,23 +1,29 @@ -use ring::signature; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; -use crate::algorithms::Algorithm; -use crate::errors::Result; -use crate::serialization::b64_encode; +use crate::errors::{new_error, ErrorKind, Result}; +use crate::serialization::{b64_decode, b64_encode}; -/// Only used internally when signing or validating EdDSA, to map from our enum to the Ring EdDSAParameters structs. -pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDSAParameters { - // To support additional key subtypes, like Ed448, we would need to match on the JWK's ("crv") - // parameter. - match alg { - Algorithm::EdDSA => &signature::ED25519, - _ => unreachable!("Tried to get EdDSA alg for a non-EdDSA algorithm"), - } +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 signing_key = signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?; + let key = key[16..].into(); + let signing_key = parse_key(key)?; let out = signing_key.sign(message); - Ok(b64_encode(out)) + Ok(b64_encode(out.to_bytes())) } diff --git a/src/crypto/hmac.rs b/src/crypto/hmac.rs new file mode 100644 index 00000000..7e4e2b21 --- /dev/null +++ b/src/crypto/hmac.rs @@ -0,0 +1,106 @@ +use hmac::{Hmac, Mac}; +use sha2::{Sha256, Sha384, Sha512}; + +use crate::errors::Result; +use crate::serialization::{b64_decode, b64_encode}; +use crate::Algorithm; + +type HmacSha256 = Hmac; +type HmacSha384 = Hmac; +type HmacSha512 = Hmac; + +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)) +} + +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)) +} + +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) +} + +trait HmacAlgorithm { + fn sign(&mut self, message: &[u8]) -> Vec; + fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool; +} + +impl HmacAlgorithm for Box { + fn sign(&mut self, message: &[u8]) -> Vec { + (**self).sign(message) + } + + fn verify(&mut self, signature: &[u8], message: &[u8]) -> bool { + (**self).verify(signature, message) + } +} + +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 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 HmacAlgorithm for HmacSha512 { + 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() + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index c2957dc8..4f5328eb 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,62 +1,33 @@ -use ring::constant_time::verify_slices_are_equal; -use ring::{hmac, signature}; - use crate::algorithms::Algorithm; use crate::decoding::{DecodingKey, DecodingKeyKind}; use crate::encoding::EncodingKey; use crate::errors::Result; -use crate::serialization::{b64_decode, b64_encode}; pub(crate) mod ecdsa; pub(crate) mod eddsa; +pub(crate) mod hmac; pub(crate) mod rsa; -/// The actual HS signing + encoding -/// Could be in its own file to match RSA/EC but it's 2 lines... -pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &[u8]) -> String { - let digest = hmac::sign(&hmac::Key::new(alg, key), message); - b64_encode(digest) -} - /// 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::HS256 => Ok(sign_hmac(hmac::HMAC_SHA256, key.inner(), message)), - Algorithm::HS384 => Ok(sign_hmac(hmac::HMAC_SHA384, key.inner(), message)), - Algorithm::HS512 => Ok(sign_hmac(hmac::HMAC_SHA512, key.inner(), message)), - - Algorithm::ES256 | Algorithm::ES384 => { - ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message) - } - + 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(rsa::alg_to_rsa_signing(algorithm), key.inner(), message), + | Algorithm::PS512 => rsa::sign(algorithm, key.inner(), message), } } -/// See Ring docs for more details -fn verify_ring( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - message: &[u8], - key: &[u8], -) -> Result { - let signature_bytes = b64_decode(signature)?; - let public_key = signature::UnparsedPublicKey::new(alg, key); - let res = public_key.verify(message, &signature_bytes); - - Ok(res.is_ok()) -} - /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA/EC. /// @@ -73,35 +44,24 @@ pub fn verify( ) -> Result { match algorithm { Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { - // we just re-sign the message with the key and compare if they are equal - let signed = sign(message, &EncodingKey::from_secret(key.as_bytes()), algorithm)?; - Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) + hmac::hmac_verify(algorithm, signature, key.as_bytes(), message) + } + Algorithm::ES256 | Algorithm::ES384 => { + ecdsa::verify(algorithm, signature, message, key.as_bytes()) } - Algorithm::ES256 | Algorithm::ES384 => verify_ring( - ecdsa::alg_to_ec_verification(algorithm), - signature, - message, - key.as_bytes(), - ), - Algorithm::EdDSA => verify_ring( - eddsa::alg_to_ec_verification(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 => { - let alg = rsa::alg_to_rsa_parameters(algorithm); - match &key.kind { - DecodingKeyKind::SecretOrDer(bytes) => verify_ring(alg, signature, message, bytes), - DecodingKeyKind::RsaModulusExponent { n, e } => { - rsa::verify_from_components(alg, signature, message, (n, e)) - } + | 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/crypto/rsa.rs b/src/crypto/rsa.rs index 4c97db3c..7d13411e 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -1,62 +1,115 @@ -use ring::{rand, signature}; +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::{ErrorKind, Result}; +use crate::errors::{new_error, ErrorKind, Result}; use crate::serialization::{b64_decode, b64_encode}; -/// Only used internally when validating RSA, to map from our enum to the Ring param structs. -pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { +fn alg_to_pss(alg: Algorithm, digest_len: usize) -> Option { match alg { - Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, - Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, - Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, - Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256, - Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384, - Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512, - _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), + 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, } } -/// Only used internally when signing with RSA, to map from our enum to the Ring signing structs. -pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaEncoding { +fn alg_to_pkcs1_v15(alg: Algorithm) -> Option { match alg { - Algorithm::RS256 => &signature::RSA_PKCS1_SHA256, - Algorithm::RS384 => &signature::RSA_PKCS1_SHA384, - Algorithm::RS512 => &signature::RSA_PKCS1_SHA512, - Algorithm::PS256 => &signature::RSA_PSS_SHA256, - Algorithm::PS384 => &signature::RSA_PSS_SHA384, - Algorithm::PS512 => &signature::RSA_PSS_SHA512, - _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), + Algorithm::RS256 => Some(Pkcs1v15Sign::new::()), + Algorithm::RS384 => Some(Pkcs1v15Sign::new::()), + Algorithm::RS512 => Some(Pkcs1v15Sign::new::()), + _ => None, } } -/// The actual RSA signing + encoding -/// The key needs to be in PKCS8 format -/// Taken from Ring doc https://docs.rs/ring/latest/ring/signature/index.html -pub(crate) fn sign( - alg: &'static dyn signature::RsaEncoding, - key: &[u8], - message: &[u8], -) -> Result { - let key_pair = signature::RsaKeyPair::from_der(key) - .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - - let mut signature = vec![0; key_pair.public().modulus_len()]; - let rng = rand::SystemRandom::new(); - key_pair.sign(alg, &rng, message, &mut signature).map_err(|_| ErrorKind::RsaFailedSigning)?; +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)) } -/// Checks that a signature is valid based on the (n, e) RSA pubkey components pub(crate) fn verify_from_components( - alg: &'static signature::RsaParameters, + 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 pubkey = signature::RsaPublicKeyComponents { n: components.0, e: components.1 }; - let res = pubkey.verify(alg, message, &signature_bytes); - Ok(res.is_ok()) + 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/errors.rs b/src/errors.rs index 2edd7df5..e9ee1dbd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -41,6 +41,8 @@ pub enum ErrorKind { InvalidSignature, /// When the secret given is not a valid ECDSA key InvalidEcdsaKey, + /// When the secret given is not a valid EDDSA key + InvalidEddsaKey, /// When the secret given is not a valid RSA key InvalidRsaKey(String), /// We could not sign with the given key @@ -76,8 +78,6 @@ pub enum ErrorKind { Json(Arc), /// Some of the text was invalid UTF-8 Utf8(::std::string::FromUtf8Error), - /// Something unspecified went wrong with crypto - Crypto(::ring::error::Unspecified), } impl StdError for Error { @@ -101,7 +101,7 @@ impl StdError for Error { ErrorKind::Base64(err) => Some(err), ErrorKind::Json(err) => Some(err.as_ref()), ErrorKind::Utf8(err) => Some(err), - ErrorKind::Crypto(err) => Some(err), + ErrorKind::InvalidEddsaKey => None, } } } @@ -121,12 +121,12 @@ impl fmt::Display for Error { | ErrorKind::ImmatureSignature | ErrorKind::InvalidAlgorithm | ErrorKind::InvalidKeyFormat + | ErrorKind::InvalidEddsaKey | ErrorKind::InvalidAlgorithmName => write!(f, "{:?}", self.0), ErrorKind::MissingRequiredClaim(c) => write!(f, "Missing required claim: {}", c), ErrorKind::InvalidRsaKey(msg) => write!(f, "RSA key invalid: {}", msg), ErrorKind::Json(err) => write!(f, "JSON error: {}", err), ErrorKind::Utf8(err) => write!(f, "UTF-8 error: {}", err), - ErrorKind::Crypto(err) => write!(f, "Crypto error: {}", err), ErrorKind::Base64(err) => write!(f, "Base64 error: {}", err), } } @@ -159,18 +159,6 @@ impl From<::std::string::FromUtf8Error> for Error { } } -impl From<::ring::error::Unspecified> for Error { - fn from(err: ::ring::error::Unspecified) -> Error { - new_error(ErrorKind::Crypto(err)) - } -} - -impl From<::ring::error::KeyRejected> for Error { - fn from(_err: ::ring::error::KeyRejected) -> Error { - new_error(ErrorKind::InvalidEcdsaKey) - } -} - impl From for Error { fn from(kind: ErrorKind) -> Error { new_error(kind) diff --git a/src/jwk.rs b/src/jwk.rs index 49c58003..9100fe0c 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -4,12 +4,14 @@ //! Most of the code in this file is taken from https://github.com/lawliet89/biscuit but //! tweaked to remove the private bits as it's not the goal for this crate currently. +use std::{fmt, str::FromStr}; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use crate::{ errors::{self, Error, ErrorKind}, Algorithm, }; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use std::{fmt, str::FromStr}; /// The intended usage of the public `KeyType`. This enum is serialized `untagged` #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -435,11 +437,12 @@ impl JwkSet { #[cfg(test)] mod tests { + use serde_json::json; + use wasm_bindgen_test::wasm_bindgen_test; + use crate::jwk::{AlgorithmParameters, JwkSet, OctetKeyType}; use crate::serialization::b64_encode; use crate::Algorithm; - use serde_json::json; - use wasm_bindgen_test::wasm_bindgen_test; #[test] #[wasm_bindgen_test] diff --git a/src/lib.rs b/src/lib.rs index 0c8664bf..2f936c8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,12 @@ //! Documentation: [stable](https://docs.rs/jsonwebtoken/) #![deny(missing_docs)] +pub use algorithms::Algorithm; +pub use decoding::{decode, decode_header, DecodingKey, TokenData}; +pub use encoding::{encode, EncodingKey}; +pub use header::Header; +pub use validation::{get_current_timestamp, Validation}; + mod algorithms; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; @@ -16,9 +22,3 @@ pub mod jwk; mod pem; mod serialization; mod validation; - -pub use algorithms::Algorithm; -pub use decoding::{decode, decode_header, DecodingKey, TokenData}; -pub use encoding::{encode, EncodingKey}; -pub use header::Header; -pub use validation::{get_current_timestamp, Validation}; diff --git a/src/validation.rs b/src/validation.rs index 289ba03c..e0b0e708 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -195,12 +195,14 @@ pub(crate) struct ClaimsForValidation<'a> { #[serde(borrow)] aud: TryParse>, } + #[derive(Debug)] enum TryParse { Parsed(T), FailedToParse, NotPresent, } + impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse { fn deserialize>( deserializer: D, @@ -212,6 +214,7 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse { }) } } + impl Default for TryParse { fn default() -> Self { Self::NotPresent @@ -237,6 +240,7 @@ enum Issuer<'a> { /// We use this struct in this case. #[derive(Deserialize, PartialEq, Eq, Hash)] struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>); + impl std::borrow::Borrow for BorrowedCowIfPossible<'_> { fn borrow(&self) -> &str { &self.0 @@ -371,14 +375,15 @@ where #[cfg(test)] mod tests { + use std::collections::HashSet; + use serde_json::json; use wasm_bindgen_test::wasm_bindgen_test; - use super::{get_current_timestamp, validate, ClaimsForValidation, Validation}; - use crate::errors::ErrorKind; use crate::Algorithm; - use std::collections::HashSet; + + use super::{get_current_timestamp, validate, ClaimsForValidation, Validation}; fn deserialize_claims(claims: &serde_json::Value) -> ClaimsForValidation { serde::Deserialize::deserialize(claims).unwrap() diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index 8c06910f..fe2fbb00 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -1,14 +1,14 @@ +use serde::{Deserialize, Serialize}; +#[cfg(feature = "use_pem")] +use time::OffsetDateTime; +use wasm_bindgen_test::wasm_bindgen_test; + use jsonwebtoken::{ crypto::{sign, verify}, Algorithm, DecodingKey, EncodingKey, }; -use serde::{Deserialize, Serialize}; - #[cfg(feature = "use_pem")] use jsonwebtoken::{decode, encode, Header, Validation}; -#[cfg(feature = "use_pem")] -use time::OffsetDateTime; -use wasm_bindgen_test::wasm_bindgen_test; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Claims { diff --git a/tests/eddsa/mod.rs b/tests/eddsa/mod.rs index 85dd0245..61cc209b 100644 --- a/tests/eddsa/mod.rs +++ b/tests/eddsa/mod.rs @@ -1,14 +1,14 @@ +use serde::{Deserialize, Serialize}; +#[cfg(feature = "use_pem")] +use time::OffsetDateTime; +use wasm_bindgen_test::wasm_bindgen_test; + use jsonwebtoken::{ crypto::{sign, verify}, Algorithm, DecodingKey, EncodingKey, }; -use serde::{Deserialize, Serialize}; -use wasm_bindgen_test::wasm_bindgen_test; - #[cfg(feature = "use_pem")] use jsonwebtoken::{decode, encode, Header, Validation}; -#[cfg(feature = "use_pem")] -use time::OffsetDateTime; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Claims { diff --git a/tests/header/mod.rs b/tests/header/mod.rs index 997a6cb7..f50e6336 100644 --- a/tests/header/mod.rs +++ b/tests/header/mod.rs @@ -1,7 +1,8 @@ use base64::{engine::general_purpose::STANDARD, Engine}; -use jsonwebtoken::Header; use wasm_bindgen_test::wasm_bindgen_test; +use jsonwebtoken::Header; + static CERT_CHAIN: [&str; 3] = include!("cert_chain.json"); #[test] diff --git a/tests/hmac.rs b/tests/hmac.rs index 47ca4484..ec24e6e7 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -1,12 +1,13 @@ +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; +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, }; -use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; -use wasm_bindgen_test::wasm_bindgen_test; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Claims { @@ -101,21 +102,19 @@ fn decode_token_missing_parts() { #[test] #[wasm_bindgen_test] -#[should_panic(expected = "InvalidSignature")] fn decode_token_invalid_signature() { let token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; let claims = decode::( token, &DecodingKey::from_secret(b"secret"), &Validation::new(Algorithm::HS256), ); - claims.unwrap(); + assert_eq!(claims.unwrap_err().into_kind(), ErrorKind::InvalidSignature); } #[test] #[wasm_bindgen_test] -#[should_panic(expected = "InvalidAlgorithm")] fn decode_token_wrong_algorithm() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; let claims = decode::( @@ -123,12 +122,11 @@ fn decode_token_wrong_algorithm() { &DecodingKey::from_secret(b"secret"), &Validation::new(Algorithm::RS512), ); - claims.unwrap(); + assert_eq!(claims.unwrap_err().into_kind(), ErrorKind::InvalidAlgorithm); } #[test] #[wasm_bindgen_test] -#[should_panic(expected = "InvalidAlgorithm")] fn encode_wrong_alg_family() { let my_claims = Claims { sub: "b@b.com".to_string(), @@ -136,7 +134,7 @@ fn encode_wrong_alg_family() { exp: OffsetDateTime::now_utc().unix_timestamp() + 10000, }; let claims = encode(&Header::default(), &my_claims, &EncodingKey::from_rsa_der(b"secret")); - claims.unwrap(); + assert_eq!(claims.unwrap_err().into_kind(), ErrorKind::InvalidAlgorithm); } #[test] diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 3297149f..9c679e88 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,14 +1,14 @@ +use serde::{Deserialize, Serialize}; +#[cfg(feature = "use_pem")] +use time::OffsetDateTime; +use wasm_bindgen_test::wasm_bindgen_test; + use jsonwebtoken::{ crypto::{sign, verify}, Algorithm, DecodingKey, EncodingKey, }; -use serde::{Deserialize, Serialize}; -use wasm_bindgen_test::wasm_bindgen_test; - #[cfg(feature = "use_pem")] use jsonwebtoken::{decode, encode, Header, Validation}; -#[cfg(feature = "use_pem")] -use time::OffsetDateTime; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256,