//! 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>, custom_verifier: Option>, 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>, ) -> 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 } }