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