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