Skip to content

Commit 199b781

Browse files
committed
feat: Add support for legacy Common Name certificate verification
Modern Go TLS (1.15+) requires certificates to use Subject Alternative Names (SANs) and rejects certificates that only have Common Name (CN) fields. This causes connection failures with older Cassandra certificates. This commit adds an AllowLegacyCN configuration option that: - Bypasses standard TLS verification when enabled - Manually verifies the certificate chain is signed by trusted CA - Manually verifies the CN matches the expected hostname - Falls back to standard SAN verification if available Security is maintained through manual certificate chain and hostname verification in the VerifyConnection callback. This provides the same security guarantees as SAN-based verification. For cqlshrc compatibility, AllowLegacyCN is automatically enabled when validate=true is set, matching cqlsh behavior with legacy certificates.
1 parent fbca424 commit 199b781

File tree

2 files changed

+70
-13
lines changed

2 files changed

+70
-13
lines changed

internal/config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type SSLConfig struct {
4747
CAPath string `json:"caPath,omitempty"` // Path to CA certificate
4848
HostVerification bool `json:"hostVerification,omitempty"` // Enable hostname verification
4949
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // Skip certificate verification (not recommended for production)
50+
AllowLegacyCN bool `json:"allowLegacyCN,omitempty"` // Allow legacy Common Name field (certificates without SANs)
5051
}
5152

5253
// AIConfig holds AI provider configuration
@@ -537,7 +538,8 @@ func loadCQLSHRC(path string, config *Config) error {
537538
logger.DebugfToFile("CQLSHRC", "Set InsecureSkipVerify to true and HostVerification to false")
538539
} else {
539540
config.SSL.HostVerification = true
540-
logger.DebugfToFile("CQLSHRC", "Set HostVerification to true")
541+
config.SSL.AllowLegacyCN = true // Enable legacy CN support for cqlshrc compatibility
542+
logger.DebugfToFile("CQLSHRC", "Set HostVerification to true and AllowLegacyCN to true")
541543
}
542544
}
543545
}

internal/db/db.go

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -561,18 +561,26 @@ func (s *Session) SetKeyspace(keyspace string) error {
561561

562562
// createTLSConfig creates a TLS configuration based on the SSL settings
563563
func createTLSConfig(sslConfig *config.SSLConfig, hostname string) (*tls.Config, error) {
564-
tlsConfig := &tls.Config{
565-
InsecureSkipVerify: sslConfig.InsecureSkipVerify, // #nosec G402 - Configurable TLS verification
566-
}
567-
568-
// Set ServerName for hostname verification
569-
// This is critical when connecting via IP but need to verify against hostname in certificate
570-
if sslConfig.HostVerification && hostname != "" {
564+
// Determine server name for hostname verification
565+
serverName := hostname
566+
if hostname != "" {
571567
// Strip port if present (hostname might be "host:port")
572568
if colonIdx := strings.LastIndex(hostname, ":"); colonIdx > 0 {
573-
hostname = hostname[:colonIdx]
569+
serverName = hostname[:colonIdx]
574570
}
575-
tlsConfig.ServerName = hostname
571+
}
572+
573+
// When AllowLegacyCN is enabled, we need to bypass standard verification
574+
// and do manual verification in VerifyConnection
575+
skipVerify := sslConfig.InsecureSkipVerify || (sslConfig.AllowLegacyCN && sslConfig.HostVerification)
576+
577+
tlsConfig := &tls.Config{
578+
InsecureSkipVerify: skipVerify, // #nosec G402 - Configurable TLS verification
579+
}
580+
581+
// Set ServerName for hostname verification (only when not using legacy CN verification)
582+
if sslConfig.HostVerification && !sslConfig.AllowLegacyCN && serverName != "" {
583+
tlsConfig.ServerName = serverName
576584
}
577585

578586
// Load client certificate if provided
@@ -598,9 +606,56 @@ func createTLSConfig(sslConfig *config.SSLConfig, hostname string) (*tls.Config,
598606
tlsConfig.RootCAs = caCertPool
599607
}
600608

601-
// Configure hostname verification
602-
if !sslConfig.HostVerification {
603-
tlsConfig.InsecureSkipVerify = true
609+
// Manual verification for legacy CN certificates
610+
// We skip standard verification and manually check the certificate
611+
if sslConfig.AllowLegacyCN && sslConfig.HostVerification {
612+
tlsConfig.VerifyConnection = func(cs tls.ConnectionState) error {
613+
if len(cs.PeerCertificates) == 0 {
614+
return fmt.Errorf("no peer certificates")
615+
}
616+
617+
// Build intermediates pool
618+
intermediates := x509.NewCertPool()
619+
for _, cert := range cs.PeerCertificates[1:] {
620+
intermediates.AddCert(cert)
621+
}
622+
623+
// First try standard verification with SANs
624+
opts := x509.VerifyOptions{
625+
DNSName: serverName,
626+
Intermediates: intermediates,
627+
}
628+
if tlsConfig.RootCAs != nil {
629+
opts.Roots = tlsConfig.RootCAs
630+
}
631+
632+
_, err := cs.PeerCertificates[0].Verify(opts)
633+
if err == nil {
634+
return nil // Standard verification with SANs passed
635+
}
636+
637+
// If standard verification failed, try legacy CN verification
638+
// First verify the certificate chain without hostname check
639+
optsNoHostname := x509.VerifyOptions{
640+
Intermediates: intermediates,
641+
}
642+
if tlsConfig.RootCAs != nil {
643+
optsNoHostname.Roots = tlsConfig.RootCAs
644+
}
645+
646+
_, err = cs.PeerCertificates[0].Verify(optsNoHostname)
647+
if err != nil {
648+
return fmt.Errorf("certificate verification failed: %v", err)
649+
}
650+
651+
// Chain is valid, now check CN
652+
cert := cs.PeerCertificates[0]
653+
if cert.Subject.CommonName == serverName {
654+
return nil // Legacy CN matches
655+
}
656+
657+
return fmt.Errorf("certificate CN %q doesn't match expected hostname %q", cert.Subject.CommonName, serverName)
658+
}
604659
}
605660

606661
return tlsConfig, nil

0 commit comments

Comments
 (0)