Skip to main content

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