pkg/controller/common/certificates/pem.go (159 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package certificates import ( "bytes" "context" "crypto" "crypto/ecdsa" cryptorand "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "reflect" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log" ) var ErrEncryptedPrivateKey = errors.New("encrypted private key") const ( ecPrivateKeyType = "EC PRIVATE KEY" pkcs1PrivateKeyType = "RSA PRIVATE KEY" pkcs8PrivateKeyType = "PRIVATE KEY" ) // ParsePEMCerts returns a list of certificates from the given PEM certs data // Based on the code of x509.CertPool.AppendCertsFromPEM (https://golang.org/src/crypto/x509/cert_pool.go) // We don't rely on x509.CertPool.AppendCertsFromPEM directly here since it returns an interface from which // we cannot extract the actual certificates if we need to compare them. func ParsePEMCerts(pemData []byte) ([]*x509.Certificate, error) { certs := []*x509.Certificate{} for len(pemData) > 0 { var block *pem.Block block, pemData = pem.Decode(pemData) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, errors.WithStack(err) } certs = append(certs, cert) } return certs, nil } // EncodePEMCert encodes the given certificate blocks as a PEM certificate func EncodePEMCert(certBlocks ...[]byte) []byte { var buf bytes.Buffer for _, block := range certBlocks { _, _ = buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: block})) } return buf.Bytes() } // EncodePEMPrivateKey encodes the given private key in the PEM format func EncodePEMPrivateKey(privateKey crypto.Signer) ([]byte, error) { pemBlock, err := pemBlockForKey(privateKey) if err != nil { return nil, err } return pem.EncodeToMemory(pemBlock), nil } func pemBlockForKey(privateKey interface{}) (*pem.Block, error) { switch k := privateKey.(type) { case *rsa.PrivateKey: return &pem.Block{Type: pkcs1PrivateKeyType, Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil case *ecdsa.PrivateKey: b, err := x509.MarshalECPrivateKey(k) if err != nil { return nil, err } return &pem.Block{Type: ecPrivateKeyType, Bytes: b}, nil default: // attempt PKCS#8 format b, err := x509.MarshalPKCS8PrivateKey(k) if err != nil { return nil, err } return &pem.Block{Type: pkcs8PrivateKeyType, Bytes: b}, nil } } // ParsePEMPrivateKey parses the given private key in the PEM format // ErrEncryptedPrivateKey is returned as an error if the private key is encrypted. func ParsePEMPrivateKey(pemData []byte) (crypto.Signer, error) { block, _ := pem.Decode(pemData) if block == nil { return nil, errors.New("failed to parse PEM block containing private key") } switch { case x509.IsEncryptedPEMBlock(block): //nolint:staticcheck // Private key is encrypted, do not attempt to parse it return nil, ErrEncryptedPrivateKey case block.Type == pkcs8PrivateKeyType: return parsePKCS8PrivateKey(block.Bytes) case block.Type == pkcs1PrivateKeyType && len(block.Headers) == 0: return x509.ParsePKCS1PrivateKey(block.Bytes) case block.Type == ecPrivateKeyType: return x509.ParseECPrivateKey(block.Bytes) default: return nil, fmt.Errorf("unsupported private key type %q", block.Type) } } func parsePKCS8PrivateKey(block []byte) (*rsa.PrivateKey, error) { key, err := x509.ParsePKCS8PrivateKey(block) if err != nil { return nil, errors.Wrap(err, "failed to parse private key") } rsaKey, ok := key.(*rsa.PrivateKey) if !ok { return nil, errors.Errorf("expected an RSA private key but got %t", key) } return rsaKey, nil } // GetPrimaryCertificate returns the primary certificate (i.e. the actual subject, not a CA or intermediate) from a PEM certificate chain func GetPrimaryCertificate(pemBytes []byte) (*x509.Certificate, error) { parsedCerts, err := ParsePEMCerts(pemBytes) if err != nil { return nil, err } // the primary certificate should always come first, see: // http://tools.ietf.org/html/rfc4346#section-7.4.2 if len(parsedCerts) < 1 { return nil, errors.New("Expected at least one certificate") } return parsedCerts[0], nil } // PrivateMatchesPublicKey returns true if the public and private keys correspond to each other. func PrivateMatchesPublicKey(ctx context.Context, publicKey crypto.PublicKey, privateKey crypto.Signer) bool { switch k := publicKey.(type) { case *rsa.PublicKey: return k.Equal(privateKey.Public()) case *ecdsa.PublicKey: return k.Equal(privateKey.Public()) default: ulog.FromContext(ctx).Error(fmt.Errorf("unsupported public key type: %T", publicKey), "") return false } } // GetCompatiblePrivateKey returns a PEM encoded private key iff the CA and the key have the same underlying type. func GetCompatiblePrivateKey(ctx context.Context, caPrivateKey crypto.Signer, secret *corev1.Secret, fileName string) crypto.Signer { if certPrivateKeyData, ok := secret.Data[fileName]; ok { log := ulog.FromContext(ctx) certPrivateKey, err := ParsePEMPrivateKey(certPrivateKeyData) if err != nil { log.Error(err, "Unable to parse stored private key", "namespace", secret.Namespace, "secret_name", secret.Name, "cert_key_filename", fileName) return nil } if caPrivateKey == nil || certPrivateKey == nil { return nil } if reflect.TypeOf(caPrivateKey) != reflect.TypeOf(certPrivateKey) { log.Info( "CA and cert private key do not share the same implementation", "namespace", secret.Namespace, "secret_name", secret.Name, "cert_key_filename", fileName, "ca_type", reflect.TypeOf(caPrivateKey), "cert_type", reflect.TypeOf(certPrivateKey), ) return nil } return certPrivateKey } return nil } // NewPrivateKey generates a new private key using the same implementation than the CA. func NewPrivateKey(caSigner crypto.Signer) (crypto.Signer, error) { switch k := caSigner.(type) { case *rsa.PrivateKey: return rsa.GenerateKey(cryptorand.Reader, 2048) case *ecdsa.PrivateKey: // re-use the same curve return ecdsa.GenerateKey(k.PublicKey.Curve, cryptorand.Reader) default: return nil, fmt.Errorf("unsupported CA private key type: %T", caSigner) } }