Skip to content

Commit 5a27f0c

Browse files
committed
ACME: poll authorizations instead of challenges.
Previously, we assumed that we should poll the challenge URL until completion, because all the servers we tested correctly responded to a POST-as-GET to the challenger URL. We also made an initial POST-as-GET request to fetch the status and skip the challenges in "valid" state. RFC8555 7.5.1 specifies that we should poll the authorization resource status instead. Additionally, the discussion around Errata ID 6317 implies that it is harmful to initiate multiple challenges, as a single failure invalidates the whole authorization. Given the above, we can simplify the code and proceed to poll the authorization status after initiating the first available challenge.
1 parent 131a681 commit 5a27f0c

File tree

1 file changed

+24
-64
lines changed

1 file changed

+24
-64
lines changed

src/acme.rs

Lines changed: 24 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -405,18 +405,34 @@ where
405405
url: http::Uri,
406406
authorization: types::Authorization,
407407
) -> Result<()> {
408-
let mut result = Err(anyhow!("no challenges"));
409408
let identifier = authorization.identifier.as_ref();
410409

411-
for challenge in authorization.challenges {
412-
result = self.do_challenge(order, &identifier, &challenge).await;
410+
// Find and set up first supported challenge.
411+
let (challenge, solver) = authorization
412+
.challenges
413+
.iter()
414+
.find_map(|x| {
415+
let solver = self.find_solver_for(&x.kind)?;
416+
Some((x, solver))
417+
})
418+
.ok_or(anyhow!("no supported challenge for {identifier:?}"))?;
413419

414-
if result.is_ok() {
415-
break;
416-
}
420+
solver.register(order, &identifier, challenge)?;
421+
422+
scopeguard::defer! {
423+
let _ = solver.unregister(&identifier, challenge);
424+
};
425+
426+
let res = self.post(&challenge.url, b"{}").await?;
427+
let result: types::Challenge = serde_json::from_slice(res.body())?;
428+
if !matches!(
429+
result.status,
430+
ChallengeStatus::Pending | ChallengeStatus::Processing | ChallengeStatus::Valid
431+
) {
432+
return Err(anyhow!("unexpected challenge status {:?}", result.status));
417433
}
418434

419-
result?;
435+
wait_for_retry(&res).await;
420436

421437
let mut tries = 10;
422438

@@ -440,63 +456,7 @@ where
440456
);
441457

442458
if result.status != AuthorizationStatus::Valid {
443-
return Err(anyhow!("authorization failed"));
444-
}
445-
446-
Ok(())
447-
}
448-
449-
async fn do_challenge(
450-
&self,
451-
ctx: &AuthorizationContext<'_>,
452-
identifier: &Identifier<&str>,
453-
challenge: &types::Challenge,
454-
) -> Result<()> {
455-
let res = self.post(&challenge.url, b"").await?;
456-
let result: types::Challenge = serde_json::from_slice(res.body())?;
457-
458-
// Previous challenge result is still valid.
459-
// Should not happen as we already skip valid authorizations.
460-
if result.status == ChallengeStatus::Valid {
461-
return Ok(());
462-
}
463-
464-
let solver = self
465-
.find_solver_for(&challenge.kind)
466-
.ok_or(anyhow!("no solver for {:?}", challenge.kind))?;
467-
468-
solver.register(ctx, identifier, challenge)?;
469-
470-
scopeguard::defer! {
471-
let _ = solver.unregister(identifier, challenge);
472-
};
473-
474-
// "{}" in request payload initiates the challenge, "" checks the status.
475-
let mut payload: &[u8] = b"{}";
476-
let mut tries = 10;
477-
478-
let result = loop {
479-
let res = self.post(&challenge.url, payload).await?;
480-
let result: types::Challenge = serde_json::from_slice(res.body())?;
481-
482-
if !matches!(
483-
result.status,
484-
ChallengeStatus::Pending | ChallengeStatus::Processing,
485-
) || tries == 0
486-
{
487-
break result;
488-
}
489-
490-
tries -= 1;
491-
payload = b"";
492-
wait_for_retry(&res).await;
493-
};
494-
495-
if result.status != ChallengeStatus::Valid {
496-
return Err(result
497-
.error
498-
.map(Into::into)
499-
.unwrap_or(anyhow!("unknown error")));
459+
return Err(anyhow!("authorization failed ({:?})", result.status));
500460
}
501461

502462
Ok(())

0 commit comments

Comments
 (0)