script/dom/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 //
561 // NOTE: There is an ongoing discussion on how to handle private keys in the
562 // "expandedKey" and "both" formats.
563 // - <https://github.com/WICG/webcrypto-modern-algos/issues/29>
564 // - <https://github.com/WICG/webcrypto-modern-algos/pull/34>
565 // For now, we accept the "seed" format, reject the "expandedKey" format with a
566 // NotSupportedError, and accept the "both" format subject to a consistency check. This
567 // behavior may change in the future once the discussion is settled.
568 let private_key_structure =
569 MlKemPrivateKeyStructure::from_der(private_key_info.private_key).map_err(|_| {
570 Error::Data(Some(
571 "Failed to parse privateKey field of PrivateKeyInfo".to_string(),
572 ))
573 })?;
574 let ml_kem_private_key = match private_key_structure {
575 MlKemPrivateKeyStructure::Seed(seed) => {
576 let (private_key_handle, _) = convert_seed_to_handles(
577 normalized_algorithm.name,
578 seed.as_bytes(),
579 None,
580 None,
581 )?;
582 private_key_handle
583 },
584 MlKemPrivateKeyStructure::ExpandedKey(_) => {
585 return Err(Error::NotSupported(Some(
586 "Not support \"expandedKey\" format of ASN.1 ML-KEM private key structures"
587 .to_string(),
588 )));
589 },
590 MlKemPrivateKeyStructure::Both(both) => {
591 let (private_key_handle, _) = convert_seed_to_handles(
592 normalized_algorithm.name,
593 both.seed.as_bytes(),
594 Some(both.expanded_key.as_bytes()),
595 None,
596 )?;
597 private_key_handle
598 },
599 };
600
601 // Step 2.9. Let key be a new CryptoKey that represents the ML-KEM private key
602 // identified by mlKemPrivateKey.
603 // Step 2.10. Set the [[type]] internal slot of key to "private"
604 // Step 2.11. Let algorithm be a new KeyAlgorithm.
605 // Step 2.12. Set the name attribute of algorithm to the name attribute of
606 // normalizedAlgorithm.
607 // Step 2.13. Set the [[algorithm]] internal slot of key to algorithm.
608 let algorithm = SubtleKeyAlgorithm {
609 name: normalized_algorithm.name,
610 };
611 CryptoKey::new(
612 cx,
613 global,
614 KeyType::Private,
615 extractable,
616 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
617 usages,
618 ml_kem_private_key,
619 )
620 },
621 // If format is "raw-public":
622 KeyFormat::Raw_public => {
623 // Step 2.1. If usages contains an entry which is not "encapsulateKey" or
624 // "encapsulateBits" then throw a SyntaxError.
625 if usages
626 .iter()
627 .any(|usage| !matches!(usage, KeyUsage::EncapsulateKey | KeyUsage::EncapsulateBits))
628 {
629 return Err(Error::Syntax(Some(
630 "Usages contains an entry which is not \"encapsulateKey\" or \
631 \"encapsulateBits\""
632 .to_string(),
633 )));
634 }
635
636 // Step 2.2. Let data be keyData.
637 // Step 2.3. Let key be a new CryptoKey that represents the ML-KEM public key data in
638 // data.
639 // Step 2.4. Set the [[type]] internal slot of key to "public"
640 // Step 2.5. Let algorithm be a new KeyAlgorithm.
641 // Step 2.6. Set the name attribute of algorithm to the name attribute of
642 // normalizedAlgorithm.
643 // Step 2.7. Set the [[algorithm]] internal slot of key to algorithm.
644 let public_key_handle =
645 convert_public_key_to_handle(normalized_algorithm.name, key_data)?;
646 let algorithm = SubtleKeyAlgorithm {
647 name: normalized_algorithm.name,
648 };
649 CryptoKey::new(
650 cx,
651 global,
652 KeyType::Public,
653 extractable,
654 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
655 usages,
656 public_key_handle,
657 )
658 },
659 // If format is "raw-seed":
660 KeyFormat::Raw_seed => {
661 // Step 2.1. If usages contains an entry which is not "decapsulateKey" or
662 // "decapsulateBits" then throw a SyntaxError.
663 if usages
664 .iter()
665 .any(|usage| !matches!(usage, KeyUsage::DecapsulateKey | KeyUsage::DecapsulateBits))
666 {
667 return Err(Error::Syntax(Some(
668 "Usages contains an entry which is not \"decapsulateKey\" or \
669 \"decapsulateBits\""
670 .to_string(),
671 )));
672 }
673
674 // Step 2.2. Let data be keyData.
675 let data = key_data;
676
677 // Step 2.3. If the length in bits of data is not 512 then throw a DataError.
678 // Step 2.4. Let privateKey be the result of performing the ML-KEM.KeyGen_internal
679 // function described in Section 6.1 of [FIPS-203] with the parameter set indicated by
680 // the name member of normalizedAlgorithm, using the first 256 bits of data as d and
681 // the last 256 bits of data as z.
682 let (private_key_handle, _) =
683 convert_seed_to_handles(normalized_algorithm.name, data, None, None)?;
684
685 // Step 2.5. Let key be a new CryptoKey that represents the ML-KEM private key
686 // identified by privateKey.
687 // Step 2.6. Set the [[type]] internal slot of key to "private"
688 // Step 2.7. Let algorithm be a new KeyAlgorithm.
689 // Step 2.8. Set the name attribute of algorithm to the name attribute of
690 // normalizedAlgorithm.
691 // Step 2.9. Set the [[algorithm]] internal slot of key to algorithm.
692 let algorithm = SubtleKeyAlgorithm {
693 name: normalized_algorithm.name,
694 };
695 CryptoKey::new(
696 cx,
697 global,
698 KeyType::Private,
699 extractable,
700 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
701 usages,
702 private_key_handle,
703 )
704 },
705 // If format is "jwk":
706 KeyFormat::Jwk => {
707 // Step 2.1.
708 // If keyData is a JsonWebKey dictionary:
709 // Let jwk equal keyData.
710 // Otherwise:
711 // Throw a DataError.
712 let jwk = JsonWebKey::parse(cx, key_data)?;
713
714 // Step 2.2. If the priv field of jwk is present and if usages contains an entry which
715 // is not "decapsulateKey" or "decapsulateBits" then throw a SyntaxError.
716 if jwk.priv_.is_some() &&
717 usages.iter().any(|usage| {
718 !matches!(usage, KeyUsage::DecapsulateKey | KeyUsage::DecapsulateBits)
719 })
720 {
721 return Err(Error::Syntax(Some(
722 "The priv field of jwk is present and usages contains an entry which is \
723 not \"decapsulateKey\" or \"decapsulateBits\""
724 .to_string(),
725 )));
726 }
727
728 // Step 2.3. If the priv field of jwk is not present and if usages contains an entry
729 // which is not "encapsulateKey" or "encapsulateBits" then throw a SyntaxError.
730 if jwk.priv_.is_none() &&
731 usages.iter().any(|usage| {
732 !matches!(usage, KeyUsage::EncapsulateKey | KeyUsage::EncapsulateBits)
733 })
734 {
735 return Err(Error::Syntax(Some(
736 "The priv field of jwk is not present and usages contains an entry which is \
737 not \"encapsulateKey\" or \"encapsulateBits\""
738 .to_string(),
739 )));
740 }
741
742 // Step 2.4. If the kty field of jwk is not "AKP", then throw a DataError.
743 if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
744 return Err(Error::Data(Some(
745 "The kty field of jwk is not \"AKP\"".to_string(),
746 )));
747 }
748
749 // Step 2.5. If the alg field of jwk is not one of the alg values corresponding to the
750 // name member of normalizedAlgorithm indicated in Section 8 of
751 // [draft-ietf-jose-pqc-kem-01] (Figure 1 or 2), then throw a DataError.
752 match normalized_algorithm.name {
753 CryptoAlgorithm::MlKem512 => {
754 if jwk
755 .alg
756 .as_ref()
757 .is_none_or(|alg| alg != "MLKEM512" && alg != "MLKEM512-AES128KW")
758 {
759 return Err(Error::Data(Some(
760 "The alg field of jwk is not invalid.".to_string(),
761 )));
762 }
763 },
764 CryptoAlgorithm::MlKem768 => {
765 if jwk
766 .alg
767 .as_ref()
768 .is_none_or(|alg| alg != "MLKEM768" && alg != "MLKEM768-AES192KW")
769 {
770 return Err(Error::Data(Some(
771 "The alg field of jwk is not invalid.".to_string(),
772 )));
773 }
774 },
775 CryptoAlgorithm::MlKem1024 => {
776 if jwk
777 .alg
778 .as_ref()
779 .is_none_or(|alg| alg != "MLKEM1024" && alg != "MLKEM1024-AES256KW")
780 {
781 return Err(Error::Data(Some(
782 "The alg field of jwk is not invalid.".to_string(),
783 )));
784 }
785 },
786 _ => {
787 return Err(Error::NotSupported(Some(format!(
788 "{} is not an ML-KEM algorithm",
789 normalized_algorithm.name.as_str()
790 ))));
791 },
792 };
793
794 // Step 2.6. If usages is non-empty and the use field of jwk is present and is not
795 // equal to "enc", then throw a DataError.
796 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
797 return Err(Error::Data(Some(
798 "usages is non-empty and the use field of jwk is present and is not \
799 equal to \"enc\""
800 .to_string(),
801 )));
802 }
803
804 // Step 2.7. If the key_ops field of jwk is present, and is invalid according to the
805 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
806 // usages values, then throw a DataError.
807 jwk.check_key_ops(&usages)?;
808
809 // Step 2.8. If the ext field of jwk is present and has the value false and extractable
810 // is true, then throw a DataError.
811 if jwk.ext.is_some_and(|ext| !ext) && extractable {
812 return Err(Error::Data(Some(
813 "The ext field of jwk is present and has the value false and extractable \
814 is true"
815 .to_string(),
816 )));
817 }
818
819 // Step 2.9.
820 // If the priv field of jwk is present:
821 let (key_type, key_handle) = if jwk.priv_.is_some() {
822 // Step 2.9.1. If the priv attribute of jwk does not contain a valid base64url
823 // encoded seed representing an ML-KEM private key, then throw a DataError.
824 let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
825
826 // Step 2.9.2. Let key be a new CryptoKey object that represents the ML-KEM private
827 // key identified by interpreting the priv attribute of jwk as a base64url encoded
828 // seed.
829 // Step 2.9.3. Set the [[type]] internal slot of Key to "private".
830 // Step 2.9.4. If the pub attribute of jwk does not contain the base64url encoded
831 // public key representing the ML-KEM public key corresponding to key, then throw a
832 // DataError.
833 // NOTE: Completed in Step 2.10 - 2.12.
834 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
835 let (private_key_handle, _) = convert_seed_to_handles(
836 normalized_algorithm.name,
837 &priv_bytes,
838 None,
839 Some(&pub_bytes),
840 )?;
841
842 (KeyType::Private, private_key_handle)
843 }
844 // Otherwise:
845 else {
846 // Step 2.9.1. If the pub attribute of jwk does not contain a valid base64url
847 // encoded public key representing an ML-KEM public key, then throw a DataError.
848 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
849
850 // Step 2.9.2. Let key be a new CryptoKey object that represents the ML-KEM public
851 // key identified by interpreting the pub attribute of jwk as a base64url encoded
852 // public key.
853 // Step 2.9.3. Set the [[type]] internal slot of Key to "public".
854 // NOTE: Completed in Step 2.10 - 2.12.
855 let public_key_handle =
856 convert_public_key_to_handle(normalized_algorithm.name, &pub_bytes)?;
857
858 (KeyType::Public, public_key_handle)
859 };
860
861 // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
862 // Step 2.11. Set the name attribute of algorithm to the name member of
863 // normalizedAlgorithm.
864 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
865 let algorithm = SubtleKeyAlgorithm {
866 name: normalized_algorithm.name,
867 };
868 CryptoKey::new(
869 cx,
870 global,
871 key_type,
872 extractable,
873 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
874 usages,
875 key_handle,
876 )
877 },
878 // Otherwise:
879 _ => {
880 // throw a NotSupportedError.
881 return Err(Error::NotSupported(Some(
882 "Unsupported import key format for ML-KEM key".to_string(),
883 )));
884 },
885 };
886
887 // Step 3. Return key.
888 Ok(key)
889}
890
891/// <https://wicg.github.io/webcrypto-modern-algos/#ml-kem-operations-export-key>
892pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
893 // Step 1. If the underlying cryptographic key material represented by the [[handle]] internal
894 // slot of key cannot be accessed, then throw an OperationError.
895
896 // Step 2.
897 let result = match format {
898 // If format is "spki":
899 KeyFormat::Spki => {
900 // Step 2.1. If the [[type]] internal slot of key is not "public", then throw an
901 // InvalidAccessError.
902 if key.Type() != KeyType::Public {
903 return Err(Error::InvalidAccess(Some(
904 "[[type]] internal slot of key is not \"public\"".to_string(),
905 )));
906 }
907
908 // Step 2.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
909 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
910 return Err(Error::Operation(Some(
911 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
912 )));
913 };
914
915 // Step 2.3.
916 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
917 // [RFC5280] with the following properties:
918 //
919 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
920 // properties:
921 //
922 // If the name member of keyAlgorithm is "ML-KEM-512":
923 // Set the algorithm object identifier to the id-alg-ml-kem-512
924 // (2.16.840.1.101.3.4.4.1) OID.
925 //
926 // If the name member of keyAlgorithm is "ML-KEM-768":
927 // Set the algorithm object identifier to the id-alg-ml-kem-768
928 // (2.16.840.1.101.3.4.4.2) OID.
929 //
930 // If the name member of keyAlgorithm is "ML-KEM-1024":
931 // Set the algorithm object identifier to the id-alg-ml-kem-1024
932 // (2.16.840.1.101.3.4.4.3) OID.
933 //
934 // Otherwise:
935 // throw a NotSupportedError.
936 //
937 // Set the subjectPublicKey field to keyData.
938 let oid = match key_algorithm.name {
939 CryptoAlgorithm::MlKem512 => ID_ALG_ML_KEM_512,
940 CryptoAlgorithm::MlKem768 => ID_ALG_ML_KEM_768,
941 CryptoAlgorithm::MlKem1024 => ID_ALG_ML_KEM_1024,
942 _ => {
943 return Err(Error::Operation(Some(format!(
944 "{} is not an ML-KEM algorithm",
945 key_algorithm.name.as_str()
946 ))));
947 },
948 };
949 let key_bytes = convert_handle_to_public_key(key.handle())?;
950 let subject_public_key = BitString::from_bytes(&key_bytes).map_err(|_| {
951 Error::Operation(Some(
952 "Failed to encode BitString for subjectPublicKey field of SubjectPublicKeyInfo"
953 .to_string(),
954 ))
955 })?;
956 let data = SubjectPublicKeyInfo {
957 algorithm: AlgorithmIdentifier::<AnyRef> {
958 oid: ObjectIdentifier::new_unwrap(oid),
959 parameters: None,
960 },
961 subject_public_key,
962 };
963
964 // Step 2.4. Let result be the result of DER-encoding data.
965 ExportedKey::Bytes(data.to_der().map_err(|_| {
966 Error::Operation(Some(
967 "Failed to encode SubjectPublicKeyInfo in DER format".to_string(),
968 ))
969 })?)
970 },
971 // If format is "pkcs8":
972 KeyFormat::Pkcs8 => {
973 // Step 2.1. If the [[type]] internal slot of key is not "private", then throw an
974 // InvalidAccessError.
975 if key.Type() != KeyType::Private {
976 return Err(Error::InvalidAccess(Some(
977 "[[type]] internal slot of key is not \"private\"".to_string(),
978 )));
979 }
980
981 // Step 2.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
982 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
983 return Err(Error::Operation(Some(
984 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
985 )));
986 };
987
988 // Step 2.3.
989 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
990 // with the following properties:
991 //
992 // Set the version field to 0.
993 //
994 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
995 // with the following properties:
996 //
997 // If the name member of keyAlgorithm is "ML-KEM-512":
998 // Set the algorithm object identifier to the id-alg-ml-kem-512
999 // (2.16.840.1.101.3.4.4.1) OID.
1000 //
1001 // If the name member of keyAlgorithm is "ML-KEM-768":
1002 // Set the algorithm object identifier to the id-alg-ml-kem-768
1003 // (2.16.840.1.101.3.4.4.2) OID.
1004 //
1005 // If the name member of keyAlgorithm is "ML-KEM-1024":
1006 // Set the algorithm object identifier to the id-alg-ml-kem-1024
1007 // (2.16.840.1.101.3.4.4.3) OID.
1008 //
1009 // Otherwise:
1010 // throw a NotSupportedError.
1011 //
1012 // Set the privateKey field as follows:
1013 //
1014 // If the name member of keyAlgorithm is "ML-KEM-512":
1015 // Set the privateKey field to the result of DER-encoding a
1016 // ML-KEM-512-PrivateKey ASN.1 type that represents the ML-KEM private key
1017 // seed represented by the [[handle]] internal slot of key using the
1018 // seed-only format (using a context-specific [0] primitive tag with an
1019 // implicit encoding of OCTET STRING).
1020 //
1021 // If the name member of keyAlgorithm is "ML-KEM-768":
1022 // Set the privateKey field to the result of DER-encoding a
1023 // ML-KEM-768-PrivateKey ASN.1 type that represents the ML-KEM private key
1024 // seed represented by the [[handle]] internal slot of key using the
1025 // seed-only format (using a context-specific [0] primitive tag with an
1026 // implicit encoding of OCTET STRING).
1027 //
1028 // If the name member of keyAlgorithm is "ML-KEM-1024":
1029 // Set the privateKey field to the result of DER-encoding a
1030 // ML-KEM-1024-PrivateKey ASN.1 type that represents the ML-KEM private key
1031 // seed represented by the [[handle]] internal slot of key using the
1032 // seed-only format (using a context-specific [0] primitive tag with an
1033 // implicit encoding of OCTET STRING).
1034 //
1035 // Otherwise:
1036 // throw a NotSupportedError.
1037 let oid = match key_algorithm.name {
1038 CryptoAlgorithm::MlKem512 => ID_ALG_ML_KEM_512,
1039 CryptoAlgorithm::MlKem768 => ID_ALG_ML_KEM_768,
1040 CryptoAlgorithm::MlKem1024 => ID_ALG_ML_KEM_1024,
1041 _ => {
1042 return Err(Error::Operation(Some(format!(
1043 "{} is not an ML-KEM algorithm",
1044 key_algorithm.name.as_str()
1045 ))));
1046 },
1047 };
1048 let (seed_bytes, _) = convert_handle_to_seed_and_public_key(key.handle())?;
1049 let private_key =
1050 MlKemPrivateKeyStructure::Seed(OctetString::new(seed_bytes).map_err(|_| {
1051 Error::Operation(Some(
1052 "Failed to encode OctetString for privateKey field of \
1053 ASN.1 ML-KEM private key structure"
1054 .to_string(),
1055 ))
1056 })?);
1057 let encoded_private_key = private_key.to_der().map_err(|_| {
1058 Error::Operation(Some(
1059 "Failed to encode ASN.1 ML-KEM private key structure in DER format".to_string(),
1060 ))
1061 })?;
1062 let private_key_info = PrivateKeyInfo {
1063 algorithm: AlgorithmIdentifier {
1064 oid: ObjectIdentifier::new_unwrap(oid),
1065 parameters: None,
1066 },
1067 private_key: &encoded_private_key,
1068 public_key: None,
1069 };
1070
1071 // Step 2.4. Let result be the result of DER-encoding data.
1072 ExportedKey::Bytes(private_key_info.to_der().map_err(|_| {
1073 Error::Operation(Some(
1074 "Failed to encode PrivateKeyInfo in DER format".to_string(),
1075 ))
1076 })?)
1077 },
1078 // If format is "raw-public":
1079 KeyFormat::Raw_public => {
1080 // Step 2.1. If the [[type]] internal slot of key is not "public", then throw an
1081 // InvalidAccessError.
1082 if key.Type() != KeyType::Public {
1083 return Err(Error::InvalidAccess(Some(
1084 "[[type]] internal slot of key is not \"public\"".to_string(),
1085 )));
1086 }
1087
1088 // Step 2.2. Let data be a byte sequence containing the raw octets of the key
1089 // represented by the [[handle]] internal slot of key.
1090 let data = convert_handle_to_public_key(key.handle())?;
1091
1092 // Step 2.3. Let result be data.
1093 ExportedKey::Bytes(data)
1094 },
1095 // If format is "raw-seed":
1096 KeyFormat::Raw_seed => {
1097 // Step 2.1. If the [[type]] internal slot of key is not "private", then throw an
1098 // InvalidAccessError.
1099 if key.Type() != KeyType::Private {
1100 return Err(Error::InvalidAccess(Some(
1101 "[[type]] internal slot of key is not \"private\"".to_string(),
1102 )));
1103 }
1104
1105 // Step 2.2. Let data be a byte sequence containing the concatenation of the d and z
1106 // seed variables of the key represented by the [[handle]] internal slot of key.
1107 let (data, _) = convert_handle_to_seed_and_public_key(key.handle())?;
1108
1109 // Step 2.3. Let result be data.
1110 ExportedKey::Bytes(data)
1111 },
1112 // If format is "jwk":
1113 KeyFormat::Jwk => {
1114 // The JWK format for ML-KEM is not standardized yet and thus subject to change.
1115
1116 // Step 2.1. Let jwk be a new JsonWebKey dictionary.
1117 let mut jwk = JsonWebKey::default();
1118
1119 // Step 2.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
1120 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1121 return Err(Error::Operation(Some(
1122 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1123 )));
1124 };
1125
1126 // Step 2.3. Set the kty attribute of jwk to "AKP".
1127 jwk.kty = Some(DOMString::from("AKP"));
1128
1129 // Step 2.4. Set the alg attribute of jwk to the alg value corresponding to the name
1130 // member of normalizedAlgorithm indicated in Section 8 of [draft-ietf-jose-pqc-kem-01]
1131 // (Figure 1).
1132 //
1133 // <https://www.ietf.org/archive/id/draft-ietf-jose-pqc-kem-01.html#direct-table>
1134 let alg = match key_algorithm.name {
1135 CryptoAlgorithm::MlKem512 => "MLKEM512",
1136 CryptoAlgorithm::MlKem768 => "MLKEM768",
1137 CryptoAlgorithm::MlKem1024 => "MLKEM1024",
1138 _ => {
1139 return Err(Error::Operation(Some(format!(
1140 "{} is not an ML-KEM algorithm",
1141 key_algorithm.name.as_str()
1142 ))));
1143 },
1144 };
1145 jwk.alg = Some(DOMString::from(alg));
1146
1147 // Step 2.5. Set the pub attribute of jwk to the base64url encoded public key
1148 // corresponding to the [[handle]] internal slot of key.
1149 // Step 2.6.
1150 // If the [[type]] internal slot of key is "private":
1151 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1152 // [[handle]] internal slot of key.
1153 if key.Type() == KeyType::Private {
1154 let (seed_bytes, public_key_bytes) =
1155 convert_handle_to_seed_and_public_key(key.handle())?;
1156 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1157 jwk.encode_string_field(JwkStringField::Priv, &seed_bytes);
1158 } else {
1159 let public_key_bytes = convert_handle_to_public_key(key.handle())?;
1160 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1161 }
1162
1163 // Step 2.7. Set the key_ops attribute of jwk to the usages attribute of key.
1164 jwk.set_key_ops(key.usages());
1165
1166 // Step 2.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1167 jwk.ext = Some(key.Extractable());
1168
1169 // Step 2.9. Let result be jwk.
1170 ExportedKey::Jwk(Box::new(jwk))
1171 },
1172 // Otherwise:
1173 _ => {
1174 // throw a NotSupportedError.
1175 return Err(Error::NotSupported(Some(
1176 "Unsupported export key format for ML-KEM key".to_string(),
1177 )));
1178 },
1179 };
1180
1181 // Step 3. Return result.
1182 Ok(result)
1183}
1184
1185/// Convert seed bytes to an ML-KEM private key handle and an ML-KEM public key handle. If private
1186/// key bytes and/or public key bytes are provided, it runs a consistency check against the seed.
1187/// If the length in bits of seed bytes is not 512, the conversion fails, or the consistency check
1188/// fails, throw a DataError.
1189fn convert_seed_to_handles(
1190 algorithm_name: CryptoAlgorithm,
1191 seed_bytes: &[u8],
1192 private_key_bytes: Option<&[u8]>,
1193 public_key_bytes: Option<&[u8]>,
1194) -> Result<(Handle, Handle), Error> {
1195 if seed_bytes.len() != 64 {
1196 return Err(Error::Data(Some(
1197 "The length in bits of seed bytes is not 512".to_string(),
1198 )));
1199 }
1200
1201 let d: B32 = (&seed_bytes[..32]).try_into().map_err(|_| {
1202 Error::Data(Some(
1203 "Failed to parse first 256 bits of seed bytes".to_string(),
1204 ))
1205 })?;
1206 let z: B32 = (&seed_bytes[32..64]).try_into().map_err(|_| {
1207 Error::Data(Some(
1208 "Failed to parse last 256 bits of seed bytes".to_string(),
1209 ))
1210 })?;
1211 let handles = match algorithm_name {
1212 CryptoAlgorithm::MlKem512 => {
1213 let (decapsulation_key, encapsulation_key) = MlKem512::generate_deterministic(&d, &z);
1214 if let Some(private_key_bytes) = private_key_bytes {
1215 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1216 return Err(Error::Data(Some(
1217 "The expanded private key does not match the seed".to_string(),
1218 )));
1219 }
1220 }
1221 if let Some(public_key_bytes) = public_key_bytes {
1222 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1223 return Err(Error::Data(Some(
1224 "The public key does not match the seed".to_string(),
1225 )));
1226 }
1227 }
1228
1229 (
1230 Handle::MlKem512PrivateKey((d, z)),
1231 Handle::MlKem512PublicKey(Box::new(encapsulation_key.as_bytes())),
1232 )
1233 },
1234 CryptoAlgorithm::MlKem768 => {
1235 let (decapsulation_key, encapsulation_key) = MlKem768::generate_deterministic(&d, &z);
1236 if let Some(private_key_bytes) = private_key_bytes {
1237 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1238 return Err(Error::Data(Some(
1239 "The expanded private key does not match the seed".to_string(),
1240 )));
1241 }
1242 }
1243 if let Some(public_key_bytes) = public_key_bytes {
1244 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1245 return Err(Error::Data(Some(
1246 "The public key does not match the seed".to_string(),
1247 )));
1248 }
1249 }
1250
1251 (
1252 Handle::MlKem768PrivateKey((d, z)),
1253 Handle::MlKem768PublicKey(Box::new(encapsulation_key.as_bytes())),
1254 )
1255 },
1256 CryptoAlgorithm::MlKem1024 => {
1257 let (decapsulation_key, encapsulation_key) = MlKem1024::generate_deterministic(&d, &z);
1258 if let Some(private_key_bytes) = private_key_bytes {
1259 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1260 return Err(Error::Data(Some(
1261 "The expanded private key does not match the seed".to_string(),
1262 )));
1263 }
1264 }
1265 if let Some(public_key_bytes) = public_key_bytes {
1266 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1267 return Err(Error::Data(Some(
1268 "The public key does not match the seed".to_string(),
1269 )));
1270 }
1271 }
1272
1273 (
1274 Handle::MlKem1024PrivateKey((d, z)),
1275 Handle::MlKem1024PublicKey(Box::new(encapsulation_key.as_bytes())),
1276 )
1277 },
1278 _ => {
1279 return Err(Error::NotSupported(Some(format!(
1280 "{} is not an ML-KEM algorithm",
1281 algorithm_name.as_str()
1282 ))));
1283 },
1284 };
1285
1286 Ok(handles)
1287}
1288
1289/// Convert public key bytes to an ML-KEM public key handle. If the conversion fails, throw a
1290/// DataError.
1291fn convert_public_key_to_handle(
1292 algorithm_name: CryptoAlgorithm,
1293 public_key_bytes: &[u8],
1294) -> Result<Handle, Error> {
1295 let public_key_handle = match algorithm_name {
1296 CryptoAlgorithm::MlKem512 => {
1297 let encoded_encapsulation_key = Encoded::<EncapsulationKey<MlKem512Params>>::try_from(
1298 public_key_bytes,
1299 )
1300 .map_err(|_| Error::Data(Some("Failed to parse ML-KEM public key".to_string())))?;
1301 Handle::MlKem512PublicKey(Box::new(encoded_encapsulation_key))
1302 },
1303 CryptoAlgorithm::MlKem768 => {
1304 let encoded_encapsulation_key = Encoded::<EncapsulationKey<MlKem768Params>>::try_from(
1305 public_key_bytes,
1306 )
1307 .map_err(|_| Error::Data(Some("Failed to parse ML-KEM public key".to_string())))?;
1308 Handle::MlKem768PublicKey(Box::new(encoded_encapsulation_key))
1309 },
1310 CryptoAlgorithm::MlKem1024 => {
1311 let encoded_encapsulation_key = Encoded::<EncapsulationKey<MlKem1024Params>>::try_from(
1312 public_key_bytes,
1313 )
1314 .map_err(|_| Error::Data(Some("Failed to parse ML-KEM public key".to_string())))?;
1315 Handle::MlKem1024PublicKey(Box::new(encoded_encapsulation_key))
1316 },
1317 _ => {
1318 return Err(Error::NotSupported(Some(format!(
1319 "{} is not an ML-KEM algorithm",
1320 algorithm_name.as_str()
1321 ))));
1322 },
1323 };
1324
1325 Ok(public_key_handle)
1326}
1327
1328/// Convert an ML-KEM private key handle to seed bytes and public key bytes. If the handle is not
1329/// representing a ML-KEM private key, throw an OperationError.
1330fn convert_handle_to_seed_and_public_key(handle: &Handle) -> Result<(Vec<u8>, Vec<u8>), Error> {
1331 let result = match handle {
1332 Handle::MlKem512PrivateKey((d, z)) => {
1333 let mut seed = d.to_vec();
1334 seed.extend_from_slice(z);
1335 let (_private_key, public_key) = MlKem512::generate_deterministic(d, z);
1336 (seed, public_key.as_bytes().to_vec())
1337 },
1338 Handle::MlKem768PrivateKey((d, z)) => {
1339 let mut seed = d.to_vec();
1340 seed.extend_from_slice(z);
1341 let (_private_key, public_key) = MlKem768::generate_deterministic(d, z);
1342 (seed, public_key.as_bytes().to_vec())
1343 },
1344 Handle::MlKem1024PrivateKey((d, z)) => {
1345 let mut seed = d.to_vec();
1346 seed.extend_from_slice(z);
1347 let (_private_key, public_key) = MlKem1024::generate_deterministic(d, z);
1348 (seed, public_key.as_bytes().to_vec())
1349 },
1350 _ => {
1351 return Err(Error::Operation(Some(
1352 "The key handle is not representing an ML-KEM private key".to_string(),
1353 )));
1354 },
1355 };
1356
1357 Ok(result)
1358}
1359
1360/// Convert an ML-KEM public key handle to public key bytes. If the handle is not representing a
1361/// ML-KEM public key, throw an OperationError.
1362fn convert_handle_to_public_key(handle: &Handle) -> Result<Vec<u8>, Error> {
1363 let result = match handle {
1364 Handle::MlKem512PublicKey(public_key) => public_key.to_vec(),
1365 Handle::MlKem768PublicKey(public_key) => public_key.to_vec(),
1366 Handle::MlKem1024PublicKey(public_key) => public_key.to_vec(),
1367 _ => {
1368 return Err(Error::Operation(Some(
1369 "The key handle is not representing an ML-KEM public key".to_string(),
1370 )));
1371 },
1372 };
1373
1374 Ok(result)
1375}