identity_credential/validator/jwt_presentation_validation/
jwt_presentation_validator.rs1use identity_core::common::Timestamp;
5use identity_core::convert::FromJson;
6use identity_did::CoreDID;
7use identity_document::document::CoreDocument;
8use identity_verification::jws::DecodedJws;
9use identity_verification::jws::JwsVerifier;
10use std::str::FromStr;
11
12use crate::credential::Jwt;
13use crate::presentation::JwtPresentationV2Claims;
14use crate::presentation::PresentationJwtClaims;
15use crate::validator::jwt_credential_validation::JwtValidationError;
16use crate::validator::jwt_credential_validation::SignerContext;
17
18use super::CompoundJwtPresentationValidationError;
19use super::DecodedJwtPresentation;
20use super::JwtPresentationValidationOptions;
21
22#[derive(Debug, Clone)]
24#[non_exhaustive]
25pub struct JwtPresentationValidator<V: JwsVerifier>(V);
26
27impl<V> JwtPresentationValidator<V>
28where
29 V: JwsVerifier,
30{
31 pub fn with_signature_verifier(signature_verifier: V) -> Self {
33 Self(signature_verifier)
34 }
35
36 pub fn validate<HDOC, CRED, T>(
62 &self,
63 presentation: &Jwt,
64 holder: &HDOC,
65 options: &JwtPresentationValidationOptions,
66 ) -> Result<DecodedJwtPresentation<CRED, T>, CompoundJwtPresentationValidationError>
67 where
68 HDOC: AsRef<CoreDocument> + ?Sized,
69 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
70 CRED: ToOwned<Owned = CRED> + serde::Serialize + serde::de::DeserializeOwned + Clone,
71 {
72 let decoded_jws: DecodedJws<'_> = holder
74 .as_ref()
75 .verify_jws(
76 presentation.as_str(),
77 None,
78 &self.0,
79 &options.presentation_verifier_options,
80 )
81 .map_err(|err| {
82 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationJwsError(err))
83 })?;
84
85 if let Ok(JwtPresentationV2Claims { vp, aud, iat, exp }) = serde_json::from_slice(&decoded_jws.claims) {
87 check_holder(vp.holder.as_str(), holder.as_ref())?;
88
89 return Ok(DecodedJwtPresentation {
90 presentation: vp,
91 header: Box::new(decoded_jws.protected),
92 expiration_date: convert_and_check_exp(exp, options.earliest_expiry_date)?,
93 issuance_date: convert_and_check_iat(iat, options.latest_issuance_date)?,
94 aud,
95 custom_claims: None,
96 });
97 }
98
99 let mut claims: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::from_json_slice(&decoded_jws.claims)
101 .map_err(|err| {
102 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
103 crate::Error::JwtClaimsSetDeserializationError(err.into()),
104 ))
105 })?;
106
107 check_holder(claims.iss.as_str(), holder.as_ref())?;
108 let expiration_date = convert_and_check_exp(claims.exp, options.earliest_expiry_date)?;
109 let issuance_date = {
110 let iat = claims
111 .issuance_date
112 .and_then(|id| id.to_issuance_date().ok())
113 .map(|ts| ts.to_unix());
114 convert_and_check_iat(iat, options.latest_issuance_date)?
115 };
116
117 let aud = claims.aud.take();
118 let custom_claims = claims.custom.take();
119
120 let presentation = claims.try_into_presentation().map_err(|err| {
121 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(err))
122 })?;
123
124 let decoded_jwt_presentation: DecodedJwtPresentation<CRED, T> = DecodedJwtPresentation {
125 presentation,
126 header: Box::new(decoded_jws.protected),
127 expiration_date,
128 issuance_date,
129 aud,
130 custom_claims,
131 };
132
133 Ok(decoded_jwt_presentation)
134 }
135}
136
137fn check_holder(holder: &str, holder_doc: &CoreDocument) -> Result<(), CompoundJwtPresentationValidationError> {
138 let holder_did: CoreDID = CoreDID::from_str(holder).map_err(|err| {
139 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::SignerUrl {
140 signer_ctx: SignerContext::Holder,
141 source: err.into(),
142 })
143 })?;
144
145 if &holder_did != <CoreDocument>::id(holder_doc) {
146 Err(CompoundJwtPresentationValidationError::one_presentation_error(
147 JwtValidationError::DocumentMismatch(SignerContext::Holder),
148 ))
149 } else {
150 Ok(())
151 }
152}
153
154fn convert_and_check_exp(
155 exp: Option<i64>,
156 earliest_expiry_date: Option<Timestamp>,
157) -> Result<Option<Timestamp>, CompoundJwtPresentationValidationError> {
158 let Some(exp) = exp else {
159 return Ok(None);
160 };
161 let exp = Timestamp::from_unix(exp).map_err(|e| {
162 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
163 crate::Error::JwtClaimsSetDeserializationError(e.into()),
164 ))
165 })?;
166
167 if exp >= earliest_expiry_date.unwrap_or_default() {
168 Ok(Some(exp))
169 } else {
170 Err(CompoundJwtPresentationValidationError::one_presentation_error(
171 JwtValidationError::ExpirationDate,
172 ))
173 }
174}
175
176fn convert_and_check_iat(
177 iat: Option<i64>,
178 latest_issuance_date: Option<Timestamp>,
179) -> Result<Option<Timestamp>, CompoundJwtPresentationValidationError> {
180 let Some(iat) = iat else {
181 return Ok(None);
182 };
183 let iat = Timestamp::from_unix(iat).map_err(|e| {
184 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
185 crate::Error::JwtClaimsSetDeserializationError(e.into()),
186 ))
187 })?;
188
189 if iat <= latest_issuance_date.unwrap_or_default() {
190 Ok(Some(iat))
191 } else {
192 Err(CompoundJwtPresentationValidationError::one_presentation_error(
193 JwtValidationError::IssuanceDate,
194 ))
195 }
196}