identity_credential/validator/jpt_credential_validation/
jpt_credential_validator_utils.rs1use crate::credential::Credential;
5use crate::revocation::RevocationDocumentExt;
6use crate::revocation::RevocationTimeframeStatus;
7use std::str::FromStr;
8
9use identity_core::common::Object;
10use identity_core::common::Timestamp;
11use identity_core::convert::FromJson;
12use identity_core::convert::ToJson;
13use identity_did::DID;
14use jsonprooftoken::encoding::SerializationType;
15use jsonprooftoken::jpt::claims::JptClaims;
16use jsonprooftoken::jwp::issued::JwpIssuedDecoder;
17
18use crate::credential::CredentialJwtClaims;
19use crate::credential::Jpt;
20use crate::validator::JwtValidationError;
21use crate::validator::SignerContext;
22
23#[derive(Debug)]
25#[non_exhaustive]
26pub struct JptCredentialValidatorUtils;
27
28type ValidationUnitResult<T = ()> = std::result::Result<T, JwtValidationError>;
29
30impl JptCredentialValidatorUtils {
31 pub fn extract_issuer<D, T>(credential: &Credential<T>) -> std::result::Result<D, JwtValidationError>
37 where
38 D: DID,
39 <D as FromStr>::Err: std::error::Error + Send + Sync + 'static,
40 {
41 D::from_str(credential.issuer.url().as_str()).map_err(|err| JwtValidationError::SignerUrl {
42 signer_ctx: SignerContext::Issuer,
43 source: err.into(),
44 })
45 }
46
47 pub fn extract_issuer_from_issued_jpt<D>(credential: &Jpt) -> std::result::Result<D, JwtValidationError>
53 where
54 D: DID,
55 <D as FromStr>::Err: std::error::Error + Send + Sync + 'static,
56 {
57 let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT)
58 .map_err(JwtValidationError::JwpDecodingError)?;
59 let claims = decoded
60 .get_header()
61 .claims()
62 .ok_or("Claims not present")
63 .map_err(|err| {
64 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
65 })?;
66 let payloads = decoded.get_payloads();
67 let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads);
68 let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| {
69 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
70 })?;
71
72 let credential_claims: CredentialJwtClaims<'_, Object> = CredentialJwtClaims::from_json_slice(&jpt_claims_json)
74 .map_err(|err| {
75 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
76 })?;
77
78 D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl {
79 signer_ctx: SignerContext::Issuer,
80 source: err.into(),
81 })
82 }
83
84 pub fn check_timeframes_with_validity_timeframe_2024<T>(
86 credential: &Credential<T>,
87 validity_timeframe: Option<Timestamp>,
88 status_check: crate::validator::StatusCheck,
89 ) -> ValidationUnitResult {
90 if status_check == crate::validator::StatusCheck::SkipAll {
91 return Ok(());
92 }
93
94 match &credential.credential_status {
95 None => Ok(()),
96 Some(status) => {
97 if status.type_ == RevocationTimeframeStatus::TYPE {
98 let status: RevocationTimeframeStatus =
99 RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?;
100
101 Self::check_validity_timeframe(status, validity_timeframe)
102 } else {
103 if status_check == crate::validator::StatusCheck::SkipUnsupported {
104 return Ok(());
105 }
106 Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
107 "unsupported type '{}'",
108 status.type_
109 ))))
110 }
111 }
112 }
113 }
114
115 pub(crate) fn check_validity_timeframe(
116 status: RevocationTimeframeStatus,
117 validity_timeframe: Option<Timestamp>,
118 ) -> ValidationUnitResult {
119 let timeframe = validity_timeframe.unwrap_or(Timestamp::now_utc());
120
121 let check = timeframe >= status.start_validity_timeframe() && timeframe <= status.end_validity_timeframe();
122
123 if !check {
124 Err(JwtValidationError::OutsideTimeframe)
125 } else {
126 Ok(())
127 }
128 }
129
130 pub fn check_revocation_with_validity_timeframe_2024<
134 DOC: AsRef<identity_document::document::CoreDocument> + ?Sized,
135 T,
136 >(
137 credential: &Credential<T>,
138 issuer: &DOC,
139 status_check: crate::validator::StatusCheck,
140 ) -> ValidationUnitResult {
141 if status_check == crate::validator::StatusCheck::SkipAll {
142 return Ok(());
143 }
144
145 match &credential.credential_status {
146 None => Ok(()),
147 Some(status) => {
148 if status.type_ == RevocationTimeframeStatus::TYPE {
149 let status: RevocationTimeframeStatus =
150 RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?;
151
152 Self::check_revocation_bitmap(issuer, status)
153 } else {
154 if status_check == crate::validator::StatusCheck::SkipUnsupported {
155 return Ok(());
156 }
157 Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
158 "unsupported type '{}'",
159 status.type_
160 ))))
161 }
162 }
163 }
164 }
165
166 fn check_revocation_bitmap<DOC: AsRef<identity_document::document::CoreDocument> + ?Sized>(
168 issuer: &DOC,
169 status: RevocationTimeframeStatus,
170 ) -> ValidationUnitResult {
171 let issuer_service_url: identity_did::DIDUrl = identity_did::DIDUrl::parse(status.id()).map_err(|err| {
172 JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
173 "could not convert status id to DIDUrl; {}",
174 err,
175 )))
176 })?;
177
178 let revocation_bitmap: crate::revocation::RevocationBitmap = issuer
180 .as_ref()
181 .resolve_revocation_bitmap(issuer_service_url.into())
182 .map_err(|_| JwtValidationError::ServiceLookupError)?;
183
184 if let Some(index) = status.index() {
185 if revocation_bitmap.is_revoked(index) {
186 return Err(JwtValidationError::Revoked);
187 }
188 }
189 Ok(())
190 }
191
192 pub fn check_timeframes_and_revocation_with_validity_timeframe_2024<
196 DOC: AsRef<identity_document::document::CoreDocument> + ?Sized,
197 T,
198 >(
199 credential: &Credential<T>,
200 issuer: &DOC,
201 validity_timeframe: Option<Timestamp>,
202 status_check: crate::validator::StatusCheck,
203 ) -> ValidationUnitResult {
204 if status_check == crate::validator::StatusCheck::SkipAll {
205 return Ok(());
206 }
207
208 match &credential.credential_status {
209 None => Ok(()),
210 Some(status) => {
211 if status.type_ == RevocationTimeframeStatus::TYPE {
212 let status: RevocationTimeframeStatus =
213 RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?;
214
215 let revocation = std::iter::once_with(|| Self::check_revocation_bitmap(issuer, status.clone()));
216
217 let timeframes = std::iter::once_with(|| Self::check_validity_timeframe(status.clone(), validity_timeframe));
218
219 let checks_iter = revocation.chain(timeframes);
220
221 let checks_error_iter = checks_iter.filter_map(|result| result.err());
222
223 let mut checks_errors: Vec<JwtValidationError> = checks_error_iter.take(1).collect();
224
225 match checks_errors.pop() {
226 Some(err) => Err(err),
227 None => Ok(()),
228 }
229 } else {
230 if status_check == crate::validator::StatusCheck::SkipUnsupported {
231 return Ok(());
232 }
233 Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
234 "unsupported type '{}'",
235 status.type_
236 ))))
237 }
238 }
239 }
240 }
241}