mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
Split root and intermediate functionality into their own sections in the API. Update documentation. Add sign-verbatim endpoint.
This commit is contained in:
@@ -32,13 +32,14 @@ func Backend() *framework.Backend {
|
||||
|
||||
Paths: []*framework.Path{
|
||||
pathRoles(&b),
|
||||
pathGenerateRootCA(&b),
|
||||
pathGenerateIntermediateCA(&b),
|
||||
pathSignIntermediateCA(&b),
|
||||
pathSetCA(&b),
|
||||
pathGenerateRoot(&b),
|
||||
pathGenerateIntermediate(&b),
|
||||
pathSetSignedIntermediate(&b),
|
||||
pathSignIntermediate(&b),
|
||||
pathConfigCA(&b),
|
||||
pathConfigCRL(&b),
|
||||
pathConfigURLs(&b),
|
||||
pathSignVerbatim(&b),
|
||||
pathSign(&b),
|
||||
pathIssue(&b),
|
||||
pathRotateCRL(&b),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
expected := urlEntries{
|
||||
IssuingCertificates: []string{
|
||||
"http://example.com/ca1",
|
||||
@@ -342,7 +341,7 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
ret := []logicaltest.TestStep{
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/root/exported",
|
||||
Path: "root/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Root Cert",
|
||||
"ttl": "180h",
|
||||
@@ -382,7 +381,7 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/sign",
|
||||
Path: "root/sign-intermediate",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Intermediate Cert",
|
||||
"csr": string(csrPem),
|
||||
@@ -452,7 +451,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
ret := []logicaltest.TestStep{
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/root/exported",
|
||||
Path: "root/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Root Cert",
|
||||
"ttl": "180h",
|
||||
@@ -462,7 +461,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/sign",
|
||||
Path: "root/sign-intermediate",
|
||||
Data: map[string]interface{}{
|
||||
"use_csr_values": true,
|
||||
"csr": string(csrPem),
|
||||
@@ -473,7 +472,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/root/exported",
|
||||
Path: "root/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Root Cert",
|
||||
"ttl": "180h",
|
||||
@@ -483,7 +482,7 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/sign",
|
||||
Path: "root/sign-intermediate",
|
||||
Data: map[string]interface{}{
|
||||
"use_csr_values": true,
|
||||
"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
|
||||
// as when uploading a CA signed as the result of a generated CSR
|
||||
// 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
|
||||
// Ensure that both parts of the PEM bundle are required
|
||||
// Here, just the cert
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/set",
|
||||
Data: map[string]interface{}{
|
||||
"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
|
||||
@@ -686,7 +684,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
// Test a bunch of generation stuff
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/root/exported",
|
||||
Path: "root/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Root Cert",
|
||||
"ttl": "180h",
|
||||
@@ -701,7 +699,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/intermediate/exported",
|
||||
Path: "intermediate/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"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{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/set",
|
||||
@@ -728,14 +727,38 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/sign",
|
||||
Path: "root/sign-intermediate",
|
||||
Data: reqdata,
|
||||
Check: func(resp *logical.Response) error {
|
||||
intdata["intermediatecert"] = resp.Data["certificate"].(string)
|
||||
delete(reqdata, "csr")
|
||||
delete(reqdata, "common_name")
|
||||
delete(reqdata, "ttl")
|
||||
intdata["intermediatecert"] = resp.Data["certificate"].(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
|
||||
},
|
||||
},
|
||||
@@ -772,7 +795,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
// Do it all again, with EC keys and DER format
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/root/exported",
|
||||
Path: "root/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"common_name": "Root Cert",
|
||||
"ttl": "180h",
|
||||
@@ -800,7 +823,7 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/generate/intermediate/exported",
|
||||
Path: "intermediate/generate/exported",
|
||||
Data: map[string]interface{}{
|
||||
"format": "der",
|
||||
"key_type": "ec",
|
||||
@@ -840,14 +863,38 @@ func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, int
|
||||
|
||||
logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config/ca/sign",
|
||||
Path: "root/sign-intermediate",
|
||||
Data: reqdata,
|
||||
Check: func(resp *logical.Response) error {
|
||||
intdata["intermediatecert"] = resp.Data["certificate"].(string)
|
||||
delete(reqdata, "csr")
|
||||
delete(reqdata, "common_name")
|
||||
delete(reqdata, "ttl")
|
||||
intdata["intermediatecert"] = resp.Data["certificate"].(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
|
||||
},
|
||||
},
|
||||
|
||||
42
builtin/logical/pki/ca_util.go
Normal file
42
builtin/logical/pki/ca_util.go
Normal 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
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -54,7 +55,19 @@ type caInfoBundle struct {
|
||||
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 {
|
||||
format := data.Get("format").(string)
|
||||
@@ -370,10 +383,8 @@ func signCert(b *backend,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isCA {
|
||||
creationBundle.IsCA = isCA
|
||||
creationBundle.UseCSRValues = useCSRValues
|
||||
}
|
||||
|
||||
parsedBundle, err := signCertificate(creationBundle, csr)
|
||||
if err != nil {
|
||||
@@ -596,7 +607,6 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(creationInfo.TTL),
|
||||
KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
SubjectKeyId: subjKeyID,
|
||||
DNSNames: creationInfo.DNSNames,
|
||||
@@ -649,6 +659,7 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle
|
||||
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
||||
}
|
||||
|
||||
certTemplate.BasicConstraintsValid = true
|
||||
certTemplate.IsCA = true
|
||||
certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign)
|
||||
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning)
|
||||
@@ -762,7 +773,6 @@ func signCertificate(creationInfo *creationBundle,
|
||||
Subject: subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(creationInfo.TTL),
|
||||
BasicConstraintsValid: true,
|
||||
SubjectKeyId: subjKeyID[:],
|
||||
}
|
||||
|
||||
@@ -780,9 +790,13 @@ func signCertificate(creationInfo *creationBundle,
|
||||
certTemplate.EmailAddresses = csr.EmailAddresses
|
||||
certTemplate.IPAddresses = csr.IPAddresses
|
||||
|
||||
if creationInfo.IsCA {
|
||||
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 {
|
||||
certTemplate.DNSNames = creationInfo.DNSNames
|
||||
certTemplate.EmailAddresses = creationInfo.EmailAddresses
|
||||
@@ -817,6 +831,7 @@ func signCertificate(creationInfo *creationBundle,
|
||||
certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers
|
||||
|
||||
if creationInfo.IsCA {
|
||||
certTemplate.BasicConstraintsValid = true
|
||||
certTemplate.IsCA = true
|
||||
|
||||
if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 &&
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/certutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
@@ -13,25 +11,6 @@ import (
|
||||
func pathConfigCA(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
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{
|
||||
"pem_bundle": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
@@ -43,363 +22,15 @@ endpoint, just the signed certificate.`,
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.WriteOperation: b.pathCASetWrite,
|
||||
logical.WriteOperation: b.pathCAWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathConfigCASetHelpSyn,
|
||||
HelpDescription: pathConfigCASetHelpDesc,
|
||||
HelpSynopsis: pathConfigCAHelpSyn,
|
||||
HelpDescription: pathConfigCAHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func pathGenerateRootCA(b *backend) *framework.Path {
|
||||
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(
|
||||
func (b *backend) pathCAWrite(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
pemBundle := data.Get("pem_bundle").(string)
|
||||
|
||||
@@ -413,67 +44,42 @@ func (b *backend) pathCASetWrite(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case of a self-signed certificate
|
||||
if parsedBundle.Certificate == nil && parsedBundle.IssuingCA != nil {
|
||||
if parsedBundle.PrivateKey == 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.CertificateBytes = parsedBundle.IssuingCABytes
|
||||
}
|
||||
|
||||
cb := &certutil.CertBundle{}
|
||||
entry, err := req.Storage.Get("config/ca_bundle")
|
||||
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 == nil {
|
||||
return logical.ErrorResponse("no certificate found in the PEM bundle"), nil
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -496,11 +102,11 @@ func (b *backend) pathCASetWrite(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const pathConfigCASetHelpSyn = `
|
||||
const pathConfigCAHelpSyn = `
|
||||
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
|
||||
by this mount. This must be a PEM-format, concatenated unencrypted
|
||||
secret key and certificate.
|
||||
|
||||
231
builtin/logical/pki/path_intermediate.go
Normal file
231
builtin/logical/pki/path_intermediate.go
Normal 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.
|
||||
`
|
||||
@@ -51,18 +51,33 @@ func pathSign(b *backend) *framework.Path {
|
||||
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(
|
||||
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)
|
||||
|
||||
// Get the role
|
||||
@@ -74,6 +89,42 @@ func (b *backend) pathIssueSignCert(
|
||||
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)
|
||||
if format == "" {
|
||||
return logical.ErrorResponse(
|
||||
@@ -92,8 +143,9 @@ func (b *backend) pathIssueSignCert(
|
||||
}
|
||||
|
||||
var parsedBundle *certutil.ParsedCertBundle
|
||||
var err error
|
||||
if useCSR {
|
||||
parsedBundle, err = signCert(b, role, signingBundle, false, false, req, data)
|
||||
parsedBundle, err = signCert(b, role, signingBundle, false, useCSRValues, req, data)
|
||||
} else {
|
||||
parsedBundle, err = generateCert(b, role, signingBundle, false, req, data)
|
||||
}
|
||||
|
||||
261
builtin/logical/pki/path_root.go
Normal file
261
builtin/logical/pki/path_root.go
Normal 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.
|
||||
`
|
||||
@@ -286,17 +286,19 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### /pki/config/ca/set
|
||||
### /pki/config/ca
|
||||
#### POST
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Allows submitting the CA information via a PEM file containing the CA
|
||||
certificate and its private key, concatenated. If you generated an
|
||||
intermediate CA CSR and received a signed certificate, you do not need to
|
||||
include the private key in the PEM file. <br /><br />The information can
|
||||
be provided from a file via a `curl` command similar to the following:<br/>
|
||||
Allows submitting the CA information for the backend via a PEM file
|
||||
containing the CA certificate and its private key, concatenated. Not needed
|
||||
if you are generating a self-signed root certificate, and not used if you
|
||||
have a signed intermediate CA certificate with a generated key (use the
|
||||
`/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
|
||||
$ curl \
|
||||
@@ -320,7 +322,7 @@ subpath for interactive help output.
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/config/ca/set`</dd>
|
||||
<dd>`/pki/config/ca`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -339,66 +341,26 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### /pki/config/ca/generate/intermediate
|
||||
#### POST
|
||||
|
||||
### /pki/config/crl
|
||||
#### GET
|
||||
|
||||
<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.
|
||||
Allows getting the duration for which the generated CRL should be marked
|
||||
valid.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/config/ca/generate/intermediate/[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">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>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
@@ -408,9 +370,9 @@ subpath for interactive help output.
|
||||
{
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": 21600,
|
||||
"lease_duration": 0,
|
||||
"data": {
|
||||
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDzDCCAragAwIBAgIUOd0ukLcjH43TfTHFG9qE0FtlMVgwCwYJKoZIhvcNAQEL\n...\numkqeYeO30g1uYvDuWLXVA==\n-----END CERTIFICATE REQUEST-----\n",
|
||||
"expiry": "72h"
|
||||
},
|
||||
"auth": null
|
||||
}
|
||||
@@ -419,192 +381,36 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
### /pki/config/ca/generate/root
|
||||
#### 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`.
|
||||
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/ca/generate/root/[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. 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>
|
||||
<dd>`/pki/config/crl`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<li>
|
||||
<span class="param">csr</span>
|
||||
<span class="param">expiry</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. 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.
|
||||
The time until expiration. Defaults to `72h`.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
A `204` response code.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
@@ -693,78 +499,6 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</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)
|
||||
#### GET
|
||||
|
||||
@@ -837,6 +571,118 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</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/
|
||||
#### POST
|
||||
|
||||
@@ -854,7 +700,7 @@ subpath for interactive help output.
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/issue/<name>`</dd>
|
||||
<dd>`/pki/issue/<role name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -977,7 +823,7 @@ subpath for interactive help output.
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/roles/<name>`</dd>
|
||||
<dd>`/pki/roles/<role name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -1117,7 +963,7 @@ subpath for interactive help output.
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/roles/<name>`</dd>
|
||||
<dd>`/pki/roles/<role name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -1164,7 +1010,7 @@ subpath for interactive help output.
|
||||
<dd>DELETE</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/roles/<name>`</dd>
|
||||
<dd>`/pki/roles/<role name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -1177,6 +1023,200 @@ subpath for interactive help output.
|
||||
</dd>
|
||||
</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/
|
||||
#### POST
|
||||
|
||||
@@ -1193,7 +1233,7 @@ subpath for interactive help output.
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/pki/sign/<name>`</dd>
|
||||
<dd>`/pki/sign/<role name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
@@ -1230,6 +1270,7 @@ subpath for interactive help output.
|
||||
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.
|
||||
</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
|
||||
@@ -1257,3 +1298,67 @@ subpath for interactive help output.
|
||||
|
||||
</dd>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user