1use super::JwkStorageDocumentError as Error;
5use crate::try_undo_key_generation;
6use crate::JwkGenOutput;
7use crate::JwkStorage;
8use crate::JwkStoragePQ;
9use crate::JwsSignatureOptions;
10use crate::KeyId;
11use crate::KeyIdStorage;
12use crate::KeyIdStorageErrorKind;
13use crate::KeyType;
14use crate::MethodDigest;
15use crate::Storage;
16use crate::StorageResult;
17use async_trait::async_trait;
18use identity_core::common::Object;
19use identity_credential::credential::Credential;
20use identity_credential::credential::Jws;
21use identity_credential::credential::Jwt;
22use identity_credential::presentation::JwtPresentationOptions;
23use identity_credential::presentation::Presentation;
24use identity_did::DIDUrl;
25use identity_document::document::CoreDocument;
26use identity_verification::jwk::CompositeAlgId;
27use identity_verification::jwk::CompositeJwk;
28use identity_verification::jwk::PostQuantumJwk;
29use identity_verification::jwk::TraditionalJwk;
30use identity_verification::jws::CharSet;
31use identity_verification::jws::CompactJwsEncoder;
32use identity_verification::jws::CompactJwsEncodingOptions;
33use identity_verification::jws::JwsAlgorithm;
34use identity_verification::jws::JwsHeader;
35use identity_verification::MethodData;
36use identity_verification::MethodScope;
37use identity_verification::VerificationMethod;
38use serde::de::DeserializeOwned;
39use serde::Serialize;
40
41macro_rules! generate_method_hybrid_for_document_type {
42 ($t:ty, $name:ident) => {
43 async fn $name<K, I>(
44 document: &mut $t,
45 storage: &Storage<K, I>,
46 alg_id: CompositeAlgId,
47 fragment: Option<&str>,
48 scope: MethodScope,
49 ) -> StorageResult<String>
50 where
51 K: JwkStorage + JwkStoragePQ,
52 I: KeyIdStorage,
53 {
54 let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id {
55 CompositeAlgId::IdMldsa44Ed25519 => (
56 KeyType::from_static_str("AKP"),
57 JwsAlgorithm::ML_DSA_44,
58 KeyType::from_static_str("Ed25519"),
59 JwsAlgorithm::EdDSA,
60 ),
61 CompositeAlgId::IdMldsa65Ed25519 => (
62 KeyType::from_static_str("AKP"),
63 JwsAlgorithm::ML_DSA_65,
64 KeyType::from_static_str("Ed25519"),
65 JwsAlgorithm::EdDSA,
66 ),
67 _ => {
68 return Err(Error::InvalidJwsAlgorithm);
69 }
70 };
71
72 let JwkGenOutput {
73 key_id: t_key_id,
74 jwk: t_jwk,
75 } = K::generate(storage.key_storage(), trad_key_type, trad_alg)
76 .await
77 .map_err(Error::KeyStorageError)?;
78
79 let JwkGenOutput {
80 key_id: pq_key_id,
81 jwk: pq_jwk,
82 } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg)
83 .await
84 .map_err(Error::KeyStorageError)?;
85
86 let composite_kid = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str()));
87
88 let pq_jwk = PostQuantumJwk::try_from(pq_jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
89
90 let traditional_jwk = TraditionalJwk::try_from(t_jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
91
92 let composite_pk = CompositeJwk::new(alg_id, traditional_jwk, pq_jwk);
93
94 let method: VerificationMethod = {
95 match VerificationMethod::new_from_compositejwk(document.id().clone(), composite_pk, fragment)
96 .map_err(Error::VerificationMethodConstructionError)
97 {
98 Ok(method) => method,
99 Err(source) => {
100 let error = try_undo_key_generation(storage, &t_key_id, source).await;
101 let error = try_undo_key_generation(storage, &pq_key_id, error).await;
102 return Err(error);
103 }
104 }
105 };
106
107 let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?;
109 let method_id: DIDUrl = method.id().clone();
110
111 let fragment: String = method_id
113 .fragment()
114 .ok_or(identity_verification::Error::MissingIdFragment)
115 .map_err(Error::VerificationMethodConstructionError)?
116 .to_owned();
117
118 if let Err(error) = document
120 .insert_method(method, scope)
121 .map_err(|_| Error::FragmentAlreadyExists)
122 {
123 let error = try_undo_key_generation(storage, &t_key_id, error).await;
124 let error = try_undo_key_generation(storage, &pq_key_id, error).await;
125 return Err(error);
126 };
127
128 if let Err(error) = <I as KeyIdStorage>::insert_key_id(&storage.key_id_storage(), method_digest, composite_kid)
131 .await
132 .map_err(Error::KeyIdStorageError)
133 {
134 let _ = document.remove_method(&method_id);
136 let error = try_undo_key_generation(storage, &t_key_id, error).await;
137 let error = try_undo_key_generation(storage, &pq_key_id, error).await;
138 return Err(error);
139 }
140
141 Ok(fragment)
142 }
143 };
144}
145
146#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
148#[cfg_attr(feature = "send-sync-storage", async_trait)]
149pub trait JwkDocumentExtHybrid {
150 async fn generate_method_hybrid<K, I>(
152 &mut self,
153 storage: &Storage<K, I>,
154 alg_id: CompositeAlgId,
155 fragment: Option<&str>,
156 scope: MethodScope,
157 ) -> StorageResult<String>
158 where
159 K: JwkStorage + JwkStoragePQ,
160 I: KeyIdStorage;
161
162 async fn create_jws<K, I>(
164 &self,
165 storage: &Storage<K, I>,
166 fragment: &str,
167 payload: &[u8],
168 options: &JwsSignatureOptions,
169 ) -> StorageResult<Jws>
170 where
171 K: JwkStorage + JwkStoragePQ,
172 I: KeyIdStorage;
173
174 async fn create_credential_jwt_hybrid<K, I, T>(
176 &self,
177 credential: &Credential<T>,
178 storage: &Storage<K, I>,
179 fragment: &str,
180 options: &JwsSignatureOptions,
181 custom_claims: Option<Object>,
182 ) -> StorageResult<Jwt>
183 where
184 K: JwkStorage + JwkStoragePQ,
185 I: KeyIdStorage,
186 T: Clone + Serialize + DeserializeOwned + Sync;
187
188 async fn create_presentation_jwt_hybrid<K, I, CRED, T>(
190 &self,
191 presentation: &Presentation<CRED, T>,
192 storage: &Storage<K, I>,
193 fragment: &str,
194 signature_options: &JwsSignatureOptions,
195 presentation_options: &JwtPresentationOptions,
196 ) -> StorageResult<Jwt>
197 where
198 K: JwkStorage + JwkStoragePQ,
199 I: KeyIdStorage,
200 T: Clone + Serialize + DeserializeOwned + Sync,
201 CRED: Serialize + DeserializeOwned + Clone + Sync;
202}
203
204generate_method_hybrid_for_document_type!(CoreDocument, generate_method_hybrid_core_document);
205
206#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
207#[cfg_attr(feature = "send-sync-storage", async_trait)]
208impl JwkDocumentExtHybrid for CoreDocument {
209 async fn generate_method_hybrid<K, I>(
210 &mut self,
211 storage: &Storage<K, I>,
212 alg_id: CompositeAlgId,
213 fragment: Option<&str>,
214 scope: MethodScope,
215 ) -> StorageResult<String>
216 where
217 K: JwkStorage + JwkStoragePQ,
218 I: KeyIdStorage,
219 {
220 generate_method_hybrid_core_document(self, storage, alg_id, fragment, scope).await
221 }
222
223 async fn create_jws<K, I>(
225 &self,
226 storage: &Storage<K, I>,
227 fragment: &str,
228 payload: &[u8],
229 options: &JwsSignatureOptions,
230 ) -> StorageResult<Jws>
231 where
232 K: JwkStorage + JwkStoragePQ,
233 I: KeyIdStorage,
234 {
235 let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
237 let MethodData::CompositeJwk(ref composite) = method.data() else {
238 return Err(Error::NotCompositePublicKey);
239 };
240
241 let alg_id = composite.alg_id();
242 let t_jwk = composite.traditional_public_key();
243 let pq_jwk = composite.pq_public_key();
244
245 let alg: JwsAlgorithm = alg_id.name().parse().map_err(|_| Error::InvalidJwsAlgorithm)?;
247
248 let header: JwsHeader = {
250 let mut header = JwsHeader::new();
251
252 header.set_alg(alg.clone());
253 if let Some(custom) = &options.custom_header_parameters {
254 header.set_custom(custom.clone())
255 }
256
257 if let Some(ref kid) = options.kid {
258 header.set_kid(kid.clone());
259 } else {
260 header.set_kid(method.id().to_string());
261 }
262
263 if let Some(b64) = options.b64 {
264 if !b64 {
266 header.set_b64(b64);
267 header.set_crit(["b64"]);
268 }
269 };
270
271 if let Some(typ) = &options.typ {
272 header.set_typ(typ.clone())
273 } else {
274 header.set_typ("JWT")
276 }
277
278 if let Some(cty) = &options.cty {
279 header.set_cty(cty.clone())
280 };
281
282 if let Some(url) = &options.url {
283 header.set_url(url.clone())
284 };
285
286 if let Some(nonce) = &options.nonce {
287 header.set_nonce(nonce.clone())
288 };
289
290 header
291 };
292
293 let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
295 let key_id: KeyId = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
296 .await
297 .map_err(Error::KeyIdStorageError)?;
298
299 let (t_key_id, pq_key_id) = match key_id.as_str().split_once("~") {
300 Some(v) => (KeyId::new(v.0), KeyId::new(v.1)),
301 None => {
302 return Err(Error::KeyIdStorageError(KeyIdStorageErrorKind::Unspecified.into()));
304 }
305 };
306
307 let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload {
309 CompactJwsEncodingOptions::NonDetached {
312 charset_requirements: CharSet::Default,
313 }
314 } else {
315 CompactJwsEncodingOptions::Detached
316 };
317
318 let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options)
319 .map_err(|err| Error::EncodingError(err.into()))?;
320
321 let domain = match alg {
322 JwsAlgorithm::IdMldsa44Ed25519 => CompositeAlgId::IdMldsa44Ed25519.domain(),
323 JwsAlgorithm::IdMldsa65Ed25519 => CompositeAlgId::IdMldsa65Ed25519.domain(),
324 _ => return Err(Error::InvalidJwsAlgorithm),
325 };
326
327 let mut input = CompositeAlgId::COMPOSITE_SIGNATURE_PREFIX.to_vec();
332
333 input.extend_from_slice(domain);
335
336 input.push(0x00);
338
339 input.extend(jws_encoder.signing_input());
341
342 let signature_t = <K as JwkStorage>::sign(storage.key_storage(), &t_key_id, &input, t_jwk)
343 .await
344 .map_err(Error::KeyStorageError)?;
345
346 let signature_pq = <K as JwkStoragePQ>::pq_sign(storage.key_storage(), &pq_key_id, &input, pq_jwk, Some(domain))
347 .await
348 .map_err(Error::KeyStorageError)?;
349
350 let signature = [signature_t, signature_pq].concat();
351
352 Ok(Jws::new(jws_encoder.into_jws(&signature)))
353 }
354
355 async fn create_credential_jwt_hybrid<K, I, T>(
356 &self,
357 credential: &Credential<T>,
358 storage: &Storage<K, I>,
359 fragment: &str,
360 options: &JwsSignatureOptions,
361 custom_claims: Option<Object>,
362 ) -> StorageResult<Jwt>
363 where
364 K: JwkStorage + JwkStoragePQ,
365 I: KeyIdStorage,
366 T: Clone + Serialize + DeserializeOwned + Sync,
367 {
368 if options.detached_payload {
369 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
370 "cannot use detached payload for credential signing",
371 )));
372 }
373
374 if !options.b64.unwrap_or(true) {
375 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
377 "cannot use `b64 = false` with JWTs",
378 )));
379 }
380
381 let payload = credential
382 .serialize_jwt(custom_claims)
383 .map_err(Error::ClaimsSerializationError)?;
384 self
385 .create_jws(storage, fragment, payload.as_bytes(), options)
386 .await
387 .map(|jws| Jwt::new(jws.into()))
388 }
389
390 async fn create_presentation_jwt_hybrid<K, I, CRED, T>(
391 &self,
392 presentation: &Presentation<CRED, T>,
393 storage: &Storage<K, I>,
394 fragment: &str,
395 jws_options: &JwsSignatureOptions,
396 jwt_options: &JwtPresentationOptions,
397 ) -> StorageResult<Jwt>
398 where
399 K: JwkStorage + JwkStoragePQ,
400 I: KeyIdStorage,
401 T: Clone + Serialize + DeserializeOwned + Sync,
402 CRED: Clone + Serialize + DeserializeOwned + Sync,
403 {
404 if jws_options.detached_payload {
405 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
406 "cannot use detached payload for presentation signing",
407 )));
408 }
409
410 if !jws_options.b64.unwrap_or(true) {
411 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
413 "cannot use `b64 = false` with JWTs",
414 )));
415 }
416 let payload = presentation
417 .serialize_jwt(jwt_options)
418 .map_err(Error::ClaimsSerializationError)?;
419 self
420 .create_jws(storage, fragment, payload.as_bytes(), jws_options)
421 .await
422 .map(|jws| Jwt::new(jws.into()))
423 }
424}
425
426#[cfg(feature = "iota-document")]
430mod iota_document {
431 use crate::StorageResult;
432
433 use super::*;
434 use identity_credential::credential::Jwt;
435 use identity_iota_core::IotaDocument;
436
437 generate_method_hybrid_for_document_type!(IotaDocument, generate_method_hybrid_iota_document);
438
439 #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
440 #[cfg_attr(feature = "send-sync-storage", async_trait)]
441 impl JwkDocumentExtHybrid for IotaDocument {
442 async fn generate_method_hybrid<K, I>(
443 &mut self,
444 storage: &Storage<K, I>,
445 alg_id: CompositeAlgId,
446 fragment: Option<&str>,
447 scope: MethodScope,
448 ) -> StorageResult<String>
449 where
450 K: JwkStorage + JwkStoragePQ,
451 I: KeyIdStorage,
452 {
453 generate_method_hybrid_iota_document(self, storage, alg_id, fragment, scope).await
454 }
455
456 async fn create_jws<K, I>(
457 &self,
458 storage: &Storage<K, I>,
459 fragment: &str,
460 payload: &[u8],
461 options: &JwsSignatureOptions,
462 ) -> StorageResult<Jws>
463 where
464 K: JwkStorage + JwkStoragePQ,
465 I: KeyIdStorage,
466 {
467 self
468 .core_document()
469 .create_jws(storage, fragment, payload, options)
470 .await
471 }
472
473 async fn create_credential_jwt_hybrid<K, I, T>(
474 &self,
475 credential: &Credential<T>,
476 storage: &Storage<K, I>,
477 fragment: &str,
478 options: &JwsSignatureOptions,
479 custom_claims: Option<Object>,
480 ) -> StorageResult<Jwt>
481 where
482 K: JwkStorage + JwkStoragePQ,
483 I: KeyIdStorage,
484 T: Clone + Serialize + DeserializeOwned + Sync,
485 {
486 self
487 .core_document()
488 .create_credential_jwt_hybrid(credential, storage, fragment, options, custom_claims)
489 .await
490 }
491
492 async fn create_presentation_jwt_hybrid<K, I, CRED, T>(
493 &self,
494 presentation: &Presentation<CRED, T>,
495 storage: &Storage<K, I>,
496 fragment: &str,
497 options: &JwsSignatureOptions,
498 jwt_options: &JwtPresentationOptions,
499 ) -> StorageResult<Jwt>
500 where
501 K: JwkStorage + JwkStoragePQ,
502 I: KeyIdStorage,
503 T: Clone + Serialize + DeserializeOwned + Sync,
504 CRED: Clone + Serialize + DeserializeOwned + Sync,
505 {
506 self
507 .core_document()
508 .create_presentation_jwt_hybrid(presentation, storage, fragment, options, jwt_options)
509 .await
510 }
511 }
512}