use crate::{
certificate::{IntoCertificate, IntoPrivateKey},
keylog::KeyLogHandle,
params::Params,
session::Session,
ConfigLoader,
};
use s2n_codec::EncoderValue;
use s2n_quic_core::{application::ServerName, crypto::tls, endpoint};
use s2n_tls::{
callbacks::VerifyHostNameCallback,
config::{self, Config},
enums::ClientAuthType,
error::Error,
};
use std::sync::Arc;
pub struct Client<L: ConfigLoader = Config> {
loader: L,
#[allow(dead_code)] keylog: Option<KeyLogHandle>,
params: Params,
}
impl Client {
pub fn builder() -> Builder {
Builder::default()
}
}
impl<L: ConfigLoader> Client<L> {
pub fn from_loader(loader: L) -> Self {
Self {
loader,
keylog: None,
params: Default::default(),
}
}
}
impl Default for Client {
fn default() -> Self {
Self::builder()
.build()
.expect("could not create a default client")
}
}
impl<L: ConfigLoader> ConfigLoader for Client<L> {
#[inline]
fn load(&mut self, cx: crate::ConnectionContext) -> s2n_tls::config::Config {
self.loader.load(cx)
}
}
pub struct Builder {
config: config::Builder,
keylog: Option<KeyLogHandle>,
}
impl Default for Builder {
fn default() -> Self {
let mut config = config::Builder::default();
config.enable_quic().unwrap();
config.set_security_policy(crate::DEFAULT_POLICY).unwrap();
config.set_application_protocol_preference([b"h3"]).unwrap();
Self {
config,
keylog: None,
}
}
}
impl Builder {
pub fn config_mut(&mut self) -> &mut s2n_tls::config::Builder {
&mut self.config
}
pub fn with_application_protocols<P: IntoIterator<Item = I>, I: AsRef<[u8]>>(
mut self,
protocols: P,
) -> Result<Self, Error> {
self.config.set_application_protocol_preference(protocols)?;
Ok(self)
}
pub fn with_certificate<C: IntoCertificate>(mut self, certificate: C) -> Result<Self, Error> {
let certificate = certificate.into_certificate()?;
let certificate = certificate
.0
.as_pem()
.expect("pem is currently the only certificate format supported");
self.config.trust_pem(certificate)?;
Ok(self)
}
pub fn with_empty_trust_store(mut self) -> Result<Self, Error> {
self.config.wipe_trust_store()?;
Ok(self)
}
pub fn with_client_identity<C: IntoCertificate, PK: IntoPrivateKey>(
mut self,
certificate: C,
private_key: PK,
) -> Result<Self, Error> {
let certificate = certificate.into_certificate()?;
let private_key = private_key.into_private_key()?;
self.config.load_pem(
certificate
.0
.as_pem()
.expect("pem is currently the only certificate format supported"),
private_key
.0
.as_pem()
.expect("pem is currently the only certificate format supported"),
)?;
self.config.set_client_auth_type(ClientAuthType::Required)?;
Ok(self)
}
pub fn with_verify_host_name_callback<T: 'static + VerifyHostNameCallback>(
mut self,
handler: T,
) -> Result<Self, Error> {
self.config.set_verify_host_callback(handler)?;
Ok(self)
}
pub fn with_max_cert_chain_depth(mut self, len: u16) -> Result<Self, Error> {
self.config.set_max_cert_chain_depth(len)?;
Ok(self)
}
pub fn with_key_logging(mut self) -> Result<Self, Error> {
use crate::keylog::KeyLog;
self.keylog = KeyLog::try_open();
unsafe {
if let Some(keylog) = self.keylog.as_ref() {
self.config
.set_key_log_callback(Some(KeyLog::callback), Arc::as_ptr(keylog) as *mut _)?;
} else {
self.config
.set_key_log_callback(None, core::ptr::null_mut())?;
}
}
Ok(self)
}
pub fn build(self) -> Result<Client, Error> {
#[cfg(feature = "fips")]
assert!(s2n_tls::init::fips_mode()?.is_enabled());
Ok(Client {
loader: self.config.build()?,
keylog: self.keylog,
params: Default::default(),
})
}
}
impl<L: ConfigLoader> tls::Endpoint for Client<L> {
type Session = Session;
fn new_server_session<Params: EncoderValue>(&mut self, _params: &Params) -> Self::Session {
panic!("cannot create a server session from a client config");
}
fn new_client_session<Params: EncoderValue>(
&mut self,
params: &Params,
server_name: ServerName,
) -> Self::Session {
let config = self.loader.load(crate::ConnectionContext {
server_name: Some(&server_name),
});
self.params.with(params, |params| {
Session::new(endpoint::Type::Client, config, params, Some(server_name)).unwrap()
})
}
fn max_tag_length(&self) -> usize {
s2n_quic_crypto::MAX_TAG_LEN
}
}