Compare commits

..

2 Commits

View File

@ -537,27 +537,32 @@ function postDRMTypeAndPssh(type, pssh) {
window.postMessage({ type: "__PSSH_DATA__", data: pssh }, "*"); window.postMessage({ type: "__PSSH_DATA__", data: pssh }, "*");
} }
function createAndOpenRemoteCDM(type, deviceInfo, pssh) {
let cdm;
if (type === "Widevine") {
const { device_type, system_id, security_level, host, secret, device_name } = deviceInfo;
cdm = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
cdm.openSession();
cdm.getChallenge(pssh);
} else if (type === "PlayReady") {
const { security_level, host, secret, device_name } = deviceInfo;
cdm = new remotePlayReadyCDM(security_level, host, secret, device_name);
cdm.openSession();
cdm.getChallenge(pssh);
}
return cdm;
}
function ensureRemoteCDM(type, deviceInfo, pssh) { function ensureRemoteCDM(type, deviceInfo, pssh) {
if (!remoteCDM) { if (!remoteCDM) {
if (type === "Widevine") { remoteCDM = createAndOpenRemoteCDM(type, deviceInfo, pssh);
const { device_type, system_id, security_level, host, secret, device_name } =
deviceInfo;
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(pssh);
} else if (type === "PlayReady") {
const { security_level, host, secret, device_name } = deviceInfo;
remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
remoteCDM.openSession();
remoteCDM.getChallenge(pssh);
}
} }
} }
@ -589,17 +594,11 @@ MediaKeySession.prototype.generateRequest = function (initDataType, initData) {
interceptType !== "DISABLED" && interceptType !== "DISABLED" &&
!serviceCertFound !serviceCertFound
) { ) {
const { device_type, system_id, security_level, host, secret, device_name } = remoteCDM = createAndOpenRemoteCDM(
widevineDeviceInfo; "Widevine",
remoteCDM = new remoteWidevineCDM( widevineDeviceInfo,
device_type, foundWidevinePssh
system_id,
security_level,
host,
secret,
device_name
); );
remoteCDM.openSession();
} }
if ( if (
!injectionSuccess && !injectionSuccess &&
@ -715,6 +714,81 @@ MediaKeySession.prototype.update = function (response) {
return updatePromise; return updatePromise;
}; };
// Helpers
function detectDRMChallenge(body) {
// Handles ArrayBuffer, Uint8Array, string, and JSON string
// Returns: { type: "Widevine"|"PlayReady"|null, base64: string|null, bodyType: "buffer"|"string"|"json"|null }
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(DRM_SIGNATURES.WIDEVINE)) {
return { type: "Widevine", base64: base64Body, bodyType: "buffer" };
}
if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
return { type: "PlayReady", base64: base64Body, bodyType: "buffer" };
}
} else if (typeof body === "string" && !isJson(body)) {
const base64EncodedBody = btoa(body);
if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
return { type: "Widevine", base64: base64EncodedBody, bodyType: "string" };
}
if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
return { type: "PlayReady", base64: base64EncodedBody, bodyType: "string" };
}
} else if (typeof body === "string" && isJson(body)) {
const jsonBody = JSON.parse(body);
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) {
return { type: "Widevine", base64: null, bodyType: "json" };
}
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
return { type: "PlayReady", base64: null, bodyType: "json" };
}
}
return { type: null, base64: null, bodyType: null };
}
function handleLicenseMode({
drmInfo,
body,
setBody, // function to set the new body (for fetch: (b) => config.body = b, for XHR: (b) => originalSend.call(this, b))
urlOrResource,
getWidevinePssh,
getPlayreadyPssh,
widevineDeviceInfo,
playreadyDeviceInfo,
}) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: urlOrResource }, "*");
// Create remoteCDM if needed
if (!remoteCDM) {
if (drmInfo.type === "Widevine") {
remoteCDM = createAndOpenRemoteCDM("Widevine", widevineDeviceInfo, getWidevinePssh());
}
if (drmInfo.type === "PlayReady") {
remoteCDM = createAndOpenRemoteCDM(
"PlayReady",
playreadyDeviceInfo,
getPlayreadyPssh()
);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(getWidevinePssh());
}
// Inject the new challenge into the request body
if (drmInfo.bodyType === "json") {
const jsonBody = JSON.parse(body);
const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
setBody(JSON.stringify(injectedBody));
} else if (drmInfo.bodyType === "buffer") {
setBody(base64ToUint8Array(remoteCDM.challenge));
} else {
setBody(atob(remoteCDM.challenge));
}
}
// fetch POST interceptor // fetch POST interceptor
(function () { (function () {
const originalFetch = window.fetch; const originalFetch = window.fetch;
@ -725,200 +799,37 @@ MediaKeySession.prototype.update = function (response) {
if (method === "POST") { if (method === "POST") {
let body = config.body; let body = config.body;
if (body) { if (body) {
if (body instanceof ArrayBuffer || body instanceof Uint8Array) { const drmInfo = detectDRMChallenge(body);
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
const base64Body = window.btoa(String.fromCharCode(...buffer));
if (
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
(!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(DRM_SIGNATURES.WIDEVINE) ||
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
if (!remoteCDM) {
if (base64Body.startsWith(DRM_SIGNATURES.WIDEVINE)) {
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();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = base64ToUint8Array(remoteCDM.challenge);
config.body = injectedBody;
return originalFetch(resource, config);
}
}
if (typeof body === "string" && !isJson(body)) {
const base64EncodedBody = btoa(body);
if (
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
(!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(DRM_SIGNATURES.WIDEVINE) ||
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
if (!remoteCDM) {
if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
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();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = atob(remoteCDM.challenge);
config.body = injectedBody;
return originalFetch(resource, config);
}
}
if (typeof body === "string" && isJson(body)) {
const jsonBody = JSON.parse(body);
if ( // EME mode: block the request if a DRM challenge is detected
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) || if (
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) && drmInfo.type &&
(!remoteCDM || remoteCDM.challenge === null) && (!remoteCDM ||
interceptType === "EME" remoteCDM.challenge === null ||
) { drmInfo.base64 !== remoteCDM.challenge) &&
foundChallengeInBody = true; interceptType === "EME"
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*"); ) {
// Block the request foundChallengeInBody = true;
return; window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
} // Block the request
return;
}
if ( // LICENSE mode: replace the challenge in the request
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) || if (drmInfo.type && interceptType === "LICENSE" && !foundChallengeInBody) {
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) && handleLicenseMode({
interceptType === "LICENSE" && drmInfo,
!foundChallengeInBody body,
) { setBody: (b) => {
foundChallengeInBody = true; config.body = b;
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*"); },
if (!remoteCDM) { urlOrResource: resource,
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) { getWidevinePssh: () => foundWidevinePssh,
const { getPlayreadyPssh: () => foundPlayreadyPssh,
device_type, widevineDeviceInfo,
system_id, playreadyDeviceInfo,
security_level, });
host, return originalFetch(resource, config);
secret,
device_name,
} = widevineDeviceInfo;
remoteCDM = new remoteWidevineCDM(
device_type,
system_id,
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
config.body = JSON.stringify(injectedBody);
}
} }
} }
} }
@ -941,200 +852,34 @@ MediaKeySession.prototype.update = function (response) {
XMLHttpRequest.prototype.send = function (body) { XMLHttpRequest.prototype.send = function (body) {
if (this._method && this._method.toUpperCase() === "POST") { if (this._method && this._method.toUpperCase() === "POST") {
if (body) { if (body) {
if (body instanceof ArrayBuffer || body instanceof Uint8Array) { const drmInfo = detectDRMChallenge(body);
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
const base64Body = window.btoa(String.fromCharCode(...buffer)); // EME mode: block the request if a DRM challenge is detected
if ( if (
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) || drmInfo.type &&
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) && (!remoteCDM ||
(!remoteCDM || remoteCDM.challenge === null ||
remoteCDM.challenge === null || drmInfo.base64 !== remoteCDM.challenge) &&
base64Body !== remoteCDM.challenge) && interceptType === "EME"
interceptType === "EME" ) {
) { foundChallengeInBody = true;
foundChallengeInBody = true; window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*"); // Block the request
// Block the request return;
return;
}
if (
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (base64Body.startsWith(DRM_SIGNATURES.WIDEVINE)) {
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();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = base64ToUint8Array(remoteCDM.challenge);
return originalSend.call(this, injectedBody);
}
} }
if (typeof body === "string" && !isJson(body)) { // LICENSE mode: replace the challenge in the request
const base64EncodedBody = btoa(body); if (drmInfo.type && interceptType === "LICENSE" && !foundChallengeInBody) {
if ( return handleLicenseMode({
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) || drmInfo,
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) && body,
(!remoteCDM || setBody: (b) => originalSend.call(this, b),
remoteCDM.challenge === null || urlOrResource: this._url,
base64EncodedBody !== remoteCDM.challenge) && getWidevinePssh: () => foundWidevinePssh,
interceptType === "EME" getPlayreadyPssh: () => foundPlayreadyPssh,
) { widevineDeviceInfo,
foundChallengeInBody = true; playreadyDeviceInfo,
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*"); });
// Block the request
return;
}
if (
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
interceptType == "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
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();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = atob(remoteCDM.challenge);
return originalSend.call(this, injectedBody);
}
}
if (typeof body === "string" && isJson(body)) {
const jsonBody = JSON.parse(body);
if (
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
(!remoteCDM || remoteCDM.challenge === null) &&
interceptType === "EME"
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
// Block the request
return;
}
if (
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
interceptType === "LICENSE" &&
!foundChallengeInBody
) {
foundChallengeInBody = true;
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
if (!remoteCDM) {
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) {
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();
remoteCDM.getChallenge(foundWidevinePssh);
}
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
const { security_level, host, secret, device_name } =
playreadyDeviceInfo;
remoteCDM = new remotePlayReadyCDM(
security_level,
host,
secret,
device_name
);
remoteCDM.openSession();
remoteCDM.getChallenge(foundPlayreadyPssh);
}
}
if (remoteCDM && remoteCDM.challenge === null) {
remoteCDM.getChallenge(foundWidevinePssh);
}
const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
return originalSend.call(this, JSON.stringify(injectedBody));
}
} }
} }
} }