consul/agent/connect/parsing.go

229 lines
6.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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 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")
}
}