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