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