Skip to main content

identity_kit/vc/validation/
error.rs

1//! Validation errors.
2
3use core::fmt;
4use std::collections::BTreeMap;
5use std::error::Error;
6
7use chrono::{DateTime, Utc};
8
9use crate::did::{DidError, DidUrl, OneOrMany, Uri};
10use crate::jose::{JwsError, VerificationError};
11use crate::vc::CredentialError;
12
13/// Why a credential or presentation JWT did not validate.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum ValidationError {
17    /// The token is not a compact JWS this crate decodes.
18    #[error("invalid JWS: {0}")]
19    Jws(#[from] JwsError),
20    /// The JWS header has no `kid`, so the signing key cannot be found.
21    #[error("the JWS header has no `kid`")]
22    MissingKid,
23    /// The `kid` is not a DID URL naming a verification method.
24    #[error("`kid` {kid:?} is not a DID URL: {source}")]
25    InvalidKid {
26        /// The `kid`.
27        kid: String,
28        /// Why it did not parse.
29        #[source]
30        source: DidError,
31    },
32    /// The `kid` carries a path or DID parameters. `versionId` and `versionTime` could select a
33    /// document version that still lists a retired key, so only `did:…#fragment` keys are
34    /// accepted.
35    #[error("`kid` `{0}` carries a path or DID parameters")]
36    KidWithParameters(DidUrl),
37    /// The header `typ` is not the one the profile requires.
38    #[error("header `typ` is {found:?}, not `{expected}`")]
39    UnexpectedTyp {
40        /// The `typ` the profile requires.
41        expected: &'static str,
42        /// The `typ` found.
43        found: String,
44    },
45    /// The payload is not a JSON claims set.
46    #[error("the payload is not a JWT claims set: {0}")]
47    Payload(String),
48    /// The claims do not map to a credential or presentation.
49    #[error(transparent)]
50    Credential(#[from] CredentialError),
51    /// The key `kid` names could not be resolved.
52    #[error("resolving key `{kid}`: {source}")]
53    KeyResolution {
54        /// The `kid`.
55        kid: DidUrl,
56        /// What went wrong.
57        #[source]
58        source: KeyResolutionError,
59    },
60    /// The signature does not verify under the resolved key.
61    #[error("signature: {0}")]
62    Signature(#[from] VerificationError),
63    /// The issuer of the credential does not control the key that signed it.
64    #[error("issuer `{issuer}` does not control signing key `{kid}`")]
65    IssuerMismatch {
66        /// The issuer of the credential.
67        issuer: Uri,
68        /// The key that signed the JWT.
69        kid: DidUrl,
70    },
71    /// The issuer is not a DID, so no key of it can be resolved.
72    #[error("{role} `{id}` is not a DID")]
73    NotDid {
74        /// `issuer` or `holder`.
75        role: &'static str,
76        /// The identifier.
77        id: String,
78    },
79    /// The holder of the presentation does not control the key that signed it.
80    #[error("holder `{holder}` does not control signing key `{kid}`")]
81    HolderMismatch {
82        /// The holder of the presentation.
83        holder: Uri,
84        /// The key that signed the JWT.
85        kid: DidUrl,
86    },
87    /// The token or credential is not valid yet.
88    #[error("not valid before {} (now is {})", not_before.to_rfc3339(), now.to_rfc3339())]
89    NotYetValid {
90        /// When it becomes valid.
91        not_before: DateTime<Utc>,
92        /// The validation time.
93        now: DateTime<Utc>,
94    },
95    /// The token or credential has expired.
96    #[error("expired at {} (now is {})", expired_at.to_rfc3339(), now.to_rfc3339())]
97    Expired {
98        /// When it expired.
99        expired_at: DateTime<Utc>,
100        /// The validation time.
101        now: DateTime<Utc>,
102    },
103    /// The token claims to have been issued in the future.
104    #[error("issued at {}, after now ({})", issued_at.to_rfc3339(), now.to_rfc3339())]
105    IssuedInFuture {
106        /// The `iat`.
107        issued_at: DateTime<Utc>,
108        /// The validation time.
109        now: DateTime<Utc>,
110    },
111    /// The `aud` does not include the expected audience.
112    #[error("audience {found:?} does not include {expected:?}")]
113    AudienceMismatch {
114        /// The audience expected.
115        expected: String,
116        /// The `aud` found.
117        found: Option<OneOrMany<String>>,
118    },
119    /// The `nonce` is not the expected one.
120    #[error("nonce {found:?} is not {expected:?}")]
121    NonceMismatch {
122        /// The nonce expected.
123        expected: String,
124        /// The `nonce` found.
125        found: Option<String>,
126    },
127    /// The credential has a `credentialStatus` and the validator has no
128    /// [`StatusCheck`](super::StatusCheck) for it.
129    #[error("`credentialStatus` of type `{0}` cannot be checked")]
130    StatusUnchecked(String),
131    /// The status check failed or found the credential revoked or suspended.
132    #[error("credential status: {0}")]
133    Status(#[from] StatusError),
134    /// The subject of a presented credential is not the holder presenting it.
135    #[error("credential subject {subject:?} is not the holder `{holder}`")]
136    SubjectHolderMismatch {
137        /// The `id` of the subject, if any.
138        subject: Option<Uri>,
139        /// The holder of the presentation.
140        holder: Uri,
141    },
142}
143
144/// Why a [`StatusCheck`] did not pass.
145///
146/// [`StatusCheck`]: super::StatusCheck
147#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
148#[non_exhaustive]
149pub enum StatusError {
150    /// The check does not understand the status type.
151    #[error("status type `{0}` is not supported")]
152    Unsupported(String),
153    /// The status could not be obtained: the list could not be fetched, say.
154    #[error("status unavailable: {0}")]
155    Unavailable(String),
156    /// The credential's status makes it unusable: `revoked`, `suspended`, …
157    #[error("credential is {0}")]
158    Invalid(String),
159}
160
161/// Why a key could not be resolved for a [`KeyResolver`](super::KeyResolver).
162#[derive(Debug)]
163pub struct KeyResolutionError(Box<dyn Error + Send + Sync + 'static>);
164
165impl KeyResolutionError {
166    /// Wraps `error`.
167    pub fn new<E: Error + Send + Sync + 'static>(error: E) -> Self {
168        Self(Box::new(error))
169    }
170
171    /// An error carrying only `message`.
172    pub fn msg<M: fmt::Display>(message: M) -> Self {
173        Self(message.to_string().into())
174    }
175
176    /// The wrapped error.
177    pub fn inner(&self) -> &(dyn Error + Send + Sync + 'static) {
178        &*self.0
179    }
180}
181
182impl fmt::Display for KeyResolutionError {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        fmt::Display::fmt(&self.0, f)
185    }
186}
187
188impl Error for KeyResolutionError {
189    fn source(&self) -> Option<&(dyn Error + 'static)> {
190        self.0.source()
191    }
192}
193
194impl From<crate::resolver::DereferenceError> for KeyResolutionError {
195    fn from(error: crate::resolver::DereferenceError) -> Self {
196        Self::new(error)
197    }
198}
199
200/// Why a presentation did not validate: the presentation itself, or some of its credentials.
201#[derive(Debug, thiserror::Error)]
202#[non_exhaustive]
203pub struct PresentationValidationError {
204    /// What is wrong with the presentation JWT; when set, no credential was validated.
205    pub presentation: Option<Box<ValidationError>>,
206    /// What is wrong with each failing credential, by its position in
207    /// `verifiableCredential`.
208    pub credentials: BTreeMap<usize, ValidationError>,
209}
210
211impl PresentationValidationError {
212    /// An error of the presentation itself.
213    pub fn presentation(error: ValidationError) -> Self {
214        Self {
215            presentation: Some(Box::new(error)),
216            credentials: BTreeMap::new(),
217        }
218    }
219
220    /// An error of the credentials at the given positions.
221    pub fn credentials(errors: BTreeMap<usize, ValidationError>) -> Self {
222        Self {
223            presentation: None,
224            credentials: errors,
225        }
226    }
227}
228
229impl fmt::Display for PresentationValidationError {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        if let Some(error) = &self.presentation {
232            return write!(f, "presentation: {error}");
233        }
234        for (i, (index, error)) in self.credentials.iter().enumerate() {
235            if i > 0 {
236                f.write_str("; ")?;
237            }
238            write!(f, "credential {index}: {error}")?;
239        }
240        Ok(())
241    }
242}