Skip to main content

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