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