Skip to main content

jwk_simple/
error.rs

1//! Error types for JWK/JWKS operations.
2//!
3//! This module provides the crate's core error taxonomy.
4//!
5//! Most fallible operations return `Result<T, Error>`. Feature-gated
6//! integration adapters may expose dedicated conversion error types when that
7//! yields clearer API boundaries.
8//!
9//! Key validation errors are split into two categories:
10//!
11//! - [`InvalidKeyError`] - the JWK is malformed (invalid encoding, missing
12//!   parameters, inconsistent fields). These mean "reject this key entirely."
13//!
14//! - [`IncompatibleKeyError`] - the JWK is well-formed but incompatible with
15//!   the requested use (wrong key type for algorithm, insufficient strength,
16//!   operation not permitted by metadata). These mean "valid key, wrong context."
17
18use std::fmt::{self, Display};
19
20use crate::jwk::{Algorithm, KeyOperation, KeyType};
21
22/// The main error type for core JWK/JWKS operations.
23#[derive(Debug)]
24#[non_exhaustive]
25pub enum Error {
26    /// Failed to parse JSON syntax or structure.
27    Json(serde_json::Error),
28
29    /// Failed to interpret valid JSON as JWK/JWKS data.
30    Parse(ParseError),
31
32    /// Invalid URL input.
33    InvalidUrl(url::ParseError),
34
35    /// URL violates a crate-level policy requirement.
36    InvalidUrlScheme(&'static str),
37
38    /// The JWK is malformed: missing parameters, invalid encoding, or
39    /// inconsistent fields.
40    InvalidKey(InvalidKeyError),
41
42    /// The JWK is well-formed but incompatible with the requested use.
43    IncompatibleKey(IncompatibleKeyError),
44
45    /// Base64 decoding failed.
46    Base64(base64ct::Error),
47
48    /// Caller provided invalid input to a public API.
49    InvalidInput(&'static str),
50
51    /// HTTP request error.
52    #[cfg(feature = "http")]
53    Http(reqwest::Error),
54
55    /// Remote fetch or transport failed outside the reqwest backend.
56    Fetch(String),
57
58    /// Cache operation failed.
59    Cache(String),
60
61    /// Other error (for platform-specific or miscellaneous errors).
62    Other(String),
63
64    /// Key type or curve not supported by WebCrypto.
65    #[cfg(feature = "web-crypto")]
66    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
67    UnsupportedForWebCrypto {
68        /// Why it's unsupported.
69        reason: &'static str,
70    },
71
72    /// WebCrypto operation failed.
73    #[cfg(feature = "web-crypto")]
74    #[cfg_attr(docsrs, doc(cfg(feature = "web-crypto")))]
75    WebCrypto(String),
76}
77
78impl Display for Error {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Error::Json(e) => write!(f, "JSON error: {}", e),
82            Error::Parse(e) => write!(f, "parse error: {}", e),
83            Error::InvalidUrl(err) => write!(f, "invalid URL: {}", err),
84            Error::InvalidUrlScheme(msg) => write!(f, "invalid URL scheme: {}", msg),
85            Error::InvalidKey(e) => write!(f, "invalid key: {}", e),
86            Error::IncompatibleKey(e) => write!(f, "incompatible key: {}", e),
87            Error::Base64(e) => write!(f, "base64 decoding error: {:?}", e),
88            Error::InvalidInput(msg) => write!(f, "invalid input: {}", msg),
89            #[cfg(feature = "http")]
90            Error::Http(e) => write!(f, "HTTP error: {}", e),
91            Error::Fetch(msg) => write!(f, "fetch error: {}", msg),
92            Error::Cache(msg) => write!(f, "cache error: {}", msg),
93            Error::Other(msg) => write!(f, "{}", msg),
94            #[cfg(feature = "web-crypto")]
95            Error::UnsupportedForWebCrypto { reason } => {
96                write!(f, "unsupported for WebCrypto: {}", reason)
97            }
98            #[cfg(feature = "web-crypto")]
99            Error::WebCrypto(msg) => write!(f, "WebCrypto error: {}", msg),
100        }
101    }
102}
103
104impl std::error::Error for Error {
105    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
106        match self {
107            Error::Json(e) => Some(e),
108            Error::Parse(e) => Some(e),
109            Error::InvalidUrl(e) => Some(e),
110            Error::InvalidKey(e) => Some(e),
111            Error::IncompatibleKey(e) => Some(e),
112            Error::Base64(e) => Some(e),
113            #[cfg(feature = "http")]
114            Error::Http(e) => Some(e),
115            _ => None,
116        }
117    }
118}
119
120impl From<InvalidKeyError> for Error {
121    fn from(e: InvalidKeyError) -> Self {
122        Error::InvalidKey(e)
123    }
124}
125
126impl From<IncompatibleKeyError> for Error {
127    fn from(e: IncompatibleKeyError) -> Self {
128        Error::IncompatibleKey(e)
129    }
130}
131
132impl From<base64ct::Error> for Error {
133    fn from(e: base64ct::Error) -> Self {
134        Error::Base64(e)
135    }
136}
137
138impl From<serde_json::Error> for Error {
139    fn from(e: serde_json::Error) -> Self {
140        Error::Json(e)
141    }
142}
143
144impl From<url::ParseError> for Error {
145    fn from(e: url::ParseError) -> Self {
146        Error::InvalidUrl(e)
147    }
148}
149
150#[cfg(feature = "http")]
151impl From<reqwest::Error> for Error {
152    fn from(e: reqwest::Error) -> Self {
153        Error::Http(e)
154    }
155}
156
157/// Errors that occur during JSON parsing.
158#[derive(Debug, Clone, PartialEq, Eq)]
159#[non_exhaustive]
160pub enum ParseError {
161    /// Unknown key type.
162    UnknownKeyType(String),
163    /// Unknown curve.
164    UnknownCurve(String),
165}
166
167impl Display for ParseError {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        match self {
170            ParseError::UnknownKeyType(kty) => write!(f, "unknown key type: {}", kty),
171            ParseError::UnknownCurve(crv) => write!(f, "unknown curve: {}", crv),
172        }
173    }
174}
175
176impl std::error::Error for ParseError {}
177
178/// The JWK is malformed.
179///
180/// These errors indicate the key material or metadata is invalid regardless
181/// of how the key is used. A key producing an `InvalidKeyError` should be
182/// rejected entirely.
183#[derive(Debug, Clone, PartialEq, Eq)]
184#[non_exhaustive]
185pub enum InvalidKeyError {
186    /// Invalid key size for the key type or curve.
187    InvalidKeySize {
188        /// Expected size in bytes.
189        expected: usize,
190        /// Actual size in bytes.
191        actual: usize,
192        /// What was being validated.
193        context: &'static str,
194    },
195    /// Missing required parameter for key type.
196    MissingParameter(&'static str),
197    /// Inconsistent key parameters (e.g., public and private parts don't match).
198    InconsistentParameters(String),
199    /// Invalid parameter value.
200    InvalidParameter {
201        /// Parameter name.
202        name: &'static str,
203        /// Why it's invalid.
204        reason: String,
205    },
206    /// Invalid `oth` entry in multi-prime RSA parameters.
207    InvalidOtherPrime {
208        /// Zero-based index of the invalid `oth` entry.
209        index: usize,
210        /// Validation error for this entry.
211        source: Box<InvalidKeyError>,
212    },
213}
214
215impl Display for InvalidKeyError {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        match self {
218            InvalidKeyError::InvalidKeySize {
219                expected,
220                actual,
221                context,
222            } => {
223                write!(
224                    f,
225                    "invalid key size for {}: expected {} bytes, got {}",
226                    context, expected, actual
227                )
228            }
229            InvalidKeyError::MissingParameter(param) => {
230                write!(f, "missing required parameter: {}", param)
231            }
232            InvalidKeyError::InconsistentParameters(msg) => {
233                write!(f, "inconsistent key parameters: {}", msg)
234            }
235            InvalidKeyError::InvalidParameter { name, reason } => {
236                write!(f, "invalid parameter '{}': {}", name, reason)
237            }
238            InvalidKeyError::InvalidOtherPrime { index, source } => {
239                write!(f, "invalid oth[{}]: {}", index, source)
240            }
241        }
242    }
243}
244
245impl std::error::Error for InvalidKeyError {
246    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
247        match self {
248            InvalidKeyError::InvalidOtherPrime { source, .. } => Some(source.as_ref()),
249            _ => None,
250        }
251    }
252}
253
254/// The JWK is well-formed but incompatible with the requested use.
255///
256/// These errors indicate the key is structurally valid but cannot be used
257/// in the requested context. A key producing an `IncompatibleKeyError` may
258/// be perfectly valid for a different algorithm or operation.
259#[derive(Debug, Clone, PartialEq, Eq)]
260#[non_exhaustive]
261pub enum IncompatibleKeyError {
262    /// Requested algorithm conflicts with the key's declared `alg` value.
263    AlgorithmMismatch {
264        /// The algorithm that was requested by the caller.
265        requested: Algorithm,
266        /// The algorithm declared on the key.
267        declared: Algorithm,
268    },
269    /// Algorithm is not compatible with the key type or curve.
270    IncompatibleAlgorithm {
271        /// The algorithm that was requested.
272        algorithm: Algorithm,
273        /// The key type that is incompatible.
274        key_type: KeyType,
275    },
276    /// Key is too weak for the requested algorithm.
277    InsufficientKeyStrength {
278        /// Minimum required size in bits.
279        minimum_bits: usize,
280        /// Actual key size in bits.
281        actual_bits: usize,
282        /// What was being validated.
283        context: &'static str,
284    },
285    /// Key size does not match the exact size required by the algorithm.
286    KeySizeMismatch {
287        /// Required size in bits.
288        required_bits: usize,
289        /// Actual key size in bits.
290        actual_bits: usize,
291        /// What was being validated.
292        context: &'static str,
293    },
294    /// Requested operation(s) are not permitted by key metadata or key material
295    /// capability (for example, a public-only key used for signing).
296    OperationNotPermitted {
297        /// The disallowed operations.
298        operations: Vec<KeyOperation>,
299        /// Why the operations are not permitted.
300        reason: String,
301    },
302}
303
304impl Display for IncompatibleKeyError {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        match self {
307            IncompatibleKeyError::AlgorithmMismatch {
308                requested,
309                declared,
310            } => {
311                let requested_display = match requested {
312                    Algorithm::Unknown(value) => {
313                        format!("unknown({})", sanitize_for_display(value))
314                    }
315                    _ => requested.to_string(),
316                };
317                let declared_display = match declared {
318                    Algorithm::Unknown(value) => {
319                        format!("unknown({})", sanitize_for_display(value))
320                    }
321                    _ => declared.to_string(),
322                };
323                write!(
324                    f,
325                    "requested algorithm '{}' does not match key's declared alg '{}'",
326                    requested_display, declared_display
327                )
328            }
329            IncompatibleKeyError::IncompatibleAlgorithm {
330                algorithm,
331                key_type,
332            } => {
333                let algorithm_display = match algorithm {
334                    Algorithm::Unknown(value) => {
335                        format!("unknown({})", sanitize_for_display(value))
336                    }
337                    _ => algorithm.to_string(),
338                };
339                write!(
340                    f,
341                    "algorithm '{}' is not compatible with key type '{}'",
342                    algorithm_display, key_type
343                )
344            }
345            IncompatibleKeyError::InsufficientKeyStrength {
346                minimum_bits,
347                actual_bits,
348                context,
349            } => {
350                write!(
351                    f,
352                    "insufficient key strength for {}: need {} bits, got {}",
353                    context, minimum_bits, actual_bits
354                )
355            }
356            IncompatibleKeyError::KeySizeMismatch {
357                required_bits,
358                actual_bits,
359                context,
360            } => {
361                write!(
362                    f,
363                    "key size mismatch for {}: expected {} bits, got {}",
364                    context, required_bits, actual_bits
365                )
366            }
367            IncompatibleKeyError::OperationNotPermitted { operations, reason } => {
368                let ops: Vec<String> = operations
369                    .iter()
370                    .map(|op| match op {
371                        KeyOperation::Unknown(value) => {
372                            format!("unknown({})", sanitize_for_display(value))
373                        }
374                        _ => op.to_string(),
375                    })
376                    .collect();
377                write!(
378                    f,
379                    "operation(s) not permitted ({}): {}",
380                    ops.join(", "),
381                    reason
382                )
383            }
384        }
385    }
386}
387
388impl std::error::Error for IncompatibleKeyError {}
389
390/// Errors that occur when converting a JWK into a `jwt-simple` key type.
391#[cfg(feature = "jwt-simple")]
392#[cfg_attr(docsrs, doc(cfg(feature = "jwt-simple")))]
393#[derive(Debug)]
394#[non_exhaustive]
395pub enum JwtSimpleKeyConversionError {
396    /// The JWK itself is malformed.
397    InvalidKey(InvalidKeyError),
398    /// The JWK is valid but unsuitable for the requested conversion.
399    IncompatibleKey(IncompatibleKeyError),
400    /// The conversion expects a different JWK key type.
401    KeyTypeMismatch {
402        /// Expected JWK `kty`.
403        expected: &'static str,
404        /// Actual JWK `kty`.
405        actual: String,
406    },
407    /// The conversion expects a different curve.
408    CurveMismatch {
409        /// Expected curve.
410        expected: &'static str,
411        /// Actual curve.
412        actual: String,
413    },
414    /// The conversion requires a specific JWK member that is absent.
415    MissingComponent {
416        /// JWK member name.
417        field: &'static str,
418    },
419    /// The conversion requires private key material, but the JWK is public-only.
420    MissingPrivateKey,
421    /// Core JWK/JWKS validation failed before conversion.
422    Core(Error),
423    /// Encoding the source key into an intermediate representation failed.
424    Encoding(String),
425    /// Importing the encoded key into `jwt-simple` failed.
426    Import(String),
427}
428
429#[cfg(feature = "jwt-simple")]
430impl Display for JwtSimpleKeyConversionError {
431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432        match self {
433            JwtSimpleKeyConversionError::InvalidKey(e) => write!(f, "invalid key: {}", e),
434            JwtSimpleKeyConversionError::IncompatibleKey(e) => {
435                write!(f, "incompatible key: {}", e)
436            }
437            JwtSimpleKeyConversionError::KeyTypeMismatch { expected, actual } => {
438                write!(
439                    f,
440                    "key type mismatch: expected {}, got {}",
441                    expected, actual
442                )
443            }
444            JwtSimpleKeyConversionError::CurveMismatch { expected, actual } => {
445                write!(f, "curve mismatch: expected {}, got {}", expected, actual)
446            }
447            JwtSimpleKeyConversionError::MissingComponent { field } => {
448                write!(f, "missing required field: {}", field)
449            }
450            JwtSimpleKeyConversionError::MissingPrivateKey => {
451                write!(f, "private key parameters required but not present")
452            }
453            JwtSimpleKeyConversionError::Core(err) => {
454                write!(f, "core error: {}", err)
455            }
456            JwtSimpleKeyConversionError::Encoding(msg) => write!(f, "encoding error: {}", msg),
457            JwtSimpleKeyConversionError::Import(msg) => write!(f, "import error: {}", msg),
458        }
459    }
460}
461
462#[cfg(feature = "jwt-simple")]
463impl std::error::Error for JwtSimpleKeyConversionError {
464    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
465        match self {
466            JwtSimpleKeyConversionError::InvalidKey(e) => Some(e),
467            JwtSimpleKeyConversionError::IncompatibleKey(e) => Some(e),
468            JwtSimpleKeyConversionError::Core(err) => Some(err),
469            _ => None,
470        }
471    }
472}
473
474#[cfg(feature = "jwt-simple")]
475impl From<InvalidKeyError> for JwtSimpleKeyConversionError {
476    fn from(e: InvalidKeyError) -> Self {
477        JwtSimpleKeyConversionError::InvalidKey(e)
478    }
479}
480
481#[cfg(feature = "jwt-simple")]
482impl From<IncompatibleKeyError> for JwtSimpleKeyConversionError {
483    fn from(e: IncompatibleKeyError) -> Self {
484        JwtSimpleKeyConversionError::IncompatibleKey(e)
485    }
486}
487
488/// Maximum number of characters to include from user-supplied identifiers
489/// (e.g. unknown algorithm or operation names) in display/error messages.
490const MAX_DISPLAY_IDENTIFIER_CHARS: usize = 256;
491
492/// Sanitizes a user-supplied identifier for use in display/error messages.
493///
494/// Truncates to [`MAX_DISPLAY_IDENTIFIER_CHARS`] and replaces control
495/// characters with spaces to prevent log injection or terminal escape attacks.
496pub(crate) fn sanitize_for_display(value: &str) -> String {
497    value
498        .chars()
499        .take(MAX_DISPLAY_IDENTIFIER_CHARS)
500        .map(|ch| if ch.is_control() { ' ' } else { ch })
501        .collect()
502}
503
504/// A type alias for `Result<T, Error>`.
505pub type Result<T> = std::result::Result<T, Error>;