Skip to content

Commit ab6455e

Browse files
committed
feat: Make PKCS1 v1.5 unpad with implicit rejection the default
1 parent 163ee69 commit ab6455e

File tree

6 files changed

+23
-169
lines changed

6 files changed

+23
-169
lines changed

Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const-oid = { version = "0.10", default-features = false }
1818
crypto-bigint = { version = "0.7", default-features = false, features = ["zeroize", "alloc"] }
1919
crypto-primes = { version = "0.7", default-features = false }
2020
digest = { version = "0.11", default-features = false, features = ["alloc", "oid"] }
21+
hmac = { version = "0.13.0", default-features = false }
2122
rand_core = { version = "0.10", default-features = false }
23+
sha2 = { version = "0.11.0-rc.5", default-features = false, features = ["oid"] }
2224
signature = { version = "3.0.0-rc.10", default-features = false, features = ["alloc", "digest", "rand_core"] }
2325
zeroize = { version = "1.8", features = ["alloc"] }
2426

@@ -28,8 +30,6 @@ pkcs1 = { version = "0.8.0-rc.4", optional = true, default-features = false, fea
2830
pkcs8 = { version = "0.11.0-rc.10", optional = true, default-features = false, features = ["alloc", "pem"] }
2931
serdect = { version = "0.4", optional = true }
3032
sha1 = { version = "0.11.0-rc.5", optional = true, default-features = false, features = ["oid"] }
31-
sha2 = { version = "0.11.0-rc.5", optional = true, default-features = false, features = ["oid"] }
32-
hmac = { version = "0.13.0", optional = true, default-features = false }
3333
spki = { version = "0.8.0-rc.4", optional = true, default-features = false, features = ["alloc"] }
3434
serde = { version = "1.0.184", optional = true, default-features = false, features = ["derive"] }
3535

@@ -41,7 +41,6 @@ serde_test = "1.0.89"
4141
rand = { version = "0.10", features = ["chacha"] }
4242
rand_core = { version = "0.10", default-features = false }
4343
sha1 = { version = "0.11.0-rc.5", default-features = false, features = ["oid"] }
44-
sha2 = { version = "0.11.0-rc.5", default-features = false, features = ["oid"] }
4544
sha3 = { version = "0.11.0-rc.8", default-features = false, features = ["oid"] }
4645
hex = { version = "0.4.3", features = ["serde"] }
4746
serde_json = "1.0.138"
@@ -59,10 +58,9 @@ getrandom = ["crypto-bigint/getrandom", "crypto-common"]
5958
serde = ["encoding", "dep:serde", "dep:serdect", "crypto-bigint/serde"]
6059
pkcs5 = ["pkcs8/encryption"]
6160
std = ["pkcs1?/std", "pkcs8?/std"]
62-
implicit_rejection = ["sha2", "dep:hmac"]
6361

6462
[package.metadata.docs.rs]
65-
features = ["std", "serde", "hazmat", "sha2"]
63+
features = ["std", "serde", "hazmat"]
6664

6765
[profile.dev]
6866
opt-level = 2

src/algorithms/pkcs1v15.rs

Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,16 @@
88
99
use alloc::vec::Vec;
1010
use const_oid::AssociatedOid;
11-
use crypto_bigint::{Choice, CtAssign, CtEq, CtSelect};
12-
use digest::Digest;
11+
use crypto_bigint::{BoxedUint, Choice, CtAssign, CtEq, CtGt, CtLt, CtSelect};
12+
use digest::{Digest, OutputSizeUser};
13+
use hmac::{Hmac, KeyInit, Mac};
1314
use rand_core::TryCryptoRng;
15+
use sha2::Sha256;
1416
use zeroize::Zeroizing;
1517

16-
use crate::errors::{Error, Result};
17-
18-
#[cfg(feature = "implicit_rejection")]
19-
use {
20-
crate::algorithms::pad::uint_to_zeroizing_be_pad,
21-
crypto_bigint::{BoxedUint, CtGt, CtLt},
22-
digest::OutputSizeUser,
23-
hmac::{Hmac, KeyInit, Mac},
24-
sha2::Sha256,
18+
use crate::{
19+
algorithms::pad::uint_to_zeroizing_be_pad,
20+
errors::{Error, Result},
2521
};
2622

2723
/// Fills the provided slice with random values, which are guaranteed
@@ -68,74 +64,16 @@ where
6864
Ok(em)
6965
}
7066

71-
/// Removes the encryption padding scheme from PKCS#1 v1.5.
72-
///
73-
/// Note that whether this function returns an error or not discloses secret
74-
/// information. If an attacker can cause this function to run repeatedly and
75-
/// learn whether each instance returned an error then they can decrypt and
76-
/// forge signatures as if they had the private key. See
77-
/// `decrypt_session_key` for a way of solving this problem.
78-
#[inline]
79-
pub(crate) fn pkcs1v15_encrypt_unpad(em: Vec<u8>, k: usize) -> Result<Vec<u8>> {
80-
let (valid, out, index) = decrypt_inner(em, k)?;
81-
if valid == 0 {
82-
return Err(Error::Decryption);
83-
}
84-
85-
Ok(out[index as usize..].to_vec())
86-
}
87-
88-
/// Removes the PKCS1v15 padding It returns one or zero in valid that indicates whether the
89-
/// plaintext was correctly structured. In either case, the plaintext is
90-
/// returned in em so that it may be read independently of whether it was valid
91-
/// in order to maintain constant memory access patterns. If the plaintext was
92-
/// valid then index contains the index of the original message in em.
93-
#[inline]
94-
fn decrypt_inner(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> {
95-
if k < 11 {
96-
return Err(Error::Decryption);
97-
}
98-
99-
let first_byte_is_zero = em[0].ct_eq(&0u8);
100-
let second_byte_is_two = em[1].ct_eq(&2u8);
101-
102-
// The remainder of the plaintext must be a string of non-zero random
103-
// octets, followed by a 0, followed by the message.
104-
// looking_for_index: 1 iff we are still looking for the zero.
105-
// index: the offset of the first zero byte.
106-
let mut looking_for_index = Choice::TRUE;
107-
let mut index = 0u32;
108-
109-
for (i, el) in em.iter().enumerate().skip(2) {
110-
let equals0 = el.ct_eq(&0u8);
111-
index.ct_assign(&(i as u32), looking_for_index & equals0);
112-
looking_for_index &= !equals0;
113-
}
114-
115-
// The PS padding must be at least 8 bytes long, and it starts two
116-
// bytes into em.
117-
// TODO: WARNING: THIS MUST BE CONSTANT TIME CHECK:
118-
// Ref: https://github.com/dalek-cryptography/subtle/issues/20
119-
// This is currently copy & paste from the constant time impl in
120-
// go, but very likely not sufficient.
121-
let valid_ps = Choice::from_u8_lsb((((2i32 + 8i32 - index as i32 - 1i32) >> 31) & 1) as u8);
122-
let valid = first_byte_is_zero & second_byte_is_two & !looking_for_index & valid_ps;
123-
index = u32::ct_select(&0, &(index + 1), valid);
124-
125-
Ok((valid.to_u8(), em, index))
126-
}
127-
12867
/// Removes PKCS#1 v1.5 encryption padding with implicit rejection.
12968
///
130-
/// Unlike [`pkcs1v15_encrypt_unpad`], this function does not return an error if
69+
/// This function does not return an error if
13170
/// the padding is invalid. Instead, it deterministically generates and returns
13271
/// a replacement random message using a key-derivation function.
13372
/// As a result, callers cannot distinguish between valid and
13473
/// invalid padding based on the output, thus preventing side-channel attacks.
13574
///
13675
/// See
13776
/// [draft-irtf-cfrg-rsa-guidance-08 § 7.2](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-guidance-08#section-7.2)
138-
#[cfg(feature = "implicit_rejection")]
13977
pub(crate) fn pkcs1v15_encrypt_unpad_implicit_rejection(
14078
em: Vec<u8>,
14179
k: usize,
@@ -168,7 +106,7 @@ pub(crate) fn pkcs1v15_encrypt_unpad_implicit_rejection(
168106

169107
// Select the rejection length from the prf output.
170108
let rejection_length = rejection_lengths.chunks_exact(2).fold(0u16, |acc, el| {
171-
let candidate_length = (u16::from(el[0]) << 8 | u16::from(el[1])) & mask;
109+
let candidate_length = ((u16::from(el[0]) << 8) | u16::from(el[1])) & mask;
172110
let less_than_max_length = candidate_length.ct_lt(&max_length);
173111
acc.ct_select(&candidate_length, less_than_max_length)
174112
});
@@ -216,10 +154,8 @@ pub(crate) fn pkcs1v15_encrypt_unpad_implicit_rejection(
216154
Ok(output)
217155
}
218156

219-
#[cfg(feature = "implicit_rejection")]
220157
pub(crate) struct KeyDerivationKey(Zeroizing<[u8; 32]>);
221158

222-
#[cfg(feature = "implicit_rejection")]
223159
impl KeyDerivationKey {
224160
/// Derives a key derivation key from the private key, the ciphertext, and the key length.
225161
///

src/lib.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020
//!
2121
//! ## OAEP encryption
2222
//!
23-
//! Note: requires `sha2` feature of `rsa` crate is enabled.
2423
//!
25-
#![cfg_attr(feature = "sha2", doc = "```")]
26-
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
24+
//! ```
2725
//! use rsa::{RsaPrivateKey, RsaPublicKey, Oaep, sha2::Sha256};
2826
//!
2927
//! let mut rng = rand::rng();
@@ -77,10 +75,8 @@
7775
//! See security notes in the <code><a href="./pkcs1v15/index.html">pkcs1v15</a></code> module.
7876
//! </div>
7977
//!
80-
//! Note: requires `sha2` feature of `rsa` crate is enabled.
8178
//!
82-
#![cfg_attr(feature = "sha2", doc = "```")]
83-
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
79+
//! ```
8480
//! use rsa::RsaPrivateKey;
8581
//! use rsa::pkcs1v15::{SigningKey, VerifyingKey};
8682
//! use rsa::signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier};
@@ -104,10 +100,8 @@
104100
//!
105101
//! ## PSS signatures
106102
//!
107-
//! Note: requires `sha2` feature of `rsa` crate is enabled.
108103
//!
109-
#![cfg_attr(feature = "sha2", doc = "```")]
110-
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
104+
//! ```
111105
//! use rsa::RsaPrivateKey;
112106
//! use rsa::pss::{BlindedSigningKey, VerifyingKey};
113107
//! use rsa::signature::{Keypair,RandomizedSigner, SignatureEncoding, Verifier};
@@ -253,7 +247,6 @@ mod key;
253247
pub use pkcs1;
254248
#[cfg(feature = "encoding")]
255249
pub use pkcs8;
256-
#[cfg(any(feature = "sha2", feature = "implicit_rejection"))]
257250
pub use sha2;
258251

259252
pub use crate::{
@@ -265,8 +258,5 @@ pub use crate::{
265258
traits::keys::CrtValue,
266259
};
267260

268-
#[cfg(feature = "implicit_rejection")]
269-
pub use pkcs1v15::Pkcs1v15EncryptImplicitRejection;
270-
271261
#[cfg(feature = "hazmat")]
272262
pub mod hazmat;

src/pkcs1v15.rs

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ pub use self::{
3737
signing_key::SigningKey, verifying_key::VerifyingKey,
3838
};
3939

40-
#[cfg(feature = "implicit_rejection")]
4140
use crate::algorithms::pkcs1v15::{pkcs1v15_encrypt_unpad_implicit_rejection, KeyDerivationKey};
4241

4342
use alloc::{boxed::Box, vec::Vec};
@@ -52,7 +51,6 @@ use crate::algorithms::pkcs1v15::*;
5251
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
5352
use crate::errors::{Error, Result};
5453
use crate::key::{self, RsaPrivateKey, RsaPublicKey};
55-
#[cfg(feature = "implicit_rejection")]
5654
use crate::traits::PrivateKeyParts;
5755
use crate::traits::{PaddingScheme, PublicKeyParts, SignatureScheme};
5856

@@ -61,32 +59,6 @@ use crate::traits::{PaddingScheme, PublicKeyParts, SignatureScheme};
6159
pub struct Pkcs1v15Encrypt;
6260

6361
impl PaddingScheme for Pkcs1v15Encrypt {
64-
fn decrypt<Rng: TryCryptoRng + ?Sized>(
65-
self,
66-
rng: Option<&mut Rng>,
67-
priv_key: &RsaPrivateKey,
68-
ciphertext: &[u8],
69-
) -> Result<Vec<u8>> {
70-
decrypt(rng, priv_key, ciphertext)
71-
}
72-
73-
fn encrypt<Rng: TryCryptoRng + ?Sized>(
74-
self,
75-
rng: &mut Rng,
76-
pub_key: &RsaPublicKey,
77-
msg: &[u8],
78-
) -> Result<Vec<u8>> {
79-
encrypt(rng, pub_key, msg)
80-
}
81-
}
82-
83-
/// Encryption using PKCS#1 v1.5 padding with implicit rejection.
84-
#[cfg(feature = "implicit_rejection")]
85-
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
86-
pub struct Pkcs1v15EncryptImplicitRejection;
87-
88-
#[cfg(feature = "implicit_rejection")]
89-
impl PaddingScheme for Pkcs1v15EncryptImplicitRejection {
9062
fn decrypt<Rng: TryCryptoRng + ?Sized>(
9163
self,
9264
rng: Option<&mut Rng>,
@@ -190,43 +162,18 @@ fn encrypt<R: TryCryptoRng + ?Sized>(
190162
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
191163
}
192164

193-
/// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5.
194-
///
195-
/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
196-
///
197-
/// Note that whether this function returns an error or not discloses secret
198-
/// information. If an attacker can cause this function to run repeatedly and
199-
/// learn whether each instance returned an error then they can decrypt and
200-
/// forge signatures as if they had the private key. See
201-
/// `decrypt_session_key` for a way of solving this problem.
202-
#[inline]
203-
fn decrypt<R: TryCryptoRng + ?Sized>(
204-
rng: Option<&mut R>,
205-
priv_key: &RsaPrivateKey,
206-
ciphertext: &[u8],
207-
) -> Result<Vec<u8>> {
208-
key::check_public(priv_key)?;
209-
210-
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
211-
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
212-
let em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
213-
214-
pkcs1v15_encrypt_unpad(em, priv_key.size())
215-
}
216-
217165
/// Decrypts plaintext using RSA and the PKCS#1 v1.5 padding scheme with implicit rejection.
218166
///
219167
/// If an `rng` is provided, RSA blinding is used to avoid timing side-channel attacks.
220168
///
221-
/// Unlike [`decrypt`], this function does not return an error if
169+
/// This function does not return an error if
222170
/// the padding is invalid. Instead, it deterministically generates and returns
223171
/// a replacement random message using a key-derivation function.
224172
/// As a result, callers cannot distinguish between valid and
225173
/// invalid paddings based on the output, thus reducing the risk of side-channel attacks.
226174
///
227175
/// See
228176
/// [draft-irtf-cfrg-rsa-guidance-08](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-guidance-08)
229-
#[cfg(feature = "implicit_rejection")]
230177
#[inline]
231178
fn decrypt_implicit_rejection<R: TryCryptoRng + ?Sized>(
232179
rng: Option<&mut R>,
@@ -299,25 +246,21 @@ mod oid {
299246
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.5");
300247
}
301248

302-
#[cfg(feature = "sha2")]
303249
impl RsaSignatureAssociatedOid for sha2::Sha224 {
304250
const OID: ObjectIdentifier =
305251
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.14");
306252
}
307253

308-
#[cfg(feature = "sha2")]
309254
impl RsaSignatureAssociatedOid for sha2::Sha256 {
310255
const OID: ObjectIdentifier =
311256
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
312257
}
313258

314-
#[cfg(feature = "sha2")]
315259
impl RsaSignatureAssociatedOid for sha2::Sha384 {
316260
const OID: ObjectIdentifier =
317261
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
318262
}
319263

320-
#[cfg(feature = "sha2")]
321264
impl RsaSignatureAssociatedOid for sha2::Sha512 {
322265
const OID: ObjectIdentifier =
323266
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
@@ -416,7 +359,7 @@ mod tests {
416359

417360
let blind: bool = rng.next_u32() < (1u32 << 31);
418361
let blinder = if blind { Some(&mut rng) } else { None };
419-
let plaintext = decrypt(blinder, &priv_key, &ciphertext).unwrap();
362+
let plaintext = decrypt_implicit_rejection(blinder, &priv_key, &ciphertext).unwrap();
420363
assert_eq!(input, plaintext);
421364
}
422365
}

src/pkcs1v15/decrypting_key.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::EncryptingKey;
22
use crate::{
33
dummy_rng::DummyRng,
4+
pkcs1v15::decrypt_implicit_rejection,
45
traits::{Decryptor, EncryptingKeypair, RandomizedDecryptor},
56
Result, RsaPrivateKey,
67
};
@@ -28,14 +29,7 @@ impl DecryptingKey {
2829

2930
impl Decryptor for DecryptingKey {
3031
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
31-
#[cfg(feature = "implicit_rejection")]
32-
{
33-
crate::pkcs1v15::decrypt_implicit_rejection::<DummyRng>(None, &self.inner, ciphertext)
34-
}
35-
#[cfg(not(feature = "implicit_rejection"))]
36-
{
37-
crate::pkcs1v15::decrypt::<DummyRng>(None, &self.inner, ciphertext)
38-
}
32+
decrypt_implicit_rejection::<DummyRng>(None, &self.inner, ciphertext)
3933
}
4034
}
4135

@@ -45,14 +39,7 @@ impl RandomizedDecryptor for DecryptingKey {
4539
rng: &mut R,
4640
ciphertext: &[u8],
4741
) -> Result<Vec<u8>> {
48-
#[cfg(feature = "implicit_rejection")]
49-
{
50-
crate::pkcs1v15::decrypt_implicit_rejection::<R>(Some(rng), &self.inner, ciphertext)
51-
}
52-
#[cfg(not(feature = "implicit_rejection"))]
53-
{
54-
crate::pkcs1v15::decrypt::<R>(Some(rng), &self.inner, ciphertext)
55-
}
42+
decrypt_implicit_rejection::<R>(Some(rng), &self.inner, ciphertext)
5643
}
5744
}
5845

0 commit comments

Comments
 (0)