vault/helper/certutil/helpers.go
Jeff Mitchell 31e680048e 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>
2015-06-17 16:07:20 -04:00

168 lines
5.1 KiB
Go

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
}