Skip to main content

rama_boring/ssl/
async_callbacks.rs

1use super::mut_only::MutOnly;
2use super::{
3    ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError, SelectCertError,
4    Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError,
5    SslVerifyMode,
6};
7use crate::ex_data::Index;
8use std::convert::identity;
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::LazyLock;
12use std::task::{ready, Context, Poll, Waker};
13
14/// The type of futures to pass to [`SslContextBuilder::set_async_select_certificate_callback`].
15pub type BoxSelectCertFuture = ExDataFuture<Result<BoxSelectCertFinish, AsyncSelectCertError>>;
16
17/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
18pub type BoxSelectCertFinish = Box<dyn FnOnce(ClientHello<'_>) -> Result<(), AsyncSelectCertError>>;
19
20/// The type of futures returned by [`AsyncPrivateKeyMethod`] methods.
21pub type BoxPrivateKeyMethodFuture =
22    ExDataFuture<Result<BoxPrivateKeyMethodFinish, AsyncPrivateKeyMethodError>>;
23
24/// The type of callbacks returned by [`BoxPrivateKeyMethodFuture`].
25pub type BoxPrivateKeyMethodFinish =
26    Box<dyn FnOnce(&mut SslRef, &mut [u8]) -> Result<usize, AsyncPrivateKeyMethodError>>;
27
28/// The type of futures to pass to [`SslContextBuilder::set_async_get_session_callback`].
29pub type BoxGetSessionFuture = ExDataFuture<Option<BoxGetSessionFinish>>;
30
31/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
32pub type BoxGetSessionFinish = Box<dyn FnOnce(&mut SslRef, &[u8]) -> Option<SslSession>>;
33
34/// The type of futures to pass to [`SslContextBuilder::set_async_custom_verify_callback`].
35pub type BoxCustomVerifyFuture = ExDataFuture<Result<BoxCustomVerifyFinish, SslAlert>>;
36
37/// The type of callbacks returned by [`BoxCustomVerifyFuture`] methods.
38pub type BoxCustomVerifyFinish = Box<dyn FnOnce(&mut SslRef) -> Result<(), SslAlert>>;
39
40/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilder`] methods.
41///
42/// Public for documentation purposes.
43pub type ExDataFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
44
45pub(crate) static TASK_WAKER_INDEX: LazyLock<Index<Ssl, Option<Waker>>> =
46    LazyLock::new(|| Ssl::new_ex_index().unwrap());
47pub(crate) static SELECT_CERT_FUTURE_INDEX: LazyLock<
48    Index<Ssl, MutOnly<Option<BoxSelectCertFuture>>>,
49> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
50pub(crate) static SELECT_PRIVATE_KEY_METHOD_FUTURE_INDEX: LazyLock<
51    Index<Ssl, MutOnly<Option<BoxPrivateKeyMethodFuture>>>,
52> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
53pub(crate) static SELECT_GET_SESSION_FUTURE_INDEX: LazyLock<
54    Index<Ssl, MutOnly<Option<BoxGetSessionFuture>>>,
55> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
56pub(crate) static SELECT_CUSTOM_VERIFY_FUTURE_INDEX: LazyLock<
57    Index<Ssl, MutOnly<Option<BoxCustomVerifyFuture>>>,
58> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
59
60impl SslContextBuilder {
61    /// Sets a callback that is called before most [`ClientHello`] processing
62    /// and before the decision whether to resume a session is made. The
63    /// callback may inspect the [`ClientHello`] and configure the connection.
64    ///
65    /// This method uses a function that returns a future whose output is
66    /// itself a closure that will be passed [`ClientHello`] to configure
67    /// the connection based on the computations done in the future.
68    ///
69    /// A task waker must be set on `Ssl` values associated with the resulting
70    /// `SslContext` with [`SslRef::set_task_waker`].
71    ///
72    /// See [`SslContextBuilder::set_select_certificate_callback`] for the sync
73    /// setter of this callback.
74    pub fn set_async_select_certificate_callback<F>(&mut self, callback: F)
75    where
76        F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
77            + Send
78            + Sync
79            + 'static,
80    {
81        self.set_select_certificate_callback(move |mut client_hello| {
82            let fut_poll_result = with_ex_data_future(
83                &mut client_hello,
84                *SELECT_CERT_FUTURE_INDEX,
85                ClientHello::ssl_mut,
86                &callback,
87                identity,
88            );
89
90            let fut_result = match fut_poll_result {
91                Poll::Ready(fut_result) => fut_result,
92                Poll::Pending => return Err(SelectCertError::RETRY),
93            };
94
95            let finish = fut_result.or(Err(SelectCertError::ERROR))?;
96
97            finish(client_hello).or(Err(SelectCertError::ERROR))
98        });
99    }
100
101    // TODO: cloudflare also has credential version which is fallible here (Result<(), ErrorStack>)
102    // ... What to do with that?
103    /// Configures a custom private key method on the context.
104    ///
105    /// A task waker must be set on `Ssl` values associated with the resulting
106    /// `SslContext` with [`SslRef::set_task_waker`].
107    ///
108    /// See [`AsyncPrivateKeyMethod`] for more details.
109    pub fn set_async_private_key_method(&mut self, method: impl AsyncPrivateKeyMethod) {
110        self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method)));
111    }
112
113    /// Sets a callback that is called when a client proposed to resume a session
114    /// but it was not found in the internal cache.
115    ///
116    /// The callback is passed a reference to the session ID provided by the client.
117    /// It should return the session corresponding to that ID if available. This is
118    /// only used for servers, not clients.
119    ///
120    /// A task waker must be set on `Ssl` values associated with the resulting
121    /// `SslContext` with [`SslRef::set_task_waker`].
122    ///
123    /// See [`SslContextBuilder::set_get_session_callback`] for the sync setter
124    /// of this callback.
125    ///
126    /// # Safety
127    ///
128    /// The returned [`SslSession`] must not be associated with a different [`SslContextBuilder`].
129    ///
130    /// [`SslContext`]: super::SslContext
131    pub unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
132    where
133        F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static,
134    {
135        let async_callback = move |ssl: &mut SslRef, id: &[u8]| {
136            let fut_poll_result = with_ex_data_future(
137                &mut *ssl,
138                *SELECT_GET_SESSION_FUTURE_INDEX,
139                |ssl| ssl,
140                |ssl| callback(ssl, id).ok_or(()),
141                |option| option.ok_or(()),
142            );
143
144            match fut_poll_result {
145                Poll::Ready(Err(())) => Ok(None),
146                Poll::Ready(Ok(finish)) => Ok(finish(ssl, id)),
147                Poll::Pending => Err(GetSessionPendingError),
148            }
149        };
150
151        self.set_get_session_callback(async_callback);
152    }
153
154    /// Configures certificate verification.
155    ///
156    /// The callback should return `Ok(())` if the certificate is valid.
157    /// If the certificate is invalid, the callback should return `SslVerifyError::Invalid(alert)`.
158    /// Some useful alerts include [`SslAlert::CERTIFICATE_EXPIRED`], [`SslAlert::CERTIFICATE_REVOKED`],
159    /// [`SslAlert::UNKNOWN_CA`], [`SslAlert::BAD_CERTIFICATE`], [`SslAlert::CERTIFICATE_UNKNOWN`],
160    /// and [`SslAlert::INTERNAL_ERROR`]. See RFC 5246 section 7.2.2 for their precise meanings.
161    ///
162    /// A task waker must be set on `Ssl` values associated with the resulting
163    /// `SslContext` with [`SslRef::set_task_waker`].
164    ///
165    /// See [`SslContextBuilder::set_custom_verify_callback`] for the sync version of this method.
166    ///
167    /// # Panics
168    ///
169    /// This method panics if this `Ssl` is associated with a RPK context.
170    pub fn set_async_custom_verify_callback<F>(&mut self, mode: SslVerifyMode, callback: F)
171    where
172        F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
173    {
174        self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
175    }
176}
177
178impl SslRef {
179    pub fn set_async_custom_verify_callback<F>(&mut self, mode: SslVerifyMode, callback: F)
180    where
181        F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
182    {
183        self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
184    }
185
186    /// Sets the task waker to be used in async callbacks installed on this `Ssl`.
187    pub fn set_task_waker(&mut self, waker: Option<Waker>) {
188        self.replace_ex_data(*TASK_WAKER_INDEX, waker);
189    }
190}
191
192fn async_custom_verify_callback<F>(
193    callback: F,
194) -> impl Fn(&mut SslRef) -> Result<(), SslVerifyError>
195where
196    F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
197{
198    move |ssl| {
199        let fut_poll_result = with_ex_data_future(
200            &mut *ssl,
201            *SELECT_CUSTOM_VERIFY_FUTURE_INDEX,
202            |ssl| ssl,
203            &callback,
204            identity,
205        );
206
207        match fut_poll_result {
208            Poll::Ready(Err(alert)) => Err(SslVerifyError::Invalid(alert)),
209            Poll::Ready(Ok(finish)) => Ok(finish(ssl).map_err(SslVerifyError::Invalid)?),
210            Poll::Pending => Err(SslVerifyError::Retry),
211        }
212    }
213}
214
215/// A fatal error to be returned from async select certificate callbacks.
216#[derive(Debug, Copy, Clone, PartialEq, Eq)]
217pub struct AsyncSelectCertError;
218
219/// Describes async private key hooks. This is used to off-load signing
220/// operations to a custom, potentially asynchronous, backend. Metadata about the
221/// key such as the type and size are parsed out of the certificate.
222///
223/// See [`PrivateKeyMethod`] for the sync version of those hooks.
224///
225/// [`ssl_private_key_method_st`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_private_key_method_st
226pub trait AsyncPrivateKeyMethod: Send + Sync + 'static {
227    /// Signs the message `input` using the specified signature algorithm.
228    ///
229    /// This method uses a function that returns a future whose output is
230    /// itself a closure that will be passed `ssl` and `output`
231    /// to finish writing the signature.
232    ///
233    /// See [`PrivateKeyMethod::sign`] for the sync version of this method.
234    fn sign(
235        &self,
236        ssl: &mut SslRef,
237        input: &[u8],
238        signature_algorithm: SslSignatureAlgorithm,
239        output: &mut [u8],
240    ) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>;
241
242    /// Decrypts `input`.
243    ///
244    /// This method uses a function that returns a future whose output is
245    /// itself a closure that will be passed `ssl` and `output`
246    /// to finish decrypting the input.
247    ///
248    /// See [`PrivateKeyMethod::decrypt`] for the sync version of this method.
249    fn decrypt(
250        &self,
251        ssl: &mut SslRef,
252        input: &[u8],
253        output: &mut [u8],
254    ) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>;
255}
256
257/// A fatal error to be returned from async private key methods.
258#[derive(Debug, Copy, Clone, PartialEq, Eq)]
259pub struct AsyncPrivateKeyMethodError;
260
261struct AsyncPrivateKeyMethodBridge(Box<dyn AsyncPrivateKeyMethod>);
262
263impl PrivateKeyMethod for AsyncPrivateKeyMethodBridge {
264    fn sign(
265        &self,
266        ssl: &mut SslRef,
267        input: &[u8],
268        signature_algorithm: SslSignatureAlgorithm,
269        output: &mut [u8],
270    ) -> Result<usize, PrivateKeyMethodError> {
271        with_private_key_method(ssl, output, |ssl, output| {
272            <dyn AsyncPrivateKeyMethod>::sign(&*self.0, ssl, input, signature_algorithm, output)
273        })
274    }
275
276    fn decrypt(
277        &self,
278        ssl: &mut SslRef,
279        input: &[u8],
280        output: &mut [u8],
281    ) -> Result<usize, PrivateKeyMethodError> {
282        with_private_key_method(ssl, output, |ssl, output| {
283            <dyn AsyncPrivateKeyMethod>::decrypt(&*self.0, ssl, input, output)
284        })
285    }
286
287    fn complete(
288        &self,
289        ssl: &mut SslRef,
290        output: &mut [u8],
291    ) -> Result<usize, PrivateKeyMethodError> {
292        with_private_key_method(ssl, output, |_, _| {
293            // This should never be reached, if it does, that's a bug on boring's side,
294            // which called `complete` without having been returned to with a pending
295            // future from `sign` or `decrypt`.
296
297            if cfg!(debug_assertions) {
298                panic!("BUG: boring called complete without a pending operation");
299            }
300
301            Err(AsyncPrivateKeyMethodError)
302        })
303    }
304}
305
306/// Creates and drives a private key method future.
307///
308/// This is a convenience function for the three methods of impl `PrivateKeyMethod``
309/// for `dyn AsyncPrivateKeyMethod`. It relies on [`with_ex_data_future`] to
310/// drive the future and then immediately calls the final [`BoxPrivateKeyMethodFinish`]
311/// when the future is ready.
312fn with_private_key_method(
313    ssl: &mut SslRef,
314    output: &mut [u8],
315    create_fut: impl FnOnce(
316        &mut SslRef,
317        &mut [u8],
318    ) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>,
319) -> Result<usize, PrivateKeyMethodError> {
320    let fut_poll_result = with_ex_data_future(
321        ssl,
322        *SELECT_PRIVATE_KEY_METHOD_FUTURE_INDEX,
323        |ssl| ssl,
324        |ssl| create_fut(ssl, output),
325        identity,
326    );
327
328    let fut_result = match fut_poll_result {
329        Poll::Ready(fut_result) => fut_result,
330        Poll::Pending => return Err(PrivateKeyMethodError::RETRY),
331    };
332
333    let finish = fut_result.or(Err(PrivateKeyMethodError::FAILURE))?;
334
335    finish(ssl, output).or(Err(PrivateKeyMethodError::FAILURE))
336}
337
338/// Creates and drives a future stored in `ssl_handle`'s `Ssl` at ex data index `index`.
339///
340/// This function won't even bother storing the future in `index` if the future
341/// created by `create_fut` returns `Poll::Ready(_)` on the first poll call.
342fn with_ex_data_future<H, R, T, E>(
343    ssl_handle: &mut H,
344    index: Index<Ssl, MutOnly<Option<ExDataFuture<R>>>>,
345    get_ssl_mut: impl Fn(&mut H) -> &mut SslRef,
346    create_fut: impl FnOnce(&mut H) -> Result<ExDataFuture<R>, E>,
347    into_result: impl Fn(R) -> Result<T, E>,
348) -> Poll<Result<T, E>> {
349    let ssl = get_ssl_mut(ssl_handle);
350    let waker = ssl
351        .ex_data(*TASK_WAKER_INDEX)
352        .cloned()
353        .flatten()
354        .expect("task waker should be set");
355
356    let mut ctx = Context::from_waker(&waker);
357
358    if let Some(data @ Some(_)) = ssl.ex_data_mut(index).map(MutOnly::get_mut) {
359        let fut_result = into_result(ready!(data.as_mut().unwrap().as_mut().poll(&mut ctx)));
360
361        *data = None;
362
363        Poll::Ready(fut_result)
364    } else {
365        let mut fut = create_fut(ssl_handle)?;
366
367        match fut.as_mut().poll(&mut ctx) {
368            Poll::Ready(fut_result) => Poll::Ready(into_result(fut_result)),
369            Poll::Pending => {
370                get_ssl_mut(ssl_handle).replace_ex_data(index, MutOnly::new(Some(fut)));
371
372                Poll::Pending
373            }
374        }
375    }
376}