From 58a10ae6771f6a01b72ef5b832b3aaf9d554c55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jernej=20Fija=C4=8Dko?= Date: Sat, 19 Aug 2023 03:26:52 +0200 Subject: [PATCH] Fix PlayReady DRM in Edge browser (#5699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jernej Fijačko --- src/controller/eme-controller.ts | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index ab699517b87..770186d926f 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -976,6 +976,52 @@ class EMEController implements ComponentAPI { ); } + private unpackPlayReadyKeyMessage( + xhr: XMLHttpRequest, + licenseChallenge: Uint8Array + ): Uint8Array { + // On Edge, the raw license message is UTF-16-encoded XML. We need + // to unpack the Challenge element (base64-encoded string containing the + // actual license request) and any HttpHeader elements (sent as request + // headers). + // For PlayReady CDMs, we need to dig the Challenge out of the XML. + const xmlString = String.fromCharCode.apply( + null, + new Uint16Array(licenseChallenge.buffer) + ); + if (!xmlString.includes('PlayReadyKeyMessage')) { + // This does not appear to be a wrapped message as on Edge. Some + // clients do not need this unwrapping, so we will assume this is one of + // them. Note that "xml" at this point probably looks like random + // garbage, since we interpreted UTF-8 as UTF-16. + xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8'); + return licenseChallenge; + } + const keyMessageXml = new DOMParser().parseFromString( + xmlString, + 'application/xml' + ); + // Set request headers. + const headers = keyMessageXml.querySelectorAll('HttpHeader'); + if (headers.length > 0) { + let header: Element; + for (let i = 0, len = headers.length; i < len; i++) { + header = headers[i]; + const name = header.querySelector('name')?.textContent; + const value = header.querySelector('value')?.textContent; + if (name && value) { + xhr.setRequestHeader(name, value); + } + } + } + const challengeElement = keyMessageXml.querySelector('Challenge'); + const challengeText = challengeElement?.textContent; + if (!challengeText) { + throw new Error(`Cannot find in key message`); + } + return strToUtf8array(atob(challengeText)); + } + private setupLicenseXHR( xhr: XMLHttpRequest, url: string, @@ -1117,6 +1163,12 @@ class EMEController implements ComponentAPI { this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then( ({ xhr, licenseChallenge }) => { + if (keySessionContext.keySystem == KeySystems.PLAYREADY) { + licenseChallenge = this.unpackPlayReadyKeyMessage( + xhr, + licenseChallenge + ); + } xhr.send(licenseChallenge); } );