1use super::JwkStorageDocumentError as Error;
5use crate::key_id_storage::MethodDigest;
6use crate::try_undo_key_generation;
7use crate::JwkGenOutput;
8use crate::JwkStoragePQ;
9use crate::JwsSignatureOptions;
10use crate::KeyIdStorage;
11use crate::KeyType;
12use crate::Storage;
13use crate::StorageResult;
14use async_trait::async_trait;
15use identity_core::common::Object;
16use identity_credential::credential::Credential;
17use identity_credential::credential::Jws;
18use identity_credential::credential::Jwt;
19use identity_credential::presentation::JwtPresentationOptions;
20use identity_credential::presentation::Presentation;
21use identity_did::DIDUrl;
22use identity_document::document::CoreDocument;
23use identity_verification::jwk::PostQuantumJwk;
24use identity_verification::jws::CharSet;
25use identity_verification::jws::CompactJwsEncoder;
26use identity_verification::jws::CompactJwsEncodingOptions;
27use identity_verification::jws::JwsAlgorithm;
28use identity_verification::jws::JwsHeader;
29use identity_verification::MethodData;
30use identity_verification::MethodScope;
31use identity_verification::VerificationMethod;
32use serde::de::DeserializeOwned;
33use serde::Serialize;
34
35#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
37#[cfg_attr(feature = "send-sync-storage", async_trait)]
38pub trait JwsDocumentExtPQC {
39 async fn generate_method_pqc<K, I>(
42 &mut self,
43 storage: &Storage<K, I>,
44 key_type: KeyType,
45 alg: JwsAlgorithm,
46 fragment: Option<&str>,
47 scope: MethodScope,
48 ) -> StorageResult<String>
49 where
50 K: JwkStoragePQ,
51 I: KeyIdStorage;
52
53 async fn create_jws_pqc<K, I>(
55 &self,
56 storage: &Storage<K, I>,
57 fragment: &str,
58 payload: &[u8],
59 options: &JwsSignatureOptions,
60 ) -> StorageResult<Jws>
61 where
62 K: JwkStoragePQ,
63 I: KeyIdStorage;
64
65 async fn create_credential_jwt_pqc<K, I, T>(
74 &self,
75 credential: &Credential<T>,
76 storage: &Storage<K, I>,
77 fragment: &str,
78 options: &JwsSignatureOptions,
79 custom_claims: Option<Object>,
80 ) -> StorageResult<Jwt>
81 where
82 K: JwkStoragePQ,
83 I: KeyIdStorage,
84 T: Clone + Serialize + DeserializeOwned + Sync;
85
86 async fn create_presentation_jwt_pqc<K, I, CRED, T>(
93 &self,
94 presentation: &Presentation<CRED, T>,
95 storage: &Storage<K, I>,
96 fragment: &str,
97 signature_options: &JwsSignatureOptions,
98 presentation_options: &JwtPresentationOptions,
99 ) -> StorageResult<Jwt>
100 where
101 K: JwkStoragePQ,
102 I: KeyIdStorage,
103 T: Clone + Serialize + DeserializeOwned + Sync,
104 CRED: Clone + Serialize + DeserializeOwned + Sync;
105}
106
107generate_method_for_document_type!(
112 CoreDocument,
113 JwsAlgorithm,
114 JwkStoragePQ,
115 JwkStoragePQ::generate_pq_key,
116 generate_method_core_document
117);
118
119#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
120#[cfg_attr(feature = "send-sync-storage", async_trait)]
121impl JwsDocumentExtPQC for CoreDocument {
122 async fn generate_method_pqc<K, I>(
123 &mut self,
124 storage: &Storage<K, I>,
125 key_type: KeyType,
126 alg: JwsAlgorithm,
127 fragment: Option<&str>,
128 scope: MethodScope,
129 ) -> StorageResult<String>
130 where
131 K: JwkStoragePQ,
132 I: KeyIdStorage,
133 {
134 generate_method_core_document(self, storage, key_type, alg, fragment, scope).await
135 }
136
137 async fn create_jws_pqc<K, I>(
138 &self,
139 storage: &Storage<K, I>,
140 fragment: &str,
141 payload: &[u8],
142 options: &JwsSignatureOptions,
143 ) -> StorageResult<Jws>
144 where
145 K: JwkStoragePQ,
146 I: KeyIdStorage,
147 {
148 let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
150 let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
151 return Err(Error::NotPublicKeyJwk);
152 };
153
154 let alg: JwsAlgorithm = jwk
156 .alg()
157 .unwrap_or("")
158 .parse()
159 .map_err(|_| Error::InvalidJwsAlgorithm)?;
160
161 let header: JwsHeader = {
163 let mut header = JwsHeader::new();
164
165 header.set_alg(alg);
166 if let Some(custom) = &options.custom_header_parameters {
167 header.set_custom(custom.clone())
168 }
169
170 if let Some(ref kid) = options.kid {
171 header.set_kid(kid.clone());
172 } else {
173 header.set_kid(method.id().to_string());
174 }
175
176 if options.attach_jwk {
177 header.set_jwk(jwk.clone())
178 };
179
180 if let Some(b64) = options.b64 {
181 if !b64 {
183 header.set_b64(b64);
184 header.set_crit(["b64"]);
185 }
186 };
187
188 if let Some(typ) = &options.typ {
189 header.set_typ(typ.clone())
190 } else {
191 header.set_typ("JWT")
193 }
194
195 if let Some(cty) = &options.cty {
196 header.set_cty(cty.clone())
197 };
198
199 if let Some(url) = &options.url {
200 header.set_url(url.clone())
201 };
202
203 if let Some(nonce) = &options.nonce {
204 header.set_nonce(nonce.clone())
205 };
206
207 header
208 };
209
210 let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
212 let key_id = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
213 .await
214 .map_err(Error::KeyIdStorageError)?;
215
216 let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload {
218 CompactJwsEncodingOptions::NonDetached {
221 charset_requirements: CharSet::Default,
222 }
223 } else {
224 CompactJwsEncodingOptions::Detached
225 };
226
227 let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options)
228 .map_err(|err| Error::EncodingError(err.into()))?;
229
230 let pq_jwk = PostQuantumJwk::try_from(jwk.clone()).map_err(|err| Error::EncodingError(Box::new(err)))?;
231
232 let signature = <K as JwkStoragePQ>::pq_sign(
233 storage.key_storage(),
234 &key_id,
235 jws_encoder.signing_input(),
236 &pq_jwk,
237 None,
238 )
239 .await
240 .map_err(Error::KeyStorageError)?;
241 Ok(Jws::new(jws_encoder.into_jws(&signature)))
242 }
243
244 async fn create_credential_jwt_pqc<K, I, T>(
245 &self,
246 credential: &Credential<T>,
247 storage: &Storage<K, I>,
248 fragment: &str,
249 options: &JwsSignatureOptions,
250 custom_claims: Option<Object>,
251 ) -> StorageResult<Jwt>
252 where
253 K: JwkStoragePQ,
254 I: KeyIdStorage,
255 T: Clone + Serialize + DeserializeOwned + Sync,
256 {
257 if options.detached_payload {
258 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
259 "cannot use detached payload for credential signing",
260 )));
261 }
262
263 if !options.b64.unwrap_or(true) {
264 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
266 "cannot use `b64 = false` with JWTs",
267 )));
268 }
269
270 let payload = credential
271 .serialize_jwt(custom_claims)
272 .map_err(Error::ClaimsSerializationError)?;
273 self
274 .create_jws_pqc(storage, fragment, payload.as_bytes(), options)
275 .await
276 .map(|jws| Jwt::new(jws.into()))
277 }
278
279 async fn create_presentation_jwt_pqc<K, I, CRED, T>(
280 &self,
281 presentation: &Presentation<CRED, T>,
282 storage: &Storage<K, I>,
283 fragment: &str,
284 jws_options: &JwsSignatureOptions,
285 jwt_options: &JwtPresentationOptions,
286 ) -> StorageResult<Jwt>
287 where
288 K: JwkStoragePQ,
289 I: KeyIdStorage,
290 T: Clone + Serialize + DeserializeOwned + Sync,
291 CRED: Clone + Serialize + DeserializeOwned + Sync,
292 {
293 if jws_options.detached_payload {
294 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
295 "cannot use detached payload for presentation signing",
296 )));
297 }
298
299 if !jws_options.b64.unwrap_or(true) {
300 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
302 "cannot use `b64 = false` with JWTs",
303 )));
304 }
305 let payload = presentation
306 .serialize_jwt(jwt_options)
307 .map_err(Error::ClaimsSerializationError)?;
308 self
309 .create_jws_pqc(storage, fragment, payload.as_bytes(), jws_options)
310 .await
311 .map(|jws| Jwt::new(jws.into()))
312 }
313}
314
315#[cfg(feature = "iota-document")]
319mod iota_document {
320
321 use super::*;
322 use identity_iota_core::IotaDocument;
323
324 generate_method_for_document_type!(
325 IotaDocument,
326 JwsAlgorithm,
327 JwkStoragePQ,
328 JwkStoragePQ::generate_pq_key,
329 generate_method_iota_document
330 );
331
332 #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
333 #[cfg_attr(feature = "send-sync-storage", async_trait)]
334 impl JwsDocumentExtPQC for IotaDocument {
335 async fn generate_method_pqc<K, I>(
336 &mut self,
337 storage: &Storage<K, I>,
338 key_type: KeyType,
339 alg: JwsAlgorithm,
340 fragment: Option<&str>,
341 scope: MethodScope,
342 ) -> StorageResult<String>
343 where
344 K: JwkStoragePQ,
345 I: KeyIdStorage,
346 {
347 generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await
348 }
349
350 async fn create_jws_pqc<K, I>(
351 &self,
352 storage: &Storage<K, I>,
353 fragment: &str,
354 payload: &[u8],
355 options: &JwsSignatureOptions,
356 ) -> StorageResult<Jws>
357 where
358 K: JwkStoragePQ,
359 I: KeyIdStorage,
360 {
361 self
362 .core_document()
363 .create_jws_pqc(storage, fragment, payload, options)
364 .await
365 }
366
367 async fn create_credential_jwt_pqc<K, I, T>(
368 &self,
369 credential: &Credential<T>,
370 storage: &Storage<K, I>,
371 fragment: &str,
372 options: &JwsSignatureOptions,
373 custom_claims: Option<Object>,
374 ) -> StorageResult<Jwt>
375 where
376 K: JwkStoragePQ,
377 I: KeyIdStorage,
378 T: Clone + Serialize + DeserializeOwned + Sync,
379 {
380 self
381 .core_document()
382 .create_credential_jwt_pqc(credential, storage, fragment, options, custom_claims)
383 .await
384 }
385
386 async fn create_presentation_jwt_pqc<K, I, CRED, T>(
387 &self,
388 presentation: &Presentation<CRED, T>,
389 storage: &Storage<K, I>,
390 fragment: &str,
391 jws_options: &JwsSignatureOptions,
392 jwt_options: &JwtPresentationOptions,
393 ) -> StorageResult<Jwt>
394 where
395 K: JwkStoragePQ,
396 I: KeyIdStorage,
397 T: Clone + Serialize + DeserializeOwned + Sync,
398 CRED: Clone + Serialize + DeserializeOwned + Sync,
399 {
400 self
401 .core_document()
402 .create_presentation_jwt_pqc(presentation, storage, fragment, jws_options, jwt_options)
403 .await
404 }
405 }
406}