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