2015-11-18 15:16:09 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/vault/helper/certutil"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
|
|
|
func pathGenerateRoot(b *backend) *framework.Path {
|
|
|
|
ret := &framework.Path{
|
|
|
|
Pattern: "root/generate/" + framework.GenericNameRegex("exported"),
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2016-01-07 15:30:47 +00:00
|
|
|
logical.UpdateOperation: b.pathCAGenerateRoot,
|
2015-11-18 15:16:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathGenerateRootHelpSyn,
|
|
|
|
HelpDescription: pathGenerateRootHelpDesc,
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
|
|
|
|
ret.Fields = addCAKeyGenerationFields(ret.Fields)
|
|
|
|
ret.Fields = addCAIssueFields(ret.Fields)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func pathSignIntermediate(b *backend) *framework.Path {
|
|
|
|
ret := &framework.Path{
|
|
|
|
Pattern: "root/sign-intermediate",
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2016-01-07 15:30:47 +00:00
|
|
|
logical.UpdateOperation: b.pathCASignIntermediate,
|
2015-11-18 15:16:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathSignIntermediateHelpSyn,
|
|
|
|
HelpDescription: pathSignIntermediateHelpDesc,
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
|
|
|
|
ret.Fields = addCAIssueFields(ret.Fields)
|
|
|
|
|
|
|
|
ret.Fields["csr"] = &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "",
|
|
|
|
Description: `PEM-format CSR to be signed.`,
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.Fields["use_csr_values"] = &framework.FieldSchema{
|
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If true, then:
|
|
|
|
1) Subject information, including names and alternate
|
|
|
|
names, will be preserved from the CSR rather than
|
|
|
|
using values provided in the other parameters to
|
|
|
|
this path;
|
|
|
|
2) Any key usages requested in the CSR will be
|
|
|
|
added to the basic set of key usages used for CA
|
|
|
|
certs signed by this path; for instance,
|
|
|
|
the non-repudiation flag.`,
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathCAGenerateRoot(
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
maxPathLengthIface, ok := data.GetOk("max_path_length")
|
|
|
|
if ok {
|
|
|
|
maxPathLength := maxPathLengthIface.(int)
|
|
|
|
role.MaxPathLength = &maxPathLength
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedBundle, err := generateCert(b, role, nil, true, req, data)
|
|
|
|
if err != nil {
|
|
|
|
switch err.(type) {
|
|
|
|
case certutil.UserError:
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
case certutil.InternalError:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cb, err := parsedBundle.ToCertBundle()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error converting raw cert bundle to cert bundle: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
resp := b.Secret(SecretCertsType).Response(
|
|
|
|
map[string]interface{}{
|
2015-11-18 15:16:09 +00:00
|
|
|
"expiration": int64(parsedBundle.Certificate.NotAfter.Unix()),
|
2015-11-19 21:51:27 +00:00
|
|
|
"serial_number": cb.SerialNumber,
|
|
|
|
"certificate": cb.Certificate,
|
|
|
|
"issuing_ca": cb.IssuingCA,
|
2015-11-18 15:16:09 +00:00
|
|
|
},
|
2015-11-19 21:51:27 +00:00
|
|
|
map[string]interface{}{
|
|
|
|
"serial_number": cb.SerialNumber,
|
|
|
|
})
|
2015-11-18 15:16:09 +00:00
|
|
|
|
|
|
|
switch format {
|
|
|
|
case "pem":
|
|
|
|
if exported {
|
|
|
|
resp.Data["private_key"] = cb.PrivateKey
|
|
|
|
resp.Data["private_key_type"] = cb.PrivateKeyType
|
|
|
|
}
|
|
|
|
case "der":
|
|
|
|
resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
|
|
|
|
resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes)
|
|
|
|
if exported {
|
|
|
|
resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
|
|
|
|
resp.Data["private_key_type"] = cb.PrivateKeyType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now())
|
|
|
|
|
|
|
|
// Store it as the CA bundle
|
2015-11-18 15:16:09 +00:00
|
|
|
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = req.Storage.Put(entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Also store it as just the certificate identified by serial number, so it
|
|
|
|
// can be revoked
|
|
|
|
err = req.Storage.Put(&logical.StorageEntry{
|
|
|
|
Key: "certs/" + cb.SerialNumber,
|
|
|
|
Value: parsedBundle.CertificateBytes,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to store certificate locally")
|
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
// For ease of later use, also store just the certificate at a known
|
2015-11-19 21:51:27 +00:00
|
|
|
// location
|
2015-11-18 15:16:09 +00:00
|
|
|
entry.Key = "ca"
|
|
|
|
entry.Value = parsedBundle.CertificateBytes
|
|
|
|
err = req.Storage.Put(entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Build a fresh CRL
|
2015-11-18 15:16:09 +00:00
|
|
|
err = buildCRL(b, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if parsedBundle.Certificate.MaxPathLen == 0 {
|
|
|
|
resp.AddWarning("Max path length of the generated certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathCASignIntermediate(
|
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
format := getFormat(data)
|
|
|
|
if format == "" {
|
|
|
|
return logical.ErrorResponse(
|
|
|
|
`The "format" path parameter must be "pem" or "der"`,
|
|
|
|
), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
role := &roleEntry{
|
|
|
|
TTL: data.Get("ttl").(string),
|
|
|
|
AllowLocalhost: true,
|
|
|
|
AllowAnyName: true,
|
|
|
|
AllowIPSANs: true,
|
|
|
|
EnforceHostnames: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
if cn := data.Get("common_name").(string); len(cn) == 0 {
|
|
|
|
role.UseCSRCommonName = true
|
|
|
|
}
|
|
|
|
|
|
|
|
var caErr error
|
|
|
|
signingBundle, caErr := fetchCAInfo(req)
|
|
|
|
switch caErr.(type) {
|
|
|
|
case certutil.UserError:
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"could not fetch the CA certificate (was one set?): %s", caErr)}
|
|
|
|
case certutil.InternalError:
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf(
|
|
|
|
"error fetching CA certificate: %s", caErr)}
|
|
|
|
}
|
|
|
|
|
|
|
|
useCSRValues := data.Get("use_csr_values").(bool)
|
|
|
|
|
|
|
|
maxPathLengthIface, ok := data.GetOk("max_path_length")
|
|
|
|
if ok {
|
|
|
|
maxPathLength := maxPathLengthIface.(int)
|
|
|
|
role.MaxPathLength = &maxPathLength
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedBundle, err := signCert(b, role, signingBundle, true, useCSRValues, req, data)
|
|
|
|
if err != nil {
|
|
|
|
switch err.(type) {
|
|
|
|
case certutil.UserError:
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
case certutil.InternalError:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cb, err := parsedBundle.ToCertBundle()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp := b.Secret(SecretCertsType).Response(
|
|
|
|
map[string]interface{}{
|
|
|
|
"expiration": int64(parsedBundle.Certificate.NotAfter.Unix()),
|
|
|
|
"serial_number": cb.SerialNumber,
|
2015-11-19 21:51:27 +00:00
|
|
|
"certificate": cb.Certificate,
|
|
|
|
"issuing_ca": cb.IssuingCA,
|
2015-11-18 15:16:09 +00:00
|
|
|
},
|
|
|
|
map[string]interface{}{
|
|
|
|
"serial_number": cb.SerialNumber,
|
|
|
|
})
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
if format == "der" {
|
2015-11-18 15:16:09 +00:00
|
|
|
resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
|
|
|
|
resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now())
|
|
|
|
|
|
|
|
err = req.Storage.Put(&logical.StorageEntry{
|
|
|
|
Key: "certs/" + cb.SerialNumber,
|
|
|
|
Value: parsedBundle.CertificateBytes,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to store certificate locally")
|
|
|
|
}
|
|
|
|
|
|
|
|
if parsedBundle.Certificate.MaxPathLen == 0 {
|
|
|
|
resp.AddWarning("Max path length of the signed certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const pathGenerateRootHelpSyn = `
|
|
|
|
Generate a new CA certificate and private key used for signing.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathGenerateRootHelpDesc = `
|
|
|
|
See the API documentation for more information.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathSignIntermediateHelpSyn = `
|
|
|
|
Issue an intermediate CA certificate based on the provided CSR.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathSignIntermediateHelpDesc = `
|
|
|
|
See the API documentation for more information.
|
|
|
|
`
|