Skip to content

Mark ACME authorization invalid when a challenge fails - #2802

Open
Gauss23 wants to merge 1 commit into
smallstep:masterfrom
Gauss23:acme-authz-invalid-on-failed-challenge
Open

Gauss23 wants to merge 1 commit into
smallstep:masterfrom
Gauss23:acme-authz-invalid-on-failed-challenge

Conversation

@Gauss23

@Gauss23 Gauss23 commented Sep 18, 2026

Copy link
Copy Markdown

Problem

RFC 8555, section 7.1.6:

If the client attempts to fulfill a challenge and fails, or if there is an
error while the authorization is still pending, then the authorization
transitions to the "invalid" state.

Authorization.UpdateStatus only moves a pending authorization to valid
(when a challenge is valid) or to invalid (when it has expired). If a
challenge becomes invalid, for example because the key authorization does
not match, the authorization stays pending until it expires, 24 hours by
default. Clients like certbot and Caddy/acmez poll the authorization, and
through it the order, for that whole time instead of reporting the error.

Change

In UpdateStatus, if no challenge is valid but at least one is invalid, set
the authorization to invalid and copy the error of the first invalid
challenge into az.Error. The existing Order.UpdateStatus then marks the
order invalid as well. A valid challenge still takes precedence over an
invalid one.

The check does not depend on the challenge type, but it only applies once a
challenge is actually invalid. dns-01 is unaffected in practice:
dns01Validate never marks a challenge invalid, so a failed TXT lookup or a
TXT record with the wrong value still leaves it pending for another
attempt.

Behavior change: after one challenge has failed, a client can no longer try
a different challenge type in the same authorization. It has to create a
new order. This matches the RFC and what Let's Encrypt does.

Tests

  • TestAuthorization_UpdateStatus: new cases ok/invalid-challenge and
    ok/valid-wins-over-invalid.
  • TestAuthorizationInvalidAfterFailedChallenge: checks that the
    authorization is saved with status invalid and the challenge's error.
    It fails without the change. The two table cases only assert inside the
    UpdateAuthorization mock, so they also pass without the change; they
    guard the new code path against regressions.
  • go test ./acme/... ./authority/... passes with Go 1.26.8 and 1.27.1.

RFC 8555, section 7.1.6, says that if the client attempts to fulfill a
challenge and fails, the authorization transitions to "invalid".
Authorization.UpdateStatus only ever moved a pending authorization to
"valid" (or to "invalid" on expiry), so an authorization with an invalid
challenge stayed "pending" until it expired 24 hours later. Clients such
as certbot and Caddy/acmez kept polling it for that whole time.

Mark the authorization invalid, and copy the error of the failed
challenge into it, when no challenge is valid but at least one is
invalid. A valid challenge still takes precedence.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs triage Waiting for discussion / prioritization by team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant