| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // Copyright 2018 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package acme | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"crypto" | 
					
						
							|  |  |  | 	"crypto/rand" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2021-09-10 14:42:14 +02:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	"math/big" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2024-07-08 07:33:11 +00:00
										 |  |  | 	"runtime/debug" | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // retryTimer encapsulates common logic for retrying unsuccessful requests. | 
					
						
							|  |  |  | // It is not safe for concurrent use. | 
					
						
							|  |  |  | type retryTimer struct { | 
					
						
							|  |  |  | 	// backoffFn provides backoff delay sequence for retries. | 
					
						
							|  |  |  | 	// See Client.RetryBackoff doc comment. | 
					
						
							|  |  |  | 	backoffFn func(n int, r *http.Request, res *http.Response) time.Duration | 
					
						
							|  |  |  | 	// n is the current retry attempt. | 
					
						
							|  |  |  | 	n int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *retryTimer) inc() { | 
					
						
							|  |  |  | 	t.n++ | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // backoff pauses the current goroutine as described in Client.RetryBackoff. | 
					
						
							|  |  |  | func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error { | 
					
						
							|  |  |  | 	d := t.backoffFn(t.n, r, res) | 
					
						
							|  |  |  | 	if d <= 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	wakeup := time.NewTimer(d) | 
					
						
							|  |  |  | 	defer wakeup.Stop() | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case <-ctx.Done(): | 
					
						
							|  |  |  | 		return ctx.Err() | 
					
						
							|  |  |  | 	case <-wakeup.C: | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (c *Client) retryTimer() *retryTimer { | 
					
						
							|  |  |  | 	f := c.RetryBackoff | 
					
						
							|  |  |  | 	if f == nil { | 
					
						
							|  |  |  | 		f = defaultBackoff | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return &retryTimer{backoffFn: f} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // defaultBackoff provides default Client.RetryBackoff implementation | 
					
						
							|  |  |  | // using a truncated exponential backoff algorithm, | 
					
						
							|  |  |  | // as described in Client.RetryBackoff. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The n argument is always bounded between 1 and 30. | 
					
						
							|  |  |  | // The returned value is always greater than 0. | 
					
						
							|  |  |  | func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration { | 
					
						
							| 
									
										
										
										
											2025-05-10 14:27:25 +00:00
										 |  |  | 	const maxVal = 10 * time.Second | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	var jitter time.Duration | 
					
						
							|  |  |  | 	if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil { | 
					
						
							|  |  |  | 		// Set the minimum to 1ms to avoid a case where | 
					
						
							|  |  |  | 		// an invalid Retry-After value is parsed into 0 below, | 
					
						
							|  |  |  | 		// resulting in the 0 returned value which would unintentionally | 
					
						
							|  |  |  | 		// stop the retries. | 
					
						
							|  |  |  | 		jitter = (1 + time.Duration(x.Int64())) * time.Millisecond | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if v, ok := res.Header["Retry-After"]; ok { | 
					
						
							|  |  |  | 		return retryAfter(v[0]) + jitter | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if n < 1 { | 
					
						
							|  |  |  | 		n = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if n > 30 { | 
					
						
							|  |  |  | 		n = 30 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	d := time.Duration(1<<uint(n-1))*time.Second + jitter | 
					
						
							| 
									
										
										
										
											2025-05-10 14:27:25 +00:00
										 |  |  | 	return min(d, maxVal) | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // retryAfter parses a Retry-After HTTP header value, | 
					
						
							|  |  |  | // trying to convert v into an int (seconds) or use http.ParseTime otherwise. | 
					
						
							|  |  |  | // It returns zero value if v cannot be parsed. | 
					
						
							|  |  |  | func retryAfter(v string) time.Duration { | 
					
						
							|  |  |  | 	if i, err := strconv.Atoi(v); err == nil { | 
					
						
							|  |  |  | 		return time.Duration(i) * time.Second | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t, err := http.ParseTime(v) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return t.Sub(timeNow()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // resOkay is a function that reports whether the provided response is okay. | 
					
						
							|  |  |  | // It is expected to keep the response body unread. | 
					
						
							|  |  |  | type resOkay func(*http.Response) bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // wantStatus returns a function which reports whether the code | 
					
						
							|  |  |  | // matches the status code of a response. | 
					
						
							|  |  |  | func wantStatus(codes ...int) resOkay { | 
					
						
							|  |  |  | 	return func(res *http.Response) bool { | 
					
						
							|  |  |  | 		for _, code := range codes { | 
					
						
							|  |  |  | 			if code == res.StatusCode { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // get issues an unsigned GET request to the specified URL. | 
					
						
							|  |  |  | // It returns a non-error value only when ok reports true. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // get retries unsuccessful attempts according to c.RetryBackoff | 
					
						
							|  |  |  | // until the context is done or a non-retriable error is received. | 
					
						
							|  |  |  | func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) { | 
					
						
							|  |  |  | 	retry := c.retryTimer() | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		req, err := http.NewRequest("GET", url, nil) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		res, err := c.doNoRetry(ctx, req) | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case err != nil: | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		case ok(res): | 
					
						
							|  |  |  | 			return res, nil | 
					
						
							|  |  |  | 		case isRetriable(res.StatusCode): | 
					
						
							|  |  |  | 			retry.inc() | 
					
						
							|  |  |  | 			resErr := responseError(res) | 
					
						
							|  |  |  | 			res.Body.Close() | 
					
						
							|  |  |  | 			// Ignore the error value from retry.backoff | 
					
						
							|  |  |  | 			// and return the one from last retry, as received from the CA. | 
					
						
							|  |  |  | 			if retry.backoff(ctx, req, res) != nil { | 
					
						
							|  |  |  | 				return nil, resErr | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			defer res.Body.Close() | 
					
						
							|  |  |  | 			return nil, responseError(res) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | // postAsGet is POST-as-GET, a replacement for GET in RFC 8555 | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // as described in https://tools.ietf.org/html/rfc8555#section-6.3. | 
					
						
							|  |  |  | // It makes a POST request in KID form with zero JWS payload. | 
					
						
							|  |  |  | // See nopayload doc comments in jws.go. | 
					
						
							|  |  |  | func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) { | 
					
						
							|  |  |  | 	return c.post(ctx, nil, url, noPayload, ok) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // post issues a signed POST request in JWS format using the provided key | 
					
						
							|  |  |  | // to the specified URL. If key is nil, c.Key is used instead. | 
					
						
							|  |  |  | // It returns a non-error value only when ok reports true. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // post retries unsuccessful attempts according to c.RetryBackoff | 
					
						
							|  |  |  | // until the context is done or a non-retriable error is received. | 
					
						
							|  |  |  | // It uses postNoRetry to make individual requests. | 
					
						
							|  |  |  | func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) { | 
					
						
							|  |  |  | 	retry := c.retryTimer() | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		res, req, err := c.postNoRetry(ctx, key, url, body) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if ok(res) { | 
					
						
							|  |  |  | 			return res, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		resErr := responseError(res) | 
					
						
							|  |  |  | 		res.Body.Close() | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		// Check for bad nonce before isRetriable because it may have been returned | 
					
						
							|  |  |  | 		// with an unretriable response code such as 400 Bad Request. | 
					
						
							|  |  |  | 		case isBadNonce(resErr): | 
					
						
							|  |  |  | 			// Consider any previously stored nonce values to be invalid. | 
					
						
							|  |  |  | 			c.clearNonces() | 
					
						
							|  |  |  | 		case !isRetriable(res.StatusCode): | 
					
						
							|  |  |  | 			return nil, resErr | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		retry.inc() | 
					
						
							|  |  |  | 		// Ignore the error value from retry.backoff | 
					
						
							|  |  |  | 		// and return the one from last retry, as received from the CA. | 
					
						
							|  |  |  | 		if err := retry.backoff(ctx, req, res); err != nil { | 
					
						
							|  |  |  | 			return nil, resErr | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // postNoRetry signs the body with the given key and POSTs it to the provided url. | 
					
						
							|  |  |  | // It is used by c.post to retry unsuccessful attempts. | 
					
						
							|  |  |  | // The body argument must be JSON-serializable. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If key argument is nil, c.Key is used to sign the request. | 
					
						
							|  |  |  | // If key argument is nil and c.accountKID returns a non-zero keyID, | 
					
						
							|  |  |  | // the request is sent in KID form. Otherwise, JWK form is used. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // In practice, when interfacing with RFC-compliant CAs most requests are sent in KID form | 
					
						
							|  |  |  | // and JWK is used only when KID is unavailable: new account endpoint and certificate | 
					
						
							|  |  |  | // revocation requests authenticated by a cert key. | 
					
						
							|  |  |  | // See jwsEncodeJSON for other details. | 
					
						
							|  |  |  | func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) { | 
					
						
							|  |  |  | 	kid := noKeyID | 
					
						
							|  |  |  | 	if key == nil { | 
					
						
							| 
									
										
										
										
											2021-09-10 14:42:14 +02:00
										 |  |  | 		if c.Key == nil { | 
					
						
							|  |  |  | 			return nil, nil, errors.New("acme: Client.Key must be populated to make POST requests") | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 		key = c.Key | 
					
						
							|  |  |  | 		kid = c.accountKID(ctx) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	nonce, err := c.popNonce(ctx, url) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b, err := jwsEncodeJSON(body, key, kid, nonce, url) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req, err := http.NewRequest("POST", url, bytes.NewReader(b)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req.Header.Set("Content-Type", "application/jose+json") | 
					
						
							|  |  |  | 	res, err := c.doNoRetry(ctx, req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	c.addNonce(res.Header) | 
					
						
							|  |  |  | 	return res, req, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // doNoRetry issues a request req, replacing its context (if any) with ctx. | 
					
						
							|  |  |  | func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) { | 
					
						
							|  |  |  | 	req.Header.Set("User-Agent", c.userAgent()) | 
					
						
							|  |  |  | 	res, err := c.httpClient().Do(req.WithContext(ctx)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			// Prefer the unadorned context error. | 
					
						
							|  |  |  | 			// (The acme package had tests assuming this, previously from ctxhttp's | 
					
						
							|  |  |  | 			// behavior, predating net/http supporting contexts natively) | 
					
						
							|  |  |  | 			// TODO(bradfitz): reconsider this in the future. But for now this | 
					
						
							|  |  |  | 			// requires no test updates. | 
					
						
							|  |  |  | 			return nil, ctx.Err() | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return res, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (c *Client) httpClient() *http.Client { | 
					
						
							|  |  |  | 	if c.HTTPClient != nil { | 
					
						
							|  |  |  | 		return c.HTTPClient | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return http.DefaultClient | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // packageVersion is the version of the module that contains this package, for | 
					
						
							| 
									
										
										
										
											2024-07-08 07:33:11 +00:00
										 |  |  | // sending as part of the User-Agent header. | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | var packageVersion string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-08 07:33:11 +00:00
										 |  |  | func init() { | 
					
						
							|  |  |  | 	// Set packageVersion if the binary was built in modules mode and x/crypto | 
					
						
							|  |  |  | 	// was not replaced with a different module. | 
					
						
							|  |  |  | 	info, ok := debug.ReadBuildInfo() | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, m := range info.Deps { | 
					
						
							|  |  |  | 		if m.Path != "golang.org/x/crypto" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if m.Replace == nil { | 
					
						
							|  |  |  | 			packageVersion = m.Version | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // userAgent returns the User-Agent header value. It includes the package name, | 
					
						
							|  |  |  | // the module version (if available), and the c.UserAgent value (if set). | 
					
						
							|  |  |  | func (c *Client) userAgent() string { | 
					
						
							|  |  |  | 	ua := "golang.org/x/crypto/acme" | 
					
						
							|  |  |  | 	if packageVersion != "" { | 
					
						
							|  |  |  | 		ua += "@" + packageVersion | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if c.UserAgent != "" { | 
					
						
							|  |  |  | 		ua = c.UserAgent + " " + ua | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ua | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // isBadNonce reports whether err is an ACME "badnonce" error. | 
					
						
							|  |  |  | func isBadNonce(err error) bool { | 
					
						
							|  |  |  | 	// According to the spec badNonce is urn:ietf:params:acme:error:badNonce. | 
					
						
							|  |  |  | 	// However, ACME servers in the wild return their versions of the error. | 
					
						
							|  |  |  | 	// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4 | 
					
						
							|  |  |  | 	// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66. | 
					
						
							|  |  |  | 	ae, ok := err.(*Error) | 
					
						
							|  |  |  | 	return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // isRetriable reports whether a request can be retried | 
					
						
							|  |  |  | // based on the response status code. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code. | 
					
						
							|  |  |  | // Callers should parse the response and check with isBadNonce. | 
					
						
							|  |  |  | func isRetriable(code int) bool { | 
					
						
							|  |  |  | 	return code <= 399 || code >= 500 || code == http.StatusTooManyRequests | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // responseError creates an error of Error type from resp. | 
					
						
							|  |  |  | func responseError(resp *http.Response) error { | 
					
						
							|  |  |  | 	// don't care if ReadAll returns an error: | 
					
						
							|  |  |  | 	// json.Unmarshal will fail in that case anyway | 
					
						
							| 
									
										
										
										
											2023-01-30 10:45:34 +01:00
										 |  |  | 	b, _ := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	e := &wireError{Status: resp.StatusCode} | 
					
						
							|  |  |  | 	if err := json.Unmarshal(b, e); err != nil { | 
					
						
							|  |  |  | 		// this is not a regular error response: | 
					
						
							|  |  |  | 		// populate detail with anything we received, | 
					
						
							|  |  |  | 		// e.Status will already contain HTTP response code value | 
					
						
							|  |  |  | 		e.Detail = string(b) | 
					
						
							|  |  |  | 		if e.Detail == "" { | 
					
						
							|  |  |  | 			e.Detail = resp.Status | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return e.error(resp.Header) | 
					
						
							|  |  |  | } |