Repository: go
Issue: proposal: x/crypto/acme: support the ACME Renewal Information standard in Client
Labels: Proposal

(This could be viewed as a preliminary ticket because, while Let's Encrypt supports the API in question and there are [multiple ACME clients supporting it](https://mailarchive.ietf.org/arch/msg/acme/41B030p_4UYoHR6YhmOtWhP-Oao/), the standard hasn't been finalized at time of creation. Recent events described below made me want to write this out.)

It would be nice for x/crypto/acme to give consumers of it a convenient way of knowing when their certificates need renewal especially when their CAs have revoked those certificates early.

One of the outcomes of the recent Let's Encrypt outage was a recognition that many ACME clients aren't renewing certificates that were revoked by the CA. @agwa wrote a [very nice piece](https://www.agwa.name/blog/post/last_weeks_lets_encrypt_downtime) on the outage, the renewal problem, and how ACME clients might solve it. 

In that post, he mentions that Let's Encrypt currently supports the still-under-development [ACME Renewal Information](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/) (ARI) standard. That standard and API allows ACME clients to hear from the CA that they need to renew a certificate they previously made. That renewal information also, helpfully, gives guidance on when to attempt a renewal even during normal operation.

It would be nice for x/crypto/acme to support the ARI standard.

One version of this work would be to:

1. add a new method to `acme.Client` called `GetRenewalInfo` accepting a x509 certificate that it extract the necessary CertID from, and returning a `RenewalInformation` struct with the suggested liveness window and explanatory URL
2. add a new method to `acme.Client` called `UpdateRenewalInfo` accepting a x509 certificate (a more minimal version would not include this method as its not required for correct operation of `GetRenewalInfo`)

This ticket includes a proposed API below. The method and type names below are meant only as reasonable first attempts at naming and others might have other better names.

The word "CertID" has specific meaning and importance here. The ARI APIs takes the certificate's "DER-encoded CertID ASN.1 sequence" ([link](https://www.ietf.org/archive/id/draft-ietf-acme-ari-01.html#name-getting-renewal-information)). See [section 4.1.1 of RFC6960](https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1) for the definition of "CertID". A CertID is a combination of what are currently multiple fields in `crypto/x509.Certificate`. That means `GetRenewalInfo` and `UpdateRenewalInfo`, unlike `RevokeCert`, needs to parse the x509 certificate given to it.

While `GetRenewalInfo` and `UpdateRenewalInfo` could take some CertID bytes or struct, that combination of data and encoding is a bit more complicated than we could expect users to re-create well. So, we're left with the choice of taking a `[]byte` of the certificate that is parsed under the hood, or a `*x509.Certificate`. 

A `[]byte` for the cert seems preferable to a `*x509.Certificate`. First, because the `[]byte` type fits the pattern set by `RevokeCert`. Second, a `[]byte` would also likely result in less code written by most consumers as most don't care about the individual bytes inside a cert and would be passing it directly from their storage layer. Third, the `[]byte` also avoids them having to learn how to parse x509 well. However, specifying the full `x509.Certificate` type might better convey what's being requested from the consumer and avoid this library from having to inform the consumer when their x509 certificate is ill-formed. On balance, the `[]byte` seems better, but I'm open to hearing I'm wrong.

One last note: `GetRenewalInfo` doesn't require its request payload to be signed with the correct account key, but `UpdateRenewalInfo` does. 


```go
type SuggestedWindow struct {
  Start time.Time
  End time.Time
}

type RenewalInformation struct {
  SuggestedWindow SuggestedWindow
  ExplanationURL string
} 

func (c * Client) GetRenewalInfo(ctx context.Context, cert []byte) (*RenewalInformation, error)

func (c * Client) UpdateRenewalInfo(ctx context.Context, cert []byte) (error)
```