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 "runtime/debug"
19 "strconv"
20 "strings"
21 "time"
22 )
23
24
25
26 type retryTimer struct {
27
28
29 backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
30
31 n int
32 }
33
34 func (t *retryTimer) inc() {
35 t.n++
36 }
37
38
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
63
64
65
66
67
68 func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
69 const max = 10 * time.Second
70 var jitter time.Duration
71 if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
72
73
74
75
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 if d > max {
90 return max
91 }
92 return d
93 }
94
95
96
97
98 func retryAfter(v string) time.Duration {
99 if i, err := strconv.Atoi(v); err == nil {
100 return time.Duration(i) * time.Second
101 }
102 t, err := http.ParseTime(v)
103 if err != nil {
104 return 0
105 }
106 return t.Sub(timeNow())
107 }
108
109
110
111 type resOkay func(*http.Response) bool
112
113
114
115 func wantStatus(codes ...int) resOkay {
116 return func(res *http.Response) bool {
117 for _, code := range codes {
118 if code == res.StatusCode {
119 return true
120 }
121 }
122 return false
123 }
124 }
125
126
127
128
129
130
131 func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
132 retry := c.retryTimer()
133 for {
134 req, err := http.NewRequest("GET", url, nil)
135 if err != nil {
136 return nil, err
137 }
138 res, err := c.doNoRetry(ctx, req)
139 switch {
140 case err != nil:
141 return nil, err
142 case ok(res):
143 return res, nil
144 case isRetriable(res.StatusCode):
145 retry.inc()
146 resErr := responseError(res)
147 res.Body.Close()
148
149
150 if retry.backoff(ctx, req, res) != nil {
151 return nil, resErr
152 }
153 default:
154 defer res.Body.Close()
155 return nil, responseError(res)
156 }
157 }
158 }
159
160
161
162
163
164 func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
165 return c.post(ctx, nil, url, noPayload, ok)
166 }
167
168
169
170
171
172
173
174
175 func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
176 retry := c.retryTimer()
177 for {
178 res, req, err := c.postNoRetry(ctx, key, url, body)
179 if err != nil {
180 return nil, err
181 }
182 if ok(res) {
183 return res, nil
184 }
185 resErr := responseError(res)
186 res.Body.Close()
187 switch {
188
189
190 case isBadNonce(resErr):
191
192 c.clearNonces()
193 case !isRetriable(res.StatusCode):
194 return nil, resErr
195 }
196 retry.inc()
197
198
199 if err := retry.backoff(ctx, req, res); err != nil {
200 return nil, resErr
201 }
202 }
203 }
204
205
206
207
208
209
210
211
212
213
214
215
216
217 func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
218 kid := noKeyID
219 if key == nil {
220 if c.Key == nil {
221 return nil, nil, errors.New("acme: Client.Key must be populated to make POST requests")
222 }
223 key = c.Key
224 kid = c.accountKID(ctx)
225 }
226 nonce, err := c.popNonce(ctx, url)
227 if err != nil {
228 return nil, nil, err
229 }
230 b, err := jwsEncodeJSON(body, key, kid, nonce, url)
231 if err != nil {
232 return nil, nil, err
233 }
234 req, err := http.NewRequest("POST", url, bytes.NewReader(b))
235 if err != nil {
236 return nil, nil, err
237 }
238 req.Header.Set("Content-Type", "application/jose+json")
239 res, err := c.doNoRetry(ctx, req)
240 if err != nil {
241 return nil, nil, err
242 }
243 c.addNonce(res.Header)
244 return res, req, nil
245 }
246
247
248 func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
249 req.Header.Set("User-Agent", c.userAgent())
250 res, err := c.httpClient().Do(req.WithContext(ctx))
251 if err != nil {
252 select {
253 case <-ctx.Done():
254
255
256
257
258
259 return nil, ctx.Err()
260 default:
261 return nil, err
262 }
263 }
264 return res, nil
265 }
266
267 func (c *Client) httpClient() *http.Client {
268 if c.HTTPClient != nil {
269 return c.HTTPClient
270 }
271 return http.DefaultClient
272 }
273
274
275
276 var packageVersion string
277
278 func init() {
279
280
281 info, ok := debug.ReadBuildInfo()
282 if !ok {
283 return
284 }
285 for _, m := range info.Deps {
286 if m.Path != "golang.org/x/crypto" {
287 continue
288 }
289 if m.Replace == nil {
290 packageVersion = m.Version
291 }
292 break
293 }
294 }
295
296
297
298 func (c *Client) userAgent() string {
299 ua := "golang.org/x/crypto/acme"
300 if packageVersion != "" {
301 ua += "@" + packageVersion
302 }
303 if c.UserAgent != "" {
304 ua = c.UserAgent + " " + ua
305 }
306 return ua
307 }
308
309
310 func isBadNonce(err error) bool {
311
312
313
314
315 ae, ok := err.(*Error)
316 return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
317 }
318
319
320
321
322
323
324 func isRetriable(code int) bool {
325 return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
326 }
327
328
329 func responseError(resp *http.Response) error {
330
331
332 b, _ := io.ReadAll(resp.Body)
333 e := &wireError{Status: resp.StatusCode}
334 if err := json.Unmarshal(b, e); err != nil {
335
336
337
338 e.Detail = string(b)
339 if e.Detail == "" {
340 e.Detail = resp.Status
341 }
342 }
343 return e.error(resp.Header)
344 }
345
View as plain text