use alloc::vec::Vec;
use core::any::Any;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
#[cfg(feature = "webpki")]
use pki_types::PrivateKeyDer;
use pki_types::{FipsStatus, ServerName, UnixTime};
use super::ech::EchMode;
use super::handy::{ClientSessionMemoryCache, FailResolveClientCert, NoClientSessionStorage};
use super::{Tls12Session, Tls13Session};
use crate::builder::{ConfigBuilder, WantsVerifier};
use crate::client::connection::ClientConnectionBuilder;
#[cfg(doc)]
use crate::crypto;
use crate::crypto::kx::NamedGroup;
use crate::crypto::{CipherSuite, CryptoProvider, SelectedCredential, SignatureScheme, hash};
#[cfg(feature = "webpki")]
use crate::crypto::{Credentials, Identity, SingleCredential};
use crate::enums::{ApplicationProtocol, CertificateType, ProtocolVersion};
use crate::error::{ApiMisuse, Error};
use crate::key_log::NoKeyLog;
use crate::suites::SupportedCipherSuite;
use crate::sync::Arc;
use crate::time_provider::{DefaultTimeProvider, TimeProvider};
#[cfg(feature = "webpki")]
use crate::webpki::{self, WebPkiServerVerifier};
use crate::{DistinguishedName, DynHasher, KeyLog, compress, verify};
#[derive(Clone, Debug)]
pub struct ClientConfig {
pub alpn_protocols: Vec<ApplicationProtocol<'static>>,
pub check_selected_alpn: bool,
pub resumption: Resumption,
pub max_fragment_size: Option<usize>,
pub enable_sni: bool,
pub key_log: Arc<dyn KeyLog>,
pub enable_secret_extraction: bool,
pub enable_early_data: bool,
pub require_ems: bool,
pub send_ticket_request: Option<TicketRequest>,
pub(super) domain: SecurityDomain,
pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>,
pub cert_compressors: Vec<&'static dyn compress::CertCompressor>,
pub cert_compression_cache: Arc<compress::CompressionCache>,
pub(super) ech_mode: Option<EchMode>,
}
impl ClientConfig {
pub fn builder(provider: Arc<CryptoProvider>) -> ConfigBuilder<Self, WantsVerifier> {
Self::builder_with_details(provider, Arc::new(DefaultTimeProvider))
}
pub fn builder_with_details(
provider: Arc<CryptoProvider>,
time_provider: Arc<dyn TimeProvider>,
) -> ConfigBuilder<Self, WantsVerifier> {
ConfigBuilder {
state: WantsVerifier {
client_ech_mode: None,
},
provider,
time_provider,
side: PhantomData,
}
}
pub fn connect(self: &Arc<Self>, server_name: ServerName<'static>) -> ClientConnectionBuilder {
ClientConnectionBuilder {
config: self.clone(),
name: server_name,
alpn_protocols: None,
}
}
pub fn dangerous(&mut self) -> danger::DangerousClientConfig<'_> {
danger::DangerousClientConfig { cfg: self }
}
pub fn fips(&self) -> FipsStatus {
if !self.require_ems {
return FipsStatus::Unvalidated;
}
let status = self.domain.provider.fips();
match &self.ech_mode {
Some(ech) => Ord::min(status, ech.fips()),
None => status,
}
}
pub fn provider(&self) -> &Arc<CryptoProvider> {
&self.domain.provider
}
pub fn resolver(&self) -> &Arc<dyn ClientCredentialResolver> {
&self.domain.client_auth_cert_resolver
}
pub fn verifier(&self) -> &Arc<dyn verify::ServerVerifier> {
&self.domain.verifier
}
pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool {
self.domain.provider.supports_version(v)
}
pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option<SupportedCipherSuite> {
self.domain
.provider
.iter_cipher_suites()
.find(|&scs| scs.suite() == suite)
}
pub(super) fn current_time(&self) -> Result<UnixTime, Error> {
self.domain
.time_provider
.current_time()
.ok_or(Error::FailedToGetCurrentTime)
}
pub(super) fn config_hash(&self) -> [u8; 32] {
self.domain.config_hash
}
}
struct HashAdapter<'a>(&'a mut dyn hash::Context);
impl Hasher for HashAdapter<'_> {
fn finish(&self) -> u64 {
u64::from_be_bytes(
self.0.fork_finish().as_ref()[..8]
.try_into()
.unwrap(),
)
}
fn write(&mut self, bytes: &[u8]) {
self.0.update(bytes)
}
}
pub trait ClientSessionStore: fmt::Debug + Send + Sync {
fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup);
fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option<NamedGroup>;
fn set_tls12_session(&self, key: ClientSessionKey<'static>, value: Tls12Session);
fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option<Tls12Session>;
fn remove_tls12_session(&self, key: &ClientSessionKey<'static>);
fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, value: Tls13Session);
fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option<Tls13Session>;
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub struct ClientSessionKey<'a> {
pub config_hash: [u8; 32],
pub server_name: ServerName<'a>,
}
impl ClientSessionKey<'_> {
pub fn to_owned(&self) -> ClientSessionKey<'static> {
let Self {
config_hash,
server_name,
} = self;
ClientSessionKey {
config_hash: *config_hash,
server_name: server_name.to_owned(),
}
}
}
pub trait ClientCredentialResolver: fmt::Debug + Send + Sync {
fn resolve(&self, request: &CredentialRequest<'_>) -> Option<SelectedCredential>;
fn supported_certificate_types(&self) -> &'static [CertificateType];
fn hash_config(&self, h: &mut dyn Hasher);
}
pub struct CredentialRequest<'a> {
pub(super) negotiated_type: CertificateType,
pub(super) root_hint_subjects: &'a [DistinguishedName],
pub(super) signature_schemes: &'a [SignatureScheme],
}
impl CredentialRequest<'_> {
pub fn root_hint_subjects(&self) -> &[DistinguishedName] {
self.root_hint_subjects
}
pub fn signature_schemes(&self) -> &[SignatureScheme] {
self.signature_schemes
}
pub fn negotiated_type(&self) -> CertificateType {
self.negotiated_type
}
}
#[derive(Clone, Debug)]
pub(super) struct SecurityDomain {
time_provider: Arc<dyn TimeProvider>,
provider: Arc<CryptoProvider>,
verifier: Arc<dyn verify::ServerVerifier>,
client_auth_cert_resolver: Arc<dyn ClientCredentialResolver>,
config_hash: [u8; 32],
}
impl SecurityDomain {
pub(crate) fn new(
provider: Arc<CryptoProvider>,
client_auth_cert_resolver: Arc<dyn ClientCredentialResolver + 'static>,
verifier: Arc<dyn verify::ServerVerifier + 'static>,
time_provider: Arc<dyn TimeProvider + 'static>,
) -> Self {
let hash = provider
.iter_cipher_suites()
.map(|cs| cs.hash_provider())
.find(|h| h.output_len() >= 32)
.expect("no suitable cipher suite available (with |H| >= 32)");
let mut h = hash.start();
let mut adapter = HashAdapter(h.as_mut());
client_auth_cert_resolver
.type_id()
.hash(&mut DynHasher(&mut adapter));
client_auth_cert_resolver.hash_config(&mut adapter);
verifier
.type_id()
.hash(&mut DynHasher(&mut adapter));
verifier.hash_config(&mut adapter);
time_provider
.type_id()
.hash(&mut DynHasher(&mut adapter));
let config_hash = h.finish().as_ref()[..32]
.try_into()
.unwrap();
Self {
time_provider,
provider,
verifier,
client_auth_cert_resolver,
config_hash,
}
}
fn with_verifier(&self, verifier: Arc<dyn verify::ServerVerifier + 'static>) -> Self {
let Self {
time_provider,
provider,
verifier: _,
client_auth_cert_resolver,
config_hash: _,
} = self;
Self::new(
provider.clone(),
client_auth_cert_resolver.clone(),
verifier,
time_provider.clone(),
)
}
}
#[derive(Clone, Debug)]
pub struct Resumption {
pub(super) store: Arc<dyn ClientSessionStore>,
pub(super) tls12_resumption: Tls12Resumption,
}
impl Resumption {
pub fn in_memory_sessions(num: usize) -> Self {
Self {
store: Arc::new(ClientSessionMemoryCache::new(num)),
tls12_resumption: Tls12Resumption::SessionIdOrTickets,
}
}
pub fn store(store: Arc<dyn ClientSessionStore>) -> Self {
Self {
store,
tls12_resumption: Tls12Resumption::SessionIdOrTickets,
}
}
pub fn disabled() -> Self {
Self {
store: Arc::new(NoClientSessionStorage),
tls12_resumption: Tls12Resumption::Disabled,
}
}
pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self {
self.tls12_resumption = tls12;
self
}
}
impl Default for Resumption {
fn default() -> Self {
Self::in_memory_sessions(256)
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Tls12Resumption {
Disabled,
SessionIdOnly,
SessionIdOrTickets,
}
#[expect(clippy::exhaustive_structs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketRequest {
pub new_session_count: u8,
pub resumption_count: u8,
}
impl ConfigBuilder<ClientConfig, WantsVerifier> {
#[cfg(feature = "webpki")]
pub fn with_root_certificates(
self,
root_store: impl Into<Arc<webpki::RootCertStore>>,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
let algorithms = self
.provider
.signature_verification_algorithms;
self.with_webpki_verifier(
WebPkiServerVerifier::new_without_revocation(root_store, algorithms).into(),
)
}
#[cfg(feature = "webpki")]
pub fn with_webpki_verifier(
self,
verifier: Arc<WebPkiServerVerifier>,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
ConfigBuilder {
state: WantsClientCert {
verifier,
client_ech_mode: self.state.client_ech_mode,
},
provider: self.provider,
time_provider: self.time_provider,
side: PhantomData,
}
}
pub fn with_ech(mut self, mode: EchMode) -> Self {
self.state.client_ech_mode = Some(mode);
self
}
pub fn dangerous(self) -> danger::DangerousClientConfigBuilder {
danger::DangerousClientConfigBuilder { cfg: self }
}
}
#[derive(Clone)]
pub struct WantsClientCert {
verifier: Arc<dyn verify::ServerVerifier>,
client_ech_mode: Option<EchMode>,
}
impl ConfigBuilder<ClientConfig, WantsClientCert> {
#[cfg(feature = "webpki")]
pub fn with_client_auth_cert(
self,
identity: Arc<Identity<'static>>,
key_der: PrivateKeyDer<'static>,
) -> Result<ClientConfig, Error> {
let credentials = Credentials::from_der(identity, key_der, &self.provider)?;
self.with_client_credential_resolver(Arc::new(SingleCredential::from(credentials)))
}
pub fn with_no_client_auth(self) -> Result<ClientConfig, Error> {
self.with_client_credential_resolver(Arc::new(FailResolveClientCert {}))
}
pub fn with_client_credential_resolver(
self,
client_auth_cert_resolver: Arc<dyn ClientCredentialResolver>,
) -> Result<ClientConfig, Error> {
self.provider.consistency_check()?;
if self.state.client_ech_mode.is_some() {
match (
self.provider
.tls12_cipher_suites
.is_empty(),
self.provider
.tls13_cipher_suites
.is_empty(),
) {
(_, true) => return Err(ApiMisuse::EchRequiresTls13Support.into()),
(false, _) => return Err(ApiMisuse::EchForbidsTls12Support.into()),
(true, false) => {}
};
}
let require_ems = !matches!(self.provider.fips(), FipsStatus::Unvalidated);
Ok(ClientConfig {
alpn_protocols: Vec::new(),
check_selected_alpn: true,
resumption: Resumption::default(),
max_fragment_size: None,
enable_sni: true,
key_log: Arc::new(NoKeyLog {}),
enable_secret_extraction: false,
enable_early_data: false,
require_ems,
send_ticket_request: None,
domain: SecurityDomain::new(
self.provider,
client_auth_cert_resolver,
self.state.verifier,
self.time_provider,
),
cert_decompressors: compress::default_cert_decompressors().to_vec(),
cert_compressors: compress::default_cert_compressors().to_vec(),
cert_compression_cache: Arc::new(compress::CompressionCache::default()),
ech_mode: self.state.client_ech_mode,
})
}
}
pub(super) mod danger {
use core::marker::PhantomData;
use crate::client::WantsClientCert;
use crate::client::config::ClientConfig;
use crate::sync::Arc;
use crate::verify::ServerVerifier;
use crate::{ConfigBuilder, WantsVerifier};
#[derive(Debug)]
pub struct DangerousClientConfig<'a> {
pub(super) cfg: &'a mut ClientConfig,
}
impl DangerousClientConfig<'_> {
pub fn set_certificate_verifier(&mut self, verifier: Arc<dyn ServerVerifier>) {
self.cfg.domain = self.cfg.domain.with_verifier(verifier);
}
}
#[derive(Debug)]
pub struct DangerousClientConfigBuilder {
pub(super) cfg: ConfigBuilder<ClientConfig, WantsVerifier>,
}
impl DangerousClientConfigBuilder {
pub fn with_custom_certificate_verifier(
self,
verifier: Arc<dyn ServerVerifier>,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
ConfigBuilder {
state: WantsClientCert {
verifier,
client_ech_mode: self.cfg.state.client_ech_mode,
},
provider: self.cfg.provider,
time_provider: self.cfg.time_provider,
side: PhantomData,
}
}
}
}