smallstep-certificates/acme/errors.go
2021-03-29 23:16:39 -07:00

379 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package acme
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging"
)
// ProblemType is the type of the ACME problem.
type ProblemType int
const (
// ErrorAccountDoesNotExistType request specified an account that does not exist
ErrorAccountDoesNotExistType ProblemType = iota
// ErrorAlreadyRevokedType request specified a certificate to be revoked that has already been revoked
ErrorAlreadyRevokedType
// ErrorBadCSRType CSR is unacceptable (e.g., due to a short key)
ErrorBadCSRType
// ErrorBadNonceType client sent an unacceptable anti-replay nonce
ErrorBadNonceType
// ErrorBadPublicKeyType JWS was signed by a public key the server does not support
ErrorBadPublicKeyType
// ErrorBadRevocationReasonType revocation reason provided is not allowed by the server
ErrorBadRevocationReasonType
// ErrorBadSignatureAlgorithmType JWS was signed with an algorithm the server does not support
ErrorBadSignatureAlgorithmType
// ErrorCaaType Authority Authorization (CAA) records forbid the CA from issuing a certificate
ErrorCaaType
// ErrorCompoundType error conditions are indicated in the “subproblems” array.
ErrorCompoundType
// ErrorConnectionType server could not connect to validation target
ErrorConnectionType
// ErrorDNSType was a problem with a DNS query during identifier validation
ErrorDNSType
// ErrorExternalAccountRequiredType request must include a value for the “externalAccountBinding” field
ErrorExternalAccountRequiredType
// ErrorIncorrectResponseType received didnt match the challenges requirements
ErrorIncorrectResponseType
// ErrorInvalidContactType URL for an account was invalid
ErrorInvalidContactType
// ErrorMalformedType request message was malformed
ErrorMalformedType
// ErrorOrderNotReadyType request attempted to finalize an order that is not ready to be finalized
ErrorOrderNotReadyType
// ErrorRateLimitedType request exceeds a rate limit
ErrorRateLimitedType
// ErrorRejectedIdentifierType server will not issue certificates for the identifier
ErrorRejectedIdentifierType
// ErrorServerInternalType server experienced an internal error
ErrorServerInternalType
// ErrorTLSType server received a TLS error during validation
ErrorTLSType
// ErrorUnauthorizedType client lacks sufficient authorization
ErrorUnauthorizedType
// ErrorUnsupportedContactType URL for an account used an unsupported protocol scheme
ErrorUnsupportedContactType
// ErrorUnsupportedIdentifierType identifier is of an unsupported type
ErrorUnsupportedIdentifierType
// ErrorUserActionRequiredType the “instance” URL and take actions specified there
ErrorUserActionRequiredType
// ErrorNotImplementedType operation is not implemented
ErrorNotImplementedType
)
// String returns the string representation of the acme problem type,
// fulfilling the Stringer interface.
func (ap ProblemType) String() string {
switch ap {
case ErrorAccountDoesNotExistType:
return "accountDoesNotExist"
case ErrorAlreadyRevokedType:
return "alreadyRevoked"
case ErrorBadCSRType:
return "badCSR"
case ErrorBadNonceType:
return "badNonce"
case ErrorBadPublicKeyType:
return "badPublicKey"
case ErrorBadRevocationReasonType:
return "badRevocationReason"
case ErrorBadSignatureAlgorithmType:
return "badSignatureAlgorithm"
case ErrorCaaType:
return "caa"
case ErrorCompoundType:
return "compound"
case ErrorConnectionType:
return "connection"
case ErrorDNSType:
return "dns"
case ErrorExternalAccountRequiredType:
return "externalAccountRequired"
case ErrorInvalidContactType:
return "incorrectResponse"
case ErrorMalformedType:
return "malformed"
case ErrorOrderNotReadyType:
return "orderNotReady"
case ErrorRateLimitedType:
return "rateLimited"
case ErrorRejectedIdentifierType:
return "rejectedIdentifier"
case ErrorServerInternalType:
return "serverInternal"
case ErrorTLSType:
return "tls"
case ErrorUnauthorizedType:
return "unauthorized"
case ErrorUnsupportedContactType:
return "unsupportedContact"
case ErrorUnsupportedIdentifierType:
return "unsupportedIdentifier"
case ErrorUserActionRequiredType:
return "userActionRequired"
case ErrorNotImplementedType:
return "notImplemented"
default:
return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap))
}
}
type errorMetadata struct {
details string
status int
typ string
String string
}
var (
officialACMEPrefix = "urn:ietf:params:acme:error:"
errorServerInternalMetadata = errorMetadata{
typ: officialACMEPrefix + ErrorServerInternalType.String(),
details: "The server experienced an internal error",
status: 500,
}
errorMap = map[ProblemType]errorMetadata{
ErrorAccountDoesNotExistType: {
typ: officialACMEPrefix + ErrorAccountDoesNotExistType.String(),
details: "Account does not exist",
status: 400,
},
ErrorAlreadyRevokedType: {
typ: officialACMEPrefix + ErrorAlreadyRevokedType.String(),
details: "Certificate already Revoked",
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,
},
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,
}
)
// Error represents an ACME
type Error struct {
Type string `json:"type"`
Detail string `json:"detail"`
Subproblems []interface{} `json:"subproblems,omitempty"`
Identifier interface{} `json:"identifier,omitempty"`
Err error `json:"-"`
Status int `json:"-"`
}
// NewError creates a new Error type.
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
return newError(pt, errors.Errorf(msg, args...))
}
func newError(pt ProblemType, err error) *Error {
meta, ok := errorMap[pt]
if !ok {
meta = errorServerInternalMetadata
return &Error{
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
Err: err,
}
}
return &Error{
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
Err: err,
}
}
// NewErrorISE creates a new ErrorServerInternalType Error.
func NewErrorISE(msg string, args ...interface{}) *Error {
return NewError(ErrorServerInternalType, msg, args...)
}
// WrapError attempts to wrap the internal error.
func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
switch e := err.(type) {
case nil:
return nil
case *Error:
if e.Err == nil {
e.Err = errors.Errorf(msg+"; "+e.Detail, args...)
} else {
e.Err = errors.Wrapf(e.Err, msg, args...)
}
return e
default:
return newError(typ, errors.Wrapf(err, msg, args...))
}
}
// WrapErrorISE shortcut to wrap an internal server error type.
func WrapErrorISE(err error, msg string, args ...interface{}) *Error {
return WrapError(ErrorServerInternalType, err, msg, args...)
}
// StatusCode returns the status code and implements the StatusCoder interface.
func (e *Error) StatusCode() int {
return e.Status
}
// Error allows AError to implement the error interface.
func (e *Error) Error() string {
return e.Detail
}
// Cause returns the internal error and implements the Causer interface.
func (e *Error) Cause() error {
if e.Err == nil {
return errors.New(e.Detail)
}
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
}
// WriteError writes to w a JSON representation of the given error.
func WriteError(w http.ResponseWriter, err *Error) {
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(err.StatusCode())
// Write errors in the response writer
if rl, ok := w.(logging.ResponseLogger); ok {
rl.WithFields(map[string]interface{}{
"error": err.Err,
})
if os.Getenv("STEPDEBUG") == "1" {
if e, ok := err.Err.(errs.StackTracer); ok {
rl.WithFields(map[string]interface{}{
"stack-trace": fmt.Sprintf("%+v", e),
})
}
}
}
if err := json.NewEncoder(w).Encode(err); err != nil {
log.Println(err)
}
}