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
23pub 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 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 let mut ctx = builder.build();
43
44 ctx.enable_early_data(true);
46
47 ctx.set_alpn_protos(&AlpnProtocols::default().encode())?;
50
51 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 ctx.set_quic_method(&QUIC_METHOD)?;
57 ctx.set_info_callback(Some(SessionState::info_callback));
58
59 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 pub fn ctx(&self) -> &SslContext {
71 &self.ctx
72 }
73
74 pub fn ctx_mut(&mut self) -> &mut SslContext {
81 &mut self.ctx
82 }
83
84 pub fn verify_peer(&mut self, verify: bool) {
88 self.ctx.verify_peer(verify)
89 }
90
91 pub fn get_session_cache(&self) -> Arc<dyn SessionCache> {
93 self.session_cache.clone()
94 }
95
96 pub fn set_session_cache(&mut self, session_cache: Arc<dyn SessionCache>) {
98 self.session_cache = session_cache;
99 }
100
101 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 pub fn set_key_log(&mut self, key_log: Option<Arc<dyn KeyLog>>) {
112 self.key_log = key_log;
113
114 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
146struct 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 ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
168
169 ssl.set_connect_state();
171
172 ssl.set_verify_hostname(server_name)
174 .map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
175
176 ssl.set_hostname(server_name)
179 .map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
180
181 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 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 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 session.state.advance_handshake()?;
248
249 Ok(session)
250 }
251
252 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 self.session_cache.remove(self.server_name.clone());
263
264 if let Err(e) = self.state.advance_handshake() {
266 warn!("failed advancing 1-RTT handshake: {:?}", e)
267 }
268 }
269
270 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 let params = match self.state.ssl.get_peer_quic_transport_params() {
279 Some(params) => {
280 match TransportParameters::read(Side::Client, &mut Cursor::new(¶ms)) {
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 let entry = Entry { session, params };
296 match entry.encode() {
297 Ok(value) => {
298 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 #[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 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 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 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}