Skip to content

Commit

Permalink
fix(ari): retry on alreadyReplaced error
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez committed Mar 9, 2025
1 parent 2bc147f commit fd28a40
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 3 deletions.
4 changes: 4 additions & 0 deletions acme/api/internal/sender/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ func checkError(req *http.Request, resp *http.Response) error {
return &acme.NonceError{ProblemDetails: errorDetails}
}

if errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr {
return &acme.AlreadyReplacedError{ProblemDetails: errorDetails}
}

return errorDetails
}
return nil
Expand Down
15 changes: 14 additions & 1 deletion acme/api/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,20 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm
var order acme.Order
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
if err != nil {
return acme.ExtendedOrder{}, err
are := &acme.AlreadyReplacedError{}
if !errors.As(err, &are) {
return acme.ExtendedOrder{}, err
}

// If the Server rejects the request because the identified certificate has already been marked as replaced,
// it MUST return an HTTP 409 (Conflict) with a problem document of type "alreadyReplaced" (see Section 7.4).
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5
orderReq.Replaces = ""

resp, err = o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
if err != nil {
return acme.ExtendedOrder{}, err
}
}

return acme.ExtendedOrder{
Expand Down
2 changes: 2 additions & 0 deletions acme/api/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ func TestOrderService_NewWithOptions(t *testing.T) {
Status: acme.StatusValid,
Expires: order.Expires,
Identifiers: order.Identifiers,
Profile: order.Profile,
NotBefore: order.NotBefore,
NotAfter: order.NotAfter,
Error: order.Error,
Authorizations: order.Authorizations,
Finalize: order.Finalize,
Certificate: order.Certificate,
Replaces: order.Replaces,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down
12 changes: 10 additions & 2 deletions acme/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (

// Errors types.
const (
errNS = "urn:ietf:params:acme:error:"
BadNonceErr = errNS + "badNonce"
errNS = "urn:ietf:params:acme:error:"
BadNonceErr = errNS + "badNonce"
AlreadyReplacedErr = errNS + "alreadyReplaced"
)

// ProblemDetails the problem details object.
Expand Down Expand Up @@ -56,3 +57,10 @@ type SubProblem struct {
type NonceError struct {
*ProblemDetails
}

// AlreadyReplacedError represents the error which is returned
// If the Server rejects the request because the identified certificate has already been marked as replaced.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5
type AlreadyReplacedError struct {
*ProblemDetails
}

0 comments on commit fd28a40

Please sign in to comment.