http.go raw

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