From 73d96357c359633699f5edd2cfed3b726fb414b4 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 16:13:22 +0000 Subject: [PATCH] Simplify header decoding --- src/errors.rs | 26 ++++++++++++++------------ src/header.rs | 14 ++++++++++++-- src/lib.rs | 12 ++++++------ src/serialization.rs | 16 ++++------------ 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 317a5cb..0c8b148 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -45,7 +45,7 @@ pub enum ErrorKind { /// When a key is provided with an invalid format InvalidKeyFormat, - // validation error + // Validation errors /// When a token’s `exp` claim indicates that it has expired ExpiredSignature, /// When a token’s `iss` claim does not match the expected issuer @@ -91,6 +91,7 @@ impl StdError for Error { ErrorKind::InvalidSubject => "invalid subject", ErrorKind::ImmatureSignature => "immature signature", ErrorKind::InvalidAlgorithm => "algorithms don't match", + ErrorKind::InvalidAlgorithmName => "not a known algorithm", ErrorKind::Base64(ref err) => err.description(), ErrorKind::Json(ref err) => err.description(), ErrorKind::Utf8(ref err) => err.description(), @@ -111,6 +112,7 @@ impl StdError for Error { ErrorKind::InvalidSubject => None, ErrorKind::ImmatureSignature => None, ErrorKind::InvalidAlgorithm => None, + ErrorKind::InvalidAlgorithmName => None, ErrorKind::Base64(ref err) => Some(err), ErrorKind::Json(ref err) => Some(err), ErrorKind::Utf8(ref err) => Some(err), @@ -123,17 +125,17 @@ impl StdError for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.0 { - ErrorKind::InvalidToken => write!(f, "invalid token"), - ErrorKind::InvalidSignature => write!(f, "invalid signature"), - ErrorKind::InvalidEcdsaKey => write!(f, "invalid ECDSA key"), - ErrorKind::InvalidRsaKey => write!(f, "invalid RSA key"), - ErrorKind::ExpiredSignature => write!(f, "expired signature"), - ErrorKind::InvalidIssuer => write!(f, "invalid issuer"), - ErrorKind::InvalidAudience => write!(f, "invalid audience"), - ErrorKind::InvalidSubject => write!(f, "invalid subject"), - ErrorKind::ImmatureSignature => write!(f, "immature signature"), - ErrorKind::InvalidAlgorithm => write!(f, "algorithms don't match"), - ErrorKind::Base64(ref err) => write!(f, "base64 error: {}", err), + ErrorKind::InvalidToken + | ErrorKind::InvalidSignature + | ErrorKind::InvalidEcdsaKey + | ErrorKind::InvalidRsaKey + | ErrorKind::ExpiredSignature + | ErrorKind::InvalidIssuer + | ErrorKind::InvalidAudience + | ErrorKind::InvalidSubject + | ErrorKind::ImmatureSignature + | ErrorKind::InvalidAlgorithm + | ErrorKind::InvalidAlgorithmName => write!(f, "{}", self.description()), ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err), ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err), ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err), diff --git a/src/header.rs b/src/header.rs index 66f38d8..73a2a1d 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,6 +1,8 @@ -use crate::algorithms::Algorithm; use serde::{Deserialize, Serialize}; +use crate::algorithms::Algorithm; +use crate::errors::Result; + /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -43,7 +45,7 @@ pub struct Header { impl Header { /// Returns a JWT header with the algorithm given - pub fn new(algorithm: Algorithm) -> Header { + pub fn new(algorithm: Algorithm) -> Self { Header { typ: Some("JWT".to_string()), alg: algorithm, @@ -54,6 +56,14 @@ impl Header { x5t: None, } } + + /// Converts an encoded part into the Header struct if possible + pub(crate) fn from_encoded(encoded_part: &str) -> Result { + let decoded = base64::decode_config(encoded_part, base64::URL_SAFE_NO_PAD)?; + let s = String::from_utf8(decoded)?; + + Ok(serde_json::from_str(&s)?) + } } impl Default for Header { diff --git a/src/lib.rs b/src/lib.rs index 6f9b1fa..0dc6cbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ use serde::de::DeserializeOwned; use serde::ser::Serialize; use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part}; +use crate::serialization::{encode_part, from_jwt_part_claims}; use crate::validation::validate; /// Encode the header and claims given and sign the payload using the algorithm from the header and the key @@ -51,8 +51,8 @@ use crate::validation::validate; /// let token = encode(&Header::default(), &my_claims, Key::Hmac("secret".as_ref())).unwrap(); /// ``` pub fn encode(header: &Header, claims: &T, key: Key) -> Result { - let encoded_header = to_jwt_part(&header)?; - let encoded_claims = to_jwt_part(&claims)?; + let encoded_header = encode_part(&header)?; + let encoded_claims = encode_part(&claims)?; let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); let signature = sign(&*signing_input, key, header.alg)?; @@ -97,7 +97,7 @@ pub fn decode( ) -> 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)?; + let header = Header::from_encoded(header)?; if !verify(signature, signing_input, key, header.alg)? { return Err(new_error(ErrorKind::InvalidSignature)); @@ -135,7 +135,7 @@ pub fn decode( pub fn dangerous_unsafe_decode(token: &str) -> Result> { let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); - let header: Header = from_jwt_part(header)?; + let header = Header::from_encoded(header)?; let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?; @@ -156,7 +156,7 @@ pub fn dangerous_unsafe_decode(token: &str) -> Result Result
{ let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); - from_jwt_part(header) + Header::from_encoded(header) } /// Decode a PEM string to obtain its key diff --git a/src/serialization.rs b/src/serialization.rs index 2a47688..d1aafeb 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -16,18 +16,10 @@ pub struct TokenData { pub claims: T, } -/// Serializes to JSON and encodes to base64 -pub fn to_jwt_part(input: &T) -> Result { - let encoded = to_string(input)?; - Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD)) -} - -/// Decodes from base64 and deserializes from JSON to a struct -pub fn from_jwt_part, T: DeserializeOwned>(encoded: B) -> Result { - let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; - let s = String::from_utf8(decoded)?; - - Ok(from_str(&s)?) +/// Serializes a struct to JSON and encodes it in base64 +pub fn encode_part(input: &T) -> Result { + let json = to_string(input)?; + Ok(base64::encode_config(json.as_bytes(), base64::URL_SAFE_NO_PAD)) } /// Decodes from base64 and deserializes from JSON to a struct AND a hashmap