package transport import ( "crypto/tls" "crypto/x509" "encoding/pem" "os" "path/filepath" "testing" "git.cloudinit.dev/coreci/orca/internal/security" ) func generateTestCerts(t *testing.T, dir, serverName string) (certPath, keyPath, caPath string) { t.Helper() ca, err := security.CAInit(dir, "orca-test-ca") if err != nil { t.Fatalf("CAInit: %v", err) } keyPEM, csrPEM, err := security.GenerateCSR(serverName, []string{serverName, "127.0.0.1"}) if err != nil { t.Fatalf("GenerateCSR: %v", err) } signedPEM, err := ca.SignCSR(csrPEM) if err != nil { t.Fatalf("SignCSR: %v", err) } certPath = filepath.Join(dir, "server.crt") keyPath = filepath.Join(dir, "server.key") caPath = filepath.Join(dir, security.CACertFile) if err := os.WriteFile(certPath, signedPEM, 0o644); err != nil { t.Fatalf("write cert: %v", err) } if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil { t.Fatalf("write key: %v", err) } return certPath, keyPath, caPath } func TestServerTLSConfig(t *testing.T) { dir := t.TempDir() certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost") cfg, err := security.ServerTLSConfig(certPath, keyPath, caPath) if err != nil { t.Fatalf("ServerTLSConfig: %v", err) } if cfg.MinVersion != tls.VersionTLS13 { t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13) } if cfg.MaxVersion != tls.VersionTLS13 { t.Errorf("MaxVersion = %d, want %d", cfg.MaxVersion, tls.VersionTLS13) } if cfg.ClientAuth != tls.RequireAndVerifyClientCert { t.Errorf("ClientAuth = %v, want RequireAndVerifyClientCert", cfg.ClientAuth) } if cfg.ClientCAs == nil { t.Error("ClientCAs is nil") } if len(cfg.CipherSuites) == 0 { t.Error("CipherSuites is empty") } } func TestServerTLSConfig_MissingFiles(t *testing.T) { dir := t.TempDir() _, err := security.ServerTLSConfig( filepath.Join(dir, "nope.crt"), filepath.Join(dir, "nope.key"), filepath.Join(dir, "nope.ca"), ) if err == nil { t.Fatal("expected error for missing files") } } func TestClientTLSConfig(t *testing.T) { dir := t.TempDir() certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost") cfg, err := security.ClientTLSConfig(caPath, "localhost", certPath, keyPath) if err != nil { t.Fatalf("ClientTLSConfig: %v", err) } if cfg.MinVersion != tls.VersionTLS13 { t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13) } if cfg.RootCAs == nil { t.Error("RootCAs is nil") } if cfg.ServerName != "localhost" { t.Errorf("ServerName = %q, want localhost", cfg.ServerName) } if len(cfg.Certificates) != 1 { t.Errorf("Certificates len = %d, want 1", len(cfg.Certificates)) } } func TestClientTLSConfig_NoClientCert(t *testing.T) { dir := t.TempDir() _, _, caPath := generateTestCerts(t, dir, "localhost") cfg, err := security.ClientTLSConfig(caPath, "localhost", "", "") if err != nil { t.Fatalf("ClientTLSConfig: %v", err) } if len(cfg.Certificates) != 0 { t.Errorf("Certificates len = %d, want 0", len(cfg.Certificates)) } } func TestClientTLSConfig_MismatchedCertKey(t *testing.T) { dir := t.TempDir() _, _, caPath := generateTestCerts(t, dir, "localhost") if _, err := security.ClientTLSConfig(caPath, "localhost", "only-cert", ""); err == nil { t.Error("expected error for cert without key") } if _, err := security.ClientTLSConfig(caPath, "localhost", "", "only-key"); err == nil { t.Error("expected error for key without cert") } } func TestNewMTLSClient(t *testing.T) { dir := t.TempDir() _, _, caPath := generateTestCerts(t, dir, "localhost") c, err := NewMTLSClient(caPath, "localhost", "", "") if err != nil { t.Fatalf("NewMTLSClient: %v", err) } if c == nil { t.Fatal("client is nil") } } func TestNewMTLSClient_EmptyCAPath(t *testing.T) { _, err := NewMTLSClient("", "localhost", "", "") if err == nil { t.Fatal("expected error for empty caPath") } } func TestNewMTLSClient_EmptyServerName(t *testing.T) { dir := t.TempDir() _, _, caPath := generateTestCerts(t, dir, "localhost") _, err := NewMTLSClient(caPath, "", "", "") if err == nil { t.Fatal("expected error for empty serverName") } } func TestNewMTLSClient_MissingCAFile(t *testing.T) { _, err := NewMTLSClient("/nonexistent/ca.crt", "localhost", "", "") if err == nil { t.Fatal("expected error for missing CA file") } } func TestMTLSClient_Do_NilReceiver(t *testing.T) { var c *MTLSClient _, err := c.Do(nil) if err == nil { t.Fatal("expected error for nil receiver") } } func TestVerifyPeerCertificate_NoCerts(t *testing.T) { cb := VerifyPeerCertificate("expected") if err := cb(nil, nil); err == nil { t.Error("expected error for no peer certs") } } func TestVerifyPeerCertificate_Mismatch(t *testing.T) { dir := t.TempDir() certPath, _, _ := generateTestCerts(t, dir, "localhost") certPEM, err := os.ReadFile(certPath) if err != nil { t.Fatalf("read cert: %v", err) } block, _ := pem.Decode(certPEM) if block == nil { t.Fatal("pem.Decode: no cert block") } cb := VerifyPeerCertificate("wrong-fingerprint") if err := cb([][]byte{block.Bytes}, nil); err == nil { t.Error("expected error for fingerprint mismatch") } } func TestVerifyPeerCertificate_Match(t *testing.T) { dir := t.TempDir() certPath, _, _ := generateTestCerts(t, dir, "localhost") certPEM, err := os.ReadFile(certPath) if err != nil { t.Fatalf("read cert: %v", err) } block, _ := pem.Decode(certPEM) if block == nil { t.Fatal("pem.Decode: no cert block") } leaf, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("ParseCertificate: %v", err) } expected := security.FingerprintOf(leaf.Raw) cb := VerifyPeerCertificate(expected) if err := cb([][]byte{block.Bytes}, nil); err != nil { t.Errorf("expected match, got: %v", err) } } func TestDialContext_EmptyCAPath(t *testing.T) { _, err := DialContext(t.Context(), "tcp", "127.0.0.1:0", "", "localhost") if err == nil { t.Fatal("expected error for empty caPath") } } func TestDialContext_ConnectionRefused(t *testing.T) { dir := t.TempDir() _, _, caPath := generateTestCerts(t, dir, "localhost") _, err := DialContext(t.Context(), "tcp", "127.0.0.1:1", caPath, "localhost") if err == nil { t.Fatal("expected error for connection refused") } }