use crate::acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY};
use crate::caches::{BoxedErrCache, CompositeCache, NoCache};
use crate::{AccountCache, Cache, CertCache};
use crate::{AcmeState, Incoming};
use futures::Stream;
use rustls::{ClientConfig, RootCertStore};
use std::convert::Infallible;
use std::fmt::Debug;
use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncWrite};
use webpki_roots::TLS_SERVER_ROOTS;
pub struct AcmeConfig<EC: Debug, EA: Debug = EC> {
pub(crate) client_config: Arc<ClientConfig>,
pub(crate) directory_url: String,
pub(crate) domains: Vec<String>,
pub(crate) contact: Vec<String>,
pub(crate) cache: Box<dyn Cache<EC = EC, EA = EA>>,
}
impl AcmeConfig<Infallible, Infallible> {
pub fn new(domains: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
let mut root_store = RootCertStore::empty();
root_store.extend(
TLS_SERVER_ROOTS
.iter()
.map(|ta| rustls::pki_types::TrustAnchor {
subject: ta.subject.clone(),
subject_public_key_info: ta.subject_public_key_info.clone(),
name_constraints: ta.name_constraints.clone(),
}),
);
let client_config = Arc::new(
ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth(),
);
AcmeConfig {
client_config,
directory_url: LETS_ENCRYPT_STAGING_DIRECTORY.into(),
domains: domains.into_iter().map(|s| s.as_ref().into()).collect(),
contact: vec![],
cache: Box::new(NoCache::new()),
}
}
}
impl<EC: 'static + Debug, EA: 'static + Debug> AcmeConfig<EC, EA> {
pub fn client_tls_config(mut self, client_config: Arc<ClientConfig>) -> Self {
self.client_config = client_config;
self
}
pub fn directory(mut self, directory_url: impl AsRef<str>) -> Self {
self.directory_url = directory_url.as_ref().into();
self
}
pub fn directory_lets_encrypt(mut self, production: bool) -> Self {
self.directory_url = match production {
true => LETS_ENCRYPT_PRODUCTION_DIRECTORY,
false => LETS_ENCRYPT_STAGING_DIRECTORY,
}
.into();
self
}
pub fn domains(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
self.domains = contact.into_iter().map(|s| s.as_ref().into()).collect();
self
}
pub fn domains_push(mut self, contact: impl AsRef<str>) -> Self {
self.domains.push(contact.as_ref().into());
self
}
pub fn contact(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
self.contact = contact.into_iter().map(|s| s.as_ref().into()).collect();
self
}
pub fn contact_push(mut self, contact: impl AsRef<str>) -> Self {
self.contact.push(contact.as_ref().into());
self
}
pub fn cache<C: 'static + Cache>(self, cache: C) -> AcmeConfig<C::EC, C::EA> {
AcmeConfig {
client_config: self.client_config,
directory_url: self.directory_url,
domains: self.domains,
contact: self.contact,
cache: Box::new(cache),
}
}
pub fn cache_compose<CC: 'static + CertCache, CA: 'static + AccountCache>(
self,
cert_cache: CC,
account_cache: CA,
) -> AcmeConfig<CC::EC, CA::EA> {
self.cache(CompositeCache::new(cert_cache, account_cache))
}
pub fn cache_with_boxed_err<C: 'static + Cache>(self, cache: C) -> AcmeConfig<Box<dyn Debug>> {
self.cache(BoxedErrCache::new(cache))
}
pub fn cache_option<C: 'static + Cache>(self, cache: Option<C>) -> AcmeConfig<C::EC, C::EA> {
match cache {
Some(cache) => self.cache(cache),
None => self.cache(NoCache::<C::EC, C::EA>::new()),
}
}
pub fn state(self) -> AcmeState<EC, EA> {
AcmeState::new(self)
}
pub fn incoming<
TCP: AsyncRead + AsyncWrite + Unpin,
ETCP,
ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin,
>(
self,
tcp_incoming: ITCP,
alpn_protocols: Vec<Vec<u8>>,
) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
self.state().incoming(tcp_incoming, alpn_protocols)
}
}