Skip to content

Commit 5d073b5

Browse files
committed
ACME: replace anyhow with error enums.
1 parent eb7099c commit 5d073b5

File tree

3 files changed

+248
-79
lines changed

3 files changed

+248
-79
lines changed

src/acme.rs

Lines changed: 81 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::{AccountStatus, 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,21 @@ 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 account: types::Account = deserialize_body(res.body())?;
291+
if !matches!(account.status, AccountStatus::Valid) {
292+
return Err(NewAccountError::Status(account.status));
293+
}
294+
295+
let key_id: &str =
296+
try_get_header(res.headers(), http::header::LOCATION).ok_or(NewAccountError::Url)?;
287297

288-
Ok(serde_json::from_slice(res.body())?)
298+
self.account = Some(key_id.to_string());
299+
300+
Ok(())
289301
}
290302

291303
pub fn is_ready(&self) -> bool {
@@ -295,7 +307,7 @@ where
295307
pub async fn new_certificate<A>(
296308
&self,
297309
req: &CertificateOrder<&str, A>,
298-
) -> Result<NewCertificateOutput>
310+
) -> Result<NewCertificateOutput, NewCertificateError>
299311
where
300312
A: Allocator,
301313
{
@@ -313,30 +325,27 @@ where
313325
not_after: None,
314326
};
315327

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

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

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"))?;
332+
let order_url = try_get_header(res.headers(), http::header::LOCATION)
333+
.and_then(|x| Uri::try_from(x).ok())
334+
.ok_or(NewCertificateError::OrderUrl)?;
325335

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

329338
let mut authorizations: Vec<(http::Uri, types::Authorization)> = Vec::new();
330339
for auth_url in order.authorizations {
331340
let res = self.post(&auth_url, b"").await?;
332-
let mut authorization: types::Authorization = serde_json::from_slice(res.body())?;
341+
let mut authorization: types::Authorization = deserialize_body(res.body())?;
333342

334343
authorization
335344
.challenges
336345
.retain(|x| self.is_supported_challenge(&x.kind));
337346

338347
if authorization.challenges.is_empty() {
339-
anyhow::bail!("no supported challenge for {:?}", authorization.identifier)
348+
return Err(NewCertificateError::NoSupportedChallenges);
340349
}
341350

342351
match authorization.status {
@@ -351,11 +360,7 @@ where
351360
authorization.identifier
352361
);
353362
}
354-
status => anyhow::bail!(
355-
"unexpected authorization status for {:?}: {:?}",
356-
authorization.identifier,
357-
status
358-
),
363+
status => return Err(NewCertificateError::AuthorizationStatus(status)),
359364
}
360365
}
361366

@@ -371,38 +376,48 @@ where
371376
}
372377

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

376381
if order.status != OrderStatus::Ready {
377-
anyhow::bail!("not ready");
382+
if let Some(err) = order.error {
383+
return Err(err.into());
384+
}
385+
return Err(NewCertificateError::OrderStatus(order.status));
378386
}
379387

380-
let csr = make_certificate_request(&order.identifiers, &pkey).and_then(|x| x.to_der())?;
388+
let csr = make_certificate_request(&order.identifiers, &pkey)
389+
.and_then(|x| x.to_der())
390+
.map_err(NewCertificateError::Csr)?;
381391
let payload = std::format!(r#"{{"csr":"{}"}}"#, crate::jws::base64url(csr));
382392

383393
match self.post(&order.finalize, payload).await {
384394
Ok(x) => {
385395
drop(order);
386396
res = x;
387-
order = serde_json::from_slice(res.body())?;
397+
order = deserialize_body(res.body())?;
388398
}
389-
Err(err) => {
390-
if !err.to_string().contains("orderNotReady") {
391-
return Err(err);
392-
}
393-
order.status = OrderStatus::Processing
399+
Err(RequestError::Protocol(problem))
400+
if matches!(
401+
problem.category(),
402+
ProblemCategory::Order | ProblemCategory::Malformed
403+
) =>
404+
{
405+
return Err(problem.into())
394406
}
407+
_ => order.status = OrderStatus::Processing,
395408
};
396409

397410
let mut tries = backoff(MAX_RETRY_INTERVAL, self.finalize_timeout);
398411

399412
while order.status == OrderStatus::Processing && wait_for_retry(&res, &mut tries).await {
400413
drop(order);
401414
res = self.post(&order_url, b"").await?;
402-
order = serde_json::from_slice(res.body())?;
415+
order = deserialize_body(res.body())?;
403416
}
404417

405-
let certificate = order.certificate.ok_or(anyhow!("certificate not ready"))?;
418+
let certificate = order
419+
.certificate
420+
.ok_or(NewCertificateError::CertificateUrl)?;
406421

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

@@ -414,7 +429,7 @@ where
414429
order: &AuthorizationContext<'_>,
415430
url: http::Uri,
416431
authorization: types::Authorization,
417-
) -> Result<()> {
432+
) -> Result<(), NewCertificateError> {
418433
let identifier = authorization.identifier.as_ref();
419434

420435
// Find and set up first supported challenge.
@@ -425,7 +440,7 @@ where
425440
let solver = self.find_solver_for(&x.kind)?;
426441
Some((x, solver))
427442
})
428-
.ok_or(anyhow!("no supported challenge for {identifier:?}"))?;
443+
.ok_or(NewCertificateError::NoSupportedChallenges)?;
429444

430445
solver.register(order, &identifier, challenge)?;
431446

@@ -434,20 +449,20 @@ where
434449
};
435450

436451
let res = self.post(&challenge.url, b"{}").await?;
437-
let result: types::Challenge = serde_json::from_slice(res.body())?;
452+
let result: types::Challenge = deserialize_body(res.body())?;
438453
if !matches!(
439454
result.status,
440455
ChallengeStatus::Pending | ChallengeStatus::Processing | ChallengeStatus::Valid
441456
) {
442-
return Err(anyhow!("unexpected challenge status {:?}", result.status));
457+
return Err(NewCertificateError::ChallengeStatus(result.status));
443458
}
444459

445460
let mut tries = backoff(MAX_RETRY_INTERVAL, self.authorization_timeout);
446461
wait_for_retry(&res, &mut tries).await;
447462

448463
let result = loop {
449464
let res = self.post(&url, b"").await?;
450-
let result: types::Authorization = serde_json::from_slice(res.body())?;
465+
let result: types::Authorization = deserialize_body(res.body())?;
451466

452467
if result.status != AuthorizationStatus::Pending
453468
|| !wait_for_retry(&res, &mut tries).await
@@ -464,7 +479,7 @@ where
464479
);
465480

466481
if result.status != AuthorizationStatus::Valid {
467-
return Err(anyhow!("authorization failed ({:?})", result.status));
482+
return Err(NewCertificateError::AuthorizationStatus(result.status));
468483
}
469484

470485
Ok(())
@@ -539,6 +554,15 @@ fn backoff(max: Duration, timeout: Duration) -> impl Iterator<Item = Duration> {
539554
.map(move |(_, x)| x.min(max))
540555
}
541556

557+
/// Deserializes JSON response body as T and converts error type.
558+
#[inline(always)]
559+
fn deserialize_body<'a, T>(bytes: &'a Bytes) -> Result<T, RequestError>
560+
where
561+
T: serde::Deserialize<'a>,
562+
{
563+
serde_json::from_slice(bytes).map_err(RequestError::ResponseFormat)
564+
}
565+
542566
fn parse_retry_after(val: &http::HeaderValue) -> Option<Duration> {
543567
let val = val.to_str().ok()?;
544568

0 commit comments

Comments
 (0)