script/dom/webcrypto/subtlecrypto/ml_dsa_operation.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use js::context::JSContext;
6use ml_dsa::common::getrandom::SysRng;
7use ml_dsa::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
8use ml_dsa::{
9 Generate, KeyExport, KeyInit, Keypair, MlDsa44, MlDsa65, MlDsa87, Signature, SignatureEncoding,
10 SigningKey, VerifyingKey,
11};
12
13use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
14 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
15};
16use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
17use crate::dom::bindings::error::Error;
18use crate::dom::bindings::root::DomRoot;
19use crate::dom::bindings::str::DOMString;
20use crate::dom::cryptokey::{CryptoKey, Handle, KeyUsageVecHelper};
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::subtlecrypto::{
23 Algorithm, ContextParams, CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField,
24 KeyAlgorithm, KeyAlgorithmAndDerivatives,
25};
26
27/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-sign>
28pub(crate) fn sign(
29 normalized_algorithm: &ContextParams,
30 key: &CryptoKey,
31 message: &[u8],
32) -> Result<Vec<u8>, Error> {
33 // Step 1. If the context member of normalizedAlgorithm is present and has a length greater than
34 // 255 bytes, then throw an OperationError.
35 if normalized_algorithm
36 .context
37 .as_ref()
38 .is_some_and(|context| context.len() > 255)
39 {
40 return Err(Error::Operation(Some(
41 "The context is present and has a length greater than 255 bytes".into(),
42 )));
43 }
44
45 // Step 2. If the [[type]] internal slot of key is not "private", then throw an
46 // InvalidAccessError.
47 if key.Type() != KeyType::Private {
48 return Err(Error::InvalidAccess(Some(
49 "[[type]] internal slot of key is not \"private\"".into(),
50 )));
51 }
52
53 // Step 3. Let context be the context member of normalizedAlgorithm or the empty octet string
54 // if the context member of normalizedAlgorithm is not present.
55 let context = normalized_algorithm.context.as_deref().unwrap_or_default();
56
57 // Step 4. Let result be the result of performing the ML-DSA.Sign signing algorithm, as
58 // specified in Section 5.2 of [FIPS-204], with the parameter set indicated by the name member
59 // of normalizedAlgorithm, using the ML-DSA private key associated with key as sk, message as M
60 // and context as ctx.
61 // Step 5. If the ML-DSA.Sign algorithm returned an error, return an OperationError.
62 let result = match normalized_algorithm.name {
63 CryptoAlgorithm::MlDsa44 => {
64 let Handle::MlDsa44PrivateKey(private_key) = key.handle() else {
65 return Err(Error::Operation(Some(
66 "The key handle is not representing an ML-DSA-44 private key".into(),
67 )));
68 };
69 private_key
70 .expanded_key()
71 .sign_randomized(message, context, &mut SysRng)
72 .map_err(|_| Error::Operation(Some("ML-DSA-44 failed to sign the message".into())))?
73 .to_vec()
74 },
75 CryptoAlgorithm::MlDsa65 => {
76 let Handle::MlDsa65PrivateKey(private_key) = key.handle() else {
77 return Err(Error::Operation(Some(
78 "The key handle is not representing an ML-DSA-65 private key".into(),
79 )));
80 };
81 private_key
82 .expanded_key()
83 .sign_randomized(message, context, &mut SysRng)
84 .map_err(|_| Error::Operation(Some("ML-DSA-65 failed to sign the message".into())))?
85 .to_vec()
86 },
87 CryptoAlgorithm::MlDsa87 => {
88 let Handle::MlDsa87PrivateKey(private_key) = key.handle() else {
89 return Err(Error::Operation(Some(
90 "The key handle is not representing an ML-DSA-87 private key".into(),
91 )));
92 };
93 private_key
94 .expanded_key()
95 .sign_randomized(message, context, &mut SysRng)
96 .map_err(|_| Error::Operation(Some("ML-DSA-87 failed to sign the message".into())))?
97 .to_vec()
98 },
99 _ => {
100 return Err(Error::NotSupported(Some(format!(
101 "{} is not an ML-DSA algorithm",
102 normalized_algorithm.name.as_str()
103 ))));
104 },
105 };
106
107 // Step 6. Return result.
108 Ok(result)
109}
110
111/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-verify>
112pub(crate) fn verify(
113 normalized_algorithm: &ContextParams,
114 key: &CryptoKey,
115 message: &[u8],
116 signature: &[u8],
117) -> Result<bool, Error> {
118 // Step 1. If the context member of normalizedAlgorithm is present and has a length greater than
119 // 255 bytes, then throw an OperationError.
120 if normalized_algorithm
121 .context
122 .as_ref()
123 .is_some_and(|context| context.len() > 255)
124 {
125 return Err(Error::Operation(Some(
126 "The context is present and has a length greater than 255 bytes".into(),
127 )));
128 }
129
130 // Step 2. If the [[type]] internal slot of key is not "public", then throw an
131 // InvalidAccessError.
132 if key.Type() != KeyType::Public {
133 return Err(Error::InvalidAccess(Some(
134 "[[type]] internal slot of key is not \"public\"".into(),
135 )));
136 }
137
138 // Step 3. Let context be the context member of normalizedAlgorithm or the empty octet string
139 // if the context member of normalizedAlgorithm is not present.
140 let context = normalized_algorithm.context.as_deref().unwrap_or_default();
141
142 // Step 4. Let result be the result of performing the ML-DSA.Verify verification algorithm, as
143 // specified in Section 5.3 of [FIPS-204], with the parameter set indicated by the name member
144 // of normalizedAlgorithm, using the ML-DSA public key associated with key as pk, message as M,
145 // signature as σ and context as ctx.
146 // Step 5. If the ML-DSA.Verify algorithm returned an error, return an OperationError.
147 let result = match normalized_algorithm.name {
148 CryptoAlgorithm::MlDsa44 => {
149 let Handle::MlDsa44PublicKey(public_key) = key.handle() else {
150 return Err(Error::Operation(Some(
151 "The key handle is not representing an ML-DSA-44 public key".into(),
152 )));
153 };
154 match Signature::try_from(signature) {
155 Ok(signature) => public_key.verify_with_context(message, context, &signature),
156 Err(_) => false,
157 }
158 },
159 CryptoAlgorithm::MlDsa65 => {
160 let Handle::MlDsa65PublicKey(public_key) = key.handle() else {
161 return Err(Error::Operation(Some(
162 "The key handle is not representing an ML-DSA-65 public key".into(),
163 )));
164 };
165 match Signature::try_from(signature) {
166 Ok(signature) => public_key.verify_with_context(message, context, &signature),
167 Err(_) => false,
168 }
169 },
170 CryptoAlgorithm::MlDsa87 => {
171 let Handle::MlDsa87PublicKey(public_key) = key.handle() else {
172 return Err(Error::Operation(Some(
173 "The key handle is not representing an ML-DSA-87 public key".into(),
174 )));
175 };
176 match Signature::try_from(signature) {
177 Ok(signature) => public_key.verify_with_context(message, context, &signature),
178 Err(_) => false,
179 }
180 },
181 _ => {
182 return Err(Error::NotSupported(Some(format!(
183 "{} is not an ML-DSA algorithm",
184 normalized_algorithm.name.as_str()
185 ))));
186 },
187 };
188
189 // Step 6. Return result.
190 Ok(result)
191}
192
193/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-generate-key>
194pub(crate) fn generate_key(
195 cx: &mut JSContext,
196 global: &GlobalScope,
197 normalized_algorithm: &Algorithm,
198 extractable: bool,
199 usages: Vec<KeyUsage>,
200) -> Result<CryptoKeyPair, Error> {
201 // Step 1. If usages contains a value which is not one of "sign" or "verify", then throw a
202 // SyntaxError.
203 if usages
204 .iter()
205 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
206 {
207 return Err(Error::Syntax(Some(
208 "Usages contains an entry which is not one of \"sign\" or \"verify\"".into(),
209 )));
210 }
211
212 // Step 2. Generate an ML-DSA key pair, as described in Section 5.1 of [FIPS-204], with the
213 // parameter set indicated by the name member of normalizedAlgorithm.
214 // Step 3. If the key generation step fails, then throw an OperationError.
215 let (private_key_handle, public_key_handle) = match normalized_algorithm.name {
216 CryptoAlgorithm::MlDsa44 => {
217 let signing_key = SigningKey::<MlDsa44>::generate();
218 let verifying_key = signing_key.verifying_key();
219 (
220 Handle::MlDsa44PrivateKey(signing_key),
221 Handle::MlDsa44PublicKey(verifying_key),
222 )
223 },
224 CryptoAlgorithm::MlDsa65 => {
225 let signing_key = SigningKey::<MlDsa65>::generate();
226 let verifying_key = signing_key.verifying_key();
227 (
228 Handle::MlDsa65PrivateKey(signing_key),
229 Handle::MlDsa65PublicKey(verifying_key),
230 )
231 },
232 CryptoAlgorithm::MlDsa87 => {
233 let signing_key = SigningKey::<MlDsa87>::generate();
234 let verifying_key = signing_key.verifying_key();
235 (
236 Handle::MlDsa87PrivateKey(signing_key),
237 Handle::MlDsa87PublicKey(verifying_key),
238 )
239 },
240 name => {
241 return Err(Error::NotSupported(Some(format!(
242 "{} is not an ML-DSA algorithm",
243 name.as_str()
244 ))));
245 },
246 };
247
248 // Step 4. Let algorithm be a new KeyAlgorithm object.
249 // Step 5. Set the name attribute of algorithm to the name attribute of normalizedAlgorithm.
250 let algorithm = KeyAlgorithm {
251 name: normalized_algorithm.name,
252 };
253
254 // Step 6. Let publicKey be a new CryptoKey representing the public key of the generated key
255 // pair.
256 // Step 7. Set the [[type]] internal slot of publicKey to "public".
257 // Step 8. Set the [[algorithm]] internal slot of publicKey to algorithm.
258 // Step 9. Set the [[extractable]] internal slot of publicKey to true.
259 // Step 10. Set the [[usages]] internal slot of publicKey to be the usage intersection of
260 // usages and [ "verify" ].
261 let public_key = CryptoKey::new(
262 cx,
263 global,
264 KeyType::Public,
265 true,
266 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
267 usages.usage_intersection(&[KeyUsage::Verify]),
268 public_key_handle,
269 );
270
271 // Step 11. Let privateKey be a new CryptoKey representing the private key of the generated key
272 // pair.
273 // Step 12. Set the [[type]] internal slot of privateKey to "private".
274 // Step 13. Set the [[algorithm]] internal slot of privateKey to algorithm.
275 // Step 14. Set the [[extractable]] internal slot of privateKey to extractable.
276 // Step 15. Set the [[usages]] internal slot of privateKey to be the usage intersection of
277 // usages and [ "sign" ].
278 let private_key = CryptoKey::new(
279 cx,
280 global,
281 KeyType::Private,
282 extractable,
283 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
284 usages.usage_intersection(&[KeyUsage::Sign]),
285 private_key_handle,
286 );
287
288 // Step 16. Let result be a new CryptoKeyPair dictionary.
289 // Step 17. Set the publicKey attribute of result to be publicKey.
290 // Step 18. Set the privateKey attribute of result to be privateKey.
291 let result = CryptoKeyPair {
292 publicKey: Some(public_key),
293 privateKey: Some(private_key),
294 };
295
296 // Step 19. Return result.
297 Ok(result)
298}
299
300/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-import-key>
301pub(crate) fn import_key(
302 cx: &mut JSContext,
303 global: &GlobalScope,
304 normalized_algorithm: &Algorithm,
305 format: KeyFormat,
306 key_data: &[u8],
307 extractable: bool,
308 usages: Vec<KeyUsage>,
309) -> Result<DomRoot<CryptoKey>, Error> {
310 // Step 1. Let keyData be the key data to be imported.
311
312 // Step 2.
313 let key = match format {
314 // If format is "spki":
315 KeyFormat::Spki => {
316 // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
317 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
318 return Err(Error::Syntax(Some(
319 "Usages contains an entry which is not \"verify\"".into(),
320 )));
321 }
322
323 // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
324 // algorithm over keyData.
325 // Step 2.3. If an error occurred while parsing, then throw a DataError.
326 // Step 2.4.
327 // If the name member of normalizedAlgorithm is "ML-DSA-44":
328 // Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
329 // If the name member of normalizedAlgorithm is "ML-DSA-65":
330 // Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
331 // If the name member of normalizedAlgorithm is "ML-DSA-87":
332 // Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
333 // Otherwise:
334 // throw a NotSupportedError.
335 // Step 2.5. If the algorithm object identifier field of the algorithm
336 // AlgorithmIdentifier field of spki is not equal to expectedOid, then throw a
337 // DataError.
338 // Step 2.6. If the parameters field of the algorithm AlgorithmIdentifier field of spki
339 // is present, then throw a DataError.
340 // Step 2.7. Let publicKey be the ML-DSA public key identified by the subjectPublicKey
341 // field of spki.
342 let public_key = match normalized_algorithm.name {
343 CryptoAlgorithm::MlDsa44 => Handle::MlDsa44PublicKey(
344 VerifyingKey::from_public_key_der(key_data).map_err(|_| {
345 Error::Data(Some(
346 "Failed to decode the ML-DSA-44 public key from SPKI format".into(),
347 ))
348 })?,
349 ),
350 CryptoAlgorithm::MlDsa65 => Handle::MlDsa65PublicKey(
351 VerifyingKey::from_public_key_der(key_data).map_err(|_| {
352 Error::Data(Some(
353 "Failed to decode the ML-DSA-65 public key from SPKI format".into(),
354 ))
355 })?,
356 ),
357 CryptoAlgorithm::MlDsa87 => Handle::MlDsa87PublicKey(
358 VerifyingKey::from_public_key_der(key_data).map_err(|_| {
359 Error::Data(Some(
360 "Failed to decode the ML-DSA-87 pulic key from SPKI format".into(),
361 ))
362 })?,
363 ),
364 name => {
365 return Err(Error::NotSupported(Some(format!(
366 "{} is not an ML-DSA algorithm",
367 name.as_str()
368 ))));
369 },
370 };
371
372 // Step 2.8. Let key be a new CryptoKey that represents publicKey.
373 // Step 2.9. Set the [[type]] internal slot of key to "public"
374 // Step 2.10. Let algorithm be a new KeyAlgorithm.
375 // Step 2.11. Set the name attribute of algorithm to the name attribute of
376 // normalizedAlgorithm.
377 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
378 let algorithm = KeyAlgorithm {
379 name: normalized_algorithm.name,
380 };
381 CryptoKey::new(
382 cx,
383 global,
384 KeyType::Public,
385 extractable,
386 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
387 usages.normalized_value(),
388 public_key,
389 )
390 },
391 // If format is "pkcs8":
392 KeyFormat::Pkcs8 => {
393 // Step 2.1. If usages contains a value which is not "sign" then throw a SyntaxError.
394 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
395 return Err(Error::Syntax(Some(
396 "Usages contains an entry which is not \"sign\"".into(),
397 )));
398 }
399
400 // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
401 // algorithm over keyData.
402 // Step 2.3. If an error occurs while parsing, then throw a DataError.
403 // Step 2.4.
404 // If the name member of normalizedAlgorithm is "ML-DSA-44":
405 // Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
406 // Let asn1Structure be the ASN.1 ML-DSA-44-PrivateKey structure.
407 // If the name member of normalizedAlgorithm is "ML-DSA-65":
408 // Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
409 // Let asn1Structure be the ASN.1 ML-DSA-65-PrivateKey structure.
410 // If the name member of normalizedAlgorithm is "ML-DSA-87":
411 // Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
412 // Let asn1Structure be the ASN.1 ML-DSA-87-PrivateKey structure.
413 // Otherwise:
414 // throw a NotSupportedError.
415 // Step 2.5. If the algorithm object identifier field of the privateKeyAlgorithm
416 // PrivateKeyAlgorithm field of privateKeyInfo is not equal to expectedOid, then throw
417 // a DataError.
418 // Step 2.6. If the parameters field of the privateKeyAlgorithm
419 // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
420 // DataError.
421 // Step 2.7. Let mlDsaPrivateKey be the result of performing the parse an ASN.1
422 // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
423 // as asn1Structure, and exactData set to true.
424 // Step 2.8. If an error occurred while parsing, then throw a DataError.
425 // Step 2.9. If mlDsaPrivateKey represents an ML-DSA key in the expandedKey format, or
426 // if mlDsaPrivateKey represents an ML-DSA key in the both format and the both format
427 // is not supported, throw a NotSupportedError.
428 // Step 2.10. If mlDsaPrivateKey represents an ML-DSA key in the both format, and the
429 // seed field does not correspond to the expandedKey field, throw a DataError.
430 //
431 // NOTE: We do not support the `both` format.
432 let ml_dsa_private_key = match normalized_algorithm.name {
433 CryptoAlgorithm::MlDsa44 => Handle::MlDsa44PrivateKey(
434 SigningKey::from_pkcs8_der(key_data).map_err(|_| {
435 Error::Data(Some(
436 "Failed to decode the ML-DSA-44 private key from PKCS#8 format".into(),
437 ))
438 })?,
439 ),
440 CryptoAlgorithm::MlDsa65 => Handle::MlDsa65PrivateKey(
441 SigningKey::from_pkcs8_der(key_data).map_err(|_| {
442 Error::Data(Some(
443 "Failed to decode the ML-DSA-65 private key from PKCS#8 format".into(),
444 ))
445 })?,
446 ),
447 CryptoAlgorithm::MlDsa87 => Handle::MlDsa87PrivateKey(
448 SigningKey::from_pkcs8_der(key_data).map_err(|_| {
449 Error::Data(Some(
450 "Failed to decode the ML-DSA-87 private key from PKCS#8 format".into(),
451 ))
452 })?,
453 ),
454 name => {
455 return Err(Error::NotSupported(Some(format!(
456 "{} is not an ML-DSA algorithm",
457 name.as_str()
458 ))));
459 },
460 };
461
462 // Step 2.11. Let key be a new CryptoKey that represents the ML-DSA private key
463 // identified by mlDsaPrivateKey.
464 // Step 2.12. Set the [[type]] internal slot of key to "private"
465 // Step 2.13. Let algorithm be a new KeyAlgorithm.
466 // Step 2.14. Set the name attribute of algorithm to the name attribute of
467 // normalizedAlgorithm.
468 // Step 2.15. Set the [[algorithm]] internal slot of key to algorithm.
469 let algorithm = KeyAlgorithm {
470 name: normalized_algorithm.name,
471 };
472 CryptoKey::new(
473 cx,
474 global,
475 KeyType::Private,
476 extractable,
477 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
478 usages.normalized_value(),
479 ml_dsa_private_key,
480 )
481 },
482 // If format is "raw-public":
483 KeyFormat::Raw_public => {
484 // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
485 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
486 return Err(Error::Syntax(Some(
487 "Usages contains an entry which is not \"verify\"".into(),
488 )));
489 }
490
491 // Step 2.2. Let algorithm be a new KeyAlgorithm object.
492 // Step 2.3. Set the name attribute of algorithm to the name attribute of
493 // normalizedAlgorithm.
494 // Step 2.4. Let key be a new CryptoKey representing the key data provided in keyData.
495 // Step 2.5. Set the [[type]] internal slot of key to "public"
496 // Step 2.6. Set the [[algorithm]] internal slot of key to algorithm.
497 let public_key = match normalized_algorithm.name {
498 CryptoAlgorithm::MlDsa44 => {
499 let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
500 Error::Data(Some(
501 "Failed to parse the public ML-DSA-44 key in raw format".into(),
502 ))
503 })?;
504 Handle::MlDsa44PublicKey(verifying_key)
505 },
506 CryptoAlgorithm::MlDsa65 => {
507 let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
508 Error::Data(Some(
509 "Failed to parse the public ML-DSA-65 key in raw format".into(),
510 ))
511 })?;
512 Handle::MlDsa65PublicKey(verifying_key)
513 },
514 CryptoAlgorithm::MlDsa87 => {
515 let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
516 Error::Data(Some(
517 "Failed to parse the public ML-DSA-87 key in raw format".into(),
518 ))
519 })?;
520 Handle::MlDsa87PublicKey(verifying_key)
521 },
522 name => {
523 return Err(Error::NotSupported(Some(format!(
524 "{} is not an ML-DSA algorithm",
525 name.as_str()
526 ))));
527 },
528 };
529 let algorithm = KeyAlgorithm {
530 name: normalized_algorithm.name,
531 };
532 CryptoKey::new(
533 cx,
534 global,
535 KeyType::Public,
536 extractable,
537 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
538 usages.normalized_value(),
539 public_key,
540 )
541 },
542 // If format is "raw-seed":
543 KeyFormat::Raw_seed => {
544 // Step 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError.
545 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
546 return Err(Error::Syntax(Some(
547 "Usages contains an entry which is not \"sign\"".into(),
548 )));
549 }
550
551 // Step 2.2. Let data be keyData.
552 // Step 2.3. If the length in bits of data is not 256 then throw a DataError.
553 // Step 2.4. Let privateKey be the result of performing the ML-DSA.KeyGen_internal
554 // function described in Section 6.1 of [FIPS-204] with the parameter set indicated by
555 // the name member of normalizedAlgorithm, using data as ξ.
556 let private_key = match normalized_algorithm.name {
557 CryptoAlgorithm::MlDsa44 => {
558 let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
559 Error::Data(Some(
560 "Failed to parse the private ML-DSA-44 key in raw format".into(),
561 ))
562 })?;
563 Handle::MlDsa44PrivateKey(signing_key)
564 },
565 CryptoAlgorithm::MlDsa65 => {
566 let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
567 Error::Data(Some(
568 "Failed to parse the private ML-DSA-65 key in raw format".into(),
569 ))
570 })?;
571 Handle::MlDsa65PrivateKey(signing_key)
572 },
573 CryptoAlgorithm::MlDsa87 => {
574 let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
575 Error::Data(Some(
576 "Failed to parse the private ML-DSA-87 key in raw format".into(),
577 ))
578 })?;
579 Handle::MlDsa87PrivateKey(signing_key)
580 },
581 name => {
582 return Err(Error::NotSupported(Some(format!(
583 "{} is not an ML-DSA algorithm",
584 name.as_str()
585 ))));
586 },
587 };
588
589 // Step 2.5. Let key be a new CryptoKey that represents the ML-DSA private key
590 // identified by privateKey.
591 // Step 2.6. Set the [[type]] internal slot of key to "private"
592 // Step 2.7. Let algorithm be a new KeyAlgorithm.
593 // Step 2.8. Set the name attribute of algorithm to the name attribute of
594 // normalizedAlgorithm.
595 // Step 2.9. Set the [[algorithm]] internal slot of key to algorithm.
596 let algorithm = KeyAlgorithm {
597 name: normalized_algorithm.name,
598 };
599 CryptoKey::new(
600 cx,
601 global,
602 KeyType::Private,
603 extractable,
604 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
605 usages.normalized_value(),
606 private_key,
607 )
608 },
609 // If format is "jwk":
610 KeyFormat::Jwk => {
611 // Step 2.1.
612 // If keyData is a JsonWebKey dictionary:
613 // Let jwk equal keyData.
614 // Otherwise:
615 // Throw a DataError.
616 let jwk = JsonWebKey::parse(cx, key_data)?;
617
618 // Step 2.2. If the priv field is present and usages contains a value which is not
619 // "sign", or, if the priv field is not present and usages contains a value which is
620 // not "verify" then throw a SyntaxError.
621 if jwk.priv_.is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign) {
622 return Err(Error::Syntax(Some(
623 "The priv field is present and usages contains a value which is not \"sign\""
624 .into(),
625 )));
626 }
627 if jwk.priv_.is_none() && usages.iter().any(|usage| *usage != KeyUsage::Verify) {
628 return Err(Error::Syntax(Some(
629 "The priv field is not present and usages contains a value which is not \
630 \"verify\""
631 .into(),
632 )));
633 }
634
635 // Step 2.3. If the kty field of jwk is not "AKP", then throw a DataError.
636 if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
637 return Err(Error::Data(Some(
638 "The kty field of jwk is not \"AKP\"".into(),
639 )));
640 }
641
642 // Step 2.4. If the alg field of jwk is not equal to the name member of
643 // normalizedAlgorithm, then throw a DataError.
644 if jwk
645 .alg
646 .as_ref()
647 .is_none_or(|alg| alg != normalized_algorithm.name.as_str())
648 {
649 return Err(Error::Data(Some(
650 "The alg field of jwk is not equal to the name member of normalizedAlgorithm"
651 .into(),
652 )));
653 }
654
655 // Step 2.5. If usages is non-empty and the use field of jwk is present and is not
656 // equal to "sig", then throw a DataError.
657 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
658 return Err(Error::Data(Some(
659 "Usages is non-empty and the use field of jwk is present and is not \
660 equal to \"sig\""
661 .into(),
662 )));
663 }
664
665 // Step 2.6. If the key_ops field of jwk is present, and is invalid according to the
666 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
667 // usages values, then throw a DataError.
668 jwk.check_key_ops(&usages)?;
669
670 // Step 2.7. If the ext field of jwk is present and has the value false and extractable
671 // is true, then throw a DataError.
672 if jwk.ext.is_some_and(|ext| !ext) && extractable {
673 return Err(Error::Data(Some(
674 "The ext field of jwk is present and has the value false and extractable \
675 is true"
676 .into(),
677 )));
678 }
679
680 // Step 2.8.
681 // If the priv field of jwk is present:
682 let (key_type, key_handle) = if jwk.priv_.is_some() {
683 // Step 2.8.1. If the priv attribute of jwk does not contain a valid base64url
684 // encoded seed representing an ML-DSA private key, then throw a DataError.
685 let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
686
687 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA private
688 // key identified by interpreting the priv attribute of jwk as a base64url encoded
689 // seed.
690 // Step 2.8.3. Set the [[type]] internal slot of key to "private".
691 // Step 2.8.4. If the pub attribute of jwk does not contain the base64url encoded
692 // public key representing the ML-DSA public key corresponding to key, then throw a
693 // DataError.
694 // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
695 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
696 let private_key_handle = match normalized_algorithm.name {
697 CryptoAlgorithm::MlDsa44 => {
698 let signing_key =
699 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
700 Error::Data(Some(
701 "Failed to parse the private ML-DSA-44 key in priv attribute"
702 .into(),
703 ))
704 })?;
705 let verifying_key =
706 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
707 Error::Data(Some(
708 "Failed to parse the public ML-DSA-44 key in pub attribute"
709 .into(),
710 ))
711 })?;
712 if signing_key.verifying_key() != verifying_key {
713 return Err(Error::Data(Some(
714 "The public key in pub attribute does not match \
715 the private key in priv attribute"
716 .into(),
717 )));
718 }
719 Handle::MlDsa44PrivateKey(signing_key)
720 },
721 CryptoAlgorithm::MlDsa65 => {
722 let signing_key =
723 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
724 Error::Data(Some(
725 "Failed to parse the private ML-DSA-65 key in priv attribute"
726 .into(),
727 ))
728 })?;
729 let verifying_key =
730 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
731 Error::Data(Some(
732 "Failed to parse the public ML-DSA-65 key in pub attribute"
733 .into(),
734 ))
735 })?;
736 if signing_key.verifying_key() != verifying_key {
737 return Err(Error::Data(Some(
738 "The public key in pub attribute does not match \
739 the private key in priv attribute"
740 .into(),
741 )));
742 }
743 Handle::MlDsa65PrivateKey(signing_key)
744 },
745 CryptoAlgorithm::MlDsa87 => {
746 let signing_key =
747 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
748 Error::Data(Some(
749 "Failed to parse the private ML-DSA-87 key in priv attribute"
750 .into(),
751 ))
752 })?;
753 let verifying_key =
754 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
755 Error::Data(Some(
756 "Failed to parse the public ML-DSA-87 key in pub attribute"
757 .into(),
758 ))
759 })?;
760 if signing_key.verifying_key() != verifying_key {
761 return Err(Error::Data(Some(
762 "The public key in pub attribute does not match \
763 the private key in priv attribute"
764 .into(),
765 )));
766 }
767 Handle::MlDsa87PrivateKey(signing_key)
768 },
769 name => {
770 return Err(Error::NotSupported(Some(format!(
771 "{} is not an ML-DSA algorithm",
772 name.as_str()
773 ))));
774 },
775 };
776 (KeyType::Private, private_key_handle)
777 }
778 // Otherwise:
779 else {
780 // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
781 // encoded public key representing an ML-DSA public key, then throw a DataError.
782 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
783
784 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
785 // key identified by interpreting the pub attribute of jwk as a base64url encoded
786 // public key.
787 // Step 2.8.3. Set the [[type]] internal slot of key to "public".
788 // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
789 let public_key_handle = match normalized_algorithm.name {
790 CryptoAlgorithm::MlDsa44 => {
791 let verifying_key =
792 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
793 Error::Data(Some(
794 "Failed to parse the public ML-DSA-44 key in pub attribute"
795 .into(),
796 ))
797 })?;
798 Handle::MlDsa44PublicKey(verifying_key)
799 },
800 CryptoAlgorithm::MlDsa65 => {
801 let verifying_key =
802 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
803 Error::Data(Some(
804 "Failed to parse the public ML-DSA-65 key in pub attribute"
805 .into(),
806 ))
807 })?;
808 Handle::MlDsa65PublicKey(verifying_key)
809 },
810 CryptoAlgorithm::MlDsa87 => {
811 let verifying_key =
812 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
813 Error::Data(Some(
814 "Failed to parse the public ML-DSA-87 key in pub attribute"
815 .into(),
816 ))
817 })?;
818 Handle::MlDsa87PublicKey(verifying_key)
819 },
820 name => {
821 return Err(Error::NotSupported(Some(format!(
822 "{} is not an ML-DSA algorithm",
823 name.as_str()
824 ))));
825 },
826 };
827 (KeyType::Public, public_key_handle)
828 };
829
830 // Step 2.9. Let algorithm be a new instance of a KeyAlgorithm object.
831 // Step 2.10. Set the name attribute of algorithm to the name member of
832 // normalizedAlgorithm.
833 // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
834 let algorithm = KeyAlgorithm {
835 name: normalized_algorithm.name,
836 };
837 CryptoKey::new(
838 cx,
839 global,
840 key_type,
841 extractable,
842 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
843 usages.normalized_value(),
844 key_handle,
845 )
846 },
847 // Otherwise:
848 _ => {
849 // throw a NotSupportedError.
850 return Err(Error::NotSupported(Some(
851 "Unsupported import key format for ML-DSA key".into(),
852 )));
853 },
854 };
855
856 // Step 3. Return key.
857 Ok(key)
858}
859
860/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
861pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
862 // Step 1. Let key be the CryptoKey to be exported.
863
864 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
865 // slot of key cannot be accessed, then throw an OperationError.
866
867 // Step 3.
868 let result = match format {
869 KeyFormat::Spki => {
870 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
871 // InvalidAccessError.
872 if key.Type() != KeyType::Public {
873 return Err(Error::InvalidAccess(Some(
874 "[[type]] internal slot of key is not \"public\"".into(),
875 )));
876 }
877
878 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
879 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
880 return Err(Error::Operation(Some(
881 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
882 )));
883 };
884
885 // Step 3.3.
886 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
887 // [RFC5280] with the following properties:
888 //
889 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
890 // properties:
891 //
892 // If the name member of keyAlgorithm is "ML-DSA-44":
893 // Set the algorithm object identifier to the id-ml-dsa-44
894 // (2.16.840.1.101.3.4.3.17) OID.
895 //
896 // If the name member of keyAlgorithm is "ML-DSA-65":
897 // Set the algorithm object identifier to the id-ml-dsa-65
898 // (2.16.840.1.101.3.4.3.18) OID.
899 //
900 // If the name member of keyAlgorithm is "ML-DSA-87":
901 // Set the algorithm object identifier to the id-ml-dsa-87
902 // (2.16.840.1.101.3.4.3.19) OID.
903 //
904 // Otherwise:
905 // throw a NotSupportedError.
906 //
907 // Set the subjectPublicKey field to keyData.
908 let data = match (key_algorithm.name, key.handle()) {
909 (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PublicKey(public_key)) => {
910 public_key.to_public_key_der().map_err(|_| {
911 Error::Operation(Some(
912 "Failed to encode the ML-DSA-44 public key into SPKI format".into(),
913 ))
914 })?
915 },
916 (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PublicKey(public_key)) => {
917 public_key.to_public_key_der().map_err(|_| {
918 Error::Operation(Some(
919 "Failed to encode the ML-DSA-65 public key into SPKI format".into(),
920 ))
921 })?
922 },
923 (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PublicKey(public_key)) => {
924 public_key.to_public_key_der().map_err(|_| {
925 Error::Operation(Some(
926 "Failed to encode the ML-DSA-87 public key into SPKI format".into(),
927 ))
928 })?
929 },
930 _ => {
931 return Err(Error::Operation(Some(
932 "The key handle is not representing an ML-DSA public key".into(),
933 )));
934 },
935 };
936
937 // Step 3.4. Let result be the result of DER-encoding data.
938 ExportedKey::new_bytes(data.into_vec())
939 },
940 KeyFormat::Pkcs8 => {
941 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
942 // InvalidAccessError.
943 if key.Type() != KeyType::Private {
944 return Err(Error::InvalidAccess(Some(
945 "[[type]] internal slot of key is not \"private\"".into(),
946 )));
947 }
948
949 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
950 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
951 return Err(Error::Operation(Some(
952 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
953 )));
954 };
955
956 // Step 3.3.
957 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
958 // with the following properties:
959 //
960 // Set the version field to 0.
961 //
962 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
963 // with the following properties:
964 //
965 // If the name member of keyAlgorithm is "ML-DSA-44":
966 // Set the algorithm object identifier to the id-ml-dsa-44
967 // (2.16.840.1.101.3.4.3.17) OID.
968 //
969 // If the name member of keyAlgorithm is "ML-DSA-65":
970 // Set the algorithm object identifier to the id-ml-dsa-65
971 // (2.16.840.1.101.3.4.3.18) OID.
972 //
973 // If the name member of keyAlgorithm is "ML-DSA-87":
974 // Set the algorithm object identifier to the id-ml-dsa-87
975 // (2.16.840.1.101.3.4.3.19) OID.
976 //
977 // Otherwise:
978 // throw a NotSupportedError.
979 //
980 // Set the privateKey field as follows:
981 //
982 // If the name member of keyAlgorithm is "ML-DSA-44":
983 // Set the privateKey field to the result of DER-encoding a
984 // ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
985 // seed represented by the [[handle]] internal slot of key using the
986 // seed-only format (using a context-specific [0] primitive tag with an
987 // implicit encoding of OCTET STRING).
988 //
989 // If the name member of keyAlgorithm is "ML-DSA-65":
990 // Set the privateKey field to the result of DER-encoding a
991 // ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
992 // seed represented by the [[handle]] internal slot of key using the
993 // seed-only format (using a context-specific [0] primitive tag with an
994 // implicit encoding of OCTET STRING).
995 //
996 // If the name member of keyAlgorithm is "ML-DSA-87":
997 // Set the privateKey field to the result of DER-encoding a
998 // ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
999 // seed represented by the [[handle]] internal slot of key using the
1000 // seed-only format (using a context-specific [0] primitive tag with an
1001 // implicit encoding of OCTET STRING).
1002 //
1003 // Otherwise:
1004 // throw a NotSupportedError.
1005 let private_key_info = match (key_algorithm.name, key.handle()) {
1006 (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PrivateKey(private_key)) => {
1007 private_key.to_pkcs8_der().map_err(|_| {
1008 Error::Operation(Some(
1009 "Failed to encode the ML-DSA-44 private key into PKCS#8 format".into(),
1010 ))
1011 })?
1012 },
1013 (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PrivateKey(private_key)) => {
1014 private_key.to_pkcs8_der().map_err(|_| {
1015 Error::Operation(Some(
1016 "Failed to encode the ML-DSA-65 private key into PKCS#8 format".into(),
1017 ))
1018 })?
1019 },
1020 (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PrivateKey(private_key)) => {
1021 private_key.to_pkcs8_der().map_err(|_| {
1022 Error::Operation(Some(
1023 "Failed to encode the ML-DSA-87 private key into PKCS#8 format".into(),
1024 ))
1025 })?
1026 },
1027 _ => {
1028 return Err(Error::Operation(Some(
1029 "The key handle is not representing an ML-DSA private key".into(),
1030 )));
1031 },
1032 };
1033
1034 // Step 3.4. Let result be the result of DER-encoding data.
1035 ExportedKey::Bytes(private_key_info.to_bytes())
1036 },
1037 // If format is "raw-public":
1038 KeyFormat::Raw_public => {
1039 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
1040 // InvalidAccessError.
1041 if key.Type() != KeyType::Public {
1042 return Err(Error::InvalidAccess(Some(
1043 "[[type]] internal slot of key is not \"public\"".into(),
1044 )));
1045 }
1046
1047 // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1048 // by the [[handle]] internal slot of key.
1049 let data = match key.handle() {
1050 Handle::MlDsa44PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1051 Handle::MlDsa65PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1052 Handle::MlDsa87PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1053 _ => {
1054 return Err(Error::Operation(Some(
1055 "The key handle is not representing an ML-DSA public key".into(),
1056 )));
1057 },
1058 };
1059
1060 // Step 3.2. Let result be data.
1061 ExportedKey::new_bytes(data)
1062 },
1063 // If format is "raw-seed":
1064 KeyFormat::Raw_seed => {
1065 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1066 // InvalidAccessError.
1067 if key.Type() != KeyType::Private {
1068 return Err(Error::InvalidAccess(Some(
1069 "[[type]] internal slot of key is not \"private\"".into(),
1070 )));
1071 }
1072
1073 // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1074 // represented by the [[handle]] internal slot of key.
1075 let data = match key.handle() {
1076 Handle::MlDsa44PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1077 Handle::MlDsa65PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1078 Handle::MlDsa87PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1079 _ => {
1080 return Err(Error::Operation(Some(
1081 "The key handle is not representing an ML-DSA private key".into(),
1082 )));
1083 },
1084 };
1085
1086 // Step 3.3. Let result be data.
1087 ExportedKey::new_bytes(data)
1088 },
1089 // If format is "jwk":
1090 KeyFormat::Jwk => {
1091 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1092 let mut jwk = JsonWebKey::default();
1093
1094 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
1095 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1096 return Err(Error::Operation(Some(
1097 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
1098 )));
1099 };
1100
1101 // Step 3.3. Set the kty attribute of jwk to "AKP".
1102 jwk.kty = Some(DOMString::from_static("AKP"));
1103
1104 // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1105 jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1106
1107 // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1108 // corresponding to the [[handle]] internal slot of key.
1109 // Step 3.6.
1110 // If the [[type]] internal slot of key is "private":
1111 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1112 // [[handle]] internal slot of key.
1113 if key.Type() == KeyType::Private {
1114 match key.handle() {
1115 Handle::MlDsa44PrivateKey(private_key) => {
1116 jwk.encode_string_field(
1117 JwkStringField::Priv,
1118 private_key.as_seed().as_slice(),
1119 );
1120 jwk.encode_string_field(
1121 JwkStringField::Pub,
1122 private_key.as_ref().to_bytes().as_slice(),
1123 );
1124 },
1125 Handle::MlDsa65PrivateKey(private_key) => {
1126 jwk.encode_string_field(
1127 JwkStringField::Priv,
1128 private_key.as_seed().as_slice(),
1129 );
1130 jwk.encode_string_field(
1131 JwkStringField::Pub,
1132 private_key.as_ref().to_bytes().as_slice(),
1133 );
1134 },
1135 Handle::MlDsa87PrivateKey(private_key) => {
1136 jwk.encode_string_field(
1137 JwkStringField::Priv,
1138 private_key.as_seed().as_slice(),
1139 );
1140 jwk.encode_string_field(
1141 JwkStringField::Pub,
1142 private_key.as_ref().to_bytes().as_slice(),
1143 );
1144 },
1145 _ => {
1146 return Err(Error::Operation(Some(
1147 "The key handle is not representing an ML-DSA private key".into(),
1148 )));
1149 },
1150 }
1151 } else {
1152 match key.handle() {
1153 Handle::MlDsa44PublicKey(public_key) => {
1154 jwk.encode_string_field(
1155 JwkStringField::Pub,
1156 public_key.to_bytes().as_slice(),
1157 );
1158 },
1159 Handle::MlDsa65PublicKey(public_key) => {
1160 jwk.encode_string_field(
1161 JwkStringField::Pub,
1162 public_key.to_bytes().as_slice(),
1163 );
1164 },
1165 Handle::MlDsa87PublicKey(public_key) => {
1166 jwk.encode_string_field(
1167 JwkStringField::Pub,
1168 public_key.to_bytes().as_slice(),
1169 );
1170 },
1171 _ => {
1172 return Err(Error::Operation(Some(
1173 "The key handle is not representing an ML-DSA public key".into(),
1174 )));
1175 },
1176 };
1177 }
1178
1179 // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1180 jwk.set_key_ops(key.usages());
1181
1182 // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1183 jwk.ext = Some(key.Extractable());
1184
1185 // Step 3.9. Let result be jwk.
1186 ExportedKey::new_jwk(jwk)
1187 },
1188 // Otherwise:
1189 _ => {
1190 // throw a NotSupportedError.
1191 return Err(Error::NotSupported(Some(
1192 "Unsupported export key format for ML-DSA key".into(),
1193 )));
1194 },
1195 };
1196
1197 // Step 3. Return result.
1198 Ok(result)
1199}
1200
1201/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
1202/// Step 9 - 15, for ML-DSA
1203pub(crate) fn get_public_key(
1204 cx: &mut JSContext,
1205 global: &GlobalScope,
1206 key: &CryptoKey,
1207 algorithm: &KeyAlgorithmAndDerivatives,
1208 usages: Vec<KeyUsage>,
1209) -> Result<DomRoot<CryptoKey>, Error> {
1210 // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
1211 // identified by algorithm, then throw a SyntaxError.
1212 //
1213 // NOTE: See "importKey" operation for supported usages
1214 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
1215 return Err(Error::Syntax(Some(
1216 "Usages contains an entry which is not \"verify\"".into(),
1217 )));
1218 }
1219
1220 // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
1221 // private key represented by the [[handle]] internal slot of key.
1222 // Step 11. If an error occurred, then throw a OperationError.
1223 // Step 12. Set the [[type]] internal slot of publicKey to "public".
1224 // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
1225 // Step 14. Set the [[extractable]] internal slot of publicKey to true.
1226 // Step 15. Set the [[usages]] internal slot of publicKey to usages.
1227 let public_key_handle = match key.handle() {
1228 Handle::MlDsa44PrivateKey(private_key) => {
1229 Handle::MlDsa44PublicKey(private_key.verifying_key())
1230 },
1231 Handle::MlDsa65PrivateKey(private_key) => {
1232 Handle::MlDsa65PublicKey(private_key.verifying_key())
1233 },
1234 Handle::MlDsa87PrivateKey(private_key) => {
1235 Handle::MlDsa87PublicKey(private_key.verifying_key())
1236 },
1237 _ => {
1238 return Err(Error::Operation(Some(
1239 "[[handle]] internal slot of key is not an ML-DSA private key".into(),
1240 )));
1241 },
1242 };
1243 let public_key = CryptoKey::new(
1244 cx,
1245 global,
1246 KeyType::Public,
1247 true,
1248 algorithm.clone(),
1249 usages,
1250 public_key_handle,
1251 );
1252
1253 Ok(public_key)
1254}