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 didn’t match the challenge’s 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) } }