Externes Architektur-, Security- & Code-Audit: TLS-Gate (NativePHP RC1.0)
Datum: 18.10.2026
Geprüftes Repository: TLS-NativePHP_v1 (TLS-NativePHP_v1_gemini_rmx)
Geprüfter Stand: RC1.0 (Produktivbetrieb auf Docker-VM / Node, Vorbereitung für Rust-Rewrite)
Prüfumfang: Vollständiger Quellcode (src/, public/, bin/, database/, deploy/, tests/)
1. Executive Summary & Gesamturteil
Fragestellung: Ist das solider Code, der unter realen Bedingungen standhält, und taugt er als Fundament für den geplanten Rewrite in Rust?
Gesamturteil: JA — Außergewöhnlich solide (0 Critical / 0 High / 1 Medium / 5 Low-Info Befunde).
Diese Codebasis hat nichts mit typischem Legacy-PHP oder überladenen Framework-Projekten gemein. Sie ist als Zero-Dependency, Fail-Closed, Local-First Systemprogramm in PHP 8 geschrieben und setzt die Prinzipien aus 42_OZM.md (Epistemologische Hygiene, kein Raten, keine stillen Fallbacks) kompromisslos um.
- Belastbarkeit im Realbetrieb: Der Code hält im produktiven Einsatz problemlos stand. Kryptographie (Libsodium KDF + XChaCha20-Poly1305 mit Associated Data), Authentifizierung (Argon2id + Constant-Time Dummy-Hash + TOTP mit Replay-Sperre), Datenbank-Isolation (
tlsgate_ddlvs.tlsgate_app, 100 % Prepared Statements, Append-Only-Trigger) und Web-Härtung (strikte CSP ohne Inline-JS, CSRF-Schutz,__Host--Cookies) bewegen sich auf Referenzniveau. - Eignung für den Rust-Rewrite: Die Architektur ist bereits heute wie ein Rust-Programm geschnitten (
final classohne Vererbung, Pure Functions für Guard/Normalisierung, deklarative JSON-Verträge für State-Machines und Katalog, binärkompatiblestg1:-Verschlüsselungsformat). Ein späterer Rust-Service kann direkt gegen dieselbe MariaDB und dieselben verschlüsselten Secrets arbeiten.
Die im Audit identifizierten Härtungspunkte (SOAP-Stream-Timeouts/TLS, atomares TOTP-Step-Update gegen Race-Conditions, FQDN-Validierung beim Live-Cross-Check, $_POST-Entkopplung und fehlende Kategorie-Filter im Audit-Log-UI) wurden im Zuge dieses Audits direkt als Patch eingespielt.
2. Positiv-Audit: Architektur- & Security-Stärken
2.1 Kryptographie & Secret-Management (src/Security/Secrets.php)
- Schlüsselableitung (KDF): Ein einziger 32-Byte Root-Key (
TLSGATE_MASTER_KEYbzw. Keyfile) leitet übersodium_crypto_kdf_derive_from_key(KontextTLSGATE1) strikt getrennte Subkeys pro Zweck (totp,soap,outbox,ca,netbox) undkey_idab. - AEAD mit Context-Binding: Jeder Chiffretext (
tg1:+ Version +key_id+ 24-Byte Nonce +XChaCha20-Poly1305) bindet den logischen Eigentümer über die Associated Data (tg1|<purpose>|<bind>, z. B.user:<id>oder<group_id>|<label>). Selbst bei kompromittierter Datenbank kann ein Angreifer keinen verschlüsselten Blob in eine andere Zeile oder Gruppe kopieren. - Speicher- und Log-Hygiene: Abgeleitete Subkeys werden im
finally-Block persodium_memzero()aus dem RAM gelöscht. Die Value-ObjekteSoapCredentialundNetBoxCredentialüberschreiben__debugInfo(),__toString(),__serialize()und__clone(), sodass Zugangsdaten niemals in Stacktraces, Logs oder Sessions gelangen. - Key-Rotation im laufenden Betrieb: Transaktionales
key rotateundkey migrateüber alle fünf Secret-Stores hinweg, abgesichert durchtests/test_secrets.sh.
2.2 Authentifizierung, MFA & Session-Führung (src/Auth/)
- Schutz gegen Timing-Enumeration:
Auth::attemptPassword()prüft bei nicht existierenden E-Mail-Adressen gegenAuth::dummyHash()(Argon2id mit identischen Kostenparametern). - Privilegiengrenzen & Session-Hardening: Cookie
__Host-tlsgate(Secure,HttpOnly,SameSite=Strict),session_regenerate_id(true)bei jedem Phasenübergang (Passwort $\rightarrow$ MFA $\rightarrow$ Login) und Live-Abfrage vonactiveundroleaus der Datenbank bei jedem Request. - Verifizierte Rollen-Matrix:
tests/test_roles.shprüft alle vier Rollen (readonly,manager,security,admin) gegen jeden Endpunkt und schlägt fehl, sobald eine neue Datei inpublic/*.phpauftaucht, die nicht in der Testmatrix deklariert ist.
2.3 Datenbank-Design & Revisionssicherheit (database/schema.mysql.sql, src/Storage/)
- 100 % echte Prepared Statements:
PDO::ATTR_EMULATE_PREPARES => falseundPDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTIONinDb.php. - Zwei-Konten-Modell:
tlsgate_ddlwird ausschließlich fürbin/tg migrategenutzt; der Web-/CLI-Pool läuft untertlsgate_app(optional passwortlos per Unix-SocketDB_AUTH=socket) ohneDROP-,ALTER- oderTRIGGER-Rechte. - Unveränderliche Event-Tabellen:
audit_log,booking_events,catalog_eventsundregistrar_inventory_eventsblockierenUPDATEundDELETEauf Datenbankebene per Trigger (SIGNAL SQLSTATE '45000'). Jeder Audit-Eintrag trägt zudem den exakten Build-/Git-Stempel (Config::buildStamp()).
2.4 Fail-Closed Synchronisation & Betrieb (src/Orders/Inventory.php, src/Netbox/, deploy/)
- Keine stillen Mock-Daten: Fällt SOAP oder NetBox aus, meldet das System explizit
source => 'unavailable'statt Beispieldaten unterzuschieben. Sample-Seeds sind kryptographisch/logisch getrennt und nur explizit per CLI (--allow-sample) ladbar. - Mass-Deletion Circuit-Breaker: Sowohl
Inventory::sync()als auchDeviceMirror::sync()verweigern den Abgleich automatisch, wenn ab einem Mindestbestand (MASS_GONE_MIN = 10) plötzlich mehr als 50 % der Einträge fehlen (Schutz vor kaputten Upstream-Responses). - Geprüfte Backups:
deploy/node/bend.shfolgt dem Grundsatz „Ein Backup zählt erst, wenn es eingespielt wurde“ und verifiziert jeden Datenbank-Dump unmittelbar in einer temporären Prüfdatenbank inkl. Trigger-Test.
3. Detail-Befunde & Eingespielte Patches
| ID | Schweregrad | Komponente | Befund | Status |
|---|---|---|---|---|
| AUD-01 | Medium | src/Ssl/SslHandler.php src/Security/CredentialStore.php |
SoapClient wurde ohne connection_timeout, ohne HTTP-Stream-Timeouts und ohne Laufzeit-Prüfung von TLSGATE_SOAP_HOSTS initialisiert. Bei hängendem Registrar drohte eine Blockade aller PHP-FPM-Worker (pm.max_children = 5). |
Behoben (Patch) |
| AUD-02 | Low / Medium | src/Auth/Auth.php |
TOCTOU-Race-Condition bei der TOTP-Replay-Prüfung: totp_last_step wurde per SELECT gelesen und danach ohne atomare WHERE-Bedingung per UPDATE geschrieben. |
Behoben (Patch) |
| AUD-03 | Low / Medium | src/Ssl/SslHandler.php |
crossCheck() öffnete ssl://$domain:443 direkt mit dem String aus der SOAP-Antwort ohne vorherige FQDN-/IP-Literal-Validierung und ohne expliziten SNI-Hostnamen. |
Behoben (Patch) |
| AUD-04 | Low | src/Ssl/SslHandler.php |
Direkte $_POST[...]-Zugriffe ohne ?? '' in genCsr(), sslCreate() und sslGetDCVList() konnten bei unvollständigen Requests PHP-8-Warnings (Undefined array key) erzeugen. |
Behoben (Patch) |
| AUD-05 | Low | public/auditlog.php |
Die Kategorie-Whitelist $allowedCat kannte nur auth., ssl., admin., cli. — neuere Audit-Präfixe (web., booking., catalog., module.) waren nicht als Filter wählbar. |
Behoben (Patch) |
| AUD-06 | Info / Ops | src/Orders/Inventory.php src/Ssl/SslHandler.php |
sslListExtended() fragt bis zu LIMIT = 2000 Zertifikate ab; ab 2.000 Einträgen pro Gruppe bricht Inventory::sync() sicherheitshalber (fail-closed) ab, bis Paging implementiert wird. |
Dokumentiert (Fail-Closed greift) |
| AUD-07 | Info / Ops | database/schema.mysql.sql |
Tabelle login_attempts wächst unbegrenzt (bereits in docs/ADMIN.md §10 vermerkt). Empfehlung: periodisches Bereinigen alter Einträge (> 30 Tage). |
Dokumentiert |
Technische Details zum eingespielten Patch
- SOAP-Client-Härtung (
AUD-01): CredentialStore::assertAllowedWsdl($wsdl)als öffentliche Prüfmethode extrahiert und zusätzlich direkt vornew SoapClient(...)inSslHandler::getSoapClient()aufgerufen.SoapClienterhält nunconnection_timeout = 15sowie einenstream_contextmithttp.timeout = 20,follow_location = 0,max_redirects = 0undssl.verify_peer = true/verify_peer_name = true.- Atomarer TOTP-Replay-Schutz (
AUD-02): - In
Auth::verifyMfa()prüft dasUPDATE-Statement nun atomarWHERE id = ? AND (totp_last_step IS NULL OR totp_last_step < ?). - Ist
$upd->rowCount() !== 1, wurde derselbe Zeitschritt in einem parallelen Request bereits verbraucht; der Versuch wird alsauth.mfa(reason: replay_race) geloggt und abgewiesen. - FQDN-Validierung & SNI in
crossCheck()(AUD-03): - Vor
stream_socket_client()wird ein eventuelles*.-Wildcard-Präfix für den Socket-Connect entfernt und streng geprüft, dass ein syntaktisch gültiger FQDN (mit Punkt, max. 253 Zeichen, kein IP-Literal) vorliegt. Zusätzlich wird SNI (peer_name) explizit gesetzt. - Defensive
$_POST-Zugriffe (AUD-04) & Audit-Filter (AUD-05): - Alle
$_POST-Zugriffe inSslHandlersind mit?? ''abgesichert. public/auditlog.phpunterstützt in Dropdown und Schnellfilter-Chips nun auchweb.,booking.,catalog.undmodule..
4. Blueprint für den Rust-Rewrite (Migrations-Architektur)
Die bestehende PHP-Anwendung ist so strukturiert, dass der geplante Rewrite in Rust ohne Bruch an Datenbank, Krypto-Blobs oder CLI-/API-Verträgen erfolgen kann:
4.1 Binärkompatibilität der Datenbank-Secrets (tg1:)
Das Format in src/Security/Secrets.php ist sprachunabhängig spezifiziert:
"tg1:" || Base64( 0x01 || u32_le(key_id) || nonce[24] || XChaCha20Poly1305_Seal(plaintext, ad) )
- KDF: Blake2b-basierte Libsodium-KDF (
crypto_kdf_derive_from_key, Kontext"TLSGATE1", Subkey-ID =key_id * 100 + slot). - AEAD:
crypto_aead_xchacha20poly1305_ietf_encryptmit Associated Data"tg1|" + purpose + "|" + bind. - Vorteil für Rust: Ein Rust-Binary (über
libsodium-sysoderchacha20poly1305+blake2b_simd) kann alle bestehenden Secrets in MariaDB sofort lesen und schreiben, ohne dass TOTP-Token der Benutzer oder SOAP-/NetBox-Zugangsdaten neu eingegeben werden müssen.
4.2 Empfohlener Rust-Stack & Modul-Mapping
| PHP-Komponente | Rust-Äquivalent (Empfehlung) | Migrationsvorteil |
|---|---|---|
src/Storage/Db.php |
sqlx (MySQL/MariaDB, Compile-Time Query-Check) |
Identisches SQL-Schema (schema.mysql.sql), inkl. Unix-Socket-Auth |
src/Security/Secrets.php |
libsodium-sys / chacha20poly1305 + zeroize |
zeroize::Zeroizing ersetzt sodium_memzero + SoapCredential-Wrapper |
src/Auth/Auth.php & Totp.php |
argon2, hmac + sha1, tower-sessions |
Argon2id-Hashes ($argon2id$v=19$...) sind 1:1 kompatibel |
database/order_states.json database/catalog_matrix.json |
serde + serde_json (enum OrderState) |
Deklarative JSON-Verträge werden direkt zu typensicheren Rust-Enums |
public/*.php + SslController |
axum + askama (Templates) |
Gleiche Routen, gleiche CSP/Security-Header via tower-http |
bin/tg + bin/*_cli.php |
Ein einziges statisches Rust-Binary (clap) |
Ersetzt den gesamten PHP-CLI-Baum durch ein einziges Binary |
4.3 Inkrementelle Strangler-Fig-Migration
Da sämtliche Integrationstests (tests/test_secrets.sh, tests/test_roles.sh, tests/test_bookings.sh, tests/test_inventory.sh, tests/test_netbox.sh) über CLI-Aufrufe, curl und SQL-Prüfungen gegen die echte Datenbank arbeiten, kann der Rust-Rewrite schrittweise gegen exakt dieselbe Test-Suite verifiziert werden:
1. Stufe 1: Rust-Binary übernimmt die CLI-Kommandos (bin/tg) und Hintergrund-Syncs (inventory sync, netbox sync, key status).
2. Stufe 2: Rust-HTTP-Server (axum) übernimmt /api.php und die Web-Views hinter Apache/Reverse-Proxy.
3. Stufe 3: Abschalten von PHP-FPM auf der Node – Datenbank und Secrets bleiben unverändert bestehen.