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}