1
2
3
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
24
25 type retryTimer struct {
26
27
28 backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
29
30 n int
31 }
32
33 func (t *retryTimer) inc() {
34 t.n++
35 }
36
37
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
62
63
64
65
66
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
72
73
74
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
95
96
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
109
110 type resOkay func(*http.Response) bool
111
112
113
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
126
127
128
129
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
148
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
160
161
162
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
168
169
170
171
172
173
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
188
189 case isBadNonce(resErr):
190
191 c.clearNonces()
192 case !isRetriable(res.StatusCode):
193 return nil, resErr
194 }
195 retry.inc()
196
197
198 if err := retry.backoff(ctx, req, res); err != nil {
199 return nil, resErr
200 }
201 }
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
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
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
254
255
256
257
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
274
275 var packageVersion string
276
277
278
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
291 func isBadNonce(err error) bool {
292
293
294
295
296 ae, ok := err.(*Error)
297 return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
298 }
299
300
301
302
303
304
305 func isRetriable(code int) bool {
306 return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
307 }
308
309
310 func responseError(resp *http.Response) error {
311
312
313 b, _ := io.ReadAll(resp.Body)
314 e := &wireError{Status: resp.StatusCode}
315 if err := json.Unmarshal(b, e); err != nil {
316
317
318
319 e.Detail = string(b)
320 if e.Detail == "" {
321 e.Detail = resp.Status
322 }
323 }
324 return e.error(resp.Header)
325 }
326
View as plain text