quinn_boring/
client.rs

1use crate::alpn::AlpnProtocols;
2use crate::bffi_ext::QuicSslContext;
3use crate::error::{map_result, Result};
4use crate::session_state::{SessionState, QUIC_METHOD};
5use crate::version::QuicVersion;
6use crate::{Entry, KeyLog, NoKeyLog, QuicSsl, QuicSslSession, SessionCache, SimpleCache};
7use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslSession, SslVersion};
8use boring_sys as bffi;
9use bytes::{Bytes, BytesMut};
10use foreign_types_shared::ForeignType;
11use once_cell::sync::Lazy;
12use quinn_proto::{
13    crypto, transport_parameters::TransportParameters, ConnectError, ConnectionId, Side,
14    TransportError,
15};
16use std::any::Any;
17use std::ffi::c_int;
18use std::io::Cursor;
19use std::result::Result as StdResult;
20use std::sync::Arc;
21use tracing::{trace, warn};
22
23/// Configuration for a client-side QUIC. Wraps around a BoringSSL [SslContext].
24pub struct Config {
25    ctx: SslContext,
26    session_cache: Arc<dyn SessionCache>,
27    key_log: Option<Arc<dyn KeyLog>>,
28}
29
30impl Config {
31    pub fn new() -> Result<Self> {
32        let mut builder = SslContextBuilder::new(SslMethod::tls())?;
33
34        // QUIC requires TLS 1.3.
35        builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
36        builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
37
38        builder.set_default_verify_paths()?;
39
40        // We build the context early, since we are not allowed to further mutate the context
41        // in start_session.
42        let mut ctx = builder.build();
43
44        // By default, enable early data (used for 0-RTT).
45        ctx.enable_early_data(true);
46
47        // Set the default ALPN protocols offered by the client. QUIC requires ALPN be configured
48        // (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
49        ctx.set_alpn_protos(&AlpnProtocols::default().encode())?;
50
51        // Configure session caching.
52        ctx.set_session_cache_mode(bffi::SSL_SESS_CACHE_CLIENT | bffi::SSL_SESS_CACHE_NO_INTERNAL);
53        ctx.set_new_session_callback(Some(Session::new_session_callback));
54
55        // Set callbacks for the SessionState.
56        ctx.set_quic_method(&QUIC_METHOD)?;
57        ctx.set_info_callback(Some(SessionState::info_callback));
58
59        // For clients, verification of the server is on by default.
60        ctx.verify_peer(true);
61
62        Ok(Self {
63            ctx,
64            session_cache: Arc::new(SimpleCache::new(256)),
65            key_log: None,
66        })
67    }
68
69    /// Returns the underlying [SslContext] backing all created sessions.
70    pub fn ctx(&self) -> &SslContext {
71        &self.ctx
72    }
73
74    /// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
75    /// the provided methods to modify settings rather than accessing this directly.
76    ///
77    /// Care should be taken to avoid overriding required behavior. In particular, this
78    /// configuration will set callbacks for QUIC events, alpn selection, server name,
79    /// as well as info and key logging.
80    pub fn ctx_mut(&mut self) -> &mut SslContext {
81        &mut self.ctx
82    }
83
84    /// Sets whether or not the peer certificate should be verified. If `true`, any error
85    /// during verification will be fatal. If not called, verification of the server is
86    /// enabled by default.
87    pub fn verify_peer(&mut self, verify: bool) {
88        self.ctx.verify_peer(verify)
89    }
90
91    /// Gets the [SessionCache] used to cache all client sessions.
92    pub fn get_session_cache(&self) -> Arc<dyn SessionCache> {
93        self.session_cache.clone()
94    }
95
96    /// Sets the [SessionCache] to be shared by all created client sessions.
97    pub fn set_session_cache(&mut self, session_cache: Arc<dyn SessionCache>) {
98        self.session_cache = session_cache;
99    }
100
101    /// Sets the ALPN protocols supported by the client. QUIC requires that
102    /// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
103    /// By default, the client will offer "h3".
104    pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
105        self.ctx
106            .set_alpn_protos(&AlpnProtocols::from(alpn_protocols).encode())?;
107        Ok(())
108    }
109
110    /// Sets the [KeyLog] for the client. By default, no key logging will occur.
111    pub fn set_key_log(&mut self, key_log: Option<Arc<dyn KeyLog>>) {
112        self.key_log = key_log;
113
114        // Optimization for key logging. Only set the callback if a logger was supplied,
115        // since the BoringSSL processing isn't free.
116        match &self.key_log {
117            Some(_) => {
118                self.ctx
119                    .set_keylog_callback(Some(SessionState::keylog_callback));
120            }
121            None => {
122                self.ctx.set_keylog_callback(None);
123            }
124        }
125    }
126}
127
128impl crypto::ClientConfig for Config {
129    fn start_session(
130        self: Arc<Self>,
131        version: u32,
132        server_name: &str,
133        params: &TransportParameters,
134    ) -> StdResult<Box<dyn crypto::Session>, ConnectError> {
135        let version = QuicVersion::parse(version).unwrap();
136
137        Ok(Session::new(self, version, server_name, params)
138            .map_err(|_| ConnectError::EndpointStopping)?)
139    }
140}
141
142static SESSION_INDEX: Lazy<c_int> = Lazy::new(|| unsafe {
143    bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
144});
145
146/// The [crypto::Session] implementation for BoringSSL.
147struct Session {
148    state: Box<SessionState>,
149    server_name: Bytes,
150    session_cache: Arc<dyn SessionCache>,
151    zero_rtt_peer_params: Option<TransportParameters>,
152    handshake_data_available: bool,
153    handshake_data_sent: bool,
154}
155
156impl Session {
157    fn new(
158        cfg: Arc<Config>,
159        version: QuicVersion,
160        server_name: &str,
161        params: &TransportParameters,
162    ) -> Result<Box<Self>> {
163        let session_cache = cfg.session_cache.clone();
164        let mut ssl = Ssl::new(&cfg.ctx).unwrap();
165
166        // Configure the TLS extension based on the QUIC version used.
167        ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
168
169        // Configure the SSL to be a client.
170        ssl.set_connect_state();
171
172        // Configure verification for the server hostname.
173        ssl.set_verify_hostname(server_name)
174            .map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
175
176        // Set the SNI hostname.
177        // TODO: should we validate the hostname?
178        ssl.set_hostname(server_name)
179            .map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
180
181        // Set the transport parameters.
182        ssl.set_quic_transport_params(&encode_params(params))
183            .map_err(|_| ConnectError::EndpointStopping)?;
184
185        let server_name_bytes = Bytes::copy_from_slice(server_name.as_bytes());
186
187        // If we have a cached session, use it.
188        let mut zero_rtt_peer_params = None;
189        if let Some(entry) = session_cache.get(server_name_bytes.clone()) {
190            match Entry::decode(ssl.ssl_context(), entry) {
191                Ok(entry) => {
192                    zero_rtt_peer_params = Some(entry.params);
193                    match unsafe { ssl.set_session(entry.session.as_ref()) } {
194                        Ok(()) => {
195                            trace!("attempting resumption (0-RTT) for server: {}.", server_name);
196                        }
197                        Err(e) => {
198                            warn!(
199                                "failed setting cached session for server {}: {:?}",
200                                server_name, e
201                            )
202                        }
203                    }
204                }
205                Err(e) => {
206                    warn!(
207                        "failed decoding session entry for server {}: {:?}",
208                        server_name, e
209                    )
210                }
211            }
212        } else {
213            trace!(
214                "no cached session found for server: {}. Will continue with 1-RTT.",
215                server_name
216            );
217        }
218
219        let mut session = Box::new(Self {
220            state: SessionState::new(
221                ssl,
222                Side::Client,
223                version,
224                cfg.key_log
225                    .as_ref()
226                    .map_or(Arc::new(NoKeyLog), |key_log| key_log.clone()),
227            )?,
228            server_name: server_name_bytes,
229            session_cache,
230            zero_rtt_peer_params,
231            handshake_data_available: false,
232            handshake_data_sent: false,
233        });
234
235        // Register the instance in SSL ex_data. This allows the static callbacks to
236        // reference the instance.
237        unsafe {
238            map_result(bffi::SSL_set_ex_data(
239                session.state.ssl.as_ptr(),
240                *SESSION_INDEX,
241                &mut *session as *mut Self as *mut _,
242            ))?;
243        }
244
245        // Start the handshake in order to emit the Client Hello on the first
246        // call to write_handshake.
247        session.state.advance_handshake()?;
248
249        Ok(session)
250    }
251
252    /// Handler for the rejection of a 0-RTT attempt. Will continue with 1-RTT.
253    fn on_zero_rtt_rejected(&mut self) {
254        trace!(
255            "0-RTT handshake attempted but was rejected by the server: {}",
256            Ssl::early_data_reason_string(self.state.ssl.get_early_data_reason())
257        );
258
259        self.zero_rtt_peer_params = None;
260
261        // Removed the failed cache entry.
262        self.session_cache.remove(self.server_name.clone());
263
264        // Now retry advancing the handshake, this time in 1-RTT mode.
265        if let Err(e) = self.state.advance_handshake() {
266            warn!("failed advancing 1-RTT handshake: {:?}", e)
267        }
268    }
269
270    /// Client-side only callback from BoringSSL to allow caching of a new session.
271    fn on_new_session(&mut self, session: SslSession) {
272        if !session.early_data_capable() {
273            warn!("failed caching session: not early data capable");
274            return;
275        }
276
277        // Get the server transport parameters.
278        let params = match self.state.ssl.get_peer_quic_transport_params() {
279            Some(params) => {
280                match TransportParameters::read(Side::Client, &mut Cursor::new(&params)) {
281                    Ok(params) => params,
282                    Err(e) => {
283                        warn!("failed parsing server transport parameters: {:?}", e);
284                        return;
285                    }
286                }
287            }
288            None => {
289                warn!("failed caching session: server transport parameters are not available");
290                return;
291            }
292        };
293
294        // Encode the session cache entry, including both the session and the server params.
295        let entry = Entry { session, params };
296        match entry.encode() {
297            Ok(value) => {
298                // Cache the session.
299                self.session_cache.put(self.server_name.clone(), value)
300            }
301            Err(e) => {
302                warn!("failed caching session: unable to encode entry: {:?}", e);
303            }
304        }
305    }
306
307    /// Called by the static callbacks to retrieve the instance pointer.
308    #[inline]
309    fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
310        unsafe {
311            let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
312            if data.is_null() {
313                panic!("BUG: Session instance missing")
314            }
315            &mut *(data as *mut Session)
316        }
317    }
318
319    /// Raw callback from BoringSSL.
320    extern "C" fn new_session_callback(
321        ssl: *mut bffi::SSL,
322        session: *mut bffi::SSL_SESSION,
323    ) -> c_int {
324        let inst = Self::get_instance(ssl);
325        let session = unsafe { SslSession::from_ptr(session) };
326        inst.on_new_session(session);
327
328        // Return 1 to indicate we've taken ownership of the session.
329        1
330    }
331}
332
333impl crypto::Session for Session {
334    fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
335        self.state.initial_keys(dcid, side)
336    }
337
338    fn handshake_data(&self) -> Option<Box<dyn Any>> {
339        self.state.handshake_data()
340    }
341
342    fn peer_identity(&self) -> Option<Box<dyn Any>> {
343        self.state.peer_identity()
344    }
345
346    fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
347        self.state.early_crypto()
348    }
349
350    fn early_data_accepted(&self) -> Option<bool> {
351        Some(self.state.ssl.early_data_accepted())
352    }
353
354    fn is_handshaking(&self) -> bool {
355        self.state.is_handshaking()
356    }
357
358    fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
359        self.state.read_handshake(plaintext)?;
360
361        if self.state.early_data_rejected {
362            self.on_zero_rtt_rejected();
363        }
364
365        // Only indicate that handshake data is available once.
366        // On the client side there is no ALPN callback, so we need to manually check
367        // if the ALPN protocol has been selected.
368        if !self.handshake_data_sent {
369            if self.state.ssl.selected_alpn_protocol().is_some() {
370                self.handshake_data_available = true;
371            }
372
373            if self.handshake_data_available {
374                self.handshake_data_sent = true;
375                return Ok(true);
376            }
377        }
378
379        Ok(false)
380    }
381
382    fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
383        match self.state.transport_parameters()? {
384            Some(params) => Ok(Some(params)),
385            None => {
386                if self.state.ssl.in_early_data() {
387                    Ok(self.zero_rtt_peer_params)
388                } else {
389                    Ok(None)
390                }
391            }
392        }
393    }
394
395    fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
396        self.state.write_handshake(buf)
397    }
398
399    fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
400        self.state.next_1rtt_keys()
401    }
402
403    fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
404        self.state.is_valid_retry(orig_dst_cid, header, payload)
405    }
406
407    fn export_keying_material(
408        &self,
409        output: &mut [u8],
410        label: &[u8],
411        context: &[u8],
412    ) -> StdResult<(), crypto::ExportKeyingMaterialError> {
413        self.state.export_keying_material(output, label, context)
414    }
415}
416
417fn encode_params(params: &TransportParameters) -> Bytes {
418    let mut out = BytesMut::with_capacity(128);
419    params.write(&mut out);
420    out.freeze()
421}