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 ml_dsa::{
8 B32, EncodedVerifyingKey, KeyGen, MlDsa44, MlDsa65, MlDsa87, Signature, VerifyingKey,
9};
10use pkcs8::rand_core::{OsRng, RngCore};
11use pkcs8::spki::AlgorithmIdentifier;
12use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo};
13
14use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
15 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
16};
17use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
18use crate::dom::bindings::error::Error;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::bindings::str::DOMString;
21use crate::dom::cryptokey::{CryptoKey, Handle};
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::subtlecrypto::{
24 ALG_ML_DSA_44, ALG_ML_DSA_65, ALG_ML_DSA_87, ExportedKey, JsonWebKeyExt, JwkStringField,
25 KeyAlgorithmAndDerivatives, SubtleAlgorithm, SubtleContextParams, SubtleKeyAlgorithm,
26};
27use crate::script_runtime::CanGc;
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.as_str() {
149 ALG_ML_DSA_44 => {
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 ALG_ML_DSA_65 => {
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 ALG_ML_DSA_87 => {
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.as_str() {
231 ALG_ML_DSA_44 => {
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 ALG_ML_DSA_65 => {
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 ALG_ML_DSA_87 => {
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 global: &GlobalScope,
288 normalized_algorithm: &SubtleAlgorithm,
289 extractable: bool,
290 usages: Vec<KeyUsage>,
291 can_gc: CanGc,
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.clone(),
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 global,
327 KeyType::Public,
328 true,
329 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
330 usages
331 .iter()
332 .filter(|usage| **usage == KeyUsage::Verify)
333 .cloned()
334 .collect(),
335 public_key_handle,
336 can_gc,
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 global,
348 KeyType::Private,
349 extractable,
350 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
351 usages
352 .iter()
353 .filter(|usage| **usage == KeyUsage::Sign)
354 .cloned()
355 .collect(),
356 private_key_handle,
357 can_gc,
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 global: &GlobalScope,
375 normalized_algorithm: &SubtleAlgorithm,
376 format: KeyFormat,
377 key_data: &[u8],
378 extractable: bool,
379 usages: Vec<KeyUsage>,
380 can_gc: CanGc,
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.as_str() {
415 ALG_ML_DSA_44 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_44),
416 ALG_ML_DSA_65 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_65),
417 ALG_ML_DSA_87 => 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.clone(),
458 };
459 CryptoKey::new(
460 global,
461 KeyType::Public,
462 extractable,
463 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
464 usages,
465 public_key,
466 can_gc,
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.as_str() {
500 ALG_ML_DSA_44 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_44),
501 ALG_ML_DSA_65 => ObjectIdentifier::new_unwrap(ID_ALG_ML_DSA_65),
502 ALG_ML_DSA_87 => 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.clone(),
584 };
585 CryptoKey::new(
586 global,
587 KeyType::Private,
588 extractable,
589 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
590 usages,
591 ml_dsa_private_key,
592 can_gc,
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.clone(),
614 };
615 CryptoKey::new(
616 global,
617 KeyType::Public,
618 extractable,
619 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
620 usages,
621 public_key_handle,
622 can_gc,
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.clone(),
653 };
654 CryptoKey::new(
655 global,
656 KeyType::Private,
657 extractable,
658 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
659 usages,
660 private_key_handle,
661 can_gc,
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(GlobalScope::get_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.clone(),
758 };
759 CryptoKey::new(
760 global,
761 KeyType::Private,
762 extractable,
763 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
764 usages,
765 private_key_handle,
766 can_gc,
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.clone(),
783 };
784 CryptoKey::new(
785 global,
786 KeyType::Public,
787 extractable,
788 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
789 usages,
790 public_key_handle,
791 can_gc,
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>
809///
810/// The exportKey() method does not involve AlgorithmIdentifier and algorithm normalization, so
811/// there should not be normalizedAlgorithm in the export key operation. It could be a mistake in
812/// the specification (Related issue: <https://github.com/WICG/webcrypto-modern-algos/issues/47>).
813///
814/// In our implementation, we use the name attribute of the [[algorithhm]] internal slot of key to
815/// determine the security category.
816pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
817 // Step 1. Let key be the CryptoKey to be exported.
818
819 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
820 // slot of key cannot be accessed, then throw an OperationError.
821
822 // Step 3.
823 let result = match format {
824 KeyFormat::Spki => {
825 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
826 // InvalidAccessError.
827 if key.Type() != KeyType::Public {
828 return Err(Error::InvalidAccess(Some(
829 "[[type]] internal slot of key is not \"public\"".to_string(),
830 )));
831 }
832
833 // Step 3.2.
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 normalizedAlgorithm 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 normalizedAlgorithm 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 normalizedAlgorithm 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 KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
857 return Err(Error::Operation(Some(
858 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
859 )));
860 };
861 let oid = match key_algorithm.name.as_str() {
862 ALG_ML_DSA_44 => ID_ALG_ML_DSA_44,
863 ALG_ML_DSA_65 => ID_ALG_ML_DSA_65,
864 ALG_ML_DSA_87 => ID_ALG_ML_DSA_87,
865 _ => {
866 return Err(Error::Operation(Some(format!(
867 "{} is not an ML-DSA algorithm",
868 key_algorithm.name.as_str()
869 ))));
870 },
871 };
872 let key_bytes = convert_handle_to_public_key(key.handle())?;
873 let subject_public_key = BitString::from_bytes(&key_bytes).map_err(|_| {
874 Error::Operation(Some(
875 "Failed to encode BitString for subjectPublicKey field of SubjectPublicKeyInfo"
876 .to_string(),
877 ))
878 })?;
879 let data = SubjectPublicKeyInfo {
880 algorithm: AlgorithmIdentifier::<AnyRef> {
881 oid: ObjectIdentifier::new_unwrap(oid),
882 parameters: None,
883 },
884 subject_public_key,
885 };
886
887 // Step 3.3. Let result be the result of DER-encoding data.
888 ExportedKey::Bytes(data.to_der().map_err(|_| {
889 Error::Operation(Some(
890 "Failed to encode SubjectPublicKeyInfo in DER format".to_string(),
891 ))
892 })?)
893 },
894 KeyFormat::Pkcs8 => {
895 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
896 // InvalidAccessError.
897 if key.Type() != KeyType::Private {
898 return Err(Error::InvalidAccess(Some(
899 "[[type]] internal slot of key is not \"private\"".to_string(),
900 )));
901 }
902
903 // Step 3.2.
904 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
905 // with the following properties:
906 //
907 // Set the version field to 0.
908 //
909 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
910 // with the following properties:
911 //
912 // If the name member of normalizedAlgorithm is "ML-DSA-44":
913 // Set the algorithm object identifier to the id-ml-dsa-44
914 // (2.16.840.1.101.3.4.3.17) OID.
915 //
916 // If the name member of normalizedAlgorithm is "ML-DSA-65":
917 // Set the algorithm object identifier to the id-ml-dsa-65
918 // (2.16.840.1.101.3.4.3.18) OID.
919 //
920 // If the name member of normalizedAlgorithm is "ML-DSA-87":
921 // Set the algorithm object identifier to the id-ml-dsa-87
922 // (2.16.840.1.101.3.4.3.19) OID.
923 //
924 // Otherwise:
925 // throw a NotSupportedError.
926 //
927 // Set the privateKey field as follows:
928 //
929 // If the name member of normalizedAlgorithm is "ML-DSA-44":
930 // Set the privateKey field to the result of DER-encoding a
931 // ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
932 // seed represented by the [[handle]] internal slot of key using the
933 // seed-only format (using a context-specific [0] primitive tag with an
934 // implicit encoding of OCTET STRING).
935 //
936 // If the name member of normalizedAlgorithm is "ML-DSA-65":
937 // Set the privateKey field to the result of DER-encoding a
938 // ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
939 // seed represented by the [[handle]] internal slot of key using the
940 // seed-only format (using a context-specific [0] primitive tag with an
941 // implicit encoding of OCTET STRING).
942 //
943 // If the name member of normalizedAlgorithm is "ML-DSA-87":
944 // Set the privateKey field to the result of DER-encoding a
945 // ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
946 // seed represented by the [[handle]] internal slot of key using the
947 // seed-only format (using a context-specific [0] primitive tag with an
948 // implicit encoding of OCTET STRING).
949 //
950 // Otherwise:
951 // throw a NotSupportedError.
952 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
953 return Err(Error::Operation(Some(
954 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
955 )));
956 };
957 let oid = match key_algorithm.name.as_str() {
958 ALG_ML_DSA_44 => ID_ALG_ML_DSA_44,
959 ALG_ML_DSA_65 => ID_ALG_ML_DSA_65,
960 ALG_ML_DSA_87 => ID_ALG_ML_DSA_87,
961 _ => {
962 return Err(Error::Operation(Some(format!(
963 "{} is not an ML-DSA algorithm",
964 key_algorithm.name.as_str()
965 ))));
966 },
967 };
968 let (seed_bytes, _public_key_bytes) =
969 convert_handle_to_seed_and_public_key(key.handle())?;
970 let private_key =
971 MlDsaPrivateKeyStructure::Seed(OctetString::new(seed_bytes).map_err(|_| {
972 Error::Operation(Some(
973 "Failed to encode OctetString for privateKey field of \
974 ASN.1 ML-DSA private key structure"
975 .to_string(),
976 ))
977 })?);
978 let encoded_private_key = private_key.to_der().map_err(|_| {
979 Error::Operation(Some(
980 "Failed to encode ASN.1 ML-DSA private key structure in DER format".to_string(),
981 ))
982 })?;
983 let private_key_info = PrivateKeyInfo {
984 algorithm: AlgorithmIdentifier {
985 oid: ObjectIdentifier::new_unwrap(oid),
986 parameters: None,
987 },
988 private_key: &encoded_private_key,
989 public_key: None,
990 };
991
992 // Step 3.3. Let result be the result of DER-encoding data.
993 ExportedKey::Bytes(private_key_info.to_der().map_err(|_| {
994 Error::Operation(Some(
995 "Failed to encode PrivateKeyInfo in DER format".to_string(),
996 ))
997 })?)
998 },
999 // If format is "raw-public":
1000 KeyFormat::Raw_public => {
1001 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
1002 // InvalidAccessError.
1003 if key.Type() != KeyType::Public {
1004 return Err(Error::InvalidAccess(Some(
1005 "[[type]] internal slot of key is not \"public\"".to_string(),
1006 )));
1007 }
1008
1009 // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1010 // by the [[handle]] internal slot of key.
1011 let data = convert_handle_to_public_key(key.handle())?;
1012
1013 // Step 3.2. Let result be data.
1014 ExportedKey::Bytes(data)
1015 },
1016 // If format is "raw-seed":
1017 KeyFormat::Raw_seed => {
1018 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1019 // InvalidAccessError.
1020 if key.Type() != KeyType::Private {
1021 return Err(Error::InvalidAccess(Some(
1022 "[[type]] internal slot of key is not \"private\"".to_string(),
1023 )));
1024 }
1025
1026 // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1027 // represented by the [[handle]] internal slot of key.
1028 let (data, _public_key_bytes) = convert_handle_to_seed_and_public_key(key.handle())?;
1029
1030 // Step 3.3. Let result be data.
1031 ExportedKey::Bytes(data)
1032 },
1033 // If format is "jwk":
1034 KeyFormat::Jwk => {
1035 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1036 // Step 3.2. Set the kty attribute of jwk to "AKP".
1037 // Step 3.3. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1038 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1039 return Err(Error::Operation(Some(
1040 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1041 )));
1042 };
1043 let mut jwk = JsonWebKey {
1044 kty: Some(DOMString::from("AKP")),
1045 alg: Some(DOMString::from(key_algorithm.name.as_str())),
1046 ..Default::default()
1047 };
1048
1049 // Step 3.4. Set the pub attribute of jwk to the base64url encoded public key
1050 // corresponding to the [[handle]] internal slot of key.
1051 // Step 3.5
1052 // If the [[type]] internal slot of key is "private":
1053 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1054 // [[handle]] internal slot of key.
1055 if key.Type() == KeyType::Private {
1056 let (seed_bytes, public_key_bytes) =
1057 convert_handle_to_seed_and_public_key(key.handle())?;
1058 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1059 jwk.encode_string_field(JwkStringField::Priv, &seed_bytes);
1060 } else {
1061 let public_key_bytes = convert_handle_to_public_key(key.handle())?;
1062 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1063 }
1064
1065 // Step 3.6. Set the key_ops attribute of jwk to the usages attribute of key.
1066 jwk.set_key_ops(key.usages());
1067
1068 // Step 3.7. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1069 jwk.ext = Some(key.Extractable());
1070
1071 // Step 3.8. Let result be jwk.
1072 ExportedKey::Jwk(Box::new(jwk))
1073 },
1074 // Otherwise:
1075 _ => {
1076 // throw a NotSupportedError.
1077 return Err(Error::NotSupported(Some(
1078 "Unsupported export key format for ML-DSA key".to_string(),
1079 )));
1080 },
1081 };
1082
1083 // Step 3. Return result.
1084 Ok(result)
1085}
1086
1087/// Convert seed bytes to an ML-DSA private key handle and an ML-DSA public key handle. If private
1088/// key bytes and/or public key bytes are provided, it runs a consistency check against the seed.
1089/// If the length in bits of seed bytes is not 256, the conversion fails, or the consistency check
1090/// fails, throw a DataError.
1091fn convert_seed_to_handles(
1092 algo_name: &str,
1093 seed_bytes: &[u8],
1094 private_key_bytes: Option<&[u8]>,
1095 public_key_bytes: Option<&[u8]>,
1096) -> Result<(Handle, Handle), Error> {
1097 if seed_bytes.len() != 32 {
1098 return Err(Error::Data(Some(
1099 "The length in bits of seed bytes is not 256".to_string(),
1100 )));
1101 }
1102
1103 let seed: B32 = seed_bytes
1104 .try_into()
1105 .map_err(|_| Error::Data(Some("Failed to parse the seed bytes".to_string())))?;
1106 let handles = match algo_name {
1107 ALG_ML_DSA_44 => {
1108 let key_pair = MlDsa44::key_gen_internal(&seed);
1109 if let Some(private_key_bytes) = private_key_bytes {
1110 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1111 return Err(Error::Data(Some(
1112 "The expanded private key does not match the seed".to_string(),
1113 )));
1114 }
1115 }
1116 if let Some(public_key_bytes) = public_key_bytes {
1117 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1118 return Err(Error::Data(Some(
1119 "The public key does not match the seed".to_string(),
1120 )));
1121 }
1122 }
1123
1124 (
1125 Handle::MlDsa44PrivateKey(seed),
1126 Handle::MlDsa44PublicKey(Box::new(key_pair.verifying_key().encode())),
1127 )
1128 },
1129 ALG_ML_DSA_65 => {
1130 let key_pair = MlDsa65::key_gen_internal(&seed);
1131 if let Some(private_key_bytes) = private_key_bytes {
1132 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1133 return Err(Error::Data(Some(
1134 "The expanded private key does not match the seed".to_string(),
1135 )));
1136 }
1137 }
1138 if let Some(public_key_bytes) = public_key_bytes {
1139 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1140 return Err(Error::Data(Some(
1141 "The public key does not match the seed".to_string(),
1142 )));
1143 }
1144 }
1145
1146 (
1147 Handle::MlDsa65PrivateKey(seed),
1148 Handle::MlDsa65PublicKey(Box::new(key_pair.verifying_key().encode())),
1149 )
1150 },
1151 ALG_ML_DSA_87 => {
1152 let key_pair = MlDsa87::key_gen_internal(&seed);
1153 if let Some(private_key_bytes) = private_key_bytes {
1154 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1155 return Err(Error::Data(Some(
1156 "The expanded private key does not match the seed".to_string(),
1157 )));
1158 }
1159 }
1160 if let Some(public_key_bytes) = public_key_bytes {
1161 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1162 return Err(Error::Data(Some(
1163 "The public key does not match the seed".to_string(),
1164 )));
1165 }
1166 }
1167
1168 (
1169 Handle::MlDsa87PrivateKey(seed),
1170 Handle::MlDsa87PublicKey(Box::new(key_pair.verifying_key().encode())),
1171 )
1172 },
1173 _ => {
1174 return Err(Error::NotSupported(Some(format!(
1175 "{} is not an ML-DSA algorithm",
1176 algo_name
1177 ))));
1178 },
1179 };
1180
1181 Ok(handles)
1182}
1183
1184/// Convert public key bytes to an ML-DSA public key handle. If the conversion fails, throw a
1185/// DataError.
1186fn convert_public_key_to_handle(algo_name: &str, public_key_bytes: &[u8]) -> Result<Handle, Error> {
1187 let public_key_handle = match algo_name {
1188 ALG_ML_DSA_44 => {
1189 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa44>::try_from(public_key_bytes)
1190 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1191 Handle::MlDsa44PublicKey(Box::new(encoded_verifying_key))
1192 },
1193 ALG_ML_DSA_65 => {
1194 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa65>::try_from(public_key_bytes)
1195 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1196 Handle::MlDsa65PublicKey(Box::new(encoded_verifying_key))
1197 },
1198 ALG_ML_DSA_87 => {
1199 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa87>::try_from(public_key_bytes)
1200 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1201 Handle::MlDsa87PublicKey(Box::new(encoded_verifying_key))
1202 },
1203 _ => {
1204 return Err(Error::NotSupported(Some(format!(
1205 "{} is not an ML-DSA algorithm",
1206 algo_name
1207 ))));
1208 },
1209 };
1210
1211 Ok(public_key_handle)
1212}
1213
1214/// Convert an ML-DSA private key handle to seed bytes and public key bytes. If the handle is not
1215/// representing a ML-DSA private key, throw an OperationError.
1216fn convert_handle_to_seed_and_public_key(handle: &Handle) -> Result<(Vec<u8>, Vec<u8>), Error> {
1217 let result = match handle {
1218 Handle::MlDsa44PrivateKey(seed) => {
1219 let key_pair = MlDsa44::key_gen_internal(seed);
1220 (
1221 seed.to_vec(),
1222 key_pair.verifying_key().encode().as_slice().to_vec(),
1223 )
1224 },
1225 Handle::MlDsa65PrivateKey(seed) => {
1226 let key_pair = MlDsa65::key_gen_internal(seed);
1227 (
1228 seed.to_vec(),
1229 key_pair.verifying_key().encode().as_slice().to_vec(),
1230 )
1231 },
1232 Handle::MlDsa87PrivateKey(seed) => {
1233 let key_pair = MlDsa87::key_gen_internal(seed);
1234 (
1235 seed.to_vec(),
1236 key_pair.verifying_key().encode().as_slice().to_vec(),
1237 )
1238 },
1239 _ => {
1240 return Err(Error::Operation(Some(
1241 "The key handle is not representing an ML-DSA private key".to_string(),
1242 )));
1243 },
1244 };
1245
1246 Ok(result)
1247}
1248
1249/// Convert an ML-DSA public key handle to public key bytes. If the handle is not representing a
1250/// ML-DSA public key, throw an OperationError.
1251fn convert_handle_to_public_key(handle: &Handle) -> Result<Vec<u8>, Error> {
1252 let result = match handle {
1253 Handle::MlDsa44PublicKey(public_key) => public_key.to_vec(),
1254 Handle::MlDsa65PublicKey(public_key) => public_key.as_slice().to_vec(),
1255 Handle::MlDsa87PublicKey(public_key) => public_key.to_vec(),
1256 _ => {
1257 return Err(Error::Operation(Some(
1258 "The key handle is not representing an ML-DSA public key".to_string(),
1259 )));
1260 },
1261 };
1262
1263 Ok(result)
1264}