Skip to content

Restructure ErrorKind into categorised sub-enums #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/custom_header.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::errors::{ErrorKind, FundamentalError};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};

#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -39,7 +39,7 @@ fn main() {
) {
Ok(c) => c,
Err(err) => match *err.kind() {
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
ErrorKind::Fundamental(FundamentalError::InvalidToken) => panic!(), // Example on how to handle a specific error
_ => panic!(),
},
};
Expand Down
6 changes: 3 additions & 3 deletions examples/validation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::errors::{ErrorKind, FundamentalError, ValidationError};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -30,8 +30,8 @@ fn main() {
let token_data = match decode::<Claims>(&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
ErrorKind::InvalidIssuer => panic!("Issuer is invalid"), // Example on how to handle a specific error
ErrorKind::Fundamental(FundamentalError::InvalidToken) => panic!("Token is invalid"), // Example on how to handle a specific error
ErrorKind::Validation(ValidationError::InvalidIssuer) => panic!("Issuer is invalid"), // Example on how to handle a specific error
_ => panic!("Some other errors"),
},
};
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::errors::{Error, ErrorKind, Result};
use crate::errors::{Error, ErrorKind, FundamentalError, Result};
use serde::{Deserialize, Serialize};
use std::str::FromStr;

Expand Down Expand Up @@ -61,7 +61,7 @@ impl FromStr for Algorithm {
"PS512" => Ok(Algorithm::PS512),
"RS512" => Ok(Algorithm::RS512),
"EdDSA" => Ok(Algorithm::EdDSA),
_ => Err(ErrorKind::InvalidAlgorithmName.into()),
_ => Err(ErrorKind::from(FundamentalError::InvalidAlgorithmName).into()),
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/crypto/rsa.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ring::{rand, signature};

use crate::algorithms::Algorithm;
use crate::errors::{ErrorKind, Result};
use crate::errors::{ErrorKind, FundamentalError, Result};
use crate::serialization::{b64_decode, b64_encode};

/// Only used internally when validating RSA, to map from our enum to the Ring param structs.
Expand Down Expand Up @@ -39,11 +39,13 @@ pub(crate) fn sign(
message: &[u8],
) -> Result<String> {
let key_pair = signature::RsaKeyPair::from_der(key)
.map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
.map_err(|e| ErrorKind::Fundamental(FundamentalError::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)?;
key_pair
.sign(alg, &rng, message, &mut signature)
.map_err(|_| ErrorKind::Fundamental(FundamentalError::RsaFailedSigning))?;

Ok(b64_encode(signature))
}
Expand Down
12 changes: 6 additions & 6 deletions src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned;

use crate::algorithms::AlgorithmFamily;
use crate::crypto::verify;
use crate::errors::{new_error, ErrorKind, Result};
use crate::errors::{new_error, ErrorKind, FundamentalError, Result, ValidationError};
use crate::header::Header;
use crate::jwk::{AlgorithmParameters, Jwk};
#[cfg(feature = "use_pem")]
Expand Down Expand Up @@ -36,7 +36,7 @@ macro_rules! expect_two {
let mut i = $iter;
match (i.next(), i.next(), i.next()) {
(Some(first), Some(second), None) => (first, second),
_ => return Err(new_error(ErrorKind::InvalidToken)),
_ => return Err(new_error(ErrorKind::from(FundamentalError::InvalidToken))),
}
}};
}
Expand Down Expand Up @@ -210,13 +210,13 @@ fn verify_signature<'a>(
validation: &Validation,
) -> Result<(Header, &'a str)> {
if validation.validate_signature && validation.algorithms.is_empty() {
return Err(new_error(ErrorKind::MissingAlgorithm));
return Err(new_error(ErrorKind::from(ValidationError::MissingAlgorithm)));
}

if validation.validate_signature {
for alg in &validation.algorithms {
if key.family != alg.family() {
return Err(new_error(ErrorKind::InvalidAlgorithm));
return Err(new_error(ErrorKind::from(ValidationError::InvalidAlgorithm)));
}
}
}
Expand All @@ -226,11 +226,11 @@ fn verify_signature<'a>(
let header = Header::from_encoded(header)?;

if validation.validate_signature && !validation.algorithms.contains(&header.alg) {
return Err(new_error(ErrorKind::InvalidAlgorithm));
return Err(new_error(ErrorKind::from(ValidationError::InvalidAlgorithm)));
}

if validation.validate_signature && !verify(signature, message.as_bytes(), key, header.alg)? {
return Err(new_error(ErrorKind::InvalidSignature));
return Err(new_error(ErrorKind::from(ValidationError::InvalidAlgorithm)));
}

Ok((header, payload))
Expand Down
4 changes: 2 additions & 2 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::ser::Serialize;

use crate::algorithms::AlgorithmFamily;
use crate::crypto;
use crate::errors::{new_error, ErrorKind, Result};
use crate::errors::{new_error, ErrorKind, Result, ValidationError};
use crate::header::Header;
#[cfg(feature = "use_pem")]
use crate::pem::decoder::PemEncodedKey;
Expand Down Expand Up @@ -120,7 +120,7 @@ impl EncodingKey {
/// ```
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &EncodingKey) -> Result<String> {
if key.family != header.alg.family() {
return Err(new_error(ErrorKind::InvalidAlgorithm));
return Err(new_error(ErrorKind::from(ValidationError::InvalidAlgorithm)));
}
let encoded_header = b64_encode_part(header)?;
let encoded_claims = b64_encode_part(claims)?;
Expand Down
121 changes: 77 additions & 44 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ impl Error {
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum ErrorKind {
/// Errors related to malformed tokens or cryptographic key problems.
Fundamental(FundamentalError),
/// Errors that occur when a token fails claim-based validation.
Validation(ValidationError),
/// Errors originating from third-party libraries used for tasks like
/// base64 decoding, JSON serialization, or cryptographic operations.
ThirdParty(ThirdPartyError),
}

impl From<FundamentalError> for ErrorKind {
fn from(value: FundamentalError) -> Self {
Self::Fundamental(value)
}
}

#[non_exhaustive]
#[derive(Clone, Debug)]
/// Errors that indicate a fundamental issue with the JWT or cryptographic key configuration.
/// This enum may grow additional variants, the `#[non_exhaustive]`
/// attribute makes sure clients don't count on exhaustive matching.
/// (Otherwise, adding a new variant could break existing code.)
pub enum FundamentalError {
/// When a token doesn't have a valid JWT shape
InvalidToken,
/// When the signature doesn't match
Expand All @@ -49,7 +71,19 @@ pub enum ErrorKind {
InvalidAlgorithmName,
/// When a key is provided with an invalid format
InvalidKeyFormat,
}

impl From<ValidationError> for ErrorKind {
fn from(value: ValidationError) -> Self {
Self::Validation(value)
}
}

#[non_exhaustive]
#[derive(Clone, Debug)]
/// Errors which relate to the validation of a JWT's claims (such as expiration, audience, or issuer)
/// and whether they meet the defined criteria.
pub enum ValidationError {
// Validation errors
/// When a claim required by the validation is not present
MissingRequiredClaim(String),
Expand All @@ -68,7 +102,19 @@ pub enum ErrorKind {
InvalidAlgorithm,
/// When the Validation struct does not contain at least 1 algorithm
MissingAlgorithm,
}

impl From<ThirdPartyError> for ErrorKind {
fn from(value: ThirdPartyError) -> Self {
Self::ThirdParty(value)
}
}

#[non_exhaustive]
#[derive(Clone, Debug)]
/// Errors originating from external libraries/underlying systems
/// used during the JWT encoding or decoding process.
pub enum ThirdPartyError {
// 3rd party errors
/// An error happened when decoding some base64 text
Base64(base64::DecodeError),
Expand All @@ -83,51 +129,38 @@ pub enum ErrorKind {
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match &*self.0 {
ErrorKind::InvalidToken => None,
ErrorKind::InvalidSignature => None,
ErrorKind::InvalidEcdsaKey => None,
ErrorKind::RsaFailedSigning => None,
ErrorKind::InvalidRsaKey(_) => None,
ErrorKind::ExpiredSignature => None,
ErrorKind::MissingAlgorithm => None,
ErrorKind::MissingRequiredClaim(_) => None,
ErrorKind::InvalidIssuer => None,
ErrorKind::InvalidAudience => None,
ErrorKind::InvalidSubject => None,
ErrorKind::ImmatureSignature => None,
ErrorKind::InvalidAlgorithm => None,
ErrorKind::InvalidAlgorithmName => None,
ErrorKind::InvalidKeyFormat => None,
ErrorKind::Base64(err) => Some(err),
ErrorKind::Json(err) => Some(err.as_ref()),
ErrorKind::Utf8(err) => Some(err),
ErrorKind::Crypto(err) => Some(err),
ErrorKind::Fundamental(_) => None,
ErrorKind::Validation(_) => None,
ErrorKind::ThirdParty(ThirdPartyError::Base64(err)) => Some(err),
ErrorKind::ThirdParty(ThirdPartyError::Json(err)) => Some(err.as_ref()),
ErrorKind::ThirdParty(ThirdPartyError::Utf8(err)) => Some(err),
ErrorKind::ThirdParty(ThirdPartyError::Crypto(err)) => Some(err),
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &*self.0 {
ErrorKind::InvalidToken
| ErrorKind::InvalidSignature
| ErrorKind::InvalidEcdsaKey
| ErrorKind::ExpiredSignature
| ErrorKind::RsaFailedSigning
| ErrorKind::MissingAlgorithm
| ErrorKind::InvalidIssuer
| ErrorKind::InvalidAudience
| ErrorKind::InvalidSubject
| ErrorKind::ImmatureSignature
| ErrorKind::InvalidAlgorithm
| ErrorKind::InvalidKeyFormat
| 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),
ErrorKind::Fundamental(FundamentalError::InvalidRsaKey(msg)) => {
write!(f, "RSA key invalid: {}", msg)
}
ErrorKind::Validation(ValidationError::MissingRequiredClaim(claim)) => {
write!(f, "Missing required claim: {}", claim)
}
ErrorKind::ThirdParty(ThirdPartyError::Json(err)) => {
write!(f, "JSON error: {}", err)
}
ErrorKind::ThirdParty(ThirdPartyError::Utf8(err)) => {
write!(f, "UTF-8 error: {}", err)
}
ErrorKind::ThirdParty(ThirdPartyError::Crypto(err)) => {
write!(f, "Crypto error: {}", err)
}
ErrorKind::ThirdParty(ThirdPartyError::Base64(err)) => {
write!(f, "Base64 error: {}", err)
}
_ => write!(f, "{:?}", self.0),
}
}
}
Expand All @@ -143,31 +176,31 @@ impl Eq for ErrorKind {}

impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
new_error(ErrorKind::Base64(err))
new_error(ErrorKind::ThirdParty(ThirdPartyError::Base64(err)))
}
}

impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error {
new_error(ErrorKind::Json(Arc::new(err)))
new_error(ErrorKind::ThirdParty(ThirdPartyError::Json(Arc::new(err))))
}
}

impl From<::std::string::FromUtf8Error> for Error {
fn from(err: ::std::string::FromUtf8Error) -> Error {
new_error(ErrorKind::Utf8(err))
new_error(ErrorKind::ThirdParty(ThirdPartyError::Utf8(err)))
}
}

impl From<::ring::error::Unspecified> for Error {
fn from(err: ::ring::error::Unspecified) -> Error {
new_error(ErrorKind::Crypto(err))
new_error(ErrorKind::ThirdParty(ThirdPartyError::Crypto(err)))
}
}

impl From<::ring::error::KeyRejected> for Error {
fn from(_err: ::ring::error::KeyRejected) -> Error {
new_error(ErrorKind::InvalidEcdsaKey)
new_error(ErrorKind::Fundamental(FundamentalError::InvalidEcdsaKey))
}
}

Expand All @@ -188,7 +221,7 @@ mod tests {
fn test_error_rendering() {
assert_eq!(
"InvalidAlgorithmName",
Error::from(ErrorKind::InvalidAlgorithmName).to_string()
Error::from(ErrorKind::Fundamental(FundamentalError::InvalidAlgorithmName)).to_string()
);
}
}
4 changes: 2 additions & 2 deletions src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! tweaked to remove the private bits as it's not the goal for this crate currently.

use crate::{
errors::{self, Error, ErrorKind},
errors::{self, Error, ErrorKind, FundamentalError},
Algorithm,
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -209,7 +209,7 @@ impl FromStr for KeyAlgorithm {
"RSA1_5" => Ok(KeyAlgorithm::RSA1_5),
"RSA-OAEP" => Ok(KeyAlgorithm::RSA_OAEP),
"RSA-OAEP-256" => Ok(KeyAlgorithm::RSA_OAEP_256),
_ => Err(ErrorKind::InvalidAlgorithmName.into()),
_ => Err(ErrorKind::from(FundamentalError::InvalidAlgorithmName).into()),
}
}
}
Expand Down
Loading