Skip to main content

jwt_compact/alg/
rsa.rs

1//! RSA-based JWT algorithms: `RS*` and `PS*`.
2
3pub use rsa::{errors::Error as RsaError, RsaPrivateKey, RsaPublicKey};
4
5use rand_core::{CryptoRng, RngCore};
6use rsa::{
7    traits::{PrivateKeyParts, PublicKeyParts},
8    BigUint, Pkcs1v15Sign, Pss,
9};
10use sha2::{Digest, Sha256, Sha384, Sha512};
11
12use core::{fmt, str::FromStr};
13
14use crate::{
15    alg::{SecretBytes, StrongKey, WeakKeyError},
16    alloc::{Box, Cow, String, ToOwned, Vec},
17    jwk::{JsonWebKey, JwkError, KeyType, RsaPrimeFactor, RsaPrivateParts},
18    Algorithm, AlgorithmSignature,
19};
20
21/// RSA signature.
22#[derive(Debug)]
23#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
24pub struct RsaSignature(Vec<u8>);
25
26impl AlgorithmSignature for RsaSignature {
27    fn try_from_slice(bytes: &[u8]) -> anyhow::Result<Self> {
28        Ok(RsaSignature(bytes.to_vec()))
29    }
30
31    fn as_bytes(&self) -> Cow<'_, [u8]> {
32        Cow::Borrowed(&self.0)
33    }
34}
35
36/// RSA hash algorithm.
37#[derive(Debug, Copy, Clone, Eq, PartialEq)]
38enum HashAlg {
39    Sha256,
40    Sha384,
41    Sha512,
42}
43
44impl HashAlg {
45    fn digest(self, message: &[u8]) -> Box<[u8]> {
46        match self {
47            Self::Sha256 => {
48                let digest: [u8; 32] = *(Sha256::digest(message).as_ref());
49                Box::new(digest)
50            }
51            Self::Sha384 => {
52                let mut digest = [0_u8; 48];
53                digest.copy_from_slice(Sha384::digest(message).as_ref());
54                Box::new(digest)
55            }
56            Self::Sha512 => {
57                let mut digest = [0_u8; 64];
58                digest.copy_from_slice(Sha512::digest(message).as_ref());
59                Box::new(digest)
60            }
61        }
62    }
63}
64
65/// RSA padding algorithm.
66#[derive(Debug, Copy, Clone, Eq, PartialEq)]
67enum Padding {
68    Pkcs1v15,
69    Pss,
70}
71
72#[derive(Debug)]
73enum PaddingScheme {
74    Pkcs1v15(Pkcs1v15Sign),
75    Pss(Pss),
76}
77
78/// Bit length of an RSA key modulus (aka RSA key length).
79#[derive(Debug, Copy, Clone, Eq, PartialEq)]
80#[non_exhaustive]
81#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
82pub enum ModulusBits {
83    /// 2048 bits. This is the minimum recommended key length as of 2020.
84    TwoKibibytes,
85    /// 3072 bits.
86    ThreeKibibytes,
87    /// 4096 bits.
88    FourKibibytes,
89}
90
91impl ModulusBits {
92    /// Converts this length to the numeric value.
93    pub fn bits(self) -> usize {
94        match self {
95            Self::TwoKibibytes => 2_048,
96            Self::ThreeKibibytes => 3_072,
97            Self::FourKibibytes => 4_096,
98        }
99    }
100
101    fn is_valid_bits(bits: usize) -> bool {
102        matches!(bits, 2_048 | 3_072 | 4_096)
103    }
104}
105
106impl TryFrom<usize> for ModulusBits {
107    type Error = ModulusBitsError;
108
109    fn try_from(value: usize) -> Result<Self, Self::Error> {
110        match value {
111            2_048 => Ok(Self::TwoKibibytes),
112            3_072 => Ok(Self::ThreeKibibytes),
113            4_096 => Ok(Self::FourKibibytes),
114            _ => Err(ModulusBitsError(())),
115        }
116    }
117}
118
119/// Error type returned when a conversion of an integer into `ModulusBits` fails.
120#[derive(Debug)]
121#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
122pub struct ModulusBitsError(());
123
124impl fmt::Display for ModulusBitsError {
125    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126        formatter.write_str(
127            "Unsupported bit length of RSA modulus; only lengths 2048, 3072 and 4096 \
128            are supported.",
129        )
130    }
131}
132
133#[cfg(feature = "std")]
134impl std::error::Error for ModulusBitsError {}
135
136/// Integrity algorithm using [RSA] digital signatures.
137///
138/// Depending on the variation, the algorithm employs PKCS#1 v1.5 or PSS padding and
139/// one of the hash functions from the SHA-2 family: SHA-256, SHA-384, or SHA-512.
140/// See [RFC 7518] for more details. Depending on the chosen parameters,
141/// the name of the algorithm is one of `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`:
142///
143/// - `R` / `P` denote the padding scheme: PKCS#1 v1.5 for `R`, PSS for `P`
144/// - `256` / `384` / `512` denote the hash function
145///
146/// The length of RSA keys is not unequivocally specified by the algorithm; nevertheless,
147/// it **MUST** be at least 2048 bits as per RFC 7518. To minimize risks of misconfiguration,
148/// use [`StrongAlg`](super::StrongAlg) wrapper around `Rsa`:
149///
150/// ```
151/// # use jwt_compact::alg::{StrongAlg, Rsa};
152/// const ALG: StrongAlg<Rsa> = StrongAlg(Rsa::rs256());
153/// // `ALG` will not support RSA keys with unsecure lengths by design!
154/// ```
155///
156/// [RSA]: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
157/// [RFC 7518]: https://www.rfc-editor.org/rfc/rfc7518.html
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
160pub struct Rsa {
161    hash_alg: HashAlg,
162    padding_alg: Padding,
163}
164
165impl Algorithm for Rsa {
166    type SigningKey = RsaPrivateKey;
167    type VerifyingKey = RsaPublicKey;
168    type Signature = RsaSignature;
169
170    fn name(&self) -> Cow<'static, str> {
171        Cow::Borrowed(self.alg_name())
172    }
173
174    fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
175        let digest = self.hash_alg.digest(message);
176        let signing_result = match self.padding_scheme() {
177            PaddingScheme::Pkcs1v15(padding) => {
178                signing_key.sign_with_rng(&mut rand_core::OsRng, padding, &digest)
179            }
180            PaddingScheme::Pss(padding) => {
181                signing_key.sign_with_rng(&mut rand_core::OsRng, padding, &digest)
182            }
183        };
184        RsaSignature(signing_result.expect("Unexpected RSA signature failure"))
185    }
186
187    fn verify_signature(
188        &self,
189        signature: &Self::Signature,
190        verifying_key: &Self::VerifyingKey,
191        message: &[u8],
192    ) -> bool {
193        let digest = self.hash_alg.digest(message);
194        let verify_result = match self.padding_scheme() {
195            PaddingScheme::Pkcs1v15(padding) => {
196                verifying_key.verify(padding, &digest, &signature.0)
197            }
198            PaddingScheme::Pss(padding) => verifying_key.verify(padding, &digest, &signature.0),
199        };
200        verify_result.is_ok()
201    }
202}
203
204impl Rsa {
205    const fn new(hash_alg: HashAlg, padding_alg: Padding) -> Self {
206        Rsa {
207            hash_alg,
208            padding_alg,
209        }
210    }
211
212    /// RSA with SHA-256 and PKCS#1 v1.5 padding.
213    pub const fn rs256() -> Rsa {
214        Rsa::new(HashAlg::Sha256, Padding::Pkcs1v15)
215    }
216
217    /// RSA with SHA-384 and PKCS#1 v1.5 padding.
218    pub const fn rs384() -> Rsa {
219        Rsa::new(HashAlg::Sha384, Padding::Pkcs1v15)
220    }
221
222    /// RSA with SHA-512 and PKCS#1 v1.5 padding.
223    pub const fn rs512() -> Rsa {
224        Rsa::new(HashAlg::Sha512, Padding::Pkcs1v15)
225    }
226
227    /// RSA with SHA-256 and PSS padding.
228    pub const fn ps256() -> Rsa {
229        Rsa::new(HashAlg::Sha256, Padding::Pss)
230    }
231
232    /// RSA with SHA-384 and PSS padding.
233    pub const fn ps384() -> Rsa {
234        Rsa::new(HashAlg::Sha384, Padding::Pss)
235    }
236
237    /// RSA with SHA-512 and PSS padding.
238    pub const fn ps512() -> Rsa {
239        Rsa::new(HashAlg::Sha512, Padding::Pss)
240    }
241
242    /// RSA based on the specified algorithm name.
243    ///
244    /// # Panics
245    ///
246    /// - Panics if the name is not one of the six RSA-based JWS algorithms. Prefer using
247    ///   the [`FromStr`] trait if the conversion is potentially fallible.
248    pub fn with_name(name: &str) -> Self {
249        name.parse().unwrap()
250    }
251
252    fn padding_scheme(self) -> PaddingScheme {
253        match self.padding_alg {
254            Padding::Pkcs1v15 => PaddingScheme::Pkcs1v15(match self.hash_alg {
255                HashAlg::Sha256 => Pkcs1v15Sign::new::<Sha256>(),
256                HashAlg::Sha384 => Pkcs1v15Sign::new::<Sha384>(),
257                HashAlg::Sha512 => Pkcs1v15Sign::new::<Sha512>(),
258            }),
259            Padding::Pss => {
260                // The salt length needs to be set to the size of hash function output;
261                // see https://www.rfc-editor.org/rfc/rfc7518.html#section-3.5.
262                PaddingScheme::Pss(match self.hash_alg {
263                    HashAlg::Sha256 => Pss::new_with_salt::<Sha256>(Sha256::output_size()),
264                    HashAlg::Sha384 => Pss::new_with_salt::<Sha384>(Sha384::output_size()),
265                    HashAlg::Sha512 => Pss::new_with_salt::<Sha512>(Sha512::output_size()),
266                })
267            }
268        }
269    }
270
271    fn alg_name(self) -> &'static str {
272        match (self.padding_alg, self.hash_alg) {
273            (Padding::Pkcs1v15, HashAlg::Sha256) => "RS256",
274            (Padding::Pkcs1v15, HashAlg::Sha384) => "RS384",
275            (Padding::Pkcs1v15, HashAlg::Sha512) => "RS512",
276            (Padding::Pss, HashAlg::Sha256) => "PS256",
277            (Padding::Pss, HashAlg::Sha384) => "PS384",
278            (Padding::Pss, HashAlg::Sha512) => "PS512",
279        }
280    }
281
282    /// Generates a new key pair with the specified modulus bit length (aka key length).
283    pub fn generate<R: CryptoRng + RngCore>(
284        rng: &mut R,
285        modulus_bits: ModulusBits,
286    ) -> rsa::errors::Result<(StrongKey<RsaPrivateKey>, StrongKey<RsaPublicKey>)> {
287        let signing_key = RsaPrivateKey::new(rng, modulus_bits.bits())?;
288        let verifying_key = signing_key.to_public_key();
289        Ok((StrongKey(signing_key), StrongKey(verifying_key)))
290    }
291}
292
293impl FromStr for Rsa {
294    type Err = RsaParseError;
295
296    fn from_str(s: &str) -> Result<Self, Self::Err> {
297        Ok(match s {
298            "RS256" => Self::rs256(),
299            "RS384" => Self::rs384(),
300            "RS512" => Self::rs512(),
301            "PS256" => Self::ps256(),
302            "PS384" => Self::ps384(),
303            "PS512" => Self::ps512(),
304            _ => return Err(RsaParseError(s.to_owned())),
305        })
306    }
307}
308
309/// Errors that can occur when parsing an [`Rsa`] algorithm from a string.
310#[derive(Debug)]
311#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
312pub struct RsaParseError(String);
313
314impl fmt::Display for RsaParseError {
315    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(formatter, "Invalid RSA algorithm name: {}", self.0)
317    }
318}
319
320#[cfg(feature = "std")]
321impl std::error::Error for RsaParseError {}
322
323impl StrongKey<RsaPrivateKey> {
324    /// Converts this private key to a public key.
325    pub fn to_public_key(&self) -> StrongKey<RsaPublicKey> {
326        StrongKey(self.0.to_public_key())
327    }
328}
329
330impl TryFrom<RsaPrivateKey> for StrongKey<RsaPrivateKey> {
331    type Error = WeakKeyError<RsaPrivateKey>;
332
333    fn try_from(key: RsaPrivateKey) -> Result<Self, Self::Error> {
334        if ModulusBits::is_valid_bits(key.n().bits()) {
335            Ok(StrongKey(key))
336        } else {
337            Err(WeakKeyError(key))
338        }
339    }
340}
341
342impl TryFrom<RsaPublicKey> for StrongKey<RsaPublicKey> {
343    type Error = WeakKeyError<RsaPublicKey>;
344
345    fn try_from(key: RsaPublicKey) -> Result<Self, Self::Error> {
346        if ModulusBits::is_valid_bits(key.n().bits()) {
347            Ok(StrongKey(key))
348        } else {
349            Err(WeakKeyError(key))
350        }
351    }
352}
353
354impl<'a> From<&'a RsaPublicKey> for JsonWebKey<'a> {
355    fn from(key: &'a RsaPublicKey) -> JsonWebKey<'a> {
356        JsonWebKey::Rsa {
357            modulus: Cow::Owned(key.n().to_bytes_be()),
358            public_exponent: Cow::Owned(key.e().to_bytes_be()),
359            private_parts: None,
360        }
361    }
362}
363
364impl TryFrom<&JsonWebKey<'_>> for RsaPublicKey {
365    type Error = JwkError;
366
367    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
368        let JsonWebKey::Rsa {
369            modulus,
370            public_exponent,
371            ..
372        } = jwk
373        else {
374            return Err(JwkError::key_type(jwk, KeyType::Rsa));
375        };
376
377        let e = BigUint::from_bytes_be(public_exponent);
378        let n = BigUint::from_bytes_be(modulus);
379        Self::new(n, e).map_err(|err| JwkError::custom(anyhow::anyhow!(err)))
380    }
381}
382
383/// ⚠ **Warning.** Contrary to [RFC 7518], this implementation does not set `dp`, `dq`, and `qi`
384/// fields in the JWK root object, as well as `d` and `t` fields for additional factors
385/// (i.e., in the `oth` array).
386///
387/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-6.3.2
388impl<'a> From<&'a RsaPrivateKey> for JsonWebKey<'a> {
389    fn from(key: &'a RsaPrivateKey) -> JsonWebKey<'a> {
390        const MSG: &str = "RsaPrivateKey must have at least 2 prime factors";
391
392        let p = key.primes().get(0).expect(MSG);
393        let q = key.primes().get(1).expect(MSG);
394
395        let private_parts = RsaPrivateParts {
396            private_exponent: SecretBytes::owned(key.d().to_bytes_be()),
397            prime_factor_p: SecretBytes::owned(p.to_bytes_be()),
398            prime_factor_q: SecretBytes::owned(q.to_bytes_be()),
399            p_crt_exponent: None,
400            q_crt_exponent: None,
401            q_crt_coefficient: None,
402            other_prime_factors: key.primes()[2..]
403                .iter()
404                .map(|factor| RsaPrimeFactor {
405                    factor: SecretBytes::owned(factor.to_bytes_be()),
406                    crt_exponent: None,
407                    crt_coefficient: None,
408                })
409                .collect(),
410        };
411
412        JsonWebKey::Rsa {
413            modulus: Cow::Owned(key.n().to_bytes_be()),
414            public_exponent: Cow::Owned(key.e().to_bytes_be()),
415            private_parts: Some(private_parts),
416        }
417    }
418}
419
420/// ⚠ **Warning.** Contrary to [RFC 7518] (at least, in spirit), this conversion ignores
421/// `dp`, `dq`, and `qi` fields from JWK, as well as `d` and `t` fields for additional factors.
422///
423/// [RFC 7518]: https://www.rfc-editor.org/rfc/rfc7518.html
424impl TryFrom<&JsonWebKey<'_>> for RsaPrivateKey {
425    type Error = JwkError;
426
427    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
428        let JsonWebKey::Rsa {
429            modulus,
430            public_exponent,
431            private_parts,
432        } = jwk
433        else {
434            return Err(JwkError::key_type(jwk, KeyType::Rsa));
435        };
436
437        let RsaPrivateParts {
438            private_exponent: d,
439            prime_factor_p,
440            prime_factor_q,
441            other_prime_factors,
442            ..
443        } = private_parts
444            .as_ref()
445            .ok_or_else(|| JwkError::NoField("d".into()))?;
446
447        let e = BigUint::from_bytes_be(public_exponent);
448        let n = BigUint::from_bytes_be(modulus);
449        let d = BigUint::from_bytes_be(d);
450
451        let mut factors = Vec::with_capacity(2 + other_prime_factors.len());
452        factors.push(BigUint::from_bytes_be(prime_factor_p));
453        factors.push(BigUint::from_bytes_be(prime_factor_q));
454        factors.extend(
455            other_prime_factors
456                .iter()
457                .map(|prime| BigUint::from_bytes_be(&prime.factor)),
458        );
459
460        let key = Self::from_components(n, e, d, factors);
461        let key = key.map_err(|err| JwkError::custom(anyhow::anyhow!(err)))?;
462        key.validate()
463            .map_err(|err| JwkError::custom(anyhow::anyhow!(err)))?;
464        Ok(key)
465    }
466}