fix(ios-vpn): blocklist.bin File-Protection — .completeUntilFirstUserAuthentication

Layer 1 (PacketTunnel-DNS-Sinkhole) blockte nichts: `startTunnel` lud
0 Hashes, obwohl `blocklist.bin` 330k Hashes hatte. Ursache: `syncBlocklist`
setzte `URLFileProtection.complete` — die Datei ist damit bei GESPERRTEM
Gerät unlesbar. Der PacketTunnel wird OS-getrieben (on-demand) auch während
Lock-Phasen neu gestartet → `open()` schlägt fehl → 0 Hashes → Layer 1 tot
bis zum nächsten App-Sync.

Beweis aus den Extension-Logs: `blocklist reloaded` (Darwin-Notif nach
App-Sync, Gerät entsperrt) = 329k-330k Hashes; `startTunnel → blocklist
geladen` = 0 — dieselbe Datei, dieselbe Path.

Fix: `.complete` → `.completeUntilFirstUserAuthentication` (blocklist.bin +
webContent-Cache). Bleibt at-rest verschlüsselt (DiGA-konform), ist aber nach
dem ersten Entsperren seit Boot lesbar — die korrekte Klasse für eine Datei,
die eine Network-Extension 24/7 lesen muss.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-22 18:15:50 +02:00
parent fe156a5f58
commit ef28f4947a

View File

@ -735,9 +735,18 @@ public class RebreakProtectionModule: Module {
let tmpURL = finalURL.appendingPathExtension("tmp") let tmpURL = finalURL.appendingPathExtension("tmp")
try data.write(to: tmpURL, options: .atomic) try data.write(to: tmpURL, options: .atomic)
// DiGA-Hardening // DiGA-Hardening: Datei verschlüsselt at-rest. WICHTIG:
// `.completeUntilFirstUserAuthentication`, NICHT `.complete`
// die PacketTunnel-Extension muss `blocklist.bin` auch bei
// GESPERRTEM Gerät lesen können. `startTunnel` läuft OS-getrieben
// (on-demand) auch während Lock-Phasen; `.complete` macht die Datei
// dann unlesbar `HashList.load()` liefert 0 Hashes Layer 1
// blockt nichts, bis die App das nächste Mal synct.
// `.completeUntilFirstUserAuthentication` bleibt at-rest
// verschlüsselt, ist aber nach dem ersten Entsperren seit Boot
// lesbar die korrekte Klasse für eine 24/7-NE-Datei.
try? (tmpURL as NSURL).setResourceValue( try? (tmpURL as NSURL).setResourceValue(
URLFileProtection.complete, URLFileProtection.completeUntilFirstUserAuthentication,
forKey: .fileProtectionKey forKey: .fileProtectionKey
) )
var mut = tmpURL var mut = tmpURL
@ -895,9 +904,11 @@ public class RebreakProtectionModule: Module {
let tmpURL = finalURL.appendingPathExtension("tmp") let tmpURL = finalURL.appendingPathExtension("tmp")
try data.write(to: tmpURL, options: .atomic) try data.write(to: tmpURL, options: .atomic)
// DiGA-Hardening (gespiegelt von syncBlocklist). // DiGA-Hardening (gespiegelt von syncBlocklist)
// `.completeUntilFirstUserAuthentication`, damit die Datei auch aus
// Hintergrund-Kontexten bei gesperrtem Gerät lesbar bleibt.
try? (tmpURL as NSURL).setResourceValue( try? (tmpURL as NSURL).setResourceValue(
URLFileProtection.complete, URLFileProtection.completeUntilFirstUserAuthentication,
forKey: .fileProtectionKey forKey: .fileProtectionKey
) )
var mut = tmpURL var mut = tmpURL