rsa/algorithms/
pkcs1v15.rs

1//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2].
2//!
3//! # Usage
4//!
5//! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures).
6//!
7//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
8
9use alloc::vec::Vec;
10use digest::Digest;
11use pkcs8::AssociatedOid;
12use rand_core::CryptoRngCore;
13use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
14use zeroize::Zeroizing;
15
16use crate::errors::{Error, Result};
17
18/// Fills the provided slice with random values, which are guaranteed
19/// to not be zero.
20#[inline]
21fn non_zero_random_bytes<R: CryptoRngCore + ?Sized>(rng: &mut R, data: &mut [u8]) {
22    rng.fill_bytes(data);
23
24    for el in data {
25        if *el == 0u8 {
26            // TODO: break after a certain amount of time
27            while *el == 0u8 {
28                rng.fill_bytes(core::slice::from_mut(el));
29            }
30        }
31    }
32}
33
34/// Applied the padding scheme from PKCS#1 v1.5 for encryption.  The message must be no longer than
35/// the length of the public modulus minus 11 bytes.
36pub(crate) fn pkcs1v15_encrypt_pad<R>(
37    rng: &mut R,
38    msg: &[u8],
39    k: usize,
40) -> Result<Zeroizing<Vec<u8>>>
41where
42    R: CryptoRngCore + ?Sized,
43{
44    if msg.len() + 11 > k {
45        return Err(Error::MessageTooLong);
46    }
47
48    // EM = 0x00 || 0x02 || PS || 0x00 || M
49    let mut em = Zeroizing::new(vec![0u8; k]);
50    em[1] = 2;
51    non_zero_random_bytes(rng, &mut em[2..k - msg.len() - 1]);
52    em[k - msg.len() - 1] = 0;
53    em[k - msg.len()..].copy_from_slice(msg);
54    Ok(em)
55}
56
57/// Removes the encryption padding scheme from PKCS#1 v1.5.
58///
59/// Note that whether this function returns an error or not discloses secret
60/// information. If an attacker can cause this function to run repeatedly and
61/// learn whether each instance returned an error then they can decrypt and
62/// forge signatures as if they had the private key. See
63/// `decrypt_session_key` for a way of solving this problem.
64#[inline]
65pub(crate) fn pkcs1v15_encrypt_unpad(em: Vec<u8>, k: usize) -> Result<Vec<u8>> {
66    let (valid, out, index) = decrypt_inner(em, k)?;
67    if valid == 0 {
68        return Err(Error::Decryption);
69    }
70
71    Ok(out[index as usize..].to_vec())
72}
73
74/// Removes the PKCS1v15 padding It returns one or zero in valid that indicates whether the
75/// plaintext was correctly structured. In either case, the plaintext is
76/// returned in em so that it may be read independently of whether it was valid
77/// in order to maintain constant memory access patterns. If the plaintext was
78/// valid then index contains the index of the original message in em.
79#[inline]
80fn decrypt_inner(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> {
81    if k < 11 {
82        return Err(Error::Decryption);
83    }
84
85    let first_byte_is_zero = em[0].ct_eq(&0u8);
86    let second_byte_is_two = em[1].ct_eq(&2u8);
87
88    // The remainder of the plaintext must be a string of non-zero random
89    // octets, followed by a 0, followed by the message.
90    //   looking_for_index: 1 iff we are still looking for the zero.
91    //   index: the offset of the first zero byte.
92    let mut looking_for_index = 1u8;
93    let mut index = 0u32;
94
95    for (i, el) in em.iter().enumerate().skip(2) {
96        let equals0 = el.ct_eq(&0u8);
97        index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals0);
98        looking_for_index.conditional_assign(&0u8, equals0);
99    }
100
101    // The PS padding must be at least 8 bytes long, and it starts two
102    // bytes into em.
103    // TODO: WARNING: THIS MUST BE CONSTANT TIME CHECK:
104    // Ref: https://github.com/dalek-cryptography/subtle/issues/20
105    // This is currently copy & paste from the constant time impl in
106    // go, but very likely not sufficient.
107    let valid_ps = Choice::from((((2i32 + 8i32 - index as i32 - 1i32) >> 31) & 1) as u8);
108    let valid =
109        first_byte_is_zero & second_byte_is_two & Choice::from(!looking_for_index & 1) & valid_ps;
110    index = u32::conditional_select(&0, &(index + 1), valid);
111
112    Ok((valid.unwrap_u8(), em, index))
113}
114
115#[inline]
116pub(crate) fn pkcs1v15_sign_pad(prefix: &[u8], hashed: &[u8], k: usize) -> Result<Vec<u8>> {
117    let hash_len = hashed.len();
118    let t_len = prefix.len() + hashed.len();
119    if k < t_len + 11 {
120        return Err(Error::MessageTooLong);
121    }
122
123    // EM = 0x00 || 0x01 || PS || 0x00 || T
124    let mut em = vec![0xff; k];
125    em[0] = 0;
126    em[1] = 1;
127    em[k - t_len - 1] = 0;
128    em[k - t_len..k - hash_len].copy_from_slice(prefix);
129    em[k - hash_len..k].copy_from_slice(hashed);
130
131    Ok(em)
132}
133
134#[inline]
135pub(crate) fn pkcs1v15_sign_unpad(prefix: &[u8], hashed: &[u8], em: &[u8], k: usize) -> Result<()> {
136    let hash_len = hashed.len();
137    let t_len = prefix.len() + hashed.len();
138    if k < t_len + 11 {
139        return Err(Error::Verification);
140    }
141
142    // EM = 0x00 || 0x01 || PS || 0x00 || T
143    let mut ok = em[0].ct_eq(&0u8);
144    ok &= em[1].ct_eq(&1u8);
145    ok &= em[k - hash_len..k].ct_eq(hashed);
146    ok &= em[k - t_len..k - hash_len].ct_eq(prefix);
147    ok &= em[k - t_len - 1].ct_eq(&0u8);
148
149    for el in em.iter().skip(2).take(k - t_len - 3) {
150        ok &= el.ct_eq(&0xff)
151    }
152
153    if ok.unwrap_u8() != 1 {
154        return Err(Error::Verification);
155    }
156
157    Ok(())
158}
159
160/// prefix = 0x30 <oid_len + 8 + digest_len> 0x30 <oid_len + 4> 0x06 <oid_len> oid 0x05 0x00 0x04 <digest_len>
161#[inline]
162pub(crate) fn pkcs1v15_generate_prefix<D>() -> Vec<u8>
163where
164    D: Digest + AssociatedOid,
165{
166    let oid = D::OID.as_bytes();
167    let oid_len = oid.len() as u8;
168    let digest_len = <D as Digest>::output_size() as u8;
169    let mut v = vec![
170        0x30,
171        oid_len + 8 + digest_len,
172        0x30,
173        oid_len + 4,
174        0x6,
175        oid_len,
176    ];
177    v.extend_from_slice(oid);
178    v.extend_from_slice(&[0x05, 0x00, 0x04, digest_len]);
179    v
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
186
187    #[test]
188    fn test_non_zero_bytes() {
189        for _ in 0..10 {
190            let mut rng = ChaCha8Rng::from_seed([42; 32]);
191            let mut b = vec![0u8; 512];
192            non_zero_random_bytes(&mut rng, &mut b);
193            for el in &b {
194                assert_ne!(*el, 0u8);
195            }
196        }
197    }
198
199    #[test]
200    fn test_encrypt_tiny_no_crash() {
201        let mut rng = ChaCha8Rng::from_seed([42; 32]);
202        let k = 8;
203        let message = vec![1u8; 4];
204        let res = pkcs1v15_encrypt_pad(&mut rng, &message, k);
205        assert_eq!(res, Err(Error::MessageTooLong));
206    }
207}