identity_storage/storage/
timeframe_revocation_ext.rs

1// Copyright 2020-2024 IOTA Stiftung, Fondazione Links
2// SPDX-License-Identifier: Apache-2.0
3
4use super::JwkStorageDocumentError as Error;
5use crate::JwkStorageBbsPlusExt;
6use crate::KeyIdStorage;
7use crate::MethodDigest;
8use crate::Storage;
9use crate::StorageResult;
10use async_trait::async_trait;
11use identity_core::common::Duration;
12use identity_core::common::Timestamp;
13use identity_credential::credential::Jpt;
14use identity_credential::revocation::RevocationTimeframeStatus;
15use identity_document::document::CoreDocument;
16use identity_verification::MethodData;
17use identity_verification::VerificationMethod;
18use jsonprooftoken::encoding::SerializationType;
19use jsonprooftoken::jpt::payloads::Payloads;
20use jsonprooftoken::jwp::issued::JwpIssued;
21use serde_json::Value;
22use zkryptium::bbsplus::signature::BBSplusSignature;
23
24/// Contains information needed to update the signature in the RevocationTimeframe2024 revocation mechanism.
25pub struct ProofUpdateCtx {
26  /// Old `startValidityTimeframe` value
27  pub old_start_validity_timeframe: Vec<u8>,
28  /// New `startValidityTimeframe` value to be signed
29  pub new_start_validity_timeframe: Vec<u8>,
30  /// Old `endValidityTimeframe` value
31  pub old_end_validity_timeframe: Vec<u8>,
32  /// New `endValidityTimeframe` value to be signed
33  pub new_end_validity_timeframe: Vec<u8>,
34  /// Index of `startValidityTimeframe` claim inside the array of Claims
35  pub index_start_validity_timeframe: usize,
36  /// Index of `endValidityTimeframe` claim inside the array of Claims
37  pub index_end_validity_timeframe: usize,
38  /// Number of signed messages, number of payloads in a JWP
39  pub number_of_signed_messages: usize,
40}
41
42/// CoreDocument and IotaDocument extension to handle Credential' signature update for RevocationTimeframe2024
43#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
44#[cfg_attr(feature = "send-sync-storage", async_trait)]
45pub trait TimeframeRevocationExtension {
46  /// Update Credential' signature considering the Timeframe interval
47  async fn update<K, I>(
48    &self,
49    storage: &Storage<K, I>,
50    fragment: &str,
51    start_validity: Option<Timestamp>,
52    duration: Duration,
53    credential_jwp: &mut JwpIssued,
54  ) -> StorageResult<Jpt>
55  where
56    K: JwkStorageBbsPlusExt,
57    I: KeyIdStorage;
58}
59
60// ====================================================================================================================
61// CoreDocument
62// ====================================================================================================================
63
64#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
65#[cfg_attr(feature = "send-sync-storage", async_trait)]
66impl TimeframeRevocationExtension for CoreDocument {
67  async fn update<K, I>(
68    &self,
69    storage: &Storage<K, I>,
70    fragment: &str,
71    start_validity: Option<Timestamp>,
72    duration: Duration,
73    credential_jwp: &mut JwpIssued,
74  ) -> StorageResult<Jpt>
75  where
76    K: JwkStorageBbsPlusExt,
77    I: KeyIdStorage,
78  {
79    // Obtain the method corresponding to the given fragment.
80    let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
81    let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
82      return Err(Error::NotPublicKeyJwk);
83    };
84
85    // Get the key identifier corresponding to the given method from the KeyId storage.
86    let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
87    let key_id = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
88      .await
89      .map_err(Error::KeyIdStorageError)?;
90
91    let new_start_validity_timeframe = start_validity.unwrap_or(Timestamp::now_utc());
92    let new_end_validity_timeframe = new_start_validity_timeframe
93      .checked_add(duration)
94      .ok_or(Error::ProofUpdateError("Invalid granularity".to_owned()))?;
95    let new_start_validity_timeframe = new_start_validity_timeframe.to_rfc3339();
96    let new_end_validity_timeframe = new_end_validity_timeframe.to_rfc3339();
97
98    let proof = credential_jwp.get_proof();
99    let claims = credential_jwp
100      .get_claims()
101      .ok_or(Error::ProofUpdateError("Should not happen".to_owned()))?;
102    let mut payloads: Payloads = credential_jwp.get_payloads().clone();
103
104    let index_start_validity_timeframe = claims
105      .get_claim_index(format!(
106        "vc.credentialStatus.{}",
107        RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY
108      ))
109      .ok_or(Error::ProofUpdateError(
110        "'startValidityTimeframe' property NOT found".to_owned(),
111      ))?;
112    let index_end_validity_timeframe = claims
113      .get_claim_index(format!(
114        "vc.credentialStatus.{}",
115        RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY
116      ))
117      .ok_or(Error::ProofUpdateError(
118        "'endValidityTimeframe' property NOT found".to_owned(),
119      ))?;
120
121    let old_start_validity_timeframe = payloads
122      .replace_payload_at_index(
123        index_start_validity_timeframe,
124        Value::String(new_start_validity_timeframe.clone()),
125      )
126      .map(serde_json::from_value::<String>)
127      .map_err(|_| Error::ProofUpdateError("'startValidityTimeframe' value NOT found".to_owned()))?
128      .map_err(|_| Error::ProofUpdateError("'startValidityTimeframe' value NOT a JSON String".to_owned()))?;
129
130    let old_end_validity_timeframe = payloads
131      .replace_payload_at_index(
132        index_end_validity_timeframe,
133        Value::String(new_end_validity_timeframe.clone()),
134      )
135      .map(serde_json::from_value::<String>)
136      .map_err(|_| Error::ProofUpdateError("'endValidityTimeframe' value NOT found".to_owned()))?
137      .map_err(|_| Error::ProofUpdateError("'endValidityTimeframe' value NOT a JSON String".to_owned()))?;
138
139    let proof: [u8; BBSplusSignature::BYTES] = proof
140      .try_into()
141      .map_err(|_| Error::ProofUpdateError("Invalid bytes length of JWP proof".to_owned()))?;
142
143    let proof_update_ctx = ProofUpdateCtx {
144      old_start_validity_timeframe: serde_json::to_vec(&old_start_validity_timeframe).unwrap(),
145      new_start_validity_timeframe: serde_json::to_vec(&new_start_validity_timeframe).unwrap(),
146      old_end_validity_timeframe: serde_json::to_vec(&old_end_validity_timeframe).unwrap(),
147      new_end_validity_timeframe: serde_json::to_vec(&new_end_validity_timeframe).unwrap(),
148      index_start_validity_timeframe,
149      index_end_validity_timeframe,
150      number_of_signed_messages: payloads.0.len(),
151    };
152
153    let new_proof =
154      <K as JwkStorageBbsPlusExt>::update_signature(storage.key_storage(), &key_id, jwk, &proof, proof_update_ctx)
155        .await
156        .map_err(Error::KeyStorageError)?;
157
158    credential_jwp.set_proof(&new_proof);
159    credential_jwp.set_payloads(payloads);
160
161    let jpt = credential_jwp
162      .encode(SerializationType::COMPACT)
163      .map_err(|e| Error::EncodingError(Box::new(e)))?;
164
165    Ok(Jpt::new(jpt))
166  }
167}
168
169// ====================================================================================================================
170// IotaDocument
171// ====================================================================================================================
172#[cfg(feature = "iota-document")]
173mod iota_document {
174  use super::*;
175  use identity_iota_core::IotaDocument;
176
177  #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
178  #[cfg_attr(feature = "send-sync-storage", async_trait)]
179  impl TimeframeRevocationExtension for IotaDocument {
180    async fn update<K, I>(
181      &self,
182      storage: &Storage<K, I>,
183      fragment: &str,
184      start_validity: Option<Timestamp>,
185      duration: Duration,
186      credential_jwp: &mut JwpIssued,
187    ) -> StorageResult<Jpt>
188    where
189      K: JwkStorageBbsPlusExt,
190      I: KeyIdStorage,
191    {
192      self
193        .core_document()
194        .update(storage, fragment, start_validity, duration, credential_jwp)
195        .await
196    }
197  }
198}