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