1use crate::error::*;
2use crate::jws::jws;
3use hyper::body::Bytes;
4use openssl::pkey::PKey;
5use openssl::pkey::Private;
6use serde::de::DeserializeOwned;
7use serde::Deserialize;
8use serde::Serialize;
9use std::sync::Arc;
10use std::sync::Mutex;
11use tracing::debug;
12use tracing::field;
13use tracing::instrument;
14use tracing::Level;
15use tracing::Span;
16
17pub struct DirectoryBuilder {
19 url: String,
20 http_client: Option<reqwest::Client>,
21}
22
23impl DirectoryBuilder {
24 pub fn new(url: String) -> Self {
30 DirectoryBuilder {
31 url,
32 http_client: None,
33 }
34 }
35
36 pub fn http_client(&mut self, http_client: reqwest::Client) -> &mut Self {
39 self.http_client = Some(http_client);
40 self
41 }
42
43 #[instrument(
48 level = Level::INFO,
49 name = "acme2::DirectoryBuilder::build",
50 err,
51 skip(self),
52 fields(url = %self.url, custom_http_client = self.http_client.is_some(), dir = field::Empty)
53 )]
54 pub async fn build(&mut self) -> Result<Arc<Directory>, Error> {
55 let http_client = self.http_client.clone().unwrap_or_default();
56
57 let resp = http_client.get(&self.url).send().await?;
58
59 let res: Result<Directory, Error> = resp.json::<ServerResult<Directory>>().await?.into();
60 let mut dir = res?;
61 Span::current().record("dir", &field::debug(&dir));
62
63 dir.http_client = http_client;
64 dir.nonce = Mutex::new(None);
65
66 Ok(Arc::new(dir))
67 }
68}
69
70#[derive(Deserialize, Debug)]
74#[serde(rename_all = "camelCase")]
75#[allow(unused)]
76pub struct Directory {
77 #[serde(skip)]
78 pub(crate) http_client: reqwest::Client,
79 #[serde(skip)]
80 pub(crate) nonce: Mutex<Option<String>>,
81 #[serde(rename = "newNonce")]
82 pub(crate) new_nonce_url: String,
83 #[serde(rename = "newAccount")]
84 pub(crate) new_account_url: String,
85 #[serde(rename = "newOrder")]
86 pub(crate) new_order_url: String,
87 #[serde(rename = "revokeCert")]
88 pub(crate) revoke_cert_url: String,
89 #[serde(rename = "keyChange")]
90 pub(crate) key_change_url: Option<String>,
91 #[serde(rename = "newAuthz")]
92 pub(crate) new_authz_url: Option<String>,
93 pub meta: Option<DirectoryMeta>,
95}
96
97#[derive(Deserialize, Clone, Debug)]
101#[serde(rename_all = "camelCase")]
102pub struct DirectoryMeta {
103 pub terms_of_service: Option<String>,
104 pub website: Option<String>,
105 pub caa_identities: Option<Vec<String>>,
106 pub external_account_required: Option<bool>,
107}
108
109fn extract_nonce_from_response(resp: &reqwest::Response) -> Result<Option<String>, Error> {
110 let headers = resp.headers();
111 let maybe_nonce_res = headers
112 .get("replay-nonce")
113 .map::<Result<String, Error>, _>(|hv| Ok(map_transport_err(hv.to_str())?.to_string()));
114 match maybe_nonce_res {
115 Some(Ok(n)) => Ok(Some(n)),
116 Some(Err(err)) => Err(err),
117 None => Ok(None),
118 }
119}
120
121impl Directory {
122 #[instrument(
123 level = Level::DEBUG,
124 name = "acme2::Directory::get_nonce",
125 err,
126 skip(self),
127 fields(cached = field::Empty)
128 )]
129 pub(crate) async fn get_nonce(&self) -> Result<String, Error> {
130 let maybe_nonce = {
131 let mut guard = self.nonce.lock().unwrap();
132 (*guard).take()
133 };
134 let span = Span::current();
135 span.record("cached", maybe_nonce.is_some());
136 if let Some(nonce) = maybe_nonce {
137 return Ok(nonce);
138 }
139 let resp = self.http_client.get(&self.new_nonce_url).send().await?;
140 let maybe_nonce = extract_nonce_from_response(&resp)?;
141 match maybe_nonce {
142 Some(nonce) => Ok(nonce),
143 None => Err(transport_err("newNonce request must return a nonce")),
144 }
145 }
146
147 #[instrument(level = Level::DEBUG, name = "acme2::Directory::authenticated_request_raw", err, skip(self, payload, pkey))]
148 async fn authenticated_request_raw(
149 &self,
150 url: &str,
151 payload: &str,
152 pkey: &PKey<Private>,
153 account_id: &Option<String>,
154 ) -> Result<reqwest::Response, Error> {
155 let nonce = self.get_nonce().await?;
156 let body = jws(url, Some(nonce), payload, pkey, account_id.clone())?;
157 let body = serde_json::to_vec(&body)?;
158 let resp = self
159 .http_client
160 .post(url)
161 .header(reqwest::header::CONTENT_TYPE, "application/jose+json")
162 .body(body)
163 .send()
164 .await?;
165
166 if let Some(nonce) = extract_nonce_from_response(&resp)? {
167 let mut guard = self.nonce.lock().unwrap();
168 *guard = Some(nonce);
169 }
170
171 Ok(resp)
172 }
173
174 #[instrument(
175 level = Level::DEBUG,
176 name = "acme2::Directory::authenticated_request_bytes",
177 err,
178 skip(self, payload, pkey),
179 fields()
180 )]
181 pub(crate) async fn authenticated_request_bytes(
182 &self,
183 url: &str,
184 payload: &str,
185 pkey: &PKey<Private>,
186 account_id: &Option<String>,
187 ) -> Result<(Result<Bytes, ServerError>, reqwest::header::HeaderMap), Error> {
188 let mut attempt = 0;
189
190 loop {
191 attempt += 1;
192
193 let resp = self
194 .authenticated_request_raw(url, payload, pkey, account_id)
195 .await?;
196
197 let headers = resp.headers().clone();
198
199 if resp.status().is_success() {
200 return Ok((Ok(resp.bytes().await?), headers));
201 }
202
203 let err: ServerError = resp.json().await?;
204
205 if let Some(typ) = err.r#type.clone() {
206 if &typ == "urn:ietf:params:acme:error:badNonce" && attempt <= 3 {
207 debug!({ attempt }, "bad nonce, retrying");
208 continue;
209 }
210 }
211
212 return Ok((Err(err), headers));
213 }
214 }
215
216 #[instrument(
217 level = Level::DEBUG,
218 name = "acme2::Directory::authenticated_request",
219 err,
220 skip(self, payload, pkey),
221 fields()
222 )]
223 pub(crate) async fn authenticated_request<T, R>(
224 &self,
225 url: &str,
226 payload: T,
227 pkey: PKey<Private>,
228 account_id: Option<String>,
229 ) -> Result<(ServerResult<R>, reqwest::header::HeaderMap), Error>
230 where
231 T: Serialize,
232 R: DeserializeOwned,
233 {
234 let payload = serde_json::to_string(&payload)?;
235 let payload = if payload == "\"\"" {
236 "".to_string()
237 } else {
238 payload
239 };
240
241 let (res, headers) = self
242 .authenticated_request_bytes(url, &payload, &pkey, &account_id)
243 .await?;
244
245 let bytes = match res {
246 Ok(bytes) => bytes,
247 Err(err) => return Ok((ServerResult::Err(err), headers)),
248 };
249
250 let val: R = serde_json::from_slice(&bytes)?;
251
252 Ok((ServerResult::Ok(val), headers))
253 }
254}