Skip to main content

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