kaltura / nginx-vod-module

NGINX-based MP4 Repackager
GNU Affero General Public License v3.0
2k stars 439 forks source link

How to set different drm(clearkey) when I use adaptive bit rate by DASH? #1344

Open seedoubleu opened 2 years ago

seedoubleu commented 2 years ago

hi, i have some confuse with DASH DRM configuration, can you help me? The situation as follows.

I use local mode, nginx configure:

location /vod_dash {
            vod dash;
            vod_mode local;

            vod_align_segments_to_key_frames on;

            vod_dash_manifest_format segmenttemplate;
            vod_dash_profiles urn:mpeg:dash:profile:isoff-live:2011;

            vod_drm_enabled on;
            vod_drm_clear_lead_segment_count 0;
            vod_drm_upstream_location /php_proxy/;
            vod_drm_request_uri /dash_clear_key.php;

            add_header Access-Control-Allow-Headers '*';
            add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range,Date';
            add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
            add_header Access-Control-Allow-Origin '*';

            alias /media;
}

the PHP file is: "https://github.com/kaltura/nginx-vod-module/blob/master/test/dash_clear_key.php"

and the manifest for adaptive bit rate is: "https://192.168.192.128/vod_dash/,hdh/,hdk/,example.mp4.urlset/manifest.mpd"

How can I configure different DRM for "hdh/example.mp4" and "hdk/example.mp4" ? Modify the “/dash_clear_key.php” or change the mode to mapped mode? is there any simple example for that?

erankor commented 2 years ago

If by 'different DRM' you mean 'different key' then yes - you need to update the php file. This file is only a sample... it returns a single key id, and hashes it to generate the key. In a real implementation, you'd probably want to return a key id that is derived from the id of the video.

seedoubleu commented 2 years ago

Yes,I mean 'different key'.   And I tried different ways for it. In my understanding, the PHP file will generate the different 'key' and the corresponding 'kid', and encrypt the video with 'key', when play the video we need decrypt the video, so we pass 'kid' to PHP and generate the corresponding 'license'. A pair of 'key' and 'license' is not the same thing, but they have same 'kid'.  Is there any mistake in my mind?  What confused me is how to tell nginx-vod-module which 'key' to use, I found the attribute named "encryptionKey" and "encryptionIv" in 'mapped' mode, but I thought it just used for HLS, and there is no attribute to set 'kid'. Similarly, I try to find the way to configure 'kid' to different video when I use 'local' mode.  Can you help me with these?

erankor commented 2 years ago

The idea in the sample PHP is that it will generate a kid according to some id of the media. It gets the full URL on the 'encryption' request, so it can parse the media id out of it. Since this is implementation-specific, this part is not implemented, and there's a TODO for it. In the sample, the key is just a hash of the kid with some secret string, you can of course implement something more sophisticated, but IMHO for clear-key, it's good enough.

There are 3 todos in the sample code that you need to close in order to use it in the real world -

  1. Update the hashing secret to something stronger
  2. Add some security controls to decide who can get the key
  3. Write some code to derive the kid from the URL

Regarding encryptionKey/Iv, you don't need to use them here, the module will use the key returned in the JSON built by the PHP script.

seedoubleu commented 2 years ago

Thanks erankor, I have seen the content of the PHP file, and make some change.

php file:

<?php

if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
    // get liense
    $key1 = array('k' => '6JDXwILoGcZuYDKGNPiXgA', 'kty' => 'oct', 'kid' => 'ASNFZ4mrze8BI0VniavN7w');
    $key2 = array('k' => 'XHYyt3r8i4EDVtu3HoFl1g', 'kty' => 'oct', 'kid' => 'ASNFZ4kBI0VniavN76vN7w');
    $license = array('keys' => array($key1, $key2), 'type' => 'temporary');

    echo str_replace('\\/', '/', json_encode($license));
    header('access-control-allow-origin: *');
}
else
{
    $currentUrl = $_SERVER["REQUEST_URI"];

    // get key
    $pssh1 = array('uuid' => '1077efec-c0b2-4d02-ace3-3c1e52e2fb4b', 'data' => 'AAAAAQEjRWeJq83vASNFZ4mrze8AAAAA');
    $encrpytion1 = array('key' => '6JDXwILoGcZuYDKGNPiXgA==', 'key_id' => 'ASNFZ4mrze8BI0VniavN7w==', 'pssh' => array($pssh1));

    $pssh2 = array('uuid' => '1077efec-c0b2-4d02-ace3-3c1e52e2fb4b', 'data' => 'AAAAAQEjRWeJASNFZ4mrze+rze8AAAAA');
    $encrpytion2 = array('key' => 'XHYyt3r8i4EDVtu3HoFl1g==', 'key_id' => 'ASNFZ4kBI0VniavN76vN7w==', 'pssh' => array($pssh2));

    if (strpos($currentUrl, 'f1') !== false)
        $encrpytion = array($encrpytion1/*, $encrpytion2*/);
    else
        $encrpytion = array($encrpytion2);

    echo str_replace('\\/', '/', json_encode($encrpytion));
}

?>

and if URL content string 'f1', GET request return key that convert from "0123456789abcdef0123456789abcdef":

[
    {
        "key": "6JDXwILoGcZuYDKGNPiXgA==",
        "key_id": "ASNFZ4mrze8BI0VniavN7w==",
        "pssh": [
            {
                "uuid": "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b",
                "data": "AAAAAQEjRWeJq83vASNFZ4mrze8AAAAA"
            }
        ]
    }
]

and if URL do not content string 'f1', GET request return key that convert from "01234567890123456789abcdefabcdef":

[
    {
        "key": "XHYyt3r8i4EDVtu3HoFl1g==",
        "key_id": "ASNFZ4kBI0VniavN76vN7w==",
        "pssh": [
            {
                "uuid": "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b",
                "data": "AAAAAQEjRWeJASNFZ4mrze+rze8AAAAA"
            }
        ]
    }
]

and POST request return licenses:

{
    "keys": [
        {
            "k": "6JDXwILoGcZuYDKGNPiXgA",
            "kty": "oct",
            "kid": "ASNFZ4mrze8BI0VniavN7w"
        },
        {
            "k": "XHYyt3r8i4EDVtu3HoFl1g",
            "kty": "oct",
            "kid": "ASNFZ4kBI0VniavN76vN7w"
        }
    ],
    "type": "temporary"
}

it can play successfully, but when I check manifest.mpd:

<?xml version="1.0"?>
<MPD
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="urn:mpeg:dash:schema:mpd:2011"
    xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
    type="static"
    mediaPresentationDuration="PT1880.979S"
    minBufferTime="PT10S"
    profiles="urn:mpeg:dash:profile:isoff-live:2011">
  <Period>
    <AdaptationSet
        id="1"
        segmentAlignment="true"
        maxWidth="1280"
        maxHeight="720"
        maxFrameRate="25">
        <SegmentTemplate
            timescale="1000"
            media="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-$Number$-$RepresentationID$.m4s"
            initialization="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/init-$RepresentationID$.mp4"
            duration="10000"
            startNumber="1">
        </SegmentTemplate>
      <Representation
          id="f1-v1-x3"
          mimeType="video/mp4"
          codecs="avc1.640029"
          width="1280"
          height="720"
          frameRate="25"
          sar="1:1"
          startWithSAP="1"
          bandwidth="549135">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
      <Representation
          id="f2-v1-x3"
          mimeType="video/mp4"
          codecs="avc1.42c01e"
          width="760"
          height="428"
          frameRate="25"
          sar="1:1"
          startWithSAP="1"
          bandwidth="178233">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
    </AdaptationSet>
    <AdaptationSet
        id="2"
        segmentAlignment="true">
      <AudioChannelConfiguration
          schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
          value="1"/>
        <SegmentTemplate
            timescale="1000"
            media="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-$Number$-$RepresentationID$.m4s"
            initialization="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/init-$RepresentationID$.mp4"
            duration="10000"
            startNumber="1">
        </SegmentTemplate>
      <Representation
          id="f1-a1-x3"
          mimeType="audio/mp4"
          codecs="mp4a.40.2"
          audioSamplingRate="44100"
          startWithSAP="1"
          bandwidth="64000">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
      <Representation
          id="f2-a1-x3"
          mimeType="audio/mp4"
          codecs="mp4a.40.2"
          audioSamplingRate="44100"
          startWithSAP="1"
          bandwidth="48000">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

I can see the default_KID="01234567-8901-2345-6789-abcdefabcdef" in the ‘ContentProtection‘ field, that the different video use the same KID to encryption.

and my Request URL, the m4s file use the default name:

……
Request URL: https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-1-f1-v1-x3.m4s
……
Request URL: https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-67-f2-v1-x3.m4s
……

I use "if (strpos($currentUrl, 'f1') !== false)" in PHP file to select the key when encryption, but it seems not work. Is there any mistake here?

erankor commented 2 years ago

You don't get the file name part of the URL (/fragment...m4s), so you can't use 'f1', it has to be something in the path.

seedoubleu commented 2 years ago

I change PHP , and use "basename" to get URL file name:

$clipName = basename($currentUrl,".m4s");
if (strpos($clipName, 'f1') !== false)

still not the expected result. Maybe I didn't get what you mean, Is there a simple example?

erankor commented 2 years ago

As I wrote - the module does NOT send the file name part of the request. The file name part, for example fragment-1-f1-v1-x3.m4s changes every segment, and it doesn't make sense to have a separate DRM request for each segment... I suggest you will just print the URL you get on the PHP to some log, and then you will see it...

seedoubleu commented 2 years ago

 Oh, thanks. Now I saw all request to PHP server is the same "https://.../dash_clear_key.php", the URL doesn't content file name, and I also found each segment has a request to PHP server.  But if URL doesn't contain file name, how can I tell module to encrypt the video with the right key?  I am trying adaptive bit rate, there are two video with different bit rate "flash_hdh/cwgl0101.mp4" and "flash_hdk/cwgl0101.mp4", and I want to encrypt different video with different key. I am still confused with these.

erankor commented 2 years ago

You set 'vod_drm_request_uri /dash_clear_key.php;', so that's what you get... You can add to it $uri or $vod_suburi etc.

seedoubleu commented 2 years ago

Hi erankor, thanks for your help. I've solved my problem. The situation is as follow.

I added $vod_suburi to vod_drm_request_uri:

vod_drm_request_uri /dash_clear_key.php$vod_suburi;

and the Request URL to PHP server:

……
GET //dash_clear_key.php/vod_dash/cjcwgl/flash_hdh/cwgl0101.mp4 HTTP/1.0"
……
GET //dash_clear_key.php/vod_dash/cjcwgl/flash_hdk/cwgl0101.mp4 HTTP/1.0"
……

so that the PHP server can recognize the different video:

if (strpos($currentUrl, 'hdh') !== false) // just for test

and the manifest.mpd file:

<?xml version="1.0"?>
<MPD
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="urn:mpeg:dash:schema:mpd:2011"
    xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
    type="static"
    mediaPresentationDuration="PT1880.979S"
    minBufferTime="PT10S"
    profiles="urn:mpeg:dash:profile:isoff-live:2011">
  <Period>
    <AdaptationSet
        id="1"
        segmentAlignment="true"
        maxWidth="1280"
        maxHeight="720"
        maxFrameRate="25">
        <SegmentTemplate
            timescale="1000"
            media="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-$Number$-$RepresentationID$.m4s"
            initialization="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/init-$RepresentationID$.mp4"
            duration="10000"
            startNumber="1">
        </SegmentTemplate>
      <Representation
          id="f1-v1-x3"
          mimeType="video/mp4"
          codecs="avc1.640029"
          width="1280"
          height="720"
          frameRate="25"
          sar="1:1"
          startWithSAP="1"
          bandwidth="549135">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-89ab-cdef-0123-456789abcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniavN7wEjRWeJq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
      <Representation
          id="f2-v1-x3"
          mimeType="video/mp4"
          codecs="avc1.42c01e"
          width="760"
          height="428"
          frameRate="25"
          sar="1:1"
          startWithSAP="1"
          bandwidth="178233">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
    </AdaptationSet>
    <AdaptationSet
        id="2"
        segmentAlignment="true">
      <AudioChannelConfiguration
          schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
          value="1"/>
        <SegmentTemplate
            timescale="1000"
            media="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/fragment-$Number$-$RepresentationID$.m4s"
            initialization="https://192.168.192.128/vod_dash/cjcwgl/flash_,hdh/,hdk/,cwgl0101.mp4.urlset/init-$RepresentationID$.mp4"
            duration="10000"
            startNumber="1">
        </SegmentTemplate>
      <Representation
          id="f1-a1-x3"
          mimeType="audio/mp4"
          codecs="mp4a.40.2"
          audioSamplingRate="44100"
          startWithSAP="1"
          bandwidth="64000">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-89ab-cdef-0123-456789abcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniavN7wEjRWeJq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
      <Representation
          id="f2-a1-x3"
          mimeType="audio/mp4"
          codecs="mp4a.40.2"
          audioSamplingRate="44100"
          startWithSAP="1"
          bandwidth="48000">
        <ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
        <ContentProtection xmlns:cenc="urn:mpeg:cenc:2013" schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="01234567-8901-2345-6789-abcdefabcdef">
          <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniQEjRWeJq83vq83vAAAAAA==</cenc:pssh>
        </ContentProtection>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>
seedoubleu commented 2 years ago

hi erankor, I have a new question, when playing video each segment send a request to DRM server, if I want make the License that will expire after a period of time (like 5 minutes), or make the License just one-off. Is that feasible?

erankor commented 2 years ago

Don't know, you should probably check whatever spec defines the clear key license response. But, IMHO, it's a bit useless, because as the name "clear key" suggests, you just provide the key to the client, so nothing prevents the client from manually decrypting the content using the key, and using it indefinitely.