Skip to main content

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