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}