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 ml_kem::kem::{Decapsulate, Encapsulate, EncapsulationKey};
8use ml_kem::{
9 B32, Encoded, EncodedSizeUser, KemCore, MlKem512, MlKem512Params, MlKem768, MlKem768Params,
10 MlKem1024, MlKem1024Params,
11};
12use pkcs8::rand_core::{OsRng, RngCore};
13use pkcs8::spki::AlgorithmIdentifier;
14use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo};
15
16use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
17 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
18};
19use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
20use crate::dom::bindings::error::Error;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::cryptokey::{CryptoKey, Handle};
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::subtlecrypto::{
26 ALG_ML_KEM_512, ALG_ML_KEM_768, ALG_ML_KEM_1024, ExportedKey, JsonWebKeyExt, JwkStringField,
27 KeyAlgorithmAndDerivatives, SubtleAlgorithm, SubtleEncapsulatedBits, SubtleKeyAlgorithm,
28};
29use crate::script_runtime::CanGc;
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.as_str() {
150 ALG_ML_KEM_512 => {
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 ALG_ML_KEM_768 => {
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 ALG_ML_KEM_1024 => {
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.as_str() {
232 ALG_ML_KEM_512 => {
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 ALG_ML_KEM_768 => {
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 ALG_ML_KEM_1024 => {
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 global: &GlobalScope,
295 normalized_algorithm: &SubtleAlgorithm,
296 extractable: bool,
297 usages: Vec<KeyUsage>,
298 can_gc: CanGc,
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.clone(),
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 global,
341 KeyType::Public,
342 true,
343 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
344 usages
345 .iter()
346 .filter(|usage| matches!(usage, KeyUsage::EncapsulateKey | KeyUsage::EncapsulateBits))
347 .cloned()
348 .collect(),
349 public_key_handle,
350 can_gc,
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 global,
362 KeyType::Private,
363 extractable,
364 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
365 usages
366 .iter()
367 .filter(|usage| matches!(usage, KeyUsage::DecapsulateKey | KeyUsage::DecapsulateBits))
368 .cloned()
369 .collect(),
370 private_key_handle,
371 can_gc,
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 global: &GlobalScope,
389 normalized_algorithm: &SubtleAlgorithm,
390 format: KeyFormat,
391 key_data: &[u8],
392 extractable: bool,
393 usages: Vec<KeyUsage>,
394 can_gc: CanGc,
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.as_str() {
435 ALG_ML_KEM_512 => ObjectIdentifier::new_unwrap(ID_ALG_ML_KEM_512),
436 ALG_ML_KEM_768 => ObjectIdentifier::new_unwrap(ID_ALG_ML_KEM_768),
437 ALG_ML_KEM_1024 => 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.clone(),
478 };
479 CryptoKey::new(
480 global,
481 KeyType::Public,
482 extractable,
483 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
484 usages,
485 public_key,
486 can_gc,
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.as_str() {
526 ALG_ML_KEM_512 => ObjectIdentifier::new_unwrap(ID_ALG_ML_KEM_512),
527 ALG_ML_KEM_768 => ObjectIdentifier::new_unwrap(ID_ALG_ML_KEM_768),
528 ALG_ML_KEM_1024 => 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.clone(),
610 };
611 CryptoKey::new(
612 global,
613 KeyType::Private,
614 extractable,
615 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
616 usages,
617 ml_kem_private_key,
618 can_gc,
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.clone(),
648 };
649 CryptoKey::new(
650 global,
651 KeyType::Public,
652 extractable,
653 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
654 usages,
655 public_key_handle,
656 can_gc,
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.clone(),
694 };
695 CryptoKey::new(
696 global,
697 KeyType::Private,
698 extractable,
699 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
700 usages,
701 private_key_handle,
702 can_gc,
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(GlobalScope::get_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.as_str() {
753 ALG_ML_KEM_512 => {
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 ALG_ML_KEM_768 => {
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 ALG_ML_KEM_1024 => {
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.clone(),
867 };
868 CryptoKey::new(
869 global,
870 key_type,
871 extractable,
872 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
873 usages,
874 key_handle,
875 can_gc,
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>
892///
893/// The exportKey() method does not involve AlgorithmIdentifier and algorithm normalization, so
894/// there should not be normalizedAlgorithm in the export key operation. It could be a mistake in
895/// the specification (Related issue: <https://github.com/WICG/webcrypto-modern-algos/issues/47>).
896///
897/// In our implementation, we use the name attribute of the [[algorithhm]] internal slot of key to
898/// determine the security category.
899pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
900 // Step 1. If the underlying cryptographic key material represented by the [[handle]] internal
901 // slot of key cannot be accessed, then throw an OperationError.
902
903 // Step 2.
904 let result = match format {
905 // If format is "spki":
906 KeyFormat::Spki => {
907 // Step 2.1. If the [[type]] internal slot of key is not "public", then throw an
908 // InvalidAccessError.
909 if key.Type() != KeyType::Public {
910 return Err(Error::InvalidAccess(Some(
911 "[[type]] internal slot of key is not \"public\"".to_string(),
912 )));
913 }
914
915 // Step 2.2.
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 normalizedAlgorithm 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 normalizedAlgorithm 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 normalizedAlgorithm 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 KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
939 return Err(Error::Operation(Some(
940 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
941 )));
942 };
943 let oid = match key_algorithm.name.as_str() {
944 ALG_ML_KEM_512 => ID_ALG_ML_KEM_512,
945 ALG_ML_KEM_768 => ID_ALG_ML_KEM_768,
946 ALG_ML_KEM_1024 => ID_ALG_ML_KEM_1024,
947 _ => {
948 return Err(Error::Operation(Some(format!(
949 "{} is not an ML-KEM algorithm",
950 key_algorithm.name.as_str()
951 ))));
952 },
953 };
954 let key_bytes = convert_handle_to_public_key(key.handle())?;
955 let subject_public_key = BitString::from_bytes(&key_bytes).map_err(|_| {
956 Error::Operation(Some(
957 "Failed to encode BitString for subjectPublicKey field of SubjectPublicKeyInfo"
958 .to_string(),
959 ))
960 })?;
961 let data = SubjectPublicKeyInfo {
962 algorithm: AlgorithmIdentifier::<AnyRef> {
963 oid: ObjectIdentifier::new_unwrap(oid),
964 parameters: None,
965 },
966 subject_public_key,
967 };
968
969 // Step 2.3. Let result be the result of DER-encoding data.
970 ExportedKey::Bytes(data.to_der().map_err(|_| {
971 Error::Operation(Some(
972 "Failed to encode SubjectPublicKeyInfo in DER format".to_string(),
973 ))
974 })?)
975 },
976 // If format is "pkcs8":
977 KeyFormat::Pkcs8 => {
978 // Step 2.1. If the [[type]] internal slot of key is not "private", then throw an
979 // InvalidAccessError.
980 if key.Type() != KeyType::Private {
981 return Err(Error::InvalidAccess(Some(
982 "[[type]] internal slot of key is not \"private\"".to_string(),
983 )));
984 }
985
986 // Step 2.2.
987 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
988 // with the following properties:
989 //
990 // Set the version field to 0.
991 //
992 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
993 // with the following properties:
994 //
995 // If the name member of normalizedAlgorithm is "ML-KEM-512":
996 // Set the algorithm object identifier to the id-alg-ml-kem-512
997 // (2.16.840.1.101.3.4.4.1) OID.
998 //
999 // If the name member of normalizedAlgorithm is "ML-KEM-768":
1000 // Set the algorithm object identifier to the id-alg-ml-kem-768
1001 // (2.16.840.1.101.3.4.4.2) OID.
1002 //
1003 // If the name member of normalizedAlgorithm is "ML-KEM-1024":
1004 // Set the algorithm object identifier to the id-alg-ml-kem-1024
1005 // (2.16.840.1.101.3.4.4.3) OID.
1006 //
1007 // Otherwise:
1008 // throw a NotSupportedError.
1009 //
1010 // Set the privateKey field as follows:
1011 //
1012 // If the name member of normalizedAlgorithm is "ML-KEM-512":
1013 // Set the privateKey field to the result of DER-encoding a
1014 // ML-KEM-512-PrivateKey ASN.1 type that represents the ML-KEM private key
1015 // seed represented by the [[handle]] internal slot of key using the
1016 // seed-only format (using a context-specific [0] primitive tag with an
1017 // implicit encoding of OCTET STRING).
1018 //
1019 // If the name member of normalizedAlgorithm is "ML-KEM-768":
1020 // Set the privateKey field to the result of DER-encoding a
1021 // ML-KEM-768-PrivateKey ASN.1 type that represents the ML-KEM private key
1022 // seed represented by the [[handle]] internal slot of key using the
1023 // seed-only format (using a context-specific [0] primitive tag with an
1024 // implicit encoding of OCTET STRING).
1025 //
1026 // If the name member of normalizedAlgorithm is "ML-KEM-1024":
1027 // Set the privateKey field to the result of DER-encoding a
1028 // ML-KEM-1024-PrivateKey ASN.1 type that represents the ML-KEM private key
1029 // seed represented by the [[handle]] internal slot of key using the
1030 // seed-only format (using a context-specific [0] primitive tag with an
1031 // implicit encoding of OCTET STRING).
1032 //
1033 // Otherwise:
1034 // throw a NotSupportedError.
1035 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1036 return Err(Error::Operation(Some(
1037 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1038 )));
1039 };
1040 let oid = match key_algorithm.name.as_str() {
1041 ALG_ML_KEM_512 => ID_ALG_ML_KEM_512,
1042 ALG_ML_KEM_768 => ID_ALG_ML_KEM_768,
1043 ALG_ML_KEM_1024 => ID_ALG_ML_KEM_1024,
1044 _ => {
1045 return Err(Error::Operation(Some(format!(
1046 "{} is not an ML-KEM algorithm",
1047 key_algorithm.name.as_str()
1048 ))));
1049 },
1050 };
1051 let (seed_bytes, _) = convert_handle_to_seed_and_public_key(key.handle())?;
1052 let private_key =
1053 MlKemPrivateKeyStructure::Seed(OctetString::new(seed_bytes).map_err(|_| {
1054 Error::Operation(Some(
1055 "Failed to encode OctetString for privateKey field of \
1056 ASN.1 ML-KEM private key structure"
1057 .to_string(),
1058 ))
1059 })?);
1060 let encoded_private_key = private_key.to_der().map_err(|_| {
1061 Error::Operation(Some(
1062 "Failed to encode ASN.1 ML-KEM private key structure in DER format".to_string(),
1063 ))
1064 })?;
1065 let private_key_info = PrivateKeyInfo {
1066 algorithm: AlgorithmIdentifier {
1067 oid: ObjectIdentifier::new_unwrap(oid),
1068 parameters: None,
1069 },
1070 private_key: &encoded_private_key,
1071 public_key: None,
1072 };
1073
1074 // Step 2.3. Let result be the result of DER-encoding data.
1075 ExportedKey::Bytes(private_key_info.to_der().map_err(|_| {
1076 Error::Operation(Some(
1077 "Failed to encode PrivateKeyInfo in DER format".to_string(),
1078 ))
1079 })?)
1080 },
1081 // If format is "raw-public":
1082 KeyFormat::Raw_public => {
1083 // Step 2.1. If the [[type]] internal slot of key is not "public", then throw an
1084 // InvalidAccessError.
1085 if key.Type() != KeyType::Public {
1086 return Err(Error::InvalidAccess(Some(
1087 "[[type]] internal slot of key is not \"public\"".to_string(),
1088 )));
1089 }
1090
1091 // Step 2.2. Let data be a byte sequence containing the raw octets of the key
1092 // represented by the [[handle]] internal slot of key.
1093 let data = convert_handle_to_public_key(key.handle())?;
1094
1095 // Step 2.3. Let result be data.
1096 ExportedKey::Bytes(data)
1097 },
1098 // If format is "raw-seed":
1099 KeyFormat::Raw_seed => {
1100 // Step 2.1. If the [[type]] internal slot of key is not "private", then throw an
1101 // InvalidAccessError.
1102 if key.Type() != KeyType::Private {
1103 return Err(Error::InvalidAccess(Some(
1104 "[[type]] internal slot of key is not \"private\"".to_string(),
1105 )));
1106 }
1107
1108 // Step 2.2. Let data be a byte sequence containing the concatenation of the d and z
1109 // seed variables of the key represented by the [[handle]] internal slot of key.
1110 let (data, _) = convert_handle_to_seed_and_public_key(key.handle())?;
1111
1112 // Step 2.3. Let result be data.
1113 ExportedKey::Bytes(data)
1114 },
1115 // If format is "jwk":
1116 KeyFormat::Jwk => {
1117 // The JWK format for ML-KEM is not standardized yet and thus subject to change.
1118
1119 // Step 2.1. Let jwk be a new JsonWebKey dictionary.
1120 // Step 2.2. Set the kty attribute of jwk to "AKP".
1121 let mut jwk = JsonWebKey {
1122 kty: Some(DOMString::from("AKP")),
1123 ..Default::default()
1124 };
1125
1126 // Step 2.3. Set the alg attribute of jwk to the alg value corresponding to the name
1127 // member of normalizedAlgorithm indicated in Section 8 of [draft-ietf-jose-pqc-kem-01]
1128 // (Figure 1).
1129 //
1130 // <https://www.ietf.org/archive/id/draft-ietf-jose-pqc-kem-01.html#direct-table>
1131 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1132 return Err(Error::Operation(Some(
1133 "[[algorithm]] internal slot of key is not a KeyAlgorithm".to_string(),
1134 )));
1135 };
1136 let alg = match key_algorithm.name.as_str() {
1137 ALG_ML_KEM_512 => "MLKEM512",
1138 ALG_ML_KEM_768 => "MLKEM768",
1139 ALG_ML_KEM_1024 => "MLKEM1024",
1140 _ => {
1141 return Err(Error::Operation(Some(format!(
1142 "{} is not an ML-KEM algorithm",
1143 key_algorithm.name.as_str()
1144 ))));
1145 },
1146 };
1147 jwk.alg = Some(DOMString::from(alg));
1148
1149 // Step 2.4. Set the pub attribute of jwk to the base64url encoded public key
1150 // corresponding to the [[handle]] internal slot of key.
1151 // Step 2.5.
1152 // If the [[type]] internal slot of key is "private":
1153 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1154 // [[handle]] internal slot of key.
1155 if key.Type() == KeyType::Private {
1156 let (seed_bytes, public_key_bytes) =
1157 convert_handle_to_seed_and_public_key(key.handle())?;
1158 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1159 jwk.encode_string_field(JwkStringField::Priv, &seed_bytes);
1160 } else {
1161 let public_key_bytes = convert_handle_to_public_key(key.handle())?;
1162 jwk.encode_string_field(JwkStringField::Pub, &public_key_bytes);
1163 }
1164
1165 // Step 2.6. Set the key_ops attribute of jwk to the usages attribute of key.
1166 jwk.set_key_ops(key.usages());
1167
1168 // Step 2.7. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1169 jwk.ext = Some(key.Extractable());
1170
1171 // Step 2.8. Let result be jwk.
1172 ExportedKey::Jwk(Box::new(jwk))
1173 },
1174 // Otherwise:
1175 _ => {
1176 // throw a NotSupportedError.
1177 return Err(Error::NotSupported(Some(
1178 "Unsupported export key format for ML-KEM key".to_string(),
1179 )));
1180 },
1181 };
1182
1183 // Step 3. Return result.
1184 Ok(result)
1185}
1186
1187/// Convert seed bytes to an ML-KEM private key handle and an ML-KEM public key handle. If private
1188/// key bytes and/or public key bytes are provided, it runs a consistency check against the seed.
1189/// If the length in bits of seed bytes is not 512, the conversion fails, or the consistency check
1190/// fails, throw a DataError.
1191fn convert_seed_to_handles(
1192 algo_name: &str,
1193 seed_bytes: &[u8],
1194 private_key_bytes: Option<&[u8]>,
1195 public_key_bytes: Option<&[u8]>,
1196) -> Result<(Handle, Handle), Error> {
1197 if seed_bytes.len() != 64 {
1198 return Err(Error::Data(Some(
1199 "The length in bits of seed bytes is not 512".to_string(),
1200 )));
1201 }
1202
1203 let d: B32 = (&seed_bytes[..32]).try_into().map_err(|_| {
1204 Error::Data(Some(
1205 "Failed to parse first 256 bits of seed bytes".to_string(),
1206 ))
1207 })?;
1208 let z: B32 = (&seed_bytes[32..64]).try_into().map_err(|_| {
1209 Error::Data(Some(
1210 "Failed to parse last 256 bits of seed bytes".to_string(),
1211 ))
1212 })?;
1213 let handles = match algo_name {
1214 ALG_ML_KEM_512 => {
1215 let (decapsulation_key, encapsulation_key) = MlKem512::generate_deterministic(&d, &z);
1216 if let Some(private_key_bytes) = private_key_bytes {
1217 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1218 return Err(Error::Data(Some(
1219 "The expanded private key does not match the seed".to_string(),
1220 )));
1221 }
1222 }
1223 if let Some(public_key_bytes) = public_key_bytes {
1224 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1225 return Err(Error::Data(Some(
1226 "The public key does not match the seed".to_string(),
1227 )));
1228 }
1229 }
1230
1231 (
1232 Handle::MlKem512PrivateKey((d, z)),
1233 Handle::MlKem512PublicKey(Box::new(encapsulation_key.as_bytes())),
1234 )
1235 },
1236 ALG_ML_KEM_768 => {
1237 let (decapsulation_key, encapsulation_key) = MlKem768::generate_deterministic(&d, &z);
1238 if let Some(private_key_bytes) = private_key_bytes {
1239 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1240 return Err(Error::Data(Some(
1241 "The expanded private key does not match the seed".to_string(),
1242 )));
1243 }
1244 }
1245 if let Some(public_key_bytes) = public_key_bytes {
1246 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1247 return Err(Error::Data(Some(
1248 "The public key does not match the seed".to_string(),
1249 )));
1250 }
1251 }
1252
1253 (
1254 Handle::MlKem768PrivateKey((d, z)),
1255 Handle::MlKem768PublicKey(Box::new(encapsulation_key.as_bytes())),
1256 )
1257 },
1258 ALG_ML_KEM_1024 => {
1259 let (decapsulation_key, encapsulation_key) = MlKem1024::generate_deterministic(&d, &z);
1260 if let Some(private_key_bytes) = private_key_bytes {
1261 if private_key_bytes != decapsulation_key.as_bytes().as_slice() {
1262 return Err(Error::Data(Some(
1263 "The expanded private key does not match the seed".to_string(),
1264 )));
1265 }
1266 }
1267 if let Some(public_key_bytes) = public_key_bytes {
1268 if public_key_bytes != encapsulation_key.as_bytes().as_slice() {
1269 return Err(Error::Data(Some(
1270 "The public key does not match the seed".to_string(),
1271 )));
1272 }
1273 }
1274
1275 (
1276 Handle::MlKem1024PrivateKey((d, z)),
1277 Handle::MlKem1024PublicKey(Box::new(encapsulation_key.as_bytes())),
1278 )
1279 },
1280 _ => {
1281 return Err(Error::NotSupported(Some(format!(
1282 "{} is not an ML-KEM algorithm",
1283 algo_name
1284 ))));
1285 },
1286 };
1287
1288 Ok(handles)
1289}
1290
1291/// Convert public key bytes to an ML-KEM public key handle. If the conversion fails, throw a
1292/// DataError.
1293fn convert_public_key_to_handle(algo_name: &str, public_key_bytes: &[u8]) -> Result<Handle, Error> {
1294 let public_key_handle = match algo_name {
1295 ALG_ML_KEM_512 => {
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 ALG_ML_KEM_768 => {
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 ALG_ML_KEM_1024 => {
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 algo_name
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}