Skip to main content

rws_server/rsa_key/
mod.rs

1//! RSA public keys and RSASSA-PKCS1-v1_5 / SHA-256 signature verification —
2//! everything `rws` does with RSA (RS256 JWTs, SAML `rsa-sha256` signatures),
3//! implemented in-crate.
4//!
5//! This replaces the `rsa` crate, whose RUSTSEC-2023-0071 ("Marvin": a timing
6//! side channel in private-key operations) has no fixed release. `rws` never
7//! holds an RSA private key: verification uses only public data — the key,
8//! the message and the signature — so nothing secret can leak through
9//! timing, and the arithmetic here needn't be constant-time. It is pure Rust
10//! (works on `wasm32` too).
11//!
12//! Verification follows RFC 8017 §8.2.2: the signature must be exactly the
13//! modulus length and smaller than the modulus; `s^e mod n` is computed with
14//! Montgomery multiplication; and the result must equal, byte for byte, the
15//! expected encoding `00 01 FF…FF 00 || DigestInfo(SHA-256) || hash`.
16//! Comparing the whole encoded block (rather than parsing it) rules out the
17//! classic padding-forgery tricks. Keys must be 2048–8192 bits with an odd
18//! public exponent of at least 3.
19
20#[cfg(test)]
21mod tests;
22
23#[cfg(all(test, not(target_arch = "wasm32")))]
24#[doc(hidden)]
25pub mod test_support;
26
27use sha2::{Digest, Sha256};
28
29/// DER `DigestInfo` prefix for SHA-256 (RFC 8017 §9.2, note 1).
30const SHA256_DIGEST_INFO: [u8; 19] = [
31    0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20,
32];
33
34/// `rsaEncryption` OID 1.2.840.113549.1.1.1, DER-encoded content.
35const RSA_ENCRYPTION_OID: [u8; 9] = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
36
37const MIN_BITS: usize = 2048;
38const MAX_BITS: usize = 8192;
39
40/// An RSA public key, ready to verify signatures.
41#[derive(Clone)]
42pub struct RsaPublicKey {
43    /// Modulus, little-endian 32-bit limbs.
44    n: Vec<u32>,
45    /// Modulus length in bytes (the signature length).
46    k: usize,
47    e: u64,
48    /// `-n⁻¹ mod 2³²`, for Montgomery reduction.
49    n0_inv: u32,
50    /// `R² mod n`, `R = 2^(32·limbs)`.
51    r2: Vec<u32>,
52    /// Big-endian modulus as given (for `Debug` and comparisons).
53    n_bytes: Vec<u8>,
54}
55
56impl std::fmt::Debug for RsaPublicKey {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "RsaPublicKey({} bits, e = {})", self.size_bits(), self.e)
59    }
60}
61
62impl PartialEq for RsaPublicKey {
63    fn eq(&self, other: &Self) -> bool {
64        self.n_bytes == other.n_bytes && self.e == other.e
65    }
66}
67
68impl Eq for RsaPublicKey {}
69
70impl RsaPublicKey {
71    /// From the big-endian modulus `n` and public exponent `e` — e.g. a JWK's
72    /// base64url-decoded `n` and `e`.
73    pub fn from_components(n: &[u8], e: &[u8]) -> Result<RsaPublicKey, String> {
74        let n = strip_leading_zeros(n);
75        let e_bytes = strip_leading_zeros(e);
76        if n.is_empty() {
77            return Err("RSA modulus is zero".to_string());
78        }
79        let bits = (n.len() - 1) * 8 + (8 - n[0].leading_zeros() as usize);
80        if !(MIN_BITS..=MAX_BITS).contains(&bits) {
81            return Err(format!("RSA key is {bits} bits; {MIN_BITS}–{MAX_BITS} are accepted"));
82        }
83        if n[n.len() - 1] & 1 == 0 {
84            return Err("RSA modulus is even".to_string());
85        }
86        if e_bytes.is_empty() || e_bytes.len() > 8 {
87            return Err("RSA public exponent out of range".to_string());
88        }
89        let e = e_bytes.iter().fold(0u64, |acc, b| (acc << 8) | *b as u64);
90        if e < 3 || e & 1 == 0 {
91            return Err(format!("RSA public exponent {e} is not an odd number of at least 3"));
92        }
93
94        let limbs = n.len().div_ceil(4);
95        let n_limbs = from_be_bytes(n, limbs);
96        let n0_inv = montgomery_n0_inv(n_limbs[0]);
97        let r2 = r_squared_mod(&n_limbs);
98        Ok(RsaPublicKey { n: n_limbs, k: n.len(), e, n0_inv, r2, n_bytes: n.to_vec() })
99    }
100
101    /// From a PKCS#1 `RSAPublicKey` (`SEQUENCE { n INTEGER, e INTEGER }`), DER.
102    pub fn from_pkcs1_der(der: &[u8]) -> Result<RsaPublicKey, String> {
103        let (seq, rest) = expect_tlv(der, 0x30, "RSAPublicKey")?;
104        if !rest.is_empty() {
105            return Err("trailing bytes after RSAPublicKey".to_string());
106        }
107        let (n, after_n) = expect_tlv(seq, 0x02, "modulus")?;
108        let (e, after_e) = expect_tlv(after_n, 0x02, "public exponent")?;
109        if !after_e.is_empty() {
110            return Err("unexpected fields in RSAPublicKey".to_string());
111        }
112        RsaPublicKey::from_components(positive_integer(n)?, positive_integer(e)?)
113    }
114
115    /// From an X.509 `SubjectPublicKeyInfo` (the `BEGIN PUBLIC KEY` form, and
116    /// what a certificate carries), DER. Must be an `rsaEncryption` key.
117    pub fn from_public_key_der(spki: &[u8]) -> Result<RsaPublicKey, String> {
118        let (seq, rest) = expect_tlv(spki, 0x30, "SubjectPublicKeyInfo")?;
119        if !rest.is_empty() {
120            return Err("trailing bytes after SubjectPublicKeyInfo".to_string());
121        }
122        let (algorithm, after_alg) = expect_tlv(seq, 0x30, "AlgorithmIdentifier")?;
123        let (oid, _params) = expect_tlv(algorithm, 0x06, "algorithm OID")?;
124        if oid != RSA_ENCRYPTION_OID {
125            return Err("not an RSA public key".to_string());
126        }
127        let (bits, after_bits) = expect_tlv(after_alg, 0x03, "subjectPublicKey")?;
128        if !after_bits.is_empty() {
129            return Err("unexpected fields in SubjectPublicKeyInfo".to_string());
130        }
131        match bits.split_first() {
132            Some((0, key)) => RsaPublicKey::from_pkcs1_der(key),
133            _ => Err("subjectPublicKey has unused bits".to_string()),
134        }
135    }
136
137    /// From PEM: `-----BEGIN PUBLIC KEY-----` (SubjectPublicKeyInfo) or
138    /// `-----BEGIN RSA PUBLIC KEY-----` (PKCS#1).
139    pub fn from_public_key_pem(pem: &str) -> Result<RsaPublicKey, String> {
140        for (label, spki) in [("PUBLIC KEY", true), ("RSA PUBLIC KEY", false)] {
141            let begin = format!("-----BEGIN {label}-----");
142            let end = format!("-----END {label}-----");
143            if let Some(start) = pem.find(&begin) {
144                let body_start = start + begin.len();
145                let body_end = pem[body_start..].find(&end).map(|i| body_start + i).ok_or("PEM end line missing")?;
146                let der = base64_decode(&pem[body_start..body_end]).ok_or("PEM body is not base64")?;
147                return if spki { RsaPublicKey::from_public_key_der(&der) } else { RsaPublicKey::from_pkcs1_der(&der) };
148            }
149        }
150        Err("no `BEGIN PUBLIC KEY` or `BEGIN RSA PUBLIC KEY` block".to_string())
151    }
152
153    /// Modulus size in bits.
154    pub fn size_bits(&self) -> usize {
155        (self.k - 1) * 8 + (8 - self.n_bytes[0].leading_zeros() as usize)
156    }
157
158    /// The big-endian modulus.
159    pub fn modulus(&self) -> &[u8] {
160        &self.n_bytes
161    }
162
163    /// The public exponent.
164    pub fn exponent(&self) -> u64 {
165        self.e
166    }
167
168    /// `true` if `signature` is a valid RSASSA-PKCS1-v1_5 signature with
169    /// SHA-256 (JWS `RS256`, XML-DSig `rsa-sha256`) of `message`.
170    pub fn verify_pkcs1v15_sha256(&self, message: &[u8], signature: &[u8]) -> bool {
171        let hash = Sha256::digest(message);
172        self.verify_pkcs1v15_sha256_prehashed(&hash, signature)
173    }
174
175    /// As [`verify_pkcs1v15_sha256`](Self::verify_pkcs1v15_sha256), given the
176    /// SHA-256 hash of the message.
177    pub fn verify_pkcs1v15_sha256_prehashed(&self, hash: &[u8], signature: &[u8]) -> bool {
178        if hash.len() != 32 || signature.len() != self.k {
179            return false;
180        }
181        let s = from_be_bytes(signature, self.n.len());
182        if !less_than(&s, &self.n) {
183            return false;
184        }
185        let m = self.mod_pow(&s);
186        let em = to_be_bytes(&m, self.k);
187
188        // 00 01 FF…FF 00 || DigestInfo || H  (at least 8 bytes of FF)
189        let t_len = SHA256_DIGEST_INFO.len() + 32;
190        if self.k < t_len + 11 {
191            return false;
192        }
193        let mut expected = Vec::with_capacity(self.k);
194        expected.extend_from_slice(&[0x00, 0x01]);
195        expected.resize(self.k - t_len - 1, 0xff);
196        expected.push(0x00);
197        expected.extend_from_slice(&SHA256_DIGEST_INFO);
198        expected.extend_from_slice(hash);
199        em == expected
200    }
201
202    /// `base^e mod n` (`base < n`), square-and-multiply in Montgomery form.
203    fn mod_pow(&self, base: &[u32]) -> Vec<u32> {
204        let one = {
205            let mut v = vec![0u32; self.n.len()];
206            v[0] = 1;
207            v
208        };
209        let base_m = self.mont_mul(base, &self.r2);
210        let mut acc = self.mont_mul(&one, &self.r2); // R mod n: Montgomery 1
211        for bit in (0..64 - self.e.leading_zeros()).rev() {
212            acc = self.mont_mul(&acc, &acc);
213            if (self.e >> bit) & 1 == 1 {
214                acc = self.mont_mul(&acc, &base_m);
215            }
216        }
217        self.mont_mul(&acc, &one)
218    }
219
220    /// Montgomery product `a·b·R⁻¹ mod n` (CIOS).
221    fn mont_mul(&self, a: &[u32], b: &[u32]) -> Vec<u32> {
222        let s = self.n.len();
223        let n = &self.n;
224        let mut t = vec![0u32; s + 2];
225        for &bi in b.iter().take(s) {
226            let mut carry: u64 = 0;
227            for j in 0..s {
228                let x = t[j] as u64 + a[j] as u64 * bi as u64 + carry;
229                t[j] = x as u32;
230                carry = x >> 32;
231            }
232            let x = t[s] as u64 + carry;
233            t[s] = x as u32;
234            t[s + 1] = (x >> 32) as u32;
235
236            let m = t[0].wrapping_mul(self.n0_inv);
237            let x = t[0] as u64 + m as u64 * n[0] as u64;
238            let mut carry = x >> 32;
239            for j in 1..s {
240                let x = t[j] as u64 + m as u64 * n[j] as u64 + carry;
241                t[j - 1] = x as u32;
242                carry = x >> 32;
243            }
244            let x = t[s] as u64 + carry;
245            t[s - 1] = x as u32;
246            t[s] = t[s + 1] + (x >> 32) as u32;
247            t[s + 1] = 0;
248        }
249        let mut result = t[..s].to_vec();
250        if t[s] != 0 || !less_than(&result, n) {
251            subtract_in_place(&mut result, n);
252        }
253        result
254    }
255}
256
257fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
258    let first = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
259    &bytes[first..]
260}
261
262/// Big-endian bytes → `limbs` little-endian u32 limbs (zero-extended).
263fn from_be_bytes(bytes: &[u8], limbs: usize) -> Vec<u32> {
264    let mut out = vec![0u32; limbs.max(bytes.len().div_ceil(4))];
265    for (i, &b) in bytes.iter().rev().enumerate() {
266        out[i / 4] |= (b as u32) << (8 * (i % 4));
267    }
268    out
269}
270
271/// Little-endian limbs → exactly `len` big-endian bytes.
272fn to_be_bytes(limbs: &[u32], len: usize) -> Vec<u8> {
273    let mut out = vec![0u8; len];
274    for i in 0..len {
275        let limb = limbs.get(i / 4).copied().unwrap_or(0);
276        out[len - 1 - i] = (limb >> (8 * (i % 4))) as u8;
277    }
278    out
279}
280
281fn less_than(a: &[u32], b: &[u32]) -> bool {
282    let len = a.len().max(b.len());
283    for i in (0..len).rev() {
284        let (x, y) = (a.get(i).copied().unwrap_or(0), b.get(i).copied().unwrap_or(0));
285        if x != y {
286            return x < y;
287        }
288    }
289    false
290}
291
292/// `a -= b`, assuming the true result is non-negative modulo 2^(32·len).
293fn subtract_in_place(a: &mut [u32], b: &[u32]) {
294    let mut borrow: i64 = 0;
295    for i in 0..a.len() {
296        let x = a[i] as i64 - b.get(i).copied().unwrap_or(0) as i64 - borrow;
297        if x < 0 {
298            a[i] = (x + (1i64 << 32)) as u32;
299            borrow = 1;
300        } else {
301            a[i] = x as u32;
302            borrow = 0;
303        }
304    }
305}
306
307/// `-n0⁻¹ mod 2³²` for odd `n0` (Newton iteration: each step doubles the
308/// number of correct bits).
309fn montgomery_n0_inv(n0: u32) -> u32 {
310    let mut inv: u32 = 1;
311    for _ in 0..5 {
312        inv = inv.wrapping_mul(2u32.wrapping_sub(n0.wrapping_mul(inv)));
313    }
314    inv.wrapping_neg()
315}
316
317/// `R² mod n`, `R = 2^(32·limbs)`: start from 1 and double `2·32·limbs`
318/// times, subtracting `n` whenever the value reaches it.
319fn r_squared_mod(n: &[u32]) -> Vec<u32> {
320    let s = n.len();
321    let mut x = vec![0u32; s];
322    x[0] = 1;
323    for _ in 0..2 * 32 * s {
324        let mut carry = 0u32;
325        for limb in x.iter_mut() {
326            let next = *limb >> 31;
327            *limb = (*limb << 1) | carry;
328            carry = next;
329        }
330        if carry != 0 || !less_than(&x, n) {
331            subtract_in_place(&mut x, n);
332        }
333    }
334    x
335}
336
337/// One DER element with tag `tag`: `(contents, rest)`.
338fn expect_tlv<'a>(input: &'a [u8], tag: u8, what: &str) -> Result<(&'a [u8], &'a [u8]), String> {
339    let (&t, rest) = input.split_first().ok_or_else(|| format!("{what}: unexpected end of DER"))?;
340    if t != tag {
341        return Err(format!("{what}: expected DER tag {tag:#04x}, found {t:#04x}"));
342    }
343    let (&first, rest) = rest.split_first().ok_or_else(|| format!("{what}: missing length"))?;
344    let (len, rest) = if first < 0x80 {
345        (first as usize, rest)
346    } else {
347        let count = (first & 0x7f) as usize;
348        if count == 0 || count > 4 || rest.len() < count {
349            return Err(format!("{what}: unsupported DER length"));
350        }
351        let len = rest[..count].iter().fold(0usize, |acc, b| (acc << 8) | *b as usize);
352        (len, &rest[count..])
353    };
354    if rest.len() < len {
355        return Err(format!("{what}: DER length past the end of the input"));
356    }
357    Ok((&rest[..len], &rest[len..]))
358}
359
360/// A DER INTEGER's magnitude; negative values are rejected.
361fn positive_integer(bytes: &[u8]) -> Result<&[u8], String> {
362    match bytes.first() {
363        None => Err("empty DER INTEGER".to_string()),
364        Some(b) if b & 0x80 != 0 => Err("negative DER INTEGER".to_string()),
365        Some(_) => Ok(strip_leading_zeros(bytes)),
366    }
367}
368
369/// Standard base64 (RFC 4648 §4), ignoring whitespace; padding optional.
370fn base64_decode(input: &str) -> Option<Vec<u8>> {
371    crate::core::base64::Decoder::STANDARD.skip_whitespace().decode(input).ok()
372}