tokio-gemini/src/client/builder.rs

152 lines
5 KiB
Rust

//! Builder for Client
use std::sync::Arc;
use crate::{
certs::{verifier::CustomCertVerifier, SelfsignedCertVerifier},
Client,
};
use tokio_rustls::rustls::{self, client::danger::ServerCertVerifier, SupportedProtocolVersion};
#[cfg(feature = "webpki")]
use tokio_rustls::rustls::{client::WebPkiServerVerifier, pki_types::TrustAnchor};
/// Builder for creating configured [`Client`] instance
pub struct ClientBuilder {
root_certs: rustls::RootCertStore,
ss_verifier: Option<Box<dyn SelfsignedCertVerifier>>,
custom_verifier: Option<Arc<dyn ServerCertVerifier + 'static>>,
tls_versions: Option<&'static [&'static SupportedProtocolVersion]>,
}
impl Default for ClientBuilder {
/// Same as [`ClientBuilder::new()`].
fn default() -> Self {
Self::new()
}
}
impl ClientBuilder {
/// Instantiate a builder with empty [`rustls::RootCertStore`],
/// no cert verifiers and default TLS versions.
pub fn new() -> Self {
ClientBuilder {
root_certs: rustls::RootCertStore::empty(),
ss_verifier: None,
custom_verifier: None,
tls_versions: None,
}
}
/// Construct a [`Client`] from the configuration.
pub fn build(self) -> Client {
let provider = rustls::crypto::CryptoProvider::get_default()
.cloned()
.unwrap_or_else(|| Arc::new(rustls::crypto::ring::default_provider()));
let tls_config = rustls::ClientConfig::builder_with_provider(provider.clone())
.with_protocol_versions(if let Some(versions) = self.tls_versions {
versions
} else {
rustls::DEFAULT_VERSIONS
})
.unwrap();
let tls_config = if let Some(cv) = self.custom_verifier {
tls_config.dangerous().with_custom_certificate_verifier(cv)
} else if let Some(ssv) = self.ss_verifier {
let webpki_verifier = {
#[cfg(feature = "webpki")]
if !self.root_certs.is_empty() {
Some(
WebPkiServerVerifier::builder_with_provider(
Arc::new(self.root_certs),
provider.clone(),
)
.build()
// panics only if roots are empty (that is checked above)
// or CRLs couldn't be parsed (we didn't provide any)
.unwrap(),
)
} else {
None
}
#[cfg(not(feature = "webpki"))]
None
};
tls_config
.dangerous()
.with_custom_certificate_verifier(Arc::new(CustomCertVerifier {
provider: provider.clone(),
webpki_verifier,
ss_allowed: true,
ss_verifier: ssv,
}))
} else {
tls_config.with_root_certificates(self.root_certs)
};
// TODO
let tls_config = tls_config.with_no_client_auth();
Client::from(tls_config)
}
/// Limit the supported TLS versions list to the specified ones.
/// Default is [`rustls::DEFAULT_VERSIONS`].
pub fn with_tls_versions(
mut self,
versions: &'static [&'static SupportedProtocolVersion],
) -> Self {
self.tls_versions = Some(versions);
self
}
/// Include webpki trust anchors.
/// Not recommended (useless) as most Gemini capsules use self-signed
/// TLS certs and properly configured TOFU policy is enough.
#[cfg(feature = "webpki")]
pub fn with_webpki_roots(mut self) -> Self {
self.root_certs
.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
self
}
/// Include custom trust anchors.
/// Not recommended (useless), see note for [`ClientBuilder::with_webpki_roots`].
#[cfg(feature = "webpki")]
pub fn with_custom_roots(
mut self,
iter: impl IntoIterator<Item = TrustAnchor<'static>>,
) -> Self {
self.root_certs.extend(iter);
self
}
/// Include a self-signed cert verifier.
/// If you only need a known_hosts file, consider using
/// [`crate::certs::file_sscv::FileBasedCertVerifier`],
/// it will do all the work for you.
pub fn with_selfsigned_cert_verifier(
mut self,
ss_verifier: impl SelfsignedCertVerifier + 'static,
) -> Self {
self.ss_verifier = Some(Box::new(ss_verifier));
self
}
/// Include a custom TLS cert verifier implementing rustls' [`ServerCertVerifier`].
/// Normally need to be used only for [`crate::certs::insecure::AllowAllCertVerifier`].
/// Note: the webpki verifier and a self-signed cert verifier are not called
/// when a custom verifier is set.
pub fn with_custom_verifier(
mut self,
custom_verifier: impl ServerCertVerifier + 'static,
) -> Self {
self.custom_verifier = Some(Arc::new(custom_verifier));
self
}
}