| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package cassandra |
| |
| import ( |
| "crypto/tls" |
| "crypto/x509" |
| "encoding/json" |
| "encoding/pem" |
| "fmt" |
| |
| "github.com/hashicorp/vault/sdk/helper/certutil" |
| "github.com/hashicorp/vault/sdk/helper/errutil" |
| ) |
| |
| func jsonBundleToTLSConfig(rawJSON string, tlsMinVersion uint16, serverName string, insecureSkipVerify bool) (*tls.Config, error) { |
| var certBundle certutil.CertBundle |
| err := json.Unmarshal([]byte(rawJSON), &certBundle) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse JSON: %w", err) |
| } |
| |
| if certBundle.IssuingCA != "" && len(certBundle.CAChain) > 0 { |
| return nil, fmt.Errorf("issuing_ca and ca_chain cannot both be specified") |
| } |
| if certBundle.IssuingCA != "" { |
| certBundle.CAChain = []string{certBundle.IssuingCA} |
| certBundle.IssuingCA = "" |
| } |
| |
| return toClientTLSConfig(certBundle.Certificate, certBundle.PrivateKey, certBundle.CAChain, tlsMinVersion, serverName, insecureSkipVerify) |
| } |
| |
| func pemBundleToTLSConfig(pemBundle string, tlsMinVersion uint16, serverName string, insecureSkipVerify bool) (*tls.Config, error) { |
| if len(pemBundle) == 0 { |
| return nil, errutil.UserError{Err: "empty pem bundle"} |
| } |
| |
| pemBytes := []byte(pemBundle) |
| var pemBlock *pem.Block |
| |
| certificate := "" |
| privateKey := "" |
| caChain := []string{} |
| |
| for len(pemBytes) > 0 { |
| pemBlock, pemBytes = pem.Decode(pemBytes) |
| if pemBlock == nil { |
| return nil, errutil.UserError{Err: "no data found in PEM block"} |
| } |
| blockBytes := pem.EncodeToMemory(pemBlock) |
| |
| switch pemBlock.Type { |
| case "CERTIFICATE": |
| // Parse the cert so we know if it's a CA or not |
| cert, err := x509.ParseCertificate(pemBlock.Bytes) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse certificate: %w", err) |
| } |
| if cert.IsCA { |
| caChain = append(caChain, string(blockBytes)) |
| continue |
| } |
| |
| // Only one leaf certificate supported |
| if certificate != "" { |
| return nil, errutil.UserError{Err: "multiple leaf certificates not supported"} |
| } |
| certificate = string(blockBytes) |
| |
| case "RSA PRIVATE KEY", "EC PRIVATE KEY", "PRIVATE KEY": |
| if privateKey != "" { |
| return nil, errutil.UserError{Err: "multiple private keys not supported"} |
| } |
| privateKey = string(blockBytes) |
| default: |
| return nil, fmt.Errorf("unsupported PEM block type [%s]", pemBlock.Type) |
| } |
| } |
| |
| return toClientTLSConfig(certificate, privateKey, caChain, tlsMinVersion, serverName, insecureSkipVerify) |
| } |
| |
| func toClientTLSConfig(certificatePEM string, privateKeyPEM string, caChainPEMs []string, tlsMinVersion uint16, serverName string, insecureSkipVerify bool) (*tls.Config, error) { |
| if certificatePEM != "" && privateKeyPEM == "" { |
| return nil, fmt.Errorf("found certificate for client-side TLS authentication but no private key") |
| } else if certificatePEM == "" && privateKeyPEM != "" { |
| return nil, fmt.Errorf("found private key for client-side TLS authentication but no certificate") |
| } |
| |
| var certificates []tls.Certificate |
| if certificatePEM != "" { |
| certificate, err := tls.X509KeyPair([]byte(certificatePEM), []byte(privateKeyPEM)) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse certificate and private key pair: %w", err) |
| } |
| certificates = append(certificates, certificate) |
| } |
| |
| var rootCAs *x509.CertPool |
| if len(caChainPEMs) > 0 { |
| rootCAs = x509.NewCertPool() |
| for _, caBlock := range caChainPEMs { |
| ok := rootCAs.AppendCertsFromPEM([]byte(caBlock)) |
| if !ok { |
| return nil, fmt.Errorf("failed to add CA certificate to certificate pool: it may be malformed or empty") |
| } |
| } |
| } |
| |
| config := &tls.Config{ |
| Certificates: certificates, |
| RootCAs: rootCAs, |
| ServerName: serverName, |
| InsecureSkipVerify: insecureSkipVerify, |
| MinVersion: tlsMinVersion, |
| } |
| return config, nil |
| } |