diff --git a/README.md b/README.md index d67bf77..f83fe19 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Look at custom headers section to see how to change that. ### Decoding ```rust -let token = decode::(&token, "secret", Algorithm::HS256, &Validation::default()).unwrap(); +let token = decode::(&token, "secret", &Validation::default()).unwrap(); // token is a struct with 2 params: header and claims ``` `decode` can error for a variety of reasons: @@ -47,10 +47,11 @@ let token = decode::(&token, "secret", Algorithm::HS256, &Validation::de ### Validation This library validates automatically the `iat`, `exp` and `nbf` claims if found. You can also validate the `sub`, `iss` and `aud` but those require setting the expected value. -You can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` parameter as shown in the example below. +You can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` parameter as shown in the example below as well +as select allowed algorithms. ```rust -use jsonwebtoken::Validation; +use jsonwebtoken::{Validation, Algorithm}; // Default valuation let validation = Validation::default(); @@ -62,6 +63,8 @@ let mut validation = Validation {iss: Some("issuer".to_string()), ..Default::def let mut validation = Validation::default(); validation.set_audience(&"Me"); // string validation.set_audience(&["Me", "You"]); // array of strings +// Will error if the token given has an algorithm that isn't HS256 +let mut validation = Validation {algorithms: Some(vec![Algorithm::HS256]), ..Default::default()}; ``` It's also possible to disable verifying the signature of a token by setting the `validate_signature` to `false`. This should diff --git a/benches/jwt.rs b/benches/jwt.rs index f98009d..2def445 100644 --- a/benches/jwt.rs +++ b/benches/jwt.rs @@ -25,5 +25,5 @@ fn bench_encode(b: &mut test::Bencher) { #[bench] fn bench_decode(b: &mut test::Bencher) { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; - b.iter(|| decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default())); + b.iter(|| decode::(token, "secret".as_ref(), &Validation::default())); } diff --git a/examples/custom_header.rs b/examples/custom_header.rs index 2cdfc22..d552c50 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -29,7 +29,7 @@ fn main() { }; println!("{:?}", token); - let token_data = match decode::(&token, key.as_ref(), Algorithm::HS512, &Validation::default()) { + let token_data = match decode::(&token, key.as_ref(), &Validation::default()) { 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 c170df3..8821dde 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -2,7 +2,7 @@ extern crate jsonwebtoken as jwt; #[macro_use] extern crate serde_derive; -use jwt::{encode, decode, Header, Algorithm, Validation}; +use jwt::{encode, decode, Header, Validation}; use jwt::errors::{ErrorKind}; @@ -23,10 +23,9 @@ fn main() { Err(_) => panic!() // in practice you would return the error }; - println!("{:?}", token); let validation = Validation {sub: Some("b@b.com".to_string()), ..Validation::default()}; - let token_data = match decode::(&token, key.as_ref(), Algorithm::HS256, &validation) { + let token_data = match decode::(&token, key.as_ref(), &validation) { Ok(c) => c, Err(err) => match *err.kind() { ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error diff --git a/src/errors.rs b/src/errors.rs index 729ab51..86fa067 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -4,7 +4,7 @@ use ring; error_chain! { errors { - /// When a token doesn't have a valid token shape + /// When a token doesn't have a valid JWT shape InvalidToken { description("invalid token") display("Invalid token") @@ -14,11 +14,6 @@ error_chain! { description("invalid signature") display("Invalid signature") } - /// When the algorithm in the header doesn't match the one passed to `decode` - WrongAlgorithmHeader { - description("wrong algorithm header") - display("Wrong Algorithm Header") - } /// When the secret given is not a valid RSA key InvalidKey { description("invalid key") @@ -57,6 +52,11 @@ error_chain! { description("immature signature") display("Immature Signature") } + /// When the algorithm in the header doesn't match the one passed to `decode` + InvalidAlgorithm { + description("Invalid algorithm") + display("Invalid Algorithm") + } } foreign_links { diff --git a/src/lib.rs b/src/lib.rs index e323eab..3b6ee7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ macro_rules! expect_two { /// ```rust,ignore /// #[macro_use] /// extern crate serde_derive; -/// use jsonwebtoken::{decode, Algorithm, Validation}; +/// use jsonwebtoken::{decode, Validation}; /// /// #[derive(Debug, Serialize, Deserialize)] /// struct Claims { @@ -99,31 +99,26 @@ macro_rules! expect_two { /// /// let token = "a.jwt.token".to_string(); /// // Claims is a struct that implements Deserialize -/// let token_data = decode::(&token, "secret", Algorithm::HS256, &Validation::default()); +/// let token_data = decode::(&token, "secret", &Validation::default()); /// ``` -pub fn decode(token: &str, key: &[u8], algorithm: Algorithm, validation: &Validation) -> Result> { +pub fn decode(token: &str, key: &[u8], validation: &Validation) -> Result> { let (signature, signing_input) = expect_two!(token.rsplitn(2, '.')); + let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); + let header: Header = from_jwt_part(header)?; - if validation.validate_signature && !verify(signature, signing_input, key, algorithm)? { + if validation.validate_signature && !verify(signature, signing_input, key, header.alg)? { return Err(ErrorKind::InvalidSignature.into()); } - let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); - - let header: Header = from_jwt_part(header)?; - if header.alg != algorithm { - return Err(ErrorKind::WrongAlgorithmHeader.into()); + if let Some(ref allowed_algs) = validation.algorithms { + if !allowed_algs.contains(&header.alg) { + return Err(ErrorKind::InvalidAlgorithm.into()); + } } + let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; validate(&claims_map, validation)?; Ok(TokenData { header: header, claims: decoded_claims }) } - -// To consider: -//pub mod prelude { -// pub use crypto::{Algorithm, encode, decode}; -// pub use validation::Validation; -// pub use header::Header; -//} diff --git a/src/validation.rs b/src/validation.rs index 4a2cf4e..0594151 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -4,6 +4,7 @@ use serde_json::{Value, from_value, to_value}; use serde_json::map::Map; use errors::{Result, ErrorKind}; +use crypto::Algorithm; /// Contains the various validations that are applied after decoding a token. @@ -57,18 +58,23 @@ pub struct Validation { /// Since `aud` can be either a String or a Vec in the JWT spec, you will need to use /// the [set_audience](struct.Validation.html#method.set_audience) method to set it. /// - /// Default to `None`. + /// Defaults to `None`. pub aud: Option, /// If it contains a value, the validation will check that the `iss` field is the same as the /// one provided and will error otherwise. /// - /// Default to None + /// Defaults to `None`. pub iss: Option, /// If it contains a value, the validation will check that the `sub` field is the same as the /// one provided and will error otherwise. /// - /// Default to `None`. + /// Defaults to `None`. pub sub: Option, + /// If it contains a value, the validation will check that the `alg` of the header is container + /// in the ones provided and will error otherwise. + /// + /// Defaults to `None`. + pub algorithms: Option>, } impl Validation { @@ -93,6 +99,8 @@ impl Default for Validation { iss: None, sub: None, aud: None, + + algorithms: None, } } } diff --git a/tests/lib.rs b/tests/lib.rs index 88f2815..bab1886 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -34,7 +34,7 @@ fn encode_with_custom_header() { let mut header = Header::default(); header.kid = Some("kid".to_string()); let token = encode(&header, &my_claims, "secret".as_ref()).unwrap(); - let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256, &Validation::default()).unwrap(); + let token_data = decode::(&token, "secret".as_ref(), &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert_eq!("kid", token_data.header.kid.unwrap()); } @@ -46,7 +46,7 @@ fn round_trip_claim() { company: "ACME".to_string() }; let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); - let token_data = decode::(&token, "secret".as_ref(), Algorithm::HS256, &Validation::default()).unwrap(); + let token_data = decode::(&token, "secret".as_ref(), &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } @@ -54,7 +54,7 @@ fn round_trip_claim() { #[test] fn decode_token() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default()); + let claims = decode::(token, "secret".as_ref(), &Validation::default()); claims.unwrap(); } @@ -62,7 +62,7 @@ fn decode_token() { #[should_panic(expected = "InvalidToken")] fn decode_token_missing_parts() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default()); + let claims = decode::(token, "secret".as_ref(), &Validation::default()); claims.unwrap(); } @@ -70,36 +70,36 @@ fn decode_token_missing_parts() { #[should_panic(expected = "InvalidSignature")] fn decode_token_invalid_signature() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default()); + let claims = decode::(token, "secret".as_ref(), &Validation::default()); claims.unwrap(); } #[test] -#[should_panic(expected = "WrongAlgorithmHeader")] +#[should_panic(expected = "InvalidAlgorithm")] fn decode_token_wrong_algorithm() { - let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default()); + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let claims = decode::(token, "secret".as_ref(), &Validation {algorithms: Some(vec![Algorithm::RS512]), ..Validation::default()}); claims.unwrap(); } #[test] fn decode_token_with_bytes_secret() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs"; - let claims = decode::(token, b"\x01\x02\x03", Algorithm::HS256, &Validation::default()); + let claims = decode::(token, b"\x01\x02\x03", &Validation::default()); assert!(claims.is_ok()); } #[test] fn decode_token_with_shuffled_header_fields() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation::default()); + let claims = decode::(token, "secret".as_ref(), &Validation::default()); assert!(claims.is_ok()); } #[test] fn decode_without_validating_signature() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; - let claims = decode::(token, "secret".as_ref(), Algorithm::HS256, &Validation {validate_signature: false, ..Validation::default()}); + let claims = decode::(token, "secret".as_ref(), &Validation {validate_signature: false, ..Validation::default()}); assert!(claims.is_ok()); } diff --git a/tests/rsa.rs b/tests/rsa.rs index e1cb31c..da6b172 100644 --- a/tests/rsa.rs +++ b/tests/rsa.rs @@ -26,7 +26,7 @@ fn round_trip_claim() { company: "ACME".to_string() }; let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_bytes!("private_rsa_key.der")).unwrap(); - let token_data = decode::(&token, include_bytes!("public_rsa_key.der"), Algorithm::RS256, &Validation::default()).unwrap(); + let token_data = decode::(&token, include_bytes!("public_rsa_key.der"), &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); }