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