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