Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add EME ClearKey support #2934

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

botengyao
Copy link

This PR will...

This PR will add the check of org.w3.clearkey and define the usage of the Clear Key.
Adding ClearKey support will allow users to use SAMPLE-AES in HLS (cbcs pattern)

the config will add :

  • clearkeyServerUrl?: string,
  • clearkeyPair: KeyidValue,

Users can specify the KeyID-Key pair in their configuration. hls.js will synchronously generate the license on the client following the spec. The usage is like:

clearkeyPair: { keyid (128 HEX) : key (128 HEX) } 

Users can also specify a ClearKey server URL through clearkeyServerUrl

clearkeyServerUrl: "https://xxxxxx"

Why is this Pull Request needed?

We currently only support Widevine, which requires us to establish a license server. There isn't a clear path to use Clear Key with hls.js according to #2901 . From the spec, all browsers supporting EME must implement Clear Key. That allows us to play the encrypted video by providing the Key.

The org.w3.clearkey Key System uses plain-text clear key(s) to decrypt the source. It is also handy for testing EME implementations, and applications using EME, without the need to request a content key from a complicated license server.

Are there any points in the code the reviewer needs to double check?

Resolves issues:

#2901

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable)
  • API or design changes are documented in API.md

@botengyao
Copy link
Author

Hi @itsjamie , can you (or anyone) help me review the code? Don't know why I cannot add a reviewer for this PR.

I generated a cbcs encrypted fMP4 video (SAMPLE-AES) with a common PSSH through Shaka Packager. It works on Chrome, Firefox, and New Microsoft Edge. Safari is tricky and I will look into that, while it can directly use video teg to play the video. Microsoft Edge Legacy (EgdeHTML) doesn't work, because it doesn't support ClearKey.

@itsjamie
Copy link
Collaborator

itsjamie commented Jul 31, 2020

Hello @calmbryan, if you could upload the test media somewhere, or the command you used to generate it, that would help testing.

I understand that you're removing the transmux of SAMPLE-AES because you want to push users to push the MSE data through hls.js without it having to add code to decrypt the content. Have you looked to see what happens through transmuxUnencrypted on the mp4-demuxer to see if it is able to parse the boxes that are unencrypted?

There was an earlier ticket around adding support for SAMPLE-AES support for fMP4 and ClearKey came up as a option then. It was closed after the reporter stated it wasn't necessary anymore. #1491

I've got some concerns with saying we support SAMPLE-AES encrypted content full-stop (removing the separate code path) if it requires setting up a ClearKey session that a user may not have set up.

For example, if we were to use ClearKey for the purpose of supporting SAMPLE-AES, we should go about adding detection for a EXT-X-KEY within the m3u8-parser and the fragment encrypted property, and setup the ClearKey EME session for the user using the provided information in the HLS playlist.

If you want to just add support for the ClearKey "CDM" to test an EME implementation, I wouldn't include your changes to the SAMPLE-AES transmuxing in this PR.

@botengyao
Copy link
Author

botengyao commented Jul 31, 2020

Thanks @itsjamie !

Here is an sample mp4 video. This video will be parsed and encrypted in cbcs pattern by Shaka-packager. Here is the command in Shaka-pakager (in docker):

pssh_common=`pssh-box.py --system-id 1077efecc0b24d02ace33c1e52e2fb4b --key-id abba271e8bcf552bbd2e86a434a9a5d9 --hex`

packager \
  'in=/media/5mb.mp4,stream=video,init_segment=/media/h264_360p/init.mp4,segment_template=/media/h264_360p/$Number$.m4s,drm_label=SD' \
  'in=/media/5mb.mp4,stream=audio,init_segment=/media/h264_360p/audioinit.mp4,segment_template=/media/h264_360p/audio$Number$.m4s,drm_label=AU' \
  --protection_scheme cbcs \
  --enable_raw_key_encryption \
  --keys label=SD:key_id=abba271e8bcf552bbd2e86a434a9a5d9:key=69eaa802a6763af979e8d1940fb88392,label=AU:key_id=abba271e8bcf552bbd2e86a434a9a5d9:key=69eaa802a6763af979e8d1940fb88392\
  --pssh $pssh_common \
  --iv 11223344556677889900112233445566 \
  --hls_master_playlist_output /media/h264_master.m3u8 \

Regarding the transmux of mp4, if we want to use SAMPLE-AES with ClearKey, the check should pass. Therefore, I add the check of clearkeyPair and clearkeyLicenseUrl in transmux.ts. The SAMPLE-AES fMP4 video will not play if users don't provide both the above values or don't specify the EXT-X-KEY:METHOD=SAMPLE-AES. This is similar to the usage of Shaka-player. In addition, PSSH should be common PSSH box.

For the next step, the check of URI and Key format can be added to m3u8-parser. I also find some To-do/issues regarding manifest, and it can be another PR.

I went through #1491 when I began to explore SMAPLE-AES. According to @erankor : "there is no support for a 10% encryption ratio as in cbcs", that may be one of the reasons to close the issue. I don't think so in 2020. All CDMs begin to support cbcs pattern including ClearKey.

@botengyao
Copy link
Author

Want to check the reason of failing of continuous-integration/travis-ci/pr, I always get this kind of error even I am in feature/v1.0.0 branch. It seems that the license server is down.

1) testing hls.js playback in the browser on "chrome (latest)"
       should receive video loadeddata event for Shaka-packager Widevine DRM (EME) HLS-fMP4 - Angel One Demo:

      AssertionError: {
  "code": "keySystemLicenseRequestFailed"
}: expected 'keySystemLicenseRequestFailed' to equal 'loadeddata'
      + expected - actual

      -keySystemLicenseRequestFailed
      +loadeddata

@robwalch robwalch added this to Top priorities in Release Planning and Backlog via automation Aug 12, 2020
@robwalch robwalch moved this from Top priorities to DRM (needs prioritization) in Release Planning and Backlog Aug 12, 2020
videoCodecs: string[]
): MediaKeySystemConfiguration[] { /* jshint ignore:line */
const baseConfig: MediaKeySystemConfiguration = {
initDataTypes: ['keyids', 'mp4'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some browsers; "cenc" is the init data type that will be selected when a org.w3.clearkey keysystem is requested.

Additionally, encryptionScheme: 'cbcs' can be added to further clarify the request

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initDataTypes for all keysystems can be found in utils/mediakeys-helper.ts:

https://github.com/video-dev/hls.js/blob/v1.3.0-beta.2/src/utils/mediakeys-helper.ts#L125-L126

keyarray.push(
{
kty: 'oct',
alg: 'A128KW',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alg property here can be removed.

w3c/encrypted-media#48


var start = 0;
var end = encodedBase64.length;
while (end > start && encodedBase64[end - 1] === '=') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to also handle converting + -> -. and / -> _.

I had come up with these helpers when I was doing my validation.

// Helper function to do the Base64 UrlDecoding as described in the ClearKey spec
    function base64urldecode(str) {
      return window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
    }
    
// Helper function to do the Base64 UrlEncoding as described in the ClearKey spec
    function base64urlencode(str) {
      return window.btoa(str).split('=')[0].replace(/\+/g, "-").replace(/\//g, "_")
    }

@@ -589,8 +728,11 @@ class EMEController implements ComponentAPI {
const videoCodecs = data.levels.map((level) => level.videoCodec).filter(
(videoCodec: string | undefined): videoCodec is string => !!videoCodec
);

this._attemptKeySystemAccess(KeySystems.WIDEVINE, audioCodecs, videoCodecs);
if (this._clearkeyPair || this._clearkeyServerUrl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely don't want to always prefer setting up a ClearKey session over any real DRM protection if we get an encrypted event from the browser.

I think this is potentially why we want to hook the HLS manifest parsing and preemptively setup a ClearKey MediaKeySession when we detect SAMPLE-AES based content. We can request from the user if they want to decrypt using ClearKey. And thats when we are provided with the ClearKey information the user thinks we will need to do playback.

@@ -501,6 +629,10 @@ class EMEController implements ComponentAPI {
case KeySystems.WIDEVINE:
// For Widevine CDMs, the challenge is the keyMessage.
return keyMessage;
case KeySystems.CLEARKEY:
// For CLEARKEY, the challenge is the keyMessage.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a PSSH box. If we needed to for whatever reason provide the end-user with the contents of the key message, we could parse like this.

// precompute hex mapping from Uint8
const byteToHex = [];
for (let n = 0; n <= 0xff; ++n) {
    const hexOctet = n.toString(16).padStart(2, "0");
    byteToHex.push(hexOctet);
}

function hexFromAb(arrayBuffer) {
    const buff = new Uint8Array(arrayBuffer);
    const hexOctets = new Array(buff.length);
    for (let i = 0; i < buff.length; ++i) {
        hexOctets[i] = byteToHex[buff[i]];
    }
    return hexOctets.join("");
}

function parsePssh(buffer) {
    const view = new DataView(buffer)
    const boxSize = view.getUint32(0)
    if (buffer.byteLength !== boxSize && boxSize > 44) {
        console.assert('yikes not right')
    }
    const type = view.getUint32(4)
    if (type != 1886614376) { // pssh
        console.assert('yikes not right')
    }
    const fbHeader = view.getUint32(8)
    const version = fbHeader >>> 24
    const flags = fbHeader & 0x00FFFFFF;
    if (version == 1) {
        const systemId = hexFromAb(buffer.slice(12, 28))
        const kidCount = view.getUint32(28)
        const keyIds = []
        for (let i = 0; i < kidCount; i++) {
            keyIds.push(hexFromAb(buffer.slice(32 + (i * 16), 48 + (i * 16))))
        }
        return {
            systemId,
            keyIds
        }
    } else {
        console.assert('unknown version')
    }
}

Copy link
Collaborator

@robwalch robwalch Jan 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mp4pssh parsing has been added to utils/mp4-tools.ts:

https://github.com/video-dev/hls.js/blob/v1.3.0-beta.2/src/utils/mp4-tools.ts#L1108

@robwalch robwalch changed the base branch from feature/v1.0.0 to master December 23, 2020 18:05
@robwalch robwalch added the DRM label Apr 6, 2021
@robwalch robwalch added this to the 1.3.0 milestone Jul 16, 2022
@robwalch robwalch added the Stale label Jul 16, 2022
@robwalch robwalch mentioned this pull request Sep 28, 2022
3 tasks
Copy link
Collaborator

@robwalch robwalch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to include the license generation/key pair config, and transmux change on top of #4930. Since this PR has been open for some time, I can merge this changes into a working branch and rebase from there.

@botengyao let me know if that sounds good to you or if you'd prefer to rebase after #4930 is merged.

clearkeyServerUrl should not be needed as we're adding config.drmSystems[key-system] blocks for all license and certificate URLs. I am thinking that is also where config.drmSystems['org.w3.clearkey'].keyPairs can go (we should support multiple key-pairs no?).

@botengyao
Copy link
Author

botengyao commented Oct 5, 2023

Hi @robwalch, sorry, it is long time and I am not actively working on this. Feel free to reassign if someone wants to take over. Re-branching to the latest works for me with the best convenience.

@robwalch
Copy link
Collaborator

robwalch commented Oct 5, 2023

Thank you @botengyao. Apologies for not finding a path to accepting these contributions earlier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

3 participants