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