b4c3aca7a1
* 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>
180 lines
7.3 KiB
Go
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()
|
|
}
|