mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 11:07:00 +02:00
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>
168 lines
5.1 KiB
Go
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
|
|
}
|