Skip to content

Commit 6d1a12c

Browse files
committed
ACME: replace anyhow with error enums.
1 parent 1ec24c8 commit 6d1a12c

File tree

3 files changed

+239
-79
lines changed

3 files changed

+239
-79
lines changed

src/acme.rs

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ use core::time::Duration;
99
use std::collections::VecDeque;
1010
use std::string::{String, ToString};
1111

12-
use anyhow::{anyhow, Result};
1312
use bytes::Bytes;
13+
use error::{NewAccountError, NewCertificateError, RequestError};
1414
use http::Uri;
1515
use ngx::allocator::{Allocator, Box};
1616
use ngx::async_::sleep;
1717
use ngx::collections::Vec;
1818
use ngx::ngx_log_debug;
1919
use openssl::pkey::{PKey, PKeyRef, Private};
2020
use openssl::x509::{self, extension as x509_ext, X509Req};
21+
use types::ProblemCategory;
2122

22-
use self::account_key::AccountKey;
23+
use self::account_key::{AccountKey, AccountKeyError};
2324
use self::types::{AuthorizationStatus, ChallengeKind, ChallengeStatus, OrderStatus};
2425
use crate::conf::identifier::Identifier;
2526
use crate::conf::issuer::Issuer;
@@ -28,6 +29,7 @@ use crate::net::http::HttpClient;
2829
use crate::time::Time;
2930

3031
pub mod account_key;
32+
pub mod error;
3133
pub mod solvers;
3234
pub mod types;
3335

@@ -97,8 +99,13 @@ fn try_get_header<K: http::header::AsHeaderName>(
9799
impl<'a, Http> AcmeClient<'a, Http>
98100
where
99101
Http: HttpClient,
102+
RequestError: From<<Http as HttpClient>::Error>,
100103
{
101-
pub fn new(http: Http, issuer: &'a Issuer, log: NonNull<nginx_sys::ngx_log_t>) -> Result<Self> {
104+
pub fn new(
105+
http: Http,
106+
issuer: &'a Issuer,
107+
log: NonNull<nginx_sys::ngx_log_t>,
108+
) -> Result<Self, AccountKeyError> {
102109
let key = AccountKey::try_from(
103110
issuer
104111
.pkey
@@ -137,21 +144,21 @@ where
137144
self.solvers.iter().any(|s| s.supports(kind))
138145
}
139146

140-
async fn get_directory(&self) -> Result<types::Directory> {
147+
async fn get_directory(&self) -> Result<types::Directory, RequestError> {
141148
let res = self.get(&self.issuer.uri).await?;
142-
let directory = serde_json::from_slice(res.body())?;
149+
let directory = deserialize_body(res.body())?;
143150

144151
Ok(directory)
145152
}
146153

147-
async fn get_nonce(&self) -> Result<String> {
154+
async fn get_nonce(&self) -> Result<String, RequestError> {
148155
let res = self.get(&self.directory.new_nonce).await?;
149156
try_get_header(res.headers(), &REPLAY_NONCE)
150-
.ok_or(anyhow!("no nonce in response headers"))
157+
.ok_or(RequestError::Nonce)
151158
.map(String::from)
152159
}
153160

154-
pub async fn get(&self, url: &Uri) -> Result<http::Response<Bytes>> {
161+
pub async fn get(&self, url: &Uri) -> Result<http::Response<Bytes>, RequestError> {
155162
let req = http::Request::builder()
156163
.uri(url)
157164
.method(http::Method::GET)
@@ -164,7 +171,7 @@ where
164171
&self,
165172
url: &Uri,
166173
payload: P,
167-
) -> Result<http::Response<Bytes>> {
174+
) -> Result<http::Response<Bytes>, RequestError> {
168175
let mut nonce = if let Some(nonce) = self.nonce.get() {
169176
nonce
170177
} else {
@@ -215,10 +222,10 @@ where
215222
// 8555.6.5, when retrying in response to a "badNonce" error, the client MUST use
216223
// the nonce provided in the error response.
217224
nonce = try_get_header(res.headers(), &REPLAY_NONCE)
218-
.ok_or(anyhow!("no nonce in response"))?
225+
.ok_or(RequestError::Nonce)?
219226
.to_string();
220227

221-
let err: types::Problem = serde_json::from_slice(res.body())?;
228+
let err: types::Problem = deserialize_body(res.body())?;
222229

223230
let retriable = matches!(
224231
err.kind,
@@ -239,20 +246,23 @@ where
239246
Ok(res)
240247
}
241248

242-
pub async fn new_account(&mut self) -> Result<types::Account> {
243-
self.directory = self.get_directory().await?;
249+
pub async fn new_account(&mut self) -> Result<(), NewAccountError> {
250+
self.directory = self
251+
.get_directory()
252+
.await
253+
.map_err(NewAccountError::Directory)?;
244254

245255
if self.directory.meta.external_account_required == Some(true)
246256
&& self.issuer.eab_key.is_none()
247257
{
248-
return Err(anyhow!("external account key required"));
258+
return Err(NewAccountError::ExternalAccount);
249259
}
250260

251261
let external_account_binding = self
252262
.issuer
253263
.eab_key
254264
.as_ref()
255-
.map(|x| -> Result<_> {
265+
.map(|x| -> Result<_, RequestError> {
256266
let key = crate::jws::ShaWithHmacKey::new(&x.key, 256);
257267
let payload = serde_json::to_vec(&self.key)?;
258268
let message = crate::jws::sign_jws(
@@ -273,19 +283,16 @@ where
273283

274284
..Default::default()
275285
};
276-
let payload = serde_json::to_string(&payload)?;
286+
let payload = serde_json::to_string(&payload).map_err(RequestError::RequestFormat)?;
277287

278288
let res = self.post(&self.directory.new_account, payload).await?;
279289

280-
let key_id = res
281-
.headers()
282-
.get("location")
283-
.ok_or(anyhow!("account URL unavailable"))?
284-
.to_str()?
285-
.to_string();
286-
self.account = Some(key_id);
290+
let key_id: &str =
291+
try_get_header(res.headers(), http::header::LOCATION).ok_or(NewAccountError::Url)?;
292+
293+
self.account = Some(key_id.to_string());
287294

288-
Ok(serde_json::from_slice(res.body())?)
295+
Ok(())
289296
}
290297

291298
pub fn is_ready(&self) -> bool {
@@ -295,7 +302,7 @@ where
295302
pub async fn new_certificate<A>(
296303
&self,
297304
req: &CertificateOrder<&str, A>,
298-
) -> Result<NewCertificateOutput>
305+
) -> Result<NewCertificateOutput, NewCertificateError>
299306
where
300307
A: Allocator,
301308
{
@@ -313,30 +320,27 @@ where
313320
not_after: None,
314321
};
315322

316-
let payload = serde_json::to_string(&payload)?;
323+
let payload = serde_json::to_string(&payload).map_err(RequestError::RequestFormat)?;
317324

318325
let res = self.post(&self.directory.new_order, payload).await?;
319326

320-
let order_url = res
321-
.headers()
322-
.get("location")
323-
.and_then(|x| x.to_str().ok())
324-
.ok_or(anyhow!("no order URL"))?;
327+
let order_url = try_get_header(res.headers(), http::header::LOCATION)
328+
.and_then(|x| Uri::try_from(x).ok())
329+
.ok_or(NewCertificateError::OrderUrl)?;
325330

326-
let order_url = Uri::try_from(order_url)?;
327-
let order: types::Order = serde_json::from_slice(res.body())?;
331+
let order: types::Order = deserialize_body(res.body())?;
328332

329333
let mut authorizations: Vec<(http::Uri, types::Authorization)> = Vec::new();
330334
for auth_url in order.authorizations {
331335
let res = self.post(&auth_url, b"").await?;
332-
let mut authorization: types::Authorization = serde_json::from_slice(res.body())?;
336+
let mut authorization: types::Authorization = deserialize_body(res.body())?;
333337

334338
authorization
335339
.challenges
336340
.retain(|x| self.is_supported_challenge(&x.kind));
337341

338342
if authorization.challenges.is_empty() {
339-
anyhow::bail!("no supported challenge for {:?}", authorization.identifier)
343+
return Err(NewCertificateError::NoSupportedChallenges);
340344
}
341345

342346
match authorization.status {
@@ -351,11 +355,7 @@ where
351355
authorization.identifier
352356
);
353357
}
354-
status => anyhow::bail!(
355-
"unexpected authorization status for {:?}: {:?}",
356-
authorization.identifier,
357-
status
358-
),
358+
status => return Err(NewCertificateError::AuthorizationStatus(status)),
359359
}
360360
}
361361

@@ -371,38 +371,48 @@ where
371371
}
372372

373373
let mut res = self.post(&order_url, b"").await?;
374-
let mut order: types::Order = serde_json::from_slice(res.body())?;
374+
let mut order: types::Order = deserialize_body(res.body())?;
375375

376376
if order.status != OrderStatus::Ready {
377-
anyhow::bail!("not ready");
377+
if let Some(err) = order.error {
378+
return Err(err.into());
379+
}
380+
return Err(NewCertificateError::OrderStatus(order.status));
378381
}
379382

380-
let csr = make_certificate_request(&order.identifiers, &pkey).and_then(|x| x.to_der())?;
383+
let csr = make_certificate_request(&order.identifiers, &pkey)
384+
.and_then(|x| x.to_der())
385+
.map_err(NewCertificateError::Csr)?;
381386
let payload = std::format!(r#"{{"csr":"{}"}}"#, crate::jws::base64url(csr));
382387

383388
match self.post(&order.finalize, payload).await {
384389
Ok(x) => {
385390
drop(order);
386391
res = x;
387-
order = serde_json::from_slice(res.body())?;
392+
order = deserialize_body(res.body())?;
388393
}
389-
Err(err) => {
390-
if !err.to_string().contains("orderNotReady") {
391-
return Err(err);
392-
}
393-
order.status = OrderStatus::Processing
394+
Err(RequestError::Protocol(problem))
395+
if matches!(
396+
problem.category(),
397+
ProblemCategory::Order | ProblemCategory::Malformed
398+
) =>
399+
{
400+
return Err(problem.into())
394401
}
402+
_ => order.status = OrderStatus::Processing,
395403
};
396404

397405
let mut tries = backoff(MAX_RETRY_INTERVAL, self.finalize_timeout);
398406

399407
while order.status == OrderStatus::Processing && wait_for_retry(&res, &mut tries).await {
400408
drop(order);
401409
res = self.post(&order_url, b"").await?;
402-
order = serde_json::from_slice(res.body())?;
410+
order = deserialize_body(res.body())?;
403411
}
404412

405-
let certificate = order.certificate.ok_or(anyhow!("certificate not ready"))?;
413+
let certificate = order
414+
.certificate
415+
.ok_or(NewCertificateError::CertificateUrl)?;
406416

407417
let chain = self.post(&certificate, b"").await?.into_body();
408418

@@ -414,7 +424,7 @@ where
414424
order: &AuthorizationContext<'_>,
415425
url: http::Uri,
416426
authorization: types::Authorization,
417-
) -> Result<()> {
427+
) -> Result<(), NewCertificateError> {
418428
let identifier = authorization.identifier.as_ref();
419429

420430
// Find and set up first supported challenge.
@@ -425,7 +435,7 @@ where
425435
let solver = self.find_solver_for(&x.kind)?;
426436
Some((x, solver))
427437
})
428-
.ok_or(anyhow!("no supported challenge for {identifier:?}"))?;
438+
.ok_or(NewCertificateError::NoSupportedChallenges)?;
429439

430440
solver.register(order, &identifier, challenge)?;
431441

@@ -434,20 +444,20 @@ where
434444
};
435445

436446
let res = self.post(&challenge.url, b"{}").await?;
437-
let result: types::Challenge = serde_json::from_slice(res.body())?;
447+
let result: types::Challenge = deserialize_body(res.body())?;
438448
if !matches!(
439449
result.status,
440450
ChallengeStatus::Pending | ChallengeStatus::Processing | ChallengeStatus::Valid
441451
) {
442-
return Err(anyhow!("unexpected challenge status {:?}", result.status));
452+
return Err(NewCertificateError::ChallengeStatus(result.status));
443453
}
444454

445455
let mut tries = backoff(MAX_RETRY_INTERVAL, self.authorization_timeout);
446456
wait_for_retry(&res, &mut tries).await;
447457

448458
let result = loop {
449459
let res = self.post(&url, b"").await?;
450-
let result: types::Authorization = serde_json::from_slice(res.body())?;
460+
let result: types::Authorization = deserialize_body(res.body())?;
451461

452462
if result.status != AuthorizationStatus::Pending
453463
|| !wait_for_retry(&res, &mut tries).await
@@ -464,7 +474,7 @@ where
464474
);
465475

466476
if result.status != AuthorizationStatus::Valid {
467-
return Err(anyhow!("authorization failed ({:?})", result.status));
477+
return Err(NewCertificateError::AuthorizationStatus(result.status));
468478
}
469479

470480
Ok(())
@@ -539,6 +549,15 @@ fn backoff(max: Duration, timeout: Duration) -> impl Iterator<Item = Duration> {
539549
.map(move |(_, x)| x.min(max))
540550
}
541551

552+
/// Deserializes JSON response body as T and converts error type.
553+
#[inline(always)]
554+
fn deserialize_body<'a, T>(bytes: &'a Bytes) -> Result<T, RequestError>
555+
where
556+
T: serde::Deserialize<'a>,
557+
{
558+
serde_json::from_slice(bytes).map_err(RequestError::ResponseFormat)
559+
}
560+
542561
fn parse_retry_after(val: &http::HeaderValue) -> Option<Duration> {
543562
let val = val.to_str().ok()?;
544563

0 commit comments

Comments
 (0)