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}