mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 03:58:01 +00:00
A lot of refactoring: move PEM bundle parsing into helper/certutil, so that it is usable by other backends that want to use it to get the necessary data for TLS auth.
Also, enhance the raw cert bundle => parsed cert bundle to make it more useful and perform more validation checks. More refactoring could be done within the PKI backend itself, but that can wait. Commit contents (C)2015 Akamai Technologies, Inc. <opensource@akamai.com>
This commit is contained in:
@@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/helper/certutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/certutil"
|
||||
logicaltest "github.com/hashicorp/vault/logical/testing"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/certutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/certutil"
|
||||
)
|
||||
|
||||
type certUsage int
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
)
|
||||
|
||||
type certCreationBundle struct {
|
||||
RawSigningBundle *certutil.RawCertBundle
|
||||
SigningBundle *certutil.ParsedCertBundle
|
||||
CACert *x509.Certificate
|
||||
CommonNames []string
|
||||
IPSANs []net.IP
|
||||
@@ -55,7 +55,7 @@ func getCertBundle(s logical.Storage, path string) (*certutil.CertBundle, error)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func fetchCAInfo(req *logical.Request) (*certutil.RawCertBundle, *x509.Certificate, error, error) {
|
||||
func fetchCAInfo(req *logical.Request) (*certutil.ParsedCertBundle, *x509.Certificate, error, error) {
|
||||
bundle, err := getCertBundle(req.Storage, "config/ca_bundle")
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("Unable to fetch local CA certificate/key: %s", err)
|
||||
@@ -64,12 +64,12 @@ func fetchCAInfo(req *logical.Request) (*certutil.RawCertBundle, *x509.Certifica
|
||||
return nil, nil, fmt.Errorf("Backend must be configured with a CA certificate/key"), nil
|
||||
}
|
||||
|
||||
rawBundle, err := bundle.ToRawCertBundle()
|
||||
parsedBundle, err := bundle.ToParsedCertBundle()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
certificates, err := x509.ParseCertificates(rawBundle.CertificateBytes)
|
||||
certificates, err := x509.ParseCertificates(parsedBundle.CertificateBytes)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, nil, nil, err
|
||||
@@ -77,7 +77,7 @@ func fetchCAInfo(req *logical.Request) (*certutil.RawCertBundle, *x509.Certifica
|
||||
return nil, nil, nil, fmt.Errorf("Length of CA certificate bundle is wrong")
|
||||
}
|
||||
|
||||
return rawBundle, certificates[0], nil, nil
|
||||
return parsedBundle, certificates[0], nil, nil
|
||||
}
|
||||
|
||||
func fetchCertBySerial(req *logical.Request, prefix, serial string) (certEntry *logical.StorageEntry, userError, internalError error) {
|
||||
@@ -168,26 +168,28 @@ func validateCommonNames(req *logical.Request, commonNames []string, role *roleE
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func createCertificate(creationInfo *certCreationBundle) (rawBundle *certutil.RawCertBundle, userErr, intErr error) {
|
||||
func createCertificate(creationInfo *certCreationBundle) (parsedBundle *certutil.ParsedCertBundle, userErr, intErr error) {
|
||||
var clientPrivKey crypto.Signer
|
||||
var err error
|
||||
rawBundle = &certutil.RawCertBundle{}
|
||||
parsedBundle = &certutil.ParsedCertBundle{}
|
||||
|
||||
rawBundle.SerialNumber, err = rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
||||
var serialNumber *big.Int
|
||||
serialNumber, err = rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error getting random serial number")
|
||||
}
|
||||
|
||||
switch creationInfo.KeyType {
|
||||
case "rsa":
|
||||
rawBundle.PrivateKeyType = certutil.RSAPrivateKeyType
|
||||
parsedBundle.PrivateKeyType = certutil.RSAPrivateKey
|
||||
clientPrivKey, err = rsa.GenerateKey(rand.Reader, creationInfo.KeyBits)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error generating RSA private key")
|
||||
}
|
||||
rawBundle.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(clientPrivKey.(*rsa.PrivateKey))
|
||||
parsedBundle.PrivateKey = clientPrivKey
|
||||
parsedBundle.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(clientPrivKey.(*rsa.PrivateKey))
|
||||
case "ec":
|
||||
rawBundle.PrivateKeyType = certutil.ECPrivateKeyType
|
||||
parsedBundle.PrivateKeyType = certutil.ECPrivateKey
|
||||
var curve elliptic.Curve
|
||||
switch creationInfo.KeyBits {
|
||||
case 224:
|
||||
@@ -205,7 +207,8 @@ func createCertificate(creationInfo *certCreationBundle) (rawBundle *certutil.Ra
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error generating EC private key")
|
||||
}
|
||||
rawBundle.PrivateKeyBytes, err = x509.MarshalECPrivateKey(clientPrivKey.(*ecdsa.PrivateKey))
|
||||
parsedBundle.PrivateKey = clientPrivKey
|
||||
parsedBundle.PrivateKeyBytes, err = x509.MarshalECPrivateKey(clientPrivKey.(*ecdsa.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error marshalling EC private key")
|
||||
}
|
||||
@@ -213,7 +216,7 @@ func createCertificate(creationInfo *certCreationBundle) (rawBundle *certutil.Ra
|
||||
return nil, fmt.Errorf("Unknown key type: %s", creationInfo.KeyType), nil
|
||||
}
|
||||
|
||||
subjKeyID, err := rawBundle.GetSubjKeyID()
|
||||
subjKeyID, err := certutil.GetSubjKeyID(parsedBundle.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error getting subject key ID: %s", err)
|
||||
}
|
||||
@@ -226,13 +229,13 @@ func createCertificate(creationInfo *certCreationBundle) (rawBundle *certutil.Ra
|
||||
Province: creationInfo.CACert.Subject.Province,
|
||||
StreetAddress: creationInfo.CACert.Subject.StreetAddress,
|
||||
PostalCode: creationInfo.CACert.Subject.PostalCode,
|
||||
SerialNumber: rawBundle.SerialNumber.String(),
|
||||
SerialNumber: serialNumber.String(),
|
||||
CommonName: creationInfo.CommonNames[0],
|
||||
}
|
||||
|
||||
certTemplate := &x509.Certificate{
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
SerialNumber: rawBundle.SerialNumber,
|
||||
SerialNumber: serialNumber,
|
||||
Subject: subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(creationInfo.Lease),
|
||||
@@ -257,18 +260,19 @@ func createCertificate(creationInfo *certCreationBundle) (rawBundle *certutil.Ra
|
||||
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning)
|
||||
}
|
||||
|
||||
signingPrivKey, err := creationInfo.RawSigningBundle.GetSigner()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Unable to get signing private key: %s", err)
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, certTemplate, creationInfo.CACert, clientPrivKey.Public(), signingPrivKey)
|
||||
cert, err := x509.CreateCertificate(rand.Reader, certTemplate, creationInfo.CACert, clientPrivKey.Public(), creationInfo.SigningBundle.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Unable to create certificate: %s", err)
|
||||
}
|
||||
|
||||
rawBundle.CertificateBytes = cert
|
||||
rawBundle.IssuingCABytes = creationInfo.RawSigningBundle.CertificateBytes
|
||||
parsedBundle.CertificateBytes = cert
|
||||
parsedBundle.Certificate, err = x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Unable to parse created certificate: %s", err)
|
||||
}
|
||||
|
||||
parsedBundle.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes
|
||||
parsedBundle.IssuingCA = creationInfo.SigningBundle.Certificate
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func buildCRL(req *logical.Request) (error, error) {
|
||||
})
|
||||
}
|
||||
|
||||
rawSigningBundle, caCert, userErr, intErr := fetchCAInfo(req)
|
||||
signingBundle, caCert, userErr, intErr := fetchCAInfo(req)
|
||||
switch {
|
||||
case userErr != nil:
|
||||
return fmt.Errorf("Could not fetch the CA certificate: %s", userErr), nil
|
||||
@@ -161,13 +161,8 @@ func buildCRL(req *logical.Request) (error, error) {
|
||||
return nil, fmt.Errorf("Error fetching CA certificate: %s", intErr)
|
||||
}
|
||||
|
||||
signingPrivKey, err := rawSigningBundle.GetSigner()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to get signing private key: %s", err)
|
||||
}
|
||||
|
||||
// TODO: Make expiry configurable
|
||||
crlBytes, err := caCert.CreateCRL(rand.Reader, signingPrivKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime))
|
||||
crlBytes, err := caCert.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating new CRL: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/helper/certutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/certutil"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
@@ -33,71 +31,33 @@ func (b *backend) pathCAWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
pemBundle := d.Get("pem_bundle").(string)
|
||||
|
||||
if len(pemBundle) == 0 {
|
||||
return logical.ErrorResponse("Empty PEM bundle"), nil
|
||||
parsedBundle, err := certutil.ParsePEMBundle(pemBundle)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case certutil.InternalError:
|
||||
return nil, err
|
||||
default:
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
}
|
||||
|
||||
pemBytes := []byte(pemBundle)
|
||||
var pemBlock *pem.Block
|
||||
rawBundle := &certutil.RawCertBundle{}
|
||||
|
||||
for {
|
||||
pemBlock, pemBytes = pem.Decode(pemBytes)
|
||||
if pemBlock == nil {
|
||||
return logical.ErrorResponse("No PEM data found"), nil
|
||||
}
|
||||
|
||||
if _, err := x509.ParseECPrivateKey(pemBlock.Bytes); err == nil {
|
||||
if rawBundle.PrivateKeyType != certutil.UnknownPrivateKeyType {
|
||||
return logical.ErrorResponse("More than one private key given; provide only one private key in the bundle"), nil
|
||||
}
|
||||
rawBundle.PrivateKeyType = certutil.ECPrivateKeyType
|
||||
rawBundle.PrivateKeyBytes = pemBlock.Bytes
|
||||
// TODO?: CRLs can only be generated with RSA keys right now, in the
|
||||
// Go standard library. The plubming is here to support non-RSA keys
|
||||
// if the library gets support
|
||||
return logical.ErrorResponse("Only RSA keys are supported at this time due to limitations in the Go standard library"), nil
|
||||
} else if _, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err == nil {
|
||||
|
||||
if rawBundle.PrivateKeyType != certutil.UnknownPrivateKeyType {
|
||||
return logical.ErrorResponse("More than one private key given; provide only one private key in the bundle"), nil
|
||||
if parsedBundle.PrivateKeyType != certutil.RSAPrivateKey {
|
||||
return logical.ErrorResponse("Currently, only RSA keys are supported for the CA certificate"), nil
|
||||
}
|
||||
rawBundle.PrivateKeyType = certutil.RSAPrivateKeyType
|
||||
rawBundle.PrivateKeyBytes = pemBlock.Bytes
|
||||
} else if certificates, err := x509.ParseCertificates(pemBlock.Bytes); err == nil {
|
||||
switch len(certificates) {
|
||||
case 0:
|
||||
return logical.ErrorResponse("No certificates found in the bundle"), nil
|
||||
case 1:
|
||||
cert := certificates[0]
|
||||
if !cert.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
|
||||
}
|
||||
rawBundle.CertificateBytes = pemBlock.Bytes
|
||||
rawBundle.SerialNumber = cert.SerialNumber
|
||||
default:
|
||||
return logical.ErrorResponse("More than one certificate given; provide only one certificate in the bundle"), nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(pemBytes) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case rawBundle.PrivateKeyType == certutil.UnknownPrivateKeyType:
|
||||
return logical.ErrorResponse("Unable to figure out the private key type; must be RSA or EC"), nil
|
||||
case len(rawBundle.PrivateKeyBytes) == 0:
|
||||
return logical.ErrorResponse("Unable to decode the private key from the bundle"), nil
|
||||
case len(rawBundle.CertificateBytes) == 0:
|
||||
return logical.ErrorResponse("Unable to decode the certificate from the bundle"), nil
|
||||
}
|
||||
|
||||
cb, err := rawBundle.ToCertBundle()
|
||||
cb, err := parsedBundle.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
|
||||
@@ -110,7 +70,7 @@ func (b *backend) pathCAWrite(
|
||||
// For ease of later use, also store just the certificate at a known
|
||||
// location, plus a blank CRL
|
||||
entry.Key = "ca"
|
||||
entry.Value = rawBundle.CertificateBytes
|
||||
entry.Value = parsedBundle.CertificateBytes
|
||||
err = req.Storage.Put(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -118,7 +118,7 @@ func (b *backend) pathIssueCert(
|
||||
return nil, fmt.Errorf("Error validating name %s: %s", badName, err)
|
||||
}
|
||||
|
||||
rawSigningBundle, caCert, userErr, intErr := fetchCAInfo(req)
|
||||
signingBundle, caCert, userErr, intErr := fetchCAInfo(req)
|
||||
switch {
|
||||
case userErr != nil:
|
||||
return logical.ErrorResponse(fmt.Sprintf("Could not fetch the CA certificate: %s", userErr)), nil
|
||||
@@ -142,7 +142,7 @@ func (b *backend) pathIssueCert(
|
||||
}
|
||||
|
||||
creationBundle := &certCreationBundle{
|
||||
RawSigningBundle: rawSigningBundle,
|
||||
SigningBundle: signingBundle,
|
||||
CACert: caCert,
|
||||
CommonNames: commonNames,
|
||||
IPSANs: ipSANs,
|
||||
@@ -152,7 +152,7 @@ func (b *backend) pathIssueCert(
|
||||
Usage: usage,
|
||||
}
|
||||
|
||||
rawBundle, userErr, intErr := createCertificate(creationBundle)
|
||||
parsedBundle, userErr, intErr := createCertificate(creationBundle)
|
||||
switch {
|
||||
case userErr != nil:
|
||||
return logical.ErrorResponse(userErr.Error()), nil
|
||||
@@ -160,7 +160,7 @@ func (b *backend) pathIssueCert(
|
||||
return nil, intErr
|
||||
}
|
||||
|
||||
cb, err := rawBundle.ToCertBundle()
|
||||
cb, err := parsedBundle.ToCertBundle()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (b *backend) pathIssueCert(
|
||||
|
||||
err = req.Storage.Put(&logical.StorageEntry{
|
||||
Key: "certs/" + cb.SerialNumber,
|
||||
Value: rawBundle.CertificateBytes,
|
||||
Value: parsedBundle.CertificateBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to store certificate locally")
|
||||
|
||||
167
helper/certutil/helpers.go
Normal file
167
helper/certutil/helpers.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package certutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// GetOctalFormatted returns the byte buffer formatted in octal with
|
||||
// the specified separator between bytes.
|
||||
func GetOctalFormatted(buf []byte, sep string) string {
|
||||
var ret bytes.Buffer
|
||||
for _, cur := range buf {
|
||||
if ret.Len() > 0 {
|
||||
fmt.Fprintf(&ret, sep)
|
||||
}
|
||||
fmt.Fprintf(&ret, "%02x", cur)
|
||||
}
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
// GetSubjKeyID returns the subject key ID, e.g. the SHA1 sum
|
||||
// of the marshaled public key
|
||||
func GetSubjKeyID(privateKey crypto.Signer) ([]byte, error) {
|
||||
if privateKey == nil {
|
||||
return nil, InternalError{"Passed-in private key is nil"}
|
||||
}
|
||||
marshaledKey, err := x509.MarshalPKIXPublicKey(privateKey.Public())
|
||||
if err != nil {
|
||||
return nil, InternalError{fmt.Sprintf("Error marshalling public key: %s", err)}
|
||||
}
|
||||
|
||||
subjKeyID := sha1.Sum(marshaledKey)
|
||||
|
||||
return subjKeyID[:], nil
|
||||
}
|
||||
|
||||
// ParsePKISecret takes an api.Secret returned from the PKI backend)
|
||||
// and returns a ParsedCertBundle.
|
||||
func ParsePKISecret(secret *api.Secret) (*ParsedCertBundle, error) {
|
||||
return ParsePKIMap(secret.Data)
|
||||
}
|
||||
|
||||
// ParsePKIMap takes a map (for instance, the Secret.Data
|
||||
// returned from the PKI backend) and returns a ParsedCertBundle.
|
||||
func ParsePKIMap(data map[string]interface{}) (*ParsedCertBundle, error) {
|
||||
result := &CertBundle{}
|
||||
err := mapstructure.Decode(data, result)
|
||||
if err != nil {
|
||||
return nil, UserError{err.Error()}
|
||||
}
|
||||
|
||||
return result.ToParsedCertBundle()
|
||||
}
|
||||
|
||||
// ParsePKIJSON takes a JSON-encoded string and returns a CertBundle
|
||||
// ParsedCertBundle.
|
||||
//
|
||||
// This can be either the output of an
|
||||
// issue call from the PKI backend or just its data member; or,
|
||||
// JSON not coming from the PKI backend.
|
||||
func ParsePKIJSON(input []byte) (*ParsedCertBundle, error) {
|
||||
result := &CertBundle{}
|
||||
err := json.Unmarshal(input, &result)
|
||||
|
||||
if err == nil {
|
||||
return result.ToParsedCertBundle()
|
||||
}
|
||||
|
||||
var secret api.Secret
|
||||
err = json.Unmarshal(input, &secret)
|
||||
|
||||
if err == nil {
|
||||
return ParsePKIMap(secret.Data)
|
||||
}
|
||||
|
||||
return nil, UserError{"Unable to parse out of either secret data or a secret object"}
|
||||
}
|
||||
|
||||
// ParsePEMBundle takes a string of concatenated PEM-format certificate
|
||||
// and private key values and decodes/parses them, checking validity along
|
||||
// the way. There must be at max two certificates (a certificate and its
|
||||
// issuing certificate) and one private key.
|
||||
func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) {
|
||||
if len(pemBundle) == 0 {
|
||||
return nil, UserError{"Empty PEM bundle"}
|
||||
}
|
||||
|
||||
pemBytes := []byte(pemBundle)
|
||||
var pemBlock *pem.Block
|
||||
parsedBundle := &ParsedCertBundle{}
|
||||
|
||||
for {
|
||||
pemBlock, pemBytes = pem.Decode(pemBytes)
|
||||
if pemBlock == nil {
|
||||
return nil, UserError{"No data found"}
|
||||
}
|
||||
|
||||
if signer, err := x509.ParseECPrivateKey(pemBlock.Bytes); err == nil {
|
||||
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
|
||||
return nil, UserError{"More than one private key given; provide only one private key in the bundle"}
|
||||
}
|
||||
parsedBundle.PrivateKeyType = ECPrivateKey
|
||||
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
|
||||
parsedBundle.PrivateKey = signer
|
||||
|
||||
} else if signer, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err == nil {
|
||||
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
|
||||
return nil, UserError{"More than one private key given; provide only one private key in the bundle"}
|
||||
}
|
||||
parsedBundle.PrivateKeyType = RSAPrivateKey
|
||||
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
|
||||
parsedBundle.PrivateKey = signer
|
||||
|
||||
} else if certificates, err := x509.ParseCertificates(pemBlock.Bytes); err == nil {
|
||||
switch len(certificates) {
|
||||
case 0:
|
||||
return nil, UserError{"PEM block cannot be decoded to a private key or certificate"}
|
||||
|
||||
case 1:
|
||||
if parsedBundle.Certificate != nil {
|
||||
switch {
|
||||
// We just found the issuing CA
|
||||
case bytes.Equal(parsedBundle.Certificate.AuthorityKeyId, certificates[0].SubjectKeyId):
|
||||
parsedBundle.IssuingCABytes = pemBlock.Bytes
|
||||
parsedBundle.IssuingCA = certificates[0]
|
||||
|
||||
// Our saved certificate is actually the issuing CA
|
||||
case bytes.Equal(parsedBundle.Certificate.SubjectKeyId, certificates[0].AuthorityKeyId):
|
||||
parsedBundle.IssuingCA = parsedBundle.Certificate
|
||||
parsedBundle.IssuingCABytes = parsedBundle.CertificateBytes
|
||||
parsedBundle.CertificateBytes = pemBlock.Bytes
|
||||
parsedBundle.Certificate = certificates[0]
|
||||
}
|
||||
} else {
|
||||
parsedBundle.CertificateBytes = pemBlock.Bytes
|
||||
parsedBundle.Certificate = certificates[0]
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, UserError{"Too many certificates given; provide a maximum of two certificates in the bundle"}
|
||||
}
|
||||
}
|
||||
|
||||
if len(pemBytes) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case parsedBundle.PrivateKeyType == UnknownPrivateKey:
|
||||
return nil, UserError{"Unable to figure out the private key type; must be RSA or EC"}
|
||||
case len(parsedBundle.PrivateKeyBytes) == 0:
|
||||
return nil, UserError{"Unable to decode the private key from the bundle"}
|
||||
case len(parsedBundle.CertificateBytes) == 0:
|
||||
return nil, UserError{"Unable to decode the certificate from the bundle"}
|
||||
}
|
||||
|
||||
return parsedBundle, nil
|
||||
}
|
||||
255
helper/certutil/types.go
Normal file
255
helper/certutil/types.go
Normal file
@@ -0,0 +1,255 @@
|
||||
// Package certutil contains helper functions that are mostly used
|
||||
// with the PKI backend but can be generally useful. Functionality
|
||||
// includes helpers for converting a certificate/private key bundle
|
||||
// between DER and PEM, printing certificate serial numbers, and more.
|
||||
//
|
||||
// Functionality specific to the PKI backend includes some types
|
||||
// and helper methods to make requesting certificates from the
|
||||
// backend easy.
|
||||
package certutil
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TLSUsage controls whether the intended usage of a *tls.Config
|
||||
// returned from ParsedCertBundle.GetTLSConfig is for server use,
|
||||
// client use, or both, which affects which values are set
|
||||
type TLSUsage int
|
||||
|
||||
// The type of of the Private Key referenced in CertBundle
|
||||
// and ParsedCertBundle. This uses colloquial names rather than
|
||||
// official names, to eliminate confusion
|
||||
const (
|
||||
UnknownPrivateKey = iota
|
||||
RSAPrivateKey
|
||||
ECPrivateKey
|
||||
|
||||
TLSServer TLSUsage = 1 << iota
|
||||
TLSClient
|
||||
)
|
||||
|
||||
// UserError represents an error generated due to invalid user input
|
||||
type UserError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e UserError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
// InternalError represents an error generated internally,
|
||||
// presumably not due to invalid user input
|
||||
type InternalError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e InternalError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
// CertBundle contains a key type, a PEM-encoded private key,
|
||||
// a PEM-encoded certificate, and a string-encoded serial number,
|
||||
// returned from a successful Issue request
|
||||
type CertBundle struct {
|
||||
PrivateKeyType string `json:"private_key_type" structs:"private_key_type" mapstructure:"private_key_type"`
|
||||
Certificate string `json:"certificate" structs:"certificate" mapstructure:"certificate"`
|
||||
IssuingCA string `json:"issuing_ca" structs:"issuing_ca" mapstructure:"issuing_ca"`
|
||||
PrivateKey string `json:"private_key" structs:"private_key" mapstructure:"private_key"`
|
||||
SerialNumber string `json:"serial_number" structs:"serial_number" mapstructure:"serial_number"`
|
||||
}
|
||||
|
||||
// ParsedCertBundle contains a key type, a DER-encoded private key,
|
||||
// a DER-encoded certificate, and a big.Int serial number
|
||||
type ParsedCertBundle struct {
|
||||
PrivateKeyType int
|
||||
PrivateKeyBytes []byte
|
||||
PrivateKey crypto.Signer
|
||||
IssuingCABytes []byte
|
||||
IssuingCA *x509.Certificate
|
||||
CertificateBytes []byte
|
||||
Certificate *x509.Certificate
|
||||
}
|
||||
|
||||
// ToParsedCertBundle converts a string-based certificate bundle
|
||||
// to a byte-based raw certificate bundle
|
||||
func (c *CertBundle) ToParsedCertBundle() (*ParsedCertBundle, error) {
|
||||
result := &ParsedCertBundle{}
|
||||
var err error
|
||||
var pemBlock *pem.Block
|
||||
|
||||
pemBlock, _ = pem.Decode([]byte(c.PrivateKey))
|
||||
if pemBlock == nil {
|
||||
return nil, UserError{"Error decoding private key from cert bundle"}
|
||||
}
|
||||
result.PrivateKeyBytes = pemBlock.Bytes
|
||||
|
||||
switch c.PrivateKeyType {
|
||||
case "ec":
|
||||
result.PrivateKeyType = ECPrivateKey
|
||||
case "rsa":
|
||||
result.PrivateKeyType = RSAPrivateKey
|
||||
default:
|
||||
// Try to figure it out and correct
|
||||
if _, err := x509.ParseECPrivateKey(pemBlock.Bytes); err == nil {
|
||||
result.PrivateKeyType = ECPrivateKey
|
||||
c.PrivateKeyType = "ec"
|
||||
} else if _, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err == nil {
|
||||
result.PrivateKeyType = RSAPrivateKey
|
||||
c.PrivateKeyType = "rsa"
|
||||
} else {
|
||||
return nil, UserError{fmt.Sprintf("Unknown private key type in bundle: %s", c.PrivateKeyType)}
|
||||
}
|
||||
}
|
||||
|
||||
result.PrivateKey, err = result.getSigner()
|
||||
if err != nil {
|
||||
return nil, UserError{fmt.Sprintf("Error getting signer: %s", err)}
|
||||
}
|
||||
|
||||
pemBlock, _ = pem.Decode([]byte(c.Certificate))
|
||||
if pemBlock == nil {
|
||||
return nil, UserError{"Error decoding certificate from cert bundle"}
|
||||
}
|
||||
result.CertificateBytes = pemBlock.Bytes
|
||||
result.Certificate, err = x509.ParseCertificate(result.CertificateBytes)
|
||||
if err != nil {
|
||||
return nil, UserError{"Error encountered parsing certificate bytes from raw bundle"}
|
||||
}
|
||||
|
||||
if len(c.IssuingCA) != 0 {
|
||||
pemBlock, _ = pem.Decode([]byte(c.IssuingCA))
|
||||
if pemBlock == nil {
|
||||
return nil, UserError{"Error decoding issuing CA from cert bundle"}
|
||||
}
|
||||
result.IssuingCABytes = pemBlock.Bytes
|
||||
result.IssuingCA, err = x509.ParseCertificate(result.IssuingCABytes)
|
||||
if err != nil {
|
||||
return nil, UserError{fmt.Sprintf("Error parsing CA certificate: %s", err)}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.SerialNumber) == 0 {
|
||||
c.SerialNumber = GetOctalFormatted(result.Certificate.SerialNumber.Bytes(), ":")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ToCertBundle converts a byte-based raw DER certificate bundle
|
||||
// to a PEM-based string certificate bundle
|
||||
func (p *ParsedCertBundle) ToCertBundle() (*CertBundle, error) {
|
||||
result := &CertBundle{
|
||||
SerialNumber: GetOctalFormatted(p.Certificate.SerialNumber.Bytes(), ":"),
|
||||
}
|
||||
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: p.CertificateBytes,
|
||||
}
|
||||
result.Certificate = string(pem.EncodeToMemory(&block))
|
||||
|
||||
if len(p.IssuingCABytes) != 0 {
|
||||
block.Bytes = p.IssuingCABytes
|
||||
result.IssuingCA = string(pem.EncodeToMemory(&block))
|
||||
}
|
||||
|
||||
block.Bytes = p.PrivateKeyBytes
|
||||
switch p.PrivateKeyType {
|
||||
case RSAPrivateKey:
|
||||
result.PrivateKeyType = "rsa"
|
||||
block.Type = "RSA PRIVATE KEY"
|
||||
case ECPrivateKey:
|
||||
result.PrivateKeyType = "ec"
|
||||
block.Type = "EC PRIVATE KEY"
|
||||
default:
|
||||
return nil, InternalError{"Could not determine private key type when creating block"}
|
||||
}
|
||||
result.PrivateKey = string(pem.EncodeToMemory(&block))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetSigner returns a crypto.Signer corresponding to the private key
|
||||
// contained in this ParsedCertBundle. The Signer contains a Public() function
|
||||
// for getting the corresponding public. The Signer can also be
|
||||
// type-converted to private keys
|
||||
func (p *ParsedCertBundle) getSigner() (crypto.Signer, error) {
|
||||
var signer crypto.Signer
|
||||
var err error
|
||||
switch p.PrivateKeyType {
|
||||
case ECPrivateKey:
|
||||
signer, err = x509.ParseECPrivateKey(p.PrivateKeyBytes)
|
||||
if err != nil {
|
||||
return nil, UserError{fmt.Sprintf("Unable to parse CA's private EC key: %s", err)}
|
||||
}
|
||||
case RSAPrivateKey:
|
||||
signer, err = x509.ParsePKCS1PrivateKey(p.PrivateKeyBytes)
|
||||
if err != nil {
|
||||
return nil, UserError{fmt.Sprintf("Unable to parse CA's private RSA key: %s", err)}
|
||||
}
|
||||
default:
|
||||
return nil, UserError{"Unable to determine type of private key; only RSA and EC are supported"}
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// GetTLSConfig returns a TLS config generally suitable for client
|
||||
// authentiation. The returned TLS config can be modified slightly
|
||||
// to be made suitable for a server requiring client authentication;
|
||||
// specifically, you should set the value of ClientAuth in the returned
|
||||
// config to match your needs.
|
||||
func (p *ParsedCertBundle) GetTLSConfig(usage TLSUsage) (*tls.Config, error) {
|
||||
tlsCert := &tls.Certificate{
|
||||
Certificate: [][]byte{
|
||||
p.CertificateBytes,
|
||||
},
|
||||
PrivateKey: p.PrivateKey,
|
||||
Leaf: p.Certificate,
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
Certificates: []tls.Certificate{*tlsCert},
|
||||
}
|
||||
|
||||
if len(p.IssuingCABytes) > 0 {
|
||||
tlsCert.Certificate = append(tlsCert.Certificate, p.IssuingCABytes)
|
||||
|
||||
// Technically we only need one cert, but this doesn't duplicate code
|
||||
certBundle, err := p.ToCertBundle()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error converting parsed bundle to string bundle when getting TLS config: %s", err)
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
ok := caPool.AppendCertsFromPEM([]byte(certBundle.IssuingCA))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Could not append CA certificate")
|
||||
}
|
||||
|
||||
if usage&TLSServer != 0 {
|
||||
tlsConfig.ClientCAs = caPool
|
||||
}
|
||||
if usage&TLSClient != 0 {
|
||||
tlsConfig.RootCAs = caPool
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// IssueData is a structure that is suitable for marshaling into a request;
|
||||
// either via JSON, or into a map[string]interface{} via the structs package
|
||||
type IssueData struct {
|
||||
Lease string `json:"lease" structs:"lease" mapstructure:"lease"`
|
||||
CommonName string `json:"common_name" structs:"common_name" mapstructure:"common_name"`
|
||||
AltNames string `json:"alt_names" structs:"alt_names" mapstructure:"alt_names"`
|
||||
IPSANs string `json:"ip_sans" structs:"ip_sans" mapstructure:"ip_sans"`
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package certutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// GetOctalFormatted returns the byte buffer formatted in octal with
|
||||
// the specified separator between bytes
|
||||
func GetOctalFormatted(buf []byte, sep string) string {
|
||||
var ret bytes.Buffer
|
||||
for _, cur := range buf {
|
||||
if ret.Len() > 0 {
|
||||
fmt.Fprintf(&ret, sep)
|
||||
}
|
||||
fmt.Fprintf(&ret, "%02x", cur)
|
||||
}
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
// ParsePKISecret takes a Secret returned from the PKI backend
|
||||
// and returns a CertBundle for further processing (espcially
|
||||
// by converting to a RawCertBundle)
|
||||
func ParsePKISecret(secret *api.Secret) (*CertBundle, error) {
|
||||
var result CertBundle
|
||||
err := mapstructure.Decode(secret.Data, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
// Package certutil contains helper functions that are mostly used
|
||||
// with the PKI backend but can be generally useful. Functionality
|
||||
// includes helpers for converting a certificate/private key bundle
|
||||
// between DER and PEM, printing certificate serial numbers, and more.
|
||||
//
|
||||
// Functionality specific to the PKI backend includes some types
|
||||
// and helper methods to make requesting certificates from the
|
||||
// backend easy.
|
||||
package certutil
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// The type of of the Private Key referenced in CertBundle
|
||||
// and RawCertBundle. This uses colloquial names rather than
|
||||
// official names, to eliminate confusion
|
||||
const (
|
||||
UnknownPrivateKeyType = iota
|
||||
RSAPrivateKeyType
|
||||
ECPrivateKeyType
|
||||
)
|
||||
|
||||
// CertBundle contains a key type, a private key,
|
||||
// a certificate, and a string-encoded serial number,
|
||||
// returned from a successful Issue request
|
||||
type CertBundle struct {
|
||||
PrivateKeyType string `json:"private_key_type" structs:"private_key_type" mapstructure:"private_key_type"`
|
||||
Certificate string `json:"certificate" structs:"certificate" mapstructure:"certificate"`
|
||||
IssuingCA string `json:"issuing_ca" structs:"issuing_ca" mapstructure:"issuing_ca"`
|
||||
PrivateKey string `json:"private_key" structs:"private_key" mapstructure:"private_key"`
|
||||
SerialNumber string `json:"serial_number" structs:"serial_number" mapstructure:"serial_number"`
|
||||
}
|
||||
|
||||
// ToRawCertBundle converts a string-based certificate bundle
|
||||
// to a byte-based raw certificate bundle
|
||||
func (c *CertBundle) ToRawCertBundle() (*RawCertBundle, error) {
|
||||
result := &RawCertBundle{}
|
||||
switch c.PrivateKeyType {
|
||||
case "ec":
|
||||
result.PrivateKeyType = ECPrivateKeyType
|
||||
case "rsa":
|
||||
result.PrivateKeyType = RSAPrivateKeyType
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown private key type in bundle: %s", c.PrivateKeyType)
|
||||
}
|
||||
|
||||
var pemBlock *pem.Block
|
||||
pemBlock, _ = pem.Decode([]byte(c.PrivateKey))
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("Error decoding private key from cert bundle")
|
||||
}
|
||||
result.PrivateKeyBytes = pemBlock.Bytes
|
||||
|
||||
pemBlock, _ = pem.Decode([]byte(c.Certificate))
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("Error decoding certificate from cert bundle")
|
||||
}
|
||||
result.CertificateBytes = pemBlock.Bytes
|
||||
|
||||
if len(c.IssuingCA) != 0 {
|
||||
pemBlock, _ = pem.Decode([]byte(c.IssuingCA))
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("Error decoding issuing CA from cert bundle")
|
||||
}
|
||||
result.IssuingCABytes = pemBlock.Bytes
|
||||
}
|
||||
|
||||
if err := result.populateSerialNumber(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RawCertBundle contains a key type, a DER-encoded private key,
|
||||
// a DER-encoded certificate, and a big.Int serial number
|
||||
type RawCertBundle struct {
|
||||
PrivateKeyType int
|
||||
PrivateKeyBytes []byte
|
||||
IssuingCABytes []byte
|
||||
CertificateBytes []byte
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// ToCertBundle converts a byte-based raw DER certificate bundle
|
||||
// to a PEM-based string certificate bundle
|
||||
func (r *RawCertBundle) ToCertBundle() (*CertBundle, error) {
|
||||
result := &CertBundle{
|
||||
SerialNumber: GetOctalFormatted(r.SerialNumber.Bytes(), ":"),
|
||||
}
|
||||
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: r.CertificateBytes,
|
||||
}
|
||||
result.Certificate = string(pem.EncodeToMemory(&block))
|
||||
|
||||
if len(r.IssuingCABytes) != 0 {
|
||||
block.Bytes = r.IssuingCABytes
|
||||
result.IssuingCA = string(pem.EncodeToMemory(&block))
|
||||
}
|
||||
|
||||
block.Bytes = r.PrivateKeyBytes
|
||||
switch r.PrivateKeyType {
|
||||
case RSAPrivateKeyType:
|
||||
result.PrivateKeyType = "rsa"
|
||||
block.Type = "RSA PRIVATE KEY"
|
||||
case ECPrivateKeyType:
|
||||
result.PrivateKeyType = "ec"
|
||||
block.Type = "EC PRIVATE KEY"
|
||||
default:
|
||||
return nil, fmt.Errorf("Could not determine private key type when creating block")
|
||||
}
|
||||
result.PrivateKey = string(pem.EncodeToMemory(&block))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *RawCertBundle) populateSerialNumber() error {
|
||||
cert, err := x509.ParseCertificate(r.CertificateBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error encountered parsing certificate bytes from raw bundle")
|
||||
}
|
||||
r.SerialNumber = cert.SerialNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigner returns a crypto.Signer corresponding to the private key
|
||||
// contained in this RawCertBundle. The Signer contains a Public() function
|
||||
// for getting the corresponding public. The Signer can also be
|
||||
// type-converted to private keys
|
||||
func (r *RawCertBundle) GetSigner() (crypto.Signer, error) {
|
||||
var signer crypto.Signer
|
||||
var err error
|
||||
switch r.PrivateKeyType {
|
||||
case ECPrivateKeyType:
|
||||
signer, err = x509.ParseECPrivateKey(r.PrivateKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse CA's private EC key: %s", err)
|
||||
}
|
||||
case RSAPrivateKeyType:
|
||||
signer, err = x509.ParsePKCS1PrivateKey(r.PrivateKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse CA's private RSA key: %s", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unable to determine the type of private key")
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// GetSubjKeyID returns the subject key ID, e.g. the SHA1 sum
|
||||
// of the marshaled public key
|
||||
func (r *RawCertBundle) GetSubjKeyID() ([]byte, error) {
|
||||
privateKey, err := r.GetSigner()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
marshaledKey, err := x509.MarshalPKIXPublicKey(privateKey.Public())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error marshalling public key: %s", err)
|
||||
}
|
||||
|
||||
subjKeyID := sha1.Sum(marshaledKey)
|
||||
|
||||
return subjKeyID[:], nil
|
||||
}
|
||||
|
||||
// IssueData is a structure that is suitable for marshaling into a request;
|
||||
// either via JSON, or into a map[string]interface{} via the structs package
|
||||
type IssueData struct {
|
||||
Lease string `json:"lease" structs:"lease" mapstructure:"lease"`
|
||||
CommonName string `json:"common_name" structs:"common_name" mapstructure:"common_name"`
|
||||
AltNames string `json:"alt_names" structs:"alt_names" mapstructure:"alt_names"`
|
||||
IPSANs string `json:"ip_sans" structs:"ip_sans" mapstructure:"ip_sans"`
|
||||
}
|
||||
Reference in New Issue
Block a user