mirror of
https://github.com/ThatNotEasy/PlayReadyProxy.git
synced 2026-04-02 10:38:18 +00:00
released
This commit is contained in:
131
background.js
131
background.js
@@ -2,6 +2,7 @@ import {
|
||||
uint8ArrayToBase64,
|
||||
SettingsManager,
|
||||
RemoteCDMManager,
|
||||
LocalCDMManager,
|
||||
PSSHFromKID,
|
||||
stringToUTF16LEBytes,
|
||||
} from "./util.js";
|
||||
@@ -9,6 +10,7 @@ import { RemoteCdm } from "./remote_cdm.js";
|
||||
|
||||
let manifests = new Map();
|
||||
let requests = new Map();
|
||||
const sessions = new Map();
|
||||
let logs = [];
|
||||
|
||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
@@ -46,7 +48,7 @@ chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
async function generateChallengeRemote(body, sendResponse) {
|
||||
try {
|
||||
// Decode the base64-encoded body into a binary string
|
||||
const binaryString = decodeBase64(body); // Use the decodeBase64 function
|
||||
const binaryString = decodeBase64(body);
|
||||
const byteArray = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
byteArray[i] = binaryString.charCodeAt(i);
|
||||
@@ -54,13 +56,13 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
|
||||
// Decode using UTF-16LE encoding
|
||||
const decoder = new TextDecoder("utf-16le");
|
||||
var xmlString = decoder.decode(byteArray);
|
||||
var xmlDecoded;
|
||||
let xmlString = decoder.decode(byteArray);
|
||||
let xmlDecoded;
|
||||
|
||||
// Extract the Challenge element from the XML string
|
||||
const challengeRegex = /<Challenge[^>]*>([\s\S]*?)<\/Challenge>/i;
|
||||
const challengeMatch = challengeRegex.exec(xmlString);
|
||||
var encoding;
|
||||
let encoding;
|
||||
|
||||
if (challengeMatch) {
|
||||
const challengeContent = challengeMatch[1].trim();
|
||||
@@ -70,7 +72,7 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
|
||||
// If encoding is base64encoded, decode the challenge content
|
||||
if (encoding === "base64encoded") {
|
||||
const challengeBinaryString = decodeBase64(challengeContent); // Use the decodeBase64 function
|
||||
const challengeBinaryString = decodeBase64(challengeContent);
|
||||
const challengeByteArray = new Uint8Array(challengeBinaryString.length);
|
||||
for (let i = 0; i < challengeBinaryString.length; i++) {
|
||||
challengeByteArray[i] = challengeBinaryString.charCodeAt(i);
|
||||
@@ -87,11 +89,11 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
// Extract the KID element
|
||||
const kidRegex = /<KID>([^<]+)<\/KID>/i;
|
||||
const kidMatch = kidRegex.exec(xmlDecoded);
|
||||
var kidBase64;
|
||||
let kidBase64;
|
||||
if (kidMatch) {
|
||||
kidBase64 = kidMatch[1].trim();
|
||||
} else {
|
||||
console.log("[PlayreadyProxy]", "NO_KID_IN_CHALLENGE");
|
||||
console.log("[PlayReadyProxy]", "NO_KID_IN_CHALLENGE");
|
||||
sendResponse(body);
|
||||
return;
|
||||
}
|
||||
@@ -99,7 +101,7 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
// Get PSSH from KID
|
||||
const pssh = PSSHFromKID(kidBase64);
|
||||
if (!pssh) {
|
||||
console.log("[PlayreadyProxy]", "NO_PSSH_DATA_IN_CHALLENGE");
|
||||
console.log("[PlayReadyProxy]", "NO_PSSH_DATA_IN_CHALLENGE");
|
||||
sendResponse(body);
|
||||
return;
|
||||
}
|
||||
@@ -117,14 +119,11 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
try {
|
||||
// Check if the selected_remote_cdm is Base64-encoded XML
|
||||
if (selected_remote_cdm.startsWith("PD94bWwgdm")) {
|
||||
const decodedString = decodeBase64(selected_remote_cdm); // Use the decodeBase64 function
|
||||
const decodedString = decodeBase64(selected_remote_cdm);
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parseXML(decodedString); // Use parseXML function
|
||||
|
||||
// Convert the XML document into a RemoteCdm object
|
||||
const xmlDoc = parseXML(decodedString);
|
||||
remoteCdmObj = RemoteCdm.from_xml(xmlDoc);
|
||||
} else {
|
||||
// Otherwise, parse as JSON
|
||||
remoteCdmObj = JSON.parse(selected_remote_cdm);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -134,9 +133,16 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
}
|
||||
|
||||
const remote_cdm = RemoteCdm.from_object(remoteCdmObj);
|
||||
const session_id = await remote_cdm.open();
|
||||
if (!session_id) {
|
||||
console.error("[PlayReadyProxy] Failed to open session.");
|
||||
sendResponse(body);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the license challenge
|
||||
const challenge = await remote_cdm.get_license_challenge(pssh);
|
||||
console.log("[PlayReadyProxy]", "SESSION_ID", session_id);
|
||||
|
||||
const challenge = await remote_cdm.get_license_challenge(session_id, pssh);
|
||||
|
||||
// Replace the challenge content in the original XML with the new challenge
|
||||
const newXmlString = xmlString.replace(
|
||||
@@ -158,45 +164,72 @@ async function generateChallengeRemote(body, sendResponse) {
|
||||
|
||||
|
||||
async function parseLicenseRemote(body, sendResponse, tab_url) {
|
||||
const response = atob(body);
|
||||
try {
|
||||
const license_b64 = body; // License message is already Base64-encoded
|
||||
|
||||
const selected_remote_cdm_name =
|
||||
await RemoteCDMManager.getSelectedRemoteCDM();
|
||||
if (!selected_remote_cdm_name) {
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
// Fetch the selected remote CDM and load it
|
||||
const selected_remote_cdm_name = await RemoteCDMManager.getSelectedRemoteCDM();
|
||||
if (!selected_remote_cdm_name) {
|
||||
console.error("[PlayReadyProxy] No remote CDM selected.");
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
const selected_remote_cdm = JSON.parse(
|
||||
await RemoteCDMManager.loadRemoteCDM(selected_remote_cdm_name)
|
||||
);
|
||||
const remote_cdm = RemoteCdm.from_object(selected_remote_cdm);
|
||||
const selected_remote_cdm = await RemoteCDMManager.loadRemoteCDM(selected_remote_cdm_name);
|
||||
let remoteCdmObj;
|
||||
|
||||
const returned_keys = await remote_cdm.get_keys(btoa(response));
|
||||
try {
|
||||
remoteCdmObj = JSON.parse(selected_remote_cdm);
|
||||
} catch (e) {
|
||||
console.error("[PlayReadyProxy] Error parsing remote CDM JSON:", e);
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
if (returned_keys.length === 0) {
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
const remote_cdm = RemoteCdm.from_object(remoteCdmObj);
|
||||
const session_id = await remote_cdm.open();
|
||||
if (!session_id) {
|
||||
console.error("[PlayReadyProxy] Failed to open session.");
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = returned_keys.map((s) => {
|
||||
return {
|
||||
console.log("[PlayReadyProxy]", "SESSION_ID", session_id);
|
||||
|
||||
// Fetch keys using get_keys(session_id, license_b64)
|
||||
const returned_keys = await remote_cdm.get_keys(session_id, license_b64);
|
||||
if (!returned_keys || returned_keys.length === 0) {
|
||||
console.log("[PlayReadyProxy] No keys returned.");
|
||||
sendResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the keys correctly
|
||||
const keys = returned_keys.map((s) => ({
|
||||
k: s.key,
|
||||
kid: s.key_id,
|
||||
}));
|
||||
|
||||
console.log("[PlayReadyProxy]", "KEYS", JSON.stringify(keys), tab_url);
|
||||
|
||||
// Store log data
|
||||
const log = {
|
||||
type: "PLAYREADY",
|
||||
keys: keys,
|
||||
url: tab_url,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
manifests: manifests.has(tab_url) ? manifests.get(tab_url) : [],
|
||||
};
|
||||
});
|
||||
logs.push(log);
|
||||
|
||||
console.log("[PlayreadyProxy]", "KEYS", JSON.stringify(keys), tab_url);
|
||||
// Close the session after key retrieval
|
||||
await remote_cdm.close(session_id);
|
||||
|
||||
const log = {
|
||||
type: "PLAYREADY",
|
||||
keys: keys,
|
||||
url: tab_url,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
manifests: manifests.has(tab_url) ? manifests.get(tab_url) : [],
|
||||
};
|
||||
logs.push(log);
|
||||
sendResponse();
|
||||
sendResponse();
|
||||
} catch (error) {
|
||||
console.error("[PlayReadyProxy] Error in parseLicenseRemote:", error);
|
||||
sendResponse();
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
@@ -246,6 +279,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
case "GET_LOGS":
|
||||
sendResponse(logs);
|
||||
break;
|
||||
case "OPEN_PICKER_LOCAL":
|
||||
chrome.windows.create({
|
||||
url: "picker/filePickerLocal.html",
|
||||
type: "popup",
|
||||
width: 300,
|
||||
height: 200,
|
||||
});
|
||||
break;
|
||||
case "OPEN_PICKER":
|
||||
chrome.windows.create({
|
||||
url: "picker/filePicker.html",
|
||||
@@ -281,4 +322,4 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
});
|
||||
});
|
||||
147
remote_cdm.js
147
remote_cdm.js
@@ -4,7 +4,7 @@ export class RemoteCdm {
|
||||
this.host = host;
|
||||
this.secret = secret;
|
||||
this.device_name = device_name;
|
||||
this.proxy = proxy; // Optional proxy parameter
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
static from_object(obj) {
|
||||
@@ -13,65 +13,128 @@ export class RemoteCdm {
|
||||
obj.host,
|
||||
obj.secret,
|
||||
obj.device_name ?? obj.name,
|
||||
obj.proxy ?? null // Handle proxy from object if present
|
||||
obj.proxy ?? null
|
||||
);
|
||||
}
|
||||
|
||||
get_name() {
|
||||
const type = this.security_level;
|
||||
return `[${type}] ${this.host}/${this.device_name}`;
|
||||
return `[PlayReady] ${this.host}/${this.device_name}`;
|
||||
}
|
||||
|
||||
async fetch_with_proxy(url, options) {
|
||||
// If proxy is set, prepend proxy URL to the original host URL
|
||||
if (this.proxy) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
'X-Forwarded-For': this.proxy, // Optional: Forward the proxy information
|
||||
"X-Forwarded-For": this.proxy,
|
||||
};
|
||||
url = `${this.proxy}${url}`;
|
||||
}
|
||||
const response = await fetch(url, options);
|
||||
return response;
|
||||
|
||||
console.log(`[PlayReadyProxy] Fetching: ${options.method} ${url}`);
|
||||
console.log(`[PlayReadyProxy] Headers:`, options.headers);
|
||||
if (options.body) console.log(`[PlayReadyProxy] Body:`, options.body);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
console.log(`[PlayReadyProxy] Response Status: ${response.status}`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`[PlayReadyProxy] Request failed: ${url} [${response.status}]`);
|
||||
const errorText = await response.text();
|
||||
console.error(`[PlayReadyProxy] Error Response:`, errorText);
|
||||
throw new Error(`Request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`[PlayReadyProxy] Network Error:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async get_license_challenge(pssh) {
|
||||
const license_request = await this.fetch_with_proxy(`${this.host}/api/playready/extension`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"X-API-KEY": this.secret,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: "Challenge?",
|
||||
pssh: pssh,
|
||||
}),
|
||||
});
|
||||
console.log(
|
||||
"[PlayreadyProxy]",
|
||||
"REMOTE_CDM",
|
||||
"GET_LICENSE_CHALLENGE",
|
||||
license_request.status
|
||||
async open() {
|
||||
console.log("[PlayReadyProxy] Opening PlayReady session...");
|
||||
|
||||
const open_request = await this.fetch_with_proxy(
|
||||
`${this.host}/api/playready/${this.device_name}/open`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { "X-API-KEY": this.secret },
|
||||
}
|
||||
);
|
||||
const license_request_json = await license_request.json();
|
||||
|
||||
return license_request_json.data;
|
||||
console.log("[PlayReadyProxy]", "REMOTE_CDM", "OPEN", open_request.status);
|
||||
const response = await open_request.json();
|
||||
console.log("[PlayReadyProxy] Open Response:", response);
|
||||
|
||||
return response.responseData.session_id;
|
||||
}
|
||||
|
||||
async get_keys(license_challenge) {
|
||||
const keys = await this.fetch_with_proxy(`${this.host}/api/playready/extension`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"X-API-KEY": this.secret,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: "Keys?",
|
||||
license: license_challenge,
|
||||
}),
|
||||
});
|
||||
console.log("[PlayreadyProxy]", "REMOTE_CDM", "GET_KEYS", keys.status);
|
||||
async close(session_id) {
|
||||
console.log("[PlayReadyProxy] Closing PlayReady session:", session_id);
|
||||
|
||||
return await keys.json();
|
||||
await this.fetch_with_proxy(
|
||||
`${this.host}/api/playready/${this.device_name}/close/${session_id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { "X-API-KEY": this.secret },
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[PlayReadyProxy]", "REMOTE_CDM", "CLOSE", session_id);
|
||||
}
|
||||
}
|
||||
|
||||
async get_license_challenge(session_id, pssh) {
|
||||
console.log("[PlayReadyProxy] Requesting License Challenge...");
|
||||
console.log("[PlayReadyProxy] SESSION_ID:", session_id);
|
||||
console.log("[PlayReadyProxy] PSSH:", pssh);
|
||||
|
||||
const license_request = await this.fetch_with_proxy(
|
||||
`${this.host}/${this.device_name}/get_challenge`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"X-API-KEY": this.secret,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: session_id,
|
||||
pssh: pssh,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[PlayReadyProxy]", "REMOTE_CDM", "GET_LICENSE_CHALLENGE", license_request.status);
|
||||
const response = await license_request.json();
|
||||
console.log("[PlayReadyProxy] License Challenge Response:", response);
|
||||
|
||||
return response.responseData.challenge_b64;
|
||||
}
|
||||
|
||||
async get_keys(session_id, license_b64) {
|
||||
console.log("[PlayReadyProxy] Requesting Decryption Keys...");
|
||||
console.log("[PlayReadyProxy] SESSION_ID:", session_id);
|
||||
console.log("[PlayReadyProxy] License (Base64):", license_b64);
|
||||
|
||||
const keys_request = await this.fetch_with_proxy(
|
||||
`${this.host}/api/playready/${this.device_name}/get_keys`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"X-API-KEY": this.secret,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: session_id,
|
||||
license_b64: license_b64,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[PlayReadyProxy]", "REMOTE_CDM", "GET_KEYS", keys_request.status);
|
||||
const response = await keys_request.json();
|
||||
console.log("[PlayReadyProxy] Keys Response:", response);
|
||||
|
||||
return response.responseData.keys;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user