From 7b320119ba532fd409ec7dade7ad02011c309599 Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Wed, 18 Oct 2017 13:15:14 +0100 Subject: Update dependencies --- vendor/golang.org/x/crypto/acme/acme.go | 154 +++++++++++++++++--------------- 1 file changed, 80 insertions(+), 74 deletions(-) (limited to 'vendor/golang.org/x/crypto/acme/acme.go') diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go index 140d422..e8388b0 100644 --- a/vendor/golang.org/x/crypto/acme/acme.go +++ b/vendor/golang.org/x/crypto/acme/acme.go @@ -51,38 +51,6 @@ const ( maxNonces = 100 ) -// CertOption is an optional argument type for Client methods which manipulate -// certificate data. -type CertOption interface { - privateCertOpt() -} - -// WithKey creates an option holding a private/public key pair. -// The private part signs a certificate, and the public part represents the signee. -func WithKey(key crypto.Signer) CertOption { - return &certOptKey{key} -} - -type certOptKey struct { - key crypto.Signer -} - -func (*certOptKey) privateCertOpt() {} - -// WithTemplate creates an option for specifying a certificate template. -// See x509.CreateCertificate for template usage details. -// -// In TLSSNIxChallengeCert methods, the template is also used as parent, -// resulting in a self-signed certificate. -// The DNSNames field of t is always overwritten for tls-sni challenge certs. -func WithTemplate(t *x509.Certificate) CertOption { - return (*certOptTemplate)(t) -} - -type certOptTemplate x509.Certificate - -func (*certOptTemplate) privateCertOpt() {} - // Client is an ACME client. // The only required field is Key. An example of creating a client with a new key // is as follows: @@ -152,7 +120,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) { CAA []string `json:"caa-identities"` } } - if json.NewDecoder(res.Body).Decode(&v); err != nil { + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { return Directory{}, err } c.dir = &Directory{ @@ -198,7 +166,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, req.NotAfter = now.Add(exp).Format(time.RFC3339) } - res, err := c.postJWS(ctx, c.Key, c.dir.CertURL, req) + res, err := c.retryPostJWS(ctx, c.Key, c.dir.CertURL, req) if err != nil { return nil, "", err } @@ -207,7 +175,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, return nil, "", responseError(res) } - curl := res.Header.Get("location") // cert permanent URL + curl := res.Header.Get("Location") // cert permanent URL if res.ContentLength == 0 { // no cert in the body; poll until we get it cert, err := c.FetchCert(ctx, curl, bundle) @@ -240,7 +208,7 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by if res.StatusCode > 299 { return nil, responseError(res) } - d := retryAfter(res.Header.Get("retry-after"), 3*time.Second) + d := retryAfter(res.Header.Get("Retry-After"), 3*time.Second) select { case <-time.After(d): // retry @@ -273,7 +241,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, if key == nil { key = c.Key } - res, err := c.postJWS(ctx, key, c.dir.RevokeURL, body) + res, err := c.retryPostJWS(ctx, key, c.dir.RevokeURL, body) if err != nil { return err } @@ -289,7 +257,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, func AcceptTOS(tosURL string) bool { return true } // Register creates a new account registration by following the "new-reg" flow. -// It returns registered account. The a argument is not modified. +// It returns registered account. The account is not modified. // // The registration may require the caller to agree to the CA's Terms of Service (TOS). // If so, and the account has not indicated the acceptance of the terms (see Account for details), @@ -361,7 +329,7 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, Resource: "new-authz", Identifier: authzID{Type: "dns", Value: domain}, } - res, err := c.postJWS(ctx, c.Key, c.dir.AuthzURL, req) + res, err := c.retryPostJWS(ctx, c.Key, c.dir.AuthzURL, req) if err != nil { return nil, err } @@ -419,7 +387,7 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { Status: "deactivated", Delete: true, } - res, err := c.postJWS(ctx, c.Key, url, req) + res, err := c.retryPostJWS(ctx, c.Key, url, req) if err != nil { return err } @@ -436,29 +404,15 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { // // 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, the returned error is ErrAuthorizationFailed. +// If the Status is StatusInvalid, the returned error is of type *AuthorizationError. func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) { - var count int - sleep := func(v string, inc int) error { - count += inc - d := backoff(count, 10*time.Second) - d = retryAfter(v, d) - wakeup := time.NewTimer(d) - defer wakeup.Stop() - select { - case <-ctx.Done(): - return ctx.Err() - case <-wakeup.C: - return nil - } - } - + sleep := sleeper(ctx) for { res, err := c.get(ctx, url) if err != nil { return nil, err } - retry := res.Header.Get("retry-after") + retry := res.Header.Get("Retry-After") if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { res.Body.Close() if err := sleep(retry, 1); err != nil { @@ -479,7 +433,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat return raw.authorization(url), nil } if raw.Status == StatusInvalid { - return nil, ErrAuthorizationFailed + return nil, raw.error(url) } if err := sleep(retry, 0); err != nil { return nil, err @@ -525,7 +479,7 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error Type: chal.Type, Auth: auth, } - res, err := c.postJWS(ctx, c.Key, chal.URI, req) + res, err := c.retryPostJWS(ctx, c.Key, chal.URI, req) if err != nil { return nil, err } @@ -658,7 +612,7 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun req.Contact = acct.Contact req.Agreement = acct.AgreedTerms } - res, err := c.postJWS(ctx, c.Key, url, req) + res, err := c.retryPostJWS(ctx, c.Key, url, req) if err != nil { return nil, err } @@ -695,6 +649,40 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun }, nil } +// retryPostJWS will retry calls to postJWS if there is a badNonce error, +// clearing the stored nonces after each error. +// If the response was 4XX-5XX, then responseError is called on the body, +// the body is closed, and the error returned. +func (c *Client) retryPostJWS(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) { + sleep := sleeper(ctx) + for { + res, err := c.postJWS(ctx, key, url, body) + if err != nil { + return nil, err + } + // handle errors 4XX-5XX with responseError + if res.StatusCode >= 400 && res.StatusCode <= 599 { + err := responseError(res) + res.Body.Close() + // according to spec badNonce is urn:ietf:params:acme:error:badNonce + // however, acme servers in the wild return their version of the error + // https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4 + if ae, ok := err.(*Error); ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce") { + // clear any nonces that we might've stored that might now be + // considered bad + c.clearNonces() + retry := res.Header.Get("Retry-After") + if err := sleep(retry, 1); err != nil { + return nil, err + } + continue + } + return nil, err + } + return res, nil + } +} + // postJWS signs the body with the given key and POSTs it to the provided url. // The body argument must be JSON-serializable. func (c *Client) postJWS(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) { @@ -730,6 +718,13 @@ func (c *Client) popNonce(ctx context.Context, url string) (string, error) { return nonce, nil } +// clearNonces clears any stored nonces +func (c *Client) clearNonces() { + c.noncesMu.Lock() + defer c.noncesMu.Unlock() + c.nonces = make(map[string]struct{}) +} + // addNonce stores a nonce value found in h (if any) for future use. func (c *Client) addNonce(h http.Header) { v := nonceFromHeader(h) @@ -855,14 +850,8 @@ func responseError(resp *http.Response) error { // don't care if ReadAll returns an error: // json.Unmarshal will fail in that case anyway b, _ := ioutil.ReadAll(resp.Body) - e := struct { - Status int - Type string - Detail string - }{ - Status: resp.StatusCode, - } - if err := json.Unmarshal(b, &e); err != nil { + e := &wireError{Status: resp.StatusCode} + if err := json.Unmarshal(b, e); err != nil { // this is not a regular error response: // populate detail with anything we received, // e.Status will already contain HTTP response code value @@ -871,12 +860,7 @@ func responseError(resp *http.Response) error { e.Detail = resp.Status } } - return &Error{ - StatusCode: e.Status, - ProblemType: e.Type, - Detail: e.Detail, - Header: resp.Header, - } + return e.error(resp.Header) } // chainCert fetches CA certificate chain recursively by following "up" links. @@ -941,6 +925,28 @@ func linkHeader(h http.Header, rel string) []string { return links } +// sleeper returns a function that accepts the Retry-After HTTP header value +// and an increment that's used with backoff to increasingly sleep on +// consecutive calls until the context is done. If the Retry-After header +// cannot be parsed, then backoff is used with a maximum sleep time of 10 +// seconds. +func sleeper(ctx context.Context) func(ra string, inc int) error { + var count int + return func(ra string, inc int) error { + count += inc + d := backoff(count, 10*time.Second) + d = retryAfter(ra, d) + wakeup := time.NewTimer(d) + defer wakeup.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-wakeup.C: + return nil + } + } +} + // retryAfter parses a Retry-After HTTP header value, // trying to convert v into an int (seconds) or use http.ParseTime otherwise. // It returns d if v cannot be parsed. -- cgit v1.2.3