Skip to main content

dpp_crypto/sd_jwt/
error.rs

1//! How reading an SD-JWT fails.
2//!
3//! Both enums live here rather than beside the types they belong to because the
4//! two are a single contract: a caller handed a credential from outside gets one
5//! or the other, and should be able to see every way that can go without reading
6//! the mechanism.
7
8/// Anything that can go wrong reading a disclosure someone else produced.
9#[derive(Debug, PartialEq, Eq, thiserror::Error)]
10#[non_exhaustive]
11pub enum DisclosureError {
12    /// The string is not base64url.
13    #[error("disclosure is not base64url")]
14    NotBase64,
15    /// The decoded bytes are not JSON.
16    #[error("disclosure does not decode to JSON")]
17    NotJson,
18    /// The JSON is not the three-element array clause 4.2.1 requires.
19    ///
20    /// The two-element form (array-element disclosures, clause 4.2.2) is
21    /// deliberately not accepted — see the module documentation.
22    #[error("disclosure is not a [salt, name, value] triple")]
23    NotATriple,
24    /// The claim name is one clause 4.2.1 forbids: `_sd` or `...`.
25    #[error("disclosure names a reserved claim")]
26    ReservedClaimName,
27}
28
29/// Anything that can go wrong reading an SD-JWT.
30#[derive(Debug, PartialEq, Eq, thiserror::Error)]
31#[non_exhaustive]
32pub enum SdJwtError {
33    /// Fewer than two `~`-separated segments, so not an SD-JWT at all.
34    #[error("not an SD-JWT: fewer than two segments")]
35    NotAnSdJwt,
36    /// A disclosure segment could not be read.
37    #[error(transparent)]
38    Disclosure(#[from] DisclosureError),
39    /// The issuer-signed JWT is not three dot-separated parts.
40    #[error("issuer-signed JWT is malformed")]
41    MalformedJwt,
42    /// The JWT payload is not a JSON object.
43    #[error("JWT payload is not a JSON object")]
44    PayloadNotAnObject,
45    /// `_sd_alg` names a hash function this does not implement.
46    ///
47    /// Carried rather than defaulted: clause 4.1.1 permits the claim to be
48    /// absent and to mean `sha-256`, but a *present* value naming something else
49    /// must not be read as if it said `sha-256`.
50    #[error("unsupported _sd_alg: {0}")]
51    UnsupportedHashAlg(String),
52    /// A disclosure was supplied whose digest appears nowhere in the token.
53    ///
54    /// Clause 7.1 step 4: every disclosure must be used. The count is reported
55    /// rather than the disclosure itself, which carries a claim value.
56    #[error("{0} disclosure(s) matched nothing in the token")]
57    UnusedDisclosures(usize),
58    /// The same digest appeared more than once.
59    ///
60    /// RFC 9901 clause 4.1: *"The same digest value MUST NOT appear more than
61    /// once in the SD-JWT."* Repetition is how a token smuggles a second
62    /// meaning past a reader that de-duplicates, so it is refused rather than
63    /// collapsed.
64    #[error("digest {0} appears more than once")]
65    DuplicateDigest(String),
66    /// A disclosure would overwrite a claim already present in cleartext.
67    ///
68    /// Clause 7.1 makes this a rejection condition: the token would otherwise
69    /// have two values for one claim and the reader would pick one.
70    #[error("disclosure for '{0}' collides with a cleartext claim")]
71    ClaimCollision(String),
72}