identity_credential/validator/jwt_presentation_validation/
jwt_presentation_validator_hybrid.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 JwtPresentationValidatorHybrid<TRV: JwsVerifier, PQV: JwsVerifier>(TRV, PQV);
28
29impl<TRV, PQV> JwtPresentationValidatorHybrid<TRV, PQV>
30where
31 TRV: JwsVerifier,
32 PQV: JwsVerifier,
33{
34 pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self {
37 Self(traditional_signature_verifier, pq_signature_verifier)
38 }
39
40 pub fn validate<HDOC, CRED, T>(
66 &self,
67 presentation: &Jwt,
68 holder: &HDOC,
69 options: &JwtPresentationValidationOptions,
70 ) -> Result<DecodedJwtPresentation<CRED, T>, CompoundJwtPresentationValidationError>
71 where
72 HDOC: AsRef<CoreDocument> + ?Sized,
73 T: Clone + serde::Serialize + serde::de::DeserializeOwned,
74 CRED: Clone + serde::Serialize + serde::de::DeserializeOwned + Clone,
75 {
76 let decoded_jws: DecodedJws<'_> = holder
78 .as_ref()
79 .verify_jws_hybrid(
80 presentation.as_str(),
81 None,
82 &self.0,
83 &self.1,
84 &options.presentation_verifier_options,
85 )
86 .map_err(|err| {
87 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationJwsError(err))
88 })?;
89
90 let claims: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::from_json_slice(&decoded_jws.claims)
91 .map_err(|err| {
92 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
93 crate::Error::JwtClaimsSetDeserializationError(err.into()),
94 ))
95 })?;
96
97 let holder_did: CoreDID = CoreDID::from_str(claims.iss.as_str()).map_err(|err| {
99 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::SignerUrl {
100 signer_ctx: SignerContext::Holder,
101 source: err.into(),
102 })
103 })?;
104
105 if &holder_did != <CoreDocument>::id(holder.as_ref()) {
106 return Err(CompoundJwtPresentationValidationError::one_presentation_error(
107 JwtValidationError::DocumentMismatch(SignerContext::Holder),
108 ));
109 }
110
111 let expiration_date: Option<Timestamp> = claims
113 .exp
114 .map(|exp| {
115 Timestamp::from_unix(exp).map_err(|err| {
116 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
117 crate::Error::JwtClaimsSetDeserializationError(err.into()),
118 ))
119 })
120 })
121 .transpose()?;
122
123 if expiration_date.is_some_and(|exp| exp < options.earliest_expiry_date.unwrap_or_default()) {
124 return Err(CompoundJwtPresentationValidationError::one_presentation_error(
125 JwtValidationError::ExpirationDate,
126 ));
127 }
128 let issuance_date: Option<Timestamp> = match claims.issuance_date {
130 Some(iss) => {
131 if iss.iat.is_some() || iss.nbf.is_some() {
132 Some(iss.to_issuance_date().map_err(|err| {
133 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(
134 crate::Error::JwtClaimsSetDeserializationError(err.into()),
135 ))
136 })?)
137 } else {
138 None
139 }
140 }
141 None => None,
142 };
143
144 if issuance_date.is_some_and(|iss| iss > options.latest_issuance_date.unwrap_or_default()) {
145 return Err(CompoundJwtPresentationValidationError::one_presentation_error(
146 JwtValidationError::IssuanceDate,
147 ));
148 }
149
150 let aud: Option<Url> = claims.aud.clone();
151 let custom_claims: Option<Object> = claims.custom.clone();
152
153 let presentation: Presentation<CRED, T> = claims.try_into_presentation().map_err(|err| {
154 CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(err))
155 })?;
156
157 let decoded_jwt_presentation: DecodedJwtPresentation<CRED, T> = DecodedJwtPresentation {
158 presentation,
159 header: Box::new(decoded_jws.protected),
160 expiration_date,
161 issuance_date,
162 aud,
163 custom_claims,
164 };
165
166 Ok(decoded_jwt_presentation)
167 }
168}