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