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