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	"errors"
14	"fmt"
15	"io/ioutil"
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.
25type 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
33func (t *retryTimer) inc() {
34	t.n++
35}
36
37// backoff pauses the current goroutine as described in Client.RetryBackoff.
38func (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
53func (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.
67func 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.
97func 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.
110type resOkay func(*http.Response) bool
111
112// wantStatus returns a function which reports whether the code
113// matches the status code of a response.
114func 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.
130func (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 RFC8555
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.
163func (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.
174func (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.
216func (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.
247func (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
266func (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.
275var 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).
279func (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.
291func 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.
305func 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.
310func 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, _ := ioutil.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