open-vault/builtin/logical/pki/path_intermediate.go
2019-04-12 17:54:35 -04:00

255 lines
6.4 KiB
Go

package pki
import (
"context"
"encoding/base64"
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/framework"
)
func pathGenerateIntermediate(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "intermediate/generate/" + framework.GenericNameRegex("exported"),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathGenerateIntermediate,
},
HelpSynopsis: pathGenerateIntermediateHelpSyn,
HelpDescription: pathGenerateIntermediateHelpDesc,
}
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
ret.Fields = addCAKeyGenerationFields(ret.Fields)
ret.Fields["add_basic_constraints"] = &framework.FieldSchema{
Type: framework.TypeBool,
Description: `Whether to add a Basic Constraints
extension with CA: true. Only needed as a
workaround in some compatibility scenarios
with Active Directory Certificate Services.`,
}
return ret
}
func pathSetSignedIntermediate(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "intermediate/set-signed",
Fields: map[string]*framework.FieldSchema{
"certificate": &framework.FieldSchema{
Type: framework.TypeString,
Description: `PEM-format certificate. This must be a CA
certificate with a public key matching the
previously-generated key from the generation
endpoint.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathSetSignedIntermediate,
},
HelpSynopsis: pathSetSignedIntermediateHelpSyn,
HelpDescription: pathSetSignedIntermediateHelpDesc,
}
return ret
}
func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var err error
exported, format, role, errorResp := b.getGenerationParams(data)
if errorResp != nil {
return errorResp, nil
}
var resp *logical.Response
input := &dataBundle{
role: role,
req: req,
apiData: data,
}
parsedBundle, err := generateIntermediateCSR(b, input)
if err != nil {
switch err.(type) {
case errutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case errutil.InternalError:
return nil, err
}
}
csrb, err := parsedBundle.ToCSRBundle()
if err != nil {
return nil, errwrap.Wrapf("error converting raw CSR bundle to CSR bundle: {{err}}", err)
}
resp = &logical.Response{
Data: map[string]interface{}{},
}
switch format {
case "pem":
resp.Data["csr"] = csrb.CSR
if exported {
resp.Data["private_key"] = csrb.PrivateKey
resp.Data["private_key_type"] = csrb.PrivateKeyType
}
case "pem_bundle":
resp.Data["csr"] = csrb.CSR
if exported {
resp.Data["csr"] = fmt.Sprintf("%s\n%s", csrb.PrivateKey, csrb.CSR)
resp.Data["private_key"] = csrb.PrivateKey
resp.Data["private_key_type"] = csrb.PrivateKeyType
}
case "der":
resp.Data["csr"] = base64.StdEncoding.EncodeToString(parsedBundle.CSRBytes)
if exported {
resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
resp.Data["private_key_type"] = csrb.PrivateKeyType
}
}
if data.Get("private_key_format").(string) == "pkcs8" {
err = convertRespToPKCS8(resp)
if err != nil {
return nil, err
}
}
cb := &certutil.CertBundle{}
cb.PrivateKey = csrb.PrivateKey
cb.PrivateKeyType = csrb.PrivateKeyType
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
return resp, nil
}
func (b *backend) pathSetSignedIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
cert := data.Get("certificate").(string)
if cert == "" {
return logical.ErrorResponse("no certificate provided in the \"certificate\" parameter"), nil
}
inputBundle, err := certutil.ParsePEMBundle(cert)
if err != nil {
switch err.(type) {
case errutil.InternalError:
return nil, err
default:
return logical.ErrorResponse(err.Error()), nil
}
}
if inputBundle.Certificate == nil {
return logical.ErrorResponse("supplied certificate could not be successfully parsed"), nil
}
cb := &certutil.CertBundle{}
entry, err := req.Storage.Get(ctx, "config/ca_bundle")
if err != nil {
return nil, err
}
if entry == nil {
return logical.ErrorResponse("could not find any existing entry with a private key"), nil
}
err = entry.DecodeJSON(cb)
if err != nil {
return nil, err
}
if len(cb.PrivateKey) == 0 || cb.PrivateKeyType == "" {
return logical.ErrorResponse("could not find an existing private key"), nil
}
parsedCB, err := cb.ToParsedCertBundle()
if err != nil {
return nil, err
}
if parsedCB.PrivateKey == nil {
return nil, fmt.Errorf("saved key could not be parsed successfully")
}
inputBundle.PrivateKey = parsedCB.PrivateKey
inputBundle.PrivateKeyType = parsedCB.PrivateKeyType
inputBundle.PrivateKeyBytes = parsedCB.PrivateKeyBytes
if !inputBundle.Certificate.IsCA {
return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil
}
if err := inputBundle.Verify(); err != nil {
return nil, errwrap.Wrapf("verification of parsed bundle failed: {{err}}", err)
}
cb, err = inputBundle.ToCertBundle()
if err != nil {
return nil, errwrap.Wrapf("error converting raw values into cert bundle: {{err}}", err)
}
entry, err = logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
entry.Key = "certs/" + normalizeSerial(cb.SerialNumber)
entry.Value = inputBundle.CertificateBytes
err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
// For ease of later use, also store just the certificate at a known
// location
entry.Key = "ca"
entry.Value = inputBundle.CertificateBytes
err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
// Build a fresh CRL
err = buildCRL(ctx, b, req, true)
return nil, err
}
const pathGenerateIntermediateHelpSyn = `
Generate a new CSR and private key used for signing.
`
const pathGenerateIntermediateHelpDesc = `
See the API documentation for more information.
`
const pathSetSignedIntermediateHelpSyn = `
Provide the signed intermediate CA cert.
`
const pathSetSignedIntermediateHelpDesc = `
See the API documentation for more information.
`