Leider funktioniert es vorerst nicht... unten die technischen Details :
Markdown unten ## Wie der Schutz funktioniert — mehrschichtige Abwehr
Es gibt **drei unabhängige Schichten**, von denen jede für sich allein ausreichen würde. Zusammen machen sie es praktisch unmöglich, den Inhalt in voller Qualität zu speichern, ohne dass die CDM-Hardware mitspielt.
---
### Schicht 1 — Transportverschlüsselung (AES-GCM auf Anwendungsebene)
Jede HTTP-Antwort des Proxys (`/api/secure-proxy/…`) wird **auf Anwendungsebene** mit AES-256-GCM verschlüsselt, zusätzlich zum normalen HTTPS-Transport. Das Player-JavaScript hält einen Sitzungsschlüssel, entschlüsselt den Ciphertext nach dem Eintreffen und gibt erst dann die Rohbytes an den Browser weiter. Wenn du das Netzwerk abfängst (Wireshark, HAR, Proxy), bekommst du für alles nutzlosen Ciphertext — m3u8-Playlists, Init-Segmente, Media-Segmente, alles.
**Was das blockt**: jedes passive Abfangen im Netzwerk.
---
### Schicht 2 — Wasserzeichen (nutzerspezifische Stream-URLs)
Noch bevor die Entschlüsselung überhaupt eine Rolle spielt, brennt der Server ein sichtbares Wasserzeichen ein, indem er jeden Nutzer über einen eigenen URL-Pfad leitet:
```
/api/secure-proxy/wm-stream/{64-char token}/{userId}/{resolution}/seg.m4s
```
Das Segment `{userId}` sagt dem Server, die Kennung des Nutzers beim Encodieren in die Videoframes einzurechnen. Selbst wenn du das Video irgendwie mitschneidest, steckt also deine Account-Info drin — und es lässt sich zu dir zurückverfolgen.
Es gibt auch eine saubere Version desselben Streams unter:
```
/api/secure-proxy/stream/{64-char token}/{resolution}/seg.m4s
```
Beide URLs sind gültig und liefern denselben Inhalt, nur ohne das Wasserzeichen-Overlay.
**Was das blockt**: das Teilen von mitgeschnittenem Video (es identifiziert den Quell-Account).
---
### Schicht 3 — Widevine CENC DRM (der unknackbare Teil)
Das ist der Kernschutz. Die fMP4-Segmente, die der Player aus AES-GCM entschlüsselt, sind **kein normales Video** — sie sind selbst auf Sample-Ebene mit [Common Encryption (CENC)](https://www.w3.org/TR/eme-stream-mp4/) verschlüsselt. Jeder Videoframe ist einzeln mit einem Schlüssel verschlüsselt, den das Widevine CDM (Content Decryption Module) hält.
Das CDM ist ein **natives Binär-Plugin**, das in einer hardware-isolierten Enklave läuft. Es empfängt die verschlüsselten Samples, entschlüsselt sie mit Schlüsseln vom Lizenzserver und schreibt die dekodierten Frames **direkt in den GPU-Compositor** — komplett außerhalb der Reichweite von JavaScript. Die entschlüsselten Pixel existieren nie in adressierbarem Speicher, an den eine Webseite oder Erweiterung rankommt.
Das kannst du bestätigen, wenn du dir die rohe fMP4-Struktur anschaust:
- `stsd`-Box: Sample-Entry-Typ ist `encv` (verschlüsseltes Video), nicht `avc1`
- `moov`-Box: enthält eine `pssh`-Box mit der System-ID von Widevine (`edef8ba9…`)
- Jedes `moof` (Media-Fragment) enthält `senc`-, `saiz`-, `saio`-Boxen — Initialisierungsvektoren und Key-IDs pro Sample
- Der `mdat`-Payload: H.264-Parameter-Sets (SPS/PPS) liegen im Klartext, aber jede Slice-NAL-Unit ist verschlüsselt — Entropie ≈ 5,7 Bit/Byte vs. ≈ 7,9 bei voll komprimierten Daten
**Was das blockt**: jeden Ansatz, der Daten innerhalb der JavaScript-Engine des Browsers abfängt.
---
## Warum es fast unmöglich ist
```
Network wire
│ HTTPS (TLS) ← Standard, nichts Besonderes
▼
Proxy server returns
│ AES-GCM encrypted blob ← Schicht 1
▼
Player JS decrypts with session key
│ fMP4 segments ← hier zugänglich, aber…
▼
appendBuffer() ← hier haben wir eingehakt
│ CENC-encrypted samples ← Schicht 3 noch aktiv
▼
Widevine CDM (native, hardware-isolated)
│ Decrypted frames → GPU compositor ← Schicht 3 entschlüsselt HIER
▼
Screen pixels
```
Die einzige Stelle, an der du **voll entschlüsseltes, anzeigbares Video** abfangen kannst, ist nach dem GPU-Compositor — also der Bildschirm selbst. Genau das macht `chrome.tabCapture`: Es greift die gerenderte Compositor-Ausgabe ab. Aber dann encodierst du neu, was schon auf dem Bildschirm ist, verlierst also eine Generation Qualität (und musst in Echtzeit aufnehmen).
---
## Die Schritte, die wir gemacht haben, der Reihe nach
| Schritt | Was wir versucht haben | Ergebnis |
|--- | --- | ---|
| **1. HAR-Analyse** | Netzwerkverkehr angeschaut, um die URL-Struktur zu verstehen | Das `wm-stream`- / `stream`-URL-Muster und die AES-GCM-Verschlüsselung auf allen Antworten gefunden |
| **2. Fetch-Hook (erster Versuch)** | `window.fetch` abgefangen, um `wm-stream`-URLs → `stream`-URLs umzuschreiben | Umschreibungen wurden stillschweigend verworfen — Strict-Mode-`arguments`-Aliasing-Bug: Das Neuzuweisen des Parameters `input` aktualisierte `arguments[0]` nicht, also schickte `.apply(this, arguments)` die Original-URL |
| **3. arguments-Bug fixen** | Auf `_origFetch.call(this, input, init)` statt `.apply(this, arguments)` umgestellt | Die Wasserzeichen-Entfernung funktioniert jetzt tatsächlich |
| **4. CSP-Fehler** | Die Erweiterung hat ein