opnsense-portal/internal/auth/dial_test.go
Carsten Abele 11a3ec2e5f feat(auth): ad.max_tls_version zum Deckeln der angebotenen TLS-Version
Alte Schannel-Stacks brechen an einem TLS-1.3-ClientHello kommentarlos ab:
sie nehmen die TCP-Verbindung an, lesen den ClientHello und setzen zurueck,
ohne ein Zertifikat zu schicken. Herunterhandeln hilft dann nicht — TLS 1.3
darf gar nicht erst angeboten werden.

max_tls_version (Default 1.3) deckelt die Hoechstversion; die Validierung
lehnt eine Hoechstversion unterhalb der Mindestversion ab.

check erkennt zusaetzlich zurueckgesetzte Verbindungen und nennt die beiden
plausiblen Ursachen: fehlendes LDAPS-Zertifikat auf dem Server oder
TLS-1.3-Inkompatibilitaet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
2026-08-14 13:36:03 +02:00

128 lines
4 KiB
Go

package auth
import (
"crypto/tls"
"testing"
)
func TestParseTLSVersion(t *testing.T) {
cases := map[string]uint16{
"1.0": tls.VersionTLS10,
"1.1": tls.VersionTLS11,
"1.2": tls.VersionTLS12,
"1.3": tls.VersionTLS13,
}
for in, want := range cases {
if got := ParseTLSVersion(in); got != want {
t.Errorf("ParseTLSVersion(%q) = %#04x, want %#04x", in, got, want)
}
}
// Unbekanntes oder leeres Eingaben fallen auf das Sichere zurück.
for _, in := range []string{"", "1.4", "unsinn", "TLSv1"} {
if got := ParseTLSVersion(in); got != tls.VersionTLS12 {
t.Errorf("ParseTLSVersion(%q) = %#04x, want TLS 1.2", in, got)
}
}
}
func TestTLSConfigDefaultsToTLS12(t *testing.T) {
cfg, err := tlsConfigFor("dc01.firma.local", "", 0, 0)
if err != nil {
t.Fatal(err)
}
if cfg.MinVersion != tls.VersionTLS12 {
t.Errorf("MinVersion = %#04x, want TLS 1.2", cfg.MinVersion)
}
if cfg.ServerName != "dc01.firma.local" {
t.Errorf("ServerName = %q", cfg.ServerName)
}
if cfg.InsecureSkipVerify {
t.Error("die Zertifikatspruefung darf fuer LDAP niemals abschaltbar sein")
}
if cfg.CipherSuites != nil {
t.Error("bei TLS 1.2+ sollen Gos sichere Vorgaben gelten, keine eigene Liste")
}
if cfg.MaxVersion != tls.VersionTLS13 {
t.Errorf("MaxVersion = %#04x, want TLS 1.3", cfg.MaxVersion)
}
}
func TestTLSConfigCapsMaxVersion(t *testing.T) {
// Alte Schannel-Stacks brechen an einem TLS-1.3-ClientHello kommentarlos ab.
// Dann darf 1.3 gar nicht erst angeboten werden.
cfg, err := tlsConfigFor("dc01.firma.local", "", tls.VersionTLS12, tls.VersionTLS12)
if err != nil {
t.Fatal(err)
}
if cfg.MaxVersion != tls.VersionTLS12 {
t.Errorf("MaxVersion = %#04x, want TLS 1.2", cfg.MaxVersion)
}
if cfg.MinVersion != tls.VersionTLS12 {
t.Errorf("MinVersion = %#04x, want TLS 1.2", cfg.MinVersion)
}
}
func TestTLSConfigMaxNeverBelowMin(t *testing.T) {
// Eine widerspruechliche Kombination darf keine unmoegliche Konfiguration
// erzeugen; die Validierung faengt sie ohnehin vorher ab.
cfg, err := tlsConfigFor("dc01", "", tls.VersionTLS12, tls.VersionTLS10)
if err != nil {
t.Fatal(err)
}
if cfg.MaxVersion < cfg.MinVersion {
t.Errorf("MaxVersion %#04x < MinVersion %#04x", cfg.MaxVersion, cfg.MinVersion)
}
}
func TestTLSConfigLegacyAddsCBCSuites(t *testing.T) {
cfg, err := tlsConfigFor("dc01.firma.local", "", tls.VersionTLS10, 0)
if err != nil {
t.Fatal(err)
}
if cfg.MinVersion != tls.VersionTLS10 {
t.Errorf("MinVersion = %#04x, want TLS 1.0", cfg.MinVersion)
}
if cfg.InsecureSkipVerify {
t.Error("auch im Legacy-Modus bleibt die Zertifikatspruefung aktiv")
}
has := func(id uint16) bool {
for _, got := range cfg.CipherSuites {
if got == id {
return true
}
}
return false
}
// Die CBC-SHA256-Suite fuehrt Go als unsicher und bietet sie sonst nicht an.
if !has(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) {
t.Error("ECDHE_RSA_AES_128_CBC_SHA256 fehlt in der Legacy-Liste")
}
// Die CBC-SHA1-Suiten eines 2012 R2 stecken schon in Gos Vorgaben.
if !has(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) {
t.Error("ECDHE_RSA_AES_256_CBC_SHA fehlt — die braucht ein 2012 R2 unter TLS 1.0")
}
// Die modernen Suiten duerfen im Legacy-Modus nicht wegfallen.
if !has(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) {
t.Error("die modernen Suiten duerfen im Legacy-Modus nicht wegfallen")
}
// Kein reiner RSA-Schluesselaustausch: keine Forward Secrecy, und Go
// filtert die Suiten ohnehin weg.
for _, id := range []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA} {
if has(id) {
t.Errorf("Suite %#04x ohne Forward Secrecy gehoert nicht in die Liste", id)
}
}
// RC4 und 3DES niemals.
for _, id := range []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA} {
if has(id) {
t.Errorf("veraltete Suite %#04x darf nie angeboten werden", id)
}
}
}
func TestTLSConfigRejectsBadCAFile(t *testing.T) {
if _, err := tlsConfigFor("dc01", "/gibt/es/nicht.pem", tls.VersionTLS12, 0); err == nil {
t.Fatal("fehlende CA-Datei muss abgelehnt werden")
}
}