Skip to main content

script/dom/webcrypto/subtlecrypto/
hybrid_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 x_wing::{
7    Decapsulate, DecapsulationKey, Decapsulator, Encapsulate, EncapsulationKey, Generate,
8    KeyExport, KeyInit, TryKeyInit,
9};
10
11use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
12    CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
13};
14use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
15use crate::dom::bindings::error::Error;
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::bindings::str::DOMString;
18use crate::dom::cryptokey::{CryptoKey, Handle, KeyUsageVecHelper};
19use crate::dom::globalscope::GlobalScope;
20use crate::dom::subtlecrypto::{
21    Algorithm, CryptoAlgorithm, EncapsulatedBits, ExportedKey, JsonWebKeyExt, JwkStringField,
22    KeyAlgorithm, KeyAlgorithmAndDerivatives,
23};
24
25/// <https://wicg.github.io/webcrypto-modern-algos/#hybrid-kems-operations-encapsulate>
26pub(crate) fn encapsulate(
27    normalized_algorithm: &Algorithm,
28    key: &CryptoKey,
29) -> Result<EncapsulatedBits, Error> {
30    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
31    // InvalidAccessError.
32    if key.Type() != KeyType::Public {
33        return Err(Error::InvalidAccess(Some(
34            "[[type]] internal slot of key is not \"public\"".into(),
35        )));
36    }
37
38    // Step 2. Let sharedKey and ciphertext be the outputs that result from performing the Encaps
39    // function for the hybrid KEM instance indicated by the name member of algorithm in Section 4
40    // of [draft-irtf-cfrg-concrete-hybrid-kems-04], using the key represented by the [[handle]]
41    // internal slot of key as the ek input parameter.
42    // Step 3. If the Encaps function returned an error, return an OperationError.
43    let (shared_key, ciphertext) = match normalized_algorithm.name {
44        CryptoAlgorithm::MlKem768X25519 => {
45            let Handle::MlKem768X25519PublicKey(public_key) = key.handle() else {
46                return Err(Error::Operation(Some(
47                    "The key handle is not representing a MLKEM768-X25519 public key".into(),
48                )));
49            };
50            let (ciphertext, shared_key) = public_key.encapsulate();
51            (shared_key.to_vec(), ciphertext.to_vec())
52        },
53        name => {
54            return Err(Error::NotSupported(Some(format!(
55                "{} is not a hybrid KEM algorithm",
56                name.as_str()
57            ))));
58        },
59    };
60
61    // Step 4. Let result be a new EncapsulatedBits dictionary.
62    // Step 5. Set the sharedKey attribute of result to the result of creating an ArrayBuffer
63    // containing sharedKey.
64    // Step 6. Set the ciphertext attribute of result to the result of creating an ArrayBuffer
65    // containing ciphertext.
66    let result = EncapsulatedBits {
67        shared_key: Some(shared_key.into()),
68        ciphertext: Some(ciphertext),
69    };
70
71    // Step 7. Return result.
72    Ok(result)
73}
74
75/// <https://wicg.github.io/webcrypto-modern-algos/#hybrid-kems-operations-decapsulate>
76pub(crate) fn decapsulate(
77    normalized_algorithm: &Algorithm,
78    key: &CryptoKey,
79    ciphertext: &[u8],
80) -> Result<Vec<u8>, Error> {
81    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
82    // InvalidAccessError.
83    if key.Type() != KeyType::Private {
84        return Err(Error::InvalidAccess(Some(
85            "[[type]] internal slot of key is not \"private\"".into(),
86        )));
87    }
88
89    // Step 2. Let sharedKey be the output that results from performing the Decaps function for the
90    // hybrid KEM instance indicated by the name member of algorithm in Section 4 of
91    // [draft-irtf-cfrg-concrete-hybrid-kems-04], using the key represented by the [[handle]]
92    // internal slot of key as the dk input parameter, and ciphertext as the ct input parameter.
93    // Step 3. If the Decaps function returned an error, return an OperationError.
94    let shared_key = match normalized_algorithm.name {
95        CryptoAlgorithm::MlKem768X25519 => {
96            let Handle::MlKem768X25519PrivateKey(private_key) = key.handle() else {
97                return Err(Error::Operation(Some(
98                    "The key handle is not representing an MLKEM768-X25519 private key".into(),
99                )));
100            };
101            private_key
102                .decapsulate_slice(ciphertext)
103                .map_err(|_| {
104                    Error::Operation(Some(
105                        "Failed to perform MLKEM768-X25519 decapsulation".into(),
106                    ))
107                })?
108                .to_vec()
109        },
110        name => {
111            return Err(Error::NotSupported(Some(format!(
112                "{} is not a hybrid KEM algorithm",
113                name.as_str()
114            ))));
115        },
116    };
117
118    // Step 4. Return sharedKey.
119    Ok(shared_key)
120}
121
122/// <https://wicg.github.io/webcrypto-modern-algos/#hybrid-kems-operations-get-shared-key-length>
123pub(crate) fn get_shared_key_length() -> u32 {
124    // Step 1. Return 256.
125    256
126}
127
128/// <https://wicg.github.io/webcrypto-modern-algos/#ml-kem-operations-generate-key>
129pub(crate) fn generate_key(
130    cx: &mut JSContext,
131    global: &GlobalScope,
132    normalized_algorithm: &Algorithm,
133    extractable: bool,
134    usages: Vec<KeyUsage>,
135) -> Result<CryptoKeyPair, Error> {
136    // Step 1. If usages contains an entry which is not one of "encapsulateKey", "encapsulateBits",
137    // "decapsulateKey" or "decapsulateBits", then throw a SyntaxError.
138    if usages.iter().any(|usage| {
139        !matches!(
140            usage,
141            KeyUsage::EncapsulateKey |
142                KeyUsage::EncapsulateBits |
143                KeyUsage::DecapsulateKey |
144                KeyUsage::DecapsulateBits
145        )
146    }) {
147        return Err(Error::Syntax(Some(
148            "Usages contains any entry which is not one of \"encapsulateKey\", \
149            \"encapsulateBits\", \"decapsulateKey\" or \"decapsulateBits\""
150                .into(),
151        )));
152    }
153
154    // Step 2. Generate an ML-KEM key pair, as described in Section 7.1 of [FIPS-203], with the
155    // parameter set indicated by the name member of normalizedAlgorithm.
156    // Step 3. If the key generation step fails, then throw an OperationError.
157    let (private_key_handle, public_key_handle) = match normalized_algorithm.name {
158        CryptoAlgorithm::MlKem768X25519 => {
159            let decapsulation_key = DecapsulationKey::generate();
160            let encapsulation_key = decapsulation_key.encapsulation_key().clone();
161            (
162                Handle::MlKem768X25519PrivateKey(decapsulation_key),
163                Handle::MlKem768X25519PublicKey(encapsulation_key),
164            )
165        },
166        name => {
167            return Err(Error::NotSupported(Some(format!(
168                "{} is not a hybrid KEM algorithm",
169                name.as_str()
170            ))));
171        },
172    };
173
174    // Step 4. Let algorithm be a new KeyAlgorithm object.
175    // Step 5. Set the name attribute of algorithm to the name attribute of normalizedAlgorithm.
176    let algorithm = KeyAlgorithm {
177        name: normalized_algorithm.name,
178    };
179
180    // Step 6. Let publicKey be a new CryptoKey representing the encapsulation key of the generated
181    // key pair.
182    // Step 7. Set the [[type]] internal slot of publicKey to "public".
183    // Step 8. Set the [[algorithm]] internal slot of publicKey to algorithm.
184    // Step 9. Set the [[extractable]] internal slot of publicKey to true.
185    // Step 10. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages
186    // and [ "encapsulateKey", "encapsulateBits" ].
187    let public_key = CryptoKey::new(
188        cx,
189        global,
190        KeyType::Public,
191        true,
192        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
193        usages.usage_intersection(&[KeyUsage::EncapsulateKey, KeyUsage::EncapsulateBits]),
194        public_key_handle,
195    );
196
197    // Step 11. Let privateKey be a new CryptoKey representing the decapsulation key of the
198    // generated key pair.
199    // Step 12. Set the [[type]] internal slot of privateKey to "private".
200    // Step 13. Set the [[algorithm]] internal slot of privateKey to algorithm.
201    // Step 14. Set the [[extractable]] internal slot of privateKey to extractable.
202    // Step 15. Set the [[usages]] internal slot of privateKey to be the usage intersection of
203    // usages and [ "decapsulateKey", "decapsulateBits" ].
204    let private_key = CryptoKey::new(
205        cx,
206        global,
207        KeyType::Private,
208        extractable,
209        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
210        usages.usage_intersection(&[KeyUsage::DecapsulateKey, KeyUsage::DecapsulateBits]),
211        private_key_handle,
212    );
213
214    // Step 16. Let result be a new CryptoKeyPair dictionary.
215    // Step 17. Set the publicKey attribute of result to be publicKey.
216    // Step 18. Set the privateKey attribute of result to be privateKey.
217    let result = CryptoKeyPair {
218        publicKey: Some(public_key),
219        privateKey: Some(private_key),
220    };
221
222    // Step 19. Return result.
223    Ok(result)
224}
225
226/// <https://wicg.github.io/webcrypto-modern-algos/#hybrid-kems-operations-import-key>
227pub(crate) fn import_key(
228    cx: &mut JSContext,
229    global: &GlobalScope,
230    normalized_algorithm: &Algorithm,
231    format: KeyFormat,
232    key_data: &[u8],
233    extractable: bool,
234    usages: Vec<KeyUsage>,
235) -> Result<DomRoot<CryptoKey>, Error> {
236    // Step 1. Let keyData be the key data to be imported.
237
238    // Step 2.
239    let key =
240        match format {
241            // If format is "raw-public":
242            KeyFormat::Raw_public => {
243                // Step 2.1. If usages contains an entry which is not "encapsulateKey" or
244                // "encapsulateBits" then throw a SyntaxError.
245                if usages.iter().any(|usage| {
246                    !matches!(usage, KeyUsage::EncapsulateKey | KeyUsage::EncapsulateBits)
247                }) {
248                    return Err(Error::Syntax(Some(
249                        "Usages contains an entry which is not \"encapsulateKey\" or \
250                        \"encapsulateBits\""
251                            .into(),
252                    )));
253                }
254
255                // Step 2.2. Let data be keyData.
256                let data = key_data;
257
258                // Step 2.3. If the length in bytes of data is not the raw public key length, Nek, for
259                // the hybrid KEM instance indicated by the name member of normalizedAlgorithm in
260                // Section 4 of [draft-irtf-cfrg-concrete-hybrid-kems-04], then throw a DataError.
261                // Step 2.4. Let key be a new CryptoKey that represents the hybrid KEM public key data
262                // in data.
263                // Step 2.5. Set the [[type]] internal slot of key to "public"
264                // Step 2.6. Let algorithm be a new KeyAlgorithm.
265                // Step 2.7. Set the name attribute of algorithm to the name attribute of
266                // normalizedAlgorithm.
267                // Step 2.8. Set the [[algorithm]] internal slot of key to algorithm.
268                let public_key = match normalized_algorithm.name {
269                    CryptoAlgorithm::MlKem768X25519 => {
270                        if key_data.len() != 1216 {
271                            return Err(Error::Data(Some(
272                                "Invalid key length for MLKEM768-X25519 public key".into(),
273                            )));
274                        }
275                        let encapsulation_key =
276                            EncapsulationKey::new_from_slice(data).map_err(|_| {
277                                Error::Data(Some(
278                                    "Failed to parse the public MLKEM768-X25519 key in raw format"
279                                        .into(),
280                                ))
281                            })?;
282                        Handle::MlKem768X25519PublicKey(encapsulation_key)
283                    },
284                    name => {
285                        return Err(Error::NotSupported(Some(format!(
286                            "{} is not a hybrid KEM algorithm",
287                            name.as_str()
288                        ))));
289                    },
290                };
291                let algorithm = KeyAlgorithm {
292                    name: normalized_algorithm.name,
293                };
294                CryptoKey::new(
295                    cx,
296                    global,
297                    KeyType::Public,
298                    extractable,
299                    KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
300                    usages.normalized_value(),
301                    public_key,
302                )
303            },
304            // If format is "raw-seed":
305            KeyFormat::Raw_seed => {
306                // Step 2.1. If usages contains an entry which is not "decapsulateKey" or
307                // "decapsulateBits" then throw a SyntaxError.
308                if usages.iter().any(|usage| {
309                    !matches!(usage, KeyUsage::DecapsulateKey | KeyUsage::DecapsulateBits)
310                }) {
311                    return Err(Error::Syntax(Some(
312                        "Usages contains an entry which is not \"decapsulateKey\" or \
313                        \"decapsulateBits\""
314                            .into(),
315                    )));
316                }
317
318                // Step 2.2. Let data be keyData.
319                let data = key_data;
320
321                // Step 2.3. If the length in bits of data is not 256 then throw a DataError.
322                if data.len() != 32 {
323                    return Err(Error::Data(Some(
324                        "The length in bits of data is not 256".into(),
325                    )));
326                }
327
328                // Step 2.4. Let keyPair be the result of performing the DeriveKeyPair function
329                // described in Section 5.5 of [draft-irtf-cfrg-hybrid-kems-12] with the hybrid KEM
330                // instance indicated by the name member of normalizedAlgorithm, using data as the seed
331                // input parameter.
332                // Step 2.5. If the DeriveKeyPair function returned an error, then throw an
333                // OperationError.
334                let private_key = match normalized_algorithm.name {
335                    CryptoAlgorithm::MlKem768X25519 => {
336                        let decapsulation_key = DecapsulationKey::new_from_slice(key_data)
337                            .map_err(|_| {
338                                Error::Data(Some(
339                                    "Failed to parse the private MLKEM768-X25519 key in raw format"
340                                        .into(),
341                                ))
342                            })?;
343                        Handle::MlKem768X25519PrivateKey(decapsulation_key)
344                    },
345                    name => {
346                        return Err(Error::NotSupported(Some(format!(
347                            "{} is not a hybrid KEM algorithm",
348                            name.as_str()
349                        ))));
350                    },
351                };
352
353                // Step 2.6. Let key be a new CryptoKey that represents the hybrid KEM private key
354                // identified by the decapsulation key of keyPair.
355                // Step 2.7. Set the [[type]] internal slot of key to "private"
356                // Step 2.8. Let algorithm be a new KeyAlgorithm.
357                // Step 2.9. Set the name attribute of algorithm to the name attribute of
358                // normalizedAlgorithm.
359                // Step 2.10. Set the [[algorithm]] internal slot of key to algorithm.
360                let algorithm = KeyAlgorithm {
361                    name: normalized_algorithm.name,
362                };
363                CryptoKey::new(
364                    cx,
365                    global,
366                    KeyType::Private,
367                    extractable,
368                    KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
369                    usages.normalized_value(),
370                    private_key,
371                )
372            },
373            // If format is "jwk":
374            KeyFormat::Jwk => {
375                // Step 2.1.
376                // If keyData is a JsonWebKey dictionary:
377                //     Let jwk equal keyData.
378                // Otherwise:
379                //     Throw a DataError.
380                let jwk = JsonWebKey::parse(cx, key_data)?;
381
382                // Step 2.2. If the priv field of jwk is present and if usages contains an entry which
383                // is not "decapsulateKey" or "decapsulateBits" then throw a SyntaxError.
384                if jwk.priv_.is_some() &&
385                    usages.iter().any(|usage| {
386                        !matches!(usage, KeyUsage::DecapsulateKey | KeyUsage::DecapsulateBits)
387                    })
388                {
389                    return Err(Error::Syntax(Some(
390                        "The priv field of jwk is present and usages contains an entry which is \
391                        not \"decapsulateKey\" or \"decapsulateBits\""
392                            .into(),
393                    )));
394                }
395
396                // Step 2.3. If the priv field of jwk is not present and if usages contains an entry
397                // which is not "encapsulateKey" or "encapsulateBits" then throw a SyntaxError.
398                if jwk.priv_.is_none() &&
399                    usages.iter().any(|usage| {
400                        !matches!(usage, KeyUsage::EncapsulateKey | KeyUsage::EncapsulateBits)
401                    })
402                {
403                    return Err(Error::Syntax(Some(
404                        "The priv field of jwk is not present and usages contains an entry which \
405                        is not \"encapsulateKey\" or \"encapsulateBits\""
406                            .into(),
407                    )));
408                }
409
410                // Step 2.4. If the kty field of jwk is not "AKP", then throw a DataError.
411                if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
412                    return Err(Error::Data(Some(
413                        "The kty field of jwk is not \"AKP\"".into(),
414                    )));
415                }
416
417                // Step 2.5. If the alg field of jwk is not present, or its value does not identify the
418                // hybrid KEM instance indicated by the name member of normalizedAlgorithm, then throw a
419                // DataError.
420                match normalized_algorithm.name {
421                    CryptoAlgorithm::MlKem768X25519 => {
422                        if jwk.alg.as_ref().is_none_or(|alg| alg != "MLKEM768-X25519") {
423                            return Err(Error::Data(Some(
424                                "The alg field of jwk is not invalid.".into(),
425                            )));
426                        }
427                    },
428                    name => {
429                        return Err(Error::NotSupported(Some(format!(
430                            "{} is not a hybrid KEM algorithm",
431                            name.as_str()
432                        ))));
433                    },
434                }
435
436                // Step 2.6. If usages is non-empty and the use field of jwk is present and is not equal
437                // to "enc", then throw a DataError.
438                if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
439                    return Err(Error::Data(Some(
440                        "Usages is non-empty and the use field of jwk is present and is not \
441                        equal to \"enc\""
442                            .into(),
443                    )));
444                }
445
446                // Step 2.7. If the key_ops field of jwk is present, and is invalid according to the
447                // requirements of JSON Web Key [JWK], or it does not contain all of the specified
448                // usages values, then throw a DataError.
449                jwk.check_key_ops(&usages)?;
450
451                // Step 2.8. If the ext field of jwk is present and has the value false and extractable
452                // is true, then throw a DataError.
453                if jwk.ext.is_some_and(|ext| !ext) && extractable {
454                    return Err(Error::Data(Some(
455                        "The ext field of jwk is present and has the value false and extractable \
456                        is true"
457                            .into(),
458                    )));
459                }
460
461                // Step 2.9.
462                // If the priv field of jwk is present:
463                let (key_type, key_handle) = if jwk.priv_.is_some() {
464                    // Step 2.9.1. If the priv attribute of jwk does not contain a valid base64url
465                    // encoded 32-byte seed representing a hybrid KEM private key, then throw a
466                    // DataError.
467                    let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
468                    if priv_bytes.len() != 32 {
469                        return Err(Error::Data(Some(
470                            "The priv attribute of jwk does not contain a valid base64url \
471                            encoded 32-byte seed"
472                                .into(),
473                        )));
474                    }
475
476                    // Step 2.9.2. Let key be a new CryptoKey object that represents the hybrid KEM
477                    // private key identified by interpreting the priv attribute of jwk as a base64url
478                    // encoded seed.
479                    // Step 2.9.3. Set the [[type]] internal slot of key to "private".
480                    // Step 2.9.4. If the pub attribute of jwk does not contain the base64url encoded
481                    // public key representing the hybrid KEM public key corresponding to key, then
482                    // throw a DataError.
483                    // NOTE: The CryptoKey object is created in Step 2.10 - 2.12.
484                    let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
485                    let private_key_handle = match normalized_algorithm.name {
486                        CryptoAlgorithm::MlKem768X25519 => {
487                            let decapsulation_key = DecapsulationKey::new_from_slice(&priv_bytes)
488                                .map_err(|_| {
489                                Error::Data(Some(
490                                "Failed to parse the private MLKEM768-X25519 key in priv attribute"
491                                    .into(),
492                            ))
493                            })?;
494                            let encapsulation_key = EncapsulationKey::new_from_slice(&pub_bytes)
495                                .map_err(|_| {
496                                    Error::Data(Some(
497                                "Failed to parse the public MLKEM768-X25519 key in pub attribute"
498                                    .into(),
499                            ))
500                                })?;
501                            if *decapsulation_key.encapsulation_key() != encapsulation_key {
502                                return Err(Error::Data(Some(
503                                    "The public key in pub attribute does not match \
504                                    the private key in priv attribute"
505                                        .into(),
506                                )));
507                            }
508                            Handle::MlKem768X25519PrivateKey(decapsulation_key)
509                        },
510                        name => {
511                            return Err(Error::NotSupported(Some(format!(
512                                "{} is not a hybrid KEM algorithm",
513                                name.as_str()
514                            ))));
515                        },
516                    };
517                    (KeyType::Private, private_key_handle)
518                }
519                // Otherwise:
520                else {
521                    // Step 2.9.1. If the pub attribute of jwk does not contain a valid base64url
522                    // encoded raw public key whose length is Nek for the hybrid KEM instance indicated
523                    // by the name member of normalizedAlgorithm in Section 4 of
524                    // [draft-irtf-cfrg-concrete-hybrid-kems-04], then throw a DataError.
525                    // Step 2.9.2. Let key be a new CryptoKey object that represents the hybrid KEM
526                    // public key identified by interpreting the pub attribute of jwk as a base64url
527                    // encoded public key.
528                    // Step 2.9.3. Set the [[type]] internal slot of key to "public".
529                    // NOTE: The CryptoKey object is created in Step 2.10 - 2.12.
530                    let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
531                    let public_key_handle = match normalized_algorithm.name {
532                        CryptoAlgorithm::MlKem768X25519 => {
533                            if pub_bytes.len() != 1216 {
534                                return Err(Error::Data(Some(
535                                    "The pub attribute of jwk does not contain a valid base64url \
536                                    encoded raw public key with valid length"
537                                        .into(),
538                                )));
539                            }
540                            let encapsulation_key = EncapsulationKey::new_from_slice(&pub_bytes)
541                                .map_err(|_| {
542                                    Error::Data(Some(
543                                        "Failed to parse the public MLKEM768-X25519 key in pub \
544                                        attribute"
545                                            .into(),
546                                    ))
547                                })?;
548                            Handle::MlKem768X25519PublicKey(encapsulation_key)
549                        },
550                        name => {
551                            return Err(Error::NotSupported(Some(format!(
552                                "{} is not a hybrid KEM algorithm",
553                                name.as_str()
554                            ))));
555                        },
556                    };
557                    (KeyType::Public, public_key_handle)
558                };
559
560                // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
561                // Step 2.11. Set the name attribute of algorithm to the name member of
562                // normalizedAlgorithm.
563                // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
564                let algorithm = KeyAlgorithm {
565                    name: normalized_algorithm.name,
566                };
567                CryptoKey::new(
568                    cx,
569                    global,
570                    key_type,
571                    extractable,
572                    KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
573                    usages.normalized_value(),
574                    key_handle,
575                )
576            },
577            // Otherwise:
578            _ => {
579                // throw a NotSupportedError.
580                return Err(Error::NotSupported(Some(
581                    "Unsupported import key format for ML-KEM key".into(),
582                )));
583            },
584        };
585
586    // Step 3. Return key.
587    Ok(key)
588}
589
590/// <https://wicg.github.io/webcrypto-modern-algos/#hybrid-kems-operations-export-key>
591pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
592    // Step 1. Let key be the CryptoKey to be exported.
593
594    // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
595    // slot of key cannot be accessed, then throw an OperationError.
596    // NOTE: Done in Step 3.
597
598    // Step 3.
599    let result = match format {
600        // If format is "raw-public":
601        KeyFormat::Raw_public => {
602            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
603            // InvalidAccessError.
604            if key.Type() != KeyType::Public {
605                return Err(Error::InvalidAccess(Some(
606                    "[[type]] internal slot of key is not \"public\"".into(),
607                )));
608            }
609
610            // Step 3.2. Let data be a byte sequence containing the raw octets of the key
611            // represented by the [[handle]] internal slot of key.
612            let data = match key.handle() {
613                Handle::MlKem768X25519PublicKey(public_key) => public_key.to_bytes().to_vec(),
614                _ => {
615                    return Err(Error::Operation(Some(
616                        "The key handle is not representing a hybrid KEM public key".into(),
617                    )));
618                },
619            };
620
621            // Step 3.3. Let result be data.
622            ExportedKey::new_bytes(data)
623        },
624        // If format is "raw-seed":
625        KeyFormat::Raw_seed => {
626            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
627            // InvalidAccessError.
628            if key.Type() != KeyType::Private {
629                return Err(Error::InvalidAccess(Some(
630                    "[[type]] internal slot of key is not \"private\"".into(),
631                )));
632            }
633
634            // Step 3.2. Let data be a byte sequence containing the 32-byte seed represented by the
635            // [[handle]] internal slot of key.
636            let data = match key.handle() {
637                Handle::MlKem768X25519PrivateKey(private_key) => private_key.as_bytes().to_vec(),
638                _ => {
639                    return Err(Error::Operation(Some(
640                        "The key handle is not representing a hybrid KEM private key".into(),
641                    )));
642                },
643            };
644
645            // Step 3.3. Let result be data.
646            ExportedKey::new_bytes(data)
647        },
648        // If format is "jwk":
649        KeyFormat::Jwk => {
650            // Step 3.1. Let jwk be a new JsonWebKey dictionary.
651            let mut jwk = JsonWebKey::default();
652
653            // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
654            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
655                return Err(Error::Operation(Some(
656                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
657                )));
658            };
659
660            // Step 3.3. Set the kty attribute of jwk to "AKP".
661            jwk.kty = Some(DOMString::from_static("AKP"));
662
663            // Step 3.4. Set the alg attribute of jwk to the name member of keyAlgorithm.
664            jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
665
666            // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
667            // corresponding to the [[handle]] internal slot of key.
668            // Step 3.6.
669            // If the [[type]] internal slot of key is "private":
670            //     Set the priv attribute of jwk to the base64url encoded 32-byte seed represented
671            //     by the [[handle]] internal slot of key.
672            if key.Type() == KeyType::Private {
673                match key.handle() {
674                    Handle::MlKem768X25519PrivateKey(private_key) => {
675                        jwk.encode_string_field(JwkStringField::Priv, private_key.as_bytes());
676                        jwk.encode_string_field(
677                            JwkStringField::Pub,
678                            private_key.encapsulation_key().to_bytes().as_slice(),
679                        );
680                    },
681                    _ => {
682                        return Err(Error::Operation(Some(
683                            "The key handle is not representing a hybrid KEM private key".into(),
684                        )));
685                    },
686                }
687            } else {
688                match key.handle() {
689                    Handle::MlKem768X25519PublicKey(public_key) => {
690                        jwk.encode_string_field(
691                            JwkStringField::Pub,
692                            public_key.to_bytes().as_slice(),
693                        );
694                    },
695                    _ => {
696                        return Err(Error::Operation(Some(
697                            "The key handle is not representing a hybrid KEM public key".into(),
698                        )));
699                    },
700                };
701            }
702
703            // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
704            jwk.set_key_ops(key.usages());
705
706            // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
707            jwk.ext = Some(key.Extractable());
708
709            // Step 3.9. Let result be jwk.
710            ExportedKey::new_jwk(jwk)
711        },
712        // Otherwise:
713        _ => {
714            // throw a NotSupportedError.
715            return Err(Error::NotSupported(Some(
716                "Unsupported export key format for hybrid KEM key".into(),
717            )));
718        },
719    };
720
721    // Step 4.  Return result.
722    Ok(result)
723}