open-vault/builtin/logical/pki/acme_errors.go
Alexander Scheel b4c3aca7a1
Merge ACME package back into the PKI package (#19826)
* Squash pki/acme package down to pki folder

Without refactoring most of PKI to export the storage layer, which we
were initially hesitant about, it would be nearly impossible to have the
ACME layer handle its own storage while being in the acme/ subpackage
under the pki package.

Thus, merge the two packages together again.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Properly format errors for missing parameters

When missing required ACME request parameters, don't return Vault-level
errors, but drop into the PKI package to return properly-formatted ACME
error messages.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Error type clarifications

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Fix GetOk with type conversion calls

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-03-29 21:08:31 +00:00

180 lines
7.3 KiB
Go

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 (
ErrAlreadyRevoked = errors.New("The request specified a certificate to be revoked that has already been revoked")
ErrBadCSR = errors.New("The CSR is unacceptable (e.g., due to a short key)")
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) {
for err, id = range errIdMappings {
if errors.Is(given, err) {
break
}
}
if err == nil {
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
}
// 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()
}