open-vault/builtin/logical/pki/acme_errors.go
hc-github-team-secure-vault-core 0f66ddb8f8
backport of commit 34571d4d230537126ba4fa879fc161beada742ae (#20850)
Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
2023-05-30 17:42:15 +00:00

193 lines
7.7 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pki
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/sdk/logical"
)
// Error prefix; see RFC 8555 Section 6.7. Errors.
const ErrorPrefix = "urn:ietf:params:acme:error:"
const ErrorContentType = "application/problem+json"
// See RFC 8555 Section 6.7. Errors.
var ErrAccountDoesNotExist = errors.New("The request specified an account that does not exist")
var ErrAcmeDisabled = errors.New("ACME feature is disabled")
var (
ErrAlreadyRevoked = errors.New("The request specified a certificate to be revoked that has already been revoked")
ErrBadCSR = errors.New("The CSR is unacceptable")
ErrBadNonce = errors.New("The client sent an unacceptable anti-replay nonce")
ErrBadPublicKey = errors.New("The JWS was signed by a public key the server does not support")
ErrBadRevocationReason = errors.New("The revocation reason provided is not allowed by the server")
ErrBadSignatureAlgorithm = errors.New("The JWS was signed with an algorithm the server does not support")
ErrCAA = errors.New("Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate")
ErrCompound = errors.New("Specific error conditions are indicated in the 'subproblems' array")
ErrConnection = errors.New("The server could not connect to validation target")
ErrDNS = errors.New("There was a problem with a DNS query during identifier validation")
ErrExternalAccountRequired = errors.New("The request must include a value for the 'externalAccountBinding' field")
ErrIncorrectResponse = errors.New("Response received didn't match the challenge's requirements")
ErrInvalidContact = errors.New("A contact URL for an account was invalid")
ErrMalformed = errors.New("The request message was malformed")
ErrOrderNotReady = errors.New("The request attempted to finalize an order that is not ready to be finalized")
ErrRateLimited = errors.New("The request exceeds a rate limit")
ErrRejectedIdentifier = errors.New("The server will not issue certificates for the identifier")
ErrServerInternal = errors.New("The server experienced an internal error")
ErrTLS = errors.New("The server received a TLS error during validation")
ErrUnauthorized = errors.New("The client lacks sufficient authorization")
ErrUnsupportedContact = errors.New("A contact URL for an account used an unsupported protocol scheme")
ErrUnsupportedIdentifier = errors.New("An identifier is of an unsupported type")
ErrUserActionRequired = errors.New("Visit the 'instance' URL and take actions specified there")
)
// Mapping of err->name; see table in RFC 8555 Section 6.7. Errors.
var errIdMappings = map[error]string{
ErrAccountDoesNotExist: "accountDoesNotExist",
ErrAlreadyRevoked: "alreadyRevoked",
ErrBadCSR: "badCSR",
ErrBadNonce: "badNonce",
ErrBadPublicKey: "badPublicKey",
ErrBadRevocationReason: "badRevocationReason",
ErrBadSignatureAlgorithm: "badSignatureAlgorithm",
ErrCAA: "caa",
ErrCompound: "compound",
ErrConnection: "connection",
ErrDNS: "dns",
ErrExternalAccountRequired: "externalAccountRequired",
ErrIncorrectResponse: "incorrectResponse",
ErrInvalidContact: "invalidContact",
ErrMalformed: "malformed",
ErrOrderNotReady: "orderNotReady",
ErrRateLimited: "rateLimited",
ErrRejectedIdentifier: "rejectedIdentifier",
ErrServerInternal: "serverInternal",
ErrTLS: "tls",
ErrUnauthorized: "unauthorized",
ErrUnsupportedContact: "unsupportedContact",
ErrUnsupportedIdentifier: "unsupportedIdentifier",
ErrUserActionRequired: "userActionRequired",
}
// Mapping of err->status codes; see table in RFC 8555 Section 6.7. Errors.
var errCodeMappings = map[error]int{
ErrAccountDoesNotExist: http.StatusBadRequest, // See RFC 8555 Section 7.3.1. Finding an Account URL Given a Key.
ErrAlreadyRevoked: http.StatusBadRequest,
ErrBadCSR: http.StatusBadRequest,
ErrBadNonce: http.StatusBadRequest,
ErrBadPublicKey: http.StatusBadRequest,
ErrBadRevocationReason: http.StatusBadRequest,
ErrBadSignatureAlgorithm: http.StatusBadRequest,
ErrCAA: http.StatusForbidden,
ErrCompound: http.StatusBadRequest,
ErrConnection: http.StatusInternalServerError,
ErrDNS: http.StatusInternalServerError,
ErrExternalAccountRequired: http.StatusUnauthorized,
ErrIncorrectResponse: http.StatusBadRequest,
ErrInvalidContact: http.StatusBadRequest,
ErrMalformed: http.StatusBadRequest,
ErrOrderNotReady: http.StatusForbidden, // See RFC 8555 Section 7.4. Applying for Certificate Issuance.
ErrRateLimited: http.StatusTooManyRequests,
ErrRejectedIdentifier: http.StatusBadRequest,
ErrServerInternal: http.StatusInternalServerError,
ErrTLS: http.StatusInternalServerError,
ErrUnauthorized: http.StatusUnauthorized,
ErrUnsupportedContact: http.StatusBadRequest,
ErrUnsupportedIdentifier: http.StatusBadRequest,
ErrUserActionRequired: http.StatusUnauthorized,
}
type ErrorResponse struct {
StatusCode int `json:"-"`
Type string `json:"type"`
Detail string `json:"detail"`
Subproblems []*ErrorResponse `json:"subproblems"`
}
func (e *ErrorResponse) Marshal() (*logical.Response, error) {
body, err := json.Marshal(e)
if err != nil {
return nil, fmt.Errorf("failed marshalling of error response: %w", err)
}
var resp logical.Response
resp.Data = map[string]interface{}{
logical.HTTPContentType: ErrorContentType,
logical.HTTPRawBody: body,
logical.HTTPStatusCode: e.StatusCode,
}
return &resp, nil
}
func FindType(given error) (err error, id string, code int, found bool) {
matchedError := false
for err, id = range errIdMappings {
if errors.Is(given, err) {
matchedError = true
break
}
}
// If the given error was not matched from one of the standard ACME errors
// make this error, force ErrServerInternal
if !matchedError {
err = ErrServerInternal
id = errIdMappings[err]
}
code = errCodeMappings[err]
return
}
func TranslateError(given error) (*logical.Response, error) {
if errors.Is(given, logical.ErrReadOnly) {
return nil, given
}
if errors.Is(given, ErrAcmeDisabled) {
return logical.RespondWithStatusCode(nil, nil, http.StatusNotFound)
}
// We're multierror aware here: if we're given a list of errors, assume
// they're structured so the first error is the outer error and the inner
// subproblems are subsequent in the multierror.
var remaining []error
if unwrapped, ok := given.(*multierror.Error); ok {
remaining = unwrapped.Errors[1:]
given = unwrapped.Errors[0]
}
_, id, code, found := FindType(given)
if !found && len(remaining) > 0 {
// Translate multierrors into a generic error code.
id = errIdMappings[ErrCompound]
code = errCodeMappings[ErrCompound]
}
var body ErrorResponse
body.Type = ErrorPrefix + id
body.Detail = given.Error()
body.StatusCode = code
for _, subgiven := range remaining {
_, subid, _, _ := FindType(subgiven)
var sub ErrorResponse
sub.Type = ErrorPrefix + subid
body.Detail = subgiven.Error()
body.Subproblems = append(body.Subproblems, &sub)
}
return body.Marshal()
}