feat(xmpp): natieve calls F2 — eigen XMPP-verbindingslaag + test-verbinding #1125

Merged
brenno merged 8 commits from feat/native-calls-f2-xmpp into main 2026-08-02 14:21:25 +00:00
Owner

Fase F2 van de natieve-calls-functie (docs/design/NATIVE_CALLS.md §5): OciDecks eigen (from-scratch) XMPP-over-WebSocket-client — de enkelvoudige spine die straks de Jitsi-calls én de samenwerkings-data-plane draagt — plus één werkende consument: een test-verbinding vanuit de (standaard-uit) Videovergaderingen-modulekaart. Nog géén join/call en géén media (dat is F3+); wat hier werkt is dat OciDeck een XMPP-server bereikt en met een account inlogt (SASL), en dat eerlijk terugmeldt.

Lagen

  • lib/xmpp/xmpp_stanza.dart — stanza-wire-model via package:xml (al een dependency), DTD's geweigerd (XXE-veilig).
  • lib/xmpp/xmpp_sasl.dart — PLAIN/ANONYMOUS + ScramClient (SCRAM-SHA-1/256, Hi/PBKDF2 op HMAC), bewezen tegen de RFC 5802 §5-vector, constant-time server-signature-vergelijking. Clientnonce uit Random.secure().
  • lib/xmpp/xmpp_frame_transport*.dart — injecteerbare frame-naad; de io-helft opent de enige uitgaande socket met een ruwe WebSocket.connect (zichtbaar voor de egress-scan), NetGuard-geresolved + gepind (DNS-rebind), fail-closed op een schema-downgrade; web faalt gesloten.
  • lib/xmpp/xmpp_connection.dart — auth-state-machine. Mechanismekeuze SCRAM > PLAIN (alleen als maySendReusableSecret klaart) > ANONYMOUS, weigert een <see-other-host>-redirect (fail-closed), geeft een getypeerde XmppAuthFailure terug (code, geen proza — l10n hoort in de UI).
  • lib/models/xmpp_settings.dart + SecretStore.xmppPasswordKey — spiegelen MatrixServer/matrixTokenKey; wachtwoord alleen in de keychain.
  • lib/widgets/dialogs/xmpp_test_connection_dialog.dart — de consument (sluit de dode-code-poort). Server/JID/wachtwoord + testknop → gelukt (met mechanisme) of een vertaalde fout. Injecteerbare connect → widget-test zonder socket. Bewaart niets.

Waarborgen

  • Vier harde security-voorwaarden (vooraf door de security-architect gesteld) geïmplementeerd: wss→https-map vóór de secret-poort, fail-closed schema-assert, see-other-host-weigering, alleen-wss (ws enkel naar literal-loopback). De io-transport staat op alle drie de egress-allowlists in network_sink_guard_test.dart, incl. een onderbouwde redirect-ratchet-uitzondering (WS-upgrade kan followRedirects niet zetten; de connectionFactory pint elke hop).
  • Reviews: beveiligingsarchitect (GO na de redirect-ratchet-onderbouwing) + kernwaardenbewaker (GO; ARCHITECTURE-accuratesse aangescherpt naar de trustedInternal-opt-in). Bewaker vereist want F2 raakt uitgaand verkeer + een publieke belofte.
  • Secrets-scan (gitleaks/trufflehog) schoon; SAST (semgrep, lokale regels) 0 findings. Geen pubspec-wijziging → geen SBOM. package:xml was al een dependency → geen ketenkeuring.
  • Tests: stanza, SASL (RFC-vector), 9× verbindings-state-machine (incl. SCRAM end-to-end + mutual-auth + see-other-host-weigering + verse-nonce), secret-store, dialoog-widget.
  • Docs: SOURCE_MAP lib/xmpp/-sectie + model + dialoog; ARCHITECTURE tweede-netwerkstack-belofte (desktop-only, web fail-closed, gebruiker-geconfigureerd, NetGuard-bewaakt; "phones home" blijft onwaar).
  • l10n: 15 nieuwe strings × 31 talen.

Poortstand (eerlijk)

make check is groen op alle statische poorten (format, analyze, toolchain, conventies, privacy-projectie, methodelengte, dode-code, hardcoded-text, commentaartaal) én op alle F2-eigen tests. In de volledige testsuite staan 2 tests rood: chrome_no_fixed_white_surface_test en ui_text_scale_test, beide op lib/widgets/shell/welcome_screen.dart:147/159. Dat is geërfde #1124-drift (het onboarding-redesign), byte-identiek aan main, niet door deze PR geraakt — precies de main-drift waarvan de volledige suite niet per-PR gepoort is. Die wordt apart opgepakt (codex/fix-welcome-screen-gates); F2 verandert die rode stand niet.

Fase F2 van de natieve-calls-functie (`docs/design/NATIVE_CALLS.md` §5): OciDecks **eigen (from-scratch) XMPP-over-WebSocket-client** — de enkelvoudige spine die straks de Jitsi-calls én de samenwerkings-data-plane draagt — plus één werkende consument: een **test-verbinding** vanuit de (standaard-uit) Videovergaderingen-modulekaart. Nog géén join/call en géén media (dat is F3+); wat hier werkt is dat OciDeck een XMPP-server bereikt en met een account inlogt (SASL), en dat eerlijk terugmeldt. ## Lagen - **`lib/xmpp/xmpp_stanza.dart`** — stanza-wire-model via `package:xml` (al een dependency), DTD's geweigerd (XXE-veilig). - **`lib/xmpp/xmpp_sasl.dart`** — PLAIN/ANONYMOUS + `ScramClient` (SCRAM-SHA-1/256, `Hi`/PBKDF2 op HMAC), **bewezen tegen de RFC 5802 §5-vector**, constant-time server-signature-vergelijking. Clientnonce uit `Random.secure()`. - **`lib/xmpp/xmpp_frame_transport*.dart`** — injecteerbare frame-naad; de io-helft opent de enige uitgaande socket met een **ruwe `WebSocket.connect`** (zichtbaar voor de egress-scan), **NetGuard**-geresolved + gepind (DNS-rebind), fail-closed op een schema-downgrade; **web faalt gesloten**. - **`lib/xmpp/xmpp_connection.dart`** — auth-state-machine. Mechanismekeuze **SCRAM > PLAIN (alleen als `maySendReusableSecret` klaart) > ANONYMOUS**, weigert een **`<see-other-host>`-redirect** (fail-closed), geeft een **getypeerde `XmppAuthFailure`** terug (code, geen proza — l10n hoort in de UI). - **`lib/models/xmpp_settings.dart`** + `SecretStore.xmppPasswordKey` — spiegelen `MatrixServer`/`matrixTokenKey`; wachtwoord alleen in de keychain. - **`lib/widgets/dialogs/xmpp_test_connection_dialog.dart`** — de consument (sluit de dode-code-poort). Server/JID/wachtwoord + testknop → gelukt (met mechanisme) of een vertaalde fout. Injecteerbare connect → widget-test zonder socket. Bewaart niets. ## Waarborgen - **Vier harde security-voorwaarden** (vooraf door de security-architect gesteld) geïmplementeerd: wss→https-map vóór de secret-poort, fail-closed schema-assert, see-other-host-weigering, alleen-wss (ws enkel naar literal-loopback). De io-transport staat op alle drie de egress-allowlists in `network_sink_guard_test.dart`, incl. een onderbouwde redirect-ratchet-uitzondering (WS-upgrade kan `followRedirects` niet zetten; de connectionFactory pint elke hop). - **Reviews**: beveiligingsarchitect (GO na de redirect-ratchet-onderbouwing) + kernwaardenbewaker (GO; ARCHITECTURE-accuratesse aangescherpt naar de `trustedInternal`-opt-in). Bewaker vereist want F2 raakt uitgaand verkeer + een publieke belofte. - **Secrets-scan** (gitleaks/trufflehog) schoon; **SAST** (semgrep, lokale regels) 0 findings. Geen pubspec-wijziging → geen SBOM. `package:xml` was al een dependency → geen ketenkeuring. - **Tests**: stanza, SASL (RFC-vector), 9× verbindings-state-machine (incl. SCRAM end-to-end + mutual-auth + see-other-host-weigering + verse-nonce), secret-store, dialoog-widget. - **Docs**: SOURCE_MAP `lib/xmpp/`-sectie + model + dialoog; ARCHITECTURE tweede-netwerkstack-belofte (desktop-only, web fail-closed, gebruiker-geconfigureerd, NetGuard-bewaakt; "phones home" blijft onwaar). - **l10n**: 15 nieuwe strings × 31 talen. ## Poortstand (eerlijk) `make check` is groen op **alle statische poorten** (format, analyze, toolchain, conventies, privacy-projectie, methodelengte, dode-code, hardcoded-text, commentaartaal) én op **alle F2-eigen tests**. In de **volledige testsuite** staan 2 tests rood: `chrome_no_fixed_white_surface_test` en `ui_text_scale_test`, beide op `lib/widgets/shell/welcome_screen.dart:147/159`. Dat is **geërfde #1124-drift** (het onboarding-redesign), byte-identiek aan main, **niet door deze PR geraakt** — precies de main-drift waarvan de volledige suite niet per-PR gepoort is. Die wordt apart opgepakt (`codex/fix-welcome-screen-gates`); F2 verandert die rode stand niet.
Het eigen (from-scratch) XMPP-fundament voor de natieve-calls-spine
(NATIVE_CALLS.md §5). Stanza-(de)serialisatie via package:xml (al een
dependency), DTD's geweigerd (XXE-veilig). SASL: PLAIN/ANONYMOUS + een
ScramClient (SCRAM-SHA-1/256, Hi/PBKDF2 op HMAC), bewezen tegen de
RFC 5802 §5-vector, met constant-time server-signature-vergelijking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
XmppSettings spiegelt MatrixServer; wachtwoord in de keychain
(SecretStore.xmppPasswordKey). De io-transport opent de enige uitgaande
socket met een ruwe WebSocket.connect (zichtbaar voor de egress-scan),
NetGuard-geresolved + gepind, fail-closed op een schema-downgrade; web
faalt gesloten. XmppConnection.authenticate kiest een mechanisme
(SCRAM > PLAIN alleen als de secret-poort dat toestaat > ANONYMOUS),
weigert een see-other-host-redirect (fail-closed), en geeft een
getypeerde XmppAuthFailure terug (geen proza — l10n hoort in de UI).
9 tests groen incl. SCRAM-SHA-1 end-to-end tegen de RFC-vector.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De eerste consument van de XMPP-client, geopend vanuit de
Videovergaderingen-modulekaart (maakt lib/xmpp/ bereikbaar voor de
dode-code-poort). Server/JID/wachtwoord + testknop openen een bewaakte
wss-stream en authenticeren; toont succes (met mechanisme) of een
vertaalde foutmelding. Bewaart niets. De verbindingsaanroep is
injecteerbaar → de widget-test doorloopt de hele flow zonder socket.
Faalcodes (XmppAuthFailure) worden hier op l10n afgebeeld.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SOURCE_MAP: een lib/xmpp/-sectie, de XmppSettings-model-entry en de
test-verbinding-dialoog. ARCHITECTURE: de netwerkbelofte eerlijk
bijgewerkt — de optionele, standaard-uit Videovergaderingen-module opent
een tweede uitgaande stack (XMPP-over-WebSocket naar een door de
gebruiker geconfigureerde server, NetGuard-bewaakt, desktop-only, web
fail-closed). "Phones home" blijft onwaar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opvolging van de beveiligingsarchitect- en bewaker-review:
- SCRAM-clientnonce nu uit Random.secure() (CSPRNG in dart:math, geen
  dependency) i.p.v. tijd+hashCode — uniek én onvoorspelbaar (RFC 5802
  §5.1); test dekt verse, distincte nonces af.
- redirect-ratchet: xmpp_frame_transport_io.dart bewust vrijgesteld met
  onderbouwing — WebSocket.connect bouwt het upgrade-verzoek intern (geen
  followRedirects te zetten), maar de connectionFactory pint elke hop
  (ook een 3xx) aan het goedgekeurde IP en de https-assert weigert een
  downgrade; ook als regelcommentaar in de io-transport vastgelegd.
- ARCHITECTURE: 'internal targets refused' → '…unless the user opts in
  per server as for WebDAV' (spiegelt de trustedInternal-opt-in eerlijk).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 nieuwe Nederlandse d()-bronstrings uit de F2 XMPP-testverbinding
(xmpp_test_connection_dialog.dart en video_calls_module_card.dart) in alle 31
niet-Nederlandse talen, via de additions-overlay. Reeds bestaande strings
(Wachtwoord, Sluiten, Verbinding testen, Videovergaderingen en de lange
module-omschrijving) overgeslagen. make l10n-check groen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(xmpp): F2 — schrijf de PLAIN-NUL-scheiding als \u0000-escape
All checks were successful
scans / scans (pull_request) Successful in 3m39s
static-gate / static-gate (pull_request) Successful in 23m18s
2aefb907de
De conventies-poort weigert rauwe control-bytes: een letterlijke NUL
maakt het bestand binair voor grep/git-diff (onreviewbaar). De
6-teken-escape \u0000 levert een byte-identieke string op, maar blijft
greppable. Raakt xmpp_sasl.dart (saslPlain) en de PLAIN-test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit d2d6441c81 into main 2026-08-02 14:21:25 +00:00
Sign in to join this conversation.
No description provided.