# PIR-Server — iOS NEURLFilter Backend **Status: LIVE (Staging)** — `https://pir.staging.rebreak.org` · Stand 2026-05-21 Dieses Dokument beschreibt den PIR-Server vollständig, damit eine andere Session ihn verstehen, betreiben und weiterentwickeln kann. --- ## 1. Was & Warum Rebreaks iOS-Gambling-Blocker nutzt Apples **`NEURLFilter`** (iOS 26). NEURLFilter filtert URLs **systemweit** (Safari + alle WebKit-Browser + `URLSession`) und verlangt dafür **zwingend** einen vom Vendor gehosteten **PIR-Server** (Private Information Retrieval) — `NEURLFilterManager.setConfiguration()` akzeptiert keine Konfiguration ohne `pirServerURL`. Dieser Server ist dieser PIR-Server. **Ablauf zur Laufzeit:** 1. Das Gerät prüft die URL gegen einen lokalen **Bloom-Filter** (Prefilter, in der iOS-App-Extension `NEURLFilterControlProvider`). 2. Bei einem Bloom-Treffer → **PIR-Lookup** gegen diesen Server, geroutet über Apples Oblivious-HTTP-Relay. 3. Per Homomorphic Encryption sieht der Server die geprüfte URL **nie im Klartext**. **DSGVO/DiGA-Vorteil:** Kein Beteiligter sieht URL + Identität zusammen — Rebreak erfährt das Surfverhalten der User nie. Datenminimierung per Krypto-Konstruktion. --- ## 2. Architektur-Bausteine Basiert auf Apples Open-Source-Vorlagen: - `apple/pir-service-example` → `PIRService` (Swift/Hummingbird-HTTP-Server, bedient PIR-Queries + eingebauten PrivacyPass-Issuer) + `ConstructDatabase`. - `apple/swift-homomorphic-encryption` → `PIRProcessDatabase` (wandelt `input.txtpb` in PIR-optimierte Shards). --- ## 3. Deployment-Stand (Hetzner `rebreak-server`, 49.13.55.22) | Komponente | Ort / Wert | |---|---| | Docker-Container | `pir-service-staging`, `restart=unless-stopped`, `127.0.0.1:8090->8090` | | Docker-Image | `pir-service-staging:latest` (Multi-Stage Swift-Build, ~5 GB) | | Build-Repos | `/srv/pir-build/{pir-service-example,swift-homomorphic-encryption}` + `Dockerfile` | | Deploy-Verzeichnis | `/srv/pir-server/` (`build-and-deploy.sh`, `gen-test-input.sh`, `config/`, `data/`) | | PIR-DB-Artifacts | `/srv/pir-server/data/` — `input.txtpb` + `url-0..3.bin` + `url-0..3.params.txtpb` (4 Shards) | | Service-Config | `/srv/pir-server/config/service-config.json` (Token inline — NICHT im Repo) | | nginx | `/etc/nginx/sites-{available,enabled}/pir-staging.rebreak.org`, `server_name pir.staging.rebreak.org` | | TLS | Let's Encrypt für `pir.staging.rebreak.org`, läuft 2026-08-18 ab, Auto-Renew aktiv | | Public-URL | `https://pir.staging.rebreak.org` | --- ## 4. Konfiguration **`service-config.json`** (auf dem Server; Token nicht im Repo → siehe `service-config.template.json`): - usecase-Name: **`org.rebreak.app.url.filtering`** — muss EXAKT mit der iOS-`setConfiguration` matchen, sonst HTTP 404. - `shardCount`: **4** (215k Domains / 50k pro Shard). - `users[].tokens`: enthält den `PIR_AUTH_TOKEN`. **`PIR_AUTH_TOKEN`** — liegt in **Infisical**, Env `staging`, Key `PIR_AUTH_TOKEN`, Project-ID `14b11b35-ef59-4b8a-a16b-398f0cc3ad93`. App-weit halten (NICHT pro-User — ein per-User-Token würde die PIR-Nicht-Verkettbarkeit schwächen). **`process-config.json`** — Parameter für `PIRProcessDatabase`: `entryCountPerShard 50000`, `rlweParameters n_4096_logq_27_28_28_logt_5`, `databaseType keyword`. **`PIR_ISSUER_REQUEST_URI`** — Env-Var am Container (`docker run -e ...`), gesetzt von `build-and-deploy.sh` auf `https://pir.staging.rebreak.org/issue`. **Zwingend absolut** (RFC 9578 §6): das Apple-`pir-service-example` advertised von Haus aus nur ein **relatives** `/issue` im Issuer-Directory (`PrivacyPassController.swift`, hardcodiert) — der NEURLFilter-Client kann damit keinen Privacy-Pass-Token holen und scheitert mit `serverSetupIncomplete`. Behoben durch `patches/0001-absolute-issuer-request-uri.patch`, das die URL aus dieser Env-Var liest. --- ## 5. iOS-Integrationswerte (`NEURLFilterManager.setConfiguration`) | Parameter | Wert | |---|---| | `pirServerURL` | `https://pir.staging.rebreak.org` | | `pirPrivacyPassIssuerURL` | `https://pir.staging.rebreak.org` (PIRService bedient beides) | | `pirAuthenticationToken` | Infisical `staging/PIR_AUTH_TOKEN` | | usecase-Name | `org.rebreak.app.url.filtering` | | `controlProviderBundleIdentifier` | `org.rebreak.app.URLFilterExtension` (geplant — beim iOS-Scaffolding final festlegen) | --- ## 6. Daten-Pipeline (Blocklist → PIR-DB) 1. `backend/scripts/generate-pir-input.ts` erzeugt `input.txtpb` aus ~215k Gambling-Domains. - **DB-Modus** (Default): liest die `BlocklistDomain`-Tabelle — braucht `infisical run` für `DATABASE_URL`. - **HaGeZi-Modus** (`--source hagezi`): direkt von HaGeZis `gambling.txt`, kein DB/Infisical nötig. **Aktuell verwendet**, weil `infisical` nicht auf dem Dev-Mac liegt. Äquivalent — die `BlocklistDomain`-Tabelle wird ohnehin aus genau dieser HaGeZi-Liste gespeist. - Output-Format: pro Domain eine Zeile `rows: { keyword: "" value: "1" }`. 2. `input.txtpb` → per `scp` nach `/srv/pir-server/data/input.txtpb`. 3. `PIRProcessDatabase /data/process-config.json` (im Container) → `url-N.bin` + `url-N.params.txtpb`. --- ## 7. Betrieb / Runbook **Health-Check:** ```bash curl -s -o /dev/null -w '%{http_code}\n' \ https://pir.staging.rebreak.org/.well-known/private-token-issuer-directory # → 200 ssh rebreak-server 'docker ps | grep pir-service-staging' ``` **Blocklist aktualisieren:** 1. Neue `input.txtpb` generieren (`backend/scripts/generate-pir-input.ts`) + nach `/srv/pir-server/data/input.txtpb` scp'en. 2. Alte Shards löschen: `ssh rebreak-server 'rm /srv/pir-server/data/url-*.bin /srv/pir-server/data/url-*.params.txtpb'`. 3. `PIRProcessDatabase` neu laufen lassen (siehe `build-and-deploy.sh` Step 6) — erzeugt neue Shards. 4. `shardCount` in `service-config.json` prüfen + Container neu starten. **Komplett neu bauen/deployen:** ```bash ssh rebreak-server 'bash /srv/pir-server/build-and-deploy.sh' ``` Swift-Build ~20–30 Min, RAM-intensiv (~2–3 GB → Swap nötig, off-peak bauen). --- ## 8. Bekannte Issues / TODOs - ✅ **`build-and-deploy.sh` shardCount-Bug (BEHOBEN 2026-05-21):** Step 3 leitet den `shardCount` jetzt aus den vorhandenen `url-*.bin`-Artifacts ab statt ihn fix auf `1` zu setzen — kein Regress mehr von 4 Shards auf 1 bei Re-Runs. - ✅ **Relatives `issuer-request-uri` (BEHOBEN 2026-05-21):** siehe §4 `PIR_ISSUER_REQUEST_URI`. War die Ursache für `serverSetupIncomplete` auf der iOS-Seite. Fix via `patches/0001-absolute-issuer-request-uri.patch`, von `build-and-deploy.sh` Step 3b automatisch auf den `pir-service-example`-Clone angewandt. - Deploy-Skripte liegen sowohl auf dem Server (`/srv/pir-server/`, `/srv/pir-build/`) als auch hier im Repo. Bei Änderungen synchron halten — oder das Repo als Single-Source-of-Truth etablieren (empfohlen). - **App-Store-Distribution:** braucht zusätzlich Apples OHTTP-Onboarding-Formular, ein OHTTP-Gateway und einen DNS-TXT-Record `apple-url-filter=org.rebreak.app`. Für TestFlight/Dev NICHT nötig. → Phase 2. - **Entitlement** `com.apple.developer.networking.networkextension.url-filter-provider`: Dev-Builds ausgenommen; vor TestFlight/Store über den „Capability Requests"-Tab im Apple-Developer-Portal (Certificates, Identifiers & Profiles → Identifiers) beantragen. - Apple zu `pir-service-example`: „example service, not for production" — Produktionshärtung (persistente PrivacyPass-Keys statt ephemerer, Monitoring, Ressourcen-Tuning) ist ein eigener späterer Schritt. --- ## 9. Dateien in diesem Verzeichnis | Datei | Zweck | |---|---| | `README.md` | dieses Dokument | | `Dockerfile` | Multi-Stage Swift-Build (Kopie von `/srv/pir-build/Dockerfile`) | | `build-and-deploy.sh` | Deploy-Script (Kopie von `/srv/pir-server/`) | | `gen-test-input.sh` | Test-DB-Generator (10 Domains, Fallback) | | `process-config.json` | `PIRProcessDatabase`-Parameter | | `service-config.template.json` | Service-Config-Vorlage (Token-Platzhalter) | | `nginx-pir.staging.rebreak.org.conf` | nginx-Reverse-Proxy-Config (Referenz) | | `patches/*.patch` | Quell-Patches für `pir-service-example`, von `build-and-deploy.sh` Step 3b angewandt | Der `input.txtpb`-Generator selbst liegt bei `backend/scripts/generate-pir-input.ts`.