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
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(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 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 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 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 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 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 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}