====== 3. Gateway.Service — Auth-Erweiterungen für WvdS.Shell ======
//Stand: 2026-03-05//
Übergeordnet: [[de:int:wvdsshell:notes:01-siam:start|Auth-Architektur — Gesamtübersicht]]
Verwandt: [[de:int:wvdsshell:notes:01-siam:auth-shell|2. WvdS.Shell]] | [[de:int:wvdsshell:notes:01-siam:auth-database|4. Datenschicht]] | [[de:int:wvdsshell:notes:01-siam:auth-migration|5. Migration]]
===== Ausgangssituation =====
Das Gateway.Service existierte bereits als HTTP-REST-Gateway mit einer vollständigen Middleware-Pipeline — Logging, Rate-Limiting, HTTPS-Enforcement, Routing. Was fehlte, war ein vollständiges Auth-Subsystem für interaktive Benutzer. Die vorhandenen Modi (Basic, ApiKey) waren für Dienst-zu-Dienst-Kommunikation ausgelegt, nicht für Session-Management mit Token-Lifecycle und Mehrfaktor-Authentifizierung.
Ein Gateway ohne vollständiges Auth-Subsystem bedeutet: Entweder werden Credentials bei jedem Request übertragen — ein fundamentales Sicherheitsproblem — oder der Client muss selbst den Zustand verwalten, was die Architektur unnötig verkompliziert. JWT-Tokens lösen das: Ein einmaliger Auth-Vorgang erzeugt ein kurzlebiges Access Token und ein langlebiges Refresh Token. Alle nachfolgenden Requests sind stateless und tragen nur den signierten Token — kein Passwort, kein serverseitiger Session-State.
Die Wahl von ML-DSA-65 für die JWT-Signierung ist dabei keine akademische Entscheidung. Klassische Signaturverfahren (RSA, ECDSA) könnten durch Quantencomputer kompromittiert werden. Mit ML-DSA-65 als post-quanten-sicherem Verfahren bleibt die Plattform auch dann vertrauenswürdig, wenn klassische Kryptografie in absehbarer Zeit nicht mehr ausreicht — ohne dass bestehende Token-Strukturen nachträglich geändert werden müssten.
===== Ausgangslage =====
Gateway.Service hat eine vollständige Middleware-Pipeline und ein Auth-Enum mit
allen benötigten Modi — jedoch ist nur ein Teil davon implementiert.
==== Aktuell implementiert ====
^ Auth-Modus ^ Status ^ Anmerkung ^
| ''amNone'' | ✓ Aktiv | Nur für DEV (''$IFDEF DEBUG'') |
| ''amBasic'' | ✓ Aktiv | ConstantTimeEqual, CWE-208 |
| ''amApiKey'' | ✓ Aktiv | ConstantTimeEqual, Header konfigurierbar |
| ''amBearer'' | — Reserviert | Interface vorhanden, nicht implementiert |
| ''amCertificate'' | — Reserviert | Interface vorhanden, nicht implementiert |
| ''amWindows'' | — Reserviert | Interface vorhanden, nicht implementiert |
==== Benötigt für WvdS.Shell ====
Für die Shell-Integration müssen folgende Komponenten neu implementiert werden:
- **PFX Install-ID Validierung** (''amCertificate'')
- **Kerberos/SSPI Handler** (''amWindows'')
- **JWT Ausstellung und Validierung** (''amBearer'')
- **TOTP Service** (neu — für externen MFA-Flow)
- **Refresh Token Management** (neu)
- **Feature-Flags Endpoint** (neu — ENIVERSASYS-Anbindung)
===== Neue Endpoints =====
Alle neuen Auth-Endpoints unter ''%%/api/v1/auth/%%''.
Ein neuer Controller ''WvdS.Data.Gateway.Controller.Auth'' wird benötigt.
==== Übersicht ====
^ Verb ^ Route ^ Auth erforderlich ^ Beschreibung ^ Warum so gestaltet? ^
| POST | ''/api/v1/auth/kerberos'' | Nein (ist der Auth-Schritt selbst) | Kerberos Negotiate-Handshake | Der Handshake selbst ist der Auth-Vorgang — ein Token als Voraussetzung wäre ein Henne-Ei-Problem. |
| POST | ''/api/v1/auth/external'' | Nein | Username/Passwort → TOTP-Challenge | Kein direktes Token bei Passwort allein: Erst nach TOTP-Bestätigung gilt die Identität als gesichert. |
| POST | ''/api/v1/auth/totp/verify'' | Nein (Challenge-ID statt Token) | TOTP-Code einlösen → JWT | Die Challenge-ID ist temporär und wertlos ohne den passenden Code — kein Missbrauch ohne gültigen TOTP. |
| POST | ''/api/v1/auth/token/refresh'' | Refresh Token | Access Token erneuern | Silent Refresh im Hintergrund — der Benutzer erlebt keinen Logout, solange er aktiv ist. |
| GET | ''/api/v1/auth/totp/enroll'' | JWT Bearer | TOTP Enrollment-URI + QR generieren | Enrollment erfordert eine laufende Session — verhindert unbefugtes Einrichten fremder TOTP-Secrets. |
| DELETE | ''/api/v1/auth/totp/enroll'' | JWT Bearer | TOTP-Enrollment zurücksetzen | Nur der Kontoinhaber selbst (oder ein Admin) kann das TOTP-Geheimnis löschen. |
| GET | ''/api/v1/features'' | JWT Bearer | Feature-Flags für aktuelle Session | Feature-Flags außerhalb des JWT: Änderungen greifen sofort ohne Token-Neuausstellung. |
==== POST /api/v1/auth/kerberos ====
Kerberos Negotiate-Handshake (mehrstufig, wie HTTP Negotiate definiert):
Request:
POST /api/v1/auth/kerberos
Authorization: Negotiate
X-WvdS-Install-Id: ENIVERS-2026-001
Response (ggf. mehrstufig):
HTTP 401 Authorization: Negotiate ← Server-Challenge
HTTP 200 { "access_token": "...", "refresh_token": "...", "expires_in": 900 }
==== POST /api/v1/auth/external ====
Erster Schritt des externen MFA-Flows. Gibt bei gültigem Username/Passwort
eine ''challenge_id'' zurück — noch kein Token.
Request:
{ "username": "max.muster", "password": "...", "install_id": "ENIVERS-2026-001" }
Response 200:
{ "mfa_required": true, "challenge_id": "c7f3a..." }
Response 401:
{ "error": "invalid_credentials" }
Passwort wird nach Validierung sofort via ''FillChar'' zeroiert (CWE-316).
==== POST /api/v1/auth/totp/verify ====
Zweiter Schritt des externen MFA-Flows. Löst die ''challenge_id'' gegen
den TOTP-Code ein und gibt bei Erfolg die Token-Pair aus.
Request:
{ "challenge_id": "c7f3a...", "code": "482910" }
Response 200:
{ "access_token": "...", "refresh_token": "...", "expires_in": 900 }
Response 401:
{ "error": "invalid_code", "attempts_remaining": 2 }
==== POST /api/v1/auth/token/refresh ====
Silent Token Refresh. Refresh-Token wird im Request-Body oder als
''Authorization: Refresh '' übergeben.
Request:
{ "refresh_token": "..." }
Response 200:
{ "access_token": "...", "expires_in": 900 }
Response 401:
{ "error": "refresh_token_expired" }
==== GET /api/v1/features ====
Lädt die Feature-Flags für den authentifizierten User/die Installation.
Liest aktuell aus ENIVERSCAFM (''auth.*''-Namespace), zukünftig aus ENIVERSASYS.
Response 200:
{ "features": ["grids", "charts", "scheduling", "docking"] }
===== JWT — Ausstellung und Validierung =====
==== Signaturalgorithmus ====
JWT wird mit **ML-DSA-65** signiert — die Bibliothek ist im Gateway bereits
vorhanden (''WvdS.Security.Cryptography''). Kein externer Abhängigkeitsbedarf.
Standard-Header: ''{{"alg": "ML-DSA-65", "typ": "JWT"}}''
==== Payload-Struktur ====
{
"sub": "max.muster@corp.example.com",
"install_id": "ENIVERS-2026-001",
"auth_method": "windows",
"mfa": false,
"iat": 1741132800,
"exp": 1741133700,
"jti": "a3f9..."
}
Feature-Flags werden **nicht** im JWT mitgeführt — sie werden bei Bedarf
frisch vom ''/api/v1/features''-Endpoint geladen. Grund: Flags können sich
ändern ohne dass ein neues Token ausgestellt werden muss.
==== Token-Lebensdauer ====
^ Token ^ Lebensdauer ^ Speicherort (Client) ^ Begründung ^
| Access Token | 15 Minuten | RAM (Shell SessionManager) | Ein gestohlenes Token ist nach maximal 15 Minuten wertlos — ohne Admin-Eingriff. RAM statt Datei: Kein Risiko durch Dateisystem-Analyse; Neustart löscht das Token automatisch. |
| Refresh Token | 30 Tage | DPAPI SecretStorage | Bequeme Wiederanmeldung ohne täglichen Login. DPAPI bindet das Speichergeheimnis an das Windows-Benutzerkonto — andere Benutzer desselben PCs kommen nicht an das Token. |
| TOTP Challenge | 5 Minuten | Gateway-RAM (nach Ablauf gelöscht) | Ein abgefangener 6-stelliger Code ist nach 5 Minuten wertlos. Kein Disk-Zustand: Ein Serverneustart bereinigt alle offenen Challenges automatisch. |
==== Validierung in Middleware ====
''TWvdSGatewayAuthHandler'' wird erweitert: Bei ''amBearer'' wird der JWT-Header
extrahiert, ML-DSA-Signatur geprüft, ''exp''-Claim validiert und ''install_id''
gegen die PFX-Registrierung abgeglichen.
===== TOTP Service =====
==== Zuständigkeit ====
Die gesamte TOTP-Logik liegt im Gateway — die Shell liefert nur den Code
(6 Ziffern). Details: [[de:int:wvdsshell:notes:01-siam:auth-shell#totp_aufgabenteilung|2. Shell-Doku: TOTP Aufgabenteilung]].
==== Implementierungsdetails ====
**Algorithmus:** HMAC-SHA1 (RFC 6238) über OpenSSL (''WvdS.Security.Cryptography'') — kein neuer Crypto-Code nötig.
**Shared Secret Speicherung:**
* Pro User ein Base32-kodiertes Secret
* Gespeichert AES-256-GCM-verschlüsselt in der Gateway-Datenbank (CWE-256)
* Kein Plaintext auf Disk
**Validierungsfenster:** ±1 Zeitfenster (30 s × 3 = 90 s Toleranz für Uhrabweichung)
**Lockout-Policy:** Bestehender ''FailedAuthPerMinute''-Alert-Counter wird genutzt.
Nach 5 Fehlversuchen: Challenge ungültig, neuer ''POST /api/v1/auth/external'' nötig.
**Challenge-Lifetime:** 5 Minuten. Challenges in Gateway-RAM (''TDictionary''), kein DB-Roundtrip.
==== Enrollment Flow ====
Admin-seitig:
GET /api/v1/auth/totp/enroll (JWT des Admin-Users)
→ { "otpauth_uri": "otpauth://totp/...", "qr_base64": "..." }
→ Admin zeigt QR dem User (oder schickt URI per E-Mail)
Self-Service (Shell öffnet WebView):
Shell navigiert zu: https://gateway/auth/enroll?token=
→ Gateway zeigt HTML-Seite mit QR-Code
→ User scannt mit MS Authenticator
→ Enrollment-Bestätigung (User gibt ersten Code ein zum Verifizieren)
===== PFX Install-ID Validierung =====
Das Gateway prüft bei jedem Auth-Request ob die ''install_id'' aus dem
PFX registriert und aktiv ist.
==== Header-Konvention ====
X-WvdS-Install-Id: ENIVERS-2026-001
Dieser Header wird von der Shell bei **jedem** Request mitgesendet — nicht nur beim Auth-Request.
==== Validierungslogik ====
1. X-WvdS-Install-Id Header vorhanden?
2. install_id in Deployment-Tabelle (DB) vorhanden und aktiv?
3. Zugehöriges Ablaufdatum noch nicht überschritten?
4. → Validierung OK: weiter mit Auth-Flow
5. → Validierung FAIL: HTTP 403 "unlicensed installation"
Eine neue Tabelle (z. B. ''WVDS_DEPLOYMENTS'') in der Gateway-Datenbank speichert:
^ Spalte ^ Typ ^ Beschreibung ^
| ''INSTALL_ID'' | VARCHAR(64) | PK — z. B. ''ENIVERS-2026-001'' |
| ''TENANT_NAME'' | VARCHAR(256) | Anzeigename |
| ''LICENSE_TIER'' | VARCHAR(32) | ''professional'' / ''enterprise'' |
| ''VALID_UNTIL'' | DATE | Ablaufdatum (aus PFX synchronisiert) |
| ''DOMAINS'' | VARCHAR(512) | Erlaubte AD-Domänen (Komma-getrennt) |
| ''ACTIVE'' | BIT | Manuell deaktivierbar |
===== Kerberos/SSPI Handler =====
==== Standalone-Modus ====
Wenn Gateway als Windows Service läuft (nicht hinter IIS/NGINX):
* ''AcceptSecurityContext'' aus ''Secur32.dll'' (Windows SSPI API)
* HTTP ''Authorization: Negotiate'' Header — mehrstufiger Handshake
* Nach Erfolg: Identität via ''QueryContextAttributes(SECPKG_ATTR_NAMES)'' lesen
* → JWT für diese Identität ausstellen
==== FastCGI-Modus (hinter IIS) ====
IIS übernimmt Windows Auth vollständig:
* IIS sendet ''LOGON_USER'' als CGI-Umgebungsvariable
* Gateway-FastCGI-Adapter (''WvdS.Data.Gateway.FastCGI.Adapter'') liest ''LOGON_USER''
* Kein ''AcceptSecurityContext'' im Gateway nötig — IIS hat bereits validiert
* Direkt JWT ausstellen
> **Empfehlung:** FastCGI hinter IIS für Corpnet-Deployments.
> Standalone SSPI für Self-hosted Szenarien (kleinere Installationen).
===== Middleware-Änderungen =====
==== Bestehende Pipeline (9 Stufen) ====
1. TWvdSMetricsMiddleware
2. TWvdSLoggingMiddleware
3. TWvdSHttpsEnforcementMiddleware
4. TWvdSRequestSizeMiddleware
5. TWvdSRateLimitMiddleware
6. TWvdSGatewayAuthHandler ← Erweiterung: amBearer + amWindows
7. TWvdSPayloadEncryptionMiddleware
8. TWvdSCompressionMiddleware
9. MapControllers
==== Geplante Erweiterungen ====
^ Stufe ^ Komponente ^ Änderung ^
| 3.5 | ''TWvdSInstallIdMiddleware'' (neu) | ''X-WvdS-Install-Id'' prüfen, vor Auth |
| 6 | ''TWvdSGatewayAuthHandler'' | ''amBearer'': JWT ML-DSA validieren + Claims extrahieren |
| 6 | ''TWvdSGatewayAuthHandler'' | ''amWindows'': SSPI Negotiate oder LOGON_USER (FastCGI) |
''TWvdSInstallIdMiddleware'' greift vor der eigentlichen Auth — ein Request ohne
gültige Install-ID wird mit HTTP 403 abgelehnt, bevor Credentials geprüft werden.
===== Neue Units =====
src/
controllers/
WvdS.Data.Gateway.Controller.Auth.pas ← /api/v1/auth/*, /api/v1/features
core/
WvdS.Data.Gateway.Auth.JwtService.pas ← JWT Ausstellung (ML-DSA-65), Validierung
WvdS.Data.Gateway.Auth.TotpService.pas ← Shared Secret, Challenge, HMAC-SHA1 (OpenSSL)
WvdS.Data.Gateway.Auth.KerberosHandler.pas ← SSPI AcceptSecurityContext (Windows-only)
WvdS.Data.Gateway.Auth.CertValidator.pas ← Install-ID gegen DB prüfen
WvdS.Data.Gateway.Auth.RefreshStore.pas ← Refresh Token (gehashed, DB-backed)
middleware/
WvdS.Data.Gateway.Middleware.InstallId.pas ← X-WvdS-Install-Id Middleware
Alle neuen Units im ''src/core/'' und ''src/middleware/'' — passend zur
bestehenden Schichtenstruktur.
===== Security — CWE-Ergänzungen =====
Zusätzlich zu den bestehenden 22 CWEs kommen durch die Auth-Erweiterung:
^ CWE ^ Beschreibung ^ Implementierung ^
| CWE-287 | Improper Authentication | Kerberos-Ticket muss vollständig validiert sein (nicht nur Presence-Check) |
| CWE-290 | Token Spoofing | JWT-Signatur (ML-DSA-65) wird bei jedem Request geprüft |
| CWE-613 | Insufficient Session Expiration | Refresh Token: 30 Tage, serverseitig revokierbar |
| CWE-620 | Unverified Password Change | TOTP-Enrollment erfordert bestätigten ersten Code |
| CWE-384 | Session Fixation | ''jti'' (JWT ID) einmalig, Replay via ''jti''-Blacklist |
===== Design-Entscheidungen (protokolliert) =====
^ Thema ^ Entscheidung ^ Begründung ^
| JWT-Signatur | ML-DSA-65 — Bibliothek bereits vorhanden, kein neuer Crypto-Code | Post-quanten-sicher: Klassische Signaturverfahren (RSA, ECDSA) können durch Quantencomputer gebrochen werden. ML-DSA-65 ist heute zukunftssicher — ohne spätere Architekturänderung. |
| TOTP-Crypto | HMAC-SHA1 via OpenSSL — kein neuer Crypto-Code | RFC 6238 definiert HMAC-SHA1 als TOTP-Standard. Vorhandene OpenSSL-Integration nutzen statt neuen Crypto-Code einzuführen — weniger Fehlerquellen. |
| Kerberos Standalone | ''Secur32.dll'' SSPI direkt; FastCGI-Modus bevorzugt hinter IIS | FastCGI hinter IIS: IIS übernimmt Windows Auth vollständig, das Gateway braucht keinen SSPI-Code. Standalone SSPI bleibt als Fallback für selbst-gehostete Umgebungen ohne IIS. |
| Install-ID Transport | HTTP-Header ''X-WvdS-Install-Id'' bei jedem Request | Jeder API-Aufruf trägt die Installations-ID — nicht nur der Login. Das Gateway kann eine deaktivierte Lizenz sofort bei jedem Request abweisen, nicht erst beim nächsten Login. |
| Feature-Flags | Nicht im JWT — separater Endpoint, frisch laden | Flags im JWT würden eingefroren bleiben bis zum Token-Ablauf. Frisch geladen: Lizenzänderungen oder Feature-Aktivierungen greifen ohne Token-Neuausstellung sofort. |
| Refresh Token Storage | Gehashed (SHA-256) in DB — nie im Klartext gespeichert | Ein Datenbankdump gibt keinen verwertbaren Refresh Token preis. Nur der Client kennt das Klartext-Token — das Gateway kann nur prüfen, nicht rekonstruieren. |
| TOTP Enrollment | Admin-seitig (primär) oder Self-Service WebView (BYOD) | Admin-seitig: IT kontrolliert den Prozess bei verwalteten Geräten. Self-Service: BYOD-Nutzer können eigenständig einrichten ohne IT-Eingriff — beide Wege über denselben Gateway-Enrollment-Endpoint. |