diff --git a/acme/error.go b/acme/error.go index 6d7013cf..e4bc934c 100644 --- a/acme/error.go +++ b/acme/error.go @@ -8,7 +8,10 @@ import ( "strings" ) -const tosAgreementError = "Must agree to subscriber agreement before any further actions" +const ( + tosAgreementError = "Must agree to subscriber agreement before any further actions" + invalidNonceError = "JWS has invalid anti-replay nonce" +) // RemoteError is the base type for all errors specific to the ACME protocol. type RemoteError struct { @@ -28,6 +31,12 @@ type TOSError struct { RemoteError } +// NonceError represents the error which is returned if the +// nonce sent by the client was not accepted by the server. +type NonceError struct { + RemoteError +} + type domainError struct { Domain string Error error @@ -73,6 +82,10 @@ func handleHTTPError(resp *http.Response) error { return TOSError{errorDetail} } + if errorDetail.StatusCode == http.StatusBadRequest && strings.HasPrefix(errorDetail.Detail, invalidNonceError) { + return NonceError{errorDetail} + } + return errorDetail } diff --git a/acme/jws.go b/acme/jws.go index 4818f17e..876b39b7 100644 --- a/acme/jws.go +++ b/acme/jws.go @@ -41,15 +41,26 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) { } resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize()))) - if err != nil { - return nil, err - } + // Even in case of an error, the response should still contain a nonce. nonce, nonceErr := getNonceFromResponse(resp) if nonceErr == nil { j.nonces.Push(nonce) } + if err != nil { + switch err.(type) { + case NonceError: + // In case of a nonce error - retry once + resp, err = httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize()))) + if err != nil { + return nil, err + } + default: + return nil, err + } + } + return resp, err }