mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 12:07: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{
|
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),
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
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/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 &&
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
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
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user