Skip to content

crypto/acme: add RetryAfter field to the Authorization, Order & Challenge objects #321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions acme/acme.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
if v.Status != StatusPending && v.Status != StatusValid {
return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
}
return v.authorization(res.Header.Get("Location")), nil
return v.authorization(res.Header.Get("Location"), 0), nil
}

// GetAuthorization retrieves an authorization identified by the given URL.
Expand All @@ -402,7 +402,8 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.authorization(url), nil
d := retryAfter(res.Header.Get("Retry-After"))
return v.authorization(url, d), nil
}

// RevokeAuthorization relinquishes an existing authorization identified
Expand Down Expand Up @@ -460,7 +461,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
case err != nil:
// Skip and retry.
case raw.Status == StatusValid:
return raw.authorization(url), nil
return raw.authorization(url, 0), nil
case raw.Status == StatusInvalid:
return nil, raw.error(url)
}
Expand Down Expand Up @@ -505,7 +506,8 @@ func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, erro
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.challenge(), nil
d := retryAfter(res.Header.Get("Retry-After"))
return v.challenge(d), nil
}

// Accept informs the server that the client accepts one of its challenges
Expand Down Expand Up @@ -534,7 +536,7 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.challenge(), nil
return v.challenge(0), nil
}

// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
Expand Down
1 change: 1 addition & 0 deletions acme/rfc8555.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ func responseOrder(res *http.Response) (*Order, error) {
AuthzURLs: v.Authorizations,
FinalizeURL: v.Finalize,
CertURL: v.Certificate,
RetryAfter: retryAfter(res.Header.Get("Retry-After")),
}
for _, id := range v.Identifiers {
o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})
Expand Down
38 changes: 31 additions & 7 deletions acme/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,13 @@ type Order struct {

// The error that occurred while processing the order as received from a CA, if any.
Error *Error

// RetryAfter specifies how long the client should wait before polling the order again,
// based on the Retry-After header provided by the server while the order is in the
// StatusProcessing state.
//
// See RFC 8555 Section 7.4.
RetryAfter time.Duration
}

// OrderOption allows customizing Client.AuthorizeOrder call.
Expand Down Expand Up @@ -426,6 +433,14 @@ type Authorization struct {
//
// This field is unused in RFC 8555.
Combinations [][]int

// RetryAfter specifies how long the client should wait before polling the
// authorization resource again, if indicated by the server.
// This corresponds to the optional Retry-After HTTP header included in a
// 200 (OK) response when the authorization is still StatusPending.
//
// See RFC 8555 Section 7.5.1.
RetryAfter time.Duration
}

// AuthzID is an identifier that an account is authorized to represent.
Expand Down Expand Up @@ -471,7 +486,7 @@ type wireAuthz struct {
Error *wireError
}

func (z *wireAuthz) authorization(uri string) *Authorization {
func (z *wireAuthz) authorization(uri string, retryAfter time.Duration) *Authorization {
a := &Authorization{
URI: uri,
Status: z.Status,
Expand All @@ -480,9 +495,10 @@ func (z *wireAuthz) authorization(uri string) *Authorization {
Wildcard: z.Wildcard,
Challenges: make([]*Challenge, len(z.Challenges)),
Combinations: z.Combinations, // shallow copy
RetryAfter: retryAfter,
}
for i, v := range z.Challenges {
a.Challenges[i] = v.challenge()
a.Challenges[i] = v.challenge(0)
}
return a
}
Expand Down Expand Up @@ -542,6 +558,13 @@ type Challenge struct {
// where the client must send additional data for the server to validate
// the challenge.
Payload json.RawMessage

// RetryAfter specifies how long the client should wait before polling the
// challenge again, based on the Retry-After header provided by the server
// while the challenge is in the StatusProcessing state.
//
// See RFC 8555 Section 8.2.
RetryAfter time.Duration
}

// wireChallenge is ACME JSON challenge representation.
Expand All @@ -555,12 +578,13 @@ type wireChallenge struct {
Error *wireError
}

func (c *wireChallenge) challenge() *Challenge {
func (c *wireChallenge) challenge(retryAfter time.Duration) *Challenge {
v := &Challenge{
URI: c.URL,
Type: c.Type,
Token: c.Token,
Status: c.Status,
URI: c.URL,
Type: c.Type,
Token: c.Token,
Status: c.Status,
RetryAfter: retryAfter,
}
if v.URI == "" {
v.URI = c.URI // c.URL was empty; use legacy
Expand Down