identity_kit/sd_jwt_vc/error.rs
1//! Errors of SD-JWT parsing, processing and building.
2
3use super::ClaimPath;
4use crate::cause::cause;
5use crate::jose::JwsError;
6
7/// Why an SD-JWT could not be parsed, processed, selected from or built.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum SdJwtError {
11 /// The issuer-signed JWT or the key binding JWT is not a compact JWS this crate decodes.
12 #[error("invalid JWS: {0}")]
13 Jws(#[from] JwsError),
14 /// The compact serialization is malformed: no `~`, an empty disclosure.
15 #[error("invalid SD-JWT: {0}")]
16 Format(String),
17 /// A disclosure is not base64url of a JSON array of the right shape.
18 #[error("invalid disclosure {disclosure:?}: {reason}")]
19 InvalidDisclosure {
20 /// The encoded disclosure.
21 disclosure: String,
22 /// What is wrong with it.
23 reason: String,
24 },
25 /// The same disclosure is given twice.
26 #[error("disclosure {0:?} is given twice")]
27 DuplicateDisclosure(String),
28 /// The issuer-signed JWT payload is not a JSON object, or `_sd`, `...` or `_sd_alg` is
29 /// malformed. The source is the JSON error, when there is one.
30 #[error("invalid SD-JWT payload: {reason}{}", cause(.source.as_ref()))]
31 Payload {
32 /// What is wrong with it.
33 reason: String,
34 /// The JSON error.
35 source: Option<serde_json::Error>,
36 },
37 /// The `_sd_alg` names a hash algorithm this crate does not implement.
38 #[error("unsupported hash algorithm {0:?}")]
39 UnsupportedHashAlgorithm(String),
40 /// A digest appears more than once in the payload, directly or through disclosures.
41 #[error("digest {0:?} appears more than once")]
42 DuplicateDigest(String),
43 /// A disclosure matches no digest in the payload or in another disclosure.
44 #[error("disclosure {0:?} is referenced by no digest")]
45 UnusedDisclosure(String),
46 /// A disclosure of the wrong kind matches a digest: an array element disclosure in `_sd`,
47 /// or an object property disclosure in an array.
48 #[error("disclosure {disclosure:?} is not {expected}")]
49 WrongDisclosureKind {
50 /// The encoded disclosure.
51 disclosure: String,
52 /// `an object property disclosure` or `an array element disclosure`.
53 expected: &'static str,
54 },
55 /// A disclosure names the reserved claim `_sd` or `...`.
56 #[error("disclosure claim name {0:?} is reserved")]
57 ReservedClaimName(String),
58 /// A disclosure names a claim that already exists at the level of its `_sd`.
59 #[error("claim {name:?} at {path} is both disclosed and present")]
60 DuplicateClaim {
61 /// The claim name.
62 name: String,
63 /// The object it is duplicated in.
64 path: ClaimPath,
65 },
66 /// A disclosure was selected without the disclosure whose value references it.
67 #[error("disclosure {disclosure:?} needs the disclosure {parent:?} it is nested in")]
68 MissingParentDisclosure {
69 /// The encoded disclosure selected.
70 disclosure: String,
71 /// The encoded disclosure it needs.
72 parent: String,
73 },
74 /// A path to conceal or disclose selects no claim.
75 #[error("no claim at {0}")]
76 NoSuchClaim(ClaimPath),
77 /// A claim cannot be made selectively disclosable: it is `_sd` or `...`, or a profile
78 /// forbids it.
79 #[error("claim at {path} cannot be selectively disclosable: {reason}")]
80 NotConcealable {
81 /// The claim.
82 path: ClaimPath,
83 /// Why.
84 reason: String,
85 },
86 /// The claims nest deeper than [`SdJwtBuilder::MAX_DEPTH`](super::SdJwtBuilder::MAX_DEPTH)
87 /// containers: claims to issue, which no parser of this crate would read back, or claims
88 /// being processed, through the payload and the disclosures nested in it.
89 #[error("the claims nest deeper than {max} containers")]
90 TooDeep {
91 /// The deepest nesting accepted.
92 max: usize,
93 },
94 /// The key binding JWT is present where an SD-JWT without key binding was expected, or
95 /// missing.
96 #[error("{0}")]
97 KeyBinding(String),
98 /// The operating system's random number generator could not provide a salt: unavailable
99 /// on some WebAssembly targets, and in sandboxes or early boot. The source is its error.
100 #[error("cannot generate a salt: {0}")]
101 Randomness(#[source] Box<dyn std::error::Error + Send + Sync>),
102}
103
104impl SdJwtError {
105 /// A [`SdJwtError::Payload`] for `reason`, with no source.
106 pub(crate) fn payload(reason: impl Into<String>) -> Self {
107 Self::Payload {
108 reason: reason.into(),
109 source: None,
110 }
111 }
112}