identity_credential/validator/jwt_presentation_validation/
jwt_presentation_validator.rs1use identity_core::common::Object;
5use identity_core::common::Timestamp;
6use identity_core::common::Url;
7use identity_core::convert::FromJson;
8use identity_did::CoreDID;
9use identity_document::document::CoreDocument;
10use identity_verification::jws::DecodedJws;
11use identity_verification::jws::JwsVerifier;
12use std::str::FromStr;
13
14use crate::credential::Jwt;
15use crate::presentation::Presentation;
16use crate::presentation::PresentationJwtClaims;
17use crate::validator::jwt_credential_validation::JwtValidationError;
18use crate::validator::jwt_credential_validation::SignerContext;
19
20use super::CompoundJwtPresentationValidationError;
21use super::DecodedJwtPresentation;
22use super::JwtPresentationValidationOptions;
23
24#[derive(Debug, Clone)]
26#[non_exhaustive]
27pub struct JwtPresentationValidator<V: JwsVerifier>(V);
28
29impl<V> JwtPresentationValidator<V>
30where
31 V: JwsVerifier,
32{
33 pub fn with_signature_verifier(signature_verifier: V) -> Self {
35 Self(signature_verifier)
36 }
37
38 pub fn validate<HDOC, CRED, T>(
64 &self,
65 presentation: &Jwt,
66 holder: &HDOC,
67 options: &JwtPresentationValidationOptions,
68 ) -> Result<DecodedJwtPresentation<CRED, T>, CompoundJwtPresentationValidationError>
69 where
70 HDOC: AsRef<CoreDocument> + ?Sized,
71 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
72 CRED: ToOwned<Owned = CRED> + serde::Serialize + serde::de::DeserializeOwned + Clone,
73 {
74 let decoded_jws: DecodedJws<'_> = holder
76 .as_ref()
77 .verify_jws(
78 presentation.as_str(),
79 None,
80 &self.0,
81 &options.presentation_verifier_options,
82 )
83 .map_err(|err| {
84 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationJwsError(err))
85 })?;
86
87 let claims: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::from_json_slice(&decoded_jws.claims)
88 .map_err(|err| {
89 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
90 crate::Error::JwtClaimsSetDeserializationError(err.into()),
91 ))
92 })?;
93
94 let holder_did: CoreDID = CoreDID::from_str(claims.iss.as_str()).map_err(|err| {
96 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::SignerUrl {
97 signer_ctx: SignerContext::Holder,
98 source: err.into(),
99 })
100 })?;
101
102 if &holder_did != <CoreDocument>::id(holder.as_ref()) {
103 return Err(CompoundJwtPresentationValidationError::one_presentation_error(
104 JwtValidationError::DocumentMismatch(SignerContext::Holder),
105 ));
106 }
107
108 let expiration_date: Option<Timestamp> = claims
110 .exp
111 .map(|exp| {
112 Timestamp::from_unix(exp).map_err(|err| {
113 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
114 crate::Error::JwtClaimsSetDeserializationError(err.into()),
115 ))
116 })
117 })
118 .transpose()?;
119
120 (expiration_date.is_none() || expiration_date >= Some(options.earliest_expiry_date.unwrap_or_default()))
121 .then_some(())
122 .ok_or(CompoundJwtPresentationValidationError::one_presentation_error(
123 JwtValidationError::ExpirationDate,
124 ))?;
125
126 let issuance_date: Option<Timestamp> = match claims.issuance_date {
128 Some(iss) => {
129 if iss.iat.is_some() || iss.nbf.is_some() {
130 Some(iss.to_issuance_date().map_err(|err| {
131 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
132 crate::Error::JwtClaimsSetDeserializationError(err.into()),
133 ))
134 })?)
135 } else {
136 None
137 }
138 }
139 None => None,
140 };
141
142 (issuance_date.is_none() || issuance_date <= Some(options.latest_issuance_date.unwrap_or_default()))
143 .then_some(())
144 .ok_or(CompoundJwtPresentationValidationError::one_presentation_error(
145 JwtValidationError::IssuanceDate,
146 ))?;
147
148 let aud: Option<Url> = claims.aud.clone();
149 let custom_claims: Option<Object> = claims.custom.clone();
150
151 let presentation: Presentation<CRED, T> = claims.try_into_presentation().map_err(|err| {
152 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(err))
153 })?;
154
155 let decoded_jwt_presentation: DecodedJwtPresentation<CRED, T> = DecodedJwtPresentation {
156 presentation,
157 header: Box::new(decoded_jws.protected),
158 expiration_date,
159 issuance_date,
160 aud,
161 custom_claims,
162 };
163
164 Ok(decoded_jwt_presentation)
165 }
166}