tokio_rustls_acme/
config.rs

1use crate::acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY};
2use crate::caches::{BoxedErrCache, CompositeCache, NoCache};
3use crate::{AccountCache, Cache, CertCache};
4use crate::{AcmeState, Incoming};
5use futures::Stream;
6use rustls::{ClientConfig, RootCertStore};
7use std::convert::Infallible;
8use std::fmt::Debug;
9use std::sync::Arc;
10use tokio::io::{AsyncRead, AsyncWrite};
11use webpki_roots::TLS_SERVER_ROOTS;
12
13/// Configuration for an ACME resolver.
14///
15/// The type parameters represent the error types for the certificate cache and account cache.
16pub struct AcmeConfig<EC: Debug, EA: Debug = EC> {
17    pub(crate) client_config: Arc<ClientConfig>,
18    pub(crate) directory_url: String,
19    pub(crate) domains: Vec<String>,
20    pub(crate) contact: Vec<String>,
21    pub(crate) cache: Box<dyn Cache<EC = EC, EA = EA>>,
22}
23
24impl AcmeConfig<Infallible, Infallible> {
25    /// Creates a new [AcmeConfig] instance.
26    ///
27    /// The new [AcmeConfig] instance will initially have no cache, and its type parameters for
28    /// error types will be `Infallible` since the cache cannot return an error. The methods to set
29    /// a cache will change the error types to match those returned by the supplied cache.
30    ///
31    /// ```rust
32    /// # use tokio_rustls_acme::AcmeConfig;
33    /// use tokio_rustls_acme::caches::DirCache;
34    /// let config = AcmeConfig::new(["example.com"]).cache(DirCache::new("./rustls_acme_cache"));
35    /// ```
36    ///
37    /// Due to limited support for type parameter inference in Rust (see
38    /// [RFC213](https://github.com/rust-lang/rfcs/blob/master/text/0213-defaulted-type-params.md)),
39    /// [AcmeConfig::new] is not (yet) generic over the [AcmeConfig]'s type parameters.
40    /// An uncached instance of [AcmeConfig] with particular type parameters can be created using
41    /// [NoCache].
42    ///
43    /// ```rust
44    /// # use tokio_rustls_acme::AcmeConfig;
45    /// use tokio_rustls_acme::caches::NoCache;
46    /// # type EC = std::io::Error;
47    /// # type EA = EC;
48    /// let config: AcmeConfig<EC, EA> = AcmeConfig::new(["example.com"]).cache(NoCache::new());
49    /// ```
50    ///
51    pub fn new(domains: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
52        let mut root_store = RootCertStore::empty();
53        root_store.extend(
54            TLS_SERVER_ROOTS
55                .iter()
56                .map(|ta| rustls::pki_types::TrustAnchor {
57                    subject: ta.subject.clone(),
58                    subject_public_key_info: ta.subject_public_key_info.clone(),
59                    name_constraints: ta.name_constraints.clone(),
60                }),
61        );
62        let client_config = Arc::new(
63            ClientConfig::builder()
64                .with_root_certificates(root_store)
65                .with_no_client_auth(),
66        );
67        AcmeConfig {
68            client_config,
69            directory_url: LETS_ENCRYPT_STAGING_DIRECTORY.into(),
70            domains: domains.into_iter().map(|s| s.as_ref().into()).collect(),
71            contact: vec![],
72            cache: Box::new(NoCache::new()),
73        }
74    }
75}
76
77impl<EC: 'static + Debug, EA: 'static + Debug> AcmeConfig<EC, EA> {
78    /// Set custom `rustls::ClientConfig` for ACME API calls.
79    pub fn client_tls_config(mut self, client_config: Arc<ClientConfig>) -> Self {
80        self.client_config = client_config;
81        self
82    }
83    pub fn directory(mut self, directory_url: impl AsRef<str>) -> Self {
84        self.directory_url = directory_url.as_ref().into();
85        self
86    }
87    pub fn directory_lets_encrypt(mut self, production: bool) -> Self {
88        self.directory_url = match production {
89            true => LETS_ENCRYPT_PRODUCTION_DIRECTORY,
90            false => LETS_ENCRYPT_STAGING_DIRECTORY,
91        }
92        .into();
93        self
94    }
95    pub fn domains(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
96        self.domains = contact.into_iter().map(|s| s.as_ref().into()).collect();
97        self
98    }
99    pub fn domains_push(mut self, contact: impl AsRef<str>) -> Self {
100        self.domains.push(contact.as_ref().into());
101        self
102    }
103
104    /// Provide a list of contacts for the account.
105    ///
106    /// Note that email addresses must include a `mailto:` prefix.
107    pub fn contact(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
108        self.contact = contact.into_iter().map(|s| s.as_ref().into()).collect();
109        self
110    }
111
112    /// Provide a contact for the account.
113    ///
114    /// Note that an email address must include a `mailto:` prefix.
115    pub fn contact_push(mut self, contact: impl AsRef<str>) -> Self {
116        self.contact.push(contact.as_ref().into());
117        self
118    }
119
120    pub fn cache<C: 'static + Cache>(self, cache: C) -> AcmeConfig<C::EC, C::EA> {
121        AcmeConfig {
122            client_config: self.client_config,
123            directory_url: self.directory_url,
124            domains: self.domains,
125            contact: self.contact,
126            cache: Box::new(cache),
127        }
128    }
129    pub fn cache_compose<CC: 'static + CertCache, CA: 'static + AccountCache>(
130        self,
131        cert_cache: CC,
132        account_cache: CA,
133    ) -> AcmeConfig<CC::EC, CA::EA> {
134        self.cache(CompositeCache::new(cert_cache, account_cache))
135    }
136    pub fn cache_with_boxed_err<C: 'static + Cache>(self, cache: C) -> AcmeConfig<Box<dyn Debug>> {
137        self.cache(BoxedErrCache::new(cache))
138    }
139    pub fn cache_option<C: 'static + Cache>(self, cache: Option<C>) -> AcmeConfig<C::EC, C::EA> {
140        match cache {
141            Some(cache) => self.cache(cache),
142            None => self.cache(NoCache::<C::EC, C::EA>::new()),
143        }
144    }
145    pub fn state(self) -> AcmeState<EC, EA> {
146        AcmeState::new(self)
147    }
148    /// Turn a stream of TCP connections into a stream of TLS connections.
149    ///
150    /// Specify supported protocol names in `alpn_protocols`, most preferred first. If emtpy (`Vec::new()`), we don't do ALPN.
151    pub fn incoming<
152        TCP: AsyncRead + AsyncWrite + Unpin,
153        ETCP,
154        ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin,
155    >(
156        self,
157        tcp_incoming: ITCP,
158        alpn_protocols: Vec<Vec<u8>>,
159    ) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
160        self.state().incoming(tcp_incoming, alpn_protocols)
161    }
162}