Split root and intermediate functionality into their own sections in the API. Update documentation. Add sign-verbatim endpoint.

This commit is contained in:
Jeff Mitchell
2015-11-18 10:16:09 -05:00
parent cb5514f3f3
commit 3437af0711
9 changed files with 1143 additions and 783 deletions

View File

@@ -32,13 +32,14 @@ func Backend() *framework.Backend {
Paths: []*framework.Path{ Paths: []*framework.Path{
pathRoles(&b), pathRoles(&b),
pathGenerateRootCA(&b), pathGenerateRoot(&b),
pathGenerateIntermediateCA(&b), pathGenerateIntermediate(&b),
pathSignIntermediateCA(&b), pathSetSignedIntermediate(&b),
pathSetCA(&b), pathSignIntermediate(&b),
pathConfigCA(&b), pathConfigCA(&b),
pathConfigCRL(&b), pathConfigCRL(&b),
pathConfigURLs(&b), pathConfigURLs(&b),
pathSignVerbatim(&b),
pathSign(&b), pathSign(&b),
pathIssue(&b), pathIssue(&b),
pathRotateCRL(&b), pathRotateCRL(&b),

View File

@@ -311,7 +311,6 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage certUsage,
} }
func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep { func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep {
expected := urlEntries{ expected := urlEntries{
IssuingCertificates: []string{ IssuingCertificates: []string{
"http://example.com/ca1", "http://example.com/ca1",
@@ -342,7 +341,7 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
ret := []logicaltest.TestStep{ ret := []logicaltest.TestStep{
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/root/exported", Path: "root/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Root Cert", "common_name": "Root Cert",
"ttl": "180h", "ttl": "180h",
@@ -382,7 +381,7 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/sign", Path: "root/sign-intermediate",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Intermediate Cert", "common_name": "Intermediate Cert",
"csr": string(csrPem), "csr": string(csrPem),
@@ -452,7 +451,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
ret := []logicaltest.TestStep{ ret := []logicaltest.TestStep{
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/root/exported", Path: "root/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Root Cert", "common_name": "Root Cert",
"ttl": "180h", "ttl": "180h",
@@ -462,7 +461,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/sign", Path: "root/sign-intermediate",
Data: map[string]interface{}{ Data: map[string]interface{}{
"use_csr_values": true, "use_csr_values": true,
"csr": string(csrPem), "csr": string(csrPem),
@@ -473,7 +472,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/root/exported", Path: "root/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Root Cert", "common_name": "Root Cert",
"ttl": "180h", "ttl": "180h",
@@ -483,7 +482,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/sign", Path: "root/sign-intermediate",
Data: map[string]interface{}{ Data: map[string]interface{}{
"use_csr_values": true, "use_csr_values": true,
"csr": string(csrPem), "csr": string(csrPem),
@@ -612,26 +611,25 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
}, },
}, },
// Now test uploading when the private key is already stored, such // Ensure that both parts of the PEM bundle are required
// as when uploading a CA signed as the result of a generated CSR // Here, just the cert
// First we test the wrong one, to ensure that the key comparator is
// working correctly
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "config/ca/set",
Data: map[string]interface{}{
"pem_bundle": otherCaCert,
},
ErrorOk: true,
},
// Now, the right one
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/set", Path: "config/ca/set",
Data: map[string]interface{}{ Data: map[string]interface{}{
"pem_bundle": caCert, "pem_bundle": caCert,
}, },
ErrorOk: true,
},
// Here, just the key
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "config/ca/set",
Data: map[string]interface{}{
"pem_bundle": caKey,
},
ErrorOk: true,
}, },
// Ensure we can fetch it back via unauthenticated means, in various formats // Ensure we can fetch it back via unauthenticated means, in various formats
@@ -686,7 +684,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
// Test a bunch of generation stuff // Test a bunch of generation stuff
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/root/exported", Path: "root/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Root Cert", "common_name": "Root Cert",
"ttl": "180h", "ttl": "180h",
@@ -701,7 +699,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/intermediate/exported", Path: "intermediate/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Intermediate Cert", "common_name": "Intermediate Cert",
}, },
@@ -712,6 +710,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
}, },
}, },
// Re-load the root key in so we can sign it
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/set", Path: "config/ca/set",
@@ -728,14 +727,38 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/sign", Path: "root/sign-intermediate",
Data: reqdata, Data: reqdata,
Check: func(resp *logical.Response) error { Check: func(resp *logical.Response) error {
intdata["intermediatecert"] = resp.Data["certificate"].(string)
delete(reqdata, "csr") delete(reqdata, "csr")
delete(reqdata, "common_name") delete(reqdata, "common_name")
delete(reqdata, "ttl") delete(reqdata, "ttl")
intdata["intermediatecert"] = resp.Data["certificate"].(string)
reqdata["serial_number"] = resp.Data["serial_number"].(string) reqdata["serial_number"] = resp.Data["serial_number"].(string)
reqdata["certificate"] = resp.Data["certificate"].(string)
reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string)
return nil
},
},
// First load in this way to populate the private key
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "config/ca/set",
Data: reqdata,
Check: func(resp *logical.Response) error {
delete(reqdata, "pem_bundle")
return nil
},
},
// Now test setting the intermediate, signed CA cert
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "intermediate/set-signed",
Data: reqdata,
Check: func(resp *logical.Response) error {
delete(reqdata, "certificate")
return nil return nil
}, },
}, },
@@ -772,7 +795,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
// Do it all again, with EC keys and DER format // Do it all again, with EC keys and DER format
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/root/exported", Path: "root/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"common_name": "Root Cert", "common_name": "Root Cert",
"ttl": "180h", "ttl": "180h",
@@ -800,7 +823,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/generate/intermediate/exported", Path: "intermediate/generate/exported",
Data: map[string]interface{}{ Data: map[string]interface{}{
"format": "der", "format": "der",
"key_type": "ec", "key_type": "ec",
@@ -840,14 +863,38 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
logicaltest.TestStep{ logicaltest.TestStep{
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: "config/ca/sign", Path: "root/sign-intermediate",
Data: reqdata, Data: reqdata,
Check: func(resp *logical.Response) error { Check: func(resp *logical.Response) error {
intdata["intermediatecert"] = resp.Data["certificate"].(string)
delete(reqdata, "csr") delete(reqdata, "csr")
delete(reqdata, "common_name") delete(reqdata, "common_name")
delete(reqdata, "ttl") delete(reqdata, "ttl")
intdata["intermediatecert"] = resp.Data["certificate"].(string)
reqdata["serial_number"] = resp.Data["serial_number"].(string) reqdata["serial_number"] = resp.Data["serial_number"].(string)
reqdata["certificate"] = resp.Data["certificate"].(string)
reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string)
return nil
},
},
// First load in this way to populate the private key
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "config/ca/set",
Data: reqdata,
Check: func(resp *logical.Response) error {
delete(reqdata, "pem_bundle")
return nil
},
},
// Now test setting the intermediate, signed CA cert
logicaltest.TestStep{
Operation: logical.WriteOperation,
Path: "intermediate/set-signed",
Data: reqdata,
Check: func(resp *logical.Response) error {
delete(reqdata, "certificate")
return nil return nil
}, },
}, },

View File

@@ -0,0 +1,42 @@
package pki
import (
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func (b *backend) getGenerationParams(
data *framework.FieldData,
) (exported bool, format string, role *roleEntry, errorResp *logical.Response) {
exportedStr := data.Get("exported").(string)
switch exportedStr {
case "exported":
exported = true
case "internal":
default:
errorResp = logical.ErrorResponse(
`The "exported" path parameter must be "internal" or "exported"`)
return
}
format = getFormat(data)
if format == "" {
errorResp = logical.ErrorResponse(
`The "format" path parameter must be "pem" or "der"`)
return
}
role = &roleEntry{
TTL: data.Get("ttl").(string),
KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int),
AllowLocalhost: true,
AllowAnyName: true,
AllowIPSANs: true,
EnforceHostnames: false,
}
errorResp = validateKeyTypeLength(role.KeyType, role.KeyBits)
return
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net" "net"
@@ -54,7 +55,19 @@ type caInfoBundle struct {
URLs *urlEntries URLs *urlEntries
} }
var hostnameRegex = regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`) var (
hostnameRegex = regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
oidExtensionBasicConstraints = []int{2, 5, 29, 19}
)
func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) bool {
for _, e := range extensions {
if e.Id.Equal(oid) {
return true
}
}
return false
}
func getFormat(data *framework.FieldData) string { func getFormat(data *framework.FieldData) string {
format := data.Get("format").(string) format := data.Get("format").(string)
@@ -370,10 +383,8 @@ func signCert(b *backend,
return nil, err return nil, err
} }
if isCA {
creationBundle.IsCA = isCA creationBundle.IsCA = isCA
creationBundle.UseCSRValues = useCSRValues creationBundle.UseCSRValues = useCSRValues
}
parsedBundle, err := signCertificate(creationBundle, csr) parsedBundle, err := signCertificate(creationBundle, csr)
if err != nil { if err != nil {
@@ -596,7 +607,6 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(creationInfo.TTL), NotAfter: time.Now().Add(creationInfo.TTL),
KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement), KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement),
BasicConstraintsValid: true,
IsCA: false, IsCA: false,
SubjectKeyId: subjKeyID, SubjectKeyId: subjKeyID,
DNSNames: creationInfo.DNSNames, DNSNames: creationInfo.DNSNames,
@@ -649,6 +659,7 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
} }
certTemplate.BasicConstraintsValid = true
certTemplate.IsCA = true certTemplate.IsCA = true
certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign) certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign)
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning) certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning)
@@ -762,7 +773,6 @@ func signCertificate(creationInfo *creationBundle,
Subject: subject, Subject: subject,
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(creationInfo.TTL), NotAfter: time.Now().Add(creationInfo.TTL),
BasicConstraintsValid: true,
SubjectKeyId: subjKeyID[:], SubjectKeyId: subjKeyID[:],
} }
@@ -780,9 +790,13 @@ func signCertificate(creationInfo *creationBundle,
certTemplate.EmailAddresses = csr.EmailAddresses certTemplate.EmailAddresses = csr.EmailAddresses
certTemplate.IPAddresses = csr.IPAddresses certTemplate.IPAddresses = csr.IPAddresses
if creationInfo.IsCA {
certTemplate.ExtraExtensions = csr.Extensions certTemplate.ExtraExtensions = csr.Extensions
// Do not sign a CA certificate if they didn't go through the sign-intermediate
// endpoint
if !creationInfo.IsCA && oidInExtensions(oidExtensionBasicConstraints, certTemplate.ExtraExtensions) {
return nil, certutil.UserError{Err: "will not sign a CSR asking for CA rights through this endpoint"}
} }
} else { } else {
certTemplate.DNSNames = creationInfo.DNSNames certTemplate.DNSNames = creationInfo.DNSNames
certTemplate.EmailAddresses = creationInfo.EmailAddresses certTemplate.EmailAddresses = creationInfo.EmailAddresses
@@ -817,6 +831,7 @@ func signCertificate(creationInfo *creationBundle,
certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers
if creationInfo.IsCA { if creationInfo.IsCA {
certTemplate.BasicConstraintsValid = true
certTemplate.IsCA = true certTemplate.IsCA = true
if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 && if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 &&

View File

@@ -1,9 +1,7 @@
package pki package pki
import ( import (
"encoding/base64"
"fmt" "fmt"
"time"
"github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@@ -13,25 +11,6 @@ import (
func pathConfigCA(b *backend) *framework.Path { func pathConfigCA(b *backend) *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: "config/ca", Pattern: "config/ca",
Fields: map[string]*framework.FieldSchema{
"pem_bundle": &framework.FieldSchema{
Type: framework.TypeString,
Description: `DEPRECATED: use "config/ca/set" instead.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCASetWrite,
},
HelpSynopsis: pathConfigCASetHelpSyn,
HelpDescription: pathConfigCASetHelpDesc,
}
}
func pathSetCA(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/ca/set",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
"pem_bundle": &framework.FieldSchema{ "pem_bundle": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
@@ -43,363 +22,15 @@ endpoint, just the signed certificate.`,
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCASetWrite, logical.WriteOperation: b.pathCAWrite,
}, },
HelpSynopsis: pathConfigCASetHelpSyn, HelpSynopsis: pathConfigCAHelpSyn,
HelpDescription: pathConfigCASetHelpDesc, HelpDescription: pathConfigCAHelpDesc,
} }
} }
func pathGenerateRootCA(b *backend) *framework.Path { func (b *backend) pathCAWrite(
ret := &framework.Path{
Pattern: "config/ca/generate/root/" + framework.GenericNameRegex("exported"),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCAGenerateRoot,
},
HelpSynopsis: pathConfigCAGenerateHelpSyn,
HelpDescription: pathConfigCAGenerateHelpDesc,
}
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
ret.Fields = addCAKeyGenerationFields(ret.Fields)
ret.Fields = addCAIssueFields(ret.Fields)
return ret
}
func pathGenerateIntermediateCA(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "config/ca/generate/intermediate/" + framework.GenericNameRegex("exported"),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCAGenerateIntermediate,
},
HelpSynopsis: pathConfigCAGenerateHelpSyn,
HelpDescription: pathConfigCAGenerateHelpDesc,
}
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
ret.Fields = addCAKeyGenerationFields(ret.Fields)
return ret
}
func pathSignIntermediateCA(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "config/ca/sign",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCASignIntermediate,
},
HelpSynopsis: pathConfigCASignHelpSyn,
HelpDescription: pathConfigCASignHelpDesc,
}
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) getGenerationParams(
data *framework.FieldData,
) (exported bool, format string, role *roleEntry, errorResp *logical.Response) {
exportedStr := data.Get("exported").(string)
switch exportedStr {
case "exported":
exported = true
case "internal":
default:
errorResp = logical.ErrorResponse(
`The "exported" path parameter must be "internal" or "exported"`)
return
}
format = getFormat(data)
if format == "" {
errorResp = logical.ErrorResponse(
`The "format" path parameter must be "pem" or "der"`)
return
}
role = &roleEntry{
TTL: data.Get("ttl").(string),
KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int),
AllowLocalhost: true,
AllowAnyName: true,
AllowIPSANs: true,
EnforceHostnames: false,
}
errorResp = validateKeyTypeLength(role.KeyType, role.KeyBits)
return
}
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
}
var resp *logical.Response
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)
}
resp = &logical.Response{
Data: map[string]interface{}{
"serial_number": cb.SerialNumber,
"expiration": int64(parsedBundle.Certificate.NotAfter.Unix()),
},
}
switch format {
case "pem":
resp.Data["certificate"] = cb.Certificate
resp.Data["issuing_ca"] = cb.IssuingCA
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
}
}
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
}
// For ease of later use, also store just the certificate at a known
// location, plus a fresh CRL
entry.Key = "ca"
entry.Value = parsedBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
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) pathCAGenerateIntermediate(
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
parsedBundle, err := generateIntermediateCSR(b, role, nil, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
csrb, err := parsedBundle.ToCSRBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw CSR bundle to CSR bundle: %s", 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 "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
}
}
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(entry)
if err != nil {
return nil, err
}
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,
},
map[string]interface{}{
"serial_number": cb.SerialNumber,
})
switch format {
case "pem":
resp.Data["certificate"] = cb.Certificate
resp.Data["issuing_ca"] = cb.IssuingCA
case "der":
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
}
func (b *backend) pathCASetWrite(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) { req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
pemBundle := data.Get("pem_bundle").(string) pemBundle := data.Get("pem_bundle").(string)
@@ -413,67 +44,42 @@ func (b *backend) pathCASetWrite(
} }
} }
// Handle the case of a self-signed certificate if parsedBundle.PrivateKey == nil ||
if parsedBundle.Certificate == nil && parsedBundle.IssuingCA != nil { parsedBundle.PrivateKeyType == certutil.UnknownPrivateKey {
return logical.ErrorResponse("private key not found in the PEM bundle"), nil
}
// Handle the case of a self-signed certificate; the parsing function will
// see the CA and put it into the issuer
if parsedBundle.Certificate == nil &&
parsedBundle.IssuingCA != nil {
equal, err := certutil.ComparePublicKeys(parsedBundle.IssuingCA.PublicKey, parsedBundle.PrivateKey.Public())
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"got only a CA and private key but could not verify the public keys match: %v", err)), nil
}
if !equal {
return logical.ErrorResponse(
"got only a CA and private key but keys do not match"), nil
}
parsedBundle.Certificate = parsedBundle.IssuingCA parsedBundle.Certificate = parsedBundle.IssuingCA
parsedBundle.CertificateBytes = parsedBundle.IssuingCABytes parsedBundle.CertificateBytes = parsedBundle.IssuingCABytes
} }
cb := &certutil.CertBundle{} if parsedBundle.Certificate == nil {
entry, err := req.Storage.Get("config/ca_bundle") return logical.ErrorResponse("no certificate found in the PEM bundle"), nil
if err != nil {
return nil, err
}
if entry != nil {
err = entry.DecodeJSON(cb)
if err != nil {
return nil, err
}
// If we have a stored private key and did not get one, attempt to
// correlate the two -- this could be due to a CSR being signed
// for a generated CA cert and the resulting cert now being uploaded
if len(cb.PrivateKey) != 0 &&
cb.PrivateKeyType != "" &&
parsedBundle.PrivateKeyType == certutil.UnknownPrivateKey &&
(parsedBundle.PrivateKeyBytes == nil || len(parsedBundle.PrivateKeyBytes) == 0) {
parsedCB, err := cb.ToParsedCertBundle()
if err != nil {
return nil, err
}
if parsedCB.PrivateKey == nil {
return nil, fmt.Errorf("Encountered nil private key from saved key")
}
// If true, the stored private key corresponds to the cert's
// public key, so fill it in
equal, err := certutil.ComparePublicKeys(parsedCB.PrivateKey.Public(), parsedBundle.Certificate.PublicKey)
if err != nil {
return logical.ErrorResponse(
"stored public key does not match the public key on the certificate"), nil
}
if equal {
parsedBundle.PrivateKey = parsedCB.PrivateKey
parsedBundle.PrivateKeyType = parsedCB.PrivateKeyType
parsedBundle.PrivateKeyBytes = parsedCB.PrivateKeyBytes
}
}
}
if parsedBundle.PrivateKey == nil ||
parsedBundle.PrivateKeyBytes == nil ||
len(parsedBundle.PrivateKeyBytes) == 0 {
return logical.ErrorResponse("No private key given and no matching key stored"), nil
} }
if !parsedBundle.Certificate.IsCA { if !parsedBundle.Certificate.IsCA {
return logical.ErrorResponse("The given certificate is not marked for CA use and cannot be used with this backend"), nil return logical.ErrorResponse("the given certificate is not marked for CA use and cannot be used with this backend"), nil
} }
cb, err = parsedBundle.ToCertBundle() cb, err := parsedBundle.ToCertBundle()
if err != nil { if err != nil {
return nil, fmt.Errorf("Error converting raw values into cert bundle: %s", err) return nil, fmt.Errorf("error converting raw values into cert bundle: %s", err)
} }
entry, err = logical.StorageEntryJSON("config/ca_bundle", cb) entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -496,11 +102,11 @@ func (b *backend) pathCASetWrite(
return nil, err return nil, err
} }
const pathConfigCASetHelpSyn = ` const pathConfigCAHelpSyn = `
Set the CA certificate and private key used for generated credentials. Set the CA certificate and private key used for generated credentials.
` `
const pathConfigCASetHelpDesc = ` const pathConfigCAHelpDesc = `
This sets the CA information used for credentials generated by this This sets the CA information used for credentials generated by this
by this mount. This must be a PEM-format, concatenated unencrypted by this mount. This must be a PEM-format, concatenated unencrypted
secret key and certificate. secret key and certificate.

View File

@@ -0,0 +1,231 @@
package pki
import (
"encoding/base64"
"fmt"
"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathGenerateIntermediate(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "intermediate/generate/" + framework.GenericNameRegex("exported"),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathGenerateIntermediate,
},
HelpSynopsis: pathGenerateIntermediateHelpSyn,
HelpDescription: pathGenerateIntermediateHelpDesc,
}
ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{})
ret.Fields = addCAKeyGenerationFields(ret.Fields)
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.WriteOperation: b.pathSetSignedIntermediate,
},
HelpSynopsis: pathSetSignedIntermediateHelpSyn,
HelpDescription: pathSetSignedIntermediateHelpDesc,
}
return ret
}
func (b *backend) pathGenerateIntermediate(
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
parsedBundle, err := generateIntermediateCSR(b, role, nil, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
csrb, err := parsedBundle.ToCSRBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw CSR bundle to CSR bundle: %s", 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 "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
}
}
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(entry)
if err != nil {
return nil, err
}
return resp, nil
}
func (b *backend) pathSetSignedIntermediate(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
cert := data.Get("certificate").(string)
if cert == "" {
return logical.ErrorResponse("no certificate provided in the \"certficate\" parameter"), nil
}
inputBundle, err := certutil.ParsePEMBundle(cert)
if err != nil {
switch err.(type) {
case certutil.InternalError:
return nil, err
default:
return logical.ErrorResponse(err.Error()), nil
}
}
// If only one certificate is provided and it's a CA
// the parsing will assign it to the IssuingCA, so move it over
if inputBundle.Certificate == nil && inputBundle.IssuingCA != nil {
inputBundle.Certificate = inputBundle.IssuingCA
inputBundle.IssuingCA = nil
inputBundle.CertificateBytes = inputBundle.IssuingCABytes
inputBundle.IssuingCABytes = nil
}
if inputBundle.Certificate == nil {
return logical.ErrorResponse("supplied certificate could not be successfully parsed"), nil
}
cb := &certutil.CertBundle{}
entry, err := req.Storage.Get("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 privat 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")
}
equal, err := certutil.ComparePublicKeys(parsedCB.PrivateKey.Public(), inputBundle.Certificate.PublicKey)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"error matching public keys: %v", err)), nil
}
if !equal {
return logical.ErrorResponse("key in certificate does not match stored key"), nil
}
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
}
cb, err = inputBundle.ToCertBundle()
if err != nil {
return nil, fmt.Errorf("error converting raw values into cert bundle: %s", err)
}
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
}
// For ease of later use, also store just the certificate at a known
// location, plus a fresh CRL
entry.Key = "ca"
entry.Value = inputBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
err = buildCRL(b, req)
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.
`

View File

@@ -51,18 +51,33 @@ func pathSign(b *backend) *framework.Path {
return ret return ret
} }
func pathSignVerbatim(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "sign-verbatim/" + framework.GenericNameRegex("role"),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathSignVerbatim,
},
HelpSynopsis: pathSignHelpSyn,
HelpDescription: pathSignHelpDesc,
}
ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{})
ret.Fields["csr"] = &framework.FieldSchema{
Type: framework.TypeString,
Default: "",
Description: `PEM-format CSR to be signed. Values will be
taken verbatim from the CSR, except for
basic constraints.`,
}
return ret
}
func (b *backend) pathIssue( func (b *backend) pathIssue(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) { req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.pathIssueSignCert(req, data, false)
}
func (b *backend) pathSign(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.pathIssueSignCert(req, data, true)
}
func (b *backend) pathIssueSignCert(
req *logical.Request, data *framework.FieldData, useCSR bool) (*logical.Response, error) {
roleName := data.Get("role").(string) roleName := data.Get("role").(string)
// Get the role // Get the role
@@ -74,6 +89,42 @@ func (b *backend) pathIssueSignCert(
return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil
} }
return b.pathIssueSignCert(req, data, role, false, false)
}
func (b *backend) pathSign(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
roleName := data.Get("role").(string)
// Get the role
role, err := b.getRole(req.Storage, roleName)
if err != nil {
return nil, err
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil
}
return b.pathIssueSignCert(req, data, role, true, false)
}
func (b *backend) pathSignVerbatim(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
ttl := b.System().DefaultLeaseTTL()
role := &roleEntry{
TTL: ttl.String(),
AllowLocalhost: true,
AllowAnyName: true,
AllowIPSANs: true,
EnforceHostnames: false,
}
return b.pathIssueSignCert(req, data, role, true, true)
}
func (b *backend) pathIssueSignCert(
req *logical.Request, data *framework.FieldData, role *roleEntry, useCSR, useCSRValues bool) (*logical.Response, error) {
format := getFormat(data) format := getFormat(data)
if format == "" { if format == "" {
return logical.ErrorResponse( return logical.ErrorResponse(
@@ -92,8 +143,9 @@ func (b *backend) pathIssueSignCert(
} }
var parsedBundle *certutil.ParsedCertBundle var parsedBundle *certutil.ParsedCertBundle
var err error
if useCSR { if useCSR {
parsedBundle, err = signCert(b, role, signingBundle, false, false, req, data) parsedBundle, err = signCert(b, role, signingBundle, false, useCSRValues, req, data)
} else { } else {
parsedBundle, err = generateCert(b, role, signingBundle, false, req, data) parsedBundle, err = generateCert(b, role, signingBundle, false, req, data)
} }

View File

@@ -0,0 +1,261 @@
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{
logical.WriteOperation: b.pathCAGenerateRoot,
},
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{
logical.WriteOperation: b.pathCASignIntermediate,
},
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
}
var resp *logical.Response
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)
}
resp = &logical.Response{
Data: map[string]interface{}{
"serial_number": cb.SerialNumber,
"expiration": int64(parsedBundle.Certificate.NotAfter.Unix()),
},
}
switch format {
case "pem":
resp.Data["certificate"] = cb.Certificate
resp.Data["issuing_ca"] = cb.IssuingCA
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
}
}
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
}
// For ease of later use, also store just the certificate at a known
// location, plus a fresh CRL
entry.Key = "ca"
entry.Value = parsedBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
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,
},
map[string]interface{}{
"serial_number": cb.SerialNumber,
})
switch format {
case "pem":
resp.Data["certificate"] = cb.Certificate
resp.Data["issuing_ca"] = cb.IssuingCA
case "der":
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.
`

View File

@@ -286,17 +286,19 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/config/ca/set ### /pki/config/ca
#### POST #### POST
<dl class="api"> <dl class="api">
<dt>Description</dt> <dt>Description</dt>
<dd> <dd>
Allows submitting the CA information via a PEM file containing the CA Allows submitting the CA information for the backend via a PEM file
certificate and its private key, concatenated. If you generated an containing the CA certificate and its private key, concatenated. Not needed
intermediate CA CSR and received a signed certificate, you do not need to if you are generating a self-signed root certificate, and not used if you
include the private key in the PEM file. <br /><br />The information can have a signed intermediate CA certificate with a generated key (use the
be provided from a file via a `curl` command similar to the following:<br/> `/pki/intermediate/set-signed` endpoint for that). <br /><br />The
information can be provided from a file via a `curl` command similar to the
following:<br/>
```text ```text
$ curl \ $ curl \
@@ -320,7 +322,7 @@ subpath for interactive help output.
<dd>POST</dd> <dd>POST</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/config/ca/set`</dd> <dd>`/pki/config/ca`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -339,66 +341,26 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/config/ca/generate/intermediate
#### POST ### /pki/config/crl
#### GET
<dl class="api"> <dl class="api">
<dt>Description</dt> <dt>Description</dt>
<dd> <dd>
Generates a new private key and a CSR for signing. If using Vault as a Allows getting the duration for which the generated CRL should be marked
root, and for many other CAs, the various parameters on the final valid.
certificate are set at signing time and may or may not honor the parameters
set here. If the path ends with `exported`, the private key will be
returned in the response; if it is `internal` the private key will not be
returned and *cannot be retrieved later*. <br /><br />This is mostly meant
as a helper function, and not all possible parameters that can be set in a
CSR are supported.
</dd> </dd>
<dt>Method</dt> <dt>Method</dt>
<dd>POST</dd> <dd>GET</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/config/ca/generate/intermediate/[exported|internal]`</dd> <dd>`/pki/config/crl`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
<ul> None
<li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">key_type</span>
<span class="param-flags">optional</span>
Desired key type; must be `rsa` or `ec`. Defaults to `rsa`.
</li>
<li>
<span class="param">key_bits</span>
<span class="param-flags">optional</span>
The number of bits to use. Defaults to `2048`. Must be changed to a
valid value if the `key_type` is `ec`.
</li>
</ul>
</dd> </dd>
<dt>Returns</dt> <dt>Returns</dt>
@@ -408,9 +370,9 @@ subpath for interactive help output.
{ {
"lease_id": "", "lease_id": "",
"renewable": false, "renewable": false,
"lease_duration": 21600, "lease_duration": 0,
"data": { "data": {
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE REQUEST-----\n", "expiry": "72h"
}, },
"auth": null "auth": null
} }
@@ -419,192 +381,36 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/config/ca/generate/root
#### POST #### POST
<dl class="api"> <dl class="api">
<dt>Description</dt> <dt>Description</dt>
<dd> <dd>
Generates a new self-signed CA certificate and private key. If the path Allows setting the duration for which the generated CRL should be marked
ends with `exported`, the private key will be returned in the response; if valid.
it is `internal` the private key will not be returned and *cannot be
retrieved later*. Distribution points use the values set via `config/urls`.
</dd> </dd>
<dt>Method</dt> <dt>Method</dt>
<dd>POST</dd> <dd>POST</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/config/ca/generate/root/[exported|internal]`</dd> <dd>`/pki/config/crl`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">ttl</span>
<span class="param-flags">optional</span>
Requested Time To Live (after which the certificate will be expired).
This cannot be larger than the mount max (or, if not set, the system
max).
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">key_type</span>
<span class="param-flags">optional</span>
Desired key type; must be `rsa` or `ec`. Defaults to `rsa`.
</li>
<li>
<span class="param">key_bits</span>
<span class="param-flags">optional</span>
The number of bits to use. Defaults to `2048`. Must be changed to a
valid value if the `key_type` is `ec`.
</li>
<li>
<span class="param">max_path_length</span>
<span class="param-flags">optional</span>
If set, the maximum path length to encode in the generated certificate.
Defaults to `-1`, which means no limit. A limit of `0` means a literal
path length of zero.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "",
"renewable": false,
"lease_duration": 21600,
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"serial": "39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
},
"auth": null
}
```
</dd>
</dl>
### /pki/config/ca/sign
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Uses the configured CA certificate to issue a certificate with appropriate
values for acting as an intermediate CA. Distribution points use the values
set via `config/urls`. Values set in the CSR are ignored unless
`use_csr_values` is set to true, in which case the values from the CSR are
used verbatim.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/config/ca/sign`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
<ul> <ul>
<li> <li>
<li> <li>
<span class="param">csr</span> <span class="param">expiry</span>
<span class="param-flags">required</span> <span class="param-flags">required</span>
The PEM-encoded CSR. The time until expiration. Defaults to `72h`.
</li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">ttl</span>
<span class="param-flags">optional</span>
Requested Time To Live (after which the certificate will be expired).
This cannot be larger than the mount max (or, if not set, the system
max).
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">max_path_length</span>
<span class="param-flags">optional</span>
If set, the maximum path length to encode in the generated certificate.
Defaults to `-1`, which means no limit. A limit of `0` means a literal
path length of zero.
</li>
<li>
<span class="param">use_csr_values</span>
<span class="param-flags">optional</span>
If set to `true`, then: 1) Subject information, including names and
alternate names, will be preserved from the CSR rather than using the
values provided in the other parameters to this path; 2) Any key usages
(for instance, non-repudiation) requested in the CSR will be added to
the basic set of key usages used for CA certs signed by this path.
</li> </li>
</ul> </ul>
</dd> </dd>
<dt>Returns</dt> <dt>Returns</dt>
<dd> <dd>
A `204` response code.
```javascript
{
"lease_id": "pki/config/ca/sign/bc23e3c6-8dcd-48c6-f3af-dd2db7f815c2",
"renewable": false,
"lease_duration": 21600,
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV\n...\nG/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==\n-----END CERTIFICATE-----\n",
"serial": "39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
},
"auth": null
}
```
</dd> </dd>
</dl> </dl>
@@ -693,78 +499,6 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/config/crl
#### GET
<dl class="api">
<dt>Description</dt>
<dd>
Allows getting the duration for which the generated CRL should be marked
valid.
</dd>
<dt>Method</dt>
<dd>GET</dd>
<dt>URL</dt>
<dd>`/pki/config/crl`</dd>
<dt>Parameters</dt>
<dd>
None
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"expiry": "72h"
},
"auth": null
}
```
</dd>
</dl>
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Allows setting the duration for which the generated CRL should be marked
valid.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/config/crl`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<li>
<span class="param">expiry</span>
<span class="param-flags">required</span>
The time until expiration. Defaults to `72h`.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
A `204` response code.
</dd>
</dl>
### /pki/crl(/pem) ### /pki/crl(/pem)
#### GET #### GET
@@ -837,6 +571,118 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/intermediate/generate
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Generates a new private key and a CSR for signing. If using Vault as a
root, and for many other CAs, the various parameters on the final
certificate are set at signing time and may or may not honor the parameters
set here. If the path ends with `exported`, the private key will be
returned in the response; if it is `internal` the private key will not be
returned and *cannot be retrieved later*. <br /><br />This is mostly meant
as a helper function, and not all possible parameters that can be set in a
CSR are supported.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/intermediate/generate/[exported|internal]`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">key_type</span>
<span class="param-flags">optional</span>
Desired key type; must be `rsa` or `ec`. Defaults to `rsa`.
</li>
<li>
<span class="param">key_bits</span>
<span class="param-flags">optional</span>
The number of bits to use. Defaults to `2048`. Must be changed to a
valid value if the `key_type` is `ec`.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "",
"renewable": false,
"lease_duration": 21600,
"data": {
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE REQUEST-----\n",
},
"auth": null
}
```
</dd>
</dl>
### /pki/intermediate/set-signed
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Allows submitting the signed CA certificate corresponding to a private key generated via `/pki/intermediate/generate`. The certificate should be submitted in PEM format; see the documentation for `/pki/config/ca` for some hints on submitting.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/intermediate/set-signed`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">certificate</span>
<span class="param-flags">required</span>
The certificate in PEM format.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
A `204` response code.
</dd>
</dl>
### /pki/issue/ ### /pki/issue/
#### POST #### POST
@@ -854,7 +700,7 @@ subpath for interactive help output.
<dd>POST</dd> <dd>POST</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/issue/<name>`</dd> <dd>`/pki/issue/<role name>`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -977,7 +823,7 @@ subpath for interactive help output.
<dd>POST</dd> <dd>POST</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/roles/<name>`</dd> <dd>`/pki/roles/<role name>`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -1117,7 +963,7 @@ subpath for interactive help output.
<dd>GET</dd> <dd>GET</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/roles/<name>`</dd> <dd>`/pki/roles/<role name>`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -1164,7 +1010,7 @@ subpath for interactive help output.
<dd>DELETE</dd> <dd>DELETE</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/roles/<name>`</dd> <dd>`/pki/roles/<role name>`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -1177,6 +1023,200 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/root/generate
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Generates a new self-signed CA certificate and private key. If the path
ends with `exported`, the private key will be returned in the response; if
it is `internal` the private key will not be returned and *cannot be
retrieved later*. Distribution points use the values set via `config/urls`.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/root/generate/[exported|internal]`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">ttl</span>
<span class="param-flags">optional</span>
Requested Time To Live (after which the certificate will be expired).
This cannot be larger than the mount max (or, if not set, the system
max).
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">key_type</span>
<span class="param-flags">optional</span>
Desired key type; must be `rsa` or `ec`. Defaults to `rsa`.
</li>
<li>
<span class="param">key_bits</span>
<span class="param-flags">optional</span>
The number of bits to use. Defaults to `2048`. Must be changed to a
valid value if the `key_type` is `ec`.
</li>
<li>
<span class="param">max_path_length</span>
<span class="param-flags">optional</span>
If set, the maximum path length to encode in the generated certificate.
Defaults to `-1`, which means no limit. unless the signing certificate
has a maximum path length set, in which case the path length is set to
one less than that of the signing certificate. A limit of `0` means a
literal path length of zero.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "",
"renewable": false,
"lease_duration": 21600,
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"serial": "39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
},
"auth": null
}
```
</dd>
</dl>
### /pki/root/sign-intermediate
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Uses the configured CA certificate to issue a certificate with appropriate
values for acting as an intermediate CA. Distribution points use the values
set via `config/urls`. Values set in the CSR are ignored unless
`use_csr_values` is set to true, in which case the values from the CSR are
used verbatim.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/root/sign-intermediate`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<li>
<span class="param">csr</span>
<span class="param-flags">required</span>
The PEM-encoded CSR.
</li>
<span class="param">common_name</span>
<span class="param-flags">required</span>
The requested CN for the certificate.
</li>
<li>
<span class="param">alt_names</span>
<span class="param-flags">optional</span>
Requested Subject Alternative Names, in a comma-delimited list. These
can be host names or email addresses; they will be parsed into their
respective fields.
</li>
<li>
<span class="param">ip_sans</span>
<span class="param-flags">optional</span>
Requested IP Subject Alternative Names, in a comma-delimited list.
</li>
<li>
<span class="param">ttl</span>
<span class="param-flags">optional</span>
Requested Time To Live (after which the certificate will be expired).
This cannot be larger than the mount max (or, if not set, the system
max).
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
<li>
<span class="param">max_path_length</span>
<span class="param-flags">optional</span>
If set, the maximum path length to encode in the generated certificate.
Defaults to `-1`, which means no limit. unless the signing certificate
has a maximum path length set, in which case the path length is set to
one less than that of the signing certificate. A limit of `0` means a
literal path length of zero.
</li>
<li>
<span class="param">use_csr_values</span>
<span class="param-flags">optional</span>
If set to `true`, then: 1) Subject information, including names and
alternate names, will be preserved from the CSR rather than using the
values provided in the other parameters to this path; 2) Any key usages
(for instance, non-repudiation) requested in the CSR will be added to
the basic set of key usages used for CA certs signed by this path; 3)
Extensions requested in the CSR will be copied into the issued
certificate.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "pki/root/sign-intermediate/bc23e3c6-8dcd-48c6-f3af-dd2db7f815c2",
"renewable": false,
"lease_duration": 21600,
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV\n...\nG/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==\n-----END CERTIFICATE-----\n",
"serial": "39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
},
"auth": null
}
```
</dd>
</dl>
### /pki/sign/ ### /pki/sign/
#### POST #### POST
@@ -1193,7 +1233,7 @@ subpath for interactive help output.
<dd>POST</dd> <dd>POST</dd>
<dt>URL</dt> <dt>URL</dt>
<dd>`/pki/sign/<name>`</dd> <dd>`/pki/sign/<role name>`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
@@ -1230,6 +1270,7 @@ subpath for interactive help output.
value. If not provided, the role's `ttl` value will be used. Note that value. If not provided, the role's `ttl` value will be used. Note that
the role values default to system values if not explicitly set. the role values default to system values if not explicitly set.
</li> </li>
<li>
<span class="param">format</span> <span class="param">format</span>
<span class="param-flags">optional</span> <span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
@@ -1257,3 +1298,67 @@ subpath for interactive help output.
</dd> </dd>
</dl> </dl>
### /pki/sign-verbatim
#### POST
<dl class="api">
<dt>Description</dt>
<dd>
Signs a new certificate based upon the provided CSR. Values are taken
verbatim from the CSR; the _only_ restriction is that this endpoint will
refuse to issue an intermediate CA certificate (see the
`/pki/root/sign-intermediate` endpoint for that functionality.) _This is a
potentially dangerous endpoint and only highly trusted users should
have access._
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/pki/sign-verbatim`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">csr</span>
<span class="param-flags">required</span>
The PEM-encoded CSR.
</li>
<li>
<span class="param">ttl</span>
<span class="param-flags">optional</span>
Requested Time To Live. Cannot be greater than the mount's `max_ttl`
value. If not provided, the mount's `ttl` value will be used, which
defaults to system values if not explicitly set.
</li>
<li>
<span class="param">format</span>
<span class="param-flags">optional</span>
Format for returned data. Can be `pem` or `der`; defaults to `pem`. If
`der`, the output is base64 encoded.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"lease_id": "pki/sign-verbatim/7ad6cfa5-f04f-c62a-d477-f33210475d05",
"renewable": false,
"lease_duration": 21600,
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE-----\n",
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV\n...\nG/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==\n-----END CERTIFICATE-----\n",
"serial": "39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
},
"auth": null
}
```
</dd>
</dl>