rustls_acme/
config.rs

1use crate::acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY};
2use crate::caches::{BoxedErrCache, CompositeCache, NoCache};
3use crate::UseChallenge::TlsAlpn01;
4use crate::{crypto_provider, AccountCache, Cache, CertCache};
5use crate::{AcmeState, Incoming};
6use core::fmt;
7use futures::{AsyncRead, AsyncWrite, Stream};
8use futures_rustls::pki_types::TrustAnchor;
9use futures_rustls::rustls::crypto::CryptoProvider;
10use futures_rustls::rustls::{ClientConfig, RootCertStore};
11use std::convert::Infallible;
12use std::fmt::Debug;
13use std::sync::Arc;
14
15/// Configuration for an ACME resolver.
16///
17/// The type parameters represent the error types for the certificate cache and account cache.
18pub struct AcmeConfig<EC: Debug, EA: Debug = EC> {
19    pub(crate) client_config: Arc<ClientConfig>,
20    pub(crate) directory_url: String,
21    pub(crate) domains: Vec<String>,
22    pub(crate) contact: Vec<String>,
23    pub(crate) cache: Box<dyn Cache<EC = EC, EA = EA>>,
24    pub(crate) challenge_type: UseChallenge,
25}
26
27pub enum UseChallenge {
28    Http01,
29    TlsAlpn01,
30}
31
32impl AcmeConfig<Infallible, Infallible> {
33    /// Creates a new [AcmeConfig] instance with Web PKI root certificates.
34    ///
35    /// The new [AcmeConfig] instance will initially have no cache, and its type parameters for
36    /// error types will be `Infallible` since the cache cannot return an error. The methods to set
37    /// a cache will change the error types to match those returned by the supplied cache.
38    ///
39    /// ```rust
40    /// # use rustls_acme::AcmeConfig;
41    /// use rustls_acme::caches::DirCache;
42    /// let config = AcmeConfig::new(["example.com"]).cache(DirCache::new("./rustls_acme_cache"));
43    /// ```
44    ///
45    /// Due to limited support for type parameter inference in Rust (see
46    /// [RFC213](https://github.com/rust-lang/rfcs/blob/master/text/0213-defaulted-type-params.md)),
47    /// [AcmeConfig::new] is not (yet) generic over the [AcmeConfig]'s type parameters.
48    /// An uncached instance of [AcmeConfig] with particular type parameters can be created using
49    /// [NoCache].
50    ///
51    /// ```rust
52    /// # use rustls_acme::AcmeConfig;
53    /// use rustls_acme::caches::NoCache;
54    /// # type EC = std::io::Error;
55    /// # type EA = EC;
56    /// let config: AcmeConfig<EC, EA> = AcmeConfig::new(["example.com"]).cache(NoCache::default());
57    /// ```
58    #[cfg(all(feature = "webpki-roots", any(feature = "ring", feature = "aws-lc-rs")))]
59    pub fn new(domains: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
60        Self::new_with_provider(domains, crypto_provider().into())
61    }
62
63    /// Same as [AcmeConfig::new], with a specific [CryptoProvider].
64    #[cfg(feature = "webpki-roots")]
65    pub fn new_with_provider(domains: impl IntoIterator<Item = impl AsRef<str>>, provider: Arc<CryptoProvider>) -> Self {
66        let mut root_store = RootCertStore::empty();
67        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
68            let ta = ta.to_owned();
69            TrustAnchor {
70                subject: ta.subject,
71                subject_public_key_info: ta.subject_public_key_info,
72                name_constraints: ta.name_constraints,
73            }
74        }));
75        let client_config = Arc::new(
76            ClientConfig::builder_with_provider(provider)
77                .with_safe_default_protocol_versions()
78                .unwrap()
79                .with_root_certificates(root_store)
80                .with_no_client_auth(),
81        );
82        AcmeConfig {
83            client_config,
84            directory_url: LETS_ENCRYPT_STAGING_DIRECTORY.into(),
85            domains: domains.into_iter().map(|s| s.as_ref().into()).collect(),
86            contact: vec![],
87            cache: Box::new(NoCache::default()),
88            challenge_type: TlsAlpn01,
89        }
90    }
91
92    /// Creates a new [AcmeConfig] instance with the provided TLS configuration client.
93    ///
94    /// The new [AcmeConfig] instance will initially have no cache, and its type parameters for
95    /// error types will be `Infallible` since the cache cannot return an error. The methods to set
96    /// a cache will change the error types to match those returned by the supplied cache.
97    ///
98    /// ```rust
99    /// # use rustls_acme::AcmeConfig;
100    /// use std::sync::Arc;
101    /// use futures_rustls::rustls::ClientConfig;
102    /// use rustls_acme::caches::DirCache;
103    /// # use futures_rustls::rustls::{crypto::CryptoProvider, RootCertStore};
104    /// # fn call(provider: Arc<CryptoProvider>, root_store: RootCertStore) {
105    /// let client_config = Arc::new(
106    ///     ClientConfig::builder_with_provider(provider)
107    ///         .with_safe_default_protocol_versions()
108    ///         .unwrap()
109    ///         .with_root_certificates(root_store)
110    ///         .with_no_client_auth(),
111    /// );
112    /// let config = AcmeConfig::new_with_client_config(["example.com"], client_config)
113    ///     .cache(DirCache::new("./rustls_acme_cache"));
114    /// # }
115    /// ```
116    ///
117    /// Due to limited support for type parameter inference in Rust (see
118    /// [RFC213](https://github.com/rust-lang/rfcs/blob/master/text/0213-defaulted-type-params.md)),
119    /// [AcmeConfig::new_with_client_config] is not (yet) generic over the [AcmeConfig]'s type parameters.
120    /// An uncached instance of [AcmeConfig] with particular type parameters can be created using
121    /// [NoCache].
122    ///
123    /// ```rust
124    /// # use rustls_acme::AcmeConfig;
125    /// # use std::sync::Arc;
126    /// # use futures_rustls::rustls::ClientConfig;
127    /// use rustls_acme::caches::NoCache;
128    /// # type EC = std::io::Error;
129    /// # type EA = EC;
130    /// # fn call(client_config: Arc<ClientConfig>) {
131    /// let config: AcmeConfig<EC, EA> = AcmeConfig::new_with_client_config(["example.com"], client_config)
132    ///     .cache(NoCache::default());
133    /// # }
134    /// ```
135    pub fn new_with_client_config(domains: impl IntoIterator<Item = impl AsRef<str>>, client_config: Arc<ClientConfig>) -> Self {
136        AcmeConfig {
137            client_config,
138            directory_url: LETS_ENCRYPT_STAGING_DIRECTORY.into(),
139            domains: domains.into_iter().map(|s| s.as_ref().into()).collect(),
140            contact: vec![],
141            cache: Box::new(NoCache::default()),
142            challenge_type: TlsAlpn01,
143        }
144    }
145}
146
147impl<EC: 'static + Debug, EA: 'static + Debug> AcmeConfig<EC, EA> {
148    /// Set custom `rustls::ClientConfig` for ACME API calls.
149    pub fn client_tls_config(mut self, client_config: Arc<ClientConfig>) -> Self {
150        self.client_config = client_config;
151        self
152    }
153    pub fn directory(mut self, directory_url: impl AsRef<str>) -> Self {
154        self.directory_url = directory_url.as_ref().into();
155        self
156    }
157    pub fn directory_lets_encrypt(mut self, production: bool) -> Self {
158        self.directory_url = match production {
159            true => LETS_ENCRYPT_PRODUCTION_DIRECTORY,
160            false => LETS_ENCRYPT_STAGING_DIRECTORY,
161        }
162        .into();
163        self
164    }
165    pub fn domains(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
166        self.domains = contact.into_iter().map(|s| s.as_ref().into()).collect();
167        self
168    }
169    pub fn domains_push(mut self, contact: impl AsRef<str>) -> Self {
170        self.domains.push(contact.as_ref().into());
171        self
172    }
173
174    /// Provide a list of contacts for the account.
175    ///
176    /// Note that email addresses must include a `mailto:` prefix.
177    pub fn contact(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
178        self.contact = contact.into_iter().map(|s| s.as_ref().into()).collect();
179        self
180    }
181
182    /// Provide a contact for the account.
183    ///
184    /// Note that an email address must include a `mailto:` prefix.
185    pub fn contact_push(mut self, contact: impl AsRef<str>) -> Self {
186        self.contact.push(contact.as_ref().into());
187        self
188    }
189
190    pub fn cache<C: 'static + Cache>(self, cache: C) -> AcmeConfig<C::EC, C::EA> {
191        AcmeConfig {
192            client_config: self.client_config,
193            directory_url: self.directory_url,
194            domains: self.domains,
195            contact: self.contact,
196            cache: Box::new(cache),
197            challenge_type: self.challenge_type,
198        }
199    }
200    pub fn cache_compose<CC: 'static + CertCache, CA: 'static + AccountCache>(self, cert_cache: CC, account_cache: CA) -> AcmeConfig<CC::EC, CA::EA> {
201        self.cache(CompositeCache::new(cert_cache, account_cache))
202    }
203    pub fn cache_with_boxed_err<C: 'static + Cache>(self, cache: C) -> AcmeConfig<Box<dyn Debug>> {
204        self.cache(BoxedErrCache::new(cache))
205    }
206    pub fn cache_option<C: 'static + Cache>(self, cache: Option<C>) -> AcmeConfig<C::EC, C::EA> {
207        match cache {
208            Some(cache) => self.cache(cache),
209            None => self.cache(NoCache::<C::EC, C::EA>::default()),
210        }
211    }
212    pub fn challenge_type(mut self, challenge_type: UseChallenge) -> Self {
213        self.challenge_type = challenge_type;
214        self
215    }
216    pub fn state(self) -> AcmeState<EC, EA> {
217        AcmeState::new(self)
218    }
219    /// Turn a stream of TCP connections into a stream of TLS connections.
220    ///
221    /// Specify supported protocol names in `alpn_protocols`, most preferred first. If empty (`Vec::new()`), we don't do ALPN.
222    pub fn incoming<TCP: AsyncRead + AsyncWrite + Unpin, ETCP, ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin>(
223        self,
224        tcp_incoming: ITCP,
225        alpn_protocols: Vec<Vec<u8>>,
226    ) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
227        self.state().incoming(tcp_incoming, alpn_protocols)
228    }
229    #[cfg(feature = "tokio")]
230    /// Tokio compatible wrapper for [Self::incoming].
231    pub fn tokio_incoming<
232        TokioTCP: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
233        ETCP,
234        TokioITCP: Stream<Item = Result<TokioTCP, ETCP>> + Unpin,
235    >(
236        self,
237        tcp_incoming: TokioITCP,
238        alpn_protocols: Vec<Vec<u8>>,
239    ) -> crate::tokio::TokioIncoming<
240        tokio_util::compat::Compat<TokioTCP>,
241        ETCP,
242        crate::tokio::TokioIncomingTcpWrapper<TokioTCP, ETCP, TokioITCP>,
243        EC,
244        EA,
245    > {
246        self.state().tokio_incoming(tcp_incoming, alpn_protocols)
247    }
248}
249
250impl<EC: 'static + Debug, EA: 'static + Debug> fmt::Debug for AcmeConfig<EC, EA> {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        f.debug_struct("AcmeConfig")
253            .field("directory", &self.directory_url)
254            .field("domains", &self.domains)
255            .field("contact", &self.contact)
256            .finish_non_exhaustive()
257    }
258}