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.
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:
Storehält das aktuell aktive Zertifikat hinterGetCertificate(wird voncrypto/tlsbei JEDEM neuen Handshake aufgerufen).Replace/ReplaceFromFilestauschen 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ärtetetls.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).UpgradeServerfü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 liefertNO.- Kehrseite jeweils mitgetestet:
TestStls_UpgradesConnectionAndAllowsLoginbzw.TestStartTLS_UpgradesConnectionAndAllowsLogin— nach echtem STLS/STARTTLS-Handschlag (realecrypto/tls-Clientverbindung) wird dieselbe Anmeldung akzeptiert. - SMTP:
TestStartTLS_UpgradesConnectionbelegt 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 —
- Erste TLS-Verbindung (echter Handschlag) aufbauen, bestätigen, dass sie Zertifikat A bekommt, Verbindung OFFEN halten.
store.Replace(certB)— Rotation im laufenden Betrieb.- Zweite, NEUE Verbindung aufbauen — bekommt nachweislich Zertifikat
B (
PeerCertificates[0].Rawverglichen). - 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
- Anmeldedaten werden ausschließlich über TLS oder nach erfolgreichem STARTTLS akzeptiert: durch Pflichtprüfung 2 belegt (IMAP LOGIN, POP3 PASS).
- 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). - 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.