From 0abeeac25fda44b87d8b6ecbe55905e3b105ca67 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 29 Dec 2019 18:42:35 +0100 Subject: [PATCH 1/4] Add EncodingKey --- README.md | 8 ++-- examples/custom_chrono.rs | 13 +++++-- examples/custom_header.rs | 4 +- examples/validation.rs | 6 +-- src/crypto/ecdsa.rs | 5 +-- src/crypto/mod.rs | 17 ++++----- src/crypto/rsa.rs | 6 +-- src/encoding.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 38 +------------------ src/validation.rs | 14 +++---- tests/ecdsa/mod.rs | 19 ++++++++-- tests/hmac.rs | 11 ++++-- tests/rsa/mod.rs | 30 ++++++++------- 13 files changed, 157 insertions(+), 93 deletions(-) create mode 100644 src/encoding.rs diff --git a/README.md b/README.md index ca53cae..bb3d9c5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Complete examples are available in the examples directory: a basic one and one w In terms of imports and structs: ```rust use serde::{Serialize, Deserialize}; -use jsonwebtoken::{encode, decode, Header, Algorithm, Validation}; +use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey}; /// Our claims struct, it needs to derive `Serialize` and/or `Deserialize` #[derive(Debug, Serialize, Deserialize)] @@ -53,7 +53,7 @@ struct Claims { The default algorithm is HS256, which uses a shared secret. ```rust -let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; +let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; ``` #### Custom headers & changing algorithm @@ -63,7 +63,7 @@ If you want to set the `kid` parameter or change the algorithm for example: ```rust let mut header = Header::new(Algorithm::HS512); header.kid = Some("blabla".to_owned()); -let token = encode(&header, &my_claims, "secret".as_ref())?; +let token = encode(&header, &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; ``` Look at `examples/custom_header.rs` for a full working example. @@ -71,7 +71,7 @@ Look at `examples/custom_header.rs` for a full working example. ```rust // HS256 -let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; +let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; // RSA let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_str!("privkey.pem"))?; ``` diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs index e0b1185..facab56 100644 --- a/examples/custom_chrono.rs +++ b/examples/custom_chrono.rs @@ -1,5 +1,5 @@ use chrono::prelude::*; -use jsonwebtoken::{Header, Validation}; +use jsonwebtoken::{EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; const SECRET: &str = "some-secret"; @@ -51,8 +51,9 @@ mod jwt_numeric_date { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = encode(&Header::default(), &claims, SECRET.as_ref()) - .expect("Failed to encode claims"); + let token = + encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref())) + .expect("Failed to encode claims"); assert_eq!(&token, EXPECTED_TOKEN); @@ -82,7 +83,11 @@ fn main() -> Result<(), Box> { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = jsonwebtoken::encode(&Header::default(), &claims, SECRET.as_ref())?; + let token = jsonwebtoken::encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(SECRET.as_ref()), + )?; println!("serialized token: {}", &token); diff --git a/examples/custom_header.rs b/examples/custom_header.rs index f298c8d..adaf89c 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, Algorithm, Header, Validation}; +use jsonwebtoken::{decode, encode, Algorithm, EncodingKey, Header, Validation}; #[derive(Debug, Serialize, Deserialize)] struct Claims { @@ -19,7 +19,7 @@ fn main() { header.kid = Some("signing_key".to_owned()); header.alg = Algorithm::HS512; - let token = match encode(&header, &my_claims, key) { + let token = match encode(&header, &my_claims, &EncodingKey::from_secret(key)) { Ok(t) => t, Err(_) => panic!(), // in practice you would return the error }; diff --git a/examples/validation.rs b/examples/validation.rs index 6c22d3f..88f9da3 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,5 +1,5 @@ use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, Header, Validation}; +use jsonwebtoken::{decode, encode, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -10,10 +10,10 @@ struct Claims { } fn main() { + let key = b"secret"; let my_claims = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 }; - let key = b"secret"; - let token = match encode(&Header::default(), &my_claims, key) { + let token = match encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)) { Ok(t) => t, Err(_) => panic!(), // in practice you would return the error }; diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index 25099a7..afa0f79 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -2,7 +2,6 @@ use ring::{rand, signature}; use crate::algorithms::Algorithm; use crate::errors::Result; -use crate::pem::decoder::PemEncodedKey; use crate::serialization::b64_encode; /// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. @@ -26,13 +25,13 @@ pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSign } /// The actual ECDSA signing + encoding +/// The key needs to be in PKCS8 format pub fn sign( alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], message: &str, ) -> Result { - let pem_key = PemEncodedKey::new(key)?; - let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; + let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key)?; let rng = rand::SystemRandom::new(); let out = signing_key.sign(&rng, message.as_bytes())?; Ok(b64_encode(out.as_ref())) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 98dc440..7390286 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -2,6 +2,7 @@ use ring::constant_time::verify_slices_are_equal; use ring::{hmac, signature}; use crate::algorithms::Algorithm; +use crate::encoding::EncodingKey; use crate::errors::Result; use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, b64_encode}; @@ -20,16 +21,14 @@ pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Resu /// the base64 url safe encoded of the result. /// /// If you just want to encode a JWT, use `encode` instead. -/// -/// `key` is the secret for HMAC and a pem encoded string otherwise -pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { +pub fn sign(message: &str, key: &EncodingKey, algorithm: Algorithm) -> Result { match algorithm { - Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message), - Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message), - Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message), + Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key.inner(), message), + Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key.inner(), message), + Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key.inner(), message), Algorithm::ES256 | Algorithm::ES384 => { - ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key, message) + ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key.inner(), message) } Algorithm::RS256 @@ -37,7 +36,7 @@ pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { | Algorithm::RS512 | Algorithm::PS256 | Algorithm::PS384 - | Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key, message), + | Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key.inner(), message), } } @@ -69,7 +68,7 @@ pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) 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, key, algorithm)?; + let signed = sign(message, &EncodingKey::from_secret(key), algorithm)?; Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) } Algorithm::ES256 | Algorithm::ES384 => { diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index 89da359..ec69da5 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -3,7 +3,6 @@ use simple_asn1::BigUint; use crate::algorithms::Algorithm; use crate::errors::{ErrorKind, Result}; -use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, b64_encode}; /// Only used internally when validating RSA, to map from our enum to the Ring param structs. @@ -33,15 +32,14 @@ pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaE } /// The actual RSA signing + encoding +/// The key needs to be in PKCS8 format /// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html pub(crate) fn sign( alg: &'static dyn signature::RsaEncoding, key: &[u8], message: &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 key_pair = signature::RsaKeyPair::from_der(key).map_err(|_| ErrorKind::InvalidRsaKey)?; let mut signature = vec![0; key_pair.public_modulus_len()]; let rng = rand::SystemRandom::new(); diff --git a/src/encoding.rs b/src/encoding.rs new file mode 100644 index 0000000..8df4776 --- /dev/null +++ b/src/encoding.rs @@ -0,0 +1,79 @@ +use std::borrow::Cow; + +use serde::ser::Serialize; + +use crate::crypto; +use crate::errors::Result; +use crate::header::Header; +use crate::pem::decoder::PemEncodedKey; +use crate::serialization::b64_encode_part; + +/// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. +#[derive(Debug, Clone, PartialEq)] +pub struct EncodingKey<'a> { + content: Cow<'a, [u8]>, +} + +impl<'a> EncodingKey<'a> { + /// If you're using HMAC, use that. + pub fn from_secret(secret: &'a [u8]) -> Self { + EncodingKey { content: Cow::Borrowed(secret) } + } + + /// If you are loading a RSA key from a .pem file + /// This errors if the key is not a valid RSA key + pub fn from_rsa_pem(key: &'a [u8]) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let content = pem_key.as_rsa_key()?; + Ok(EncodingKey { content: Cow::Owned(content.to_vec()) }) + } + + /// If you are loading a ECDSA key from a .pem file + /// This errors if the key is not a valid private EC key + pub fn from_ec_pem(key: &'a [u8]) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let content = pem_key.as_ec_private_key()?; + Ok(EncodingKey { content: Cow::Owned(content.to_vec()) }) + } + + /// If you know what you're doing and have the DER-encoded key, for RSA or ECDSA + pub fn from_der(der: &'a [u8]) -> Self { + EncodingKey { content: Cow::Borrowed(der) } + } + + /// Access the key, normal users do not need to use that. + pub fn inner(&'a self) -> &'a [u8] { + &self.content + } +} + +/// Encode the header and claims given and sign the payload using the algorithm from the header and the key. +/// If the algorithm given is RSA or EC, the key needs to be in the PEM format. +/// +/// ```rust +/// use serde::{Deserialize, Serialize}; +/// use jsonwebtoken::{encode, Algorithm, Header, EncodingKey}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String +/// } +/// +/// let my_claims = Claims { +/// sub: "b@b.com".to_owned(), +/// company: "ACME".to_owned() +/// }; +/// +/// // my_claims is a struct that implements Serialize +/// // This will create a JWT using HS256 as algorithm +/// let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref())).unwrap(); +/// ``` +pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> Result { + let encoded_header = b64_encode_part(&header)?; + let encoded_claims = b64_encode_part(&claims)?; + let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); + let signature = crypto::sign(&*message, key, header.alg)?; + + Ok([message, signature].join(".")) +} diff --git a/src/lib.rs b/src/lib.rs index 77d34ca..7b3d7b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod algorithms; /// Lower level functions, if you want to do something other than JWTs pub mod crypto; mod decoding; +mod encoding; /// All the errors that can be encountered while encoding/decoding JWTs pub mod errors; mod header; @@ -18,41 +19,6 @@ pub use algorithms::Algorithm; pub use decoding::{ dangerous_unsafe_decode, decode, decode_header, decode_rsa_components, TokenData, }; +pub use encoding::{encode, EncodingKey}; pub use header::Header; pub use validation::Validation; - -use serde::ser::Serialize; - -use crate::errors::Result; -use crate::serialization::b64_encode_part; - -/// Encode the header and claims given and sign the payload using the algorithm from the header and the key. -/// If the algorithm given is RSA or EC, the key needs to be in the PEM format. -/// -/// ```rust -/// use serde::{Deserialize, Serialize}; -/// use jsonwebtoken::{encode, Algorithm, Header}; -/// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Claims { -/// sub: String, -/// company: String -/// } -/// -/// let my_claims = Claims { -/// sub: "b@b.com".to_owned(), -/// company: "ACME".to_owned() -/// }; -/// -/// // my_claims is a struct that implements Serialize -/// // This will create a JWT using HS256 as algorithm -/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); -/// ``` -pub fn encode(header: &Header, claims: &T, key: &[u8]) -> Result { - let encoded_header = b64_encode_part(&header)?; - let encoded_claims = b64_encode_part(&claims)?; - let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); - let signature = crypto::sign(&*message, key, header.alg)?; - - Ok([message, signature].join(".")) -} diff --git a/src/validation.rs b/src/validation.rs index b8106ef..b6805aa 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -159,7 +159,7 @@ pub fn validate(claims: &Map, options: &Validation) -> Result<()> return Err(new_error(ErrorKind::InvalidAudience)); } } - _ => return Err(new_error(ErrorKind::InvalidAudience)) + _ => return Err(new_error(ErrorKind::InvalidAudience)), }; } else { return Err(new_error(ErrorKind::InvalidAudience)); @@ -447,17 +447,17 @@ mod tests { #[test] fn aud_use_validation_struct() { let mut claims = Map::new(); - claims.insert("aud".to_string(), to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap()); + claims.insert( + "aud".to_string(), + to_value("my-googleclientid1234.apps.googleusercontent.com").unwrap(), + ); let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string(); let mut aud_hashset = std::collections::HashSet::new(); aud_hashset.insert(aud); - let validation = Validation { - aud: Some(aud_hashset), - validate_exp: false, - ..Validation::default() - }; + let validation = + Validation { aud: Some(aud_hashset), validate_exp: false, ..Validation::default() }; let res = validate(&claims, &validation); println!("{:?}", res); assert!(res.is_ok()); diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index d90242e..2ccec50 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -1,7 +1,7 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - decode, encode, Algorithm, Header, Validation, + decode, encode, Algorithm, EncodingKey, Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -26,7 +26,8 @@ pub struct Claims { fn round_trip_sign_verification_pem() { let privkey = include_bytes!("private_ecdsa_key.pem"); let pubkey = include_bytes!("public_ecdsa_key.pem"); - let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); + let encrypted = + sign("hello world", &EncodingKey::from_ec_pem(privkey).unwrap(), Algorithm::ES256).unwrap(); let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); assert!(is_valid); } @@ -40,7 +41,12 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap(); + let token = encode( + &Header::new(Algorithm::ES256), + &my_claims, + &EncodingKey::from_ec_pem(privkey).unwrap(), + ) + .unwrap(); let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap(); assert_eq!(my_claims, token_data.claims); } @@ -56,7 +62,12 @@ fn roundtrip_with_jwtio_example() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let token = encode(&Header::new(Algorithm::ES384), &my_claims, privkey).unwrap(); + let token = encode( + &Header::new(Algorithm::ES384), + &my_claims, + &EncodingKey::from_ec_pem(privkey).unwrap(), + ) + .unwrap(); let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES384)).unwrap(); assert_eq!(my_claims, token_data.claims); } diff --git a/tests/hmac.rs b/tests/hmac.rs index 98e2d56..d77f8bc 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -1,7 +1,8 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, Header, Validation, + dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, EncodingKey, Header, + Validation, }; use serde::{Deserialize, Serialize}; @@ -14,7 +15,8 @@ pub struct Claims { #[test] fn sign_hs256() { - let result = sign("hello world", b"secret", Algorithm::HS256).unwrap(); + let result = + sign("hello world", &EncodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap(); let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; assert_eq!(result, expected); } @@ -35,7 +37,7 @@ fn encode_with_custom_header() { }; let mut header = Header::default(); header.kid = Some("kid".to_string()); - let token = encode(&header, &my_claims, b"secret").unwrap(); + let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert_eq!("kid", token_data.header.kid.unwrap()); @@ -48,7 +50,8 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let token = encode(&Header::default(), &my_claims, b"secret").unwrap(); + let token = + encode(&Header::default(), &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 9b3301e..814c645 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,7 +1,7 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - decode, decode_rsa_components, encode, Algorithm, Header, Validation, + decode, decode_rsa_components, encode, Algorithm, EncodingKey, Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -27,7 +27,8 @@ fn round_trip_sign_verification_pem_pkcs1() { let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); for &alg in RSA_ALGORITHMS { - let encrypted = sign("hello world", privkey_pem, alg).unwrap(); + let encrypted = + sign("hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); assert!(is_valid); } @@ -39,7 +40,8 @@ fn round_trip_sign_verification_pem_pkcs8() { let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); for &alg in RSA_ALGORITHMS { - let encrypted = sign("hello world", privkey_pem, alg).unwrap(); + let encrypted = + sign("hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); assert!(is_valid); } @@ -55,7 +57,9 @@ fn round_trip_claim() { let privkey = include_bytes!("private_rsa_key_pkcs1.pem"); for &alg in RSA_ALGORITHMS { - let token = encode(&Header::new(alg), &my_claims, privkey).unwrap(); + let token = + encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey).unwrap()) + .unwrap(); let token_data = decode::( &token, include_bytes!("public_rsa_key_pkcs1.pem"), @@ -78,18 +82,16 @@ fn rsa_modulus_exponent() { let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"; let e = "AQAB"; - let encrypted = encode(&Header::new(Algorithm::RS256), &my_claims, privkey.as_ref()).unwrap(); + let encrypted = encode( + &Header::new(Algorithm::RS256), + &my_claims, + &EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(), + ) + .unwrap(); let res = decode_rsa_components::(&encrypted, n, e, &Validation::new(Algorithm::RS256)); assert!(res.is_ok()); } -#[test] -#[should_panic(expected = "InvalidKeyFormat")] -fn fails_with_non_pkcs8_key_format() { - let _encrypted = - sign("hello world", include_bytes!("private_rsa_key_pkcs1.pem"), Algorithm::ES256).unwrap(); -} - // https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken #[test] fn roundtrip_with_jwtio_example_jey() { @@ -103,7 +105,9 @@ fn roundtrip_with_jwtio_example_jey() { }; for &alg in RSA_ALGORITHMS { - let token = encode(&Header::new(alg), &my_claims, privkey_pem).unwrap(); + let token = + encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap()) + .unwrap(); let token_data = decode::(&token, pubkey_pem, &Validation::new(alg)).unwrap(); assert_eq!(my_claims, token_data.claims); } From 77ae0effc86960c1af935d155edbca9a1dadd266 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 29 Dec 2019 21:50:06 +0100 Subject: [PATCH 2/4] Add DecodingKey --- Cargo.toml | 2 +- README.md | 22 ++---- benches/jwt.rs | 9 ++- examples/custom_chrono.rs | 17 +++-- examples/custom_header.rs | 8 ++- examples/validation.rs | 4 +- src/crypto/mod.rs | 62 ++++++----------- src/crypto/rsa.rs | 4 +- src/decoding.rs | 142 ++++++++++++++++++++------------------ src/encoding.rs | 7 +- src/lib.rs | 4 +- tests/ecdsa/mod.rs | 61 ++++++++++------ tests/hmac.rs | 33 ++++++--- tests/rsa/mod.rs | 45 +++++++++--- 14 files changed, 235 insertions(+), 185 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d6f4a2a..02a49ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jsonwebtoken" -version = "7.0.0-alpha.2" +version = "7.0.0-alpha.3" authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" diff --git a/README.md b/README.md index bb3d9c5..15dda9d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Complete examples are available in the examples directory: a basic one and one w In terms of imports and structs: ```rust use serde::{Serialize, Deserialize}; -use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey}; +use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; /// Our claims struct, it needs to derive `Serialize` and/or `Deserialize` #[derive(Debug, Serialize, Deserialize)] @@ -73,7 +73,7 @@ Look at `examples/custom_header.rs` for a full working example. // HS256 let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?; // RSA -let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_str!("privkey.pem"))?; +let token = encode(&Header::new(Algorithm::RS256), &my_claims, &EncodingKey::from_rsa_pem(include_bytes!("privkey.pem"))?)?; ``` Encoding a JWT takes 3 parameters: @@ -82,13 +82,13 @@ Encoding a JWT takes 3 parameters: - a key/secret When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using -RSA/EC, the key should always be the content of the private key in the PEM format. +RSA/EC, the key should always be the content of the private key in the PEM or DER format. ### Decoding ```rust // `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. -let token = decode::(&token, "secret".as_ref(), &Validation::default())?; +let token = decode::(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?; ``` `decode` can error for a variety of reasons: @@ -97,7 +97,7 @@ let token = decode::(&token, "secret".as_ref(), &Validation::default())? - validation of at least one reserved claim failed As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using -RSA/EC, the key should always be the content of the public key in the PEM format. +RSA/EC, the key should always be the content of the public key in the PEM or DER format. In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can choose to decode only the header: @@ -121,15 +121,7 @@ The main use-case is for JWK where your public key is in a JSON format like so: ```rust // `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. -let token = decode_rsa_components::(&token, jwk["n"], jwk["e"], &Validation::new(Algorithm::RS256))?; -``` - -### Converting .der to .pem - -You can use openssl for that: - -```bash -openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem +let token = decode::(&token, &EncodingKey::from_rsa_components(jwk["n"], jwk["e"]), &Validation::new(Algorithm::RS256))?; ``` ### Convert SEC1 private key to PKCS8 @@ -145,7 +137,7 @@ openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem This library validates automatically the `exp` claim and `nbf` is validated if present. You can also validate the `sub`, `iss` and `aud` but those require setting the expected value in the `Validation` struct. -Since validating time fields is always a bit tricky due to clock skew, +Since validating time fields is always a bit tricky due to clock skew, you can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` field. Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`. diff --git a/benches/jwt.rs b/benches/jwt.rs index 9efe6e1..41a4fc9 100644 --- a/benches/jwt.rs +++ b/benches/jwt.rs @@ -1,7 +1,7 @@ #![feature(test)] extern crate test; -use jsonwebtoken::{decode, encode, Header, Validation}; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -13,12 +13,15 @@ struct Claims { #[bench] fn bench_encode(b: &mut test::Bencher) { let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() }; + let key = EncodingKey::from_secret("secret".as_ref()); - b.iter(|| encode(&Header::default(), &claim, "secret".as_ref())); + b.iter(|| encode(&Header::default(), &claim, &key)); } #[bench] fn bench_decode(b: &mut test::Bencher) { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; - b.iter(|| decode::(token, "secret".as_ref(), &Validation::default())); + let key = DecodingKey::from_secret("secret".as_ref()); + + b.iter(|| decode::(token, &key, &Validation::default())); } diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs index facab56..561ad10 100644 --- a/examples/custom_chrono.rs +++ b/examples/custom_chrono.rs @@ -1,5 +1,5 @@ use chrono::prelude::*; -use jsonwebtoken::{EncodingKey, Header, Validation}; +use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; const SECRET: &str = "some-secret"; @@ -57,8 +57,12 @@ mod jwt_numeric_date { assert_eq!(&token, EXPECTED_TOKEN); - let decoded = decode::(&token, SECRET.as_ref(), &Validation::default()) - .expect("Failed to decode token"); + let decoded = decode::( + &token, + &DecodingKey::from_secret(SECRET.as_ref()), + &Validation::default(), + ) + .expect("Failed to decode token"); assert_eq!(decoded.claims, claims); } @@ -91,8 +95,11 @@ fn main() -> Result<(), Box> { println!("serialized token: {}", &token); - let token_data = - jsonwebtoken::decode::(&token, SECRET.as_ref(), &Validation::default())?; + let token_data = jsonwebtoken::decode::( + &token, + &DecodingKey::from_secret(SECRET.as_ref()), + &Validation::default(), + )?; println!("token data:\n{:#?}", &token_data); Ok(()) diff --git a/examples/custom_header.rs b/examples/custom_header.rs index adaf89c..3ea556a 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, Algorithm, EncodingKey, Header, Validation}; +use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; #[derive(Debug, Serialize, Deserialize)] struct Claims { @@ -25,7 +25,11 @@ fn main() { }; println!("{:?}", token); - let token_data = match decode::(&token, key, &Validation::new(Algorithm::HS512)) { + let token_data = match decode::( + &token, + &DecodingKey::from_secret(key), + &Validation::new(Algorithm::HS512), + ) { Ok(c) => c, Err(err) => match *err.kind() { ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error diff --git a/examples/validation.rs b/examples/validation.rs index 88f9da3..928770c 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,5 +1,5 @@ use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, EncodingKey, Header, Validation}; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -19,7 +19,7 @@ fn main() { }; let validation = Validation { sub: Some("b@b.com".to_string()), ..Validation::default() }; - let token_data = match decode::(&token, key, &validation) { + let token_data = match decode::(&token, &DecodingKey::from_secret(key), &validation) { Ok(c) => c, Err(err) => match *err.kind() { ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 7390286..fffeff5 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -2,9 +2,9 @@ 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::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, b64_encode}; pub(crate) mod ecdsa; @@ -62,57 +62,37 @@ fn verify_ring( /// `signature` is the signature part of a jwt (text after the second '.') /// /// `message` is base64(header) + "." + base64(claims) -/// For ECDSA/RSA, the `key` is the pem public key. If you want to verify using the public key -/// components (modulus/exponent), use `verify_rsa_components` instead. -pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result { +pub fn verify( + signature: &str, + message: &str, + key: &DecodingKey, + algorithm: Algorithm, +) -> 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), algorithm)?; + let signed = sign(message, &EncodingKey::from_secret(key.as_bytes()), algorithm)?; Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) } - Algorithm::ES256 | Algorithm::ES384 => { - let pem_key = PemEncodedKey::new(key)?; - verify_ring( - ecdsa::alg_to_ec_verification(algorithm), - signature, - message, - pem_key.as_ec_public_key()?, - ) - } + Algorithm::ES256 | Algorithm::ES384 => verify_ring( + ecdsa::alg_to_ec_verification(algorithm), + signature, + message, + key.as_bytes(), + ), Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 | Algorithm::PS256 | Algorithm::PS384 | Algorithm::PS512 => { - let pem_key = PemEncodedKey::new(key)?; - verify_ring( - rsa::alg_to_rsa_parameters(algorithm), - signature, - message, - pem_key.as_rsa_key()?, - ) + 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)) + } + } } } } - -/// Verify the signature given using the (n, e) components of a RSA public key. -/// -/// `signature` is the signature part of a jwt (text after the second '.') -/// -/// `message` is base64(header) + "." + base64(claims) -pub fn verify_rsa_components( - signature: &str, - message: &str, - components: (&str, &str), - alg: Algorithm, -) -> Result { - let signature_bytes = b64_decode(signature)?; - rsa::verify_from_components( - rsa::alg_to_rsa_parameters(alg), - &signature_bytes, - message, - components, - ) -} diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index ec69da5..376c13a 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -50,12 +50,14 @@ pub(crate) fn sign( 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, - signature_bytes: &[u8], + signature: &str, message: &str, components: (&str, &str), ) -> Result { + let signature_bytes = b64_decode(signature)?; let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be(); let e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be(); let pubkey = signature::RsaPublicKeyComponents { n, e }; diff --git a/src/decoding.rs b/src/decoding.rs index d740052..bbd5705 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -1,8 +1,11 @@ +use std::borrow::Cow; + use serde::de::DeserializeOwned; -use crate::crypto::{verify, verify_rsa_components}; +use crate::crypto::verify; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; +use crate::pem::decoder::PemEncodedKey; use crate::serialization::from_jwt_part_claims; use crate::validation::{validate, Validation}; @@ -27,15 +30,78 @@ macro_rules! expect_two { }}; } -/// Internal way to differentiate between public key types -enum DecodingKey<'a> { - SecretOrPem(&'a [u8]), +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum DecodingKeyKind<'a> { + SecretOrDer(Cow<'a, [u8]>), RsaModulusExponent { n: &'a str, e: &'a str }, } -fn _decode( +/// All the different kind of keys we can use to decode a JWT +/// This key can be re-used so make sure you only initialize it once if you can for better performance +#[derive(Debug, Clone, PartialEq)] +pub struct DecodingKey<'a> { + pub(crate) kind: DecodingKeyKind<'a>, +} + +impl<'a> DecodingKey<'a> { + /// If you're using HMAC, use this. + pub fn from_secret(secret: &'a [u8]) -> Self { + DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(secret)) } + } + + /// If you are loading a public RSA key in a PEM format, use this. + pub fn from_rsa_pem(key: &'a [u8]) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let content = pem_key.as_rsa_key()?; + Ok(DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())) }) + } + + /// If you have (n, e) RSA public key components, use this. + pub fn from_rsa_components(modulus: &'a str, exponent: &'a str) -> Self { + DecodingKey { kind: DecodingKeyKind::RsaModulusExponent { n: modulus, e: exponent } } + } + + /// If you have a ECDSA public key in PEM format, use this. + pub fn from_ec_pem(key: &'a [u8]) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let content = pem_key.as_ec_public_key()?; + Ok(DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())) }) + } + + /// If you know what you're doing and have the DER encoded public key, use this. + pub fn from_der(der: &'a [u8]) -> Self { + DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)) } + } + + pub(crate) fn as_bytes(&self) -> &[u8] { + match &self.kind { + DecodingKeyKind::SecretOrDer(b) => &b, + DecodingKeyKind::RsaModulusExponent { .. } => unreachable!(), + } + } +} + +/// Decode and validate a JWT +/// +/// If the token or its signature is invalid or the claims fail validation, it will return an error. +/// +/// ```rust +/// use serde::{Deserialize, Serialize}; +/// use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String +/// } +/// +/// let token = "a.jwt.token".to_string(); +/// // Claims is a struct that implements Deserialize +/// let token_message = decode::(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::new(Algorithm::HS256)); +/// ``` +pub fn decode( token: &str, - key: DecodingKey, + key: &DecodingKey, validation: &Validation, ) -> Result> { let (signature, message) = expect_two!(token.rsplitn(2, '.')); @@ -46,14 +112,7 @@ fn _decode( return Err(new_error(ErrorKind::InvalidAlgorithm)); } - let is_valid = match key { - DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg), - DecodingKey::RsaModulusExponent { n, e } => { - verify_rsa_components(signature, message, (n, e), header.alg) - } - }?; - - if !is_valid { + if !verify(signature, message, key, header.alg)? { return Err(new_error(ErrorKind::InvalidSignature)); } @@ -63,61 +122,6 @@ fn _decode( Ok(TokenData { header, claims: decoded_claims }) } -/// Decode and validate a JWT using a secret for HS and a public PEM format for RSA/EC -/// -/// If the token or its signature is invalid or the claims fail validation, it will return an error. -/// -/// ```rust -/// use serde::{Deserialize, Serialize}; -/// use jsonwebtoken::{decode, Validation, Algorithm}; -/// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Claims { -/// sub: String, -/// company: String -/// } -/// -/// let token = "a.jwt.token".to_string(); -/// // Claims is a struct that implements Deserialize -/// let token_message = decode::(&token, "secret".as_ref(), &Validation::new(Algorithm::HS256)); -/// ``` -pub fn decode( - token: &str, - key: &[u8], - validation: &Validation, -) -> Result> { - _decode(token, DecodingKey::SecretOrPem(key), validation) -} - -/// Decode and validate a JWT using (n, e) base64 encoded public key components for RSA -/// -/// If the token or its signature is invalid or the claims fail validation, it will return an error. -/// -/// ```rust -/// use serde::{Deserialize, Serialize}; -/// use jsonwebtoken::{decode_rsa_components, Validation, Algorithm}; -/// -/// #[derive(Debug, Serialize, Deserialize)] -/// struct Claims { -/// sub: String, -/// company: String -/// } -/// -/// let modulus = "some-base64-data"; -/// let exponent = "some-base64-data"; -/// let token = "a.jwt.token".to_string(); -/// // Claims is a struct that implements Deserialize -/// let token_message = decode_rsa_components::(&token, &modulus, &exponent, &Validation::new(Algorithm::HS256)); -/// ``` -pub fn decode_rsa_components( - token: &str, - modulus: &str, - exponent: &str, - validation: &Validation, -) -> Result> { - _decode(token, DecodingKey::RsaModulusExponent { n: modulus, e: exponent }, validation) -} - /// Decode a JWT without any signature verification/validations. /// /// NOTE: Do not use this unless you know what you are doing! If the token's signature is invalid, it will *not* return an error. diff --git a/src/encoding.rs b/src/encoding.rs index 8df4776..337eb4b 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -9,6 +9,7 @@ use crate::pem::decoder::PemEncodedKey; use crate::serialization::b64_encode_part; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. +/// This key can be re-used so make sure you only initialize it once if you can for better performance #[derive(Debug, Clone, PartialEq)] pub struct EncodingKey<'a> { content: Cow<'a, [u8]>, @@ -20,8 +21,8 @@ impl<'a> EncodingKey<'a> { EncodingKey { content: Cow::Borrowed(secret) } } - /// If you are loading a RSA key from a .pem file - /// This errors if the key is not a valid RSA key + /// If you are loading a RSA key from a .pem file. + /// This errors if the key is not a valid RSA key. pub fn from_rsa_pem(key: &'a [u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_rsa_key()?; @@ -42,7 +43,7 @@ impl<'a> EncodingKey<'a> { } /// Access the key, normal users do not need to use that. - pub fn inner(&'a self) -> &'a [u8] { + pub(crate) fn inner(&'a self) -> &'a [u8] { &self.content } } diff --git a/src/lib.rs b/src/lib.rs index 7b3d7b5..96ac22f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,7 @@ mod serialization; mod validation; pub use algorithms::Algorithm; -pub use decoding::{ - dangerous_unsafe_decode, decode, decode_header, decode_rsa_components, TokenData, -}; +pub use decoding::{dangerous_unsafe_decode, decode, decode_header, DecodingKey, TokenData}; pub use encoding::{encode, EncodingKey}; pub use header::Header; pub use validation::Validation; diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index 2ccec50..8e39d78 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -1,7 +1,7 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - decode, encode, Algorithm, EncodingKey, Header, Validation, + decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -12,30 +12,37 @@ pub struct Claims { exp: i64, } -// TODO: remove completely? -//#[test] -//fn round_trip_sign_verification_pk8() { -// let privkey = include_bytes!("private_ecdsa_key.pk8"); -// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); -// let pubkey = include_bytes!("public_ecdsa_key.pk8"); -// let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); -// assert!(is_valid); -//} +#[test] +fn round_trip_sign_verification_pk8() { + let privkey = include_bytes!("private_ecdsa_key.pk8"); + let pubkey = include_bytes!("public_ecdsa_key.pk8"); + + let encrypted = sign("hello world", &EncodingKey::from_der(privkey), Algorithm::ES256).unwrap(); + let is_valid = verify(&encrypted, "hello world", &DecodingKey::from_der(pubkey), Algorithm::ES256).unwrap(); + assert!(is_valid); +} #[test] fn round_trip_sign_verification_pem() { - let privkey = include_bytes!("private_ecdsa_key.pem"); - let pubkey = include_bytes!("public_ecdsa_key.pem"); + let privkey_pem = include_bytes!("private_ecdsa_key.pem"); + let pubkey_pem = include_bytes!("public_ecdsa_key.pem"); let encrypted = - sign("hello world", &EncodingKey::from_ec_pem(privkey).unwrap(), Algorithm::ES256).unwrap(); - let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); + sign("hello world", &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES256) + .unwrap(); + let is_valid = verify( + &encrypted, + "hello world", + &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), + Algorithm::ES256, + ) + .unwrap(); assert!(is_valid); } #[test] fn round_trip_claim() { - let privkey = include_bytes!("private_ecdsa_key.pem"); - let pubkey = include_bytes!("public_ecdsa_key.pem"); + let privkey_pem = include_bytes!("private_ecdsa_key.pem"); + let pubkey_pem = include_bytes!("public_ecdsa_key.pem"); let my_claims = Claims { sub: "b@b.com".to_string(), company: "ACME".to_string(), @@ -44,10 +51,15 @@ fn round_trip_claim() { let token = encode( &Header::new(Algorithm::ES256), &my_claims, - &EncodingKey::from_ec_pem(privkey).unwrap(), + &EncodingKey::from_ec_pem(privkey_pem).unwrap(), + ) + .unwrap(); + let token_data = decode::( + &token, + &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), + &Validation::new(Algorithm::ES256), ) .unwrap(); - let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap(); assert_eq!(my_claims, token_data.claims); } @@ -55,8 +67,8 @@ fn round_trip_claim() { #[test] fn roundtrip_with_jwtio_example() { // We currently do not support SEC1 so we use the converted PKCS8 formatted - let privkey = include_bytes!("private_jwtio_pkcs8.pem"); - let pubkey = include_bytes!("public_jwtio.pem"); + let privkey_pem = include_bytes!("private_jwtio_pkcs8.pem"); + let pubkey_pem = include_bytes!("public_jwtio.pem"); let my_claims = Claims { sub: "b@b.com".to_string(), company: "ACME".to_string(), @@ -65,9 +77,14 @@ fn roundtrip_with_jwtio_example() { let token = encode( &Header::new(Algorithm::ES384), &my_claims, - &EncodingKey::from_ec_pem(privkey).unwrap(), + &EncodingKey::from_ec_pem(privkey_pem).unwrap(), + ) + .unwrap(); + let token_data = decode::( + &token, + &DecodingKey::from_ec_pem(pubkey_pem).unwrap(), + &Validation::new(Algorithm::ES384), ) .unwrap(); - let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES384)).unwrap(); assert_eq!(my_claims, token_data.claims); } diff --git a/tests/hmac.rs b/tests/hmac.rs index d77f8bc..94ee664 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -1,8 +1,8 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, EncodingKey, Header, - Validation, + dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, DecodingKey, EncodingKey, + Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -24,7 +24,8 @@ fn sign_hs256() { #[test] fn verify_hs256() { let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - let valid = verify(sig, "hello world", b"secret", Algorithm::HS256).unwrap(); + let valid = + verify(sig, "hello world", &DecodingKey::from_secret(b"secret"), Algorithm::HS256).unwrap(); assert!(valid); } @@ -38,7 +39,9 @@ fn encode_with_custom_header() { let mut header = Header::default(); header.kid = Some("kid".to_string()); let token = encode(&header, &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); - let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); + let token_data = + decode::(&token, &DecodingKey::from_secret(b"secret"), &Validation::default()) + .unwrap(); assert_eq!(my_claims, token_data.claims); assert_eq!("kid", token_data.header.kid.unwrap()); } @@ -52,7 +55,9 @@ fn round_trip_claim() { }; let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(b"secret")).unwrap(); - let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); + let token_data = + decode::(&token, &DecodingKey::from_secret(b"secret"), &Validation::default()) + .unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } @@ -60,7 +65,8 @@ fn round_trip_claim() { #[test] fn decode_token() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; - let claims = decode::(token, b"secret", &Validation::default()); + let claims = + decode::(token, &DecodingKey::from_secret(b"secret"), &Validation::default()); println!("{:?}", claims); claims.unwrap(); } @@ -69,7 +75,8 @@ fn decode_token() { #[should_panic(expected = "InvalidToken")] fn decode_token_missing_parts() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = decode::(token, b"secret", &Validation::default()); + let claims = + decode::(token, &DecodingKey::from_secret(b"secret"), &Validation::default()); claims.unwrap(); } @@ -78,7 +85,8 @@ fn decode_token_missing_parts() { fn decode_token_invalid_signature() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; - let claims = decode::(token, b"secret", &Validation::default()); + let claims = + decode::(token, &DecodingKey::from_secret(b"secret"), &Validation::default()); claims.unwrap(); } @@ -86,14 +94,19 @@ fn decode_token_invalid_signature() { #[should_panic(expected = "InvalidAlgorithm")] fn decode_token_wrong_algorithm() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; - let claims = decode::(token, b"secret", &Validation::new(Algorithm::RS512)); + let claims = decode::( + token, + &DecodingKey::from_secret(b"secret"), + &Validation::new(Algorithm::RS512), + ); claims.unwrap(); } #[test] fn decode_token_with_bytes_secret() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; - let claims = decode::(token, b"\x01\x02\x03", &Validation::default()); + let claims = + decode::(token, &DecodingKey::from_secret(b"\x01\x02\x03"), &Validation::default()); assert!(claims.is_ok()); } diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 814c645..08c3f8a 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,7 +1,7 @@ use chrono::Utc; use jsonwebtoken::{ crypto::{sign, verify}, - decode, decode_rsa_components, encode, Algorithm, EncodingKey, Header, Validation, + decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -29,7 +29,9 @@ fn round_trip_sign_verification_pem_pkcs1() { for &alg in RSA_ALGORITHMS { let encrypted = sign("hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); - let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); + let is_valid = + verify(&encrypted, "hello world", &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), alg) + .unwrap(); assert!(is_valid); } } @@ -42,7 +44,24 @@ fn round_trip_sign_verification_pem_pkcs8() { for &alg in RSA_ALGORITHMS { let encrypted = sign("hello world", &EncodingKey::from_rsa_pem(privkey_pem).unwrap(), alg).unwrap(); - let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); + let is_valid = + verify(&encrypted, "hello world", &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), alg) + .unwrap(); + assert!(is_valid); + } +} + +#[test] +fn round_trip_sign_verification_der() { + let privkey_der = include_bytes!("private_rsa_key.der"); + let pubkey_der = include_bytes!("public_rsa_key.der"); + + for &alg in RSA_ALGORITHMS { + let encrypted = + sign("hello world", &EncodingKey::from_der(privkey_der), alg).unwrap(); + let is_valid = + verify(&encrypted, "hello world", &DecodingKey::from_der(pubkey_der), alg) + .unwrap(); assert!(is_valid); } } @@ -54,15 +73,16 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let privkey = include_bytes!("private_rsa_key_pkcs1.pem"); + let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); + let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); for &alg in RSA_ALGORITHMS { let token = - encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey).unwrap()) + encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap()) .unwrap(); let token_data = decode::( &token, - include_bytes!("public_rsa_key_pkcs1.pem"), + &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), &Validation::new(alg), ) .unwrap(); @@ -88,7 +108,11 @@ fn rsa_modulus_exponent() { &EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(), ) .unwrap(); - let res = decode_rsa_components::(&encrypted, n, e, &Validation::new(Algorithm::RS256)); + let res = decode::( + &encrypted, + &DecodingKey::from_rsa_components(n, e), + &Validation::new(Algorithm::RS256), + ); assert!(res.is_ok()); } @@ -108,7 +132,12 @@ fn roundtrip_with_jwtio_example_jey() { let token = encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap()) .unwrap(); - let token_data = decode::(&token, pubkey_pem, &Validation::new(alg)).unwrap(); + let token_data = decode::( + &token, + &DecodingKey::from_rsa_pem(pubkey_pem).unwrap(), + &Validation::new(alg), + ) + .unwrap(); assert_eq!(my_claims, token_data.claims); } } From 4dd2f12c6de76e455e82c1a910722bc8566814e6 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 2 Jan 2020 19:40:53 +0100 Subject: [PATCH 3/4] Remove EncodingKey lifetime --- src/encoding.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/encoding.rs b/src/encoding.rs index 337eb4b..175e081 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use serde::ser::Serialize; use crate::crypto; @@ -11,39 +9,38 @@ use crate::serialization::b64_encode_part; /// A key to encode a JWT with. Can be a secret, a PEM-encoded key or a DER-encoded key. /// This key can be re-used so make sure you only initialize it once if you can for better performance #[derive(Debug, Clone, PartialEq)] -pub struct EncodingKey<'a> { - content: Cow<'a, [u8]>, +pub struct EncodingKey { + content: Vec, } -impl<'a> EncodingKey<'a> { +impl EncodingKey { /// If you're using HMAC, use that. - pub fn from_secret(secret: &'a [u8]) -> Self { - EncodingKey { content: Cow::Borrowed(secret) } + pub fn from_secret(secret: &[u8]) -> Self { + EncodingKey { content: secret.to_vec() } } /// If you are loading a RSA key from a .pem file. /// This errors if the key is not a valid RSA key. - pub fn from_rsa_pem(key: &'a [u8]) -> Result { + pub fn from_rsa_pem(key: &[u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_rsa_key()?; - Ok(EncodingKey { content: Cow::Owned(content.to_vec()) }) + Ok(EncodingKey { content: content.to_vec() }) } /// If you are loading a ECDSA key from a .pem file /// This errors if the key is not a valid private EC key - pub fn from_ec_pem(key: &'a [u8]) -> Result { + pub fn from_ec_pem(key: &[u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_ec_private_key()?; - Ok(EncodingKey { content: Cow::Owned(content.to_vec()) }) + Ok(EncodingKey { content: content.to_vec() }) } /// If you know what you're doing and have the DER-encoded key, for RSA or ECDSA - pub fn from_der(der: &'a [u8]) -> Self { - EncodingKey { content: Cow::Borrowed(der) } + pub fn from_der(der: &[u8]) -> Self { + EncodingKey { content: der.to_vec() } } - /// Access the key, normal users do not need to use that. - pub(crate) fn inner(&'a self) -> &'a [u8] { + pub(crate) fn inner(&self) -> &[u8] { &self.content } } From 689cc6d32e05b0c04be41b706dc904aab9000206 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 13 Jan 2020 19:38:33 +0100 Subject: [PATCH 4/4] Validate key type with algo in encode/decode --- src/algorithms.rs | 22 +++++++++++++++++++ src/decoding.rs | 54 ++++++++++++++++++++++++++++++++++++++++------ src/encoding.rs | 32 ++++++++++++++++++++------- src/errors.rs | 3 ++- tests/ecdsa/mod.rs | 7 ++++-- tests/hmac.rs | 12 +++++++++++ tests/rsa/mod.rs | 6 ++---- 7 files changed, 114 insertions(+), 22 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index c9ff848..20edbf1 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -2,6 +2,13 @@ use crate::errors::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use std::str::FromStr; +#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] +pub(crate) enum AlgorithmFamily { + Hmac, + Rsa, + Ec, +} + /// The algorithms supported for signing/verifying JWTs #[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] pub enum Algorithm { @@ -58,6 +65,21 @@ impl FromStr for Algorithm { } } +impl Algorithm { + pub(crate) fn family(self) -> AlgorithmFamily { + match self { + Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => AlgorithmFamily::Hmac, + Algorithm::RS256 + | Algorithm::RS384 + | Algorithm::RS512 + | Algorithm::PS256 + | Algorithm::PS384 + | Algorithm::PS512 => AlgorithmFamily::Rsa, + Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/decoding.rs b/src/decoding.rs index bbd5705..36837f0 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use serde::de::DeserializeOwned; +use crate::algorithms::AlgorithmFamily; use crate::crypto::verify; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; @@ -40,37 +41,70 @@ pub(crate) enum DecodingKeyKind<'a> { /// This key can be re-used so make sure you only initialize it once if you can for better performance #[derive(Debug, Clone, PartialEq)] pub struct DecodingKey<'a> { + pub(crate) family: AlgorithmFamily, pub(crate) kind: DecodingKeyKind<'a>, } impl<'a> DecodingKey<'a> { /// If you're using HMAC, use this. pub fn from_secret(secret: &'a [u8]) -> Self { - DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(secret)) } + DecodingKey { + family: AlgorithmFamily::Hmac, + kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(secret)), + } + } + + /// If you're using HMAC with a base64 encoded, use this. + pub fn from_base64_secret(secret: &str) -> Result { + let out = base64::decode(&secret)?; + Ok(DecodingKey { + family: AlgorithmFamily::Hmac, + kind: DecodingKeyKind::SecretOrDer(Cow::Owned(out)), + }) } /// If you are loading a public RSA key in a PEM format, use this. pub fn from_rsa_pem(key: &'a [u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_rsa_key()?; - Ok(DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())) }) + Ok(DecodingKey { + family: AlgorithmFamily::Rsa, + kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())), + }) } /// If you have (n, e) RSA public key components, use this. pub fn from_rsa_components(modulus: &'a str, exponent: &'a str) -> Self { - DecodingKey { kind: DecodingKeyKind::RsaModulusExponent { n: modulus, e: exponent } } + DecodingKey { + family: AlgorithmFamily::Rsa, + kind: DecodingKeyKind::RsaModulusExponent { n: modulus, e: exponent }, + } } /// If you have a ECDSA public key in PEM format, use this. pub fn from_ec_pem(key: &'a [u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_ec_public_key()?; - Ok(DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())) }) + Ok(DecodingKey { + family: AlgorithmFamily::Ec, + kind: DecodingKeyKind::SecretOrDer(Cow::Owned(content.to_vec())), + }) } - /// If you know what you're doing and have the DER encoded public key, use this. - pub fn from_der(der: &'a [u8]) -> Self { - DecodingKey { kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)) } + /// If you know what you're doing and have a RSA DER encoded public key, use this. + pub fn from_rsa_der(der: &'a [u8]) -> Self { + DecodingKey { + family: AlgorithmFamily::Rsa, + kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)), + } + } + + /// If you know what you're doing and have a RSA EC encoded public key, use this. + pub fn from_ec_der(der: &'a [u8]) -> Self { + DecodingKey { + family: AlgorithmFamily::Ec, + kind: DecodingKeyKind::SecretOrDer(Cow::Borrowed(der)), + } } pub(crate) fn as_bytes(&self) -> &[u8] { @@ -104,6 +138,12 @@ pub fn decode( key: &DecodingKey, validation: &Validation, ) -> Result> { + for alg in &validation.algorithms { + if key.family != alg.family() { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + } + let (signature, message) = expect_two!(token.rsplitn(2, '.')); let (claims, header) = expect_two!(message.rsplitn(2, '.')); let header = Header::from_encoded(header)?; diff --git a/src/encoding.rs b/src/encoding.rs index 175e081..3f7a690 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,7 +1,8 @@ use serde::ser::Serialize; +use crate::algorithms::AlgorithmFamily; use crate::crypto; -use crate::errors::Result; +use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; use crate::pem::decoder::PemEncodedKey; use crate::serialization::b64_encode_part; @@ -10,13 +11,20 @@ use crate::serialization::b64_encode_part; /// This key can be re-used so make sure you only initialize it once if you can for better performance #[derive(Debug, Clone, PartialEq)] pub struct EncodingKey { + pub(crate) family: AlgorithmFamily, content: Vec, } impl EncodingKey { - /// If you're using HMAC, use that. + /// If you're using a HMAC secret that is not base64, use that. pub fn from_secret(secret: &[u8]) -> Self { - EncodingKey { content: secret.to_vec() } + EncodingKey { family: AlgorithmFamily::Hmac, content: secret.to_vec() } + } + + /// If you have a base64 HMAC secret, use that. + pub fn from_base64_secret(secret: &str) -> Result { + let out = base64::decode(&secret)?; + Ok(EncodingKey { family: AlgorithmFamily::Hmac, content: out }) } /// If you are loading a RSA key from a .pem file. @@ -24,7 +32,7 @@ impl EncodingKey { pub fn from_rsa_pem(key: &[u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_rsa_key()?; - Ok(EncodingKey { content: content.to_vec() }) + Ok(EncodingKey { family: AlgorithmFamily::Rsa, content: content.to_vec() }) } /// If you are loading a ECDSA key from a .pem file @@ -32,12 +40,17 @@ impl EncodingKey { pub fn from_ec_pem(key: &[u8]) -> Result { let pem_key = PemEncodedKey::new(key)?; let content = pem_key.as_ec_private_key()?; - Ok(EncodingKey { content: content.to_vec() }) + Ok(EncodingKey { family: AlgorithmFamily::Ec, content: content.to_vec() }) } - /// If you know what you're doing and have the DER-encoded key, for RSA or ECDSA - pub fn from_der(der: &[u8]) -> Self { - EncodingKey { content: der.to_vec() } + /// If you know what you're doing and have the DER-encoded key, for RSA only + pub fn from_rsa_der(der: &[u8]) -> Self { + EncodingKey { family: AlgorithmFamily::Rsa, content: der.to_vec() } + } + + /// If you know what you're doing and have the DER-encoded key, for ECDSA + pub fn from_ec_der(der: &[u8]) -> Self { + EncodingKey { family: AlgorithmFamily::Ec, content: der.to_vec() } } pub(crate) fn inner(&self) -> &[u8] { @@ -68,6 +81,9 @@ impl EncodingKey { /// let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref())).unwrap(); /// ``` pub fn encode(header: &Header, claims: &T, key: &EncodingKey) -> Result { + if key.family != header.alg.family() { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } let encoded_header = b64_encode_part(&header)?; let encoded_claims = b64_encode_part(&claims)?; let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); diff --git a/src/errors.rs b/src/errors.rs index 87231ed..580e8b3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -56,7 +56,8 @@ pub enum ErrorKind { InvalidSubject, /// When a token’s nbf claim represents a time in the future ImmatureSignature, - /// When the algorithm in the header doesn't match the one passed to `decode` + /// When the algorithm in the header doesn't match the one passed to `decode` or the encoding/decoding key + /// used doesn't match the alg requested InvalidAlgorithm, // 3rd party errors diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index 8e39d78..2362409 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -17,8 +17,11 @@ fn round_trip_sign_verification_pk8() { let privkey = include_bytes!("private_ecdsa_key.pk8"); let pubkey = include_bytes!("public_ecdsa_key.pk8"); - let encrypted = sign("hello world", &EncodingKey::from_der(privkey), Algorithm::ES256).unwrap(); - let is_valid = verify(&encrypted, "hello world", &DecodingKey::from_der(pubkey), Algorithm::ES256).unwrap(); + let encrypted = + sign("hello world", &EncodingKey::from_ec_der(privkey), Algorithm::ES256).unwrap(); + let is_valid = + verify(&encrypted, "hello world", &DecodingKey::from_ec_der(pubkey), Algorithm::ES256) + .unwrap(); assert!(is_valid); } diff --git a/tests/hmac.rs b/tests/hmac.rs index 94ee664..dcd9c59 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -102,6 +102,18 @@ fn decode_token_wrong_algorithm() { claims.unwrap(); } +#[test] +#[should_panic(expected = "InvalidAlgorithm")] +fn encode_wrong_alg_family() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let claims = encode(&Header::default(), &my_claims, &EncodingKey::from_rsa_der(b"secret")); + claims.unwrap(); +} + #[test] fn decode_token_with_bytes_secret() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 08c3f8a..60a2b71 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -57,11 +57,9 @@ fn round_trip_sign_verification_der() { let pubkey_der = include_bytes!("public_rsa_key.der"); for &alg in RSA_ALGORITHMS { - let encrypted = - sign("hello world", &EncodingKey::from_der(privkey_der), alg).unwrap(); + let encrypted = sign("hello world", &EncodingKey::from_rsa_der(privkey_der), alg).unwrap(); let is_valid = - verify(&encrypted, "hello world", &DecodingKey::from_der(pubkey_der), alg) - .unwrap(); + verify(&encrypted, "hello world", &DecodingKey::from_rsa_der(pubkey_der), alg).unwrap(); assert!(is_valid); } }