Files
vault/builtin/logical/transit/path_certificates.go
Max Bowsher 479519e6df Fix erroneous OpenAPI operation IDs (#22579)
The recent additions to the transit secret engine have created two new
endpoints which both have the incorrect (and duplicate) operation ID of
just `"update"`.

Amend to unique meaningful values.
2023-08-28 09:11:16 -04:00

293 lines
8.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package transit
import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"strings"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/keysutil"
"github.com/hashicorp/vault/sdk/logical"
)
func (b *backend) pathCreateCsr() *framework.Path {
return &framework.Path{
Pattern: "keys/" + framework.GenericNameRegex("name") + "/csr",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTransit,
OperationVerb: "generate-csr-for-key",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Required: true,
Description: "Name of the key",
},
"version": {
Type: framework.TypeInt,
Required: false,
Description: "Optional version of key, 'latest' if not set",
},
"csr": {
Type: framework.TypeString,
Required: false,
Description: `PEM encoded CSR template. The information attributes
will be used as a basis for the CSR with the key in transit. If not set, an empty CSR is returned.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathCreateCsrWrite,
},
},
HelpSynopsis: pathCreateCsrHelpSyn,
HelpDescription: pathCreateCsrHelpDesc,
}
}
func (b *backend) pathImportCertChain() *framework.Path {
return &framework.Path{
// NOTE: `set-certificate` or `set_certificate`? Paths seem to use different
// case, such as `transit/wrapping_key` and `transit/cache-config`.
Pattern: "keys/" + framework.GenericNameRegex("name") + "/set-certificate",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTransit,
OperationVerb: "set-certificate-for-key",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Required: true,
Description: "Name of the key",
},
"version": {
Type: framework.TypeInt,
Required: false,
Description: "Optional version of key, 'latest' if not set",
},
"certificate_chain": {
Type: framework.TypeString,
Required: true,
Description: `PEM encoded certificate chain. It should be composed
by one or more concatenated PEM blocks and ordered starting from the end-entity certificate.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathImportCertChainWrite,
},
},
HelpSynopsis: pathImportCertChainHelpSyn,
HelpDescription: pathImportCertChainHelpDesc,
}
}
func (b *backend) pathCreateCsrWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage,
Name: name,
}, b.GetRandomReader())
if err != nil {
return nil, err
}
if p == nil {
return logical.ErrorResponse(fmt.Sprintf("key with provided name '%s' not found", name)), logical.ErrInvalidRequest
}
if !b.System().CachingDisabled() {
p.Lock(false) // NOTE: No lock on "read" operations?
}
defer p.Unlock()
// Check if transit key supports signing
if !p.Type.SigningSupported() {
return logical.ErrorResponse(fmt.Sprintf("key type '%s' does not support signing", p.Type)), logical.ErrInvalidRequest
}
// Check if key can be derived
if p.Derived {
return logical.ErrorResponse("operation not supported on keys with derivation enabled"), logical.ErrInvalidRequest
}
// Transit key version
signingKeyVersion := p.LatestVersion
// NOTE: BYOK endpoints seem to remove "v" prefix from version,
// are versions like that also supported?
if version, ok := d.GetOk("version"); ok {
signingKeyVersion = version.(int)
}
// Read and parse CSR template
pemCsrTemplate := d.Get("csr").(string)
csrTemplate, err := parseCsr(pemCsrTemplate)
if err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
pemCsr, err := p.CreateCsr(signingKeyVersion, csrTemplate)
if err != nil {
prefixedErr := fmt.Errorf("could not create the csr: %w", err)
switch err.(type) {
case errutil.UserError:
return logical.ErrorResponse(prefixedErr.Error()), logical.ErrInvalidRequest
default:
return nil, prefixedErr
}
}
resp := &logical.Response{
Data: map[string]interface{}{
"name": p.Name,
"type": p.Type.String(),
"csr": string(pemCsr),
},
}
return resp, nil
}
func (b *backend) pathImportCertChainWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage,
Name: name,
}, b.GetRandomReader())
if err != nil {
return nil, err
}
if p == nil {
return logical.ErrorResponse(fmt.Sprintf("key with provided name '%s' not found", name)), logical.ErrInvalidRequest
}
if !b.System().CachingDisabled() {
p.Lock(true) // NOTE: Lock as we are might write to the policy
}
defer p.Unlock()
// Check if transit key supports signing
if !p.Type.SigningSupported() {
return logical.ErrorResponse(fmt.Sprintf("key type %s does not support signing", p.Type)), logical.ErrInvalidRequest
}
// Check if key can be derived
if p.Derived {
return logical.ErrorResponse("operation not supported on keys with derivation enabled"), logical.ErrInvalidRequest
}
// Transit key version
keyVersion := p.LatestVersion
if version, ok := d.GetOk("version"); ok {
keyVersion = version.(int)
}
// Get certificate chain
pemCertChain := d.Get("certificate_chain").(string)
certChain, err := parseCertificateChain(pemCertChain)
if err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
err = p.ValidateAndPersistCertificateChain(ctx, keyVersion, certChain, req.Storage)
if err != nil {
prefixedErr := fmt.Errorf("failed to persist certificate chain: %w", err)
switch err.(type) {
case errutil.UserError:
return logical.ErrorResponse(prefixedErr.Error()), logical.ErrInvalidRequest
default:
return nil, prefixedErr
}
}
resp := &logical.Response{
Data: map[string]interface{}{
"name": p.Name,
"type": p.Type.String(),
"certificate-chain": pemCertChain,
},
}
return resp, nil
}
func parseCsr(csrStr string) (*x509.CertificateRequest, error) {
if csrStr == "" {
return &x509.CertificateRequest{}, nil
}
block, _ := pem.Decode([]byte(csrStr))
if block == nil {
return nil, errors.New("could not decode PEM certificate request")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, err
}
return csr, nil
}
func parseCertificateChain(certChainString string) ([]*x509.Certificate, error) {
var certificates []*x509.Certificate
var pemCertBlocks []*pem.Block
pemBytes := []byte(strings.TrimSpace(certChainString))
for len(pemBytes) > 0 {
var pemCertBlock *pem.Block
pemCertBlock, pemBytes = pem.Decode(pemBytes)
if pemCertBlock == nil {
return nil, errors.New("could not decode PEM block in certificate chain")
}
switch pemCertBlock.Type {
case "CERTIFICATE", "X05 CERTIFICATE":
pemCertBlocks = append(pemCertBlocks, pemCertBlock)
default:
// Ignore any other entries
}
}
if len(pemCertBlocks) == 0 {
return nil, errors.New("provided certificate chain did not contain any valid PEM certificate")
}
for _, certBlock := range pemCertBlocks {
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate in certificate chain: %w", err)
}
certificates = append(certificates, cert)
}
return certificates, nil
}
const pathCreateCsrHelpSyn = `Create a CSR from a key in transit`
const pathCreateCsrHelpDesc = `This path is used to create a CSR from a key in
transit. If a CSR template is provided, its significant information, expect key
related data, are included in the CSR otherwise an empty CSR is returned.
`
const pathImportCertChainHelpSyn = `Imports an externally-signed certificate
chain into an existing key version`
const pathImportCertChainHelpDesc = `This path is used to import an externally-
signed certificate chain into a key in transit. The leaf certificate key has to
match the selected key in transit.
`