mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
211 lines
8.1 KiB
Go
211 lines
8.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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) MarshalForStorage() map[string]interface{} {
|
|
subProblems := []map[string]interface{}{}
|
|
for _, subProblem := range e.Subproblems {
|
|
subProblems = append(subProblems, subProblem.MarshalForStorage())
|
|
}
|
|
return map[string]interface{}{
|
|
"status": e.StatusCode,
|
|
"type": e.Type,
|
|
"detail": e.Detail,
|
|
"subproblems": 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)
|
|
}
|
|
|
|
body := TranslateErrorToErrorResponse(given)
|
|
|
|
return body.Marshal()
|
|
}
|
|
|
|
func TranslateErrorToErrorResponse(given error) ErrorResponse {
|
|
// 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
|
|
}
|