jwt_compact/alg/
rsa.rs

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