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}