script/dom/webcrypto/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 // Step 2.9. If mlDsaPrivateKey represents an ML-DSA key in the expandedKey format, or
535 // if mlDsaPrivateKey represents an ML-DSA key in the both format and the both format
536 // is not supported, throw a NotSupportedError.
537 // Step 2.10. If mlDsaPrivateKey represents an ML-DSA key in the both format, and the
538 // seed field does not correspond to the expandedKey field, throw a DataError.
539 //
540 // NOTE: We support the `both` format, with consistency check.
541 let private_key_structure =
542 MlDsaPrivateKeyStructure::from_der(private_key_info.private_key).map_err(|_| {
543 Error::Data(Some(
544 "Failed to parse privateKey field of PrivateKeyInfo".to_string(),
545 ))
546 })?;
547 let ml_dsa_private_key = match private_key_structure {
548 MlDsaPrivateKeyStructure::Seed(seed) => {
549 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
550 normalized_algorithm.name,
551 seed.as_bytes(),
552 None,
553 None,
554 )?;
555 private_key_handle
556 },
557 MlDsaPrivateKeyStructure::ExpandedKey(_) => {
558 return Err(Error::NotSupported(Some(
559 "Not support \"expandedKey\" format of ASN.1 ML-DSA private key structures"
560 .to_string(),
561 )));
562 },
563 MlDsaPrivateKeyStructure::Both(both) => {
564 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
565 normalized_algorithm.name,
566 both.seed.as_bytes(),
567 Some(both.expanded_key.as_bytes()),
568 None,
569 )?;
570 private_key_handle
571 },
572 };
573
574 // Step 2.11. Let key be a new CryptoKey that represents the ML-DSA private key
575 // identified by mlDsaPrivateKey.
576 // Step 2.12. Set the [[type]] internal slot of key to "private"
577 // Step 2.13. Let algorithm be a new KeyAlgorithm.
578 // Step 2.14. Set the name attribute of algorithm to the name attribute of
579 // normalizedAlgorithm.
580 // Step 2.15. Set the [[algorithm]] internal slot of key to algorithm.
581 let algorithm = SubtleKeyAlgorithm {
582 name: normalized_algorithm.name,
583 };
584 CryptoKey::new(
585 cx,
586 global,
587 KeyType::Private,
588 extractable,
589 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
590 usages,
591 ml_dsa_private_key,
592 )
593 },
594 // If format is "raw-public":
595 KeyFormat::Raw_public => {
596 // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
597 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
598 return Err(Error::Syntax(Some(
599 "Usages contains an entry which is not \"verify\"".to_string(),
600 )));
601 }
602
603 // Step 2.2. Let algorithm be a new KeyAlgorithm object.
604 // Step 2.3. Set the name attribute of algorithm to the name attribute of
605 // normalizedAlgorithm.
606 // Step 2.4. Let key be a new CryptoKey representing the key data provided in keyData.
607 // Step 2.5. Set the [[type]] internal slot of key to "public"
608 // Step 2.6. Set the [[algorithm]] internal slot of key to algorithm.
609 let public_key_handle =
610 convert_public_key_to_handle(normalized_algorithm.name, key_data)?;
611 let algorithm = SubtleKeyAlgorithm {
612 name: normalized_algorithm.name,
613 };
614 CryptoKey::new(
615 cx,
616 global,
617 KeyType::Public,
618 extractable,
619 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
620 usages,
621 public_key_handle,
622 )
623 },
624 // If format is "raw-seed":
625 KeyFormat::Raw_seed => {
626 // Step 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError.
627 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
628 return Err(Error::Syntax(Some(
629 "Usages contains an entry which is not \"sign\"".to_string(),
630 )));
631 }
632
633 // Step 2.2. Let data be keyData.
634 let data = key_data;
635
636 // Step 2.3. If the length in bits of data is not 256 then throw a DataError.
637 // Step 2.4. Let privateKey be the result of performing the ML-DSA.KeyGen_internal
638 // function described in Section 6.1 of [FIPS-204] with the parameter set indicated by
639 // the name member of normalizedAlgorithm, using data as ξ.
640 let (private_key_handle, _public_key_handle) =
641 convert_seed_to_handles(normalized_algorithm.name, data, None, None)?;
642
643 // Step 2.5. Let key be a new CryptoKey that represents the ML-DSA private key
644 // identified by privateKey.
645 // Step 2.6. Set the [[type]] internal slot of key to "private"
646 // Step 2.7. Let algorithm be a new KeyAlgorithm.
647 // Step 2.8. Set the name attribute of algorithm to the name attribute of
648 // normalizedAlgorithm.
649 // Step 2.9. Set the [[algorithm]] internal slot of key to algorithm.
650 let algorithm = SubtleKeyAlgorithm {
651 name: normalized_algorithm.name,
652 };
653 CryptoKey::new(
654 cx,
655 global,
656 KeyType::Private,
657 extractable,
658 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
659 usages,
660 private_key_handle,
661 )
662 },
663 // If format is "jwk":
664 KeyFormat::Jwk => {
665 // Step 2.1.
666 // If keyData is a JsonWebKey dictionary:
667 // Let jwk equal keyData.
668 // Otherwise:
669 // Throw a DataError.
670 let jwk = JsonWebKey::parse(cx, key_data)?;
671
672 // Step 2.2. If the priv field is present and usages contains a value which is not
673 // "sign", or, if the priv field is not present and usages contains a value which is
674 // not "verify" then throw a SyntaxError.
675 if jwk.priv_.is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign) {
676 return Err(Error::Syntax(Some(
677 "The priv field is present and usages contains a value which is not \"sign\""
678 .to_string(),
679 )));
680 }
681 if jwk.priv_.is_none() && usages.iter().any(|usage| *usage != KeyUsage::Verify) {
682 return Err(Error::Syntax(Some(
683 "The priv field is not present and usages contains a value which is not \
684 \"verify\""
685 .to_string(),
686 )));
687 }
688
689 // Step 2.3. If the kty field of jwk is not "AKP", then throw a DataError.
690 if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
691 return Err(Error::Data(Some(
692 "The kty field of jwk is not \"AKP\"".to_string(),
693 )));
694 }
695
696 // Step 2.4. If the alg field of jwk is not equal to the name member of
697 // normalizedAlgorithm, then throw a DataError.
698 if jwk
699 .alg
700 .as_ref()
701 .is_none_or(|alg| alg != normalized_algorithm.name.as_str())
702 {
703 return Err(Error::Data(Some(
704 "The alg field of jwk is not equal to the name member of normalizedAlgorithm"
705 .to_string(),
706 )));
707 }
708
709 // Step 2.5. If usages is non-empty and the use field of jwk is present and is not
710 // equal to "sig", then throw a DataError.
711 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
712 return Err(Error::Data(Some(
713 "Usages is non-empty and the use field of jwk is present and is not \
714 equal to \"sig\""
715 .to_string(),
716 )));
717 }
718
719 // Step 2.6. If the key_ops field of jwk is present, and is invalid according to the
720 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
721 // usages values, then throw a DataError.
722 jwk.check_key_ops(&usages)?;
723
724 // Step 2.7. If the ext field of jwk is present and has the value false and extractable
725 // is true, then throw a DataError.
726 if jwk.ext.is_some_and(|ext| !ext) && extractable {
727 return Err(Error::Data(Some(
728 "The ext field of jwk is present and has the value false and extractable \
729 is true"
730 .to_string(),
731 )));
732 }
733
734 // Step 2.8.
735 // If the priv field of jwk is present:
736 if jwk.priv_.is_some() {
737 // Step 2.8.1. If the priv attribute of jwk does not contain a valid base64url
738 // encoded seed representing an ML-DSA private key, then throw a DataError.
739 let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
740
741 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA private
742 // key identified by interpreting the priv attribute of jwk as a base64url encoded
743 // seed.
744 // Step 2.8.3. Set the [[type]] internal slot of key to "private".
745 // Step 2.8.4. If the pub attribute of jwk does not contain the base64url encoded
746 // public key representing the ML-DSA public key corresponding to key, then throw a
747 // DataError.
748 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
749 let (private_key_handle, _public_key_handle) = convert_seed_to_handles(
750 normalized_algorithm.name,
751 &priv_bytes,
752 None,
753 Some(&pub_bytes),
754 )?;
755 let algorithm = SubtleKeyAlgorithm {
756 name: normalized_algorithm.name,
757 };
758 CryptoKey::new(
759 cx,
760 global,
761 KeyType::Private,
762 extractable,
763 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
764 usages,
765 private_key_handle,
766 )
767 }
768 // Otherwise:
769 else {
770 // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
771 // encoded public key representing an ML-DSA public key, then throw a DataError.
772 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
773
774 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
775 // key identified by interpreting the pub attribute of jwk as a base64url encoded
776 // public key.
777 // Step 2.8.3. Set the [[type]] internal slot of key to "public".
778 let public_key_handle =
779 convert_public_key_to_handle(normalized_algorithm.name, &pub_bytes)?;
780 let algorithm = SubtleKeyAlgorithm {
781 name: normalized_algorithm.name,
782 };
783 CryptoKey::new(
784 cx,
785 global,
786 KeyType::Public,
787 extractable,
788 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
789 usages,
790 public_key_handle,
791 )
792 }
793 },
794 // Otherwise:
795 _ => {
796 // throw a NotSupportedError.
797 return Err(Error::NotSupported(Some(
798 "Unsupported import key format for ML-DSA key".to_string(),
799 )));
800 },
801 };
802
803 // Step 3. Return key.
804 Ok(key)
805}
806
807/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
808pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
809 // Step 1. Let key be the CryptoKey to be exported.
810
811 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
812 // slot of key cannot be accessed, then throw an OperationError.
813
814 // Step 3.
815 let result = match format {
816 KeyFormat::Spki => {
817 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
818 // InvalidAccessError.
819 if key.Type() != KeyType::Public {
820 return Err(Error::InvalidAccess(Some(
821 "[[type]] internal slot of key is not \"public\"".to_string(),
822 )));
823 }
824
825 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
826 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
827 return Err(Error::Operation(Some(
828 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
829 )));
830 };
831
832 // Step 3.3.
833 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
834 // [RFC5280] with the following properties:
835 //
836 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
837 // properties:
838 //
839 // If the name member of keyAlgorithm is "ML-DSA-44":
840 // Set the algorithm object identifier to the id-ml-dsa-44
841 // (2.16.840.1.101.3.4.3.17) OID.
842 //
843 // If the name member of keyAlgorithm is "ML-DSA-65":
844 // Set the algorithm object identifier to the id-ml-dsa-65
845 // (2.16.840.1.101.3.4.3.18) OID.
846 //
847 // If the name member of keyAlgorithm is "ML-DSA-87":
848 // Set the algorithm object identifier to the id-ml-dsa-87
849 // (2.16.840.1.101.3.4.3.19) OID.
850 //
851 // Otherwise:
852 // throw a NotSupportedError.
853 //
854 // Set the subjectPublicKey field to keyData.
855 let oid = match key_algorithm.name {
856 CryptoAlgorithm::MlDsa44 => ID_ALG_ML_DSA_44,
857 CryptoAlgorithm::MlDsa65 => ID_ALG_ML_DSA_65,
858 CryptoAlgorithm::MlDsa87 => ID_ALG_ML_DSA_87,
859 _ => {
860 return Err(Error::Operation(Some(format!(
861 "{} is not an ML-DSA algorithm",
862 key_algorithm.name.as_str()
863 ))));
864 },
865 };
866 let key_bytes = convert_handle_to_public_key(key.handle())?;
867 let subject_public_key = BitString::from_bytes(&key_bytes).map_err(|_| {
868 Error::Operation(Some(
869 "Failed to encode BitString for subjectPublicKey field of SubjectPublicKeyInfo"
870 .to_string(),
871 ))
872 })?;
873 let data = SubjectPublicKeyInfo {
874 algorithm: AlgorithmIdentifier::<AnyRef> {
875 oid: ObjectIdentifier::new_unwrap(oid),
876 parameters: None,
877 },
878 subject_public_key,
879 };
880
881 // Step 3.4. Let result be the result of DER-encoding data.
882 ExportedKey::Bytes(data.to_der().map_err(|_| {
883 Error::Operation(Some(
884 "Failed to encode SubjectPublicKeyInfo in DER format".to_string(),
885 ))
886 })?)
887 },
888 KeyFormat::Pkcs8 => {
889 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
890 // InvalidAccessError.
891 if key.Type() != KeyType::Private {
892 return Err(Error::InvalidAccess(Some(
893 "[[type]] internal slot of key is not \"private\"".to_string(),
894 )));
895 }
896
897 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
898 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
899 return Err(Error::Operation(Some(
900 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
901 )));
902 };
903
904 // Step 3.3.
905 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
906 // with the following properties:
907 //
908 // Set the version field to 0.
909 //
910 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
911 // with the following properties:
912 //
913 // If the name member of keyAlgorithm is "ML-DSA-44":
914 // Set the algorithm object identifier to the id-ml-dsa-44
915 // (2.16.840.1.101.3.4.3.17) OID.
916 //
917 // If the name member of keyAlgorithm is "ML-DSA-65":
918 // Set the algorithm object identifier to the id-ml-dsa-65
919 // (2.16.840.1.101.3.4.3.18) OID.
920 //
921 // If the name member of keyAlgorithm is "ML-DSA-87":
922 // Set the algorithm object identifier to the id-ml-dsa-87
923 // (2.16.840.1.101.3.4.3.19) OID.
924 //
925 // Otherwise:
926 // throw a NotSupportedError.
927 //
928 // Set the privateKey field as follows:
929 //
930 // If the name member of keyAlgorithm is "ML-DSA-44":
931 // Set the privateKey field to the result of DER-encoding a
932 // ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
933 // seed represented by the [[handle]] internal slot of key using the
934 // seed-only format (using a context-specific [0] primitive tag with an
935 // implicit encoding of OCTET STRING).
936 //
937 // If the name member of keyAlgorithm is "ML-DSA-65":
938 // Set the privateKey field to the result of DER-encoding a
939 // ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
940 // seed represented by the [[handle]] internal slot of key using the
941 // seed-only format (using a context-specific [0] primitive tag with an
942 // implicit encoding of OCTET STRING).
943 //
944 // If the name member of keyAlgorithm is "ML-DSA-87":
945 // Set the privateKey field to the result of DER-encoding a
946 // ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
947 // seed represented by the [[handle]] internal slot of key using the
948 // seed-only format (using a context-specific [0] primitive tag with an
949 // implicit encoding of OCTET STRING).
950 //
951 // Otherwise:
952 // throw a NotSupportedError.
953 let oid = match key_algorithm.name {
954 CryptoAlgorithm::MlDsa44 => ID_ALG_ML_DSA_44,
955 CryptoAlgorithm::MlDsa65 => ID_ALG_ML_DSA_65,
956 CryptoAlgorithm::MlDsa87 => ID_ALG_ML_DSA_87,
957 _ => {
958 return Err(Error::Operation(Some(format!(
959 "{} is not an ML-DSA algorithm",
960 key_algorithm.name.as_str()
961 ))));
962 },
963 };
964 let (seed_bytes, _public_key_bytes) =
965 convert_handle_to_seed_and_public_key(key.handle())?;
966 let private_key =
967 MlDsaPrivateKeyStructure::Seed(OctetString::new(seed_bytes).map_err(|_| {
968 Error::Operation(Some(
969 "Failed to encode OctetString for privateKey field of \
970 ASN.1 ML-DSA private key structure"
971 .to_string(),
972 ))
973 })?);
974 let encoded_private_key = private_key.to_der().map_err(|_| {
975 Error::Operation(Some(
976 "Failed to encode ASN.1 ML-DSA private key structure in DER format".to_string(),
977 ))
978 })?;
979 let private_key_info = PrivateKeyInfo {
980 algorithm: AlgorithmIdentifier {
981 oid: ObjectIdentifier::new_unwrap(oid),
982 parameters: None,
983 },
984 private_key: &encoded_private_key,
985 public_key: None,
986 };
987
988 // Step 3.4. Let result be the result of DER-encoding data.
989 ExportedKey::Bytes(private_key_info.to_der().map_err(|_| {
990 Error::Operation(Some(
991 "Failed to encode PrivateKeyInfo in DER format".to_string(),
992 ))
993 })?)
994 },
995 // If format is "raw-public":
996 KeyFormat::Raw_public => {
997 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
998 // InvalidAccessError.
999 if key.Type() != KeyType::Public {
1000 return Err(Error::InvalidAccess(Some(
1001 "[[type]] internal slot of key is not \"public\"".to_string(),
1002 )));
1003 }
1004
1005 // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1006 // by the [[handle]] internal slot of key.
1007 let data = convert_handle_to_public_key(key.handle())?;
1008
1009 // Step 3.2. Let result be data.
1010 ExportedKey::Bytes(data)
1011 },
1012 // If format is "raw-seed":
1013 KeyFormat::Raw_seed => {
1014 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1015 // InvalidAccessError.
1016 if key.Type() != KeyType::Private {
1017 return Err(Error::InvalidAccess(Some(
1018 "[[type]] internal slot of key is not \"private\"".to_string(),
1019 )));
1020 }
1021
1022 // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1023 // represented by the [[handle]] internal slot of key.
1024 let (data, _public_key_bytes) = convert_handle_to_seed_and_public_key(key.handle())?;
1025
1026 // Step 3.3. Let result be data.
1027 ExportedKey::Bytes(data)
1028 },
1029 // If format is "jwk":
1030 KeyFormat::Jwk => {
1031 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1032 let mut jwk = JsonWebKey::default();
1033
1034 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
1035 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1036 return Err(Error::Operation(Some(
1037 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1038 )));
1039 };
1040
1041 // Step 3.3. Set the kty attribute of jwk to "AKP".
1042 jwk.kty = Some(DOMString::from("AKP"));
1043
1044 // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1045 jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1046
1047 // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1048 // corresponding to the [[handle]] internal slot of key.
1049 // Step 3.6.
1050 // If the [[type]] internal slot of key is "private":
1051 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1052 // [[handle]] internal slot of key.
1053 if key.Type() == KeyType::Private {
1054 let (seed_bytes, public_key_bytes) =
1055 convert_handle_to_seed_and_public_key(key.handle())?;
1056 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1057 jwk.encode_string_field(JwkStringField::Priv, &seed_bytes);
1058 } else {
1059 let public_key_bytes = convert_handle_to_public_key(key.handle())?;
1060 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1061 }
1062
1063 // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1064 jwk.set_key_ops(key.usages());
1065
1066 // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1067 jwk.ext = Some(key.Extractable());
1068
1069 // Step 3.9. Let result be jwk.
1070 ExportedKey::Jwk(Box::new(jwk))
1071 },
1072 // Otherwise:
1073 _ => {
1074 // throw a NotSupportedError.
1075 return Err(Error::NotSupported(Some(
1076 "Unsupported export key format for ML-DSA key".to_string(),
1077 )));
1078 },
1079 };
1080
1081 // Step 3. Return result.
1082 Ok(result)
1083}
1084
1085/// Convert seed bytes to an ML-DSA private key handle and an ML-DSA public key handle. If private
1086/// key bytes and/or public key bytes are provided, it runs a consistency check against the seed.
1087/// If the length in bits of seed bytes is not 256, the conversion fails, or the consistency check
1088/// fails, throw a DataError.
1089fn convert_seed_to_handles(
1090 algorithm_name: CryptoAlgorithm,
1091 seed_bytes: &[u8],
1092 private_key_bytes: Option<&[u8]>,
1093 public_key_bytes: Option<&[u8]>,
1094) -> Result<(Handle, Handle), Error> {
1095 if seed_bytes.len() != 32 {
1096 return Err(Error::Data(Some(
1097 "The length in bits of seed bytes is not 256".to_string(),
1098 )));
1099 }
1100
1101 let seed: B32 = seed_bytes
1102 .try_into()
1103 .map_err(|_| Error::Data(Some("Failed to parse the seed bytes".to_string())))?;
1104 let handles = match algorithm_name {
1105 CryptoAlgorithm::MlDsa44 => {
1106 let key_pair = MlDsa44::key_gen_internal(&seed);
1107 if let Some(private_key_bytes) = private_key_bytes {
1108 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1109 return Err(Error::Data(Some(
1110 "The expanded private key does not match the seed".to_string(),
1111 )));
1112 }
1113 }
1114 if let Some(public_key_bytes) = public_key_bytes {
1115 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1116 return Err(Error::Data(Some(
1117 "The public key does not match the seed".to_string(),
1118 )));
1119 }
1120 }
1121
1122 (
1123 Handle::MlDsa44PrivateKey(seed),
1124 Handle::MlDsa44PublicKey(Box::new(key_pair.verifying_key().encode())),
1125 )
1126 },
1127 CryptoAlgorithm::MlDsa65 => {
1128 let key_pair = MlDsa65::key_gen_internal(&seed);
1129 if let Some(private_key_bytes) = private_key_bytes {
1130 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1131 return Err(Error::Data(Some(
1132 "The expanded private key does not match the seed".to_string(),
1133 )));
1134 }
1135 }
1136 if let Some(public_key_bytes) = public_key_bytes {
1137 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1138 return Err(Error::Data(Some(
1139 "The public key does not match the seed".to_string(),
1140 )));
1141 }
1142 }
1143
1144 (
1145 Handle::MlDsa65PrivateKey(seed),
1146 Handle::MlDsa65PublicKey(Box::new(key_pair.verifying_key().encode())),
1147 )
1148 },
1149 CryptoAlgorithm::MlDsa87 => {
1150 let key_pair = MlDsa87::key_gen_internal(&seed);
1151 if let Some(private_key_bytes) = private_key_bytes {
1152 if private_key_bytes != key_pair.signing_key().encode().as_slice() {
1153 return Err(Error::Data(Some(
1154 "The expanded private key does not match the seed".to_string(),
1155 )));
1156 }
1157 }
1158 if let Some(public_key_bytes) = public_key_bytes {
1159 if public_key_bytes != key_pair.verifying_key().encode().as_slice() {
1160 return Err(Error::Data(Some(
1161 "The public key does not match the seed".to_string(),
1162 )));
1163 }
1164 }
1165
1166 (
1167 Handle::MlDsa87PrivateKey(seed),
1168 Handle::MlDsa87PublicKey(Box::new(key_pair.verifying_key().encode())),
1169 )
1170 },
1171 _ => {
1172 return Err(Error::NotSupported(Some(format!(
1173 "{} is not an ML-DSA algorithm",
1174 algorithm_name.as_str()
1175 ))));
1176 },
1177 };
1178
1179 Ok(handles)
1180}
1181
1182/// Convert public key bytes to an ML-DSA public key handle. If the conversion fails, throw a
1183/// DataError.
1184fn convert_public_key_to_handle(
1185 algorithm_name: CryptoAlgorithm,
1186 public_key_bytes: &[u8],
1187) -> Result<Handle, Error> {
1188 let public_key_handle = match algorithm_name {
1189 CryptoAlgorithm::MlDsa44 => {
1190 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa44>::try_from(public_key_bytes)
1191 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1192 Handle::MlDsa44PublicKey(Box::new(encoded_verifying_key))
1193 },
1194 CryptoAlgorithm::MlDsa65 => {
1195 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa65>::try_from(public_key_bytes)
1196 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1197 Handle::MlDsa65PublicKey(Box::new(encoded_verifying_key))
1198 },
1199 CryptoAlgorithm::MlDsa87 => {
1200 let encoded_verifying_key = EncodedVerifyingKey::<MlDsa87>::try_from(public_key_bytes)
1201 .map_err(|_| Error::Data(Some("Failed to parse ML-DSA public key".to_string())))?;
1202 Handle::MlDsa87PublicKey(Box::new(encoded_verifying_key))
1203 },
1204 _ => {
1205 return Err(Error::NotSupported(Some(format!(
1206 "{} is not an ML-DSA algorithm",
1207 algorithm_name.as_str()
1208 ))));
1209 },
1210 };
1211
1212 Ok(public_key_handle)
1213}
1214
1215/// Convert an ML-DSA private key handle to seed bytes and public key bytes. If the handle is not
1216/// representing a ML-DSA private key, throw an OperationError.
1217fn convert_handle_to_seed_and_public_key(handle: &Handle) -> Result<(Vec<u8>, Vec<u8>), Error> {
1218 let result = match handle {
1219 Handle::MlDsa44PrivateKey(seed) => {
1220 let key_pair = MlDsa44::key_gen_internal(seed);
1221 (
1222 seed.to_vec(),
1223 key_pair.verifying_key().encode().as_slice().to_vec(),
1224 )
1225 },
1226 Handle::MlDsa65PrivateKey(seed) => {
1227 let key_pair = MlDsa65::key_gen_internal(seed);
1228 (
1229 seed.to_vec(),
1230 key_pair.verifying_key().encode().as_slice().to_vec(),
1231 )
1232 },
1233 Handle::MlDsa87PrivateKey(seed) => {
1234 let key_pair = MlDsa87::key_gen_internal(seed);
1235 (
1236 seed.to_vec(),
1237 key_pair.verifying_key().encode().as_slice().to_vec(),
1238 )
1239 },
1240 _ => {
1241 return Err(Error::Operation(Some(
1242 "The key handle is not representing an ML-DSA private key".to_string(),
1243 )));
1244 },
1245 };
1246
1247 Ok(result)
1248}
1249
1250/// Convert an ML-DSA public key handle to public key bytes. If the handle is not representing a
1251/// ML-DSA public key, throw an OperationError.
1252fn convert_handle_to_public_key(handle: &Handle) -> Result<Vec<u8>, Error> {
1253 let result = match handle {
1254 Handle::MlDsa44PublicKey(public_key) => public_key.to_vec(),
1255 Handle::MlDsa65PublicKey(public_key) => public_key.as_slice().to_vec(),
1256 Handle::MlDsa87PublicKey(public_key) => public_key.to_vec(),
1257 _ => {
1258 return Err(Error::Operation(Some(
1259 "The key handle is not representing an ML-DSA public key".to_string(),
1260 )));
1261 },
1262 };
1263
1264 Ok(result)
1265}