Skip to main content

trillium_openssl/
client.rs

1use crate::alpn::encode_alpn;
2use OpenSslClientTransportInner::{Tcp, Tls};
3use async_openssl::SslStream;
4use openssl::ssl::{SslConnector, SslMethod};
5use std::{
6    fmt::{self, Debug, Formatter},
7    io::{Error, IoSliceMut, Result},
8    net::SocketAddr,
9    pin::Pin,
10    sync::Arc,
11    task::{Context, Poll},
12};
13use trillium_server_common::{AsyncRead, AsyncWrite, Connector, Destination, Transport, Url};
14
15/// A reference-counted [`SslConnector`] with a sensible default.
16///
17/// The `Default` impl uses [`SslMethod::tls_client`] and advertises `[h2, http/1.1]` via ALPN.
18/// To customize, build a [`SslConnector`] yourself and convert via `From`/`Into`.
19#[derive(Clone, Debug)]
20pub struct OpenSslClientConfig(Arc<SslConnector>);
21
22impl OpenSslClientConfig {
23    /// borrow the inner [`SslConnector`]
24    pub fn as_inner(&self) -> &SslConnector {
25        &self.0
26    }
27}
28
29impl Default for OpenSslClientConfig {
30    fn default() -> Self {
31        let mut builder =
32            SslConnector::builder(SslMethod::tls_client()).expect("could not build SslConnector");
33        let alpn_wire = encode_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]);
34        builder
35            .set_alpn_protos(&alpn_wire)
36            .expect("could not set ALPN protocols");
37        Self(Arc::new(builder.build()))
38    }
39}
40
41impl From<SslConnector> for OpenSslClientConfig {
42    fn from(connector: SslConnector) -> Self {
43        Self(Arc::new(connector))
44    }
45}
46
47impl From<Arc<SslConnector>> for OpenSslClientConfig {
48    fn from(connector: Arc<SslConnector>) -> Self {
49        Self(connector)
50    }
51}
52
53/// Configuration for the openssl client connector
54#[derive(Clone, Default)]
55pub struct OpenSslConfig<Config> {
56    /// configuration for the inner Connector (usually tcp)
57    pub tcp_config: Config,
58
59    /// the openssl client configuration
60    pub ssl_config: OpenSslClientConfig,
61}
62
63impl<C: Connector> OpenSslConfig<C> {
64    /// build a new `OpenSslConfig` from a ssl client configuration and a tcp config
65    pub fn new(ssl_config: impl Into<OpenSslClientConfig>, tcp_config: C) -> Self {
66        Self {
67            tcp_config,
68            ssl_config: ssl_config.into(),
69        }
70    }
71
72    /// replace the tcp config
73    #[must_use]
74    pub fn with_tcp_config(mut self, config: C) -> Self {
75        self.tcp_config = config;
76        self
77    }
78}
79
80impl<Config: Debug> Debug for OpenSslConfig<Config> {
81    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
82        f.debug_struct("OpenSslConfig")
83            .field("tcp_config", &self.tcp_config)
84            .field("ssl_config", &self.ssl_config)
85            .finish()
86    }
87}
88
89impl<C> AsRef<C> for OpenSslConfig<C> {
90    fn as_ref(&self) -> &C {
91        &self.tcp_config
92    }
93}
94
95impl<C: Connector> OpenSslConfig<C> {
96    async fn tls_connect<S: Transport>(
97        &self,
98        host: &str,
99        alpn_wire: Option<&[u8]>,
100        inner: S,
101    ) -> Result<OpenSslClientTransport<S>> {
102        // OpenSSL's `into_ssl` accepts either a domain (setting SNI and hostname verification) or
103        // an IP address literal (skipping SNI and verifying the certificate's IP SAN).
104        let mut ssl = self
105            .ssl_config
106            .as_inner()
107            .configure()
108            .map_err(Error::other)?
109            .into_ssl(host)
110            .map_err(Error::other)?;
111
112        // A per-connection ALPN override replaces the connector's default; absent one, the
113        // configured default stays in place.
114        if let Some(alpn_wire) = alpn_wire {
115            ssl.set_alpn_protos(alpn_wire).map_err(Error::other)?;
116        }
117
118        let mut stream = SslStream::new(ssl, inner).map_err(Error::other)?;
119        Pin::new(&mut stream)
120            .connect()
121            .await
122            .map_err(Error::other)?;
123        Ok(OpenSslClientTransport(Tls(Box::new(stream))))
124    }
125}
126
127impl<C: Connector> Connector for OpenSslConfig<C> {
128    type Runtime = C::Runtime;
129    type Transport = OpenSslClientTransport<C::Transport>;
130    type Udp = C::Udp;
131
132    async fn connect(&self, url: &Url) -> Result<Self::Transport> {
133        self.connect_to(Destination::from_url(url)?).await
134    }
135
136    async fn connect_to(&self, destination: Destination) -> Result<Self::Transport> {
137        if !destination.secure() {
138            return self
139                .tcp_config
140                .connect_to(destination)
141                .await
142                .map(|t| OpenSslClientTransport(Tcp(t)));
143        }
144
145        // The server name comes from the destination, never the dialed address; capture a domain
146        // before the dial moves the destination, deferring the host-less (bare-IP) case to the
147        // address actually connected to.
148        let domain = destination.host().map(str::to_owned);
149        let alpn_wire = destination.alpn().map(encode_alpn);
150        let inner = self
151            .tcp_config
152            .connect_to(destination.with_secure(false))
153            .await?;
154        let host = match domain {
155            Some(domain) => domain,
156            None => inner
157                .peer_addr()?
158                .ok_or_else(|| Error::other("no peer address for bare-ip destination"))?
159                .ip()
160                .to_string(),
161        };
162
163        self.tls_connect(&host, alpn_wire.as_deref(), inner).await
164    }
165
166    async fn connect_over(
167        &self,
168        transport: Box<dyn Transport>,
169        destination: Destination,
170    ) -> Result<Box<dyn Transport>> {
171        if !destination.secure() {
172            return self.tcp_config.connect_over(transport, destination).await;
173        }
174
175        let host = if let Some(host) = destination.host() {
176            host.to_owned()
177        } else {
178            destination
179                .addrs()
180                .first()
181                .ok_or_else(|| {
182                    Error::new(
183                        std::io::ErrorKind::InvalidInput,
184                        "bare-ip destination has no socket address",
185                    )
186                })?
187                .ip()
188                .to_string()
189        };
190        let alpn_wire = destination.alpn().map(encode_alpn);
191        let stream = self
192            .tls_connect(&host, alpn_wire.as_deref(), transport)
193            .await?;
194        Ok(Box::new(stream))
195    }
196
197    fn runtime(&self) -> Self::Runtime {
198        self.tcp_config.runtime()
199    }
200
201    async fn resolve(&self, host: &str, port: u16) -> Result<Vec<SocketAddr>> {
202        self.tcp_config.resolve(host, port).await
203    }
204}
205
206#[derive(Debug)]
207enum OpenSslClientTransportInner<T: Unpin> {
208    Tcp(T),
209    Tls(Box<SslStream<T>>),
210}
211
212/// Transport for the openssl connector
213///
214/// May represent either an encrypted tls connection or a plaintext connection,
215/// depending on the request scheme.
216#[derive(Debug)]
217pub struct OpenSslClientTransport<T: Unpin>(OpenSslClientTransportInner<T>);
218
219impl<T: Unpin> OpenSslClientTransport<T> {
220    /// Borrow the underlying [`SslStream`] if this transport is TLS.
221    pub fn as_tls(&self) -> Option<&SslStream<T>> {
222        match &self.0 {
223            Tcp(_) => None,
224            Tls(s) => Some(s),
225        }
226    }
227}
228
229impl<T: Unpin> AsRef<T> for OpenSslClientTransport<T> {
230    fn as_ref(&self) -> &T {
231        match &self.0 {
232            Tcp(t) => t,
233            Tls(s) => s.get_ref(),
234        }
235    }
236}
237
238impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for OpenSslClientTransport<T> {
239    fn poll_read(
240        mut self: Pin<&mut Self>,
241        cx: &mut Context<'_>,
242        buf: &mut [u8],
243    ) -> Poll<Result<usize>> {
244        match &mut self.0 {
245            Tcp(t) => Pin::new(t).poll_read(cx, buf),
246            Tls(s) => Pin::new(&mut **s).poll_read(cx, buf),
247        }
248    }
249
250    fn poll_read_vectored(
251        mut self: Pin<&mut Self>,
252        cx: &mut Context<'_>,
253        bufs: &mut [IoSliceMut<'_>],
254    ) -> Poll<Result<usize>> {
255        match &mut self.0 {
256            Tcp(t) => Pin::new(t).poll_read_vectored(cx, bufs),
257            Tls(s) => Pin::new(&mut **s).poll_read_vectored(cx, bufs),
258        }
259    }
260}
261
262impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for OpenSslClientTransport<T> {
263    fn poll_write(
264        mut self: Pin<&mut Self>,
265        cx: &mut Context<'_>,
266        buf: &[u8],
267    ) -> Poll<Result<usize>> {
268        match &mut self.0 {
269            Tcp(t) => Pin::new(t).poll_write(cx, buf),
270            Tls(s) => Pin::new(&mut **s).poll_write(cx, buf),
271        }
272    }
273
274    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
275        match &mut self.0 {
276            Tcp(t) => Pin::new(t).poll_flush(cx),
277            Tls(s) => Pin::new(&mut **s).poll_flush(cx),
278        }
279    }
280
281    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
282        match &mut self.0 {
283            Tcp(t) => Pin::new(t).poll_close(cx),
284            Tls(s) => Pin::new(&mut **s).poll_close(cx),
285        }
286    }
287}
288
289impl<T: Transport> Transport for OpenSslClientTransport<T> {
290    fn peer_addr(&self) -> Result<Option<SocketAddr>> {
291        self.as_ref().peer_addr()
292    }
293
294    fn negotiated_alpn(&self) -> Option<std::borrow::Cow<'_, [u8]>> {
295        match &self.0 {
296            Tcp(_) => None,
297            Tls(s) => s
298                .ssl()
299                .selected_alpn_protocol()
300                .map(std::borrow::Cow::Borrowed),
301        }
302    }
303}