package crypto import ( "crypto/ecdsa" "crypto/elliptic" "crypto/tls" "crypto/x509" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" ) func TestLoadOrCreateServerCert_Creates(t *testing.T) { dir := t.TempDir() sc, err := LoadOrCreateServerCert(dir) if err != nil { t.Fatalf("LoadOrCreateServerCert: %v", err) } for name, wantPerm := range map[string]os.FileMode{serverKeyFile: 0o600, serverCertFile: 0o644} { info, err := os.Stat(filepath.Join(dir, name)) if err != nil { t.Fatalf("%s fehlt: %v", name, err) } if info.Mode().Perm() != wantPerm { t.Errorf("%s hat %o, erwartet %o", name, info.Mode().Perm(), wantPerm) } } leaf := sc.Leaf() key, ok := leaf.PublicKey.(*ecdsa.PublicKey) if !ok || key.Curve != elliptic.P256() { t.Errorf("Schlüssel ist kein ECDSA P-256: %T", leaf.PublicKey) } // Selbstsigniert, aber bewusst keine CA (könnte sonst weitere // Zertifikate ausstellen) – daher nicht CheckSignatureFrom. if err := leaf.CheckSignature(leaf.SignatureAlgorithm, leaf.RawTBSCertificate, leaf.Signature); err != nil || leaf.Issuer.String() != leaf.Subject.String() { t.Errorf("nicht selbstsigniert: %v", err) } if leaf.IsCA { t.Error("Server-Zertifikat darf keine CA sein") } if len(leaf.ExtKeyUsage) != 1 || leaf.ExtKeyUsage[0] != x509.ExtKeyUsageServerAuth { t.Errorf("ExtKeyUsage = %v, erwartet ServerAuth", leaf.ExtKeyUsage) } if leaf.NotAfter.Before(time.Now().AddDate(9, 0, 0)) { t.Errorf("gültig nur bis %v – ein Ablauf würde alle Pins brechen", leaf.NotAfter) } if sc.Fingerprint != FingerprintOf(leaf) { t.Error("Fingerprint passt nicht zum Zertifikat") } } func TestLoadOrCreateServerCert_StableAcrossRestarts(t *testing.T) { dir := t.TempDir() first, err := LoadOrCreateServerCert(dir) if err != nil { t.Fatal(err) } second, err := LoadOrCreateServerCert(dir) if err != nil { t.Fatalf("erneutes Laden: %v", err) } if first.Fingerprint != second.Fingerprint { t.Error("Fingerprint hat sich beim Neustart geändert – alle Agents würden die Verbindung verweigern") } } func TestLoadOrCreateServerCert_Errors(t *testing.T) { t.Run("nur Zertifikat vorhanden", func(t *testing.T) { dir := t.TempDir() LoadOrCreateServerCert(dir) os.Remove(filepath.Join(dir, serverKeyFile)) if _, err := LoadOrCreateServerCert(dir); !errors.Is(err, ErrIncompleteCert) { t.Errorf("erwartet ErrIncompleteCert, bekommen %v", err) } if _, err := os.Stat(filepath.Join(dir, serverKeyFile)); err == nil { t.Error("fehlender Schlüssel wurde stillschweigend neu erzeugt") } }) t.Run("nur Schlüssel vorhanden", func(t *testing.T) { dir := t.TempDir() LoadOrCreateServerCert(dir) os.Remove(filepath.Join(dir, serverCertFile)) if _, err := LoadOrCreateServerCert(dir); !errors.Is(err, ErrIncompleteCert) { t.Errorf("erwartet ErrIncompleteCert, bekommen %v", err) } }) t.Run("Schlüssel zu offen", func(t *testing.T) { dir := t.TempDir() LoadOrCreateServerCert(dir) os.Chmod(filepath.Join(dir, serverKeyFile), 0o644) if _, err := LoadOrCreateServerCert(dir); !errors.Is(err, ErrInsecureKeyFile) { t.Errorf("erwartet ErrInsecureKeyFile, bekommen %v", err) } }) t.Run("Schlüssel passt nicht zum Zertifikat", func(t *testing.T) { a, b := t.TempDir(), t.TempDir() LoadOrCreateServerCert(a) LoadOrCreateServerCert(b) data, _ := os.ReadFile(filepath.Join(b, serverCertFile)) os.WriteFile(filepath.Join(a, serverCertFile), data, 0o644) if _, err := LoadOrCreateServerCert(a); err == nil { t.Error("erwartet Fehler bei fremdem Zertifikat") } }) t.Run("Verzeichnis fehlt", func(t *testing.T) { if _, err := LoadOrCreateServerCert(filepath.Join(t.TempDir(), "fehlt")); err == nil { t.Error("erwartet Fehler") } }) } func TestServerTLSConfig_OnlyTLS13(t *testing.T) { sc, err := LoadOrCreateServerCert(t.TempDir()) if err != nil { t.Fatal(err) } cfg := ServerTLSConfig(sc) if cfg.MinVersion != tls.VersionTLS13 { t.Errorf("MinVersion = %x, erwartet TLS 1.3", cfg.MinVersion) } srv := httptest.NewUnstartedServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) srv.TLS = cfg srv.StartTLS() defer srv.Close() old := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, MaxVersion: tls.VersionTLS12, }}} if _, err := old.Get(srv.URL); err == nil { t.Error("TLS-1.2-Client wurde akzeptiert") } }