Add manifest URL field, reset keys when manifest changes, organize repo, update to Manifest v3 #3

Open
voldemort wants to merge 26 commits from voldemort/CDRM-Extension:main into main
18 changed files with 4754 additions and 4436 deletions
Showing only changes of commit c9ff17558d - Show all commits

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
react/
frontend/dist/
frontend/src/assets/

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"useTabs": false,
"printWidth": 100
}

View File

@ -4,7 +4,7 @@ chrome.browserAction.onClicked.addListener(() => {
url: chrome.runtime.getURL("react/index.html"),
type: "popup", // opens as a floating window
width: 800,
height: 600
height: 600,
});
});

View File

@ -1,8 +1,8 @@
// Inject `inject.js` into the page context
(function injectScript() {
const script = document.createElement('script');
script.src = chrome.runtime.getURL('inject.js');
script.type = 'text/javascript';
const script = document.createElement("script");
script.src = chrome.runtime.getURL("inject.js");
script.type = "text/javascript";
script.onload = () => script.remove(); // Clean up
// Inject directly into <html> or <head>
(document.documentElement || document.head || document.body).appendChild(script);
@ -12,15 +12,18 @@
window.addEventListener("message", function (event) {
if (event.source !== window) return;
if (["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(event.data?.type)) {
if (
["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(
event.data?.type
)
) {
chrome.runtime.sendMessage({
type: event.data.type.replace("__", "").replace("__", ""),
data: event.data.data
data: event.data.data,
});
}
if (event.data.type === "__GET_CDM_DEVICES__") {
chrome.storage.local.get(["widevine_device", "playready_device"], (result) => {
const widevine_device = result.widevine_device || null;
const playready_device = result.playready_device || null;
@ -29,7 +32,7 @@ window.addEventListener("message", function(event) {
{
type: "__CDM_DEVICES__",
widevine_device,
playready_device
playready_device,
},
"*"
);
@ -37,31 +40,57 @@ window.addEventListener("message", function(event) {
}
if (event.data.type === "__GET_INJECTION_TYPE__") {
chrome.storage.local.get("injection_type", (result) => {
const injectionType = result.injection_type || "LICENSE";
window.postMessage(
{
type: "__INJECTION_TYPE__",
injectionType
injectionType,
},
"*"
);
});
}
if (event.data.type === "__GET_DRM_OVERRIDE__") {
if (event.data.type === "__GET_DRM_OVERRIDE__") {
chrome.storage.local.get("drm_override", (result) => {
const drmOverride = result.drm_override || "DISABLED";
window.postMessage(
{
type: "__DRM_OVERRIDE__",
drmOverride
drmOverride,
},
"*"
);
});
}
// Manifest header and URL
const seenManifestUrls = new Set();
if (event.data?.type === "__MANIFEST_URL__") {
const url = event.data.data;
if (seenManifestUrls.has(url)) return;
seenManifestUrls.add(url);
console.log("✅ [Content] Unique manifest URL:", url);
chrome.runtime.sendMessage({
type: "MANIFEST_URL_FOUND",
data: url,
});
}
if (event.data?.type === "__MANIFEST_HEADERS__") {
const { url, headers } = event.data;
console.log("[Content.js] Manifest Headers:", url, headers);
chrome.runtime.sendMessage({
type: "MANIFEST_HEADERS",
url,
headers,
});
}
});

View File

@ -1,33 +1,30 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
export default [
{ ignores: ['dist'] },
{ ignores: ["dist"] },
{
files: ['**/*.{js,jsx}'],
files: ["**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: 'module',
sourceType: "module",
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
},
]
];

View File

@ -1,10 +1,5 @@
import { useState, useEffect } from "react";
import {
HashRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom";
import TopNav from "./components/topnav";
import SideNav from "./components/sidenav";
import Results from "./components/results";

View File

@ -8,13 +8,7 @@ function Results() {
useEffect(() => {
chrome.storage.local.get(
[
"drmType",
"latestPSSH",
"latestLicenseRequest",
"latestKeys",
"licenseURL",
],
["drmType", "latestPSSH", "latestLicenseRequest", "latestKeys", "licenseURL"],
(result) => {
if (result.drmType) setDrmType(result.drmType);
if (result.latestPSSH) setPssh(result.latestPSSH);
@ -64,9 +58,7 @@ function Results() {
});
// Get all normal windows to exclude your popup
chrome.windows.getAll(
{ populate: true, windowTypes: ["normal"] },
(windows) => {
chrome.windows.getAll({ populate: true, windowTypes: ["normal"] }, (windows) => {
if (!windows || windows.length === 0) {
console.warn("No normal Chrome windows found");
return;
@ -94,8 +86,7 @@ function Results() {
} else {
console.warn("No active tab found in the last focused normal window");
}
}
);
});
};
return (
@ -132,8 +123,7 @@ function Results() {
/>
<p className="text-2xl mt-5">Keys</p>
<div className="w-full min-h-64 h-64 flex items-center justify-center text-center overflow-y-auto bg-slate-800/50 rounded-md p-2 mt-2 text-white whitespace-pre-line">
{Array.isArray(keys) &&
keys.filter((k) => k.type !== "SIGNING").length > 0 ? (
{Array.isArray(keys) && keys.filter((k) => k.type !== "SIGNING").length > 0 ? (
keys
.filter((k) => k.type !== "SIGNING")
.map((k) => `${k.key_id || k.keyId}:${k.key}`)

View File

@ -13,10 +13,7 @@ function Settings({ onConfigSaved }) {
useEffect(() => {
chrome.storage.local.get("cdrm_instance", (result) => {
if (chrome.runtime.lastError) {
console.error(
"Error fetching CDRM instance:",
chrome.runtime.lastError
);
console.error("Error fetching CDRM instance:", chrome.runtime.lastError);
} else if (result.cdrm_instance) {
setStoredUrl(result.cdrm_instance);
}
@ -49,18 +46,12 @@ function Settings({ onConfigSaved }) {
setMessage("Successfully connected to CDRM Instance.");
setMessageType("success");
const widevineRes = await fetch(
`${trimmedUrl}/remotecdm/widevine/deviceinfo`
);
if (!widevineRes.ok)
throw new Error("Failed to fetch Widevine device info");
const widevineRes = await fetch(`${trimmedUrl}/remotecdm/widevine/deviceinfo`);
if (!widevineRes.ok) throw new Error("Failed to fetch Widevine device info");
const widevineData = await widevineRes.json();
const playreadyRes = await fetch(
`${trimmedUrl}/remotecdm/playready/deviceinfo`
);
if (!playreadyRes.ok)
throw new Error("Failed to fetch PlayReady device info");
const playreadyRes = await fetch(`${trimmedUrl}/remotecdm/playready/deviceinfo`);
if (!playreadyRes.ok) throw new Error("Failed to fetch PlayReady device info");
const playreadyData = await playreadyRes.json();
chrome.storage.local.set(

View File

@ -7,10 +7,7 @@ function SideNav({ onClose }) {
return (
<div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col bg-black">
<div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black">
<button
onClick={onClose}
className="h-full ml-auto p-3 hover:cursor-pointer"
>
<button onClick={onClose} className="h-full ml-auto p-3 hover:cursor-pointer">
<img src={closeIcon} alt="Close" className="h-full" />
</button>
</div>

View File

@ -19,10 +19,7 @@ function TopNav({ onMenuClick }) {
const handleInjectionTypeChange = (type) => {
chrome.storage.local.set({ injection_type: type }, () => {
if (chrome.runtime.lastError) {
console.error(
"Error updating injection_type:",
chrome.runtime.lastError
);
console.error("Error updating injection_type:", chrome.runtime.lastError);
} else {
setInjectionType(type);
console.log(`Injection type updated to ${type}`);

View File

@ -1,6 +1,8 @@
@import "tailwindcss";
html, body, #root {
html,
body,
#root {
height: 100%;
width: 100%;
margin: 0;

View File

@ -1,10 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById('root')).render(
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>,
)
</StrictMode>
);

View File

@ -1,9 +1,9 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
base: './',
base: "./",
plugins: [react(), tailwindcss()],
})
});

567
inject.js
View File

@ -1,6 +1,6 @@
let widevineDeviceInfo = null;
let playreadyDeviceInfo = null;
let originalChallenge = null
let originalChallenge = null;
let serviceCertFound = false;
let drmType = "NONE";
let psshFound = false;
@ -59,6 +59,122 @@ window.addEventListener("message", function(event) {
}
});
function safeHeaderShellEscape(str) {
return str
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\$/g, "\\$") // escape shell expansion
.replace(/`/g, "\\`")
.replace(/\n/g, ""); // strip newlines
}
// Intercep network to find manifest
function injectManifestInterceptor() {
const script = document.createElement("script");
script.textContent = `
(function() {
function isProbablyManifest(text = "", contentType = "") {
const lowerCT = contentType?.toLowerCase() ?? "";
const sample = text.slice(0, 2000);
const isHLSMime = lowerCT.includes("mpegurl");
const isDASHMime = lowerCT.includes("dash+xml");
const isSmoothMime = lowerCT.includes("sstr+xml");
const isHLSKeyword = sample.includes("#EXTM3U") || sample.includes("#EXT-X-STREAM-INF");
const isDASHKeyword = sample.includes("<MPD") || sample.includes("<AdaptationSet");
const isSmoothKeyword = sample.includes("<SmoothStreamingMedia");
const isJsonManifest = sample.includes('"playlist"') && sample.includes('"segments"');
return (
isHLSMime || isDASHMime || isSmoothMime ||
isHLSKeyword || isDASHKeyword || isSmoothKeyword || isJsonManifest
);
}
const originalFetch = window.fetch;
window.fetch = async function(input, init) {
const response = await originalFetch.apply(this, arguments);
try {
const clone = response.clone();
const contentType = clone.headers.get("content-type") || "";
const text = await clone.text();
const url = typeof input === "string" ? input : input.url;
if (isProbablyManifest(text, contentType)) {
window.postMessage({ type: "__MANIFEST_URL__", data: url }, "*");
console.log("[Manifest][fetch]", url, contentType);
const headersObj = {};
clone.headers.forEach((value, key) => {
headersObj[key] = value;
});
const headerFlags = Object.entries(headersObj)
.map(([key, val]) => '--add-headers "' + safeHeaderShellEscape(key) + ': ' + safeHeaderShellEscape(val) + '"')
.join(" ");
window.postMessage({
type: "__MANIFEST_HEADERS__",
url,
headers: headerFlags
}, "*");
}
} catch (e) {}
return response;
};
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this.__url = url;
return originalXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
this.addEventListener("load", function () {
try {
const contentType = this.getResponseHeader("content-type") || "";
const text = this.responseText;
if (isProbablyManifest(text, contentType)) {
window.postMessage({ type: "__MANIFEST_URL__", data: this.__url }, "*");
console.log("[Manifest][xhr]", this.__url, contentType);
const xhrHeaders = {};
const rawHeaders = this.getAllResponseHeaders().trim().split(/\\r?\\n/);
rawHeaders.forEach(line => {
const parts = line.split(": ");
if (parts.length === 2) {
xhrHeaders[parts[0]] = parts[1];
}
});
const headerFlags = Object.entries(xhrHeaders)
.map(([key, val]) => '--add-headers "' + safeHeaderShellEscape(key) + ': ' + safeHeaderShellEscape(val) + '"')
.join(" ");
window.postMessage({
type: "__MANIFEST_HEADERS__",
url: this.__url,
headers: headerFlags
}, "*");
}
} catch (e) {}
});
return originalXHRSend.apply(this, arguments);
};
})();
`;
document.documentElement.appendChild(script);
script.remove();
}
injectManifestInterceptor();
// PlayReady Remote CDM Class
class remotePlayReadyCDM {
@ -76,8 +192,8 @@ class remotePlayReadyCDM {
openSession() {
const url = `${this.host}/remotecdm/playready/${this.device_name}/open`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
const jsonData = JSON.parse(xhr.responseText);
if (jsonData.data?.session_id) {
@ -93,11 +209,11 @@ class remotePlayReadyCDM {
getChallenge(init_data) {
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_license_challenge`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id,
init_data: init_data
init_data: init_data,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
@ -114,16 +230,17 @@ class remotePlayReadyCDM {
parseLicense(license_message) {
const url = `${this.host}/remotecdm/playready/${this.device_name}/parse_license`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id,
license_message: license_message
}
license_message: license_message,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
if (jsonData.message === "Successfully parsed and loaded the Keys from the License message")
{
if (
jsonData.message === "Successfully parsed and loaded the Keys from the License message"
) {
console.log("PlayReady license response parsed successfully");
return true;
} else {
@ -136,11 +253,11 @@ class remotePlayReadyCDM {
getKeys() {
const url = `${this.host}/remotecdm/playready/${this.device_name}/get_keys`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id
}
session_id: this.session_id,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
if (jsonData.data?.keys) {
@ -156,8 +273,8 @@ class remotePlayReadyCDM {
closeSession() {
const url = `${this.host}/remotecdm/playready/${this.device_name}/close/${this.session_id}`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
const jsonData = JSON.parse(xhr.responseText);
if (jsonData) {
@ -187,8 +304,8 @@ class remoteWidevineCDM {
openSession() {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/open`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
const jsonData = JSON.parse(xhr.responseText);
if (jsonData.data?.session_id) {
@ -204,12 +321,12 @@ class remoteWidevineCDM {
setServiceCertificate(certificate) {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id,
certificate: certificate ?? null
}
certificate: certificate ?? null,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
if (jsonData.status === 200) {
@ -221,15 +338,15 @@ class remoteWidevineCDM {
}
// Get Widevine challenge
getChallenge(init_data, license_type = 'STREAMING') {
getChallenge(init_data, license_type = "STREAMING") {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id,
init_data: init_data,
privacy_mode: serviceCertFound
privacy_mode: serviceCertFound,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
@ -246,11 +363,11 @@ class remoteWidevineCDM {
parseLicense(license_message) {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/parse_license`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id,
license_message: license_message
license_message: license_message,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
@ -267,10 +384,10 @@ class remoteWidevineCDM {
getKeys() {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_keys/ALL`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
const body = {
session_id: this.session_id
session_id: this.session_id,
};
xhr.send(JSON.stringify(body));
const jsonData = JSON.parse(xhr.responseText);
@ -287,8 +404,8 @@ class remoteWidevineCDM {
closeSession() {
const url = `${this.host}/remotecdm/widevine/${this.device_name}/close/${this.session_id}`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
const jsonData = JSON.parse(xhr.responseText);
if (jsonData) {
@ -302,22 +419,22 @@ class remoteWidevineCDM {
// Utility functions
function hexStrToU8(hexString) {
return Uint8Array.from(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
}
function u8ToHexStr(bytes) {
return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
}
function b64ToHexStr(b64) {
return [...atob(b64)].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join``;
return [...atob(b64)].map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join``;
}
function jsonContainsValue(obj, prefix = "CAES") {
if (typeof obj === "string") return obj.startsWith(prefix);
if (Array.isArray(obj)) return obj.some(val => jsonContainsValue(val, prefix));
if (Array.isArray(obj)) return obj.some((val) => jsonContainsValue(val, prefix));
if (typeof obj === "object" && obj !== null) {
return Object.values(obj).some(val => jsonContainsValue(val, prefix));
return Object.values(obj).some((val) => jsonContainsValue(val, prefix));
}
return false;
}
@ -328,7 +445,7 @@ function jsonReplaceValue(obj, newValue) {
}
if (Array.isArray(obj)) {
return obj.map(item => jsonReplaceValue(item, newValue));
return obj.map((item) => jsonReplaceValue(item, newValue));
}
if (typeof obj === "object" && obj !== null) {
@ -379,10 +496,10 @@ function getPlayReadyPssh(buffer) {
}
function getClearkey(response) {
let obj = JSON.parse((new TextDecoder("utf-8")).decode(response));
return obj["keys"].map(o => ({
key_id: b64ToHexStr(o["kid"].replace(/-/g, '+').replace(/_/g, '/')),
key: b64ToHexStr(o["k"].replace(/-/g, '+').replace(/_/g, '/')),
let obj = JSON.parse(new TextDecoder("utf-8").decode(response));
return obj["keys"].map((o) => ({
key_id: b64ToHexStr(o["kid"].replace(/-/g, "+").replace(/_/g, "/")),
key: b64ToHexStr(o["k"].replace(/-/g, "+").replace(/_/g, "/")),
}));
}
@ -397,7 +514,7 @@ function base64ToUint8Array(base64) {
}
function arrayBufferToBase64(uint8array) {
let binary = '';
let binary = "";
const len = uint8array.length;
for (let i = 0; i < len; i++) {
@ -415,14 +532,14 @@ MediaKeySession.prototype.generateRequest = function(initDataType, initData) {
if (playReadyPssh) {
console.log("[DRM Detected] PlayReady");
foundPlayreadyPssh = playReadyPssh;
console.log("[PlayReady PSSH found] " + playReadyPssh)
console.log("[PlayReady PSSH found] " + playReadyPssh);
}
let wideVinePssh = getWidevinePssh(initData)
let wideVinePssh = getWidevinePssh(initData);
if (wideVinePssh) {
// Widevine code
console.log("[DRM Detected] Widevine");
foundWidevinePssh = wideVinePssh;
console.log("[Widevine PSSH found] " + wideVinePssh)
console.log("[Widevine PSSH found] " + wideVinePssh);
}
// Challenge message interceptor
if (!remoteListenerMounted) {
@ -432,10 +549,16 @@ MediaKeySession.prototype.generateRequest = function(initDataType, initData) {
const uint8Array = new Uint8Array(event.message);
const base64challenge = arrayBufferToBase64(uint8Array);
if (base64challenge === "CAQ=" && interceptType !== "DISABLED" && !serviceCertFound) {
const {
device_type, system_id, security_level, host, secret, device_name
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
const { device_type, system_id, security_level, host, secret, device_name } =
widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
}
if (!injectionSuccess && base64challenge !== "CAQ=" && interceptType !== "DISABLED") {
@ -450,30 +573,53 @@ MediaKeySession.prototype.generateRequest = function(initDataType, initData) {
window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
if (interceptType === "EME" && !remoteCDM) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}}
}
}
if (!originalChallenge.startsWith("CAES")) {
const buffer = event.message;
const decoder = new TextDecoder('utf-16');
const decoder = new TextDecoder("utf-16");
const decodedText = decoder.decode(buffer);
const match = decodedText.match(/<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/);
const match = decodedText.match(
/<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/
);
if (match) {
window.postMessage({ type: "__DRM_TYPE__", data: "PlayReady" }, "*");
window.postMessage({ type: "__PSSH_DATA__", data: foundPlayreadyPssh }, "*");
window.postMessage(
{ type: "__PSSH_DATA__", data: foundPlayreadyPssh },
"*"
);
originalChallenge = match[1];
if (interceptType === "EME" && !remoteCDM) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name)
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}}
}
}
if (interceptType === "EME" && remoteCDM) {
const uint8challenge = base64ToUint8Array(remoteCDM.challenge);
const challengeBuffer = uint8challenge.buffer;
@ -482,16 +628,16 @@ MediaKeySession.prototype.generateRequest = function(initDataType, initData) {
origin: event.origin,
lastEventId: event.lastEventId,
source: event.source,
ports: event.ports
ports: event.ports,
});
Object.defineProperty(syntheticEvent, "message", {
get: () => challengeBuffer
get: () => challengeBuffer,
});
console.log("Intercepted EME Challenge and injected custom one.")
console.log("Intercepted EME Challenge and injected custom one.");
session.dispatchEvent(syntheticEvent);
}
}
})
});
console.log("Message interceptor mounted.");
}
return originalGenerateRequest.call(session, initDataType, initData);
@ -511,7 +657,11 @@ MediaKeySession.prototype.update = function(response) {
window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
serviceCertFound = true;
}
if (!base64Response.startsWith("CAUS") && (foundWidevinePssh || foundPlayreadyPssh) && !keysRetrieved) {
if (
!base64Response.startsWith("CAUS") &&
(foundWidevinePssh || foundPlayreadyPssh) &&
!keysRetrieved
) {
if (licenseResponseCounter === 1 || foundChallengeInBody) {
remoteCDM.parseLicense(base64Response);
remoteCDM.getKeys();
@ -530,17 +680,17 @@ MediaKeySession.prototype.update = function(response) {
console.log("[CLEARKEY] ", clearKeys);
const drmType = {
type: "__DRM_TYPE__",
data: 'ClearKey'
data: "ClearKey",
};
window.postMessage(drmType, "*");
const keysData = {
type: "__KEYS_DATA__",
data: clearKeys
data: clearKeys,
};
window.postMessage(keysData, "*");
}
})
.catch(e => {
.catch((e) => {
console.log("[CLEARKEY] Not found");
});
}
@ -553,37 +703,63 @@ MediaKeySession.prototype.update = function(response) {
const originalFetch = window.fetch;
window.fetch = async function (resource, config = {}) {
const method = (config.method || 'GET').toUpperCase();
const method = (config.method || "GET").toUpperCase();
if (method === 'POST') {
if (method === "POST") {
let body = config.body;
if (body) {
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
const base64Body = window.btoa(String.fromCharCode(...buffer));
if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64Body !== remoteCDM.challenge) && interceptType === "EME") {
if (
(base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) &&
(!remoteCDM ||
remoteCDM.challenge === null ||
base64Body !== remoteCDM.challenge) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
// Block the request
return;
}
if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && interceptType == "LICENSE" &&!foundChallengeInBody) {
if (
(base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
if (!remoteCDM) {
if (base64Body.startsWith("CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64Body.startsWith("PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
@ -596,31 +772,59 @@ MediaKeySession.prototype.update = function(response) {
return originalFetch(resource, config);
}
}
if (typeof body === 'string' && !isJson(body)) {
if (typeof body === "string" && !isJson(body)) {
const base64EncodedBody = btoa(body);
if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64EncodedBody !== remoteCDM.challenge) && interceptType === "EME") {
if (
(base64EncodedBody.startsWith("CAES") ||
base64EncodedBody.startsWith("PD94")) &&
(!remoteCDM ||
remoteCDM.challenge === null ||
base64EncodedBody !== remoteCDM.challenge) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
// Block the request
return;
}
if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && interceptType == "LICENSE" && !foundChallengeInBody) {
if (
(base64EncodedBody.startsWith("CAES") ||
base64EncodedBody.startsWith("PD94")) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
if (!remoteCDM) {
if (base64EncodedBody.startsWith("CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64EncodedBody.startsWith("PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
@ -633,33 +837,59 @@ MediaKeySession.prototype.update = function(response) {
return originalFetch(resource, config);
}
}
if (typeof body === 'string' && isJson(body)) {
if (typeof body === "string" && isJson(body)) {
const jsonBody = JSON.parse(body);
if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && (!remoteCDM || remoteCDM.challenge === null) && interceptType === "EME") {
if (
(jsonContainsValue(jsonBody, "CAES") ||
jsonContainsValue(jsonBody, "PD94")) &&
(!remoteCDM || remoteCDM.challenge === null) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
// Block the request
return;
}
if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && interceptType === "LICENSE" && !foundChallengeInBody) {
if (
(jsonContainsValue(jsonBody, "CAES") ||
jsonContainsValue(jsonBody, "PD94")) &&
interceptType === "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
if (!remoteCDM) {
if (jsonContainsValue(jsonBody, "CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (jsonContainsValue(jsonBody, "PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
@ -690,35 +920,60 @@ MediaKeySession.prototype.update = function(response) {
};
XMLHttpRequest.prototype.send = function (body) {
if (this._method && this._method.toUpperCase() === 'POST') {
if (this._method && this._method.toUpperCase() === "POST") {
if (body) {
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
const base64Body = window.btoa(String.fromCharCode(...buffer));
if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64Body !== remoteCDM.challenge) && interceptType === "EME") {
if (
(base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) &&
(!remoteCDM ||
remoteCDM.challenge === null ||
base64Body !== remoteCDM.challenge) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
// Block the request
return;
}
if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && interceptType == "LICENSE" &&!foundChallengeInBody) {
if (
(base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (base64Body.startsWith("CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64Body.startsWith("PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
@ -731,31 +986,59 @@ MediaKeySession.prototype.update = function(response) {
}
}
if (typeof body === 'string' && !isJson(body)) {
if (typeof body === "string" && !isJson(body)) {
const base64EncodedBody = btoa(body);
if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64EncodedBody !== remoteCDM.challenge) && interceptType === "EME") {
if (
(base64EncodedBody.startsWith("CAES") ||
base64EncodedBody.startsWith("PD94")) &&
(!remoteCDM ||
remoteCDM.challenge === null ||
base64EncodedBody !== remoteCDM.challenge) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
// Block the request
return;
}
if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && interceptType == "LICENSE" && !foundChallengeInBody) {
if (
(base64EncodedBody.startsWith("CAES") ||
base64EncodedBody.startsWith("PD94")) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (base64EncodedBody.startsWith("CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64EncodedBody.startsWith("PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
@ -768,33 +1051,59 @@ MediaKeySession.prototype.update = function(response) {
}
}
if (typeof body === 'string' && isJson(body)) {
if (typeof body === "string" && isJson(body)) {
const jsonBody = JSON.parse(body);
if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && (!remoteCDM || remoteCDM.challenge === null) && interceptType === "EME") {
if (
(jsonContainsValue(jsonBody, "CAES") ||
jsonContainsValue(jsonBody, "PD94")) &&
(!remoteCDM || remoteCDM.challenge === null) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
// Block the request
return;
}
if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && interceptType === "LICENSE" && !foundChallengeInBody) {
if (
(jsonContainsValue(jsonBody, "CAES") ||
jsonContainsValue(jsonBody, "PD94")) &&
interceptType === "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (jsonContainsValue(jsonBody, "CAES")) {
const {
device_type, system_id, security_level, host, secret, device_name
device_type,
system_id,
security_level,
host,
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (jsonContainsValue(jsonBody, "PD94")) {
const {
security_level, host, secret, device_name
} = playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}