script/dom/subtlecrypto/ml_dsa_operation.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use der::asn1::{BitString, OctetString};
6use der::{AnyRef, Choice, Decode, Encode, Sequence};
7use js::context::JSContext;
8use ml_dsa::{
9 B32, EncodedVerifyingKey, KeyGen, MlDsa44, MlDsa65, MlDsa87, Signature, VerifyingKey,
10};
11use pkcs8::rand_core::{OsRng, RngCore};
12use pkcs8::spki::AlgorithmIdentifier;
13use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo};
14
15use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
16 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
17};
18use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
19use crate::dom::bindings::error::Error;
20use crate::dom::bindings::root::DomRoot;
21use crate::dom::bindings::str::DOMString;
22use crate::dom::cryptokey::{CryptoKey, Handle};
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::subtlecrypto::{
25 CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives,
26 SubtleAlgorithm, SubtleContextParams, SubtleKeyAlgorithm,
27};
28
29/// Object Identifier (OID) of ML-DSA-44
30/// Section 2 of <https://datatracker.ietf.org/doc/html/rfc9881>
31const ID_ALG_ML_DSA_44: &str = "2.16.840.1.101.3.4.3.17";
32
33/// Object Identifier (OID) of ML-DSA-65
34/// Section 2 of <https://datatracker.ietf.org/doc/html/rfc9881>
35const ID_ALG_ML_DSA_65: &str = "2.16.840.1.101.3.4.3.18";
36
37/// Object Identifier (OID) of ML-DSA-87
38/// Section 2 of <https://datatracker.ietf.org/doc/html/rfc9881>
39const ID_ALG_ML_DSA_87: &str = "2.16.840.1.101.3.4.3.19";
40
41/// Structure in Rust representing the `both` SEQUENCE used in the following ASN.1 structures, as
42/// defined in [RFC 9881 Section 6].
43///
44/// - ASN.1 ML-DSA-44-PrivateKey Structure
45/// - ASN.1 ML-DSA-44-PrivateKey Structure
46/// - ASN.1 ML-DSA-44-PrivateKey Structure
47///
48/// <https://datatracker.ietf.org/doc/html/rfc9881>
49///
50/// ```text
51/// both SEQUENCE {
52/// seed OCTET STRING (SIZE (32)),
53/// expandedKey OCTET STRING (SIZE (2560))
54/// }
55/// ```
56///
57/// ```text
58/// both SEQUENCE {
59/// seed OCTET STRING (SIZE (32)),
60/// expandedKey OCTET STRING (SIZE (4032))
61/// }
62/// ```
63///
64/// ```text
65/// both SEQUENCE {
66/// seed OCTET STRING (SIZE (32)),
67/// expandedKey OCTET STRING (SIZE (4896))
68/// }
69/// ```
70#[derive(Sequence)]
71struct Both {
72 seed: OctetString,
73 expanded_key: OctetString,
74}
75
76/// Structure in Rust representing all the following three structures as defined in
77/// [RFC 9881 Section 6].
78///
79/// - ASN.1 ML-DSA-44-PrivateKey Structure
80/// - ASN.1 ML-DSA-44-PrivateKey Structure
81/// - ASN.1 ML-DSA-44-PrivateKey Structure
82///
83/// <https://datatracker.ietf.org/doc/html/rfc9881>
84///
85/// ```text
86/// ML-DSA-44-PrivateKey ::= CHOICE {
87/// seed [0] OCTET STRING (SIZE (32)),
88/// expandedKey OCTET STRING (SIZE (2560)),
89/// both SEQUENCE {
90/// seed OCTET STRING (SIZE (32)),
91/// expandedKey OCTET STRING (SIZE (2560))
92/// }
93/// }
94/// ```
95///
96/// ```text
97/// ML-DSA-65-PrivateKey ::= CHOICE {
98/// seed [0] OCTET STRING (SIZE (32)),
99/// expandedKey OCTET STRING (SIZE (4032)),
100/// both SEQUENCE {
101/// seed OCTET STRING (SIZE (32)),
102/// expandedKey OCTET STRING (SIZE (4032))
103/// }
104/// }
105/// ```
106///
107/// ```text
108/// ML-DSA-87-PrivateKey ::= CHOICE {
109/// seed [0] OCTET STRING (SIZE (32)),
110/// expandedKey OCTET STRING (SIZE (4896)),
111/// both SEQUENCE {
112/// seed OCTET STRING (SIZE (32)),
113/// expandedKey OCTET STRING (SIZE (4896))
114/// }
115/// }
116/// ```
117#[derive(Choice)]
118enum MlDsaPrivateKeyStructure {
119 #[asn1(context_specific = "0", tag_mode = "IMPLICIT")]
120 Seed(OctetString),
121 ExpandedKey(OctetString),
122 Both(Both),
123}
124
125/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-sign>
126pub(crate) fn sign(
127 normalized_algorithm: &SubtleContextParams,
128 key: &CryptoKey,
129 message: &[u8],
130) -> Result<Vec<u8>, Error> {
131 // Step 1. If the [[type]] internal slot of key is not "private", then throw an
132 // InvalidAccessError.
133 if key.Type() != KeyType::Private {
134 return Err(Error::InvalidAccess(Some(
135 "[[type]] internal slot of key is not \"private\"".to_string(),
136 )));
137 }
138
139 // Step 2. Let context be the context member of normalizedAlgorithm or the empty octet string
140 // if the context member of normalizedAlgorithm is not present.
141 let context = normalized_algorithm.context.as_deref().unwrap_or_default();
142
143 // Step 3. Let result be the result of performing the ML-DSA.Sign signing algorithm, as
144 // specified in Section 5.2 of [FIPS-204], with the parameter set indicated by the name member
145 // of normalizedAlgorithm, using the ML-DSA private key associated with key as sk, message as M
146 // and context as ctx.
147 // Step 4. If the ML-DSA.Sign algorithm returned an error, return an OperationError.
148 let result = match normalized_algorithm.name {
149 CryptoAlgorithm::MlDsa44 => {
150 let Handle::MlDsa44PrivateKey(seed) = key.handle() else {
151 return Err(Error::Operation(Some(
152 "The key handle is not representing an ML-DSA-44 private key".to_string(),
153 )));
154 };
155 let key_pair = MlDsa44::key_gen_internal(seed);
156 let sk = key_pair.signing_key();
157 sk.sign_randomized(message, context, &mut OsRng)
158 .map_err(|_| {
159 Error::Operation(Some("ML-DSA-44 failed to sign the message".to_string()))
160 })?
161 .encode()
162 .to_vec()
163 },
164 CryptoAlgorithm::MlDsa65 => {
165 let Handle::MlDsa65PrivateKey(seed) = key.handle() else {
166 return Err(Error::Operation(Some(
167 "The key handle is not representing an ML-DSA-65 private key".to_string(),
168 )));
169 };
170 let key_pair = MlDsa65::key_gen_internal(seed);
171 let sk = key_pair.signing_key();
172 sk.sign_randomized(message, context, &mut OsRng)
173 .map_err(|_| {
174 Error::Operation(Some("ML-DSA-65 failed to sign the message".to_string()))
175 })?
176 .encode()
177 .to_vec()
178 },
179 CryptoAlgorithm::MlDsa87 => {
180 let Handle::MlDsa87PrivateKey(seed) = key.handle() else {
181 return Err(Error::Operation(Some(
182 "The key handle is not representing an ML-DSA-87 private key".to_string(),
183 )));
184 };
185 let key_pair = MlDsa87::key_gen_internal(seed);
186 let sk = key_pair.signing_key();
187 sk.sign_randomized(message, context, &mut OsRng)
188 .map_err(|_| {
189 Error::Operation(Some("ML-DSA-87 failed to sign the message".to_string()))
190 })?
191 .encode()
192 .to_vec()
193 },
194 _ => {
195 return Err(Error::NotSupported(Some(format!(
196 "{} is not an ML-DSA algorithm",
197 normalized_algorithm.name.as_str()
198 ))));
199 },
200 };
201
202 // Step 5. Return result.
203 Ok(result)
204}
205
206/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-verify>
207pub(crate) fn verify(
208 normalized_algorithm: &SubtleContextParams,
209 key: &CryptoKey,
210 message: &[u8],
211 signature: &[u8],
212) -> Result<bool, Error> {
213 // Step 1. If the [[type]] internal slot of key is not "public", then throw an
214 // InvalidAccessError.
215 if key.Type() != KeyType::Public {
216 return Err(Error::InvalidAccess(Some(
217 "[[type]] internal slot of key is not \"public\"".to_string(),
218 )));
219 }
220
221 // Step 2. Let context be the context member of normalizedAlgorithm or the empty octet string
222 // if the context member of normalizedAlgorithm is not present.
223 let context = normalized_algorithm.context.as_deref().unwrap_or_default();
224
225 // Step 3. Let result be the result of performing the ML-DSA.Verify verification algorithm, as
226 // specified in Section 5.3 of [FIPS-204], with the parameter set indicated by the name member
227 // of normalizedAlgorithm, using the ML-DSA public key associated with key as pk, message as M,
228 // signature as σ and context as ctx.
229 // Step 4. If the ML-DSA.Verify algorithm returned an error, return an OperationError.
230 let result = match normalized_algorithm.name {
231 CryptoAlgorithm::MlDsa44 => {
232 let Handle::MlDsa44PublicKey(public_key) = key.handle() else {
233 return Err(Error::Operation(Some(
234 "The key handle is not representing an ML-DSA-44 public key".to_string(),
235 )));
236 };
237 match Signature::try_from(signature) {
238 Ok(signature) => {
239 let pk = VerifyingKey::<MlDsa44>::decode(public_key);
240 pk.verify_with_context(message, context, &signature)
241 },
242 Err(_) => false,
243 }
244 },
245 CryptoAlgorithm::MlDsa65 => {
246 let Handle::MlDsa65PublicKey(public_key) = key.handle() else {
247 return Err(Error::Operation(Some(
248 "The key handle is not representing an ML-DSA-65 public key".to_string(),
249 )));
250 };
251 match Signature::try_from(signature) {
252 Ok(signature) => {
253 let pk = VerifyingKey::<MlDsa65>::decode(public_key);
254 pk.verify_with_context(message, context, &signature)
255 },
256 Err(_) => false,
257 }
258 },
259 CryptoAlgorithm::MlDsa87 => {
260 let Handle::MlDsa87PublicKey(public_key) = key.handle() else {
261 return Err(Error::Operation(Some(
262 "The key handle is not representing an ML-DSA-87 public key".to_string(),
263 )));
264 };
265 match Signature::try_from(signature) {
266 Ok(signature) => {
267 let pk = VerifyingKey::<MlDsa87>::decode(public_key);
268 pk.verify_with_context(message, context, &signature)
269 },
270 Err(_) => false,
271 }
272 },
273 _ => {
274 return Err(Error::NotSupported(Some(format!(
275 "{} is not an ML-DSA algorithm",
276 normalized_algorithm.name.as_str()
277 ))));
278 },
279 };
280
281 // Step 5. Return result.
282 Ok(result)
283}
284
285/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-generate-key>
286pub(crate) fn generate_key(
287 cx: &mut JSContext,
288 global: &GlobalScope,
289 normalized_algorithm: &SubtleAlgorithm,
290 extractable: bool,
291 usages: Vec<KeyUsage>,
292) -> Result<CryptoKeyPair, Error> {
293 // Step 1. If usages contains a value which is not one of "sign" or "verify", then throw a
294 // SyntaxError.
295 if usages
296 .iter()
297 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
298 {
299 return Err(Error::Syntax(Some(
300 "Usages contains an entry which is not one of \"sign\" or \"verify\"".to_string(),
301 )));
302 }
303
304 // Step 2. Generate an ML-DSA key pair, as described in Section 5.1 of [FIPS-204], with the
305 // parameter set indicated by the name member of normalizedAlgorithm.
306 // Step 3. If the key generation step fails, then throw an OperationError.
307 let mut seed_bytes = vec![0u8; 32];
308 OsRng.fill_bytes(&mut seed_bytes);
309 let (private_key_handle, public_key_handle) =
310 convert_seed_to_handles(normalized_algorithm.name, &seed_bytes, None, None)?;
311
312 // Step 4. Let algorithm be a new KeyAlgorithm object.
313 // Step 5. Set the name attribute of algorithm to the name attribute of normalizedAlgorithm.
314 let algorithm = SubtleKeyAlgorithm {
315 name: normalized_algorithm.name,
316 };
317
318 // Step 6. Let publicKey be a new CryptoKey representing the public key of the generated key
319 // pair.
320 // Step 7. Set the [[type]] internal slot of publicKey to "public".
321 // Step 8. Set the [[algorithm]] internal slot of publicKey to algorithm.
322 // Step 9. Set the [[extractable]] internal slot of publicKey to true.
323 // Step 10. Set the [[usages]] internal slot of publicKey to be the usage intersection of
324 // usages and [ "verify" ].
325 let public_key = CryptoKey::new(
326 cx,
327 global,
328 KeyType::Public,
329 true,
330 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
331 usages
332 .iter()
333 .filter(|usage| **usage == KeyUsage::Verify)
334 .cloned()
335 .collect(),
336 public_key_handle,
337 );
338
339 // Step 11. Let privateKey be a new CryptoKey representing the private key of the generated key
340 // pair.
341 // Step 12. Set the [[type]] internal slot of privateKey to "private".
342 // Step 13. Set the [[algorithm]] internal slot of privateKey to algorithm.
343 // Step 14. Set the [[extractable]] internal slot of privateKey to extractable.
344 // Step 15. Set the [[usages]] internal slot of privateKey to be the usage intersection of
345 // usages and [ "sign" ].
346 let private_key = CryptoKey::new(
347 cx,
348 global,
349 KeyType::Private,
350 extractable,
351 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
352 usages
353 .iter()
354 .filter(|usage| **usage == KeyUsage::Sign)
355 .cloned()
356 .collect(),
357 private_key_handle,
358 );
359
360 // Step 16. Let result be a new CryptoKeyPair dictionary.
361 // Step 17. Set the publicKey attribute of result to be publicKey.
362 // Step 18. Set the privateKey attribute of result to be privateKey.
363 let result = CryptoKeyPair {
364 publicKey: Some(public_key),
365 privateKey: Some(private_key),
366 };
367
368 // Step 19. Return result.
369 Ok(result)
370}
371
372/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-import-key>
373pub(crate) fn import_key(
374 cx: &mut JSContext,
375 global: &GlobalScope,
376 normalized_algorithm: &SubtleAlgorithm,
377 format: KeyFormat,
378 key_data: &[u8],
379 extractable: bool,
380 usages: Vec<KeyUsage>,
381) -> Result<DomRoot<CryptoKey>, Error> {
382 // Step 1. Let keyData be the key data to be imported.
383
384 // Step 2.
385 let key = match format {
386 // If format is "spki":
387 KeyFormat::Spki => {
388 // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
389 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
390 return Err(Error::Syntax(Some(
391 "Usages contains an entry which is not \"verify\"".to_string(),
392 )));
393 }
394
395 // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
396 // algorithm over keyData.
397 // Step 2.3. If an error occurred while parsing, then throw a DataError.
398 let spki =
399 SubjectPublicKeyInfo::<AnyRef, BitString>::from_der(key_data).map_err(|_| {
400 Error::Data(Some(
401 "Failed to parse SubjectPublicKeyInfo over keyData".to_string(),
402 ))
403 })?;
404
405 // Step 2.4.
406 // If the name member of normalizedAlgorithm is "ML-DSA-44":
407 // Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
408 // If the name member of normalizedAlgorithm is "ML-DSA-65":
409 // Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
410 // If the name member of normalizedAlgorithm is "ML-DSA-87":
411 // Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
412 // Otherwise:
413 // throw a NotSupportedError.
414 let expected_oid = match normalized_algorithm.name {
415 CryptoAlgorithm::MlDsa44 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_44),
416 CryptoAlgorithm::MlDsa65 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_65),
417 CryptoAlgorithm::MlDsa87 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_87),
418 _ => {
419 return Err(Error::NotSupported(Some(format!(
420 "{} is not an ML-DSA algorithm",
421 normalized_algorithm.name.as_str()
422 ))));
423 },
424 };
425
426 // Step 2.5. If the algorithm object identifier field of the algorithm
427 // AlgorithmIdentifier field of spki is not equal to expectedOid, then throw a
428 // DataError.
429 if spki.algorithm.oid != expected_oid {
430 return Err(Error::Data(Some(
431 "Algorithm object identifier of spki in not equal to expectedOid".to_string(),
432 )));
433 }
434
435 // Step 2.6. If the parameters field of the algorithm AlgorithmIdentifier field of spki
436 // is present, then throw a DataError.
437 if spki.algorithm.parameters.is_some() {
438 return Err(Error::Data(Some(
439 "Parameters field of spki is present".to_string(),
440 )));
441 }
442
443 // Step 2.7. Let publicKey be the ML-DSA public key identified by the subjectPublicKey
444 // field of spki.
445 let key_bytes = spki.subject_public_key.as_bytes().ok_or(Error::Data(Some(
446 "Failed to parse byte sequence over SubjectPublicKey field of spki".to_string(),
447 )))?;
448 let public_key = convert_public_key_to_handle(normalized_algorithm.name, key_bytes)?;
449
450 // Step 2.8. Let key be a new CryptoKey that represents publicKey.
451 // Step 2.9. Set the [[type]] internal slot of key to "public"
452 // Step 2.10. Let algorithm be a new KeyAlgorithm.
453 // Step 2.11. Set the name attribute of algorithm to the name attribute of
454 // normalizedAlgorithm.
455 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
456 let algorithm = SubtleKeyAlgorithm {
457 name: normalized_algorithm.name,
458 };
459 CryptoKey::new(
460 cx,
461 global,
462 KeyType::Public,
463 extractable,
464 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
465 usages,
466 public_key,
467 )
468 },
469 // If format is "pkcs8":
470 KeyFormat::Pkcs8 => {
471 // Step 2.1. If usages contains a value which is not "sign" then throw a SyntaxError.
472 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
473 return Err(Error::Syntax(Some(
474 "Usages contains an entry which is not \"sign\"".to_string(),
475 )));
476 }
477
478 // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
479 // algorithm over keyData.
480 // Step 2.3. If an error occurs while parsing, then throw a DataError.
481 let private_key_info = PrivateKeyInfo::from_der(key_data).map_err(|_| {
482 Error::Data(Some(
483 "Failed to parse PrivateKeyInfo over keyData".to_string(),
484 ))
485 })?;
486
487 // Step 2.4.
488 // If the name member of normalizedAlgorithm is "ML-DSA-44":
489 // Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
490 // Let asn1Structure be the ASN.1 ML-DSA-44-PrivateKey structure.
491 // If the name member of normalizedAlgorithm is "ML-DSA-65":
492 // Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
493 // Let asn1Structure be the ASN.1 ML-DSA-65-PrivateKey structure.
494 // If the name member of normalizedAlgorithm is "ML-DSA-87":
495 // Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
496 // Let asn1Structure be the ASN.1 ML-DSA-87-PrivateKey structure.
497 // Otherwise:
498 // throw a NotSupportedError.
499 let expected_oid = match normalized_algorithm.name {
500 CryptoAlgorithm::MlDsa44 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_44),
501 CryptoAlgorithm::MlDsa65 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_65),
502 CryptoAlgorithm::MlDsa87 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_87),
503 _ => {
504 return Err(Error::NotSupported(Some(format!(
505 "{} is not an ML-DSA algorithm",
506 normalized_algorithm.name.as_str()
507 ))));
508 },
509 };
510
511 // Step 2.5. If the algorithm object identifier field of the privateKeyAlgorithm
512 // PrivateKeyAlgorithm field of privateKeyInfo is not equal to expectedOid, then throw
513 // a DataError.
514 if private_key_info.algorithm.oid != expected_oid {
515 return Err(Error::Data(Some(
516 "Algorithm object identifier of PrivateKeyInfo is not equal to expectedOid"
517 .to_string(),
518 )));
519 }
520
521 // Step 2.6. If the parameters field of the privateKeyAlgorithm
522 // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
523 // DataError.
524 if private_key_info.algorithm.parameters.is_some() {
525 return Err(Error::Data(Some(
526 "Parameters field of PrivateKeyInfo is present".to_string(),
527 )));
528 }
529
530 // Step 2.7. Let mlDsaPrivateKey be the result of performing the parse an ASN.1
531 // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
532 // as asn1Structure, and exactData set to true.
533 // Step 2.8. If an error occurred while parsing, then throw a DataError.
534 //
535 // NOTE: There is an ongoing discussion on how to handle private keys in the
536 // "expandedKey" and "both" formats.
537 // - <https://github.com/WICG/webcrypto-modern-algos/issues/29>
538 // - <https://github.com/WICG/webcrypto-modern-algos/pull/34>
539 // For now, we accept the "seed" format, reject the "expandedKey" format with a
540 // NotSupportedError, and accept the "both" format subject to a consistency check. This
541 // behavior may change in the future once the discussion is settled.
542 let private_key_structure =
543 MlDsaPrivateKeyStructure::from_der(private_key_info.private_key).map_err(|_| {
544 Error::Data(Some(
545 "Failed to parse privateKey field of PrivateKeyInfo".to_string(),
546 ))
547 })?;
548 let ml_dsa_private_key = match private_key_structure {
549 MlDsaPrivateKeyStructure::Seed(seed) => {
550 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
551 normalized_algorithm.name,
552 seed.as_bytes(),
553 None,
554 None,
555 )?;
556 private_key_handle
557 },
558 MlDsaPrivateKeyStructure::ExpandedKey(_) => {
559 return Err(Error::NotSupported(Some(
560 "Not support \"expandedKey\" format of ASN.1 ML-DSA private key structures"
561 .to_string(),
562 )));
563 },
564 MlDsaPrivateKeyStructure::Both(both) => {
565 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
566 normalized_algorithm.name,
567 both.seed.as_bytes(),
568 Some(both.expanded_key.as_bytes()),
569 None,
570 )?;
571 private_key_handle
572 },
573 };
574
575 // Step 2.9. Let key be a new CryptoKey that represents the ML-DSA private key
576 // identified by mlDsaPrivateKey.
577 // Step 2.10. Set the [[type]] internal slot of key to "private"
578 // Step 2.11. Let algorithm be a new KeyAlgorithm.
579 // Step 2.12. Set the name attribute of algorithm to the name attribute of
580 // normalizedAlgorithm.
581 // Step 2.13. Set the [[algorithm]] internal slot of key to algorithm.
582 let algorithm = SubtleKeyAlgorithm {
583 name: normalized_algorithm.name,
584 };
585 CryptoKey::new(
586 cx,
587 global,
588 KeyType::Private,
589 extractable,
590 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
591 usages,
592 ml_dsa_private_key,
593 )
594 },
595 // If format is "raw-public":
596 KeyFormat::Raw_public => {
597 // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
598 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
599 return Err(Error::Syntax(Some(
600 "Usages contains an entry which is not \"verify\"".to_string(),
601 )));
602 }
603
604 // Step 2.2. Let algorithm be a new KeyAlgorithm object.
605 // Step 2.3. Set the name attribute of algorithm to the name attribute of
606 // normalizedAlgorithm.
607 // Step 2.4. Let key be a new CryptoKey representing the key data provided in keyData.
608 // Step 2.5. Set the [[type]] internal slot of key to "public"
609 // Step 2.6. Set the [[algorithm]] internal slot of key to algorithm.
610 let public_key_handle =
611 convert_public_key_to_handle(normalized_algorithm.name, key_data)?;
612 let algorithm = SubtleKeyAlgorithm {
613 name: normalized_algorithm.name,
614 };
615 CryptoKey::new(
616 cx,
617 global,
618 KeyType::Public,
619 extractable,
620 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
621 usages,
622 public_key_handle,
623 )
624 },
625 // If format is "raw-seed":
626 KeyFormat::Raw_seed => {
627 // Step 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError.
628 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
629 return Err(Error::Syntax(Some(
630 "Usages contains an entry which is not \"sign\"".to_string(),
631 )));
632 }
633
634 // Step 2.2. Let data be keyData.
635 let data = key_data;
636
637 // Step 2.3. If the length in bits of data is not 256 then throw a DataError.
638 // Step 2.4. Let privateKey be the result of performing the ML-DSA.KeyGen_internal
639 // function described in Section 6.1 of [FIPS-204] with the parameter set indicated by
640 // the name member of normalizedAlgorithm, using data as ξ.
641 let (private_key_handle, _public_key_handle) =
642 convert_seed_to_handles(normalized_algorithm.name, data, None, None)?;
643
644 // Step 2.5. Let key be a new CryptoKey that represents the ML-DSA private key
645 // identified by privateKey.
646 // Step 2.6. Set the [[type]] internal slot of key to "private"
647 // Step 2.7. Let algorithm be a new KeyAlgorithm.
648 // Step 2.8. Set the name attribute of algorithm to the name attribute of
649 // normalizedAlgorithm.
650 // Step 2.9. Set the [[algorithm]] internal slot of key to algorithm.
651 let algorithm = SubtleKeyAlgorithm {
652 name: normalized_algorithm.name,
653 };
654 CryptoKey::new(
655 cx,
656 global,
657 KeyType::Private,
658 extractable,
659 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
660 usages,
661 private_key_handle,
662 )
663 },
664 // If format is "jwk":
665 KeyFormat::Jwk => {
666 // Step 2.1.
667 // If keyData is a JsonWebKey dictionary:
668 // Let jwk equal keyData.
669 // Otherwise:
670 // Throw a DataError.
671 let jwk = JsonWebKey::parse(cx, key_data)?;
672
673 // Step 2.2. If the priv field is present and usages contains a value which is not
674 // "sign", or, if the priv field is not present and usages contains a value which is
675 // not "verify" then throw a SyntaxError.
676 if jwk.priv_.is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign) {
677 return Err(Error::Syntax(Some(
678 "The priv field is present and usages contains a value which is not \"sign\""
679 .to_string(),
680 )));
681 }
682 if jwk.priv_.is_none() && usages.iter().any(|usage| *usage != KeyUsage::Verify) {
683 return Err(Error::Syntax(Some(
684 "The priv field is not present and usages contains a value which is not \
685 \"verify\""
686 .to_string(),
687 )));
688 }
689
690 // Step 2.3. If the kty field of jwk is not "AKP", then throw a DataError.
691 if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
692 return Err(Error::Data(Some(
693 "The kty field of jwk is not \"AKP\"".to_string(),
694 )));
695 }
696
697 // Step 2.4. If the alg field of jwk is not equal to the name member of
698 // normalizedAlgorithm, then throw a DataError.
699 if jwk
700 .alg
701 .as_ref()
702 .is_none_or(|alg| alg != normalized_algorithm.name.as_str())
703 {
704 return Err(Error::Data(Some(
705 "The alg field of jwk is not equal to the name member of normalizedAlgorithm"
706 .to_string(),
707 )));
708 }
709
710 // Step 2.5. If usages is non-empty and the use field of jwk is present and is not
711 // equal to "sig", then throw a DataError.
712 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
713 return Err(Error::Data(Some(
714 "Usages is non-empty and the use field of jwk is present and is not \
715 equal to \"sig\""
716 .to_string(),
717 )));
718 }
719
720 // Step 2.6. If the key_ops field of jwk is present, and is invalid according to the
721 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
722 // usages values, then throw a DataError.
723 jwk.check_key_ops(&usages)?;
724
725 // Step 2.7. If the ext field of jwk is present and has the value false and extractable
726 // is true, then throw a DataError.
727 if jwk.ext.is_some_and(|ext| !ext) && extractable {
728 return Err(Error::Data(Some(
729 "The ext field of jwk is present and has the value false and extractable \
730 is true"
731 .to_string(),
732 )));
733 }
734
735 // Step 2.8.
736 // If the priv field of jwk is present:
737 if jwk.priv_.is_some() {
738 // Step 2.8.1. If the priv attribute of jwk does not contain a valid base64url
739 // encoded seed representing an ML-DSA private key, then throw a DataError.
740 let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
741
742 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA private
743 // key identified by interpreting the priv attribute of jwk as a base64url encoded
744 // seed.
745 // Step 2.8.3. Set the [[type]] internal slot of key to "private".
746 // Step 2.8.4. If the pub attribute of jwk does not contain the base64url encoded
747 // public key representing the ML-DSA public key corresponding to key, then throw a
748 // DataError.
749 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
750 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
751 normalized_algorithm.name,
752 &priv_bytes,
753 None,
754 Some(&pub_bytes),
755 )?;
756 let algorithm = SubtleKeyAlgorithm {
757 name: normalized_algorithm.name,
758 };
759 CryptoKey::new(
760 cx,
761 global,
762 KeyType::Private,
763 extractable,
764 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
765 usages,
766 private_key_handle,
767 )
768 }
769 // Otherwise:
770 else {
771 // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
772 // encoded public key representing an ML-DSA public key, then throw a DataError.
773 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
774
775 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
776 // key identified by interpreting the pub attribute of jwk as a base64url encoded
777 // public key.
778 // Step 2.8.3. Set the [[type]] internal slot of key to "public".
779 let public_key_handle =
780 convert_public_key_to_handle(normalized_algorithm.name, &pub_bytes)?;
781 let algorithm = SubtleKeyAlgorithm {
782 name: normalized_algorithm.name,
783 };
784 CryptoKey::new(
785 cx,
786 global,
787 KeyType::Public,
788 extractable,
789 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
790 usages,
791 public_key_handle,
792 )
793 }
794 },
795 // Otherwise:
796 _ => {
797 // throw a NotSupportedError.
798 return Err(Error::NotSupported(Some(
799 "Unsupported import key format for ML-DSA key".to_string(),
800 )));
801 },
802 };
803
804 // Step 3. Return key.
805 Ok(key)
806}
807
808/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
809pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
810 // Step 1. Let key be the CryptoKey to be exported.
811
812 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
813 // slot of key cannot be accessed, then throw an OperationError.
814
815 // Step 3.
816 let result = match format {
817 KeyFormat::Spki => {
818 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
819 // InvalidAccessError.
820 if key.Type() != KeyType::Public {
821 return Err(Error::InvalidAccess(Some(
822 "[[type]] internal slot of key is not \"public\"".to_string(),
823 )));
824 }
825
826 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
827 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
828 return Err(Error::Operation(Some(
829 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
830 )));
831 };
832
833 // Step 3.3.
834 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
835 // [RFC5280] with the following properties:
836 //
837 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
838 // properties:
839 //
840 // If the name member of keyAlgorithm is "ML-DSA-44":
841 // Set the algorithm object identifier to the id-ml-dsa-44
842 // (2.16.840.1.101.3.4.3.17) OID.
843 //
844 // If the name member of keyAlgorithm is "ML-DSA-65":
845 // Set the algorithm object identifier to the id-ml-dsa-65
846 // (2.16.840.1.101.3.4.3.18) OID.
847 //
848 // If the name member of keyAlgorithm is "ML-DSA-87":
849 // Set the algorithm object identifier to the id-ml-dsa-87
850 // (2.16.840.1.101.3.4.3.19) OID.
851 //
852 // Otherwise:
853 // throw a NotSupportedError.
854 //
855 // Set the subjectPublicKey field to keyData.
856 let oid = match key_algorithm.name {
857 CryptoAlgorithm::MlDsa44 => ID_ALG_ML_DSA_44,
858 CryptoAlgorithm::MlDsa65 => ID_ALG_ML_DSA_65,
859 CryptoAlgorithm::MlDsa87 => ID_ALG_ML_DSA_87,
860 _ => {
861 return Err(Error::Operation(Some(format!(
862 "{} is not an ML-DSA algorithm",
863 key_algorithm.name.as_str()
864 ))));
865 },
866 };
867 let key_bytes = convert_handle_to_public_key(key.handle())?;
868 let subject_public_key = BitString::from_bytes(&key_bytes).map_err(|_| {
869 Error::Operation(Some(
870 "Failed to encode BitString for subjectPublicKey field of SubjectPublicKeyInfo"
871 .to_string(),
872 ))
873 })?;
874 let data = SubjectPublicKeyInfo {
875 algorithm: AlgorithmIdentifier::<AnyRef> {
876 oid: ObjectIdentifier::new_unwrap(oid),
877 parameters: None,
878 },
879 subject_public_key,
880 };
881
882 // Step 3.4. Let result be the result of DER-encoding data.
883 ExportedKey::Bytes(data.to_der().map_err(|_| {
884 Error::Operation(Some(
885 "Failed to encode SubjectPublicKeyInfo in DER format".to_string(),
886 ))
887 })?)
888 },
889 KeyFormat::Pkcs8 => {
890 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
891 // InvalidAccessError.
892 if key.Type() != KeyType::Private {
893 return Err(Error::InvalidAccess(Some(
894 "[[type]] internal slot of key is not \"private\"".to_string(),
895 )));
896 }
897
898 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
899 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
900 return Err(Error::Operation(Some(
901 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
902 )));
903 };
904
905 // Step 3.3.
906 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
907 // with the following properties:
908 //
909 // Set the version field to 0.
910 //
911 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
912 // with the following properties:
913 //
914 // If the name member of keyAlgorithm is "ML-DSA-44":
915 // Set the algorithm object identifier to the id-ml-dsa-44
916 // (2.16.840.1.101.3.4.3.17) OID.
917 //
918 // If the name member of keyAlgorithm is "ML-DSA-65":
919 // Set the algorithm object identifier to the id-ml-dsa-65
920 // (2.16.840.1.101.3.4.3.18) OID.
921 //
922 // If the name member of keyAlgorithm is "ML-DSA-87":
923 // Set the algorithm object identifier to the id-ml-dsa-87
924 // (2.16.840.1.101.3.4.3.19) OID.
925 //
926 // Otherwise:
927 // throw a NotSupportedError.
928 //
929 // Set the privateKey field as follows:
930 //
931 // If the name member of keyAlgorithm is "ML-DSA-44":
932 // Set the privateKey field to the result of DER-encoding a
933 // ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
934 // seed represented by the [[handle]] internal slot of key using the
935 // seed-only format (using a context-specific [0] primitive tag with an
936 // implicit encoding of OCTET STRING).
937 //
938 // If the name member of keyAlgorithm is "ML-DSA-65":
939 // Set the privateKey field to the result of DER-encoding a
940 // ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
941 // seed represented by the [[handle]] internal slot of key using the
942 // seed-only format (using a context-specific [0] primitive tag with an
943 // implicit encoding of OCTET STRING).
944 //
945 // If the name member of keyAlgorithm is "ML-DSA-87":
946 // Set the privateKey field to the result of DER-encoding a
947 // ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
948 // seed represented by the [[handle]] internal slot of key using the
949 // seed-only format (using a context-specific [0] primitive tag with an
950 // implicit encoding of OCTET STRING).
951 //
952 // Otherwise:
953 // throw a NotSupportedError.
954 let oid = match key_algorithm.name {
955 CryptoAlgorithm::MlDsa44 => ID_ALG_ML_DSA_44,
956 CryptoAlgorithm::MlDsa65 => ID_ALG_ML_DSA_65,
957 CryptoAlgorithm::MlDsa87 => ID_ALG_ML_DSA_87,
958 _ => {
959 return Err(Error::Operation(Some(format!(
960 "{} is not an ML-DSA algorithm",
961 key_algorithm.name.as_str()
962 ))));
963 },
964 };
965 let (seed_bytes, _public_key_bytes) =
966 convert_handle_to_seed_and_public_key(key.handle())?;
967 let private_key =
968 MlDsaPrivateKeyStructure::Seed(OctetString::new(seed_bytes).map_err(|_| {
969 Error::Operation(Some(
970 "Failed to encode OctetString for privateKey field of \
971 ASN.1 ML-DSA private key structure"
972 .to_string(),
973 ))
974 })?);
975 let encoded_private_key = private_key.to_der().map_err(|_| {
976 Error::Operation(Some(
977 "Failed to encode ASN.1 ML-DSA private key structure in DER format".to_string(),
978 ))
979 })?;
980 let private_key_info = PrivateKeyInfo {
981 algorithm: AlgorithmIdentifier {
982 oid: ObjectIdentifier::new_unwrap(oid),
983 parameters: None,
984 },
985 private_key: &encoded_private_key,
986 public_key: None,
987 };
988
989 // Step 3.4. Let result be the result of DER-encoding data.
990 ExportedKey::Bytes(private_key_info.to_der().map_err(|_| {
991 Error::Operation(Some(
992 "Failed to encode PrivateKeyInfo in DER format".to_string(),
993 ))
994 })?)
995 },
996 // If format is "raw-public":
997 KeyFormat::Raw_public => {
998 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
999 // InvalidAccessError.
1000 if key.Type() != KeyType::Public {
1001 return Err(Error::InvalidAccess(Some(
1002 "[[type]] internal slot of key is not \"public\"".to_string(),
1003 )));
1004 }
1005
1006 // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1007 // by the [[handle]] internal slot of key.
1008 let data = convert_handle_to_public_key(key.handle())?;
1009
1010 // Step 3.2. Let result be data.
1011 ExportedKey::Bytes(data)
1012 },
1013 // If format is "raw-seed":
1014 KeyFormat::Raw_seed => {
1015 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1016 // InvalidAccessError.
1017 if key.Type() != KeyType::Private {
1018 return Err(Error::InvalidAccess(Some(
1019 "[[type]] internal slot of key is not \"private\"".to_string(),
1020 )));
1021 }
1022
1023 // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1024 // represented by the [[handle]] internal slot of key.
1025 let (data, _public_key_bytes) = convert_handle_to_seed_and_public_key(key.handle())?;
1026
1027 // Step 3.3. Let result be data.
1028 ExportedKey::Bytes(data)
1029 },
1030 // If format is "jwk":
1031 KeyFormat::Jwk => {
1032 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1033 let mut jwk = JsonWebKey::default();
1034
1035 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
1036 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1037 return Err(Error::Operation(Some(
1038 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1039 )));
1040 };
1041
1042 // Step 3.3. Set the kty attribute of jwk to "AKP".
1043 jwk.kty = Some(DOMString::from("AKP"));
1044
1045 // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1046 jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1047
1048 // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1049 // corresponding to the [[handle]] internal slot of key.
1050 // Step 3.6.
1051 // If the [[type]] internal slot of key is "private":
1052 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1053 // [[handle]] internal slot of key.
1054 if key.Type() == KeyType::Private {
1055 let (seed_bytes, public_key_bytes) =
1056 convert_handle_to_seed_and_public_key(key.handle())?;
1057 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1058 jwk.encode_string_field(JwkStringField::Priv, &seed_bytes);
1059 } else {
1060 let public_key_bytes = convert_handle_to_public_key(key.handle())?;
1061 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1062 }
1063
1064 // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1065 jwk.set_key_ops(key.usages());
1066
1067 // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1068 jwk.ext = Some(key.Extractable());
1069
1070 // Step 3.9. Let result be jwk.
1071 ExportedKey::Jwk(Box::new(jwk))
1072 },
1073 // Otherwise:
1074 _ => {
1075 // throw a NotSupportedError.
1076 return Err(Error::NotSupported(Some(
1077 "Unsupported export key format for ML-DSA key".to_string(),
1078 )));
1079 },
1080 };
1081
1082 // Step 3. Return result.
1083 Ok(result)
1084}
1085
1086/// Convert seed bytes to an ML-DSA private key handle and an ML-DSA public key handle. If private
1087/// key bytes and/or public key bytes are provided, it runs a consistency check against the seed.
1088/// If the length in bits of seed bytes is not 256, the conversion fails, or the consistency check
1089/// fails, throw a DataError.
1090fn convert_seed_to_handles(
1091 algorithm_name: CryptoAlgorithm,
1092 seed_bytes: &[u8],
1093 private_key_bytes: Option<&[u8]>,
1094 public_key_bytes: Option<&[u8]>,
1095) -> Result<(Handle, Handle), Error> {
1096 if seed_bytes.len() != 32 {
1097 return Err(Error::Data(Some(
1098 "The length in bits of seed bytes is not 256".to_string(),
1099 )));
1100 }
1101
1102 let seed: B32 = seed_bytes
1103 .try_into()
1104 .map_err(|_| Error::Data(Some("Failed to parse the seed bytes".to_string())))?;
1105 let handles = match algorithm_name {
1106 CryptoAlgorithm::MlDsa44 => {
1107 let key_pair = MlDsa44::key_gen_internal(&seed);
1108 if let Some(private_key_bytes) = private_key_bytes {
1109 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1110 return Err(Error::Data(Some(
1111 "The expanded private key does not match the seed".to_string(),
1112 )));
1113 }
1114 }
1115 if let Some(public_key_bytes) = public_key_bytes {
1116 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1117 return Err(Error::Data(Some(
1118 "The public key does not match the seed".to_string(),
1119 )));
1120 }
1121 }
1122
1123 (
1124 Handle::MlDsa44PrivateKey(seed),
1125 Handle::MlDsa44PublicKey(Box::new(key_pair.verifying_key().encode())),
1126 )
1127 },
1128 CryptoAlgorithm::MlDsa65 => {
1129 let key_pair = MlDsa65::key_gen_internal(&seed);
1130 if let Some(private_key_bytes) = private_key_bytes {
1131 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1132 return Err(Error::Data(Some(
1133 "The expanded private key does not match the seed".to_string(),
1134 )));
1135 }
1136 }
1137 if let Some(public_key_bytes) = public_key_bytes {
1138 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1139 return Err(Error::Data(Some(
1140 "The public key does not match the seed".to_string(),
1141 )));
1142 }
1143 }
1144
1145 (
1146 Handle::MlDsa65PrivateKey(seed),
1147 Handle::MlDsa65PublicKey(Box::new(key_pair.verifying_key().encode())),
1148 )
1149 },
1150 CryptoAlgorithm::MlDsa87 => {
1151 let key_pair = MlDsa87::key_gen_internal(&seed);
1152 if let Some(private_key_bytes) = private_key_bytes {
1153 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1154 return Err(Error::Data(Some(
1155 "The expanded private key does not match the seed".to_string(),
1156 )));
1157 }
1158 }
1159 if let Some(public_key_bytes) = public_key_bytes {
1160 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1161 return Err(Error::Data(Some(
1162 "The public key does not match the seed".to_string(),
1163 )));
1164 }
1165 }
1166
1167 (
1168 Handle::MlDsa87PrivateKey(seed),
1169 Handle::MlDsa87PublicKey(Box::new(key_pair.verifying_key().encode())),
1170 )
1171 },
1172 _ => {
1173 return Err(Error::NotSupported(Some(format!(
1174 "{} is not an ML-DSA algorithm",
1175 algorithm_name.as_str()
1176 ))));
1177 },
1178 };
1179
1180 Ok(handles)
1181}
1182
1183/// Convert public key bytes to an ML-DSA public key handle. If the conversion fails, throw a
1184/// DataError.
1185fn convert_public_key_to_handle(
1186 algorithm_name: CryptoAlgorithm,
1187 public_key_bytes: &[u8],
1188) -> Result<Handle, Error> {
1189 let public_key_handle = match algorithm_name {
1190 CryptoAlgorithm::MlDsa44 => {
1191 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa44>::try_from(public_key_bytes)
1192 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1193 Handle::MlDsa44PublicKey(Box::new(encoded_verifying_key))
1194 },
1195 CryptoAlgorithm::MlDsa65 => {
1196 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa65>::try_from(public_key_bytes)
1197 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1198 Handle::MlDsa65PublicKey(Box::new(encoded_verifying_key))
1199 },
1200 CryptoAlgorithm::MlDsa87 => {
1201 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa87>::try_from(public_key_bytes)
1202 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1203 Handle::MlDsa87PublicKey(Box::new(encoded_verifying_key))
1204 },
1205 _ => {
1206 return Err(Error::NotSupported(Some(format!(
1207 "{} is not an ML-DSA algorithm",
1208 algorithm_name.as_str()
1209 ))));
1210 },
1211 };
1212
1213 Ok(public_key_handle)
1214}
1215
1216/// Convert an ML-DSA private key handle to seed bytes and public key bytes. If the handle is not
1217/// representing a ML-DSA private key, throw an OperationError.
1218fn convert_handle_to_seed_and_public_key(handle: &Handle) -> Result<(Vec<u8>, Vec<u8>), Error> {
1219 let result = match handle {
1220 Handle::MlDsa44PrivateKey(seed) => {
1221 let key_pair = MlDsa44::key_gen_internal(seed);
1222 (
1223 seed.to_vec(),
1224 key_pair.verifying_key().encode().as_slice().to_vec(),
1225 )
1226 },
1227 Handle::MlDsa65PrivateKey(seed) => {
1228 let key_pair = MlDsa65::key_gen_internal(seed);
1229 (
1230 seed.to_vec(),
1231 key_pair.verifying_key().encode().as_slice().to_vec(),
1232 )
1233 },
1234 Handle::MlDsa87PrivateKey(seed) => {
1235 let key_pair = MlDsa87::key_gen_internal(seed);
1236 (
1237 seed.to_vec(),
1238 key_pair.verifying_key().encode().as_slice().to_vec(),
1239 )
1240 },
1241 _ => {
1242 return Err(Error::Operation(Some(
1243 "The key handle is not representing an ML-DSA private key".to_string(),
1244 )));
1245 },
1246 };
1247
1248 Ok(result)
1249}
1250
1251/// Convert an ML-DSA public key handle to public key bytes. If the handle is not representing a
1252/// ML-DSA public key, throw an OperationError.
1253fn convert_handle_to_public_key(handle: &Handle) -> Result<Vec<u8>, Error> {
1254 let result = match handle {
1255 Handle::MlDsa44PublicKey(public_key) => public_key.to_vec(),
1256 Handle::MlDsa65PublicKey(public_key) => public_key.as_slice().to_vec(),
1257 Handle::MlDsa87PublicKey(public_key) => public_key.to_vec(),
1258 _ => {
1259 return Err(Error::Operation(Some(
1260 "The key handle is not representing an ML-DSA public key".to_string(),
1261 )));
1262 },
1263 };
1264
1265 Ok(result)
1266}