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