Subject: [Acme] ACME RFC - POST-as-GET to Challenge endpoint

From: Joshua Van Leeuwen <[email-masked]>
Date: Fri, 10 Sep 2021 16:19:41 +0100

Hi all,

I am a developer for the cert-manager <https://cert-manager.io> project
which is an operator for issuing
certificates in Kubernetes.

I am reaching out as I have a question regarding perhaps some ambiguity in
the
ACME RFC.

cert-manager implements an ACME client for requesting certificates from ACME
servers. As part of the ACME flow when requesting a certificate, the ACME
Challenge controller worker is responsible for self-checking whether a
Challenge
is valid, accepting that Challenge to the server, and finally waiting for
Authorization.

Due to the nature of the operator model in Kubernetes, before this flow
takes
place, the controller fetches the current state of the ACME Challenge from
the
server. This uses an empty body ("") POST-as-GET request to the Challenge
endpoint in order to recover state which might have been lost.

cert-manager has been successfully used against ACME servers for the past 4
years, including namely Let's Encrypt. Recent implementations of ACME
servers
have had issues with this call whereby they interpret it as an Accept, or
otherwise reject the call and effectively invalidate the Order.

Though this behaviour can be mitigated by calling the authorization
endpoint and
pruning the Challenge that the controller is concerned with, my question is
whether or not querying the Challenge endpoint for its status is valid
according
to the ACME RFC?

It is described in section 7.5.1
<https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.1>:

> To check on the status of an authorization, the client sends a POST-
> as-GET request to the authorization URL, and the server responds with
> the current authorization object. In responding to poll requests
> while the validation is still in progress, the server MUST return a
> 200 (OK) response and MAY include a Retry-After header field to
> suggest a polling interval to the client.

However we are not necessarily concerned with the _authorization_, only a
particular child Challenge. Is querying the Challenge endpoint to discover
state ever intended as a supported call in the RFC?

Cheers,
Josh

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 10 Sep 2021 22:03:51 +0300

> whether or not querying the Challenge endpoint for its status is valid

I have implemented an ACME client, and it never queries challenge
endpoints, only uses them to trigger validations (it even discards the
validation trigger responses; I do not know what LE sends back for
these).

- On valid authorizations, it never pays any attention to challenges.
  Valid is valid.
- On pending challenges it grabs all challenges it understands (since
  unknown challenges might be something else than type/nonce model
  seen in 3 challenge types in the main ACME RFC) and figures out what
  it can do later. When it acknowledges a challenge, it uses URL pulled
  in this step.
- On failed authorizations, it indiscriminatedly (it pays no attention
  if type matches or not) digs whatever error records exist, plus
  whatever extended data Let's Encrypt includes.

And from the spec, I get the feeling that querying challenge objects
is ... underspecified. As well as being a footgun.

> state ever intended as a supported call in the RFC?

The client I wrote pays as little attention to challenges as possible,
it pays attention to authorizations instead.

In pre-RFC ACME (LE "ACMEv1") days, clients matching challenge types on
accepted authorizations was a not uncommon footgun. Caused a number of
spurious failures.

-Ilari

---

From: Aaron Gable <[email-masked]>
Date: Mon, 20 Sep 2021 15:49:24 -0700

RFC 8555 does not intend for Challenges to be able to be fetched
individually; their contents are bundled into the Authorization object so
that all of the relevant information can be fetched with a single request.
The fact that Let's Encrypt allows challenges to be fetched individually is
primarily a holdover from the pre-standardization days.

A particular reading of RFC 8555 might suggest that the behavior you're
seeing from more recent ACME servers is the more correct behavior:

>    If the client's response is invalid for any reason or does not
>   provide the server with appropriate information to validate the
>   challenge, then the server MUST return an HTTP error.  On receiving
>   such an error, the client SHOULD undo any actions that have been
>   taken to fulfill the challenge, e.g., removing files that have been
>   provisioned to a web server.

An empty POST-as-GET request could be interpreted to be a request that
"does not provide the server with appropriate information to validate the
challenge", and therefore MUST result in an error.

Either way, I think that you shouldn't rely on ACME servers exposing
individual challenges.

Aaron

P.S. For what it's worth, even though you can request an individual
challenge from Let's Encrypt, and even though doing so results in fewer
bytes on the wire, it doesn't reduce load on Let's Encrypt's side -- the
whole Authorization object is loaded from the database anyway:
https://github.com/letsencrypt/boulder/blob/e27634717069988a83011f0b74382ad632128c35/wfe2/wfe.go#L1076
.

On Fri, Sep 10, 2021 at 12:05 PM Ilari Liusvaara <[email-masked]>
wrote:

> _______________________________________________
> Acme mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/acme
>