script/dom/subtlecrypto/rsa_pss_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 pkcs8::der::asn1::BitString;
6use pkcs8::der::{AnyRef, Decode};
7use pkcs8::rand_core::OsRng;
8use pkcs8::{PrivateKeyInfo, SubjectPublicKeyInfo};
9use rsa::pkcs1::{self, DecodeRsaPrivateKey};
10use rsa::pss::{Signature, SigningKey, VerifyingKey};
11use rsa::signature::{RandomizedSigner, SignatureEncoding, Verifier};
12use rsa::traits::PublicKeyParts;
13use rsa::{BigUint, RsaPrivateKey, RsaPublicKey};
14use sha1::Sha1;
15use sha2::{Sha256, Sha384, Sha512};
16
17use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
18 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
19};
20use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{
21 AlgorithmIdentifier, JsonWebKey, KeyFormat,
22};
23use crate::dom::bindings::error::Error;
24use crate::dom::bindings::root::DomRoot;
25use crate::dom::bindings::str::DOMString;
26use crate::dom::cryptokey::{CryptoKey, Handle};
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::subtlecrypto::rsa_common::{self, RsaAlgorithm};
29use crate::dom::subtlecrypto::{
30 ALG_RSA_PSS, ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512, ExportedKey, JsonWebKeyExt,
31 JwkStringField, KeyAlgorithmAndDerivatives, Operation, SubtleRsaHashedImportParams,
32 SubtleRsaHashedKeyAlgorithm, SubtleRsaHashedKeyGenParams, SubtleRsaPssParams,
33 normalize_algorithm,
34};
35use crate::script_runtime::CanGc;
36
37/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-sign>
38pub(crate) fn sign(
39 normalized_algorithm: &SubtleRsaPssParams,
40 key: &CryptoKey,
41 message: &[u8],
42) -> Result<Vec<u8>, Error> {
43 // Step 1. If the [[type]] internal slot of key is not "private", then throw an
44 // InvalidAccessError.
45 if key.Type() != KeyType::Private {
46 return Err(Error::InvalidAccess(Some(
47 "[[type]] internal slot of key is not \"private\"".to_string(),
48 )));
49 }
50
51 // Step 2. Perform the signature generation operation defined in Section 8.1 of [RFC3447] with
52 // the key represented by the [[handle]] internal slot of key as the signer's private key, K,
53 // and message as the message to be signed, M, and using the hash function specified by the
54 // hash attribute of the [[algorithm]] internal slot of key as the Hash option, MGF1 (defined
55 // in Section B.2.1 of [RFC3447]) as the MGF option and the saltLength member of
56 // normalizedAlgorithm as the salt length option for the EMSA-PSS-ENCODE operation.
57 // Step 3. If performing the operation results in an error, then throw an OperationError.
58 // Step 4. Let signature be the signature, S, that results from performing the operation.
59 let Handle::RsaPrivateKey(private_key) = key.handle() else {
60 return Err(Error::Operation(Some(
61 "[[handle]] internal slot of key is not an RSA private key".to_string(),
62 )));
63 };
64 let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
65 return Err(Error::Operation(Some(
66 "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
67 )));
68 };
69 let mut rng = OsRng;
70 let signature = match algorithm.hash.name() {
71 ALG_SHA1 => {
72 let signing_key = SigningKey::<Sha1>::new_with_salt_len(
73 private_key.clone(),
74 normalized_algorithm.salt_length as usize,
75 );
76 signing_key.try_sign_with_rng(&mut rng, message)
77 },
78 ALG_SHA256 => {
79 let signing_key = SigningKey::<Sha256>::new_with_salt_len(
80 private_key.clone(),
81 normalized_algorithm.salt_length as usize,
82 );
83 signing_key.try_sign_with_rng(&mut rng, message)
84 },
85 ALG_SHA384 => {
86 let signing_key = SigningKey::<Sha384>::new_with_salt_len(
87 private_key.clone(),
88 normalized_algorithm.salt_length as usize,
89 );
90 signing_key.try_sign_with_rng(&mut rng, message)
91 },
92 ALG_SHA512 => {
93 let signing_key = SigningKey::<Sha512>::new_with_salt_len(
94 private_key.clone(),
95 normalized_algorithm.salt_length as usize,
96 );
97 signing_key.try_sign_with_rng(&mut rng, message)
98 },
99 _ => {
100 return Err(Error::Operation(Some(format!(
101 "Unsupported \"{}\" hash for RSA-PSS",
102 algorithm.hash.name()
103 ))));
104 },
105 }
106 .map_err(|_| Error::Operation(Some("RSA-PSS failed to sign message".to_string())))?;
107
108 // Step 5. Return signature.
109 Ok(signature.to_vec())
110}
111
112/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-verify>
113pub(crate) fn verify(
114 normalized_algorithm: &SubtleRsaPssParams,
115 key: &CryptoKey,
116 message: &[u8],
117 signature: &[u8],
118) -> Result<bool, Error> {
119 // Step 1. If the [[type]] internal slot of key is not "public", then throw an
120 // InvalidAccessError.
121 if key.Type() != KeyType::Public {
122 return Err(Error::InvalidAccess(Some(
123 "[[type]] internal slot of key is not \"public\"".to_string(),
124 )));
125 }
126
127 // Step 2. Perform the signature verification operation defined in Section 8.1 of [RFC3447]
128 // with the key represented by the [[handle]] internal slot of key as the signer's RSA public
129 // key and message as M and signature as S and using the hash function specified by the hash
130 // attribute of the [[algorithm]] internal slot of key as the Hash option, MGF1 (defined in
131 // Section B.2.1 of [RFC3447]) as the MGF option and the saltLength member of
132 // normalizedAlgorithm as the salt length option for the EMSA-PSS-VERIFY operation.
133 // Step 3. Let result be a boolean with the value true if the result of the operation was
134 // "valid signature" and the value false otherwise.
135 let Handle::RsaPublicKey(public_key) = key.handle() else {
136 return Err(Error::Operation(Some(
137 "[[handle]] internal slot of key is not an RSA public key".to_string(),
138 )));
139 };
140 let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
141 return Err(Error::Operation(Some(
142 "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
143 )));
144 };
145 let signature = Signature::try_from(signature)
146 .map_err(|_| Error::Operation(Some("Failed to parse RSA signature".to_string())))?;
147 let result = match algorithm.hash.name() {
148 ALG_SHA1 => {
149 let verifying_key = VerifyingKey::<Sha1>::new_with_salt_len(
150 public_key.clone(),
151 normalized_algorithm.salt_length as usize,
152 );
153 verifying_key.verify(message, &signature)
154 },
155 ALG_SHA256 => {
156 let verifying_key = VerifyingKey::<Sha256>::new_with_salt_len(
157 public_key.clone(),
158 normalized_algorithm.salt_length as usize,
159 );
160 verifying_key.verify(message, &signature)
161 },
162 ALG_SHA384 => {
163 let verifying_key = VerifyingKey::<Sha384>::new_with_salt_len(
164 public_key.clone(),
165 normalized_algorithm.salt_length as usize,
166 );
167 verifying_key.verify(message, &signature)
168 },
169 ALG_SHA512 => {
170 let verifying_key = VerifyingKey::<Sha512>::new_with_salt_len(
171 public_key.clone(),
172 normalized_algorithm.salt_length as usize,
173 );
174 verifying_key.verify(message, &signature)
175 },
176 _ => {
177 return Err(Error::Operation(Some(format!(
178 "Unsupported \"{}\" hash for RSASSA-PKCS1-v1_5",
179 algorithm.hash.name()
180 ))));
181 },
182 }
183 .is_ok();
184
185 // Step 4. Return result.
186 Ok(result)
187}
188
189/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-generate-key>
190pub(crate) fn generate_key(
191 global: &GlobalScope,
192 normalized_algorithm: &SubtleRsaHashedKeyGenParams,
193 extractable: bool,
194 usages: Vec<KeyUsage>,
195 can_gc: CanGc,
196) -> Result<CryptoKeyPair, Error> {
197 rsa_common::generate_key(
198 RsaAlgorithm::RsaPss,
199 global,
200 normalized_algorithm,
201 extractable,
202 usages,
203 can_gc,
204 )
205}
206
207/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-import-key>
208pub(crate) fn import_key(
209 global: &GlobalScope,
210 normalized_algorithm: &SubtleRsaHashedImportParams,
211 format: KeyFormat,
212 key_data: &[u8],
213 extractable: bool,
214 usages: Vec<KeyUsage>,
215 can_gc: CanGc,
216) -> Result<DomRoot<CryptoKey>, Error> {
217 // Step 1. Let keyData be the key data to be imported.
218
219 // Step 2.
220 let (key_handle, key_type) = match format {
221 // If format is "spki":
222 KeyFormat::Spki => {
223 // Step 2.1. If usages contains an entry which is not "verify" then throw a
224 // SyntaxError.
225 if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
226 return Err(Error::Syntax(Some(
227 "Usages contains an entry which is not \"verify\"".to_string(),
228 )));
229 }
230
231 // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
232 // algorithm over keyData.
233 // Step 2.3. If an error occurred while parsing, then throw a DataError.
234 let spki =
235 SubjectPublicKeyInfo::<AnyRef, BitString>::from_der(key_data).map_err(|_| {
236 Error::Data(Some(
237 "Fail to parse SubjectPublicKeyInfo over keyData".to_string(),
238 ))
239 })?;
240
241 // Step 2.4. If the algorithm object identifier field of the algorithm
242 // AlgorithmIdentifier field of spki is not equal to the rsaEncryption object
243 // identifier defined in [RFC3447], then throw a DataError.
244 if spki.algorithm.oid != pkcs1::ALGORITHM_OID {
245 return Err(Error::Data(Some(
246 "Algorithm object identifier of spki is not an rsaEncryption".to_string(),
247 )));
248 }
249
250 // Step 2.5. Let publicKey be the result of performing the parse an ASN.1 structure
251 // algorithm, with data as the subjectPublicKeyInfo field of spki, structure as the
252 // RSAPublicKey structure specified in Section A.1.1 of [RFC3447], and exactData set to
253 // true.
254 // Step 2.6. If an error occurred while parsing, or it can be determined that publicKey
255 // is not a valid public key according to [RFC3447], then throw a DataError.
256 let pkcs1_bytes = spki.subject_public_key.as_bytes().ok_or(Error::Data(Some(
257 "Fail to parse byte sequence over SubjectPublicKey field of spki".to_string(),
258 )))?;
259 let rsa_public_key_structure =
260 pkcs1::RsaPublicKey::try_from(pkcs1_bytes).map_err(|_| {
261 Error::Data(Some(
262 "SubjectPublicKey field of spki is not an RSAPublicKey structure"
263 .to_string(),
264 ))
265 })?;
266 let n = BigUint::from_bytes_be(rsa_public_key_structure.modulus.as_bytes());
267 let e = BigUint::from_bytes_be(rsa_public_key_structure.public_exponent.as_bytes());
268 let public_key = RsaPublicKey::new(n, e).map_err(|_| {
269 Error::Data(Some(
270 "Fail to construct RSA public key from modulus and public exponent".to_string(),
271 ))
272 })?;
273
274 // Step 2.7. Let key be a new CryptoKey that represents the RSA public key identified
275 // by publicKey.
276 // Step 2.8. Set the [[type]] internal slot of key to "public"
277 // NOTE: Done in Step 3-8.
278 let key_handle = Handle::RsaPublicKey(public_key);
279 let key_type = KeyType::Public;
280 (key_handle, key_type)
281 },
282 // If format is "pkcs8":
283 KeyFormat::Pkcs8 => {
284 // Step 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError.
285 if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
286 return Err(Error::Syntax(Some(
287 "Usages contains an entry which is not \"sign\"".to_string(),
288 )));
289 }
290
291 // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
292 // algorithm over keyData.
293 // Step 2.3. If an error occurred while parsing, then throw a DataError.
294 let private_key_info = PrivateKeyInfo::from_der(key_data).map_err(|_| {
295 Error::Data(Some(
296 "Fail to parse PrivateKeyInfo over keyData".to_string(),
297 ))
298 })?;
299
300 // Step 2.4. If the algorithm object identifier field of the privateKeyAlgorithm
301 // PrivateKeyAlgorithm field of privateKeyInfo is not equal to the rsaEncryption object
302 // identifier defined in [RFC3447], then throw a DataError.
303 if private_key_info.algorithm.oid != pkcs1::ALGORITHM_OID {
304 return Err(Error::Data(Some(
305 "Algorithm object identifier of PrivateKeyInfo is not an rsaEncryption"
306 .to_string(),
307 )));
308 }
309
310 // Step 2.5. Let rsaPrivateKey be the result of performing the parse an ASN.1 structure
311 // algorithm, with data as the privateKey field of privateKeyInfo, structure as the
312 // RSAPrivateKey structure specified in Section A.1.2 of [RFC3447], and exactData set
313 // to true.
314 // Step 2.6. If an error occurred while parsing, or if rsaPrivateKey is not a valid RSA
315 // private key according to [RFC3447], then throw a DataError.
316 let rsa_private_key = RsaPrivateKey::from_pkcs1_der(private_key_info.private_key)
317 .map_err(|_| {
318 Error::Data(Some(
319 "PrivateKey field of PrivateKeyInfo is not an RSAPrivateKey structure"
320 .to_string(),
321 ))
322 })?;
323
324 // Step 2.7. Let key be a new CryptoKey that represents the RSA private key identified
325 // by rsaPrivateKey.
326 // Step 2.8. Set the [[type]] internal slot of key to "private"
327 // NOTE: Done in Step 3-8.
328 let key_handle = Handle::RsaPrivateKey(rsa_private_key);
329 let key_type = KeyType::Private;
330 (key_handle, key_type)
331 },
332 // If format is "jwk":
333 KeyFormat::Jwk => {
334 // Step 2.1.
335 // If keyData is a JsonWebKey dictionary:
336 // Let jwk equal keyData.
337 // Otherwise:
338 // Throw a DataError.
339 let cx = GlobalScope::get_cx();
340 let jwk = JsonWebKey::parse(cx, key_data)?;
341
342 // Step 2.2. If the d field of jwk is present and usages contains an entry which is not
343 // "sign", or, if the d field of jwk is not present and usages contains an entry which
344 // is not "verify" then throw a SyntaxError.
345 if jwk.d.is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign) {
346 return Err(Error::Syntax(Some(
347 "The d field of jwk is present and usages contains an entry which is not \"sign\""
348 .to_string()
349 )));
350 }
351 if jwk.d.is_none() && usages.iter().any(|usage| *usage != KeyUsage::Verify) {
352 return Err(Error::Syntax(Some(
353 "The d field of jwk is not present and usages contains an entry which is not \"verify\""
354 .to_string()
355 )));
356 }
357
358 // Step 2.3. If the kty field of jwk is not a case-sensitive string match to "RSA",
359 // then throw a DataError.
360 if jwk.kty.as_ref().is_none_or(|kty| kty != "RSA") {
361 return Err(Error::Data(Some(
362 "The kty field of jwk is not a case-sensitive string match to \"RSA\""
363 .to_string(),
364 )));
365 }
366
367 // Step 2.4. If usages is non-empty and the use field of jwk is present and is not a
368 // case-sensitive string match to "sig", then throw a DataError.
369 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
370 return Err(Error::Data(Some(
371 "Usages is non-empty and the use field of jwk is present and \
372 is not a case-sensitive string match to \"sig\""
373 .to_string(),
374 )));
375 }
376
377 // Step 2.5. If the key_ops field of jwk is present, and is invalid according to the
378 // requirements of JSON Web Key [JWK] or does not contain all of the specified usages
379 // values, then throw a DataError.
380 jwk.check_key_ops(&usages)?;
381
382 // Step 2.6. If the ext field of jwk is present and has the value false and extractable
383 // is true, then throw a DataError.
384 if jwk.ext.is_some_and(|ext| !ext) && extractable {
385 return Err(Error::Data(Some(
386 "The ext field of jwk is present and \
387 has the value false and extractable is true"
388 .to_string(),
389 )));
390 }
391
392 // Step 2.7.
393 // If the alg field of jwk is not present:
394 // Let hash be undefined.
395 // If the alg field is equal to the string "PS1":
396 // Let hash be the string "SHA-1".
397 // If the alg field is equal to the string "PS256":
398 // Let hash be the string "SHA-256".
399 // If the alg field is equal to the string "PS384":
400 // Let hash be the string "SHA-384".
401 // If the alg field is equal to the string "PS512":
402 // Let hash be the string "SHA-512".
403 // Otherwise:
404 // Perform any key import steps defined by other applicable specifications, passing
405 // format, jwk and obtaining hash.
406 // If an error occurred or there are no applicable specifications, throw a DataError.
407 let hash = match &jwk.alg {
408 None => None,
409 Some(alg) => match &*alg.str() {
410 "PS1" => Some(ALG_SHA1),
411 "PS256" => Some(ALG_SHA256),
412 "PS384" => Some(ALG_SHA384),
413 "PS512" => Some(ALG_SHA512),
414 _ => None,
415 },
416 };
417
418 // Step 2.8. If hash is not undefined:
419 if let Some(hash) = hash {
420 // Step 2.8.1. Let normalizedHash be the result of normalize an algorithm with alg
421 // set to hash and op set to digest.
422 let normalized_hash = normalize_algorithm(
423 cx,
424 Operation::Digest,
425 &AlgorithmIdentifier::String(DOMString::from(hash)),
426 can_gc,
427 )?;
428
429 // Step 2.8.2. If normalizedHash is not equal to the hash member of
430 // normalizedAlgorithm, throw a DataError.
431 if normalized_hash.name() != normalized_algorithm.hash.name() {
432 return Err(Error::Data(Some(
433 "The normalizedHash is not equal to the hash member of normalizedAlgorithm"
434 .to_string(),
435 )));
436 }
437 }
438
439 // Step 2.9.
440 // If the d field of jwk is present:
441 if jwk.d.is_some() {
442 // Step 2.9.1. If jwk does not meet the requirements of Section 6.3.2 of JSON Web
443 // Algorithms [JWA], then throw a DataError.
444 let n = jwk.decode_required_string_field(JwkStringField::N)?;
445 let e = jwk.decode_required_string_field(JwkStringField::E)?;
446 let d = jwk.decode_required_string_field(JwkStringField::D)?;
447 let p = jwk.decode_optional_string_field(JwkStringField::P)?;
448 let q = jwk.decode_optional_string_field(JwkStringField::Q)?;
449 let dp = jwk.decode_optional_string_field(JwkStringField::DP)?;
450 let dq = jwk.decode_optional_string_field(JwkStringField::DQ)?;
451 let qi = jwk.decode_optional_string_field(JwkStringField::QI)?;
452 let mut primes = match (p, q, dp, dq, qi) {
453 (Some(p), Some(q), Some(_dp), Some(_dq), Some(_qi)) => vec![p, q],
454 (None, None, None, None, None) => Vec::new(),
455 _ => return Err(Error::Data(Some(
456 "The p, q, dp, dq, qi fields of jwk must be either all-present or all-absent"
457 .to_string()
458 ))),
459 };
460 jwk.decode_primes_from_oth_field(&mut primes)?;
461
462 // Step 2.9.2. Let privateKey represents the RSA private key identified by
463 // interpreting jwk according to Section 6.3.2 of JSON Web Algorithms [JWA].
464 // Step 2.9.3. If privateKey is not a valid RSA private key according to [RFC3447],
465 // then throw a DataError.
466 let private_key = RsaPrivateKey::from_components(
467 BigUint::from_bytes_be(&n),
468 BigUint::from_bytes_be(&e),
469 BigUint::from_bytes_be(&d),
470 primes
471 .into_iter()
472 .map(|prime| BigUint::from_bytes_be(&prime))
473 .collect(),
474 )
475 .map_err(|_| {
476 Error::Data(Some(
477 "Failed to construct RSA private key from values in jwk".to_string(),
478 ))
479 })?;
480
481 // Step 2.9.4. Let key be a new CryptoKey object that represents privateKey.
482 // Step 2.9.5. Set the [[type]] internal slot of key to "private"
483 // NOTE: Done in Step 3-8.
484 let key_handle = Handle::RsaPrivateKey(private_key);
485 let key_type = KeyType::Private;
486 (key_handle, key_type)
487 }
488 // Otherwise:
489 else {
490 // Step 2.9.1. If jwk does not meet the requirements of Section 6.3.1 of JSON Web
491 // Algorithms [JWA], then throw a DataError.
492 let n = jwk.decode_required_string_field(JwkStringField::N)?;
493 let e = jwk.decode_required_string_field(JwkStringField::E)?;
494
495 // Step 2.9.2. Let publicKey represent the RSA public key identified by
496 // interpreting jwk according to Section 6.3.1 of JSON Web Algorithms [JWA].
497 // Step 2.9.3. If publicKey can be determined to not be a valid RSA public key
498 // according to [RFC3447], then throw a DataError.
499 let public_key =
500 RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e))
501 .map_err(|_| {
502 Error::Data(Some(
503 "Failed to construct RSA public key from values in jwk".to_string(),
504 ))
505 })?;
506
507 // Step 2.9.4. Let key be a new CryptoKey representing publicKey.
508 // Step 2.9.5. Set the [[type]] internal slot of key to "public"
509 // NOTE: Done in Step 3-8.
510 let key_handle = Handle::RsaPublicKey(public_key);
511 let key_type = KeyType::Public;
512 (key_handle, key_type)
513 }
514 },
515 // Otherwise:
516 _ => {
517 // throw a NotSupportedError.
518 return Err(Error::NotSupported(Some(
519 "Unsupported import key format for RSA key".to_string(),
520 )));
521 },
522 };
523
524 // Step 3. Let algorithm be a new RsaHashedKeyAlgorithm dictionary.
525 // Step 4. Set the name attribute of algorithm to "RSA-PSS"
526 // Step 5. Set the modulusLength attribute of algorithm to the length, in bits, of the RSA
527 // public modulus.
528 // Step 6. Set the publicExponent attribute of algorithm to the BigInteger representation of
529 // the RSA public exponent.
530 // Step 7. Set the hash attribute of algorithm to the hash member of normalizedAlgorithm.
531 // Step 8. Set the [[algorithm]] internal slot of key to algorithm
532 let (modulus_length, public_exponent) = match &key_handle {
533 Handle::RsaPrivateKey(private_key) => {
534 (private_key.size() as u32 * 8, private_key.e().to_bytes_be())
535 },
536 Handle::RsaPublicKey(public_key) => {
537 (public_key.size() as u32 * 8, public_key.e().to_bytes_be())
538 },
539 _ => unreachable!(),
540 };
541 let algorithm = SubtleRsaHashedKeyAlgorithm {
542 name: ALG_RSA_PSS.to_string(),
543 modulus_length,
544 public_exponent,
545 hash: normalized_algorithm.hash.clone(),
546 };
547 let key = CryptoKey::new(
548 global,
549 key_type,
550 extractable,
551 KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm),
552 usages,
553 key_handle,
554 can_gc,
555 );
556
557 // Step 9. Return key.
558 Ok(key)
559}
560
561/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-export-key>
562pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
563 rsa_common::export_key(RsaAlgorithm::RsaPss, format, key)
564}