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 let (key_type, key_handle) = 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 // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
679 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
680 let private_key_handle = match normalized_algorithm.name {
681 CryptoAlgorithm::MlDsa44 => {
682 let signing_key =
683 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
684 Error::Data(Some(
685 "Failed to parse the private ML-DSA-44 key in priv attribute"
686 .into(),
687 ))
688 })?;
689 let verifying_key =
690 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
691 Error::Data(Some(
692 "Failed to parse the public ML-DSA-44 key in pub attribute"
693 .into(),
694 ))
695 })?;
696 if signing_key.verifying_key() != verifying_key {
697 return Err(Error::Data(Some(
698 "The public key in pub attribute does not match \
699 the private key in priv attribute"
700 .into(),
701 )));
702 }
703 Handle::MlDsa44PrivateKey(signing_key)
704 },
705 CryptoAlgorithm::MlDsa65 => {
706 let signing_key =
707 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
708 Error::Data(Some(
709 "Failed to parse the private ML-DSA-65 key in priv attribute"
710 .into(),
711 ))
712 })?;
713 let verifying_key =
714 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
715 Error::Data(Some(
716 "Failed to parse the public ML-DSA-65 key in pub attribute"
717 .into(),
718 ))
719 })?;
720 if signing_key.verifying_key() != verifying_key {
721 return Err(Error::Data(Some(
722 "The public key in pub attribute does not match \
723 the private key in priv attribute"
724 .into(),
725 )));
726 }
727 Handle::MlDsa65PrivateKey(signing_key)
728 },
729 CryptoAlgorithm::MlDsa87 => {
730 let signing_key =
731 SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
732 Error::Data(Some(
733 "Failed to parse the private ML-DSA-87 key in priv attribute"
734 .into(),
735 ))
736 })?;
737 let verifying_key =
738 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
739 Error::Data(Some(
740 "Failed to parse the public ML-DSA-87 key in pub attribute"
741 .into(),
742 ))
743 })?;
744 if signing_key.verifying_key() != verifying_key {
745 return Err(Error::Data(Some(
746 "The public key in pub attribute does not match \
747 the private key in priv attribute"
748 .into(),
749 )));
750 }
751 Handle::MlDsa87PrivateKey(signing_key)
752 },
753 name => {
754 return Err(Error::NotSupported(Some(format!(
755 "{} is not an ML-DSA algorithm",
756 name.as_str()
757 ))));
758 },
759 };
760 (KeyType::Private, private_key_handle)
761 }
762 // Otherwise:
763 else {
764 // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
765 // encoded public key representing an ML-DSA public key, then throw a DataError.
766 let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
767
768 // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
769 // key identified by interpreting the pub attribute of jwk as a base64url encoded
770 // public key.
771 // Step 2.8.3. Set the [[type]] internal slot of key to "public".
772 // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
773 let public_key_handle = match normalized_algorithm.name {
774 CryptoAlgorithm::MlDsa44 => {
775 let verifying_key =
776 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
777 Error::Data(Some(
778 "Failed to parse the public ML-DSA-44 key in pub attribute"
779 .into(),
780 ))
781 })?;
782 Handle::MlDsa44PublicKey(verifying_key)
783 },
784 CryptoAlgorithm::MlDsa65 => {
785 let verifying_key =
786 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
787 Error::Data(Some(
788 "Failed to parse the public ML-DSA-65 key in pub attribute"
789 .into(),
790 ))
791 })?;
792 Handle::MlDsa65PublicKey(verifying_key)
793 },
794 CryptoAlgorithm::MlDsa87 => {
795 let verifying_key =
796 VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
797 Error::Data(Some(
798 "Failed to parse the public ML-DSA-87 key in pub attribute"
799 .into(),
800 ))
801 })?;
802 Handle::MlDsa87PublicKey(verifying_key)
803 },
804 name => {
805 return Err(Error::NotSupported(Some(format!(
806 "{} is not an ML-DSA algorithm",
807 name.as_str()
808 ))));
809 },
810 };
811 (KeyType::Public, public_key_handle)
812 };
813
814 // Step 2.9. Let algorithm be a new instance of a KeyAlgorithm object.
815 // Step 2.10. Set the name attribute of algorithm to the name member of
816 // normalizedAlgorithm.
817 // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
818 let algorithm = SubtleKeyAlgorithm {
819 name: normalized_algorithm.name,
820 };
821 CryptoKey::new(
822 cx,
823 global,
824 key_type,
825 extractable,
826 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
827 usages,
828 key_handle,
829 )
830 },
831 // Otherwise:
832 _ => {
833 // throw a NotSupportedError.
834 return Err(Error::NotSupported(Some(
835 "Unsupported import key format for ML-DSA key".into(),
836 )));
837 },
838 };
839
840 // Step 3. Return key.
841 Ok(key)
842}
843
844/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
845pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
846 // Step 1. Let key be the CryptoKey to be exported.
847
848 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
849 // slot of key cannot be accessed, then throw an OperationError.
850
851 // Step 3.
852 let result = match format {
853 KeyFormat::Spki => {
854 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
855 // InvalidAccessError.
856 if key.Type() != KeyType::Public {
857 return Err(Error::InvalidAccess(Some(
858 "[[type]] internal slot of key is not \"public\"".into(),
859 )));
860 }
861
862 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
863 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
864 return Err(Error::Operation(Some(
865 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
866 )));
867 };
868
869 // Step 3.3.
870 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
871 // [RFC5280] with the following properties:
872 //
873 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
874 // properties:
875 //
876 // If the name member of keyAlgorithm is "ML-DSA-44":
877 // Set the algorithm object identifier to the id-ml-dsa-44
878 // (2.16.840.1.101.3.4.3.17) OID.
879 //
880 // If the name member of keyAlgorithm is "ML-DSA-65":
881 // Set the algorithm object identifier to the id-ml-dsa-65
882 // (2.16.840.1.101.3.4.3.18) OID.
883 //
884 // If the name member of keyAlgorithm is "ML-DSA-87":
885 // Set the algorithm object identifier to the id-ml-dsa-87
886 // (2.16.840.1.101.3.4.3.19) OID.
887 //
888 // Otherwise:
889 // throw a NotSupportedError.
890 //
891 // Set the subjectPublicKey field to keyData.
892 let data = match (key_algorithm.name, key.handle()) {
893 (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PublicKey(public_key)) => {
894 public_key.to_public_key_der().map_err(|_| {
895 Error::Operation(Some(
896 "Failed to encode the ML-DSA-44 public key into SPKI format".into(),
897 ))
898 })?
899 },
900 (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PublicKey(public_key)) => {
901 public_key.to_public_key_der().map_err(|_| {
902 Error::Operation(Some(
903 "Failed to encode the ML-DSA-65 public key into SPKI format".into(),
904 ))
905 })?
906 },
907 (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PublicKey(public_key)) => {
908 public_key.to_public_key_der().map_err(|_| {
909 Error::Operation(Some(
910 "Failed to encode the ML-DSA-87 public key into SPKI format".into(),
911 ))
912 })?
913 },
914 _ => {
915 return Err(Error::Operation(Some(
916 "The key handle is not representing an ML-DSA public key".into(),
917 )));
918 },
919 };
920
921 // Step 3.4. Let result be the result of DER-encoding data.
922 ExportedKey::new_bytes(data.into_vec())
923 },
924 KeyFormat::Pkcs8 => {
925 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
926 // InvalidAccessError.
927 if key.Type() != KeyType::Private {
928 return Err(Error::InvalidAccess(Some(
929 "[[type]] internal slot of key is not \"private\"".into(),
930 )));
931 }
932
933 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
934 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
935 return Err(Error::Operation(Some(
936 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
937 )));
938 };
939
940 // Step 3.3.
941 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
942 // with the following properties:
943 //
944 // Set the version field to 0.
945 //
946 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
947 // with the following properties:
948 //
949 // If the name member of keyAlgorithm is "ML-DSA-44":
950 // Set the algorithm object identifier to the id-ml-dsa-44
951 // (2.16.840.1.101.3.4.3.17) OID.
952 //
953 // If the name member of keyAlgorithm is "ML-DSA-65":
954 // Set the algorithm object identifier to the id-ml-dsa-65
955 // (2.16.840.1.101.3.4.3.18) OID.
956 //
957 // If the name member of keyAlgorithm is "ML-DSA-87":
958 // Set the algorithm object identifier to the id-ml-dsa-87
959 // (2.16.840.1.101.3.4.3.19) OID.
960 //
961 // Otherwise:
962 // throw a NotSupportedError.
963 //
964 // Set the privateKey field as follows:
965 //
966 // If the name member of keyAlgorithm is "ML-DSA-44":
967 // Set the privateKey field to the result of DER-encoding a
968 // ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
969 // seed represented by the [[handle]] internal slot of key using the
970 // seed-only format (using a context-specific [0] primitive tag with an
971 // implicit encoding of OCTET STRING).
972 //
973 // If the name member of keyAlgorithm is "ML-DSA-65":
974 // Set the privateKey field to the result of DER-encoding a
975 // ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
976 // seed represented by the [[handle]] internal slot of key using the
977 // seed-only format (using a context-specific [0] primitive tag with an
978 // implicit encoding of OCTET STRING).
979 //
980 // If the name member of keyAlgorithm is "ML-DSA-87":
981 // Set the privateKey field to the result of DER-encoding a
982 // ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
983 // seed represented by the [[handle]] internal slot of key using the
984 // seed-only format (using a context-specific [0] primitive tag with an
985 // implicit encoding of OCTET STRING).
986 //
987 // Otherwise:
988 // throw a NotSupportedError.
989 let private_key_info = match (key_algorithm.name, key.handle()) {
990 (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PrivateKey(private_key)) => {
991 private_key.to_pkcs8_der().map_err(|_| {
992 Error::Operation(Some(
993 "Failed to encode the ML-DSA-44 private key into PKCS#8 format".into(),
994 ))
995 })?
996 },
997 (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PrivateKey(private_key)) => {
998 private_key.to_pkcs8_der().map_err(|_| {
999 Error::Operation(Some(
1000 "Failed to encode the ML-DSA-65 private key into PKCS#8 format".into(),
1001 ))
1002 })?
1003 },
1004 (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PrivateKey(private_key)) => {
1005 private_key.to_pkcs8_der().map_err(|_| {
1006 Error::Operation(Some(
1007 "Failed to encode the ML-DSA-87 private key into PKCS#8 format".into(),
1008 ))
1009 })?
1010 },
1011 _ => {
1012 return Err(Error::Operation(Some(
1013 "The key handle is not representing an ML-DSA private key".into(),
1014 )));
1015 },
1016 };
1017
1018 // Step 3.4. Let result be the result of DER-encoding data.
1019 ExportedKey::Bytes(private_key_info.to_bytes())
1020 },
1021 // If format is "raw-public":
1022 KeyFormat::Raw_public => {
1023 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
1024 // InvalidAccessError.
1025 if key.Type() != KeyType::Public {
1026 return Err(Error::InvalidAccess(Some(
1027 "[[type]] internal slot of key is not \"public\"".into(),
1028 )));
1029 }
1030
1031 // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
1032 // by the [[handle]] internal slot of key.
1033 let data = match key.handle() {
1034 Handle::MlDsa44PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1035 Handle::MlDsa65PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1036 Handle::MlDsa87PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1037 _ => {
1038 return Err(Error::Operation(Some(
1039 "The key handle is not representing an ML-DSA public key".into(),
1040 )));
1041 },
1042 };
1043
1044 // Step 3.2. Let result be data.
1045 ExportedKey::new_bytes(data)
1046 },
1047 // If format is "raw-seed":
1048 KeyFormat::Raw_seed => {
1049 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1050 // InvalidAccessError.
1051 if key.Type() != KeyType::Private {
1052 return Err(Error::InvalidAccess(Some(
1053 "[[type]] internal slot of key is not \"private\"".into(),
1054 )));
1055 }
1056
1057 // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1058 // represented by the [[handle]] internal slot of key.
1059 let data = match key.handle() {
1060 Handle::MlDsa44PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1061 Handle::MlDsa65PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1062 Handle::MlDsa87PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1063 _ => {
1064 return Err(Error::Operation(Some(
1065 "The key handle is not representing an ML-DSA private key".into(),
1066 )));
1067 },
1068 };
1069
1070 // Step 3.3. Let result be data.
1071 ExportedKey::new_bytes(data)
1072 },
1073 // If format is "jwk":
1074 KeyFormat::Jwk => {
1075 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1076 let mut jwk = JsonWebKey::default();
1077
1078 // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
1079 let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1080 return Err(Error::Operation(Some(
1081 "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
1082 )));
1083 };
1084
1085 // Step 3.3. Set the kty attribute of jwk to "AKP".
1086 jwk.kty = Some(DOMString::from("AKP"));
1087
1088 // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1089 jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1090
1091 // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1092 // corresponding to the [[handle]] internal slot of key.
1093 // Step 3.6.
1094 // If the [[type]] internal slot of key is "private":
1095 // Set the priv attribute of jwk to the base64url encoded seed represented by the
1096 // [[handle]] internal slot of key.
1097 if key.Type() == KeyType::Private {
1098 match key.handle() {
1099 Handle::MlDsa44PrivateKey(private_key) => {
1100 jwk.encode_string_field(
1101 JwkStringField::Priv,
1102 private_key.as_seed().as_slice(),
1103 );
1104 jwk.encode_string_field(
1105 JwkStringField::Pub,
1106 private_key.as_ref().to_bytes().as_slice(),
1107 );
1108 },
1109 Handle::MlDsa65PrivateKey(private_key) => {
1110 jwk.encode_string_field(
1111 JwkStringField::Priv,
1112 private_key.as_seed().as_slice(),
1113 );
1114 jwk.encode_string_field(
1115 JwkStringField::Pub,
1116 private_key.as_ref().to_bytes().as_slice(),
1117 );
1118 },
1119 Handle::MlDsa87PrivateKey(private_key) => {
1120 jwk.encode_string_field(
1121 JwkStringField::Priv,
1122 private_key.as_seed().as_slice(),
1123 );
1124 jwk.encode_string_field(
1125 JwkStringField::Pub,
1126 private_key.as_ref().to_bytes().as_slice(),
1127 );
1128 },
1129 _ => {
1130 return Err(Error::Operation(Some(
1131 "The key handle is not representing an ML-DSA private key".into(),
1132 )));
1133 },
1134 }
1135 } else {
1136 match key.handle() {
1137 Handle::MlDsa44PublicKey(public_key) => {
1138 jwk.encode_string_field(
1139 JwkStringField::Pub,
1140 public_key.to_bytes().as_slice(),
1141 );
1142 },
1143 Handle::MlDsa65PublicKey(public_key) => {
1144 jwk.encode_string_field(
1145 JwkStringField::Pub,
1146 public_key.to_bytes().as_slice(),
1147 );
1148 },
1149 Handle::MlDsa87PublicKey(public_key) => {
1150 jwk.encode_string_field(
1151 JwkStringField::Pub,
1152 public_key.to_bytes().as_slice(),
1153 );
1154 },
1155 _ => {
1156 return Err(Error::Operation(Some(
1157 "The key handle is not representing an ML-DSA public key".into(),
1158 )));
1159 },
1160 };
1161 }
1162
1163 // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1164 jwk.set_key_ops(&key.usages());
1165
1166 // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1167 jwk.ext = Some(key.Extractable());
1168
1169 // Step 3.9. Let result be jwk.
1170 ExportedKey::new_jwk(jwk)
1171 },
1172 // Otherwise:
1173 _ => {
1174 // throw a NotSupportedError.
1175 return Err(Error::NotSupported(Some(
1176 "Unsupported export key format for ML-DSA key".into(),
1177 )));
1178 },
1179 };
1180
1181 // Step 3. Return result.
1182 Ok(result)
1183}
1184
1185/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
1186/// Step 9 - 15, for ML-DSA
1187pub(crate) fn get_public_key(
1188 cx: &mut JSContext,
1189 global: &GlobalScope,
1190 key: &CryptoKey,
1191 algorithm: &KeyAlgorithmAndDerivatives,
1192 usages: Vec<KeyUsage>,
1193) -> Result<DomRoot<CryptoKey>, Error> {
1194 // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
1195 // identified by algorithm, then throw a SyntaxError.
1196 //
1197 // NOTE: See "importKey" operation for supported usages
1198 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
1199 return Err(Error::Syntax(Some(
1200 "Usages contains an entry which is not \"verify\"".into(),
1201 )));
1202 }
1203
1204 // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
1205 // private key represented by the [[handle]] internal slot of key.
1206 // Step 11. If an error occurred, then throw a OperationError.
1207 // Step 12. Set the [[type]] internal slot of publicKey to "public".
1208 // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
1209 // Step 14. Set the [[extractable]] internal slot of publicKey to true.
1210 // Step 15. Set the [[usages]] internal slot of publicKey to usages.
1211 let public_key_handle = match key.handle() {
1212 Handle::MlDsa44PrivateKey(private_key) => {
1213 Handle::MlDsa44PublicKey(private_key.verifying_key())
1214 },
1215 Handle::MlDsa65PrivateKey(private_key) => {
1216 Handle::MlDsa65PublicKey(private_key.verifying_key())
1217 },
1218 Handle::MlDsa87PrivateKey(private_key) => {
1219 Handle::MlDsa87PublicKey(private_key.verifying_key())
1220 },
1221 _ => {
1222 return Err(Error::Operation(Some(
1223 "[[handle]] internal slot of key is not an ML-DSA private key".into(),
1224 )));
1225 },
1226 };
1227 let public_key = CryptoKey::new(
1228 cx,
1229 global,
1230 KeyType::Public,
1231 true,
1232 algorithm.clone(),
1233 usages,
1234 public_key_handle,
1235 );
1236
1237 Ok(public_key)
1238}