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