identity_credential/validator/jpt_presentation_validation/
jpt_presentation_validator_utils.rs

1// Copyright 2020-2024 IOTA Stiftung, Fondazione Links
2// SPDX-License-Identifier: Apache-2.0
3
4use std::str::FromStr;
5
6use identity_core::common::Object;
7use identity_core::common::Timestamp;
8use identity_core::convert::FromJson;
9use identity_core::convert::ToJson;
10use identity_did::DID;
11use jsonprooftoken::encoding::SerializationType;
12use jsonprooftoken::jpt::claims::JptClaims;
13use jsonprooftoken::jwp::presented::JwpPresentedDecoder;
14
15use crate::credential::Credential;
16use crate::credential::CredentialJwtClaims;
17use crate::credential::Jpt;
18use crate::revocation::RevocationTimeframeStatus;
19use crate::revocation::VerifierRevocationTimeframeStatus;
20use crate::validator::JptCredentialValidatorUtils;
21use crate::validator::JwtValidationError;
22use crate::validator::SignerContext;
23
24/// Utility functions for verifying JPT credentials.
25#[derive(Debug)]
26#[non_exhaustive]
27pub struct JptPresentationValidatorUtils;
28
29type ValidationUnitResult<T = ()> = std::result::Result<T, JwtValidationError>;
30
31impl JptPresentationValidatorUtils {
32  /// Utility for extracting the issuer field of a credential in JPT representation as DID.
33  ///
34  /// # Errors
35  ///
36  /// If the JPT decoding fails or the issuer field is not a valid DID.
37  pub fn extract_issuer_from_presented_jpt<D>(presentation: &Jpt) -> std::result::Result<D, JwtValidationError>
38  where
39    D: DID,
40    <D as FromStr>::Err: std::error::Error + Send + Sync + 'static,
41  {
42    let decoded = JwpPresentedDecoder::decode(presentation.as_str(), SerializationType::COMPACT)
43      .map_err(JwtValidationError::JwpDecodingError)?;
44    let claims = decoded
45      .get_issuer_header()
46      .claims()
47      .ok_or("Claims not present")
48      .map_err(|err| {
49        JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
50      })?;
51    let payloads = decoded.get_payloads();
52    let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads);
53    let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| {
54      JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
55    })?;
56
57    // Deserialize the raw claims
58    let credential_claims: CredentialJwtClaims<'_, Object> = CredentialJwtClaims::from_json_slice(&jpt_claims_json)
59      .map_err(|err| {
60        JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
61      })?;
62
63    D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl {
64      signer_ctx: SignerContext::Issuer,
65      source: err.into(),
66    })
67  }
68
69  /// Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`.
70  pub fn check_timeframes_with_validity_timeframe_2024<T>(
71    credential: &Credential<T>,
72    validity_timeframe: Option<Timestamp>,
73    status_check: crate::validator::StatusCheck,
74  ) -> ValidationUnitResult {
75    if status_check == crate::validator::StatusCheck::SkipAll {
76      return Ok(());
77    }
78
79    match &credential.credential_status {
80      None => Ok(()),
81      Some(status) => {
82        if status.type_ == RevocationTimeframeStatus::TYPE {
83          let status: VerifierRevocationTimeframeStatus =
84            VerifierRevocationTimeframeStatus::try_from(status.clone()).map_err(JwtValidationError::InvalidStatus)?;
85
86          JptCredentialValidatorUtils::check_validity_timeframe(status.0, validity_timeframe)
87        } else {
88          if status_check == crate::validator::StatusCheck::SkipUnsupported {
89            return Ok(());
90          }
91          Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
92            "unsupported type '{}'",
93            status.type_
94          ))))
95        }
96      }
97    }
98  }
99}