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            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                let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
679                let private_key = match normalized_algorithm.name {
680                    CryptoAlgorithm::MlDsa44 => {
681                        let signing_key =
682                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
683                                Error::Data(Some(
684                                    "Failed to parse the private ML-DSA-44 key in priv attribute"
685                                        .into(),
686                                ))
687                            })?;
688                        let verifying_key =
689                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
690                                Error::Data(Some(
691                                    "Failed to parse the public ML-DSA-44 key in pub attribute"
692                                        .into(),
693                                ))
694                            })?;
695                        if signing_key.verifying_key() != verifying_key {
696                            return Err(Error::Data(Some(
697                                "The public key in pub attribute does not match \
698                                    the private key in priv attribute"
699                                    .into(),
700                            )));
701                        }
702                        Handle::MlDsa44PrivateKey(signing_key)
703                    },
704                    CryptoAlgorithm::MlDsa65 => {
705                        let signing_key =
706                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
707                                Error::Data(Some(
708                                    "Failed to parse the private ML-DSA-65 key in priv attribute"
709                                        .into(),
710                                ))
711                            })?;
712                        let verifying_key =
713                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
714                                Error::Data(Some(
715                                    "Failed to parse the public ML-DSA-65 key in pub attribute"
716                                        .into(),
717                                ))
718                            })?;
719                        if signing_key.verifying_key() != verifying_key {
720                            return Err(Error::Data(Some(
721                                "The public key in pub attribute does not match \
722                                    the private key in priv attribute"
723                                    .into(),
724                            )));
725                        }
726                        Handle::MlDsa65PrivateKey(signing_key)
727                    },
728                    CryptoAlgorithm::MlDsa87 => {
729                        let signing_key =
730                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
731                                Error::Data(Some(
732                                    "Failed to parse the private ML-DSA-87 key in priv attribute"
733                                        .into(),
734                                ))
735                            })?;
736                        let verifying_key =
737                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
738                                Error::Data(Some(
739                                    "Failed to parse the public ML-DSA-87 key in pub attribute"
740                                        .into(),
741                                ))
742                            })?;
743                        if signing_key.verifying_key() != verifying_key {
744                            return Err(Error::Data(Some(
745                                "The public key in pub attribute does not match \
746                                    the private key in priv attribute"
747                                    .into(),
748                            )));
749                        }
750                        Handle::MlDsa87PrivateKey(signing_key)
751                    },
752                    name => {
753                        return Err(Error::NotSupported(Some(format!(
754                            "{} is not an ML-DSA algorithm",
755                            name.as_str()
756                        ))));
757                    },
758                };
759                let algorithm = SubtleKeyAlgorithm {
760                    name: normalized_algorithm.name,
761                };
762                CryptoKey::new(
763                    cx,
764                    global,
765                    KeyType::Private,
766                    extractable,
767                    KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
768                    usages,
769                    private_key,
770                )
771            }
772            // Otherwise:
773            else {
774                // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
775                // encoded public key representing an ML-DSA public key, then throw a DataError.
776                let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
777
778                // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
779                // key identified by interpreting the pub attribute of jwk as a base64url encoded
780                // public key.
781                // Step 2.8.3. Set the [[type]] internal slot of key to "public".
782                let public_key = match normalized_algorithm.name {
783                    CryptoAlgorithm::MlDsa44 => {
784                        let verifying_key =
785                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
786                                Error::Data(Some(
787                                    "Failed to parse the public ML-DSA-44 key in pub attribute"
788                                        .into(),
789                                ))
790                            })?;
791                        Handle::MlDsa44PublicKey(verifying_key)
792                    },
793                    CryptoAlgorithm::MlDsa65 => {
794                        let verifying_key =
795                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
796                                Error::Data(Some(
797                                    "Failed to parse the public ML-DSA-65 key in pub attribute"
798                                        .into(),
799                                ))
800                            })?;
801                        Handle::MlDsa65PublicKey(verifying_key)
802                    },
803                    CryptoAlgorithm::MlDsa87 => {
804                        let verifying_key =
805                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
806                                Error::Data(Some(
807                                    "Failed to parse the public ML-DSA-87 key in pub attribute"
808                                        .into(),
809                                ))
810                            })?;
811                        Handle::MlDsa87PublicKey(verifying_key)
812                    },
813                    name => {
814                        return Err(Error::NotSupported(Some(format!(
815                            "{} is not an ML-DSA algorithm",
816                            name.as_str()
817                        ))));
818                    },
819                };
820                let algorithm = SubtleKeyAlgorithm {
821                    name: normalized_algorithm.name,
822                };
823                CryptoKey::new(
824                    cx,
825                    global,
826                    KeyType::Public,
827                    extractable,
828                    KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
829                    usages,
830                    public_key,
831                )
832            }
833        },
834        // Otherwise:
835        _ => {
836            // throw a NotSupportedError.
837            return Err(Error::NotSupported(Some(
838                "Unsupported import key format for ML-DSA key".into(),
839            )));
840        },
841    };
842
843    // Step 3. Return key.
844    Ok(key)
845}
846
847/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
848pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
849    // Step 1. Let key be the CryptoKey to be exported.
850
851    // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
852    // slot of key cannot be accessed, then throw an OperationError.
853
854    // Step 3.
855    let result = match format {
856        KeyFormat::Spki => {
857            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
858            // InvalidAccessError.
859            if key.Type() != KeyType::Public {
860                return Err(Error::InvalidAccess(Some(
861                    "[[type]] internal slot of key is not \"public\"".into(),
862                )));
863            }
864
865            // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
866            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
867                return Err(Error::Operation(Some(
868                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
869                )));
870            };
871
872            // Step 3.3.
873            // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
874            // [RFC5280] with the following properties:
875            //
876            //     Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
877            //     properties:
878            //
879            //         If the name member of keyAlgorithm is "ML-DSA-44":
880            //             Set the algorithm object identifier to the id-ml-dsa-44
881            //             (2.16.840.1.101.3.4.3.17) OID.
882            //
883            //         If the name member of keyAlgorithm is "ML-DSA-65":
884            //             Set the algorithm object identifier to the id-ml-dsa-65
885            //             (2.16.840.1.101.3.4.3.18) OID.
886            //
887            //         If the name member of keyAlgorithm is "ML-DSA-87":
888            //             Set the algorithm object identifier to the id-ml-dsa-87
889            //             (2.16.840.1.101.3.4.3.19) OID.
890            //
891            //         Otherwise:
892            //             throw a NotSupportedError.
893            //
894            //     Set the subjectPublicKey field to keyData.
895            let data = match (key_algorithm.name, key.handle()) {
896                (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PublicKey(public_key)) => {
897                    public_key.to_public_key_der().map_err(|_| {
898                        Error::Operation(Some(
899                            "Failed to encode the ML-DSA-44 public key into SPKI format".into(),
900                        ))
901                    })?
902                },
903                (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PublicKey(public_key)) => {
904                    public_key.to_public_key_der().map_err(|_| {
905                        Error::Operation(Some(
906                            "Failed to encode the ML-DSA-65 public key into SPKI format".into(),
907                        ))
908                    })?
909                },
910                (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PublicKey(public_key)) => {
911                    public_key.to_public_key_der().map_err(|_| {
912                        Error::Operation(Some(
913                            "Failed to encode the ML-DSA-87 public key into SPKI format".into(),
914                        ))
915                    })?
916                },
917                _ => {
918                    return Err(Error::Operation(Some(
919                        "The key handle is not representing an ML-DSA public key".into(),
920                    )));
921                },
922            };
923
924            // Step 3.4. Let result be the result of DER-encoding data.
925            ExportedKey::new_bytes(data.into_vec())
926        },
927        KeyFormat::Pkcs8 => {
928            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
929            // InvalidAccessError.
930            if key.Type() != KeyType::Private {
931                return Err(Error::InvalidAccess(Some(
932                    "[[type]] internal slot of key is not \"private\"".into(),
933                )));
934            }
935
936            // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
937            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
938                return Err(Error::Operation(Some(
939                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
940                )));
941            };
942
943            // Step 3.3.
944            // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
945            // with the following properties:
946            //
947            //     Set the version field to 0.
948            //
949            //     Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
950            //     with the following properties:
951            //
952            //         If the name member of keyAlgorithm is "ML-DSA-44":
953            //             Set the algorithm object identifier to the id-ml-dsa-44
954            //             (2.16.840.1.101.3.4.3.17) OID.
955            //
956            //         If the name member of keyAlgorithm is "ML-DSA-65":
957            //             Set the algorithm object identifier to the id-ml-dsa-65
958            //             (2.16.840.1.101.3.4.3.18) OID.
959            //
960            //         If the name member of keyAlgorithm is "ML-DSA-87":
961            //             Set the algorithm object identifier to the id-ml-dsa-87
962            //             (2.16.840.1.101.3.4.3.19) OID.
963            //
964            //         Otherwise:
965            //             throw a NotSupportedError.
966            //
967            //     Set the privateKey field as follows:
968            //
969            //         If the name member of keyAlgorithm is "ML-DSA-44":
970            //             Set the privateKey field to the result of DER-encoding a
971            //             ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
972            //             seed represented by the [[handle]] internal slot of key using the
973            //             seed-only format (using a context-specific [0] primitive tag with an
974            //             implicit encoding of OCTET STRING).
975            //
976            //         If the name member of keyAlgorithm is "ML-DSA-65":
977            //             Set the privateKey field to the result of DER-encoding a
978            //             ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
979            //             seed represented by the [[handle]] internal slot of key using the
980            //             seed-only format (using a context-specific [0] primitive tag with an
981            //             implicit encoding of OCTET STRING).
982            //
983            //         If the name member of keyAlgorithm is "ML-DSA-87":
984            //             Set the privateKey field to the result of DER-encoding a
985            //             ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
986            //             seed represented by the [[handle]] internal slot of key using the
987            //             seed-only format (using a context-specific [0] primitive tag with an
988            //             implicit encoding of OCTET STRING).
989            //
990            //         Otherwise:
991            //             throw a NotSupportedError.
992            let private_key_info = match (key_algorithm.name, key.handle()) {
993                (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PrivateKey(private_key)) => {
994                    private_key.to_pkcs8_der().map_err(|_| {
995                        Error::Operation(Some(
996                            "Failed to encode the ML-DSA-44 private key into PKCS#8 format".into(),
997                        ))
998                    })?
999                },
1000                (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PrivateKey(private_key)) => {
1001                    private_key.to_pkcs8_der().map_err(|_| {
1002                        Error::Operation(Some(
1003                            "Failed to encode the ML-DSA-65 private key into PKCS#8 format".into(),
1004                        ))
1005                    })?
1006                },
1007                (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PrivateKey(private_key)) => {
1008                    private_key.to_pkcs8_der().map_err(|_| {
1009                        Error::Operation(Some(
1010                            "Failed to encode the ML-DSA-87 private key into PKCS#8 format".into(),
1011                        ))
1012                    })?
1013                },
1014                _ => {
1015                    return Err(Error::Operation(Some(
1016                        "The key handle is not representing an ML-DSA private key".into(),
1017                    )));
1018                },
1019            };
1020
1021            // Step 3.4. Let result be the result of DER-encoding data.
1022            ExportedKey::Bytes(private_key_info.to_bytes())
1023        },
1024        // If format is "raw-public":
1025        KeyFormat::Raw_public => {
1026            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
1027            // InvalidAccessError.
1028            if key.Type() != KeyType::Public {
1029                return Err(Error::InvalidAccess(Some(
1030                    "[[type]] internal slot of key is not \"public\"".into(),
1031                )));
1032            }
1033
1034            // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1035            // by the [[handle]] internal slot of key.
1036            let data = match key.handle() {
1037                Handle::MlDsa44PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1038                Handle::MlDsa65PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1039                Handle::MlDsa87PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1040                _ => {
1041                    return Err(Error::Operation(Some(
1042                        "The key handle is not representing an ML-DSA public key".into(),
1043                    )));
1044                },
1045            };
1046
1047            // Step 3.2. Let result be data.
1048            ExportedKey::new_bytes(data)
1049        },
1050        // If format is "raw-seed":
1051        KeyFormat::Raw_seed => {
1052            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1053            // InvalidAccessError.
1054            if key.Type() != KeyType::Private {
1055                return Err(Error::InvalidAccess(Some(
1056                    "[[type]] internal slot of key is not \"private\"".into(),
1057                )));
1058            }
1059
1060            // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1061            // represented by the [[handle]] internal slot of key.
1062            let data = match key.handle() {
1063                Handle::MlDsa44PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1064                Handle::MlDsa65PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1065                Handle::MlDsa87PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1066                _ => {
1067                    return Err(Error::Operation(Some(
1068                        "The key handle is not representing an ML-DSA private key".into(),
1069                    )));
1070                },
1071            };
1072
1073            // Step 3.3. Let result be data.
1074            ExportedKey::new_bytes(data)
1075        },
1076        // If format is "jwk":
1077        KeyFormat::Jwk => {
1078            // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1079            let mut jwk = JsonWebKey::default();
1080
1081            // Step 3.2.  Let keyAlgorithm be the [[algorithm]] internal slot of key.
1082            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1083                return Err(Error::Operation(Some(
1084                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
1085                )));
1086            };
1087
1088            // Step 3.3. Set the kty attribute of jwk to "AKP".
1089            jwk.kty = Some(DOMString::from("AKP"));
1090
1091            // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1092            jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1093
1094            // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1095            // corresponding to the [[handle]] internal slot of key.
1096            // Step 3.6.
1097            // If the [[type]] internal slot of key is "private":
1098            //     Set the priv attribute of jwk to the base64url encoded seed represented by the
1099            //     [[handle]] internal slot of key.
1100            if key.Type() == KeyType::Private {
1101                match key.handle() {
1102                    Handle::MlDsa44PrivateKey(private_key) => {
1103                        jwk.encode_string_field(
1104                            JwkStringField::Priv,
1105                            private_key.as_seed().as_slice(),
1106                        );
1107                        jwk.encode_string_field(
1108                            JwkStringField::Pub,
1109                            private_key.as_ref().to_bytes().as_slice(),
1110                        );
1111                    },
1112                    Handle::MlDsa65PrivateKey(private_key) => {
1113                        jwk.encode_string_field(
1114                            JwkStringField::Priv,
1115                            private_key.as_seed().as_slice(),
1116                        );
1117                        jwk.encode_string_field(
1118                            JwkStringField::Pub,
1119                            private_key.as_ref().to_bytes().as_slice(),
1120                        );
1121                    },
1122                    Handle::MlDsa87PrivateKey(private_key) => {
1123                        jwk.encode_string_field(
1124                            JwkStringField::Priv,
1125                            private_key.as_seed().as_slice(),
1126                        );
1127                        jwk.encode_string_field(
1128                            JwkStringField::Pub,
1129                            private_key.as_ref().to_bytes().as_slice(),
1130                        );
1131                    },
1132                    _ => {
1133                        return Err(Error::Operation(Some(
1134                            "The key handle is not representing an ML-DSA private key".into(),
1135                        )));
1136                    },
1137                }
1138            } else {
1139                match key.handle() {
1140                    Handle::MlDsa44PublicKey(public_key) => {
1141                        jwk.encode_string_field(
1142                            JwkStringField::Pub,
1143                            public_key.to_bytes().as_slice(),
1144                        );
1145                    },
1146                    Handle::MlDsa65PublicKey(public_key) => {
1147                        jwk.encode_string_field(
1148                            JwkStringField::Pub,
1149                            public_key.to_bytes().as_slice(),
1150                        );
1151                    },
1152                    Handle::MlDsa87PublicKey(public_key) => {
1153                        jwk.encode_string_field(
1154                            JwkStringField::Pub,
1155                            public_key.to_bytes().as_slice(),
1156                        );
1157                    },
1158                    _ => {
1159                        return Err(Error::Operation(Some(
1160                            "The key handle is not representing an ML-DSA public key".into(),
1161                        )));
1162                    },
1163                };
1164            }
1165
1166            // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1167            jwk.set_key_ops(key.usages());
1168
1169            // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1170            jwk.ext = Some(key.Extractable());
1171
1172            // Step 3.9. Let result be jwk.
1173            ExportedKey::new_jwk(jwk)
1174        },
1175        // Otherwise:
1176        _ => {
1177            // throw a NotSupportedError.
1178            return Err(Error::NotSupported(Some(
1179                "Unsupported export key format for ML-DSA key".into(),
1180            )));
1181        },
1182    };
1183
1184    // Step 3. Return result.
1185    Ok(result)
1186}
1187
1188/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
1189/// Step 9 - 15, for ML-DSA
1190pub(crate) fn get_public_key(
1191    cx: &mut JSContext,
1192    global: &GlobalScope,
1193    key: &CryptoKey,
1194    algorithm: &KeyAlgorithmAndDerivatives,
1195    usages: Vec<KeyUsage>,
1196) -> Result<DomRoot<CryptoKey>, Error> {
1197    // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
1198    // identified by algorithm, then throw a SyntaxError.
1199    //
1200    // NOTE: See "importKey" operation for supported usages
1201    if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
1202        return Err(Error::Syntax(Some(
1203            "Usages contains an entry which is not \"verify\"".into(),
1204        )));
1205    }
1206
1207    // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
1208    // private key represented by the [[handle]] internal slot of key.
1209    // Step 11. If an error occurred, then throw a OperationError.
1210    // Step 12. Set the [[type]] internal slot of publicKey to "public".
1211    // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
1212    // Step 14. Set the [[extractable]] internal slot of publicKey to true.
1213    // Step 15. Set the [[usages]] internal slot of publicKey to usages.
1214    let public_key_handle = match key.handle() {
1215        Handle::MlDsa44PrivateKey(private_key) => {
1216            Handle::MlDsa44PublicKey(private_key.verifying_key())
1217        },
1218        Handle::MlDsa65PrivateKey(private_key) => {
1219            Handle::MlDsa65PublicKey(private_key.verifying_key())
1220        },
1221        Handle::MlDsa87PrivateKey(private_key) => {
1222            Handle::MlDsa87PublicKey(private_key.verifying_key())
1223        },
1224        _ => {
1225            return Err(Error::Operation(Some(
1226                "[[handle]] internal slot of key is not an ML-DSA private key".into(),
1227            )));
1228        },
1229    };
1230    let public_key = CryptoKey::new(
1231        cx,
1232        global,
1233        KeyType::Public,
1234        true,
1235        algorithm.clone(),
1236        usages,
1237        public_key_handle,
1238    );
1239
1240    Ok(public_key)
1241}