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;
14use webpki_roots::TLS_SERVER_ROOTS;
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.
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 rustls_acme::AcmeConfig;
42    /// use rustls_acme::caches::DirCache;
43    /// let config = AcmeConfig::new(["example.com"]).cache(DirCache::new("./rustls_acme_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 rustls_acme::AcmeConfig;
54    /// use rustls_acme::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(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    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(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
93impl<EC: 'static + Debug, EA: 'static + Debug> AcmeConfig<EC, EA> {
94    /// Set custom `rustls::ClientConfig` for ACME API calls.
95    pub fn client_tls_config(mut self, client_config: Arc<ClientConfig>) -> Self {
96        self.client_config = client_config;
97        self
98    }
99    pub fn directory(mut self, directory_url: impl AsRef<str>) -> Self {
100        self.directory_url = directory_url.as_ref().into();
101        self
102    }
103    pub fn directory_lets_encrypt(mut self, production: bool) -> Self {
104        self.directory_url = match production {
105            true => LETS_ENCRYPT_PRODUCTION_DIRECTORY,
106            false => LETS_ENCRYPT_STAGING_DIRECTORY,
107        }
108        .into();
109        self
110    }
111    pub fn domains(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
112        self.domains = contact.into_iter().map(|s| s.as_ref().into()).collect();
113        self
114    }
115    pub fn domains_push(mut self, contact: impl AsRef<str>) -> Self {
116        self.domains.push(contact.as_ref().into());
117        self
118    }
119
120    /// Provide a list of contacts for the account.
121    ///
122    /// Note that email addresses must include a `mailto:` prefix.
123    pub fn contact(mut self, contact: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
124        self.contact = contact.into_iter().map(|s| s.as_ref().into()).collect();
125        self
126    }
127
128    /// Provide a contact for the account.
129    ///
130    /// Note that an email address must include a `mailto:` prefix.
131    pub fn contact_push(mut self, contact: impl AsRef<str>) -> Self {
132        self.contact.push(contact.as_ref().into());
133        self
134    }
135
136    pub fn cache<C: 'static + Cache>(self, cache: C) -> AcmeConfig<C::EC, C::EA> {
137        AcmeConfig {
138            client_config: self.client_config,
139            directory_url: self.directory_url,
140            domains: self.domains,
141            contact: self.contact,
142            cache: Box::new(cache),
143            challenge_type: self.challenge_type,
144        }
145    }
146    pub fn cache_compose<CC: 'static + CertCache, CA: 'static + AccountCache>(self, cert_cache: CC, account_cache: CA) -> AcmeConfig<CC::EC, CA::EA> {
147        self.cache(CompositeCache::new(cert_cache, account_cache))
148    }
149    pub fn cache_with_boxed_err<C: 'static + Cache>(self, cache: C) -> AcmeConfig<Box<dyn Debug>> {
150        self.cache(BoxedErrCache::new(cache))
151    }
152    pub fn cache_option<C: 'static + Cache>(self, cache: Option<C>) -> AcmeConfig<C::EC, C::EA> {
153        match cache {
154            Some(cache) => self.cache(cache),
155            None => self.cache(NoCache::<C::EC, C::EA>::default()),
156        }
157    }
158    pub fn challenge_type(mut self, challenge_type: UseChallenge) -> Self {
159        self.challenge_type = challenge_type;
160        self
161    }
162    pub fn state(self) -> AcmeState<EC, EA> {
163        AcmeState::new(self)
164    }
165    /// Turn a stream of TCP connections into a stream of TLS connections.
166    ///
167    /// Specify supported protocol names in `alpn_protocols`, most preferred first. If empty (`Vec::new()`), we don't do ALPN.
168    pub fn incoming<TCP: AsyncRead + AsyncWrite + Unpin, ETCP, ITCP: Stream<Item = Result<TCP, ETCP>> + Unpin>(
169        self,
170        tcp_incoming: ITCP,
171        alpn_protocols: Vec<Vec<u8>>,
172    ) -> Incoming<TCP, ETCP, ITCP, EC, EA> {
173        self.state().incoming(tcp_incoming, alpn_protocols)
174    }
175    #[cfg(feature = "tokio")]
176    /// Tokio compatible wrapper for [Self::incoming].
177    pub fn tokio_incoming<
178        TokioTCP: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
179        ETCP,
180        TokioITCP: Stream<Item = Result<TokioTCP, ETCP>> + Unpin,
181    >(
182        self,
183        tcp_incoming: TokioITCP,
184        alpn_protocols: Vec<Vec<u8>>,
185    ) -> crate::tokio::TokioIncoming<
186        tokio_util::compat::Compat<TokioTCP>,
187        ETCP,
188        crate::tokio::TokioIncomingTcpWrapper<TokioTCP, ETCP, TokioITCP>,
189        EC,
190        EA,
191    > {
192        self.state().tokio_incoming(tcp_incoming, alpn_protocols)
193    }
194}
195
196impl<EC: 'static + Debug, EA: 'static + Debug> fmt::Debug for AcmeConfig<EC, EA> {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.debug_struct("AcmeConfig")
199            .field("directory", &self.directory_url)
200            .field("domains", &self.domains)
201            .field("contact", &self.contact)
202            .finish_non_exhaustive()
203    }
204}