Skip to main content

rama_boring/ssl/
connector.rs

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