http.go (9987B)
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 "strconv" 19 "strings" 20 "time" 21 ) 22 23 // retryTimer encapsulates common logic for retrying unsuccessful requests. 24 // It is not safe for concurrent use. 25 type 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 33 func (t *retryTimer) inc() { 34 t.n++ 35 } 36 37 // backoff pauses the current goroutine as described in Client.RetryBackoff. 38 func (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 53 func (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. 67 func 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. 97 func 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. 110 type resOkay func(*http.Response) bool 111 112 // wantStatus returns a function which reports whether the code 113 // matches the status code of a response. 114 func 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. 130 func (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 RFC 8555 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. 163 func (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. 174 func (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. 216 func (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. 247 func (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 266 func (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. 275 var 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). 279 func (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. 291 func 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. 305 func 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. 310 func 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, _ := io.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 }