From 41d7136bf589b83f3e1ab85a007723e012cea25f Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Thu, 5 Jul 2018 10:27:09 +0100 Subject: [PATCH] Merge latest changes from upstream crypto/acme library --- third_party/crypto/acme/acme.go | 63 ++++++++++++++++----- third_party/crypto/acme/integration_test.go | 4 +- third_party/crypto/acme/types.go | 11 ++-- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/third_party/crypto/acme/acme.go b/third_party/crypto/acme/acme.go index 4578ee31c..0308baada 100644 --- a/third_party/crypto/acme/acme.go +++ b/third_party/crypto/acme/acme.go @@ -46,6 +46,10 @@ const ( // Max number of collected nonces kept in memory. // Expect usual peak of 1 or 2. maxNonces = 100 + + // User-Agent, bump the version each time a change is made to the + // handling of API requests. + userAgent = "go-acme/2" ) // Client is an ACME client. @@ -73,6 +77,11 @@ type Client struct { // will have no effect. DirectoryURL string + // UserAgent is an optional string that identifies this client and + // version to the ACME server. It should be set to something like + // "myclient/1.2.3". + UserAgent string + noncesMu sync.Mutex nonces map[string]struct{} // nonces collected from previous responses @@ -274,6 +283,13 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) { sleep := timeSleeper(ctx) for { o, err := c.GetOrder(ctx, url) + if e, ok := err.(*Error); ok && e.StatusCode >= 500 && e.StatusCode <= 599 { + // retriable 5xx error + if err := sleep(retryAfter(e.Header.Get("Retry-After"))); err != nil { + return nil, err + } + continue + } if err != nil { return nil, err } @@ -392,14 +408,14 @@ func (c *Client) DeactivateAuthorization(ctx context.Context, url string) error return nil } -// WaitAuthorization retrieves authorization details. If the authorization is not in -// a final state (StatusValid/StatusInvalid), it retries the request until the authorization -// is final, ctx is cancelled by the caller, an error response is received, or the ACME CA -// responded with a 4xx error. +// WaitAuthorization polls an authorization at the given URL +// until it is in one of the final states, StatusValid or StatusInvalid, +// the ACME CA responded with a 4xx error code, or the context is done. // // It returns a non-nil Authorization only if its Status is StatusValid. // In all other cases WaitAuthorization returns an error. -// If the Status is StatusInvalid or StatusDeactivated, the returned error will be of type AuthorizationError. +// If the Status is StatusInvalid, StatusDeactivated, or StatusRevoked the +// returned error will be of type AuthorizationError. func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) { sleep := sleeper(ctx) for { @@ -407,17 +423,21 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat if err != nil { return nil, err } - if res.StatusCode != http.StatusOK { - err = responseError(res) - res.Body.Close() - return nil, err - } if res.StatusCode >= 400 && res.StatusCode <= 499 { // Non-retriable error. For instance, Let's Encrypt may return 404 Not Found // when requesting an expired authorization. defer res.Body.Close() return nil, responseError(res) } + + retry := res.Header.Get("Retry-After") + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { + res.Body.Close() + if err := sleep(retry); err != nil { + return nil, err + } + continue + } var raw wireAuthz err = json.NewDecoder(res.Body).Decode(&raw) res.Body.Close() @@ -427,13 +447,13 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat switch raw.Status { case StatusValid: return raw.authorization(url), nil - case StatusInvalid, StatusDeactivated: + case StatusInvalid, StatusDeactivated, StatusRevoked: return nil, AuthorizationError{raw.authorization(url)} case StatusPending, StatusProcessing: // fall through to sleep default: return nil, fmt.Errorf("acme: unknown authorization status %q", raw.Status) } - if err := sleep(res.Header.Get("Retry-After")); err != nil { + if err := sleep(retry); err != nil { return nil, err } } @@ -723,7 +743,7 @@ func (c *Client) httpClient() *http.Client { } func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) { - req, err := http.NewRequest("GET", urlStr, nil) + req, err := c.newRequest("GET", urlStr, nil) if err != nil { return nil, err } @@ -731,7 +751,7 @@ func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) } func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error) { - req, err := http.NewRequest("HEAD", urlStr, nil) + req, err := c.newRequest("HEAD", urlStr, nil) if err != nil { return nil, err } @@ -739,7 +759,7 @@ func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error } func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.Reader) (*http.Response, error) { - req, err := http.NewRequest("POST", urlStr, body) + req, err := c.newRequest("POST", urlStr, body) if err != nil { return nil, err } @@ -747,6 +767,19 @@ func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.R return c.do(ctx, req) } +func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + ua := userAgent + if c.UserAgent != "" { + ua += " " + c.UserAgent + } + req.Header.Set("User-Agent", ua) + return req, nil +} + func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) { res, err := c.httpClient().Do(req.WithContext(ctx)) if err != nil { diff --git a/third_party/crypto/acme/integration_test.go b/third_party/crypto/acme/integration_test.go index c97998fc6..12ca92a5b 100644 --- a/third_party/crypto/acme/integration_test.go +++ b/third_party/crypto/acme/integration_test.go @@ -23,9 +23,9 @@ import ( ) // This test works with Pebble and Let's Encrypt staging. -// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test +// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test -tags integration_test // For Let's Encrypt you'll need a publicly accessible HTTP server like `ngrok http 8080` and then -// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test +// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test -tags integration_test func TestIntegration(t *testing.T) { dir := os.Getenv("ACME_DIRECTORY_URL") testAccountGet := os.Getenv("TEST_ACCOUNT_GET") != "" diff --git a/third_party/crypto/acme/types.go b/third_party/crypto/acme/types.go index 0810e4cf2..9a48c8bcc 100644 --- a/third_party/crypto/acme/types.go +++ b/third_party/crypto/acme/types.go @@ -20,7 +20,6 @@ const ( StatusInvalid = "invalid" StatusRevoked = "revoked" StatusDeactivated = "deactivated" - StatusReady = "ready" ) // CRLReasonCode identifies the reason for a certificate revocation. @@ -216,7 +215,7 @@ type Order struct { URL string // Status is the status of the order. It will be one of StatusPending, - // StatusReady, StatusProcessing, StatusValid, and StatusInvalid. + // StatusProcessing, StatusValid, and StatusInvalid. Status string // Expires is the teimstamp after which the server will consider the order invalid. @@ -287,6 +286,9 @@ type Authorization struct { // Identifier is the identifier that the account is authorized to represent. Identifier AuthzID + // Wildcard is true if the authorization is for the base domain of a wildcard identifier. + Wildcard bool + // Expires is the timestamp after which the server will consider this authorization invalid. Expires time.Time @@ -294,9 +296,6 @@ type Authorization struct { // to prove posession of the identifier. For valid/invalid authorizations, // this is the list of challenges that were used. Challenges []*Challenge - - // Wildcard is set to true if this authorization is for a 'wildcard' dnsName. - Wildcard bool } // AuthzID is an identifier that an account is authorized to represent. @@ -328,8 +327,8 @@ func (z *wireAuthz) authorization(url string) *Authorization { Status: z.Status, Expires: z.Expires, Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value}, - Challenges: make([]*Challenge, len(z.Challenges)), Wildcard: z.Wildcard, + Challenges: make([]*Challenge, len(z.Challenges)), } for i, v := range z.Challenges { a.Challenges[i] = v.challenge()