software steht so weit.

jetzt build + docker build und testen
This commit is contained in:
br committed 2026-10-02 13:25:34 +02:00
1 parent 42f0c0accb
commit ca6cdcca63
90 files changed
+10298 -480

No files matched your search

+33 -134
View File
@@ -7,78 +7,10 @@ import (
"log"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
)
func newKnownServers(t *testing.T) *KnownServers {
t.Helper()
return &KnownServers{Path: filepath.Join(t.TempDir(), "known_servers")}
}
func TestKnownServers(t *testing.T) {
ks := newKnownServers(t)
sc, _ := LoadOrCreateServerCert(t.TempDir())
other, _ := LoadOrCreateServerCert(t.TempDir())
if _, ok, err := ks.Lookup("ingress:8443"); ok || err != nil {
t.Fatalf("leere Datei: ok=%v err=%v", ok, err)
}
if err := ks.Add("ingress:8443", sc.Fingerprint); err != nil {
t.Fatalf("Add: %v", err)
}
if fp, ok, err := ks.Lookup("ingress:8443"); !ok || err != nil || fp != sc.Fingerprint {
t.Errorf("Lookup = %q, %v, %v", fp, ok, err)
}
if err := ks.Add("ingress:8443", sc.Fingerprint); err != nil {
t.Errorf("gleichen Eintrag erneut hinzufügen: %v", err)
}
if err := ks.Add("ingress:8443", other.Fingerprint); !errors.Is(err, ErrFingerprintMismatch) {
t.Errorf("Pin überschreiben: erwartet ErrFingerprintMismatch, bekommen %v", err)
}
if err := ks.Add("anderer:8443", other.Fingerprint); err != nil {
t.Errorf("zweiter Server: %v", err)
}
data, _ := os.ReadFile(ks.Path)
if n := strings.Count(string(data), "ingress:8443"); n != 1 {
t.Errorf("ingress:8443 steht %d-mal in der Datei", n)
}
info, _ := os.Stat(ks.Path)
if info.Mode().Perm() != 0o600 {
t.Errorf("Dateirechte %o, erwartet 600", info.Mode().Perm())
}
}
func TestKnownServers_FileFormat(t *testing.T) {
ks := newKnownServers(t)
sc, _ := LoadOrCreateServerCert(t.TempDir())
content := "# Kommentar\n\n ingress:8443 " + string(sc.Fingerprint) + " \n"
os.WriteFile(ks.Path, []byte(content), 0o600)
if fp, ok, err := ks.Lookup("ingress:8443"); !ok || err != nil || fp != sc.Fingerprint {
t.Errorf("Lookup = %q, %v, %v", fp, ok, err)
}
for name, bad := range map[string]string{
"ein Feld": "ingress:8443\n",
"drei Felder": "ingress:8443 " + string(sc.Fingerprint) + " x\n",
"kaputter Fingerprint": "ingress:8443 abc\n",
} {
t.Run(name, func(t *testing.T) {
os.WriteFile(ks.Path, []byte(bad), 0o600)
if _, _, err := ks.Lookup("ingress:8443"); !errors.Is(err, ErrCorruptKnownServers) {
t.Errorf("erwartet ErrCorruptKnownServers, bekommen %v", err)
}
})
}
if err := ks.Add("mit leerzeichen", sc.Fingerprint); err == nil {
t.Error("Servername mit Leerzeichen akzeptiert")
}
}
// tlsServer startet einen Test-Server mit dem Zertifikat aus einem eigenen
// Datenverzeichnis und zählt, ob ein Request ihn erreicht.
func tlsServer(t *testing.T) (*httptest.Server, ServerCert, *atomic.Int32) {
@@ -98,96 +30,63 @@ func tlsServer(t *testing.T) (*httptest.Server, ServerCert, *atomic.Int32) {
return srv, sc, &hits
}
func get(t *testing.T, srv *httptest.Server, cfg *tls.Config) error {
func get(t *testing.T, url string, cfg *tls.Config) error {
t.Helper()
c := &http.Client{Transport: &http.Transport{TLSClientConfig: cfg}}
resp, err := c.Get(srv.URL)
resp, err := c.Get(url)
if err == nil {
resp.Body.Close()
}
return err
}
func TestClientTLS_FirstContactWithExpectedFingerprint(t *testing.T) {
func TestPinnedTLS_Matches(t *testing.T) {
srv, sc, hits := tlsServer(t)
ks := newKnownServers(t)
addr := srv.Listener.Addr().String()
if err := get(t, srv, ClientTLSConfig(addr, ks, sc.Fingerprint)); err != nil {
t.Fatalf("erster Kontakt mit korrektem Fingerprint: %v", err)
if err := get(t, srv.URL, PinnedTLSConfig(sc.Fingerprint)); err != nil {
t.Fatalf("passender Fingerprint: %v", err)
}
if hits.Load() != 1 {
t.Error("Request kam nicht an")
}
if fp, ok, _ := ks.Lookup(addr); !ok || fp != sc.Fingerprint {
t.Errorf("Pin nicht gespeichert: %q %v", fp, ok)
}
// Folgekontakt ohne Token-Fingerprint: der Pin genügt.
if err := get(t, srv, ClientTLSConfig(addr, ks, "")); err != nil {
t.Errorf("Folgekontakt mit Pin: %v", err)
}
// Der Pin hängt am Server, nicht an der Adresse: derselbe Server unter einer
// anderen Adresse (hier localhost statt 127.0.0.1) ist weiterhin vertrauenswürdig.
func TestPinnedTLS_AddressIndependent(t *testing.T) {
srv, sc, _ := tlsServer(t)
port := srv.Listener.Addr().(interface{ String() string }).String()
port = port[len("127.0.0.1"):]
if err := get(t, "https://localhost"+port, PinnedTLSConfig(sc.Fingerprint)); err != nil {
t.Errorf("andere Adresse, gleicher Server: %v", err)
}
}
// Kein blindes TOFU: Ohne Pin und ohne erwarteten Fingerprint wird nicht verbunden.
func TestClientTLS_NoBlindFirstContact(t *testing.T) {
srv, _, hits := tlsServer(t)
ks := newKnownServers(t)
addr := srv.Listener.Addr().String()
if err := get(t, srv, ClientTLSConfig(addr, ks, "")); !errors.Is(err, ErrUnknownServer) {
t.Errorf("erwartet ErrUnknownServer, bekommen %v", err)
}
if hits.Load() != 0 {
t.Error("Request hat den Server erreicht")
}
if _, ok, _ := ks.Lookup(addr); ok {
t.Error("unbekannter Server wurde gepinnt")
}
}
// Angreifer beim Erstkontakt: Er präsentiert sein eigenes Zertifikat, der
// Token enthält aber den Fingerprint des echten Servers.
func TestClientTLS_MITMOnFirstContact(t *testing.T) {
// Angreifer mit eigenem Zertifikat: Abbruch im Handshake, kein Request
// (also auch kein Token, keine Signatur) erreicht ihn.
func TestPinnedTLS_Mismatch(t *testing.T) {
mitm, _, hits := tlsServer(t)
real, _ := LoadOrCreateServerCert(t.TempDir())
ks := newKnownServers(t)
addr := mitm.Listener.Addr().String()
if err := get(t, mitm, ClientTLSConfig(addr, ks, real.Fingerprint)); !errors.Is(err, ErrFingerprintMismatch) {
if err := get(t, mitm.URL, PinnedTLSConfig(real.Fingerprint)); !errors.Is(err, ErrFingerprintMismatch) {
t.Errorf("erwartet ErrFingerprintMismatch, bekommen %v", err)
}
if hits.Load() != 0 {
t.Error("Request (inkl. Token) hat den Angreifer erreicht")
}
if _, ok, _ := ks.Lookup(addr); ok {
t.Error("Angreifer wurde gepinnt")
}
}
// Angreifer nach dem Erstkontakt: Der Pin gewinnt, auch wenn ein (neuer)
// Token-Fingerprint auf den Angreifer passen würde.
func TestClientTLS_PinWinsLater(t *testing.T) {
mitm, mitmCert, hits := tlsServer(t)
real, _ := LoadOrCreateServerCert(t.TempDir())
ks := newKnownServers(t)
addr := mitm.Listener.Addr().String()
if err := ks.Add(addr, real.Fingerprint); err != nil {
t.Fatal(err)
}
for _, expected := range []Fingerprint{"", mitmCert.Fingerprint} {
if err := get(t, mitm, ClientTLSConfig(addr, ks, expected)); !errors.Is(err, ErrFingerprintMismatch) {
t.Errorf("expected=%q: erwartet ErrFingerprintMismatch, bekommen %v", expected, err)
}
}
if hits.Load() != 0 {
t.Error("Request hat den Angreifer erreicht")
}
}
func TestClientTLSConfig_OnlyTLS13(t *testing.T) {
cfg := ClientTLSConfig("x:1", newKnownServers(t), "")
if cfg.MinVersion != tls.VersionTLS13 {
t.Errorf("MinVersion = %x, erwartet TLS 1.3", cfg.MinVersion)
// Kein blindes Vertrauen: ohne erwarteten Fingerprint keine Verbindung.
func TestPinnedTLS_NoFingerprint(t *testing.T) {
srv, _, hits := tlsServer(t)
if err := get(t, srv.URL, PinnedTLSConfig("")); !errors.Is(err, ErrUnknownServer) {
t.Errorf("erwartet ErrUnknownServer, bekommen %v", err)
}
if hits.Load() != 0 {
t.Error("Request erreichte den Server")
}
}
func TestPinnedTLSConfig_OnlyTLS13(t *testing.T) {
if cfg := PinnedTLSConfig("x"); cfg.MinVersion != tls.VersionTLS13 {
t.Errorf("MinVersion = %x", cfg.MinVersion)
}
}