mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-21 12:25:02 +00:00
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.
293 lines
8.3 KiB
Go
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.
|
|
`
|