Skip to content

Commit 3e9c1b5

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

File tree

6 files changed

+990
-5
lines changed

6 files changed

+990
-5
lines changed

Cargo.toml

+1-1
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

+264-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,238 @@ 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

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

22247
#[cfg(have_min_max_version)]
23248
fn supported_protocols(
@@ -262,6 +487,9 @@ impl TlsConnector {
262487
connector.add_extra_chain_cert(cert.to_owned())?;
263488
}
264489
}
490+
if let Some(ref cipher_suites) = builder.cipher_suites {
491+
connector.set_cipher_list(&expand_algorithms(cipher_suites))?;
492+
}
265493
supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?;
266494

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

0 commit comments

Comments
 (0)