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