mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
6.2 KiB
228 lines
6.2 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package connect |
|
|
|
import ( |
|
"crypto" |
|
"crypto/ecdsa" |
|
"crypto/rsa" |
|
"crypto/sha1" |
|
"crypto/sha256" |
|
"crypto/x509" |
|
"encoding/hex" |
|
"encoding/pem" |
|
"fmt" |
|
"math/big" |
|
"strings" |
|
) |
|
|
|
// ParseCert parses the x509 certificate from a PEM-encoded value. |
|
func ParseCert(pemValue string) (*x509.Certificate, error) { |
|
// The _ result below is not an error but the remaining PEM bytes. |
|
block, _ := pem.Decode([]byte(pemValue)) |
|
if block == nil { |
|
return nil, fmt.Errorf("no PEM-encoded data found") |
|
} |
|
|
|
if block.Type != "CERTIFICATE" { |
|
return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type") |
|
} |
|
|
|
return x509.ParseCertificate(block.Bytes) |
|
} |
|
|
|
// ParseLeafCerts parses all of the x509 certificates from a PEM-encoded value |
|
// under the assumption that the first cert is a leaf (non-CA) cert and the |
|
// rest are intermediate CA certs. |
|
// |
|
// If no certificates are found this returns an error. |
|
func ParseLeafCerts(pemValue string) (*x509.Certificate, *x509.CertPool, error) { |
|
certs, err := parseCerts(pemValue) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
leaf := certs[0] |
|
if leaf.IsCA { |
|
return nil, nil, fmt.Errorf("first PEM-block should be a leaf cert") |
|
} |
|
|
|
intermediates := x509.NewCertPool() |
|
for _, cert := range certs[1:] { |
|
if !cert.IsCA { |
|
return nil, nil, fmt.Errorf("found an unexpected leaf cert after the first PEM-block") |
|
} |
|
intermediates.AddCert(cert) |
|
} |
|
|
|
return leaf, intermediates, nil |
|
} |
|
|
|
// CertSubjects can be used in debugging to return the subject of each |
|
// certificate in the PEM bundle. Each subject is separated by a newline. |
|
func CertSubjects(pem string) string { |
|
certs, err := parseCerts(pem) |
|
if err != nil { |
|
return err.Error() |
|
} |
|
var buf strings.Builder |
|
for _, cert := range certs { |
|
buf.WriteString(cert.Subject.String()) |
|
buf.WriteString("\n") |
|
} |
|
return buf.String() |
|
} |
|
|
|
// ParseCerts parses the all x509 certificates from a PEM-encoded value. |
|
// The first returned cert is a leaf cert and any other ones are intermediates. |
|
// |
|
// If no certificates are found this returns an error. |
|
func parseCerts(pemValue string) ([]*x509.Certificate, error) { |
|
var out []*x509.Certificate |
|
|
|
rest := []byte(pemValue) |
|
for { |
|
// The _ result below is not an error but the remaining PEM bytes. |
|
block, remaining := pem.Decode(rest) |
|
if block == nil { |
|
break |
|
} |
|
rest = remaining |
|
|
|
if block.Type != "CERTIFICATE" { |
|
return nil, fmt.Errorf("PEM-block should be CERTIFICATE type") |
|
} |
|
|
|
cert, err := x509.ParseCertificate(block.Bytes) |
|
if err != nil { |
|
return nil, err |
|
} |
|
out = append(out, cert) |
|
} |
|
|
|
if len(out) == 0 { |
|
return nil, fmt.Errorf("no PEM-encoded data found") |
|
} |
|
|
|
return out, nil |
|
} |
|
|
|
// CalculateCertFingerprint calculates the SHA-1 fingerprint from the cert bytes. |
|
func CalculateCertFingerprint(cert []byte) string { |
|
hash := sha1.Sum(cert) |
|
return HexString(hash[:]) |
|
} |
|
|
|
// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key |
|
// is expected to be the first block in the PEM value. |
|
func ParseSigner(pemValue string) (crypto.Signer, error) { |
|
// The _ result below is not an error but the remaining PEM bytes. |
|
block, _ := pem.Decode([]byte(pemValue)) |
|
if block == nil { |
|
return nil, fmt.Errorf("no PEM-encoded data found") |
|
} |
|
|
|
switch block.Type { |
|
case "EC PRIVATE KEY": |
|
return x509.ParseECPrivateKey(block.Bytes) |
|
|
|
case "RSA PRIVATE KEY": |
|
return x509.ParsePKCS1PrivateKey(block.Bytes) |
|
|
|
case "PRIVATE KEY": |
|
signer, err := x509.ParsePKCS8PrivateKey(block.Bytes) |
|
if err != nil { |
|
return nil, err |
|
} |
|
pk, ok := signer.(crypto.Signer) |
|
if !ok { |
|
return nil, fmt.Errorf("private key is not a valid format") |
|
} |
|
|
|
return pk, nil |
|
|
|
default: |
|
return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type) |
|
} |
|
} |
|
|
|
// ParseCSR parses a CSR from a PEM-encoded value. The certificate request |
|
// must be the first block in the PEM value. |
|
func ParseCSR(pemValue string) (*x509.CertificateRequest, error) { |
|
// The _ result below is not an error but the remaining PEM bytes. |
|
block, _ := pem.Decode([]byte(pemValue)) |
|
if block == nil { |
|
return nil, fmt.Errorf("no PEM-encoded data found") |
|
} |
|
|
|
if block.Type != "CERTIFICATE REQUEST" { |
|
return nil, fmt.Errorf("first PEM-block should be CERTIFICATE REQUEST type") |
|
} |
|
|
|
return x509.ParseCertificateRequest(block.Bytes) |
|
} |
|
|
|
// KeyId returns a x509 KeyId from the given signing key. The key must be |
|
// an *ecdsa.PublicKey currently, but may support more types in the future. |
|
func KeyId(raw interface{}) ([]byte, error) { |
|
switch raw.(type) { |
|
case *ecdsa.PublicKey: |
|
case *rsa.PublicKey: |
|
default: |
|
return nil, fmt.Errorf("invalid key type: %T", raw) |
|
} |
|
|
|
// This is not standard; RFC allows any unique identifier as long as they |
|
// match in subject/authority chains but suggests specific hashing of DER |
|
// bytes of public key including DER tags. |
|
bs, err := x509.MarshalPKIXPublicKey(raw) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
kID := sha256.Sum256(bs) |
|
return kID[:], nil |
|
} |
|
|
|
// EncodeSerialNumber encodes the given serial number as a colon-hex encoded |
|
// string. |
|
func EncodeSerialNumber(serial *big.Int) string { |
|
return HexString(serial.Bytes()) |
|
} |
|
|
|
// EncodeSigningKeyID encodes the given AuthorityKeyId or SubjectKeyId into a |
|
// colon-hex encoded string suitable for using as a SigningKeyID value. |
|
func EncodeSigningKeyID(keyID []byte) string { return HexString(keyID) } |
|
|
|
// HexString returns a standard colon-separated hex value for the input |
|
// byte slice. This should be used with cert serial numbers and so on. |
|
func HexString(input []byte) string { |
|
return strings.Replace(fmt.Sprintf("% x", input), " ", ":", -1) |
|
} |
|
|
|
// IsHexString returns true if the input is the output of HexString(). Meant |
|
// for use in tests. |
|
func IsHexString(input []byte) bool { |
|
s := string(input) |
|
if strings.Count(s, ":") < 5 { // 5 is arbitrary |
|
return false |
|
} |
|
|
|
s = strings.ReplaceAll(s, ":", "") |
|
_, err := hex.DecodeString(s) |
|
return err == nil |
|
} |
|
|
|
// KeyInfoFromCert returns the key type and key bit length for the key used by |
|
// the certificate. |
|
func KeyInfoFromCert(cert *x509.Certificate) (keyType string, keyBits int, err error) { |
|
switch k := cert.PublicKey.(type) { |
|
case *ecdsa.PublicKey: |
|
return "ec", k.Curve.Params().BitSize, nil |
|
case *rsa.PublicKey: |
|
return "rsa", k.N.BitLen(), nil |
|
default: |
|
return "", 0, fmt.Errorf("unsupported key type") |
|
} |
|
}
|
|
|