Skip to main content

async_openssl/
lib.rs

1//! Async TLS streams backed by OpenSSL.
2//!
3//! This crate provides a wrapper around the [`openssl`] crate's [`SslStream`](ssl::SslStream) type
4//! that works with with [`futures_io`]'s [`AsyncRead`] and [`AsyncWrite`] traits rather than std's
5//! blocking [`Read`] and [`Write`] traits.
6#![deny(missing_docs, missing_debug_implementations, unsafe_code)]
7#![warn(unreachable_pub, unused_qualifications, unused_lifetimes)]
8#![warn(
9    clippy::must_use_candidate,
10    clippy::unwrap_in_result,
11    clippy::panic_in_result_fn
12)]
13
14use futures_io::{AsyncRead, AsyncWrite};
15use openssl::{
16    error::ErrorStack,
17    ssl::{self, ErrorCode, ShutdownResult, Ssl, SslRef},
18};
19use std::{
20    fmt, future,
21    io::{self, Read, Write},
22    pin::Pin,
23    task::{Context, Poll, Waker},
24};
25
26#[cfg(test)]
27mod test;
28
29struct StreamWrapper<S: Unpin> {
30    stream: S,
31    waker: Option<Waker>,
32}
33
34impl<S> fmt::Debug for StreamWrapper<S>
35where
36    S: fmt::Debug + Unpin,
37{
38    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
39        self.stream.fmt(fmt)
40    }
41}
42
43impl<S: Unpin> StreamWrapper<S> {
44    fn parts(&mut self) -> (Pin<&mut S>, Context<'_>) {
45        let stream = Pin::new(&mut self.stream);
46        // The wrapper is only ever driven from inside `SslStream::with_context`, which installs
47        // the current waker first, so the fallback is unreachable in practice.
48        let context = Context::from_waker(self.waker.as_ref().unwrap_or(Waker::noop()));
49        (stream, context)
50    }
51}
52
53impl<S> Read for StreamWrapper<S>
54where
55    S: AsyncRead + Unpin,
56{
57    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
58        let (stream, mut cx) = self.parts();
59        match stream.poll_read(&mut cx, buf)? {
60            Poll::Ready(nread) => Ok(nread),
61            Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
62        }
63    }
64}
65
66impl<S> Write for StreamWrapper<S>
67where
68    S: AsyncWrite + Unpin,
69{
70    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
71        let (stream, mut cx) = self.parts();
72        match stream.poll_write(&mut cx, buf) {
73            Poll::Ready(r) => r,
74            Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
75        }
76    }
77
78    fn flush(&mut self) -> io::Result<()> {
79        let (stream, mut cx) = self.parts();
80        match stream.poll_flush(&mut cx) {
81            Poll::Ready(r) => r,
82            Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
83        }
84    }
85}
86
87fn cvt<T>(r: io::Result<T>) -> Poll<io::Result<T>> {
88    match r {
89        Ok(v) => Poll::Ready(Ok(v)),
90        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
91        Err(e) => Poll::Ready(Err(e)),
92    }
93}
94
95fn cvt_ossl<T>(r: Result<T, ssl::Error>) -> Poll<Result<T, ssl::Error>> {
96    match r {
97        Ok(v) => Poll::Ready(Ok(v)),
98        Err(e) => match e.code() {
99            ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => Poll::Pending,
100            _ => Poll::Ready(Err(e)),
101        },
102    }
103}
104
105/// An asynchronous version of [`openssl::ssl::SslStream`].
106pub struct SslStream<S: Unpin> {
107    inner: ssl::SslStream<StreamWrapper<S>>,
108    /// Whether `close_notify` has already been handed to the peer by
109    /// [`poll_close`](AsyncWrite::poll_close). See that method for why this has to be remembered
110    /// across polls.
111    close_notify_sent: bool,
112}
113
114impl<S> fmt::Debug for SslStream<S>
115where
116    S: fmt::Debug + Unpin,
117{
118    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
119        fmt.debug_tuple("SslStream").field(&self.inner).finish()
120    }
121}
122
123impl<S> SslStream<S>
124where
125    S: AsyncRead + AsyncWrite + Unpin,
126{
127    /// Like [`SslStream::new`](ssl::SslStream::new).
128    pub fn new(ssl: Ssl, stream: S) -> Result<Self, ErrorStack> {
129        ssl::SslStream::new(
130            ssl,
131            StreamWrapper {
132                stream,
133                waker: None,
134            },
135        )
136        .map(|inner| SslStream {
137            inner,
138            close_notify_sent: false,
139        })
140    }
141
142    /// Like [`SslStream::connect`](ssl::SslStream::connect).
143    pub fn poll_connect(
144        self: Pin<&mut Self>,
145        cx: &mut Context<'_>,
146    ) -> Poll<Result<(), ssl::Error>> {
147        self.with_context(cx, |s| cvt_ossl(s.connect()))
148    }
149
150    /// A convenience method wrapping [`poll_connect`](Self::poll_connect).
151    pub async fn connect(mut self: Pin<&mut Self>) -> Result<(), ssl::Error> {
152        future::poll_fn(|cx| self.as_mut().poll_connect(cx)).await
153    }
154
155    /// Like [`SslStream::accept`](ssl::SslStream::accept).
156    pub fn poll_accept(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), ssl::Error>> {
157        self.with_context(cx, |s| cvt_ossl(s.accept()))
158    }
159
160    /// A convenience method wrapping [`poll_accept`](Self::poll_accept).
161    pub async fn accept(mut self: Pin<&mut Self>) -> Result<(), ssl::Error> {
162        future::poll_fn(|cx| self.as_mut().poll_accept(cx)).await
163    }
164
165    /// Like [`SslStream::do_handshake`](ssl::SslStream::do_handshake).
166    pub fn poll_do_handshake(
167        self: Pin<&mut Self>,
168        cx: &mut Context<'_>,
169    ) -> Poll<Result<(), ssl::Error>> {
170        self.with_context(cx, |s| cvt_ossl(s.do_handshake()))
171    }
172
173    /// A convenience method wrapping [`poll_do_handshake`](Self::poll_do_handshake).
174    pub async fn do_handshake(mut self: Pin<&mut Self>) -> Result<(), ssl::Error> {
175        future::poll_fn(|cx| self.as_mut().poll_do_handshake(cx)).await
176    }
177
178    /// Like [`SslStream::ssl_peek`](ssl::SslStream::ssl_peek).
179    pub fn poll_peek(
180        self: Pin<&mut Self>,
181        cx: &mut Context<'_>,
182        buf: &mut [u8],
183    ) -> Poll<Result<usize, ssl::Error>> {
184        // `SSL_peek_ex` reports a zero-length peek as a failure with `WANT_READ`, which we would
185        // translate into a `Pending` that never resolves. Nothing can be peeked into an empty
186        // buffer anyway, so answer directly and match what `poll_read` does for an empty buffer.
187        if buf.is_empty() {
188            return Poll::Ready(Ok(0));
189        }
190        self.with_context(cx, |s| cvt_ossl(s.ssl_peek(buf)))
191    }
192
193    /// A convenience method wrapping [`poll_peek`](Self::poll_peek).
194    pub async fn peek(mut self: Pin<&mut Self>, buf: &mut [u8]) -> Result<usize, ssl::Error> {
195        future::poll_fn(|cx| self.as_mut().poll_peek(cx, buf)).await
196    }
197
198    /// Like [`SslStream::read_early_data`](ssl::SslStream::read_early_data).
199    #[cfg(ossl111)]
200    pub fn poll_read_early_data(
201        self: Pin<&mut Self>,
202        cx: &mut Context<'_>,
203        buf: &mut [u8],
204    ) -> Poll<Result<usize, ssl::Error>> {
205        self.with_context(cx, |s| cvt_ossl(s.read_early_data(buf)))
206    }
207
208    /// A convenience method wrapping [`poll_read_early_data`](Self::poll_read_early_data).
209    #[cfg(ossl111)]
210    pub async fn read_early_data(
211        mut self: Pin<&mut Self>,
212        buf: &mut [u8],
213    ) -> Result<usize, ssl::Error> {
214        future::poll_fn(|cx| self.as_mut().poll_read_early_data(cx, buf)).await
215    }
216
217    /// Like [`SslStream::write_early_data`](ssl::SslStream::write_early_data).
218    #[cfg(ossl111)]
219    pub fn poll_write_early_data(
220        self: Pin<&mut Self>,
221        cx: &mut Context<'_>,
222        buf: &[u8],
223    ) -> Poll<Result<usize, ssl::Error>> {
224        self.with_context(cx, |s| cvt_ossl(s.write_early_data(buf)))
225    }
226
227    /// A convenience method wrapping [`poll_write_early_data`](Self::poll_write_early_data).
228    #[cfg(ossl111)]
229    pub async fn write_early_data(
230        mut self: Pin<&mut Self>,
231        buf: &[u8],
232    ) -> Result<usize, ssl::Error> {
233        future::poll_fn(|cx| self.as_mut().poll_write_early_data(cx, buf)).await
234    }
235}
236
237impl<S: Unpin> SslStream<S> {
238    /// Returns a shared reference to the `Ssl` object associated with this stream.
239    #[must_use]
240    pub fn ssl(&self) -> &SslRef {
241        self.inner.ssl()
242    }
243
244    /// Returns a shared reference to the underlying stream.
245    #[must_use]
246    pub fn get_ref(&self) -> &S {
247        &self.inner.get_ref().stream
248    }
249
250    /// Returns a mutable reference to the underlying stream.
251    ///
252    /// # Warning
253    ///
254    /// Reading from or writing to the underlying stream directly will corrupt the TLS session.
255    pub fn get_mut(&mut self) -> &mut S {
256        &mut self.inner.get_mut().stream
257    }
258
259    /// Returns a pinned mutable reference to the underlying stream.
260    ///
261    /// # Warning
262    ///
263    /// Reading from or writing to the underlying stream directly will corrupt the TLS session.
264    #[must_use]
265    pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut S> {
266        Pin::new(&mut self.get_mut().inner.get_mut().stream)
267    }
268
269    fn with_context<F, R>(self: Pin<&mut Self>, ctx: &mut Context<'_>, f: F) -> R
270    where
271        F: FnOnce(&mut ssl::SslStream<StreamWrapper<S>>) -> R,
272    {
273        let this = self.get_mut();
274        match &mut this.inner.get_mut().waker {
275            // `Waker::clone_from` skips the refcount traffic when the task did not change, which
276            // is the common case across the repeated polls of a single read or write.
277            Some(waker) => waker.clone_from(ctx.waker()),
278            waker @ None => *waker = Some(ctx.waker().clone()),
279        }
280        f(&mut this.inner)
281    }
282}
283
284impl<S> AsyncRead for SslStream<S>
285where
286    S: AsyncRead + AsyncWrite + Unpin,
287{
288    fn poll_read(
289        self: Pin<&mut Self>,
290        ctx: &mut Context<'_>,
291        buf: &mut [u8],
292    ) -> Poll<io::Result<usize>> {
293        self.with_context(ctx, |s| cvt(s.read(buf)))
294    }
295}
296
297impl<S> AsyncWrite for SslStream<S>
298where
299    S: AsyncRead + AsyncWrite + Unpin,
300{
301    fn poll_write(self: Pin<&mut Self>, ctx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
302        self.with_context(ctx, |s| cvt(s.write(buf)))
303    }
304
305    fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<io::Result<()>> {
306        self.with_context(ctx, |s| cvt(s.flush()))
307    }
308
309    fn poll_close(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<io::Result<()>> {
310        // We send close_notify but do not wait for the peer's reply before closing the
311        // underlying stream. This is permitted by RFC 8446 ยง6.1 and avoids a half-close
312        // deadlock, but it means any in-flight data from the peer is silently discarded.
313        //
314        // Sending it is a one-shot step, so it has to be remembered: once our close_notify is
315        // out, a further `SSL_shutdown` moves on to the second phase and waits for the peer's
316        // close_notify, reporting `WANT_READ` until it arrives. Calling it again on a re-poll
317        // would therefore reintroduce exactly the half-close deadlock we mean to avoid, and the
318        // underlying stream would never be closed.
319        if !self.close_notify_sent {
320            match self.as_mut().with_context(ctx, |s| s.shutdown()) {
321                Ok(ShutdownResult::Sent | ShutdownResult::Received) => {}
322                Err(ref e) if e.code() == ErrorCode::ZERO_RETURN => {}
323                Err(ref e)
324                    if e.code() == ErrorCode::WANT_READ || e.code() == ErrorCode::WANT_WRITE =>
325                {
326                    return Poll::Pending;
327                }
328                Err(e) => {
329                    return Poll::Ready(Err(e.into_io_error().unwrap_or_else(io::Error::other)));
330                }
331            }
332            self.as_mut().get_mut().close_notify_sent = true;
333        }
334
335        self.get_pin_mut().poll_close(ctx)
336    }
337}