Files
Tobias Gesellchen 5edab77209 feat: add comprehensive TLS certificate SAN support with wildcard domains
- Add RFC-compliant wildcard certificates (*.api.bose.io, *.api.bosecm.com) for automatic API coverage
- Include additional Bose production domains (worldwide.bose.com, music.api.bose.com, bose-prod.apigee.net)
- Implement TLS certificate request logging and wildcard domain matching logic
- Add detailed TLS handshake debugging with connection state tracking
- Wrap TLS listener with logging to capture certificate selection and handshake failures
- Update documentation with wildcard certificate coverage and debugging features
- Normalize test data to use consistent local IP addresses

This enables automatic coverage of all current and future Bose API subdomains
while providing comprehensive TLS debugging for DNS redirection troubleshooting.
2026-02-24 21:47:20 +01:00

129 lines
3.2 KiB
Go

package certmanager
import (
"crypto/x509"
"encoding/pem"
"os"
"path/filepath"
"testing"
)
func TestCertificateManager(t *testing.T) {
tempDir, err := os.MkdirTemp("", "crypto-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
cm := NewCertificateManager(filepath.Join(tempDir, "certs"))
// Test CA generation
if err := cm.EnsureCA(); err != nil {
t.Fatalf("Failed to ensure CA: %v", err)
}
if _, err := os.Stat(cm.GetCACertPath()); os.IsNotExist(err) {
t.Errorf("CA certificate not created")
}
if _, err := os.Stat(cm.GetCAKeyPath()); os.IsNotExist(err) {
t.Errorf("CA key not created")
}
// Test loading CA
caCertPEM, err := os.ReadFile(cm.GetCACertPath())
if err != nil {
t.Fatalf("Failed to read CA cert: %v", err)
}
block, _ := pem.Decode(caCertPEM)
if block == nil || block.Type != "CERTIFICATE" {
t.Errorf("Invalid CA certificate PEM")
}
caCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("Failed to parse CA cert: %v", err)
}
if !caCert.IsCA {
t.Errorf("Generated certificate is not a CA")
}
// Test certificate generation
domains := []string{"streaming.bose.com", "updates.bose.com"}
certPEM, keyPEM, err := cm.GenerateCertificate(domains)
if err != nil {
t.Fatalf("Failed to generate certificate: %v", err)
}
if len(certPEM) == 0 || len(keyPEM) == 0 {
t.Errorf("Generated certificate or key is empty")
}
// Verify generated certificate
block, _ = pem.Decode(certPEM)
if block == nil || block.Type != "CERTIFICATE" {
t.Errorf("Invalid certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("Failed to parse certificate: %v", err)
}
if cert.Subject.CommonName != domains[0] {
t.Errorf("Expected CommonName %s, got %s", domains[0], cert.Subject.CommonName)
}
// Check DNS names
if len(cert.DNSNames) != len(domains) {
t.Errorf("Expected %d DNS names, got %d", len(domains), len(cert.DNSNames))
}
// Verify against CA
roots := x509.NewCertPool()
roots.AddCert(caCert)
opts := x509.VerifyOptions{
DNSName: domains[0],
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
t.Errorf("Failed to verify certificate against CA: %v", err)
}
// Test GetServerTLSConfig
tlsConfig, err := cm.GetServerTLSConfig(domains)
if err != nil {
t.Fatalf("Failed to get TLS config: %v", err)
}
if tlsConfig == nil {
t.Fatal("TLS config is nil")
}
if len(tlsConfig.Certificates) == 0 {
t.Fatal("TLS config has no certificates")
}
// Test certificate regeneration if domains change
newDomains := append(domains, "foo.local")
tlsConfig2, err := cm.GetServerTLSConfig(newDomains)
if err != nil {
t.Fatalf("Failed to get updated TLS config: %v", err)
}
if len(tlsConfig2.Certificates[0].Leaf.DNSNames) < 3 {
// Note: tls.LoadX509KeyPair doesn't populate Leaf by default.
// We should parse it manually or rely on the file existence/content.
certBytes, _ := os.ReadFile(cm.GetServerCertPEMPath())
block, _ := pem.Decode(certBytes)
cert, _ := x509.ParseCertificate(block.Bytes)
found := false
for _, d := range cert.DNSNames {
if d == "foo.local" {
found = true
break
}
}
if !found {
t.Errorf("Regenerated certificate does not contain new domain")
}
}
}