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:
Jeff Mitchell
2015-06-17 12:43:36 -04:00
parent 79164f38ad
commit 31e680048e
9 changed files with 493 additions and 330 deletions

View File

@@ -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"
)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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
View 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
View 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"`
}

View File

@@ -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
}

View File

@@ -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"`
}