smallstep-certificates/acme/errors.go

431 lines
14 KiB
Go
Raw Normal View History

2019-05-27 00:41:10 +00:00
package acme
import (
"encoding/json"
2021-03-01 06:49:20 +00:00
"fmt"
2021-03-30 06:16:39 +00:00
"net/http"
2021-03-01 06:49:20 +00:00
2019-05-27 00:41:10 +00:00
"github.com/pkg/errors"
"github.com/smallstep/certificates/api/render"
2019-05-27 00:41:10 +00:00
)
2021-03-01 06:49:20 +00:00
// ProblemType is the type of the ACME problem.
type ProblemType int
2019-05-27 00:41:10 +00:00
const (
2021-03-03 23:16:25 +00:00
// ErrorAccountDoesNotExistType request specified an account that does not exist
2021-03-01 06:49:20 +00:00
ErrorAccountDoesNotExistType ProblemType = iota
2021-03-03 23:16:25 +00:00
// ErrorAlreadyRevokedType request specified a certificate to be revoked that has already been revoked
2021-03-01 06:49:20 +00:00
ErrorAlreadyRevokedType
2022-09-08 17:46:58 +00:00
// ErrorBadAttestationStatementType WebAuthn attestation statement could not be verified
ErrorBadAttestationStatementType
2021-03-03 23:16:25 +00:00
// ErrorBadCSRType CSR is unacceptable (e.g., due to a short key)
2021-03-01 06:49:20 +00:00
ErrorBadCSRType
2021-03-03 23:16:25 +00:00
// ErrorBadNonceType client sent an unacceptable anti-replay nonce
2021-03-01 06:49:20 +00:00
ErrorBadNonceType
2021-03-03 23:16:25 +00:00
// ErrorBadPublicKeyType JWS was signed by a public key the server does not support
2021-03-01 06:49:20 +00:00
ErrorBadPublicKeyType
2021-03-03 23:16:25 +00:00
// ErrorBadRevocationReasonType revocation reason provided is not allowed by the server
2021-03-01 06:49:20 +00:00
ErrorBadRevocationReasonType
2021-03-03 23:16:25 +00:00
// ErrorBadSignatureAlgorithmType JWS was signed with an algorithm the server does not support
2021-03-01 06:49:20 +00:00
ErrorBadSignatureAlgorithmType
2021-03-03 23:16:25 +00:00
// ErrorCaaType Authority Authorization (CAA) records forbid the CA from issuing a certificate
2021-03-01 06:49:20 +00:00
ErrorCaaType
2021-03-03 23:16:25 +00:00
// ErrorCompoundType error conditions are indicated in the “subproblems” array.
2021-03-01 06:49:20 +00:00
ErrorCompoundType
2021-03-03 23:16:25 +00:00
// ErrorConnectionType server could not connect to validation target
2021-03-01 06:49:20 +00:00
ErrorConnectionType
2021-03-03 23:16:25 +00:00
// ErrorDNSType was a problem with a DNS query during identifier validation
2021-03-01 06:49:20 +00:00
ErrorDNSType
2021-03-03 23:16:25 +00:00
// ErrorExternalAccountRequiredType request must include a value for the “externalAccountBinding” field
2021-03-01 06:49:20 +00:00
ErrorExternalAccountRequiredType
2021-03-03 23:16:25 +00:00
// ErrorIncorrectResponseType received didnt match the challenges requirements
2021-03-01 06:49:20 +00:00
ErrorIncorrectResponseType
2021-03-03 23:16:25 +00:00
// ErrorInvalidContactType URL for an account was invalid
2021-03-01 06:49:20 +00:00
ErrorInvalidContactType
2021-03-03 23:16:25 +00:00
// ErrorMalformedType request message was malformed
2021-03-01 06:49:20 +00:00
ErrorMalformedType
2021-03-03 23:16:25 +00:00
// ErrorOrderNotReadyType request attempted to finalize an order that is not ready to be finalized
2021-03-01 06:49:20 +00:00
ErrorOrderNotReadyType
2021-03-03 23:16:25 +00:00
// ErrorRateLimitedType request exceeds a rate limit
2021-03-01 06:49:20 +00:00
ErrorRateLimitedType
2021-03-03 23:16:25 +00:00
// ErrorRejectedIdentifierType server will not issue certificates for the identifier
2021-03-01 06:49:20 +00:00
ErrorRejectedIdentifierType
2021-03-03 23:16:25 +00:00
// ErrorServerInternalType server experienced an internal error
2021-03-01 06:49:20 +00:00
ErrorServerInternalType
2021-03-03 23:16:25 +00:00
// ErrorTLSType server received a TLS error during validation
2021-03-01 06:49:20 +00:00
ErrorTLSType
2021-03-03 23:16:25 +00:00
// ErrorUnauthorizedType client lacks sufficient authorization
2021-03-01 06:49:20 +00:00
ErrorUnauthorizedType
2021-03-03 23:16:25 +00:00
// ErrorUnsupportedContactType URL for an account used an unsupported protocol scheme
2021-03-01 06:49:20 +00:00
ErrorUnsupportedContactType
2021-03-03 23:16:25 +00:00
// ErrorUnsupportedIdentifierType identifier is of an unsupported type
2021-03-01 06:49:20 +00:00
ErrorUnsupportedIdentifierType
2021-03-03 23:16:25 +00:00
// ErrorUserActionRequiredType the “instance” URL and take actions specified there
2021-03-01 06:49:20 +00:00
ErrorUserActionRequiredType
2021-03-03 23:16:25 +00:00
// ErrorNotImplementedType operation is not implemented
2021-03-01 06:49:20 +00:00
ErrorNotImplementedType
2019-05-27 00:41:10 +00:00
)
// String returns the string representation of the acme problem type,
// fulfilling the Stringer interface.
2021-03-01 06:49:20 +00:00
func (ap ProblemType) String() string {
2019-05-27 00:41:10 +00:00
switch ap {
2021-03-01 06:49:20 +00:00
case ErrorAccountDoesNotExistType:
2019-05-27 00:41:10 +00:00
return "accountDoesNotExist"
2021-03-01 06:49:20 +00:00
case ErrorAlreadyRevokedType:
2019-05-27 00:41:10 +00:00
return "alreadyRevoked"
2022-10-03 19:54:26 +00:00
case ErrorBadAttestationStatementType:
return "badAttestationStatement"
2021-03-01 06:49:20 +00:00
case ErrorBadCSRType:
2019-05-27 00:41:10 +00:00
return "badCSR"
2021-03-01 06:49:20 +00:00
case ErrorBadNonceType:
2019-05-27 00:41:10 +00:00
return "badNonce"
2021-03-01 06:49:20 +00:00
case ErrorBadPublicKeyType:
2019-05-27 00:41:10 +00:00
return "badPublicKey"
2021-03-01 06:49:20 +00:00
case ErrorBadRevocationReasonType:
2019-05-27 00:41:10 +00:00
return "badRevocationReason"
2021-03-01 06:49:20 +00:00
case ErrorBadSignatureAlgorithmType:
2019-05-27 00:41:10 +00:00
return "badSignatureAlgorithm"
2021-03-01 06:49:20 +00:00
case ErrorCaaType:
2019-05-27 00:41:10 +00:00
return "caa"
2021-03-01 06:49:20 +00:00
case ErrorCompoundType:
2019-05-27 00:41:10 +00:00
return "compound"
2021-03-01 06:49:20 +00:00
case ErrorConnectionType:
2019-05-27 00:41:10 +00:00
return "connection"
2021-03-01 06:49:20 +00:00
case ErrorDNSType:
2019-05-27 00:41:10 +00:00
return "dns"
2021-03-01 06:49:20 +00:00
case ErrorExternalAccountRequiredType:
2019-05-27 00:41:10 +00:00
return "externalAccountRequired"
2021-03-01 06:49:20 +00:00
case ErrorInvalidContactType:
2019-05-27 00:41:10 +00:00
return "incorrectResponse"
2021-03-01 06:49:20 +00:00
case ErrorMalformedType:
2019-05-27 00:41:10 +00:00
return "malformed"
2021-03-01 06:49:20 +00:00
case ErrorOrderNotReadyType:
2019-05-27 00:41:10 +00:00
return "orderNotReady"
2021-03-01 06:49:20 +00:00
case ErrorRateLimitedType:
2019-05-27 00:41:10 +00:00
return "rateLimited"
2021-03-01 06:49:20 +00:00
case ErrorRejectedIdentifierType:
2019-05-27 00:41:10 +00:00
return "rejectedIdentifier"
2021-03-01 06:49:20 +00:00
case ErrorServerInternalType:
2019-05-27 00:41:10 +00:00
return "serverInternal"
2021-03-01 06:49:20 +00:00
case ErrorTLSType:
2019-05-27 00:41:10 +00:00
return "tls"
2021-03-01 06:49:20 +00:00
case ErrorUnauthorizedType:
2019-05-27 00:41:10 +00:00
return "unauthorized"
2021-03-01 06:49:20 +00:00
case ErrorUnsupportedContactType:
2019-05-27 00:41:10 +00:00
return "unsupportedContact"
2021-03-01 06:49:20 +00:00
case ErrorUnsupportedIdentifierType:
2019-05-27 00:41:10 +00:00
return "unsupportedIdentifier"
2021-03-01 06:49:20 +00:00
case ErrorUserActionRequiredType:
2019-05-27 00:41:10 +00:00
return "userActionRequired"
2021-03-01 06:49:20 +00:00
case ErrorNotImplementedType:
return "notImplemented"
2019-05-27 00:41:10 +00:00
default:
2021-03-03 23:16:25 +00:00
return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap))
2021-03-01 06:49:20 +00:00
}
}
type errorMetadata struct {
details string
status int
typ string
String string
}
var (
officialACMEPrefix = "urn:ietf:params:acme:error:"
errorServerInternalMetadata = errorMetadata{
2021-03-01 07:33:18 +00:00
typ: officialACMEPrefix + ErrorServerInternalType.String(),
details: "The server experienced an internal error",
status: 500,
2021-03-01 06:49:20 +00:00
}
2021-03-01 07:33:18 +00:00
errorMap = map[ProblemType]errorMetadata{
2021-03-01 06:49:20 +00:00
ErrorAccountDoesNotExistType: {
typ: officialACMEPrefix + ErrorAccountDoesNotExistType.String(),
details: "Account does not exist",
status: 400,
},
ErrorAlreadyRevokedType: {
typ: officialACMEPrefix + ErrorAlreadyRevokedType.String(),
2021-11-26 16:27:42 +00:00
details: "Certificate already revoked",
2021-03-01 06:49:20 +00:00
status: 400,
},
ErrorBadCSRType: {
typ: officialACMEPrefix + ErrorBadCSRType.String(),
details: "The CSR is unacceptable",
status: 400,
},
ErrorBadNonceType: {
typ: officialACMEPrefix + ErrorBadNonceType.String(),
details: "Unacceptable anti-replay nonce",
status: 400,
},
ErrorBadPublicKeyType: {
typ: officialACMEPrefix + ErrorBadPublicKeyType.String(),
details: "The jws was signed by a public key the server does not support",
status: 400,
},
ErrorBadRevocationReasonType: {
typ: officialACMEPrefix + ErrorBadRevocationReasonType.String(),
details: "The revocation reason provided is not allowed by the server",
status: 400,
},
ErrorBadSignatureAlgorithmType: {
typ: officialACMEPrefix + ErrorBadSignatureAlgorithmType.String(),
details: "The JWS was signed with an algorithm the server does not support",
status: 400,
},
2022-09-08 17:46:58 +00:00
ErrorBadAttestationStatementType: {
2022-09-08 17:48:17 +00:00
typ: officialACMEPrefix + ErrorBadAttestationStatementType.String(),
2022-08-30 03:02:43 +00:00
details: "Attestation statement cannot be verified",
status: 400,
},
2021-03-01 06:49:20 +00:00
ErrorCaaType: {
typ: officialACMEPrefix + ErrorCaaType.String(),
details: "Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate",
status: 400,
},
ErrorCompoundType: {
typ: officialACMEPrefix + ErrorCompoundType.String(),
details: "Specific error conditions are indicated in the “subproblems” array",
status: 400,
},
ErrorConnectionType: {
typ: officialACMEPrefix + ErrorConnectionType.String(),
details: "The server could not connect to validation target",
status: 400,
},
ErrorDNSType: {
typ: officialACMEPrefix + ErrorDNSType.String(),
details: "There was a problem with a DNS query during identifier validation",
status: 400,
},
ErrorExternalAccountRequiredType: {
typ: officialACMEPrefix + ErrorExternalAccountRequiredType.String(),
details: "The request must include a value for the \"externalAccountBinding\" field",
status: 400,
},
ErrorIncorrectResponseType: {
typ: officialACMEPrefix + ErrorIncorrectResponseType.String(),
details: "Response received didn't match the challenge's requirements",
status: 400,
},
ErrorInvalidContactType: {
typ: officialACMEPrefix + ErrorInvalidContactType.String(),
details: "A contact URL for an account was invalid",
status: 400,
},
ErrorMalformedType: {
typ: officialACMEPrefix + ErrorMalformedType.String(),
details: "The request message was malformed",
status: 400,
},
ErrorOrderNotReadyType: {
typ: officialACMEPrefix + ErrorOrderNotReadyType.String(),
details: "The request attempted to finalize an order that is not ready to be finalized",
status: 400,
},
ErrorRateLimitedType: {
typ: officialACMEPrefix + ErrorRateLimitedType.String(),
details: "The request exceeds a rate limit",
status: 400,
},
ErrorRejectedIdentifierType: {
typ: officialACMEPrefix + ErrorRejectedIdentifierType.String(),
details: "The server will not issue certificates for the identifier",
status: 400,
},
ErrorNotImplementedType: {
typ: officialACMEPrefix + ErrorRejectedIdentifierType.String(),
details: "The requested operation is not implemented",
status: 501,
},
ErrorTLSType: {
typ: officialACMEPrefix + ErrorTLSType.String(),
details: "The server received a TLS error during validation",
status: 400,
},
ErrorUnauthorizedType: {
typ: officialACMEPrefix + ErrorUnauthorizedType.String(),
details: "The client lacks sufficient authorization",
status: 401,
},
ErrorUnsupportedContactType: {
typ: officialACMEPrefix + ErrorUnsupportedContactType.String(),
details: "A contact URL for an account used an unsupported protocol scheme",
status: 400,
},
ErrorUnsupportedIdentifierType: {
typ: officialACMEPrefix + ErrorUnsupportedIdentifierType.String(),
details: "An identifier is of an unsupported type",
status: 400,
},
ErrorUserActionRequiredType: {
typ: officialACMEPrefix + ErrorUserActionRequiredType.String(),
details: "Visit the “instance” URL and take actions specified there",
status: 400,
},
ErrorServerInternalType: errorServerInternalMetadata,
2019-05-27 00:41:10 +00:00
}
2021-03-01 06:49:20 +00:00
)
2019-05-27 00:41:10 +00:00
// Error represents an ACME Error
2019-05-27 00:41:10 +00:00
type Error struct {
Type string `json:"type"`
Detail string `json:"detail"`
Subproblems []Subproblem `json:"subproblems,omitempty"`
Err error `json:"-"`
Status int `json:"-"`
}
// Subproblem represents an ACME subproblem. It's fairly
// similar to an ACME error, but differs in that it can't
// include subproblems itself, the error is reflected
// in the Detail property and doesn't have a Status.
type Subproblem struct {
Type string `json:"type"`
Detail string `json:"detail"`
// The "identifier" field MUST NOT be present at the top level in ACME
// problem documents. It can only be present in subproblems.
// Subproblems need not all have the same type, and they do not need to
// match the top level type.
Identifier *Identifier `json:"identifier,omitempty"`
}
// NewError creates a new Error.
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
return newError(pt, errors.Errorf(msg, args...))
}
2023-08-04 09:24:22 +00:00
// NewDetailedError creates a new Error that includes the error
// message in the details, providing more information to the
// ACME client.
func NewDetailedError(pt ProblemType, msg string, args ...interface{}) *Error {
return NewError(pt, msg, args...).withDetail()
2021-03-01 06:49:20 +00:00
}
2023-08-04 09:24:22 +00:00
func (e *Error) withDetail() *Error {
if e == nil || e.Status >= 500 || e.Err == nil {
return e
}
e.Detail = fmt.Sprintf("%s: %s", e.Detail, e.Err)
return e
2021-03-11 07:05:46 +00:00
}
2023-08-04 09:24:22 +00:00
// AddSubproblems adds the Subproblems to Error. It
// returns the Error, allowing for fluent addition.
func (e *Error) AddSubproblems(subproblems ...Subproblem) *Error {
e.Subproblems = append(e.Subproblems, subproblems...)
return e
}
// NewSubproblem creates a new Subproblem. The msg and args
// are used to create a new error, which is set as the Detail, allowing
// for more detailed error messages to be returned to the ACME client.
func NewSubproblem(pt ProblemType, msg string, args ...interface{}) Subproblem {
e := newError(pt, fmt.Errorf(msg, args...))
s := Subproblem{
Type: e.Type,
Detail: e.Err.Error(),
}
return s
}
// NewSubproblemWithIdentifier creates a new Subproblem with a specific ACME
// Identifier. It calls NewSubproblem and sets the Identifier.
func NewSubproblemWithIdentifier(pt ProblemType, identifier Identifier, msg string, args ...interface{}) Subproblem {
s := NewSubproblem(pt, msg, args...)
s.Identifier = &identifier
return s
}
2021-03-11 07:05:46 +00:00
func newError(pt ProblemType, err error) *Error {
2021-03-01 07:33:18 +00:00
meta, ok := errorMap[pt]
2021-03-01 06:49:20 +00:00
if !ok {
meta = errorServerInternalMetadata
return &Error{
2021-03-05 07:10:46 +00:00
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
2021-03-11 07:05:46 +00:00
Err: err,
2021-03-01 06:49:20 +00:00
}
}
return &Error{
2021-03-05 07:10:46 +00:00
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
2021-03-11 07:05:46 +00:00
Err: err,
2021-03-01 06:49:20 +00:00
}
2019-05-27 00:41:10 +00:00
}
2021-03-03 23:16:25 +00:00
// NewErrorISE creates a new ErrorServerInternalType Error.
func NewErrorISE(msg string, args ...interface{}) *Error {
return NewError(ErrorServerInternalType, msg, args...)
}
2021-03-05 07:10:46 +00:00
// WrapError attempts to wrap the internal error.
func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
var e *Error
switch {
case err == nil:
2019-05-27 00:41:10 +00:00
return nil
case errors.As(err, &e):
2019-05-27 00:41:10 +00:00
if e.Err == nil {
2021-03-05 07:10:46 +00:00
e.Err = errors.Errorf(msg+"; "+e.Detail, args...)
2019-05-27 00:41:10 +00:00
} else {
2021-03-01 06:49:20 +00:00
e.Err = errors.Wrapf(e.Err, msg, args...)
2019-05-27 00:41:10 +00:00
}
return e
default:
2021-03-11 07:05:46 +00:00
return newError(typ, errors.Wrapf(err, msg, args...))
2019-05-27 00:41:10 +00:00
}
}
2023-08-04 09:24:22 +00:00
func WrapDetailedError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
return WrapError(typ, err, msg, args...).withDetail()
}
2021-03-05 07:10:46 +00:00
// WrapErrorISE shortcut to wrap an internal server error type.
func WrapErrorISE(err error, msg string, args ...interface{}) *Error {
return WrapError(ErrorServerInternalType, err, msg, args...)
2021-03-01 07:33:18 +00:00
}
2021-03-01 06:49:20 +00:00
// StatusCode returns the status code and implements the StatusCoder interface.
func (e *Error) StatusCode() int {
return e.Status
}
// Error implements the error interface.
2019-05-27 00:41:10 +00:00
func (e *Error) Error() string {
if e.Err == nil {
return e.Detail
}
return e.Err.Error()
2019-05-27 00:41:10 +00:00
}
// Cause returns the internal error and implements the Causer interface.
func (e *Error) Cause() error {
if e.Err == nil {
2021-03-05 07:10:46 +00:00
return errors.New(e.Detail)
2019-05-27 00:41:10 +00:00
}
return e.Err
}
// ToLog implements the EnableLogger interface.
func (e *Error) ToLog() (interface{}, error) {
b, err := json.Marshal(e)
if err != nil {
return nil, WrapErrorISE(err, "error marshaling acme.Error for logging")
}
return string(b), nil
}
2021-03-30 06:16:39 +00:00
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter) {
2021-03-30 06:16:39 +00:00
w.Header().Set("Content-Type", "application/problem+json")
render.JSONStatus(w, e, e.StatusCode())
2021-03-30 06:16:39 +00:00
}