...

Source file src/golang.org/x/crypto/acme/http.go

Documentation: golang.org/x/crypto/acme

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package acme
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto"
    11  	"crypto/rand"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"math/big"
    17  	"net/http"
    18  	"strconv"
    19  	"strings"
    20  	"time"
    21  )
    22  
    23  // retryTimer encapsulates common logic for retrying unsuccessful requests.
    24  // It is not safe for concurrent use.
    25  type retryTimer struct {
    26  	// backoffFn provides backoff delay sequence for retries.
    27  	// See Client.RetryBackoff doc comment.
    28  	backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
    29  	// n is the current retry attempt.
    30  	n int
    31  }
    32  
    33  func (t *retryTimer) inc() {
    34  	t.n++
    35  }
    36  
    37  // backoff pauses the current goroutine as described in Client.RetryBackoff.
    38  func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
    39  	d := t.backoffFn(t.n, r, res)
    40  	if d <= 0 {
    41  		return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
    42  	}
    43  	wakeup := time.NewTimer(d)
    44  	defer wakeup.Stop()
    45  	select {
    46  	case <-ctx.Done():
    47  		return ctx.Err()
    48  	case <-wakeup.C:
    49  		return nil
    50  	}
    51  }
    52  
    53  func (c *Client) retryTimer() *retryTimer {
    54  	f := c.RetryBackoff
    55  	if f == nil {
    56  		f = defaultBackoff
    57  	}
    58  	return &retryTimer{backoffFn: f}
    59  }
    60  
    61  // defaultBackoff provides default Client.RetryBackoff implementation
    62  // using a truncated exponential backoff algorithm,
    63  // as described in Client.RetryBackoff.
    64  //
    65  // The n argument is always bounded between 1 and 30.
    66  // The returned value is always greater than 0.
    67  func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
    68  	const max = 10 * time.Second
    69  	var jitter time.Duration
    70  	if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
    71  		// Set the minimum to 1ms to avoid a case where
    72  		// an invalid Retry-After value is parsed into 0 below,
    73  		// resulting in the 0 returned value which would unintentionally
    74  		// stop the retries.
    75  		jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
    76  	}
    77  	if v, ok := res.Header["Retry-After"]; ok {
    78  		return retryAfter(v[0]) + jitter
    79  	}
    80  
    81  	if n < 1 {
    82  		n = 1
    83  	}
    84  	if n > 30 {
    85  		n = 30
    86  	}
    87  	d := time.Duration(1<<uint(n-1))*time.Second + jitter
    88  	if d > max {
    89  		return max
    90  	}
    91  	return d
    92  }
    93  
    94  // retryAfter parses a Retry-After HTTP header value,
    95  // trying to convert v into an int (seconds) or use http.ParseTime otherwise.
    96  // It returns zero value if v cannot be parsed.
    97  func retryAfter(v string) time.Duration {
    98  	if i, err := strconv.Atoi(v); err == nil {
    99  		return time.Duration(i) * time.Second
   100  	}
   101  	t, err := http.ParseTime(v)
   102  	if err != nil {
   103  		return 0
   104  	}
   105  	return t.Sub(timeNow())
   106  }
   107  
   108  // resOkay is a function that reports whether the provided response is okay.
   109  // It is expected to keep the response body unread.
   110  type resOkay func(*http.Response) bool
   111  
   112  // wantStatus returns a function which reports whether the code
   113  // matches the status code of a response.
   114  func wantStatus(codes ...int) resOkay {
   115  	return func(res *http.Response) bool {
   116  		for _, code := range codes {
   117  			if code == res.StatusCode {
   118  				return true
   119  			}
   120  		}
   121  		return false
   122  	}
   123  }
   124  
   125  // get issues an unsigned GET request to the specified URL.
   126  // It returns a non-error value only when ok reports true.
   127  //
   128  // get retries unsuccessful attempts according to c.RetryBackoff
   129  // until the context is done or a non-retriable error is received.
   130  func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
   131  	retry := c.retryTimer()
   132  	for {
   133  		req, err := http.NewRequest("GET", url, nil)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  		res, err := c.doNoRetry(ctx, req)
   138  		switch {
   139  		case err != nil:
   140  			return nil, err
   141  		case ok(res):
   142  			return res, nil
   143  		case isRetriable(res.StatusCode):
   144  			retry.inc()
   145  			resErr := responseError(res)
   146  			res.Body.Close()
   147  			// Ignore the error value from retry.backoff
   148  			// and return the one from last retry, as received from the CA.
   149  			if retry.backoff(ctx, req, res) != nil {
   150  				return nil, resErr
   151  			}
   152  		default:
   153  			defer res.Body.Close()
   154  			return nil, responseError(res)
   155  		}
   156  	}
   157  }
   158  
   159  // postAsGet is POST-as-GET, a replacement for GET in RFC 8555
   160  // as described in https://tools.ietf.org/html/rfc8555#section-6.3.
   161  // It makes a POST request in KID form with zero JWS payload.
   162  // See nopayload doc comments in jws.go.
   163  func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
   164  	return c.post(ctx, nil, url, noPayload, ok)
   165  }
   166  
   167  // post issues a signed POST request in JWS format using the provided key
   168  // to the specified URL. If key is nil, c.Key is used instead.
   169  // It returns a non-error value only when ok reports true.
   170  //
   171  // post retries unsuccessful attempts according to c.RetryBackoff
   172  // until the context is done or a non-retriable error is received.
   173  // It uses postNoRetry to make individual requests.
   174  func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
   175  	retry := c.retryTimer()
   176  	for {
   177  		res, req, err := c.postNoRetry(ctx, key, url, body)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  		if ok(res) {
   182  			return res, nil
   183  		}
   184  		resErr := responseError(res)
   185  		res.Body.Close()
   186  		switch {
   187  		// Check for bad nonce before isRetriable because it may have been returned
   188  		// with an unretriable response code such as 400 Bad Request.
   189  		case isBadNonce(resErr):
   190  			// Consider any previously stored nonce values to be invalid.
   191  			c.clearNonces()
   192  		case !isRetriable(res.StatusCode):
   193  			return nil, resErr
   194  		}
   195  		retry.inc()
   196  		// Ignore the error value from retry.backoff
   197  		// and return the one from last retry, as received from the CA.
   198  		if err := retry.backoff(ctx, req, res); err != nil {
   199  			return nil, resErr
   200  		}
   201  	}
   202  }
   203  
   204  // postNoRetry signs the body with the given key and POSTs it to the provided url.
   205  // It is used by c.post to retry unsuccessful attempts.
   206  // The body argument must be JSON-serializable.
   207  //
   208  // If key argument is nil, c.Key is used to sign the request.
   209  // If key argument is nil and c.accountKID returns a non-zero keyID,
   210  // the request is sent in KID form. Otherwise, JWK form is used.
   211  //
   212  // In practice, when interfacing with RFC-compliant CAs most requests are sent in KID form
   213  // and JWK is used only when KID is unavailable: new account endpoint and certificate
   214  // revocation requests authenticated by a cert key.
   215  // See jwsEncodeJSON for other details.
   216  func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
   217  	kid := noKeyID
   218  	if key == nil {
   219  		if c.Key == nil {
   220  			return nil, nil, errors.New("acme: Client.Key must be populated to make POST requests")
   221  		}
   222  		key = c.Key
   223  		kid = c.accountKID(ctx)
   224  	}
   225  	nonce, err := c.popNonce(ctx, url)
   226  	if err != nil {
   227  		return nil, nil, err
   228  	}
   229  	b, err := jwsEncodeJSON(body, key, kid, nonce, url)
   230  	if err != nil {
   231  		return nil, nil, err
   232  	}
   233  	req, err := http.NewRequest("POST", url, bytes.NewReader(b))
   234  	if err != nil {
   235  		return nil, nil, err
   236  	}
   237  	req.Header.Set("Content-Type", "application/jose+json")
   238  	res, err := c.doNoRetry(ctx, req)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  	c.addNonce(res.Header)
   243  	return res, req, nil
   244  }
   245  
   246  // doNoRetry issues a request req, replacing its context (if any) with ctx.
   247  func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
   248  	req.Header.Set("User-Agent", c.userAgent())
   249  	res, err := c.httpClient().Do(req.WithContext(ctx))
   250  	if err != nil {
   251  		select {
   252  		case <-ctx.Done():
   253  			// Prefer the unadorned context error.
   254  			// (The acme package had tests assuming this, previously from ctxhttp's
   255  			// behavior, predating net/http supporting contexts natively)
   256  			// TODO(bradfitz): reconsider this in the future. But for now this
   257  			// requires no test updates.
   258  			return nil, ctx.Err()
   259  		default:
   260  			return nil, err
   261  		}
   262  	}
   263  	return res, nil
   264  }
   265  
   266  func (c *Client) httpClient() *http.Client {
   267  	if c.HTTPClient != nil {
   268  		return c.HTTPClient
   269  	}
   270  	return http.DefaultClient
   271  }
   272  
   273  // packageVersion is the version of the module that contains this package, for
   274  // sending as part of the User-Agent header. It's set in version_go112.go.
   275  var packageVersion string
   276  
   277  // userAgent returns the User-Agent header value. It includes the package name,
   278  // the module version (if available), and the c.UserAgent value (if set).
   279  func (c *Client) userAgent() string {
   280  	ua := "golang.org/x/crypto/acme"
   281  	if packageVersion != "" {
   282  		ua += "@" + packageVersion
   283  	}
   284  	if c.UserAgent != "" {
   285  		ua = c.UserAgent + " " + ua
   286  	}
   287  	return ua
   288  }
   289  
   290  // isBadNonce reports whether err is an ACME "badnonce" error.
   291  func isBadNonce(err error) bool {
   292  	// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
   293  	// However, ACME servers in the wild return their versions of the error.
   294  	// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
   295  	// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
   296  	ae, ok := err.(*Error)
   297  	return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
   298  }
   299  
   300  // isRetriable reports whether a request can be retried
   301  // based on the response status code.
   302  //
   303  // Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
   304  // Callers should parse the response and check with isBadNonce.
   305  func isRetriable(code int) bool {
   306  	return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
   307  }
   308  
   309  // responseError creates an error of Error type from resp.
   310  func responseError(resp *http.Response) error {
   311  	// don't care if ReadAll returns an error:
   312  	// json.Unmarshal will fail in that case anyway
   313  	b, _ := io.ReadAll(resp.Body)
   314  	e := &wireError{Status: resp.StatusCode}
   315  	if err := json.Unmarshal(b, e); err != nil {
   316  		// this is not a regular error response:
   317  		// populate detail with anything we received,
   318  		// e.Status will already contain HTTP response code value
   319  		e.Detail = string(b)
   320  		if e.Detail == "" {
   321  			e.Detail = resp.Status
   322  		}
   323  	}
   324  	return e.error(resp.Header)
   325  }
   326  

View as plain text