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
15pub 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 #[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 #[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 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 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 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 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 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 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}