Skip to main content

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