Merge latest changes from upstream crypto/acme library

This commit is contained in:
James Munnelly 2018-07-05 10:27:09 +01:00
parent 959aba219f
commit 41d7136bf5
3 changed files with 55 additions and 23 deletions

View File

@ -46,6 +46,10 @@ const (
// Max number of collected nonces kept in memory.
// Expect usual peak of 1 or 2.
maxNonces = 100
// User-Agent, bump the version each time a change is made to the
// handling of API requests.
userAgent = "go-acme/2"
)
// Client is an ACME client.
@ -73,6 +77,11 @@ type Client struct {
// will have no effect.
DirectoryURL string
// UserAgent is an optional string that identifies this client and
// version to the ACME server. It should be set to something like
// "myclient/1.2.3".
UserAgent string
noncesMu sync.Mutex
nonces map[string]struct{} // nonces collected from previous responses
@ -274,6 +283,13 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
sleep := timeSleeper(ctx)
for {
o, err := c.GetOrder(ctx, url)
if e, ok := err.(*Error); ok && e.StatusCode >= 500 && e.StatusCode <= 599 {
// retriable 5xx error
if err := sleep(retryAfter(e.Header.Get("Retry-After"))); err != nil {
return nil, err
}
continue
}
if err != nil {
return nil, err
}
@ -392,14 +408,14 @@ func (c *Client) DeactivateAuthorization(ctx context.Context, url string) error
return nil
}
// WaitAuthorization retrieves authorization details. If the authorization is not in
// a final state (StatusValid/StatusInvalid), it retries the request until the authorization
// is final, ctx is cancelled by the caller, an error response is received, or the ACME CA
// responded with a 4xx error.
// WaitAuthorization polls an authorization at the given URL
// until it is in one of the final states, StatusValid or StatusInvalid,
// the ACME CA responded with a 4xx error code, or the context is done.
//
// It returns a non-nil Authorization only if its Status is StatusValid.
// In all other cases WaitAuthorization returns an error.
// If the Status is StatusInvalid or StatusDeactivated, the returned error will be of type AuthorizationError.
// If the Status is StatusInvalid, StatusDeactivated, or StatusRevoked the
// returned error will be of type AuthorizationError.
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
sleep := sleeper(ctx)
for {
@ -407,17 +423,21 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
err = responseError(res)
res.Body.Close()
return nil, err
}
if res.StatusCode >= 400 && res.StatusCode <= 499 {
// Non-retriable error. For instance, Let's Encrypt may return 404 Not Found
// when requesting an expired authorization.
defer res.Body.Close()
return nil, responseError(res)
}
retry := res.Header.Get("Retry-After")
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
res.Body.Close()
if err := sleep(retry); err != nil {
return nil, err
}
continue
}
var raw wireAuthz
err = json.NewDecoder(res.Body).Decode(&raw)
res.Body.Close()
@ -427,13 +447,13 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
switch raw.Status {
case StatusValid:
return raw.authorization(url), nil
case StatusInvalid, StatusDeactivated:
case StatusInvalid, StatusDeactivated, StatusRevoked:
return nil, AuthorizationError{raw.authorization(url)}
case StatusPending, StatusProcessing: // fall through to sleep
default:
return nil, fmt.Errorf("acme: unknown authorization status %q", raw.Status)
}
if err := sleep(res.Header.Get("Retry-After")); err != nil {
if err := sleep(retry); err != nil {
return nil, err
}
}
@ -723,7 +743,7 @@ func (c *Client) httpClient() *http.Client {
}
func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) {
req, err := http.NewRequest("GET", urlStr, nil)
req, err := c.newRequest("GET", urlStr, nil)
if err != nil {
return nil, err
}
@ -731,7 +751,7 @@ func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error)
}
func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error) {
req, err := http.NewRequest("HEAD", urlStr, nil)
req, err := c.newRequest("HEAD", urlStr, nil)
if err != nil {
return nil, err
}
@ -739,7 +759,7 @@ func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error
}
func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest("POST", urlStr, body)
req, err := c.newRequest("POST", urlStr, body)
if err != nil {
return nil, err
}
@ -747,6 +767,19 @@ func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.R
return c.do(ctx, req)
}
func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
ua := userAgent
if c.UserAgent != "" {
ua += " " + c.UserAgent
}
req.Header.Set("User-Agent", ua)
return req, nil
}
func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) {
res, err := c.httpClient().Do(req.WithContext(ctx))
if err != nil {

View File

@ -23,9 +23,9 @@ import (
)
// This test works with Pebble and Let's Encrypt staging.
// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test
// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test -tags integration_test
// For Let's Encrypt you'll need a publicly accessible HTTP server like `ngrok http 8080` and then
// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test
// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test -tags integration_test
func TestIntegration(t *testing.T) {
dir := os.Getenv("ACME_DIRECTORY_URL")
testAccountGet := os.Getenv("TEST_ACCOUNT_GET") != ""

View File

@ -20,7 +20,6 @@ const (
StatusInvalid = "invalid"
StatusRevoked = "revoked"
StatusDeactivated = "deactivated"
StatusReady = "ready"
)
// CRLReasonCode identifies the reason for a certificate revocation.
@ -216,7 +215,7 @@ type Order struct {
URL string
// Status is the status of the order. It will be one of StatusPending,
// StatusReady, StatusProcessing, StatusValid, and StatusInvalid.
// StatusProcessing, StatusValid, and StatusInvalid.
Status string
// Expires is the teimstamp after which the server will consider the order invalid.
@ -287,6 +286,9 @@ type Authorization struct {
// Identifier is the identifier that the account is authorized to represent.
Identifier AuthzID
// Wildcard is true if the authorization is for the base domain of a wildcard identifier.
Wildcard bool
// Expires is the timestamp after which the server will consider this authorization invalid.
Expires time.Time
@ -294,9 +296,6 @@ type Authorization struct {
// to prove posession of the identifier. For valid/invalid authorizations,
// this is the list of challenges that were used.
Challenges []*Challenge
// Wildcard is set to true if this authorization is for a 'wildcard' dnsName.
Wildcard bool
}
// AuthzID is an identifier that an account is authorized to represent.
@ -328,8 +327,8 @@ func (z *wireAuthz) authorization(url string) *Authorization {
Status: z.Status,
Expires: z.Expires,
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
Challenges: make([]*Challenge, len(z.Challenges)),
Wildcard: z.Wildcard,
Challenges: make([]*Challenge, len(z.Challenges)),
}
for i, v := range z.Challenges {
a.Challenges[i] = v.challenge()