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