use crate::alpn::AlpnProtocols; use crate::bffi_ext::QuicSsl; use crate::error::{map_result, Result}; use crate::secret::Secrets; use crate::session_state::{SessionState, QUIC_METHOD}; use crate::version::QuicVersion; use crate::{retry, QuicSslContext}; use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslVersion}; use boring_sys as bffi; use bytes::{Bytes, BytesMut}; use foreign_types_shared::ForeignType; use quinn_proto::{ crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError, }; use std::any::Any; use std::ffi::{c_int, c_uint, c_void}; use std::result::Result as StdResult; use std::slice; use std::sync::Arc; use std::sync::LazyLock; /// Configuration for a server-side QUIC. Wraps around a BoringSSL [SslContext]. pub struct Config { ctx: SslContext, alpn_protocols: AlpnProtocols, } impl Config { pub fn new() -> Result { let mut builder = SslContextBuilder::new(SslMethod::tls())?; // QUIC requires TLS 1.3. builder.set_min_proto_version(Some(SslVersion::TLS1_3))?; builder.set_max_proto_version(Some(SslVersion::TLS1_3))?; builder.set_default_verify_paths()?; // We build the context early, since we are not allowed to further mutate the context // in start_session. let mut ctx = builder.build(); // Disable verification of the client by default. ctx.verify_peer(false); // By default, enable early data (used for 0-RTT). ctx.enable_early_data(true); // Configure default ALPN protocols accepted by the server.QUIC requires ALPN be // configured (see https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1). ctx.set_alpn_select_cb(Some(Session::alpn_select_callback)); // Set the callback for receipt of the Server Name Indication (SNI) extension. ctx.set_server_name_cb(Some(Session::server_name_callback)); // Set callbacks for the SessionState. ctx.set_quic_method(&QUIC_METHOD)?; ctx.set_info_callback(Some(SessionState::info_callback)); ctx.set_options(bffi::SSL_OP_CIPHER_SERVER_PREFERENCE as u32); Ok(Self { ctx, alpn_protocols: AlpnProtocols::default(), }) } /// Returns the underlying [SslContext] backing all created sessions. pub fn ctx(&self) -> &SslContext { &self.ctx } /// Returns the underlying [SslContext] backing all created sessions. Wherever possible use /// the provided methods to modify settings rather than accessing this directly. /// /// Care should be taken to avoid overriding required behavior. In particular, this /// configuration will set callbacks for QUIC events, alpn selection, server name, /// as well as info and key logging. pub fn ctx_mut(&mut self) -> &mut SslContext { &mut self.ctx } /// Sets whether or not the peer certificate should be verified. If `true`, any error /// during verification will be fatal. If not called, verification of the client is /// disabled by default. pub fn verify_peer(&mut self, verify: bool) { self.ctx.verify_peer(verify) } /// Sets the ALPN protocols that will be accepted by the server. QUIC requires that /// ALPN be used (see ). /// /// If this method is not called, the server will default to accepting "h3". pub fn set_alpn(&mut self, alpn_protocols: &[Vec]) -> Result<()> { self.alpn_protocols = alpn_protocols.into(); Ok(()) } } impl crypto::ServerConfig for Config { fn initial_keys( &self, version: u32, dst_cid: &ConnectionId, ) -> StdResult { let version = QuicVersion::parse(version)?; let secrets = Secrets::initial(version, dst_cid, Side::Server).unwrap(); Ok(secrets.keys().unwrap().as_crypto().unwrap()) } fn retry_tag(&self, version: u32, orig_dst_cid: &ConnectionId, packet: &[u8]) -> [u8; 16] { let version = QuicVersion::parse(version).unwrap(); retry::retry_tag(&version, orig_dst_cid, packet) } fn start_session( self: Arc, version: u32, params: &TransportParameters, ) -> Box { let version = QuicVersion::parse(version).unwrap(); Session::new(self, version, params).unwrap() } } static SESSION_INDEX: LazyLock = LazyLock::new(|| unsafe { bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None) }); /// The [crypto::Session] implementation for BoringSSL. struct Session { state: Box, alpn: AlpnProtocols, handshake_data_available: bool, handshake_data_sent: bool, } impl Session { fn new( cfg: Arc, version: QuicVersion, params: &TransportParameters, ) -> Result> { let mut ssl = Ssl::new(&cfg.ctx).unwrap(); // Configure the TLS extension based on the QUIC version used. ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension()); // Configure the SSL to be a server. ssl.set_accept_state(); // Set the transport parameters. ssl.set_quic_transport_params(&encode_params(params)) .unwrap(); // Need to se ssl.set_quic_early_data_context(b"quinn-boring").unwrap(); let mut session = Box::new(Self { state: SessionState::new(ssl, Side::Server, version)?, alpn: cfg.alpn_protocols.clone(), handshake_data_available: false, handshake_data_sent: false, }); // Register the instance in SSL ex_data. This allows the static callbacks to // reference the instance. unsafe { map_result(bffi::SSL_set_ex_data( session.state.ssl.as_ptr(), *SESSION_INDEX, &mut *session as *mut Self as *mut _, ))?; } Ok(session) } /// Server-side only callback from BoringSSL to select the ALPN protocol. #[inline] fn on_alpn_select<'a>(&mut self, offered: &'a [u8]) -> Result<&'a [u8]> { // Indicate that we now have handshake data available. self.handshake_data_available = true; self.alpn.select(offered) } /// Server-side only callback from BoringSSL indicating that the Server Name Indication (SNI) /// extension in the client hello was successfully parsed. #[inline] fn on_server_name(&mut self, _: *mut c_int) -> c_int { // Indicate that we now have handshake data available. self.handshake_data_available = true; // SSL_TLSEXT_ERR_OK causes the server_name extension to be acked in // ServerHello. bffi::SSL_TLSEXT_ERR_OK } } // Raw callbacks from BoringSSL impl Session { #[inline] fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session { unsafe { let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX); if data.is_null() { panic!("BUG: Session instance missing") } &mut *(data as *mut Session) } } extern "C" fn alpn_select_callback( ssl: *mut bffi::SSL, out: *mut *const u8, out_len: *mut u8, in_: *const u8, in_len: c_uint, _: *mut c_void, ) -> c_int { let inst = Self::get_instance(ssl); unsafe { let protos = slice::from_raw_parts(in_, in_len as _); match inst.on_alpn_select(protos) { Ok(proto) => { *out = proto.as_ptr() as _; *out_len = proto.len() as _; bffi::SSL_TLSEXT_ERR_OK } Err(_) => bffi::SSL_TLSEXT_ERR_ALERT_FATAL, } } } extern "C" fn server_name_callback( ssl: *mut bffi::SSL, out_alert: *mut c_int, _: *mut c_void, ) -> c_int { let inst = Self::get_instance(ssl); inst.on_server_name(out_alert) } } impl crypto::Session for Session { fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys { self.state.initial_keys(dcid, side) } fn handshake_data(&self) -> Option> { self.state.handshake_data() } fn peer_identity(&self) -> Option> { self.state.peer_identity() } fn early_crypto(&self) -> Option<(Box, Box)> { self.state.early_crypto() } fn early_data_accepted(&self) -> Option { None } fn is_handshaking(&self) -> bool { self.state.is_handshaking() } fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult { self.state.read_handshake(plaintext)?; // Only indicate that handshake data is available once. if !self.handshake_data_sent && self.handshake_data_available { self.handshake_data_sent = true; return Ok(true); } Ok(false) } fn transport_parameters(&self) -> StdResult, TransportError> { self.state.transport_parameters() } fn write_handshake(&mut self, buf: &mut Vec) -> Option { self.state.write_handshake(buf) } fn next_1rtt_keys(&mut self) -> Option>> { self.state.next_1rtt_keys() } fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool { self.state.is_valid_retry(orig_dst_cid, header, payload) } fn export_keying_material( &self, output: &mut [u8], label: &[u8], context: &[u8], ) -> StdResult<(), crypto::ExportKeyingMaterialError> { self.state.export_keying_material(output, label, context) } } fn encode_params(params: &TransportParameters) -> Bytes { let mut out = BytesMut::with_capacity(128); params.write(&mut out); out.freeze() }