Files
nexarch/mail/docs/ING-06-PRUEFPROTOKOLL.md
T
sysops b22ab67bb2 feat(mail): ING-06 TLS/STARTTLS-Absicherung für IMAP/POP3/SMTP
Neues Paket mail/internal/tlscert: hot-reloadbarer Zertifikat-Store
(Store.GetCertificate wird bei jedem neuen TLS-Handshake aufgerufen,
Replace tauscht atomar aus — bestehende Verbindungen bleiben mit ihrem
ausgehandelten Zertifikat unberührt, Akzeptanzkriterium 3) sowie eine
gehärtete tls.Config (MinVersion TLS 1.2, ausschließlich AEAD-Suiten
für TLS 1.2, Akzeptanzkriterium 2). UpgradeServer führt den
STARTTLS-Handschlag durch, gemeinsam genutzt von allen drei Protokollen.

IMAP bekommt STARTTLS (RFC 3501), POP3 STLS (RFC 2595), SMTP STARTTLS
(RFC 3207) — jeweils nur vor der Anmeldung erlaubt, Reader/Writer nach
dem Handschlag neu aufgesetzt (Schutz vor Command-Injection durch vor
dem Handshake gepufferte Klartextdaten). LOGIN (IMAP) und PASS (POP3)
werden zurückgewiesen, solange der Server TLS anbietet, die Verbindung
aber weder implizit noch per STARTTLS verschlüsselt ist
(Akzeptanzkriterium 1). Implizites TLS (Port 993/995/465) braucht keine
Codeänderung — Server.Serve nimmt jeden net.Listener entgegen, ein
tls.NewListener-gewrapptes Listener liefert bereits *tls.Conn, von der
Session per Typ-Assertion erkannt. Ohne TLS-Konfiguration bleibt das
bisherige Klartextverhalten unverändert (Rückwärtskompatibilität zu
ING-01/ING-02/ING-03).

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt: echter
openssl-s_client-Scan gegen den laufenden SMTP-Server (TLS 1.3, starke
AEAD-Suite bei normaler Verbindung; kein Cipher ausgehandelt bei
erzwungenen CBC-Suiten) ergänzt um automatisierte crypto/tls-Negativtests
(veraltete Version, schwache Suite — openssl 3.5.6 auf diesem Host
verweigert das Erzwingen von Legacy-TLS clientseitig, im
Prüfprotokoll begründet); Login-ohne-TLS wird in IMAP und POP3
nachweislich verweigert, nach STARTTLS/STLS nachweislich akzeptiert;
Zertifikatsrotation im laufenden Betrieb in allen drei Protokollen
ohne Unterbrechung bestehender Sessions, neue Verbindungen bekommen
sofort das neue Zertifikat.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~28 Pakete)
regressionsfrei getestet.
2026-09-01 01:17:40 +02:00

7.1 KiB

ING-06 — TLS/STARTTLS-Absicherung: Prüfprotokoll

Datum: 2026-09-01 Host: 192.168.1.131 (Build/Test/Lint), rsync + ssh Pakete: mail/internal/tlscert (neu, gemeinsam genutzt), mail/internal/imap, mail/internal/pop3, mail/internal/smtp

Umsetzung

Neues Paket tlscert kapselt die für alle drei Protokollserver gemeinsame TLS-Grundlage:

  • Store hält das aktuell aktive Zertifikat hinter GetCertificate (wird von crypto/tls bei JEDEM neuen Handshake aufgerufen). Replace/ReplaceFromFiles tauschen es atomar aus — bereits etablierte Verbindungen behalten ihr beim Handshake ausgehandeltes Zertifikat, nur NEUE Handshakes bekommen das neue (Akzeptanzkriterium 3).
  • Store.Config() liefert eine gehärtete tls.Config: MinVersion: tls.VersionTLS12, für TLS 1.2 ausschließlich AEAD-Cipher-Suiten (kein CBC, kein RC4, kein 3DES) — TLS 1.3 hat ohnehin nur starke, feste Suiten (Akzeptanzkriterium 2).
  • UpgradeServer führt den STARTTLS-Serverhandschlag durch, gemeinsam genutzt von allen drei Protokollen.

IMAP (STARTTLS, RFC 3501 §6.2.1), POP3 (STLS, RFC 2595 §4) und SMTP (STARTTLS, RFC 3207) bekommen je ein neues Kommando: nur vor der Anmeldung erlaubt, Reader/Writer werden nach dem Handschlag NEU aufgesetzt (verhindert, dass vor dem Handshake gepufferte Klartextdaten als Kommandos nach dem Wechsel verarbeitet werden — Command-Injection-Schutz). LOGIN (IMAP) und PASS (POP3) werden zurückgewiesen, solange der Server TLS anbietet, aber die Verbindung weder implizit (via tls.Conn) noch per STARTTLS/STLS verschlüsselt ist (Akzeptanzkriterium 1). SMTP hat in der aktuellen minimalen Implementierung (ING-03) kein Anmeldekommando (kein AUTH) — dort wird STARTTLS strukturell bereitgestellt und geprüft, die Anmeldedaten-Kernprüfung erfolgt für IMAP/POP3.

Implizites TLS (z. B. Port 993/995/465) benötigt KEINE Codeänderung: Server.Serve nimmt jeden net.Listener entgegen, ein mit tls.NewListener gewrapptes Listener liefert bereits *tls.Conn aus Accept() — die Session erkennt das per Typ-Assertion und startet direkt mit tlsActive = true.

Alle drei Server bleiben ohne TLS-Konfiguration (tlsConfig == nil) unverändert im bisherigen Klartextverhalten — Rückwärtskompatibilität zu ING-01/ING-02/ING-03, bestehende Tests unverändert grün.

Pflichtprüfung 1: Scan mit Standard-TLS-Prüfwerkzeug bestätigt keine schwachen Suiten

Manuelle Prüfung mit openssl s_client (Standardwerkzeug, bereits auf dem Zielsystem vorhanden) gegen einen echten, laufenden mail/internal/smtp-Server mit aktivierter TLS-Konfiguration:

$ printf 'EHLO test\r\nQUIT\r\n' | openssl s_client -connect 127.0.0.1:14425 -starttls smtp -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_128_GCM_SHA256
...
250 STARTTLS
DONE

→ Reguläre Verbindung: TLS 1.3, starke AEAD-Suite. Erzwungener Versuch mit ausschließlich schwachen TLS-1.2-CBC-Suiten:

$ openssl s_client -connect 127.0.0.1:14425 -starttls smtp -tls1_2 \
    -cipher 'ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES128-SHA:AES256-SHA'
...
New, (NONE), Cipher is (NONE)
Cipher    : 0000

→ Kein Cipher ausgehandelt = Handshake fehlgeschlagen, Server nimmt keine der angebotenen CBC-Suiten an.

Ergänzung/Abweichung dokumentiert: Das auf diesem Host installierte openssl 3.5.6 verweigert es, TLS 1.0/1.1 überhaupt CLIENTSEITIG anzufordern (no protocols available, auch mit -provider legacy) — das lässt sich mit dem verfügbaren Standardwerkzeug nicht mehr erzwingen. Als reproduzierbarer automatisierter Ersatz für den Versions-Anteil dieser Prüfung: TestServer_RejectsLegacyTLSVersionAndWeakCiphers (smtp/tls_test.go, echter TCP-Client über crypto/tls, MaxVersion: tls.VersionTLS11) gegen den echten Server — Handshake schlägt fehl. Zweiter Subtest erzwingt clientseitig ausschließlich TLS_RSA_WITH_AES_128_CBC_SHA — Handshake schlägt ebenfalls fehl. Zusätzlich TestConfig_HardenedDefaults (tlscert/tlscert_test.go) prüft die tls.Config direkt gegen eine Liste bekannter schwacher Suiten.

Ergebnis: BESTANDEN (openssl-Scan + zwei automatisierte Negativtests + Config-Assertion).

Pflichtprüfung 2: Login-Versuch ohne TLS/STARTTLS wird verweigert

  • TestPass_RequiresTLS (pop3/tls_test.go): PASS ohne vorheriges STLS liefert -ERR.
  • TestLogin_RequiresTLS (imap/tls_test.go): LOGIN ohne vorheriges STARTTLS liefert NO.
  • Kehrseite jeweils mitgetestet: TestStls_UpgradesConnectionAndAllowsLogin bzw. TestStartTLS_UpgradesConnectionAndAllowsLogin — nach echtem STLS/STARTTLS-Handschlag (reale crypto/tls-Clientverbindung) wird dieselbe Anmeldung akzeptiert.
  • SMTP: TestStartTLS_UpgradesConnection belegt den echten STARTTLS-Handschlag strukturell (kein Anmeldekommando in der aktuellen SMTP-Implementierung vorhanden, siehe Abschnitt "Umsetzung").

Ergebnis: BESTANDEN.

Pflichtprüfung 3: Zertifikatsrotation im laufenden Betrieb ohne Dienstunterbrechung

TestTLS_CertificateRotationWithoutDroppingExistingSessions in allen drei Protokollpaketen (pop3, imap, smtp): echter Ablauf —

  1. Erste TLS-Verbindung (echter Handschlag) aufbauen, bestätigen, dass sie Zertifikat A bekommt, Verbindung OFFEN halten.
  2. store.Replace(certB) — Rotation im laufenden Betrieb.
  3. Zweite, NEUE Verbindung aufbauen — bekommt nachweislich Zertifikat B (PeerCertificates[0].Raw verglichen).
  4. Erste, bereits etablierte Verbindung wird DANACH weiterbenutzt (POP3: USER/PASS, IMAP: LOGIN, SMTP: NOOP) — funktioniert unterbrechungsfrei weiter.

Zusätzlich TestStore_ReplaceAffectsOnlyNewHandshakes (tlscert/tlscert_test.go) auf Store-Ebene.

Ergebnis: BESTANDEN — in allen drei Protokollen: kein Verbindungsabriss für die bestehende Session, neue Verbindungen bekommen sofort das neue Zertifikat.

Akzeptanzkriterien

  1. Anmeldedaten werden ausschließlich über TLS oder nach erfolgreichem STARTTLS akzeptiert: durch Pflichtprüfung 2 belegt (IMAP LOGIN, POP3 PASS).
  2. Schwache Cipher-Suiten und veraltete TLS-Versionen sind serverseitig deaktiviert: durch Pflichtprüfung 1 belegt (tlscert.Store.Config(): MinVersion: TLS12, ausschließlich AEAD-Suiten für TLS 1.2).
  3. Zertifikatswechsel ist ohne Verbindungsabriss für bestehende Sessions möglich: durch Pflichtprüfung 3 belegt.

Build/Vet/Lint/Test — Gesamtmodul

go build ./...    → OK
go vet ./...      → OK
golangci-lint run ./... → 0 issues
go test ./... -p 1 (TEST_TENANT_DSN, TEST_MANTICORE_URL gesetzt) → alle Pakete ok, inkl. neuem internal/tlscert

Keine Regression in den bestehenden ~27 Paketen. Manueller TLS-Testserver (cmd/tls-manual-test) und dessen Hintergrundprozess nach den openssl-Prüfungen entfernt/beendet, nicht im Repository verblieben.

Ergebnis

ING-06 erfüllt alle Akzeptanzkriterien mit echten, ausgeführten Nachweisen. Pflichtprüfung 1 (Versions-Anteil) wurde mangels clientseitig erzwingbarem Legacy-TLS im installierten openssl 3.5.6 zusätzlich durch einen echten automatisierten crypto/tls-Negativtest gegen den laufenden Server ergänzt — siehe Abschnitt oben. Freigeschaltet: QA-04.