openssl/ssl/
connector.rs

1use cfg_if::cfg_if;
2use std::io::{Read, Write};
3use std::ops::{Deref, DerefMut};
4
5use crate::dh::Dh;
6use crate::error::ErrorStack;
7#[cfg(any(ossl111, libressl))]
8use crate::ssl::SslVersion;
9use crate::ssl::{
10    HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
11    SslOptions, SslRef, SslStream, SslVerifyMode,
12};
13use crate::version;
14use std::net::IpAddr;
15
16const FFDHE_2048: &str = "
17-----BEGIN DH PARAMETERS-----
18MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
19+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
2087VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
21YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
227MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
23ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
24-----END DH PARAMETERS-----
25";
26
27#[allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]
28fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
29    let mut ctx = SslContextBuilder::new(method)?;
30
31    cfg_if! {
32        if #[cfg(not(any(boringssl, awslc)))] {
33            let mut opts = SslOptions::ALL
34                | SslOptions::NO_COMPRESSION
35                | SslOptions::NO_SSLV2
36                | SslOptions::NO_SSLV3
37                | SslOptions::SINGLE_DH_USE
38                | SslOptions::SINGLE_ECDH_USE;
39            opts &= !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;
40
41            ctx.set_options(opts);
42        }
43    }
44
45    let mut mode =
46        SslMode::AUTO_RETRY | SslMode::ACCEPT_MOVING_WRITE_BUFFER | SslMode::ENABLE_PARTIAL_WRITE;
47
48    // This is quite a useful optimization for saving memory, but historically
49    // caused CVEs in OpenSSL pre-1.0.1h, according to
50    // https://bugs.python.org/issue25672
51    if version::number() >= 0x1_00_01_08_0 {
52        mode |= SslMode::RELEASE_BUFFERS;
53    }
54
55    ctx.set_mode(mode);
56
57    Ok(ctx)
58}
59
60/// A type which wraps client-side streams in a TLS session.
61///
62/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
63/// structures, configuring cipher suites, session options, hostname verification, and more.
64#[derive(Clone, Debug)]
65pub struct SslConnector(SslContext);
66
67impl SslConnector {
68    /// Creates a new builder for TLS connections.
69    ///
70    /// The default configuration is subject to change, and is currently derived from Python.
71    pub fn builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
72        let mut ctx = ctx(method)?;
73        ctx.set_default_verify_paths()?;
74        ctx.set_cipher_list(
75            "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
76        )?;
77        setup_verify(&mut ctx);
78
79        Ok(SslConnectorBuilder(ctx))
80    }
81
82    /// Initiates a client-side TLS session on a stream.
83    ///
84    /// The domain is used for SNI and hostname verification.
85    pub fn connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
86    where
87        S: Read + Write,
88    {
89        self.configure()?.connect(domain, stream)
90    }
91
92    /// Returns a structure allowing for configuration of a single TLS session before connection.
93    pub fn configure(&self) -> Result<ConnectConfiguration, ErrorStack> {
94        Ssl::new(&self.0).map(|ssl| ConnectConfiguration {
95            ssl,
96            sni: true,
97            verify_hostname: true,
98        })
99    }
100
101    /// Consumes the `SslConnector`, returning the inner raw `SslContext`.
102    pub fn into_context(self) -> SslContext {
103        self.0
104    }
105
106    /// Returns a shared reference to the inner raw `SslContext`.
107    pub fn context(&self) -> &SslContextRef {
108        &self.0
109    }
110}
111
112/// A builder for `SslConnector`s.
113pub struct SslConnectorBuilder(SslContextBuilder);
114
115impl SslConnectorBuilder {
116    /// Consumes the builder, returning an `SslConnector`.
117    pub fn build(self) -> SslConnector {
118        SslConnector(self.0.build())
119    }
120}
121
122impl Deref for SslConnectorBuilder {
123    type Target = SslContextBuilder;
124
125    fn deref(&self) -> &SslContextBuilder {
126        &self.0
127    }
128}
129
130impl DerefMut for SslConnectorBuilder {
131    fn deref_mut(&mut self) -> &mut SslContextBuilder {
132        &mut self.0
133    }
134}
135
136/// A type which allows for configuration of a client-side TLS session before connection.
137pub struct ConnectConfiguration {
138    ssl: Ssl,
139    sni: bool,
140    verify_hostname: bool,
141}
142
143impl ConnectConfiguration {
144    /// A builder-style version of `set_use_server_name_indication`.
145    pub fn use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration {
146        self.set_use_server_name_indication(use_sni);
147        self
148    }
149
150    /// Configures the use of Server Name Indication (SNI) when connecting.
151    ///
152    /// Defaults to `true`.
153    pub fn set_use_server_name_indication(&mut self, use_sni: bool) {
154        self.sni = use_sni;
155    }
156
157    /// A builder-style version of `set_verify_hostname`.
158    pub fn verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration {
159        self.set_verify_hostname(verify_hostname);
160        self
161    }
162
163    /// Configures the use of hostname verification when connecting.
164    ///
165    /// Defaults to `true`.
166    ///
167    /// # Warning
168    ///
169    /// You should think very carefully before you use this method. If hostname verification is not
170    /// used, *any* valid certificate for *any* site will be trusted for use from any other. This
171    /// introduces a significant vulnerability to man-in-the-middle attacks.
172    pub fn set_verify_hostname(&mut self, verify_hostname: bool) {
173        self.verify_hostname = verify_hostname;
174    }
175
176    /// Returns an `Ssl` configured to connect to the provided domain.
177    ///
178    /// The domain is used for SNI (if it is not an IP address) and hostname verification if enabled.
179    pub fn into_ssl(mut self, domain: &str) -> Result<Ssl, ErrorStack> {
180        if self.sni && domain.parse::<IpAddr>().is_err() {
181            self.ssl.set_hostname(domain)?;
182        }
183
184        if self.verify_hostname {
185            setup_verify_hostname(&mut self.ssl, domain)?;
186        }
187
188        Ok(self.ssl)
189    }
190
191    /// Initiates a client-side TLS session on a stream.
192    ///
193    /// The domain is used for SNI and hostname verification if enabled.
194    pub fn connect<S>(self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
195    where
196        S: Read + Write,
197    {
198        self.into_ssl(domain)?.connect(stream)
199    }
200}
201
202impl Deref for ConnectConfiguration {
203    type Target = SslRef;
204
205    fn deref(&self) -> &SslRef {
206        &self.ssl
207    }
208}
209
210impl DerefMut for ConnectConfiguration {
211    fn deref_mut(&mut self) -> &mut SslRef {
212        &mut self.ssl
213    }
214}
215
216/// A type which wraps server-side streams in a TLS session.
217///
218/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
219/// structures, configuring cipher suites, session options, and more.
220#[derive(Clone)]
221pub struct SslAcceptor(SslContext);
222
223impl SslAcceptor {
224    /// Creates a new builder configured to connect to non-legacy clients. This should generally be
225    /// considered a reasonable default choice.
226    ///
227    /// This corresponds to the intermediate configuration of version 5 of Mozilla's server side TLS
228    /// recommendations. See its [documentation][docs] for more details on specifics.
229    ///
230    /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
231    pub fn mozilla_intermediate_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
232        let mut ctx = ctx(method)?;
233        ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
234        let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
235        ctx.set_tmp_dh(&dh)?;
236        setup_curves(&mut ctx)?;
237        ctx.set_cipher_list(
238            "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
239             ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
240             DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
241        )?;
242        #[cfg(any(ossl111, libressl))]
243        ctx.set_ciphersuites(
244            "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
245        )?;
246        Ok(SslAcceptorBuilder(ctx))
247    }
248
249    /// Creates a new builder configured to connect to modern clients.
250    ///
251    /// This corresponds to the modern configuration of version 5 of Mozilla's server side TLS recommendations.
252    /// See its [documentation][docs] for more details on specifics.
253    ///
254    /// Requires OpenSSL 1.1.1 or newer or LibreSSL.
255    ///
256    /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
257    #[cfg(any(ossl111, libressl))]
258    pub fn mozilla_modern_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
259        let mut ctx = ctx(method)?;
260        ctx.set_min_proto_version(Some(SslVersion::TLS1_3))?;
261        ctx.set_ciphersuites(
262            "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
263        )?;
264        Ok(SslAcceptorBuilder(ctx))
265    }
266
267    /// Creates a new builder configured to connect to non-legacy clients. This should generally be
268    /// considered a reasonable default choice.
269    ///
270    /// This corresponds to the intermediate configuration of version 4 of Mozilla's server side TLS
271    /// recommendations. See its [documentation][docs] for more details on specifics.
272    ///
273    /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
274    // FIXME remove in next major version
275    pub fn mozilla_intermediate(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
276        let mut ctx = ctx(method)?;
277        ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE);
278        #[cfg(any(ossl111, libressl))]
279        ctx.set_options(SslOptions::NO_TLSV1_3);
280        let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
281        ctx.set_tmp_dh(&dh)?;
282        setup_curves(&mut ctx)?;
283        ctx.set_cipher_list(
284            "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\
285             ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
286             DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\
287             ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\
288             ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:\
289             DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\
290             EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:\
291             AES256-SHA:DES-CBC3-SHA:!DSS",
292        )?;
293        Ok(SslAcceptorBuilder(ctx))
294    }
295
296    /// Creates a new builder configured to connect to modern clients.
297    ///
298    /// This corresponds to the modern configuration of version 4 of Mozilla's server side TLS recommendations.
299    /// See its [documentation][docs] for more details on specifics.
300    ///
301    /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
302    // FIXME remove in next major version
303    pub fn mozilla_modern(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
304        let mut ctx = ctx(method)?;
305        ctx.set_options(
306            SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1,
307        );
308        #[cfg(any(ossl111, libressl))]
309        ctx.set_options(SslOptions::NO_TLSV1_3);
310        setup_curves(&mut ctx)?;
311        ctx.set_cipher_list(
312            "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:\
313             ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
314             ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
315        )?;
316        Ok(SslAcceptorBuilder(ctx))
317    }
318
319    /// Initiates a server-side TLS session on a stream.
320    pub fn accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
321    where
322        S: Read + Write,
323    {
324        let ssl = Ssl::new(&self.0)?;
325        ssl.accept(stream)
326    }
327
328    /// Consumes the `SslAcceptor`, returning the inner raw `SslContext`.
329    pub fn into_context(self) -> SslContext {
330        self.0
331    }
332
333    /// Returns a shared reference to the inner raw `SslContext`.
334    pub fn context(&self) -> &SslContextRef {
335        &self.0
336    }
337}
338
339/// A builder for `SslAcceptor`s.
340pub struct SslAcceptorBuilder(SslContextBuilder);
341
342impl SslAcceptorBuilder {
343    /// Consumes the builder, returning a `SslAcceptor`.
344    pub fn build(self) -> SslAcceptor {
345        SslAcceptor(self.0.build())
346    }
347}
348
349impl Deref for SslAcceptorBuilder {
350    type Target = SslContextBuilder;
351
352    fn deref(&self) -> &SslContextBuilder {
353        &self.0
354    }
355}
356
357impl DerefMut for SslAcceptorBuilder {
358    fn deref_mut(&mut self) -> &mut SslContextBuilder {
359        &mut self.0
360    }
361}
362
363cfg_if! {
364    if #[cfg(ossl110)] {
365        #[allow(clippy::unnecessary_wraps)]
366        fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> {
367            Ok(())
368        }
369    } else if #[cfg(any(ossl102, libressl))] {
370        fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
371            ctx.set_ecdh_auto(true)
372        }
373    } else {
374        fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
375            use crate::ec::EcKey;
376            use crate::nid::Nid;
377
378            let curve = EcKey::from_curve_name(Nid::X9_62_PRIME256V1)?;
379            ctx.set_tmp_ecdh(&curve)
380        }
381    }
382}
383
384fn setup_verify(ctx: &mut SslContextBuilder) {
385    ctx.set_verify(SslVerifyMode::PEER);
386}
387
388fn setup_verify_hostname(ssl: &mut SslRef, domain: &str) -> Result<(), ErrorStack> {
389    use crate::x509::verify::X509CheckFlags;
390
391    let param = ssl.param_mut();
392    param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
393    match domain.parse() {
394        Ok(ip) => param.set_ip(ip),
395        Err(_) => param.set_host(domain),
396    }
397}