use crate::alpn::AlpnProtocols;
use crate::bffi_ext::QuicSslContext;
use crate::error::{map_result, Result};
use crate::session_state::{SessionState, QUIC_METHOD};
use crate::version::QuicVersion;
use crate::{Entry, KeyLog, NoKeyLog, QuicSsl, QuicSslSession, SessionCache, SimpleCache};
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslSession, SslVersion};
use boring_sys as bffi;
use bytes::{Bytes, BytesMut};
use foreign_types_shared::ForeignType;
use once_cell::sync::Lazy;
use quinn_proto::{
crypto, transport_parameters::TransportParameters, ConnectError, ConnectionId, Side,
TransportError,
};
use std::any::Any;
use std::ffi::c_int;
use std::io::Cursor;
use std::result::Result as StdResult;
use std::sync::Arc;
use tracing::{trace, warn};
pub struct Config {
ctx: SslContext,
session_cache: Arc<dyn SessionCache>,
key_log: Option<Arc<dyn KeyLog>>,
}
impl Config {
pub fn new() -> Result<Self> {
let mut builder = SslContextBuilder::new(SslMethod::tls())?;
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_default_verify_paths()?;
let mut ctx = builder.build();
ctx.enable_early_data(true);
ctx.set_alpn_protos(&AlpnProtocols::default().encode())?;
ctx.set_session_cache_mode(bffi::SSL_SESS_CACHE_CLIENT | bffi::SSL_SESS_CACHE_NO_INTERNAL);
ctx.set_new_session_callback(Some(Session::new_session_callback));
ctx.set_quic_method(&QUIC_METHOD)?;
ctx.set_info_callback(Some(SessionState::info_callback));
ctx.verify_peer(true);
Ok(Self {
ctx,
session_cache: Arc::new(SimpleCache::new(256)),
key_log: None,
})
}
pub fn ctx(&self) -> &SslContext {
&self.ctx
}
pub fn ctx_mut(&mut self) -> &mut SslContext {
&mut self.ctx
}
pub fn verify_peer(&mut self, verify: bool) {
self.ctx.verify_peer(verify)
}
pub fn get_session_cache(&self) -> Arc<dyn SessionCache> {
self.session_cache.clone()
}
pub fn set_session_cache(&mut self, session_cache: Arc<dyn SessionCache>) {
self.session_cache = session_cache;
}
pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
self.ctx
.set_alpn_protos(&AlpnProtocols::from(alpn_protocols).encode())?;
Ok(())
}
pub fn set_key_log(&mut self, key_log: Option<Arc<dyn KeyLog>>) {
self.key_log = key_log;
match &self.key_log {
Some(_) => {
self.ctx
.set_keylog_callback(Some(SessionState::keylog_callback));
}
None => {
self.ctx.set_keylog_callback(None);
}
}
}
}
impl crypto::ClientConfig for Config {
fn start_session(
self: Arc<Self>,
version: u32,
server_name: &str,
params: &TransportParameters,
) -> StdResult<Box<dyn crypto::Session>, ConnectError> {
let version = QuicVersion::parse(version).unwrap();
Ok(Session::new(self, version, server_name, params)
.map_err(|_| ConnectError::EndpointStopping)?)
}
}
static SESSION_INDEX: Lazy<c_int> = Lazy::new(|| unsafe {
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
});
struct Session {
state: Box<SessionState>,
server_name: Bytes,
session_cache: Arc<dyn SessionCache>,
zero_rtt_peer_params: Option<TransportParameters>,
handshake_data_available: bool,
handshake_data_sent: bool,
}
impl Session {
fn new(
cfg: Arc<Config>,
version: QuicVersion,
server_name: &str,
params: &TransportParameters,
) -> Result<Box<Self>> {
let session_cache = cfg.session_cache.clone();
let mut ssl = Ssl::new(&cfg.ctx).unwrap();
ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
ssl.set_connect_state();
ssl.set_verify_hostname(server_name)
.map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
ssl.set_hostname(server_name)
.map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?;
ssl.set_quic_transport_params(&encode_params(params))
.map_err(|_| ConnectError::EndpointStopping)?;
let server_name_bytes = Bytes::copy_from_slice(server_name.as_bytes());
let mut zero_rtt_peer_params = None;
if let Some(entry) = session_cache.get(server_name_bytes.clone()) {
match Entry::decode(ssl.ssl_context(), entry) {
Ok(entry) => {
zero_rtt_peer_params = Some(entry.params);
match unsafe { ssl.set_session(entry.session.as_ref()) } {
Ok(()) => {
trace!("attempting resumption (0-RTT) for server: {}.", server_name);
}
Err(e) => {
warn!(
"failed setting cached session for server {}: {:?}",
server_name, e
)
}
}
}
Err(e) => {
warn!(
"failed decoding session entry for server {}: {:?}",
server_name, e
)
}
}
} else {
trace!(
"no cached session found for server: {}. Will continue with 1-RTT.",
server_name
);
}
let mut session = Box::new(Self {
state: SessionState::new(
ssl,
Side::Client,
version,
cfg.key_log
.as_ref()
.map_or(Arc::new(NoKeyLog), |key_log| key_log.clone()),
)?,
server_name: server_name_bytes,
session_cache,
zero_rtt_peer_params,
handshake_data_available: false,
handshake_data_sent: false,
});
unsafe {
map_result(bffi::SSL_set_ex_data(
session.state.ssl.as_ptr(),
*SESSION_INDEX,
&mut *session as *mut Self as *mut _,
))?;
}
session.state.advance_handshake()?;
Ok(session)
}
fn on_zero_rtt_rejected(&mut self) {
trace!(
"0-RTT handshake attempted but was rejected by the server: {}",
Ssl::early_data_reason_string(self.state.ssl.get_early_data_reason())
);
self.zero_rtt_peer_params = None;
self.session_cache.remove(self.server_name.clone());
if let Err(e) = self.state.advance_handshake() {
warn!("failed advancing 1-RTT handshake: {:?}", e)
}
}
fn on_new_session(&mut self, session: SslSession) {
if !session.early_data_capable() {
warn!("failed caching session: not early data capable");
return;
}
let params = match self.state.ssl.get_peer_quic_transport_params() {
Some(params) => {
match TransportParameters::read(Side::Client, &mut Cursor::new(¶ms)) {
Ok(params) => params,
Err(e) => {
warn!("failed parsing server transport parameters: {:?}", e);
return;
}
}
}
None => {
warn!("failed caching session: server transport parameters are not available");
return;
}
};
let entry = Entry { session, params };
match entry.encode() {
Ok(value) => {
self.session_cache.put(self.server_name.clone(), value)
}
Err(e) => {
warn!("failed caching session: unable to encode entry: {:?}", e);
}
}
}
#[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 new_session_callback(
ssl: *mut bffi::SSL,
session: *mut bffi::SSL_SESSION,
) -> c_int {
let inst = Self::get_instance(ssl);
let session = unsafe { SslSession::from_ptr(session) };
inst.on_new_session(session);
1
}
}
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<Box<dyn Any>> {
self.state.handshake_data()
}
fn peer_identity(&self) -> Option<Box<dyn Any>> {
self.state.peer_identity()
}
fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
self.state.early_crypto()
}
fn early_data_accepted(&self) -> Option<bool> {
Some(self.state.ssl.early_data_accepted())
}
fn is_handshaking(&self) -> bool {
self.state.is_handshaking()
}
fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
self.state.read_handshake(plaintext)?;
if self.state.early_data_rejected {
self.on_zero_rtt_rejected();
}
if !self.handshake_data_sent {
if self.state.ssl.selected_alpn_protocol().is_some() {
self.handshake_data_available = true;
}
if self.handshake_data_available {
self.handshake_data_sent = true;
return Ok(true);
}
}
Ok(false)
}
fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
match self.state.transport_parameters()? {
Some(params) => Ok(Some(params)),
None => {
if self.state.ssl.in_early_data() {
Ok(self.zero_rtt_peer_params)
} else {
Ok(None)
}
}
}
}
fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
self.state.write_handshake(buf)
}
fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
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()
}