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