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