Skip to content

Commit 3d20834

Browse files
committed
Support cipher suite selection
1 parent 41522da commit 3d20834

File tree

6 files changed

+974
-4
lines changed

6 files changed

+974
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ libc = "0.2"
2323
tempfile = "3.1.0"
2424

2525
[target.'cfg(target_os = "windows")'.dependencies]
26-
schannel = "0.1.16"
26+
schannel = "0.1.19"
2727

2828
[target.'cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))'.dependencies]
2929
log = "0.4.5"

src/imp/openssl.rs

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,240 @@ use self::openssl::ssl::{
1111
SslVerifyMode,
1212
};
1313
use self::openssl::x509::{store::X509StoreBuilder, X509VerifyResult, X509};
14+
use std::borrow;
15+
use std::collections::HashSet;
1416
use std::error;
1517
use std::fmt;
1618
use std::io;
1719
use std::sync::Once;
1820

21+
use {
22+
CipherSuiteSet, Protocol, TlsAcceptorBuilder, TlsBulkEncryptionAlgorithm, TlsConnectorBuilder,
23+
TlsHashAlgorithm, TlsKeyExchangeAlgorithm, TlsSignatureAlgorithm,
24+
};
1925
use self::openssl::pkey::Private;
2026
use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder};
2127

28+
const CIPHER_STRING_SUFFIX: &[&str] = &[
29+
"!aNULL",
30+
"!eNULL",
31+
"!IDEA",
32+
"!SEED",
33+
"!SRP",
34+
"!PSK",
35+
"@STRENGTH",
36+
];
37+
38+
fn cartesian_product(
39+
xs: impl IntoIterator<Item = Vec<&'static str>>,
40+
ys: impl IntoIterator<Item = &'static str> + Clone,
41+
) -> Vec<Vec<&'static str>> {
42+
xs.into_iter()
43+
.flat_map(move |x| ys.clone().into_iter().map(move |y| [&x, &[y][..]].concat()))
44+
.collect()
45+
}
46+
47+
/// AES-GCM ciphersuites aren't included in AES128 or AES256. However, specifying `AESGCM` in the
48+
/// cipher string doesn't allow us to specify the bitwidth of the AES cipher used, nor does it
49+
/// allow us to specify the bitwidth of the SHA algorithm.
50+
fn expand_gcm_algorithms(cipher_suites: &CipherSuiteSet) -> Vec<&'static str> {
51+
let first = cipher_suites
52+
.key_exchange
53+
.iter()
54+
.flat_map(|alg| -> &[&str] {
55+
match alg {
56+
TlsKeyExchangeAlgorithm::Dhe => &[
57+
"DHE-RSA-AES128-GCM-SHA256",
58+
"DHE-RSA-AES256-GCM-SHA384",
59+
"DHE-DSS-AES128-GCM-SHA256",
60+
"DHE-DSS-AES256-GCM-SHA384",
61+
],
62+
TlsKeyExchangeAlgorithm::Ecdhe => &[
63+
"ECDHE-RSA-AES128-GCM-SHA256",
64+
"ECDHE-RSA-AES256-GCM-SHA384",
65+
"ECDHE-ECDSA-AES128-GCM-SHA256",
66+
"ECDHE-ECDSA-AES256-GCM-SHA384",
67+
],
68+
TlsKeyExchangeAlgorithm::Rsa => &["AES128-GCM-SHA256", "AES256-GCM-SHA384"],
69+
TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(),
70+
}
71+
})
72+
.copied();
73+
let rest: &[HashSet<_>] = &[
74+
cipher_suites
75+
.signature
76+
.iter()
77+
.flat_map(|alg| -> &[&str] {
78+
match alg {
79+
TlsSignatureAlgorithm::Dss => &[
80+
"DH-DSS-AES128-GCM-SHA256",
81+
"DH-DSS-AES256-GCM-SHA384",
82+
"DHE-DSS-AES128-GCM-SHA256",
83+
"DHE-DSS-AES256-GCM-SHA384",
84+
],
85+
TlsSignatureAlgorithm::Ecdsa => &[
86+
"ECDH-ECDSA-AES128-GCM-SHA256",
87+
"ECDH-ECDSA-AES256-GCM-SHA384",
88+
"ECDHE-ECDSA-AES128-GCM-SHA256",
89+
"ECDHE-ECDSA-AES256-GCM-SHA384",
90+
],
91+
TlsSignatureAlgorithm::Rsa => &[
92+
"AES128-GCM-SHA256",
93+
"AES256-GCM-SHA384",
94+
"DH-RSA-AES128-GCM-SHA256",
95+
"DH-RSA-AES256-GCM-SHA384",
96+
"DHE-RSA-AES128-GCM-SHA256",
97+
"DHE-RSA-AES256-GCM-SHA384",
98+
"ECDH-RSA-AES128-GCM-SHA256",
99+
"ECDH-RSA-AES256-GCM-SHA384",
100+
"ECDHE-RSA-AES128-GCM-SHA256",
101+
"ECDHE-RSA-AES256-GCM-SHA384",
102+
],
103+
TlsSignatureAlgorithm::__NonExhaustive => unreachable!(),
104+
}
105+
})
106+
.copied()
107+
.collect(),
108+
cipher_suites
109+
.bulk_encryption
110+
.iter()
111+
.flat_map(|alg| -> &[&str] {
112+
match alg {
113+
TlsBulkEncryptionAlgorithm::Aes128 => &[
114+
"AES128-GCM-SHA256",
115+
"DH-RSA-AES128-GCM-SHA256",
116+
"DH-DSS-AES128-GCM-SHA256",
117+
"DHE-RSA-AES128-GCM-SHA256",
118+
"DHE-DSS-AES128-GCM-SHA256",
119+
"ECDH-RSA-AES128-GCM-SHA256",
120+
"ECDH-ECDSA-AES128-GCM-SHA256",
121+
"ECDHE-RSA-AES128-GCM-SHA256",
122+
"ECDHE-ECDSA-AES128-GCM-SHA256",
123+
],
124+
TlsBulkEncryptionAlgorithm::Aes256 => &[
125+
"AES256-GCM-SHA384",
126+
"DH-RSA-AES256-GCM-SHA384",
127+
"DH-DSS-AES256-GCM-SHA384",
128+
"DHE-RSA-AES256-GCM-SHA384",
129+
"DHE-DSS-AES256-GCM-SHA384",
130+
"ECDH-RSA-AES256-GCM-SHA384",
131+
"ECDH-ECDSA-AES256-GCM-SHA384",
132+
"ECDHE-RSA-AES256-GCM-SHA384",
133+
"ECDHE-ECDSA-AES256-GCM-SHA384",
134+
],
135+
TlsBulkEncryptionAlgorithm::Des => &[],
136+
TlsBulkEncryptionAlgorithm::Rc2 => &[],
137+
TlsBulkEncryptionAlgorithm::Rc4 => &[],
138+
TlsBulkEncryptionAlgorithm::TripleDes => &[],
139+
TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(),
140+
}
141+
})
142+
.copied()
143+
.collect(),
144+
cipher_suites
145+
.hash
146+
.iter()
147+
.flat_map(|alg| -> &[&str] {
148+
match alg {
149+
TlsHashAlgorithm::Md5 => &[],
150+
TlsHashAlgorithm::Sha1 => &[],
151+
TlsHashAlgorithm::Sha256 => &[
152+
"AES128-GCM-SHA256",
153+
"DH-RSA-AES128-GCM-SHA256",
154+
"DH-DSS-AES128-GCM-SHA256",
155+
"DHE-RSA-AES128-GCM-SHA256",
156+
"DHE-DSS-AES128-GCM-SHA256",
157+
"ECDH-RSA-AES128-GCM-SHA256",
158+
"ECDH-ECDSA-AES128-GCM-SHA256",
159+
"ECDHE-RSA-AES128-GCM-SHA256",
160+
"ECDHE-ECDSA-AES128-GCM-SHA256",
161+
],
162+
TlsHashAlgorithm::Sha384 => &[
163+
"AES256-GCM-SHA384",
164+
"DH-RSA-AES256-GCM-SHA384",
165+
"DH-DSS-AES256-GCM-SHA384",
166+
"DHE-RSA-AES256-GCM-SHA384",
167+
"DHE-DSS-AES256-GCM-SHA384",
168+
"ECDH-RSA-AES256-GCM-SHA384",
169+
"ECDH-ECDSA-AES256-GCM-SHA384",
170+
"ECDHE-RSA-AES256-GCM-SHA384",
171+
"ECDHE-ECDSA-AES256-GCM-SHA384",
172+
],
173+
TlsHashAlgorithm::__NonExhaustive => unreachable!(),
174+
}
175+
})
176+
.copied()
177+
.collect(),
178+
];
179+
180+
first
181+
.filter(|alg| rest.iter().all(|algs| algs.contains(alg)))
182+
.collect()
183+
}
184+
185+
fn expand_algorithms(cipher_suites: &CipherSuiteSet) -> String {
186+
let mut cipher_suite_strings: Vec<Vec<&'static str>> = vec![];
187+
188+
cipher_suite_strings.extend(cipher_suites.key_exchange.iter().map(|alg| {
189+
vec![match alg {
190+
TlsKeyExchangeAlgorithm::Dhe => "DHE",
191+
TlsKeyExchangeAlgorithm::Ecdhe => "ECDHE",
192+
TlsKeyExchangeAlgorithm::Rsa => "kRSA",
193+
TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(),
194+
}]
195+
}));
196+
197+
cipher_suite_strings = cartesian_product(
198+
cipher_suite_strings,
199+
cipher_suites.signature.iter().map(|alg| match alg {
200+
TlsSignatureAlgorithm::Dss => "aDSS",
201+
TlsSignatureAlgorithm::Ecdsa => "aECDSA",
202+
TlsSignatureAlgorithm::Rsa => "aRSA",
203+
TlsSignatureAlgorithm::__NonExhaustive => unreachable!(),
204+
}),
205+
);
206+
cipher_suite_strings = cartesian_product(
207+
cipher_suite_strings,
208+
cipher_suites.bulk_encryption.iter().map(|alg| match alg {
209+
TlsBulkEncryptionAlgorithm::Aes128 => "AES128",
210+
TlsBulkEncryptionAlgorithm::Aes256 => "AES256",
211+
TlsBulkEncryptionAlgorithm::Des => "DES",
212+
TlsBulkEncryptionAlgorithm::Rc2 => "RC2",
213+
TlsBulkEncryptionAlgorithm::Rc4 => "RC4",
214+
TlsBulkEncryptionAlgorithm::TripleDes => "3DES",
215+
TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(),
216+
}),
217+
);
218+
cipher_suite_strings = cartesian_product(
219+
cipher_suite_strings,
220+
cipher_suites.hash.iter().map(|alg| match alg {
221+
TlsHashAlgorithm::Md5 => "MD5",
222+
TlsHashAlgorithm::Sha1 => "SHA1",
223+
TlsHashAlgorithm::Sha256 => "SHA256",
224+
TlsHashAlgorithm::Sha384 => "SHA384",
225+
TlsHashAlgorithm::__NonExhaustive => unreachable!(),
226+
}),
227+
);
228+
229+
// GCM first, as `@STRENGTH` sorts purely on bitwidth, and otherwise respects the initial
230+
// ordering. GCM is generally preferred over CBC for performance and security reasons.
231+
expand_gcm_algorithms(cipher_suites)
232+
.into_iter()
233+
.map(borrow::Cow::Borrowed)
234+
.chain(
235+
cipher_suite_strings
236+
.into_iter()
237+
.map(|parts| borrow::Cow::Owned(parts.join("+"))),
238+
)
239+
.chain(
240+
CIPHER_STRING_SUFFIX
241+
.iter()
242+
.map(|s| borrow::Cow::Borrowed(*s)),
243+
)
244+
.collect::<Vec<_>>()
245+
.join(":")
246+
}
247+
22248
#[cfg(have_min_max_version)]
23249
fn supported_protocols(
24250
min: Option<Protocol>,
@@ -262,6 +488,9 @@ impl TlsConnector {
262488
connector.add_extra_chain_cert(cert.to_owned())?;
263489
}
264490
}
491+
if let Some(ref cipher_suites) = builder.cipher_suites {
492+
connector.set_cipher_list(&expand_algorithms(cipher_suites))?;
493+
}
265494
supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?;
266495

267496
if builder.disable_built_in_roots {
@@ -452,3 +681,38 @@ impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
452681
self.0.flush()
453682
}
454683
}
684+
685+
#[cfg(test)]
686+
mod tests {
687+
use super::*;
688+
689+
#[test]
690+
fn expand_algorithms_basic() {
691+
assert_eq!(
692+
expand_algorithms(&CipherSuiteSet {
693+
key_exchange: vec![TlsKeyExchangeAlgorithm::Dhe, TlsKeyExchangeAlgorithm::Ecdhe],
694+
signature: vec![TlsSignatureAlgorithm::Rsa],
695+
bulk_encryption: vec![
696+
TlsBulkEncryptionAlgorithm::Aes128,
697+
TlsBulkEncryptionAlgorithm::Aes256
698+
],
699+
hash: vec![TlsHashAlgorithm::Sha256, TlsHashAlgorithm::Sha384],
700+
}),
701+
"\
702+
DHE-RSA-AES128-GCM-SHA256:\
703+
DHE-RSA-AES256-GCM-SHA384:\
704+
ECDHE-RSA-AES128-GCM-SHA256:\
705+
ECDHE-RSA-AES256-GCM-SHA384:\
706+
DHE+aRSA+AES128+SHA256:\
707+
DHE+aRSA+AES128+SHA384:\
708+
DHE+aRSA+AES256+SHA256:\
709+
DHE+aRSA+AES256+SHA384:\
710+
ECDHE+aRSA+AES128+SHA256:\
711+
ECDHE+aRSA+AES128+SHA384:\
712+
ECDHE+aRSA+AES256+SHA256:\
713+
ECDHE+aRSA+AES256+SHA384:\
714+
!aNULL:!eNULL:!IDEA:!SEED:!SRP:!PSK:@STRENGTH\
715+
",
716+
);
717+
}
718+
}

0 commit comments

Comments
 (0)