Skip to content

Commit 86ae015

Browse files
committed
Harden provider contract enforcement
1 parent 70fcf5e commit 86ae015

4 files changed

Lines changed: 189 additions & 18 deletions

File tree

src/aead.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use windows::Win32::Security::Cryptography::{
1212
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, BCRYPT_CHACHA20_POLY1305_ALG_HANDLE,
1313
BCRYPT_FLAGS, BCRYPT_KEY_HANDLE,
1414
};
15+
use zeroize::Zeroizing;
1516

1617
/// The tag length is 16 bytes for all supported ciphers.
1718
pub(crate) const TAG_LEN: usize = 16;
@@ -113,14 +114,13 @@ impl AeadKey {
113114
..Default::default()
114115
};
115116

117+
let input = Zeroizing::new(data.to_vec());
116118
unsafe {
117-
// SAFETY: CNG supports in-place encryption, so the input and output buffers can be the same.
118119
let mut size = 0u32;
119-
let input = std::slice::from_raw_parts(data.as_ptr().cast(), data.len());
120120

121121
BCryptEncrypt(
122122
*self.handle,
123-
Some(input),
123+
Some(input.as_slice()),
124124
Some(std::ptr::from_ref(&info) as *mut _),
125125
None,
126126
Some(data),
@@ -161,14 +161,11 @@ impl AeadKey {
161161

162162
let mut size = 0u32;
163163

164+
let input = ciphertext.to_vec();
164165
unsafe {
165-
// SAFETY: CNG supports in-place decryption, so the input and output buffers can be the same.
166-
167-
let input = std::slice::from_raw_parts(ciphertext.as_ptr().cast(), ciphertext.len());
168-
169166
BCryptDecrypt(
170167
*self.handle,
171-
Some(input),
168+
Some(&input),
172169
Some(std::ptr::from_ref(&info) as *mut _),
173170
None,
174171
Some(ciphertext),

src/keys.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use windows::{
1616
BCRYPT_RSAPUBLIC_MAGIC, BCRYPT_RSA_ALG_HANDLE,
1717
},
1818
};
19+
use zeroize::Zeroizing;
1920

2021
/// Wrapper for an owned key handle that can be sent between threads.
2122
#[derive(Debug)]
@@ -47,7 +48,7 @@ pub(crate) fn import_rsa_private_key(
4748
+ prime1.len()
4849
+ prime2.len();
4950

50-
let mut blob = Vec::with_capacity(size);
51+
let mut blob = Zeroizing::new(Vec::with_capacity(size));
5152
unsafe {
5253
let p: *const BCRYPT_RSAKEY_BLOB = &header;
5354
let p: *const u8 = p.cast::<u8>();
@@ -67,7 +68,7 @@ pub(crate) fn import_rsa_private_key(
6768
None,
6869
BCRYPT_RSAPRIVATE_BLOB,
6970
&mut *key_handle,
70-
&blob,
71+
blob.as_slice(),
7172
0,
7273
)
7374
.ok()
@@ -146,14 +147,14 @@ fn import_ec_private_key(
146147
cbKey: key_len as u32,
147148
};
148149
let header_size = core::mem::size_of::<BCRYPT_ECCKEY_BLOB>();
149-
let mut blob = Vec::with_capacity(header_size + key_len * 3);
150+
let mut blob = Zeroizing::new(Vec::with_capacity(header_size + key_len * 3));
150151
unsafe {
151152
let p: *const BCRYPT_ECCKEY_BLOB = &header;
152153
let p: *const u8 = p.cast::<u8>();
153154
let slice = std::slice::from_raw_parts(p, header_size);
154155
blob.extend_from_slice(slice);
155156
}
156-
blob.extend_from_slice(&vec![0u8; key_len * 2]);
157+
blob.extend(std::iter::repeat_n(0, key_len * 2));
157158
blob.extend_from_slice(private_key);
158159
let mut key_handle = Owned::default();
159160
unsafe {
@@ -162,7 +163,7 @@ fn import_ec_private_key(
162163
None,
163164
BCRYPT_ECCPRIVATE_BLOB,
164165
&mut *key_handle,
165-
&blob,
166+
blob.as_slice(),
166167
0,
167168
)
168169
.ok()

src/tls12.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ use rustls::{
2020

2121
const GCM_EXPLICIT_NONCE_LENGTH: usize = 8;
2222
const GCM_IMPLICIT_NONCE_LENGTH: usize = 4;
23+
const MAX_TLS12_PLAINTEXT_FRAGMENT_LEN: usize = 16_384;
24+
25+
fn reject_oversized_tls12_plaintext(plaintext_len: usize) -> Result<(), Error> {
26+
if plaintext_len > MAX_TLS12_PLAINTEXT_FRAGMENT_LEN {
27+
return Err(Error::PeerSentOversizedRecord);
28+
}
29+
30+
Ok(())
31+
}
2332

2433
static ECDSA_SCHEMES: &[SignatureScheme] = &[
25-
SignatureScheme::ED25519,
2634
SignatureScheme::ECDSA_NISTP521_SHA512,
2735
SignatureScheme::ECDSA_NISTP384_SHA384,
2836
SignatureScheme::ECDSA_NISTP256_SHA256,
@@ -282,6 +290,7 @@ impl MessageDecrypter for AesGcmDecrypter {
282290
&aad,
283291
&mut payload.as_mut()[GCM_EXPLICIT_NONCE_LENGTH..],
284292
)?;
293+
reject_oversized_tls12_plaintext(plaintext_len)?;
285294

286295
// Remove the explicit nonce from the front of the buffer, as it's not part of the plaintext.
287296
payload.copy_within(
@@ -335,7 +344,34 @@ impl MessageDecrypter for ChaCha20Poly1305Crypter {
335344
tag.copy_from_slice(&payload[message_len..]);
336345

337346
let plaintext_len = self.key.open(nonce.0, &aad, payload)?;
347+
reject_oversized_tls12_plaintext(plaintext_len)?;
338348
payload.truncate(plaintext_len);
339349
Ok(msg.into_plain_message())
340350
}
341351
}
352+
353+
#[cfg(test)]
354+
mod tests {
355+
use super::*;
356+
357+
#[test]
358+
fn tls12_ecdsa_sign_schemes_do_not_advertise_ed25519() {
359+
assert!(!ECDSA_SCHEMES.contains(&SignatureScheme::ED25519));
360+
}
361+
362+
#[test]
363+
fn aes_gcm_decrypter_rejects_oversized_plaintext() {
364+
assert!(matches!(
365+
reject_oversized_tls12_plaintext(MAX_TLS12_PLAINTEXT_FRAGMENT_LEN + 1),
366+
Err(Error::PeerSentOversizedRecord)
367+
));
368+
}
369+
370+
#[test]
371+
fn chacha20_poly1305_decrypter_rejects_oversized_plaintext() {
372+
assert!(matches!(
373+
reject_oversized_tls12_plaintext(MAX_TLS12_PLAINTEXT_FRAGMENT_LEN + 1),
374+
Err(Error::PeerSentOversizedRecord)
375+
));
376+
}
377+
}

src/verify.rs

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ pub static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgori
3636
RSA_PSS_SHA384,
3737
RSA_PSS_SHA256,
3838
RSA_PKCS1_SHA512,
39+
RSA_PKCS1_SHA512_ABSENT_PARAMS,
3940
RSA_PKCS1_SHA384,
41+
RSA_PKCS1_SHA384_ABSENT_PARAMS,
4042
RSA_PKCS1_SHA256,
43+
RSA_PKCS1_SHA256_ABSENT_PARAMS,
4144
],
4245
mapping: &[
4346
//Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is.
@@ -69,6 +72,18 @@ pub(crate) static RSA_PKCS1_SHA256: &dyn SignatureVerificationAlgorithm = &Verif
6972
params: Params::Rsa(RsaPadding::PKCS1),
7073
};
7174

75+
/// RSA PKCS#1 1.5 signatures using SHA-256 with absent AlgorithmIdentifier parameters.
76+
pub(crate) static RSA_PKCS1_SHA256_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
77+
&VerificationAlgorithm {
78+
display_name: "RSA_PKCS1_SHA256_ABSENT_PARAMS",
79+
public_key_alg_id: alg_id::RSA_ENCRYPTION,
80+
signature_alg_id: AlgorithmIdentifier::from_slice(&[
81+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
82+
]),
83+
hash: SHA256,
84+
params: Params::Rsa(RsaPadding::PKCS1),
85+
};
86+
7287
/// RSA PKCS#1 1.5 signatures using SHA-384.
7388
pub(crate) static RSA_PKCS1_SHA384: &dyn SignatureVerificationAlgorithm = &VerificationAlgorithm {
7489
display_name: "RSA_PKCS1_SHA384",
@@ -78,6 +93,18 @@ pub(crate) static RSA_PKCS1_SHA384: &dyn SignatureVerificationAlgorithm = &Verif
7893
params: Params::Rsa(RsaPadding::PKCS1),
7994
};
8095

96+
/// RSA PKCS#1 1.5 signatures using SHA-384 with absent AlgorithmIdentifier parameters.
97+
pub(crate) static RSA_PKCS1_SHA384_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
98+
&VerificationAlgorithm {
99+
display_name: "RSA_PKCS1_SHA384_ABSENT_PARAMS",
100+
public_key_alg_id: alg_id::RSA_ENCRYPTION,
101+
signature_alg_id: AlgorithmIdentifier::from_slice(&[
102+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c,
103+
]),
104+
hash: SHA384,
105+
params: Params::Rsa(RsaPadding::PKCS1),
106+
};
107+
81108
/// RSA PKCS#1 1.5 signatures using SHA-512.
82109
pub(crate) static RSA_PKCS1_SHA512: &dyn SignatureVerificationAlgorithm = &VerificationAlgorithm {
83110
display_name: "RSA_PKCS1_SHA512",
@@ -87,6 +114,18 @@ pub(crate) static RSA_PKCS1_SHA512: &dyn SignatureVerificationAlgorithm = &Verif
87114
params: Params::Rsa(RsaPadding::PKCS1),
88115
};
89116

117+
/// RSA PKCS#1 1.5 signatures using SHA-512 with absent AlgorithmIdentifier parameters.
118+
pub(crate) static RSA_PKCS1_SHA512_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
119+
&VerificationAlgorithm {
120+
display_name: "RSA_PKCS1_SHA512_ABSENT_PARAMS",
121+
public_key_alg_id: alg_id::RSA_ENCRYPTION,
122+
signature_alg_id: AlgorithmIdentifier::from_slice(&[
123+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d,
124+
]),
125+
hash: SHA512,
126+
params: Params::Rsa(RsaPadding::PKCS1),
127+
};
128+
90129
/// RSA PSS signatures using SHA-256.
91130
pub(crate) static RSA_PSS_SHA256: &dyn SignatureVerificationAlgorithm = &VerificationAlgorithm {
92131
display_name: "RSA_PSS_SHA256",
@@ -210,6 +249,22 @@ enum Params {
210249
unsafe impl Send for Params {}
211250
unsafe impl Sync for Params {}
212251

252+
const RSA_MIN_MODULUS_BITS: usize = 2048;
253+
const RSA_MAX_MODULUS_BITS: usize = 8192;
254+
255+
fn rsa_public_key_allowed_by_webpki(key: &RsaPublicKey<'_>) -> bool {
256+
(RSA_MIN_MODULUS_BITS..=RSA_MAX_MODULUS_BITS)
257+
.contains(&rsa_modulus_bit_len(key.modulus.as_bytes()))
258+
}
259+
260+
fn rsa_modulus_bit_len(modulus: &[u8]) -> usize {
261+
let Some(first) = modulus.first() else {
262+
return 0;
263+
};
264+
265+
(modulus.len() - 1) * 8 + (u8::BITS as usize - first.leading_zeros() as usize)
266+
}
267+
213268
#[derive(Debug)]
214269
enum RsaPadding {
215270
PKCS1,
@@ -236,6 +291,9 @@ impl<const HASH_SIZE: usize> SignatureVerificationAlgorithm for VerificationAlgo
236291
match &self.params {
237292
Params::Rsa(padding) => {
238293
let key = RsaPublicKey::try_from(public_key).map_err(|_| InvalidSignature)?;
294+
if !rsa_public_key_allowed_by_webpki(&key) {
295+
return Err(InvalidSignature);
296+
}
239297
let handle = import_rsa_public_key(&key).map_err(|_| InvalidSignature)?;
240298

241299
match padding {
@@ -269,10 +327,7 @@ impl<const HASH_SIZE: usize> SignatureVerificationAlgorithm for VerificationAlgo
269327
BCRYPT_PAD_PSS,
270328
)
271329
.ok()
272-
.map_err(|e| {
273-
dbg!(e);
274-
InvalidSignature
275-
})
330+
.map_err(|_| InvalidSignature)
276331
}
277332
}
278333
}
@@ -343,6 +398,88 @@ mod tests {
343398
use super::*;
344399
use wycheproof::TestResult;
345400

401+
const RSA_PKCS1_SHA256_ABSENT_PARAMS_DER: &[u8] = &[
402+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
403+
];
404+
const RSA_PKCS1_SHA384_ABSENT_PARAMS_DER: &[u8] = &[
405+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c,
406+
];
407+
const RSA_PKCS1_SHA512_ABSENT_PARAMS_DER: &[u8] = &[
408+
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d,
409+
];
410+
411+
#[test]
412+
fn supported_algorithms_include_rsa_pkcs1_absent_parameter_variants() {
413+
for signature_alg_id in [
414+
AlgorithmIdentifier::from_slice(RSA_PKCS1_SHA256_ABSENT_PARAMS_DER),
415+
AlgorithmIdentifier::from_slice(RSA_PKCS1_SHA384_ABSENT_PARAMS_DER),
416+
AlgorithmIdentifier::from_slice(RSA_PKCS1_SHA512_ABSENT_PARAMS_DER),
417+
] {
418+
assert!(SUPPORTED_SIG_ALGS
419+
.all
420+
.iter()
421+
.any(|alg| alg.signature_alg_id() == signature_alg_id));
422+
}
423+
}
424+
425+
#[test]
426+
fn rsa_public_key_policy_matches_webpki_2048_to_8192_bit_bounds() {
427+
let key_2047 = rsa_public_key_with_modulus(&modulus_with_bit_len(2047));
428+
let key_2048 = rsa_public_key_with_modulus(&modulus_with_bit_len(2048));
429+
let key_8192 = rsa_public_key_with_modulus(&modulus_with_bit_len(8192));
430+
let key_8193 = rsa_public_key_with_modulus(&modulus_with_bit_len(8193));
431+
432+
assert!(!rsa_public_key_allowed_by_webpki(&key_2047));
433+
assert!(rsa_public_key_allowed_by_webpki(&key_2048));
434+
assert!(rsa_public_key_allowed_by_webpki(&key_8192));
435+
assert!(!rsa_public_key_allowed_by_webpki(&key_8193));
436+
}
437+
438+
fn rsa_public_key_with_modulus(modulus: &[u8]) -> RsaPublicKey<'static> {
439+
let mut der = Vec::new();
440+
append_der_integer(&mut der, modulus);
441+
append_der_integer(&mut der, &[0x01, 0x00, 0x01]);
442+
443+
let mut sequence = Vec::new();
444+
sequence.push(0x30);
445+
append_der_len(&mut sequence, der.len());
446+
sequence.extend_from_slice(&der);
447+
448+
RsaPublicKey::try_from(Box::leak(sequence.into_boxed_slice()).as_ref()).unwrap()
449+
}
450+
451+
fn modulus_with_bit_len(bit_len: usize) -> Vec<u8> {
452+
let len = bit_len.div_ceil(8);
453+
let mut modulus = vec![0xff; len];
454+
modulus[0] = 1 << ((bit_len - 1) % 8);
455+
modulus
456+
}
457+
458+
fn append_der_integer(der: &mut Vec<u8>, value: &[u8]) {
459+
der.push(0x02);
460+
let needs_leading_zero = value.first().is_some_and(|byte| byte & 0x80 != 0);
461+
append_der_len(der, value.len() + usize::from(needs_leading_zero));
462+
if needs_leading_zero {
463+
der.push(0);
464+
}
465+
der.extend_from_slice(value);
466+
}
467+
468+
fn append_der_len(der: &mut Vec<u8>, len: usize) {
469+
if len < 128 {
470+
der.push(len as u8);
471+
return;
472+
}
473+
474+
let len_bytes = len.to_be_bytes();
475+
let first = len_bytes
476+
.iter()
477+
.position(|byte| *byte != 0)
478+
.unwrap_or(len_bytes.len() - 1);
479+
der.push(0x80 | (len_bytes.len() - first) as u8);
480+
der.extend_from_slice(&len_bytes[first..]);
481+
}
482+
346483
#[test]
347484
fn test_open_ssl_algorithm_debug() {
348485
assert_eq!(

0 commit comments

Comments
 (0)