mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-05 21:20:34 +00:00
a26b5f322d
Add more specific wording describing what a 501 means and add more color explaining how official vs unofficial error types should be handled.
482 lines
12 KiB
Go
482 lines
12 KiB
Go
package acme
|
||
|
||
import (
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
// AccountDoesNotExistErr returns a new acme error.
|
||
func AccountDoesNotExistErr(err error) *Error {
|
||
return &Error{
|
||
Type: accountDoesNotExistErr,
|
||
Detail: "Account does not exist",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// AlreadyRevokedErr returns a new acme error.
|
||
func AlreadyRevokedErr(err error) *Error {
|
||
return &Error{
|
||
Type: alreadyRevokedErr,
|
||
Detail: "Certificate already revoked",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// BadCSRErr returns a new acme error.
|
||
func BadCSRErr(err error) *Error {
|
||
return &Error{
|
||
Type: badCSRErr,
|
||
Detail: "The CSR is unacceptable",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// BadNonceErr returns a new acme error.
|
||
func BadNonceErr(err error) *Error {
|
||
return &Error{
|
||
Type: badNonceErr,
|
||
Detail: "Unacceptable anti-replay nonce",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// BadPublicKeyErr returns a new acme error.
|
||
func BadPublicKeyErr(err error) *Error {
|
||
return &Error{
|
||
Type: badPublicKeyErr,
|
||
Detail: "The jws was signed by a public key the server does not support",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// BadRevocationReasonErr returns a new acme error.
|
||
func BadRevocationReasonErr(err error) *Error {
|
||
return &Error{
|
||
Type: badRevocationReasonErr,
|
||
Detail: "The revocation reason provided is not allowed by the server",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// BadSignatureAlgorithmErr returns a new acme error.
|
||
func BadSignatureAlgorithmErr(err error) *Error {
|
||
return &Error{
|
||
Type: badSignatureAlgorithmErr,
|
||
Detail: "The JWS was signed with an algorithm the server does not support",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// CaaErr returns a new acme error.
|
||
func CaaErr(err error) *Error {
|
||
return &Error{
|
||
Type: caaErr,
|
||
Detail: "Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// CompoundErr returns a new acme error.
|
||
func CompoundErr(err error) *Error {
|
||
return &Error{
|
||
Type: compoundErr,
|
||
Detail: "Specific error conditions are indicated in the “subproblems” array",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// ConnectionErr returns a new acme error.
|
||
func ConnectionErr(err error) *Error {
|
||
return &Error{
|
||
Type: connectionErr,
|
||
Detail: "The server could not connect to validation target",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// DNSErr returns a new acme error.
|
||
func DNSErr(err error) *Error {
|
||
return &Error{
|
||
Type: dnsErr,
|
||
Detail: "There was a problem with a DNS query during identifier validation",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// ExternalAccountRequiredErr returns a new acme error.
|
||
func ExternalAccountRequiredErr(err error) *Error {
|
||
return &Error{
|
||
Type: externalAccountRequiredErr,
|
||
Detail: "The request must include a value for the \"externalAccountBinding\" field",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// IncorrectResponseErr returns a new acme error.
|
||
func IncorrectResponseErr(err error) *Error {
|
||
return &Error{
|
||
Type: incorrectResponseErr,
|
||
Detail: "Response received didn't match the challenge's requirements",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// InvalidContactErr returns a new acme error.
|
||
func InvalidContactErr(err error) *Error {
|
||
return &Error{
|
||
Type: invalidContactErr,
|
||
Detail: "A contact URL for an account was invalid",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// MalformedErr returns a new acme error.
|
||
func MalformedErr(err error) *Error {
|
||
return &Error{
|
||
Type: malformedErr,
|
||
Detail: "The request message was malformed",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// OrderNotReadyErr returns a new acme error.
|
||
func OrderNotReadyErr(err error) *Error {
|
||
return &Error{
|
||
Type: orderNotReadyErr,
|
||
Detail: "The request attempted to finalize an order that is not ready to be finalized",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// RateLimitedErr returns a new acme error.
|
||
func RateLimitedErr(err error) *Error {
|
||
return &Error{
|
||
Type: rateLimitedErr,
|
||
Detail: "The request exceeds a rate limit",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// RejectedIdentifierErr returns a new acme error.
|
||
func RejectedIdentifierErr(err error) *Error {
|
||
return &Error{
|
||
Type: rejectedIdentifierErr,
|
||
Detail: "The server will not issue certificates for the identifier",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// ServerInternalErr returns a new acme error.
|
||
func ServerInternalErr(err error) *Error {
|
||
return &Error{
|
||
Type: serverInternalErr,
|
||
Detail: "The server experienced an internal error",
|
||
Status: 500,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// NotImplemented returns a new acme error.
|
||
func NotImplemented(err error) *Error {
|
||
return &Error{
|
||
Type: notImplemented,
|
||
Detail: "The requested operation is not implemented",
|
||
Status: 501,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// TLSErr returns a new acme error.
|
||
func TLSErr(err error) *Error {
|
||
return &Error{
|
||
Type: tlsErr,
|
||
Detail: "The server received a TLS error during validation",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// UnauthorizedErr returns a new acme error.
|
||
func UnauthorizedErr(err error) *Error {
|
||
return &Error{
|
||
Type: unauthorizedErr,
|
||
Detail: "The client lacks sufficient authorization",
|
||
Status: 401,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// UnsupportedContactErr returns a new acme error.
|
||
func UnsupportedContactErr(err error) *Error {
|
||
return &Error{
|
||
Type: unsupportedContactErr,
|
||
Detail: "A contact URL for an account used an unsupported protocol scheme",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// UnsupportedIdentifierErr returns a new acme error.
|
||
func UnsupportedIdentifierErr(err error) *Error {
|
||
return &Error{
|
||
Type: unsupportedIdentifierErr,
|
||
Detail: "An identifier is of an unsupported type",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// UserActionRequiredErr returns a new acme error.
|
||
func UserActionRequiredErr(err error) *Error {
|
||
return &Error{
|
||
Type: userActionRequiredErr,
|
||
Detail: "Visit the “instance” URL and take actions specified there",
|
||
Status: 400,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// ProbType is the type of the ACME problem.
|
||
type ProbType int
|
||
|
||
const (
|
||
// The request specified an account that does not exist
|
||
accountDoesNotExistErr ProbType = iota
|
||
// The request specified a certificate to be revoked that has already been revoked
|
||
alreadyRevokedErr
|
||
// The CSR is unacceptable (e.g., due to a short key)
|
||
badCSRErr
|
||
// The client sent an unacceptable anti-replay nonce
|
||
badNonceErr
|
||
// The JWS was signed by a public key the server does not support
|
||
badPublicKeyErr
|
||
// The revocation reason provided is not allowed by the server
|
||
badRevocationReasonErr
|
||
// The JWS was signed with an algorithm the server does not support
|
||
badSignatureAlgorithmErr
|
||
// Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate
|
||
caaErr
|
||
// Specific error conditions are indicated in the “subproblems” array.
|
||
compoundErr
|
||
// The server could not connect to validation target
|
||
connectionErr
|
||
// There was a problem with a DNS query during identifier validation
|
||
dnsErr
|
||
// The request must include a value for the “externalAccountBinding” field
|
||
externalAccountRequiredErr
|
||
// Response received didn’t match the challenge’s requirements
|
||
incorrectResponseErr
|
||
// A contact URL for an account was invalid
|
||
invalidContactErr
|
||
// The request message was malformed
|
||
malformedErr
|
||
// The request attempted to finalize an order that is not ready to be finalized
|
||
orderNotReadyErr
|
||
// The request exceeds a rate limit
|
||
rateLimitedErr
|
||
// The server will not issue certificates for the identifier
|
||
rejectedIdentifierErr
|
||
// The server experienced an internal error
|
||
serverInternalErr
|
||
// The server received a TLS error during validation
|
||
tlsErr
|
||
// The client lacks sufficient authorization
|
||
unauthorizedErr
|
||
// A contact URL for an account used an unsupported protocol scheme
|
||
unsupportedContactErr
|
||
// An identifier is of an unsupported type
|
||
unsupportedIdentifierErr
|
||
// Visit the “instance” URL and take actions specified there
|
||
userActionRequiredErr
|
||
// The operation is not implemented
|
||
notImplemented
|
||
)
|
||
|
||
// String returns the string representation of the acme problem type,
|
||
// fulfilling the Stringer interface.
|
||
func (ap ProbType) String() string {
|
||
switch ap {
|
||
case accountDoesNotExistErr:
|
||
return "accountDoesNotExist"
|
||
case alreadyRevokedErr:
|
||
return "alreadyRevoked"
|
||
case badCSRErr:
|
||
return "badCSR"
|
||
case badNonceErr:
|
||
return "badNonce"
|
||
case badPublicKeyErr:
|
||
return "badPublicKey"
|
||
case badRevocationReasonErr:
|
||
return "badRevocationReason"
|
||
case badSignatureAlgorithmErr:
|
||
return "badSignatureAlgorithm"
|
||
case caaErr:
|
||
return "caa"
|
||
case compoundErr:
|
||
return "compound"
|
||
case connectionErr:
|
||
return "connection"
|
||
case dnsErr:
|
||
return "dns"
|
||
case externalAccountRequiredErr:
|
||
return "externalAccountRequired"
|
||
case incorrectResponseErr:
|
||
return "incorrectResponse"
|
||
case invalidContactErr:
|
||
return "invalidContact"
|
||
case malformedErr:
|
||
return "malformed"
|
||
case orderNotReadyErr:
|
||
return "orderNotReady"
|
||
case rateLimitedErr:
|
||
return "rateLimited"
|
||
case rejectedIdentifierErr:
|
||
return "rejectedIdentifier"
|
||
case serverInternalErr:
|
||
return "serverInternal"
|
||
case tlsErr:
|
||
return "tls"
|
||
case unauthorizedErr:
|
||
return "unauthorized"
|
||
case unsupportedContactErr:
|
||
return "unsupportedContact"
|
||
case unsupportedIdentifierErr:
|
||
return "unsupportedIdentifier"
|
||
case userActionRequiredErr:
|
||
return "userActionRequired"
|
||
case notImplemented:
|
||
return "notImplemented"
|
||
default:
|
||
return "unsupported type"
|
||
}
|
||
}
|
||
|
||
// Error is an ACME error type complete with problem document.
|
||
type Error struct {
|
||
Type ProbType
|
||
Detail string
|
||
Err error
|
||
Status int
|
||
Sub []*Error
|
||
Identifier *Identifier
|
||
}
|
||
|
||
// Wrap attempts to wrap the internal error.
|
||
func Wrap(err error, wrap string) *Error {
|
||
switch e := err.(type) {
|
||
case nil:
|
||
return nil
|
||
case *Error:
|
||
if e.Err == nil {
|
||
e.Err = errors.New(wrap + "; " + e.Detail)
|
||
} else {
|
||
e.Err = errors.Wrap(e.Err, wrap)
|
||
}
|
||
return e
|
||
default:
|
||
return ServerInternalErr(errors.Wrap(err, wrap))
|
||
}
|
||
}
|
||
|
||
// Error implements the error interface.
|
||
func (e *Error) Error() string {
|
||
if e.Err == nil {
|
||
return e.Detail
|
||
}
|
||
return e.Err.Error()
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
// Official returns true if this error's type is listed in §6.7 of RFC 8555.
|
||
// Error types in §6.7 are registered under IETF urn namespace:
|
||
//
|
||
// "urn:ietf:params:acme:error:"
|
||
//
|
||
// and should include the namespace as a prefix when appearing as a problem
|
||
// document.
|
||
//
|
||
// RFC 8555 also says:
|
||
//
|
||
// This list is not exhaustive. The server MAY return errors whose
|
||
// "type" field is set to a URI other than those defined above. Servers
|
||
// MUST NOT use the ACME URN namespace for errors not listed in the
|
||
// appropriate IANA registry (see Section 9.6). Clients SHOULD display
|
||
// the "detail" field of all errors.
|
||
//
|
||
// In this case Official returns `false` so that a different namespace can
|
||
// be used.
|
||
func (e *Error) Official() bool {
|
||
return e.Type != notImplemented
|
||
}
|
||
|
||
// ToACME returns an acme representation of the problem type.
|
||
// For official errors, the IETF ACME namespace is prepended to the error type.
|
||
// For our own errors, we use an (yet) unregistered smallstep acme namespace.
|
||
func (e *Error) ToACME() *AError {
|
||
prefix := "urn:step:acme:error"
|
||
if e.Official() {
|
||
prefix = "urn:ietf:params:acme:error:"
|
||
}
|
||
ae := &AError{
|
||
Type: prefix + e.Type.String(),
|
||
Detail: e.Error(),
|
||
Status: e.Status,
|
||
}
|
||
if e.Identifier != nil {
|
||
ae.Identifier = *e.Identifier
|
||
}
|
||
for _, p := range e.Sub {
|
||
ae.Subproblems = append(ae.Subproblems, p.ToACME())
|
||
}
|
||
return ae
|
||
}
|
||
|
||
// StatusCode returns the status code and implements the StatusCode interface.
|
||
func (e *Error) StatusCode() int {
|
||
return e.Status
|
||
}
|
||
|
||
// AError is the error type as seen in acme request/responses.
|
||
type AError struct {
|
||
Type string `json:"type"`
|
||
Detail string `json:"detail"`
|
||
Identifier interface{} `json:"identifier,omitempty"`
|
||
Subproblems []interface{} `json:"subproblems,omitempty"`
|
||
Status int `json:"-"`
|
||
}
|
||
|
||
// Error allows AError to implement the error interface.
|
||
func (ae *AError) Error() string {
|
||
return ae.Detail
|
||
}
|
||
|
||
// StatusCode returns the status code and implements the StatusCode interface.
|
||
func (ae *AError) StatusCode() int {
|
||
return ae.Status
|
||
}
|