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