script/dom/subtlecrypto/rsa_common.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 std::ops::{Mul, Sub};
6
7use base64ct::{Base64UrlUnpadded, Encoding};
8use num_bigint_dig::{BigInt, ModInverse, Sign};
9use num_traits::One;
10use pkcs8::EncodePrivateKey;
11use pkcs8::rand_core::OsRng;
12use pkcs8::spki::EncodePublicKey;
13use rsa::traits::{PrivateKeyParts, PublicKeyParts};
14use rsa::{BigUint, RsaPrivateKey};
15use sec1::der::Encode;
16
17use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
18 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
19};
20use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{
21 JsonWebKey, KeyFormat, RsaOtherPrimesInfo,
22};
23use crate::dom::bindings::error::Error;
24use crate::dom::bindings::str::DOMString;
25use crate::dom::cryptokey::{CryptoKey, Handle};
26use crate::dom::globalscope::GlobalScope;
27use crate::dom::subtlecrypto::{
28 ALG_RSA_OAEP, ALG_RSA_PSS, ALG_RSASSA_PKCS1_V1_5, ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512,
29 ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives,
30 SubtleRsaHashedKeyAlgorithm, SubtleRsaHashedKeyGenParams,
31};
32use crate::script_runtime::CanGc;
33
34pub(crate) enum RsaAlgorithm {
35 RsaSsaPkcs1v15,
36 RsaPss,
37 RsaOaep,
38}
39
40/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-generate-key>
41/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-generate-key>
42/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-generate-key>
43pub(crate) fn generate_key(
44 rsa_algorithm: RsaAlgorithm,
45 global: &GlobalScope,
46 normalized_algorithm: &SubtleRsaHashedKeyGenParams,
47 extractable: bool,
48 usages: Vec<KeyUsage>,
49 can_gc: CanGc,
50) -> Result<CryptoKeyPair, Error> {
51 match rsa_algorithm {
52 RsaAlgorithm::RsaSsaPkcs1v15 | RsaAlgorithm::RsaPss => {
53 // Step 1. If usages contains an entry which is not "sign" or "verify", then throw a
54 // SyntaxError.
55 if usages
56 .iter()
57 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
58 {
59 return Err(Error::Syntax(Some(
60 "Usages contains an entry which is not \"sign\" or \"verify\"".to_string(),
61 )));
62 }
63 },
64 RsaAlgorithm::RsaOaep => {
65 // Step 1. If usages contains an entry which is not "encrypt", "decrypt", "wrapKey" or
66 // "unwrapKey", then throw a SyntaxError.
67 if usages.iter().any(|usage| {
68 !matches!(
69 usage,
70 KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
71 )
72 }) {
73 return Err(Error::Syntax(Some(
74 "Usages contains an entry which is not \"encrypt\", \"decrypt\", \
75 \"wrapKey\" or \"unwrapKey\""
76 .to_string(),
77 )));
78 }
79 },
80 }
81
82 // Step 2. Generate an RSA key pair, as defined in [RFC3447], with RSA modulus length equal to
83 // the modulusLength attribute of normalizedAlgorithm and RSA public exponent equal to the
84 // publicExponent attribute of normalizedAlgorithm.
85 // Step 3. If generation of the key pair fails, then throw an OperationError.
86 // NOTE: If the public exponent is even, it is invalid for RSA, and RsaPrivateKey::new_with_exp
87 // should throw an error. However, RsaPrivateKey::new_with_exp would take a long period of time
88 // to validate this case. So, we manually check it before running RsaPrivateKey::new_with_exp,
89 // in order to throw error eariler.
90 if normalized_algorithm
91 .public_exponent
92 .last()
93 .is_none_or(|last_byte| last_byte % 2 == 0)
94 {
95 return Err(Error::Operation(Some(
96 "The public expoenent is an even number".to_string(),
97 )));
98 }
99 let mut rng = OsRng;
100 let modulus_length = normalized_algorithm.modulus_length as usize;
101 let public_exponent = BigUint::from_bytes_be(&normalized_algorithm.public_exponent);
102 let private_key = RsaPrivateKey::new_with_exp(&mut rng, modulus_length, &public_exponent)
103 .map_err(|_| Error::Operation(Some("RSA failed to generate private key".to_string())))?;
104 let public_key = private_key.to_public_key();
105
106 // Step 4. Let algorithm be a new RsaHashedKeyAlgorithm dictionary.
107 // Step 6. Set the modulusLength attribute of algorithm to equal the modulusLength attribute of
108 // normalizedAlgorithm.
109 // Step 7. Set the publicExponent attribute of algorithm to equal the publicExponent attribute
110 // of normalizedAlgorithm.
111 // Step 8. Set the hash attribute of algorithm to equal the hash member of normalizedAlgorithm.
112 let algorithm = SubtleRsaHashedKeyAlgorithm {
113 name: match rsa_algorithm {
114 // Step 5. Set the name attribute of algorithm to "RSASSA-PKCS1-v1_5".
115 RsaAlgorithm::RsaSsaPkcs1v15 => ALG_RSASSA_PKCS1_V1_5,
116 // Step 5. Set the name attribute of algorithm to "RSA-PSS".
117 RsaAlgorithm::RsaPss => ALG_RSA_PSS,
118 // Step 5. Set the name attribute of algorithm to "RSA-OAEP".
119 RsaAlgorithm::RsaOaep => ALG_RSA_OAEP,
120 }
121 .to_string(),
122 modulus_length: normalized_algorithm.modulus_length,
123 public_exponent: normalized_algorithm.public_exponent.clone(),
124 hash: normalized_algorithm.hash.clone(),
125 };
126
127 // Step 9. Let publicKey be a new CryptoKey representing the public key of the generated key
128 // pair.
129 // Step 10. Set the [[type]] internal slot of publicKey to "public"
130 // Step 11. Set the [[algorithm]] internal slot of publicKey to algorithm.
131 // Step 12. Set the [[extractable]] internal slot of publicKey to true.
132 let intersected_usages = match rsa_algorithm {
133 RsaAlgorithm::RsaSsaPkcs1v15 | RsaAlgorithm::RsaPss => {
134 // Step 13. Set the [[usages]] internal slot of publicKey to be the usage intersection
135 // of usages and [ "verify" ].
136 usages
137 .iter()
138 .filter(|usage| **usage == KeyUsage::Verify)
139 .cloned()
140 .collect()
141 },
142 RsaAlgorithm::RsaOaep => {
143 // Step 13. Set the [[usages]] internal slot of publicKey to be the usage intersection
144 // of usages and [ "encrypt", "wrapKey" ].
145 usages
146 .iter()
147 .filter(|usage| matches!(usage, KeyUsage::Encrypt | KeyUsage::WrapKey))
148 .cloned()
149 .collect()
150 },
151 };
152 let public_key = CryptoKey::new(
153 global,
154 KeyType::Public,
155 true,
156 KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm.clone()),
157 intersected_usages,
158 Handle::RsaPublicKey(public_key),
159 can_gc,
160 );
161
162 // Step 14. Let privateKey be a new CryptoKey representing the private key of the generated key
163 // pair.
164 // Step 15. Set the [[type]] internal slot of privateKey to "private"
165 // Step 16. Set the [[algorithm]] internal slot of privateKey to algorithm.
166 // Step 17. Set the [[extractable]] internal slot of privateKey to extractable.
167 let intersected_usages = match rsa_algorithm {
168 RsaAlgorithm::RsaSsaPkcs1v15 | RsaAlgorithm::RsaPss => {
169 // Step 18. Set the [[usages]] internal slot of privateKey to be the usage intersection
170 // of usages and [ "sign" ].
171 usages
172 .iter()
173 .filter(|usage| **usage == KeyUsage::Sign)
174 .cloned()
175 .collect()
176 },
177 RsaAlgorithm::RsaOaep => {
178 // Step 18. Set the [[usages]] internal slot of privateKey to be the usage intersection
179 // of usages and [ "decrypt", "unwrapKey" ].
180 usages
181 .iter()
182 .filter(|usage| matches!(usage, KeyUsage::Decrypt | KeyUsage::UnwrapKey))
183 .cloned()
184 .collect()
185 },
186 };
187 let private_key = CryptoKey::new(
188 global,
189 KeyType::Private,
190 extractable,
191 KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm),
192 intersected_usages,
193 Handle::RsaPrivateKey(private_key),
194 can_gc,
195 );
196
197 // Step 19. Let result be a new CryptoKeyPair dictionary.
198 // Step 20. Set the publicKey attribute of result to be publicKey.
199 // Step 21. Set the privateKey attribute of result to be privateKey.
200 let result = CryptoKeyPair {
201 publicKey: Some(public_key),
202 privateKey: Some(private_key),
203 };
204
205 // Step 22. Return result.
206 Ok(result)
207}
208
209/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-export-key>
210/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-export-key>
211/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-export-key>
212pub(crate) fn export_key(
213 rsa_algorithm: RsaAlgorithm,
214 format: KeyFormat,
215 key: &CryptoKey,
216) -> Result<ExportedKey, Error> {
217 // Step 1. Let key be the key to be exported.
218
219 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
220 // slot of key cannot be accessed, then throw an OperationError.
221 // NOTE: Done in Step 3.
222
223 // Step 3.
224 let result = match format {
225 // If format is "spki"
226 KeyFormat::Spki => {
227 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
228 // InvalidAccessError.
229 if key.Type() != KeyType::Public {
230 return Err(Error::InvalidAccess(Some(
231 "The [[type]] internal slot of key is not \"public\"".to_string(),
232 )));
233 }
234
235 // Step 3.2.
236 // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
237 // [RFC5280] with the following properties:
238 //
239 // Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
240 // properties:
241 //
242 // Set the algorithm field to the OID rsaEncryption defined in [RFC3447].
243 //
244 // Set the params field to the ASN.1 type NULL.
245 //
246 // Set the subjectPublicKey field to the result of DER-encoding an RSAPublicKey
247 // ASN.1 type, as defined in [RFC3447], Appendix A.1.1, that represents the RSA
248 // public key represented by the [[handle]] internal slot of key
249 let Handle::RsaPublicKey(public_key) = key.handle() else {
250 return Err(Error::Operation(Some(
251 "The [[handle]] internal slot of key is not an RSA public key".to_string(),
252 )));
253 };
254 let data = public_key.to_public_key_der().map_err(|_| {
255 Error::Operation(Some(
256 "Failed to convert RSA public key to SubjectPublicKeyInfo".to_string(),
257 ))
258 })?;
259
260 // Step 3.3. Let result be the result of DER-encoding data.
261 ExportedKey::Bytes(data.to_der().map_err(|_| {
262 Error::Operation(Some(
263 "Failed to convert SubjectPublicKeyInfo to DER-encodeing data".to_string(),
264 ))
265 })?)
266 },
267 // If format is "pkcs8":
268 KeyFormat::Pkcs8 => {
269 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
270 // InvalidAccessError.
271 if key.Type() != KeyType::Private {
272 return Err(Error::InvalidAccess(Some(
273 "The [[type]] internal slot of key is not \"private\"".to_string(),
274 )));
275 }
276
277 // Step 3.2.
278 // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
279 // with the following properties:
280 //
281 // Set the version field to 0.
282 //
283 // Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
284 // with the following properties:
285 //
286 // Set the algorithm field to the OID rsaEncryption defined in [RFC3447].
287 //
288 // Set the params field to the ASN.1 type NULL.
289 //
290 // Set the privateKey field to the result of DER-encoding an RSAPrivateKey ASN.1
291 // type, as defined in [RFC3447], Appendix A.1.2, that represents the RSA private
292 // key represented by the [[handle]] internal slot of key
293 let Handle::RsaPrivateKey(private_key) = key.handle() else {
294 return Err(Error::Operation(Some(
295 "The [[handle]] internal slot of key is not an RSA private key".to_string(),
296 )));
297 };
298 let data = private_key.to_pkcs8_der().map_err(|_| {
299 Error::Operation(Some(
300 "Failed to convert RSA private key to PrivateKeyInfo".to_string(),
301 ))
302 })?;
303
304 // Step 3.3. Let result be the result of DER-encoding data.
305 ExportedKey::Bytes(data.to_bytes().to_vec())
306 },
307 // If format is "jwk":
308 KeyFormat::Jwk => {
309 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
310 // Step 3.2. Set the kty attribute of jwk to the string "RSA".
311 let mut jwk = JsonWebKey {
312 kty: Some(DOMString::from("RSA")),
313 ..Default::default()
314 };
315
316 // Step 3.3. Let hash be the name attribute of the hash attribute of the [[algorithm]]
317 // internal slot of key.
318 let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm()
319 else {
320 return Err(Error::Operation(Some(
321 "The [[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm"
322 .to_string(),
323 )));
324 };
325 let hash = algorithm.hash.name();
326
327 match rsa_algorithm {
328 RsaAlgorithm::RsaSsaPkcs1v15 => {
329 // Step 3.4.
330 // If hash is "SHA-1":
331 // Set the alg attribute of jwk to the string "RS1".
332 // If hash is "SHA-256":
333 // Set the alg attribute of jwk to the string "RS256".
334 // If hash is "SHA-384":
335 // Set the alg attribute of jwk to the string "RS384".
336 // If hash is "SHA-512":
337 // Set the alg attribute of jwk to the string "RS512".
338 // Otherwise:
339 // Perform any key export steps defined by other applicable specifications,
340 // passing format and the hash attribute of the [[algorithm]] internal slot
341 // of key and obtaining alg.
342 // Set the alg attribute of jwk to alg.
343 let alg = match hash {
344 ALG_SHA1 => "RS1",
345 ALG_SHA256 => "RS256",
346 ALG_SHA384 => "RS384",
347 ALG_SHA512 => "RS512",
348 _ => {
349 return Err(Error::NotSupported(Some(format!(
350 "Unsupported \"{}\" hash for RSASSA-PKCS1-v1_5",
351 hash
352 ))));
353 },
354 };
355 jwk.alg = Some(DOMString::from(alg));
356 },
357 RsaAlgorithm::RsaPss => {
358 // Step 3.4.
359 // If hash is "SHA-1":
360 // Set the alg attribute of jwk to the string "PS1".
361 // If hash is "SHA-256":
362 // Set the alg attribute of jwk to the string "PS256".
363 // If hash is "SHA-384":
364 // Set the alg attribute of jwk to the string "PS384".
365 // If hash is "SHA-512":
366 // Set the alg attribute of jwk to the string "PS512".
367 // Otherwise:
368 // Perform any key export steps defined by other applicable specifications,
369 // passing format and the hash attribute of the [[algorithm]] internal slot
370 // of key and obtaining alg.
371 // Set the alg attribute of jwk to alg.
372 let alg = match hash {
373 ALG_SHA1 => "PS1",
374 ALG_SHA256 => "PS256",
375 ALG_SHA384 => "PS384",
376 ALG_SHA512 => "PS512",
377 _ => {
378 return Err(Error::NotSupported(Some(format!(
379 "Unsupported \"{}\" hash for RSA-PSS",
380 hash
381 ))));
382 },
383 };
384 jwk.alg = Some(DOMString::from(alg));
385 },
386 RsaAlgorithm::RsaOaep => {
387 // Step 3.4.
388 // If hash is "SHA-1":
389 // Set the alg attribute of jwk to the string "RSA-OAEP".
390 // If hash is "SHA-256":
391 // Set the alg attribute of jwk to the string "RSA-OAEP-256".
392 // If hash is "SHA-384":
393 // Set the alg attribute of jwk to the string "RSA-OAEP-384".
394 // If hash is "SHA-512":
395 // Set the alg attribute of jwk to the string "RSA-OAEP-512".
396 // Otherwise:
397 // Perform any key export steps defined by other applicable specifications,
398 // passing format and the hash attribute of the [[algorithm]] internal slot
399 // of key and obtaining alg.
400 // Set the alg attribute of jwk to alg.
401 let alg = match hash {
402 ALG_SHA1 => "RSA-OAEP",
403 ALG_SHA256 => "RSA-OAEP-256",
404 ALG_SHA384 => "RSA-OAEP-384",
405 ALG_SHA512 => "RSA-OAEP-512",
406 _ => {
407 return Err(Error::NotSupported(Some(format!(
408 "Unsupported \"{}\" hash for RSA-OAEP",
409 hash
410 ))));
411 },
412 };
413 jwk.alg = Some(DOMString::from(alg));
414 },
415 }
416
417 // Step 3.5. Set the attributes n and e of jwk according to the corresponding
418 // definitions in JSON Web Algorithms [JWA], Section 6.3.1.
419 let (n, e) = match key.handle() {
420 Handle::RsaPrivateKey(private_key) => (private_key.n(), private_key.e()),
421 Handle::RsaPublicKey(public_key) => (public_key.n(), public_key.e()),
422 _ => {
423 return Err(Error::Operation(Some(
424 "Failed to extract modulus n and public exponent e from RSA key"
425 .to_string(),
426 )));
427 },
428 };
429 jwk.n = Some(Base64UrlUnpadded::encode_string(&n.to_bytes_be()).into());
430 jwk.e = Some(Base64UrlUnpadded::encode_string(&e.to_bytes_be()).into());
431
432 // Step 3.6. If the [[type]] internal slot of key is "private":
433 if key.Type() == KeyType::Private {
434 // Step 3.6.1. Set the attributes named d, p, q, dp, dq, and qi of jwk according to
435 // the corresponding definitions in JSON Web Algorithms [JWA], Section 6.3.2.
436 let Handle::RsaPrivateKey(private_key) = key.handle() else {
437 return Err(Error::Operation(Some(
438 "The [[handle]] internal slot of key is not an RSA private key".to_string(),
439 )));
440 };
441 let mut private_key = private_key.clone();
442 private_key.precompute().map_err(|_| {
443 Error::Operation(Some("Failed to perform RSA pre-computation".to_string()))
444 })?;
445 let primes = private_key.primes();
446 let d = private_key.d();
447 let p = primes.first().ok_or(Error::Operation(Some(
448 "Failed to extract first prime factor p from RSA private key".to_string(),
449 )))?;
450 let q = primes.get(1).ok_or(Error::Operation(Some(
451 "Failed to extract second prime factor q from RSA private key".to_string(),
452 )))?;
453 let dp = private_key.dp().ok_or(Error::Operation(Some(
454 "Failed to extract first factor CRT exponent dp from RSA private key"
455 .to_string(),
456 )))?;
457 let dq = private_key.dq().ok_or(Error::Operation(Some(
458 "Failed to extract second factor CRT exponent dq from RSA private key"
459 .to_string(),
460 )))?;
461 let qi = private_key
462 .qinv()
463 .ok_or(Error::Operation(Some(
464 "Failed to extract first CRT coefficient qi from RSA private key"
465 .to_string(),
466 )))?
467 .modpow(&BigInt::one(), &BigInt::from_biguint(Sign::Plus, p.clone()))
468 .to_biguint()
469 .ok_or(Error::Operation(Some(
470 "Failed to convert first CRT coefficient qi to BigUint".to_string(),
471 )))?;
472 jwk.encode_string_field(JwkStringField::D, &d.to_bytes_be());
473 jwk.encode_string_field(JwkStringField::P, &p.to_bytes_be());
474 jwk.encode_string_field(JwkStringField::Q, &q.to_bytes_be());
475 jwk.encode_string_field(JwkStringField::DP, &dp.to_bytes_be());
476 jwk.encode_string_field(JwkStringField::DQ, &dq.to_bytes_be());
477 jwk.encode_string_field(JwkStringField::QI, &qi.to_bytes_be());
478
479 // Step 3.6.2. If the underlying RSA private key represented by the [[handle]]
480 // internal slot of key is represented by more than two primes, set the attribute
481 // named oth of jwk according to the corresponding definition in JSON Web
482 // Algorithms [JWA], Section 6.3.2.7
483 let mut oth = Vec::new();
484 for (i, p_i) in primes.iter().enumerate().skip(2) {
485 // d_i = d mod (p_i - 1)
486 // t_i = (p_1 * p_2 * ... * p_(i-1)) ^ (-1) mod p_i
487 let d_i = private_key
488 .d()
489 .modpow(&BigUint::one(), &p_i.sub(&BigUint::one()));
490 let t_i = primes
491 .iter()
492 .take(i - 1)
493 .fold(BigUint::one(), |product, p_j| product.mul(p_j))
494 .mod_inverse(p_i)
495 .ok_or(Error::Operation(Some(
496 "Failed to compute factor CRT coefficient of other RSA primes"
497 .to_string(),
498 )))?
499 .modpow(
500 &BigInt::one(),
501 &BigInt::from_biguint(Sign::Plus, p_i.clone()),
502 )
503 .to_biguint()
504 .ok_or(Error::Operation(Some(
505 "Failed to convert factor CRT coefficient of other RSA primes to BigUint"
506 .to_string(),
507 )))?;
508 oth.push(RsaOtherPrimesInfo {
509 r: Some(Base64UrlUnpadded::encode_string(&p_i.to_bytes_be()).into()),
510 d: Some(Base64UrlUnpadded::encode_string(&d_i.to_bytes_be()).into()),
511 t: Some(Base64UrlUnpadded::encode_string(&t_i.to_bytes_be()).into()),
512 });
513 }
514 if !oth.is_empty() {
515 jwk.oth = Some(oth);
516 }
517 }
518
519 // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
520 jwk.set_key_ops(key.usages());
521
522 // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
523 jwk.ext = Some(key.Extractable());
524
525 // Step 3.9. Let result be jwk.
526 ExportedKey::Jwk(Box::new(jwk))
527 },
528 // Otherwise
529 _ => {
530 // throw a NotSupportedError.
531 return Err(Error::NotSupported(Some(
532 "Unsupported export key format for RSA key".to_string(),
533 )));
534 },
535 };
536
537 // Step 4. Return result.
538 Ok(result)
539}