====== 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. |