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