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.
166 lines
7.1 KiB
Markdown
166 lines
7.1 KiB
Markdown
# 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.
|