variant_ssl/ssl/
connector.rs

1use cfg_if::cfg_if;
2use std::io::{Read, Write};
3use std::ops::{Deref, DerefMut};
4
5use crate::error::ErrorStack;
6#[cfg(any(ossl111, libressl))]
7use crate::ssl::SslVersion;
8use crate::ssl::{
9    HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
10    SslOptions, SslRef, SslStream, SslVerifyMode,
11};
12use crate::version;
13use std::net::IpAddr;
14
15#[allow(unused)]
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        setup_dh_params(&mut ctx)?;
235        #[cfg(any(ossl111, boringssl, libressl251))]
236        ctx.set_groups_list("X25519:P-256:P-384")?;
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:DHE-RSA-CHACHA20-POLY1305"
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_groups_list("X25519:P-256:P-384")?;
262        ctx.set_ciphersuites(
263            "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
264        )?;
265        Ok(SslAcceptorBuilder(ctx))
266    }
267
268    /// Creates a new builder configured to connect to tls clients using tongsuo.
269    ///
270    /// Requires Tongsuo.
271    #[cfg(tongsuo)]
272    pub fn tongsuo_auto() -> Result<SslAcceptorBuilder, ErrorStack> {
273        let mut ctx = ctx(SslMethod::tls_server())?;
274        ctx.enable_ntls();
275        ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
276        setup_dh_params(&mut ctx)?;
277        ctx.set_groups_list("X25519:P-256:P-384")?;
278        ctx.set_cipher_list(
279            "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
280             ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
281             DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:\
282             ECDHE-SM2-SM4-CBC-SM3:ECDHE-SM2-SM4-GCM-SM3:ECC-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3:\
283             RSA-SM4-CBC-SM3:RSA-SM4-GCM-SM3:RSA-SM4-CBC-SHA256:RSA-SM4-GCM-SHA256",
284        )?;
285        ctx.set_ciphersuites(
286            "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_SM4_GCM_SM3",
287        )?;
288        Ok(SslAcceptorBuilder(ctx))
289    }
290
291    /// Creates a new builder configured to connect to tls clients using tongsuo.
292    ///
293    /// Requires Tongsuo.
294    #[cfg(tongsuo)]
295    pub fn tongsuo_tls() -> Result<SslAcceptorBuilder, ErrorStack> {
296        let mut ctx = ctx(SslMethod::tls_server())?;
297        ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
298        setup_dh_params(&mut ctx)?;
299        ctx.set_groups_list("X25519:P-256:P-384")?;
300        ctx.set_cipher_list(
301            "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
302             ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
303             DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305"
304        )?;
305        ctx.set_ciphersuites(
306            "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_SM4_GCM_SM3",
307        )?;
308        Ok(SslAcceptorBuilder(ctx))
309    }
310
311    /// Creates a new builder configured to connect to tlcp clients using tongsuo.
312    ///
313    /// Requires Tongsuo.
314    #[cfg(tongsuo)]
315    pub fn tongsuo_tlcp() -> Result<SslAcceptorBuilder, ErrorStack> {
316        let mut ctx = ctx(SslMethod::ntls_server())?;
317        ctx.enable_ntls();
318        #[cfg(ossl300)]
319        ctx.enable_force_ntls();
320        // the EC curves should always be SM2
321        ctx.set_cipher_list(
322            "ECDHE-SM2-SM4-CBC-SM3:ECDHE-SM2-SM4-GCM-SM3:ECC-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3:\
323             RSA-SM4-CBC-SM3:RSA-SM4-GCM-SM3:RSA-SM4-CBC-SHA256:RSA-SM4-GCM-SHA256",
324        )?;
325        Ok(SslAcceptorBuilder(ctx))
326    }
327
328    /// Initiates a server-side TLS session on a stream.
329    pub fn accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
330    where
331        S: Read + Write,
332    {
333        let ssl = Ssl::new(&self.0)?;
334        ssl.accept(stream)
335    }
336
337    /// Consumes the `SslAcceptor`, returning the inner raw `SslContext`.
338    pub fn into_context(self) -> SslContext {
339        self.0
340    }
341
342    /// Returns a shared reference to the inner raw `SslContext`.
343    pub fn context(&self) -> &SslContextRef {
344        &self.0
345    }
346}
347
348/// A builder for `SslAcceptor`s.
349pub struct SslAcceptorBuilder(SslContextBuilder);
350
351impl SslAcceptorBuilder {
352    /// Consumes the builder, returning a `SslAcceptor`.
353    pub fn build(self) -> SslAcceptor {
354        SslAcceptor(self.0.build())
355    }
356}
357
358impl Deref for SslAcceptorBuilder {
359    type Target = SslContextBuilder;
360
361    fn deref(&self) -> &SslContextBuilder {
362        &self.0
363    }
364}
365
366impl DerefMut for SslAcceptorBuilder {
367    fn deref_mut(&mut self) -> &mut SslContextBuilder {
368        &mut self.0
369    }
370}
371
372cfg_if! {
373    if #[cfg(ossl300)] {
374        fn setup_dh_params(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
375            ctx.set_dh_auto(true)
376        }
377    } else {
378        fn setup_dh_params(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
379            use crate::dh::Dh;
380
381            let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
382            ctx.set_tmp_dh(&dh)
383        }
384    }
385}
386
387fn setup_verify(ctx: &mut SslContextBuilder) {
388    ctx.set_verify(SslVerifyMode::PEER);
389}
390
391fn setup_verify_hostname(ssl: &mut SslRef, domain: &str) -> Result<(), ErrorStack> {
392    use crate::x509::verify::X509CheckFlags;
393
394    let param = ssl.param_mut();
395    param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
396    match domain.parse() {
397        Ok(ip) => param.set_ip(ip),
398        Err(_) => param.set_host(domain),
399    }
400}