BuyDRM

PlayReady, Widevine and Fairplay

USP supports the KeyOS Encryption Key API over SOAP.

KeyOS Encryption Key API

The KeyOS Encryption Key API can be called by passing a SOAP XML message for instance with curl:

#!/bin/bash

curl -X POST \
--header "Content-Type: text/xml; charset=utf-8" \
--header "SOAPAction: http://tempuri.org/ISmoothPackager/RequestEncryptionInfo" \
-d @request.xml \
https://package.licensekeyserver.com/pck/

Note: Please use SSL when in production. For development, simple HTTP may be used.

The above command sends whatever is in a request.xml file using a POST request. The command also makes sure that the request contains correct headers so that the KeyOS Encryption API can interpret the request correctly.

All script samples below use the same request.xml to get keys required for encryption.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <RequestEncryptionInfo xmlns="http://tempuri.org/">
      <ServerKey>YOUR_SERVER_KEY</ServerKey>
      <RequestXml><![CDATA[
        <KeyOSEncryptionInfoRequest>
          <APIVersion>5.0.0.2</APIVersion>
          <DRMType>smooth</DRMType>
          <EncoderVersion>Unified Streaming Platform (USP) version=1.7.18</EncoderVersion>
          <UserKey>YOUR_USER_KEY</UserKey>
          <KeyID>YOUR_KEY_ID</KeyID>
          <ContentID>YOUR_CONTENT_ID</ContentID>
          <fl_GeneratePRHeader>true</fl_GeneratePRHeader>
          <fl_GenerateWVHeader>true</fl_GenerateWVHeader>
          <MediaID>YOUR_MEDIA_ID</MediaID>
        </KeyOSEncryptionInfoRequest>]]>
      </RequestXml>
    </RequestEncryptionInfo>
  </soap:Body>
</soap:Envelope>

Let’s go over each property:

ServerKey (Required) - This is a value generated once. The BuyDRM Team will provide it to you. This value will remain static and must be treated with caution and protected at all cost.

UserKey (Required) - Uniquely identifies the KeyOS Customer. It changes on a per BuyDRM KeyOS customer basis.

KeyID (Required) GUID or GUID Base64 – This field’s purpose is to enable the encoding platform to specify the exact Key ID which in turn allows the KeyOS platform to support a blend of business models.

ContentID (Required) GUID or GUID Base64 – Defines a unique file ID within the KeyOS infrastructure. Each request must have a unique Content ID.

fl_GeneratePRHeader (Optional) – Defines whether you want the API to return a fully prepared PlayReady header for you to insert into your content.

fl_GenerateWVHeader (Optional) – Defines whether you want the API to return a fully prepared PSSH box for you to insert into your content.

MediaID (Required) - This field may be considered a filename that will be saved in the KeyOS infrastructure.

APIVersion (Required) – Field reserved for the KeyOSAPI version management. Current value: 5.0.0.2

DRMType (Required) - Points to the type of KeyOS DRM you are requesting. Value: smooth

EncoderVersion (Required) - Your version of your encoding platform. Example: MyEncoder v1.0.0.5

Please note that a UUID can be generated with uuid on Linux or with Python.

When all fields are filled in correctly the KeyOS API will return a RequestEncryptionInfoResponse SOAP message containing a RequestEncryptionInfoResult in which the key id (GUID), a content key (base64) and the license server acquisition url can be found. These three need to be passed to mp4split, with the kid as a UUID converted to hex (base16) and the content key converted to hex (base16) as well.

These values then can be use for dynamic as well as static packaging:

On-the-fly DRM

Example for generating a server manifest with on-the-fly DRM:

#!/bin/bash

mp4split -o video.ism  \
 --iss.key=${kid16}:${cek16} \
 --iss.license_server_url=${la_url}
 video.ismv

Note

The options must be specified before the input files.

Prepackage content

Prepackage the audio stream:

#!/bin/bash

mp4split -o video-64k.isma  \
  --timescale=10000000 \
  --iss.key=${kid16}:${cek16} \
  --iss.license_server_url=${la_url} \
  video-64k.mp4

Prepackage the video stream:

#!/bin/bash

mp4split -o video-250k.ismv  \
  --timescale=10000000 \
  --iss.key=${kid16}:${cek16} \
  --iss.license_server_url=${la_url} \
  video-250k.mp4

Generate the server manifest:

#!/bin/bash

mp4split -o video.ism video-64k.isma video-250k.ismv

Generate the client manifest:

#!/bin/bash

mp4split -o video.ismc video.ism/Manifest

Applying PlayReady DRM

The script below shows you how to request keys from the KeyOS Encryption Key API and apply PlayReady DRM to an mp4 file.

#!/bin/bash

# make a request to the API
key_request=`curl -X POST \
--header "Content-Type: text/xml; charset=utf-8" \
--header "SOAPAction: http://tempuri.org/ISmoothPackager/RequestEncryptionInfo" \
-d @request.xml \
https://packager.licensekeyserver.com/pck/`

# clean the response
key_request_clean=`echo $key_request | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find(".//{http://tempuri.org/}RequestEncryptionInfoResult").text'`

# get the required values
kid=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("KeyID").text'`
cek=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("ContentKey").text'`
laurl=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("LicAcqURL").text'`

# convert kid and cek to hex (base16)
kid16=`python -c "import uuid,base64; print base64.b16encode(uuid.UUID('$kid').bytes);"`
cek16=`python -c "import uuid,base64; print base64.b16encode(base64.b64decode('$cek'));"`

# apply PlayReady DRM
mp4split -o output.ism \
  --iss.key=${kid16}:${cek16} \
  --iss.license_server_url=${laurl} \
  input.mp4

Applying Widevine DRM

The script below shows you how to request keys from the KeyOS Encryption Key API and apply Widevine DRM to an mp4 file.

#!/bin/bash

# make a request to the API
key_request=`curl -X POST \
--header "Content-Type: text/xml; charset=utf-8" \
--header "SOAPAction: http://tempuri.org/ISmoothPackager/RequestEncryptionInfo" \
-d @request.xml \
https://packager.licensekeyserver.com/pck/`

# clean the response
key_request_clean=`echo $key_request | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find(".//{http://tempuri.org/}RequestEncryptionInfoResult").text'`

# get the required values
kid=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("KeyID").text'`
cek=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("ContentKey").text'`
pssh=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("WVHeader").text'`

# SSL is also available - https://wv-keyos.licensekeyserver.com/
laurl='http://wv-keyos.licensekeyserver.com/'

# convert kid and cek to hex (base16)
kid16=`python -c "import uuid,base64; print base64.b16encode(uuid.UUID('$kid').bytes);"`
cek16=`python -c "import uuid,base64; print base64.b16encode(base64.b64decode('$cek'));"`

# apply Widevine DRM
mp4split -o output.ism \
  --widevine.key=${kid16}:${cek16} \
  --widevine.license_server_url=${laurl} \
  --widevine.drm_specific_data=${pssh} \
  input.mp4

Note how in this example, we do not use the license acquisition URL available in a response and instead use the hardcoded value. This is because the API response will change in the future to include all possible license acquisition URLs.

Applying FairPlay DRM

The script below shows you how to request keys from the KeyOS Encryption Key API and apply FairPlay DRM to an mp4 file.

#!/bin/bash

# make a request to the API
key_request=`curl -X POST \
--header "Content-Type: text/xml; charset=utf-8" \
--header "SOAPAction: http://tempuri.org/ISmoothPackager/RequestEncryptionInfo" \
-d @request.xml \
https://packager.licensekeyserver.com/pck/`

# clean the response
key_request_clean=`echo $key_request | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find(".//{http://tempuri.org/}RequestEncryptionInfoResult").text'`

# get the required values
kid=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("KeyID").text'`
cek=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("ContentKey").text'`

# convert kid and cek to hex (base16)
kid16=`python -c "import uuid,base64; print base64.b16encode(uuid.UUID('$kid').bytes);"`
cek16=`python -c "import uuid,base64; print base64.b16encode(base64.b64decode('$cek'));"`

# LA URL for FairPlay. For the clients.
# http://fp-keyos.licensekeyserver.com/getkey
# SSL is also available - https://fp-keyos.licensekeyserver.com/getkey
#
# For the manifest.
laurl='skd://'$kid16

# apply FairPlay DRM
mp4split -o output.ism \
  --hls.key=:${cek16} \
  --hls.key_iv=${kid16} \
  --hls.license_server_url=${laurl} \
  --hls.playout=sample_aes_streamingkeydelivery
  input.mp4

Note that the license acquisition URL is set to a specific value, as in the Widevine case.

Making a Multi-DRM Asset

The script below shows you how to request keys from the KeyOS Encryption Key API and apply Multi-DRM to an mp4 file.

#!/bin/bash

# make a request to the API
key_request=`curl -X POST \
--header "Content-Type: text/xml; charset=utf-8" \
--header "SOAPAction: http://tempuri.org/ISmoothPackager/RequestEncryptionInfo" \
-d @request.xml \
https://packager.licensekeyserver.com/pck/`

# clean the response
key_request_clean=`echo $key_request | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find(".//{http://tempuri.org/}RequestEncryptionInfoResult").text'`

# get the required values
kid=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("KeyID").text'`
cek=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("ContentKey").text'`
pr_laurl=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("LicAcqURL").text'`
pssh=`echo $key_request_clean | python -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print tree.find("WVHeader").text'`

# SSL is also available - https://wv-keyos.licensekeyserver.com/
wv_laurl='http://wv-keyos.licensekeyserver.com/'

# convert kid and cek to hex (base16)
kid16=`python -c "import uuid,base64; print base64.b16encode(uuid.UUID('$kid').bytes);"`
cek16=`python -c "import uuid,base64; print base64.b16encode(base64.b64decode('$cek'));"`

# LA URL for FairPlay. For the clients.
# http://fp-keyos.licensekeyserver.com/getkey
# SSL is also available - https://fp-keyos.licensekeyserver.com/getkey
#
# For the manifest.
laurl='skd://'$kid16

# apply PlayReady, Widevine and FairPlay DRM
mp4split -o output.ism \
  --iss.key=${kid16}:${cek16} \
  --iss.license_server_url=${pr_laurl} \
  --widevine.key=${kid16}:${cek16} \
  --widevine.license_server_url=${wv_laurl} \
  --widevine.drm_specific_data=${pssh} \
  --hls.key=:${cek16} \
  --hls.key_iv=${kid16} \
  --hls.license_server_url=${fp_laurl} \
  --hls.playout=sample_aes_streamingkeydelivery
  input.mp4

Testing packaged content

To acquire a license from the KeyOS MultiKey Service, you need to make sure your client sends the Authentication XML – the security token that defines the license policies.

We will be using Python script to generate the minimum required Authentication XML to be able to acquire FairPlay, PlayReady and Widevine licenses. Python is used just as an example; one may use JavaScript, for example, or other appropriate tools.

Prerequisites

Before you run a script, make sure that you use Python 2.7 and you have pycrypto installed.

You will need openssl installed on your machine and the X.509 certificate to uniquely sign your Authentication XML for security reasons.

You can download the RSA Key Generation tool from your KeyOS Account (Products page). Once you have generated keys using that tool you must do the following:

  • Provide BuyDRM Support Team with .pub file using KeyOS Support system
  • Run the following command to generate .der file
#!/bin/bash

openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt \
  -in 7e114004397d99ddc7dccd29d0174c67.pem \
  -out 7e114004397d99ddc7dccd29d0174c67.der

Pycrypto refuses to work with keys that are not secured with a passphrase. The command above will ask you for a passphrase before creating the key.

Note: the name of the .pem and resulting .der file must be the same and will differ from what you see here.

'''
Before running this script, make sure that your .pem file is encrypted. You can add
encryption to your file using the following openssl command:

openssl rsa -des -in 97d999d0174c6743dd7e11400c7dccd2.pem -out 97d999d0174c6743dd7e11400c7dccd2.der

The 97d999d0174c6743dd7e11400c7dccd2part is just an example; you need to put the name of your own
key here which was provided to you by BuyDRM Support team or you have generated on your own using
tools provided to you by the BuyDRM Support team.
'''

import xml.etree.ElementTree as ET
import sys
import datetime
import uuid

# pycrypto
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from base64 import b64encode, b64decode

# The name of the key will be used later in authentication XML.
key_name =  97d999d0174c6743dd7e11400c7dccd2

# Import key and create a signer.
rsakey = RSA.importKey(open(97d999d0174c6743dd7e11400c7dccd2.der', 'r').read(), passphrase='your passphrase')
signer = PKCS1_v1_5.new(rsakey)
digest = SHA.new()

'''
Create the structure of the authentication XML. The same below creates a simple authentication XML
which can be used to acquire PlayReady, Widevine and FairPlay licenses. It doesn't specify any license policies
or sets license persistency. The license will be valid for a one-time playback which is usually
enough to make tests.
'''
data_el = ET.Element('Data')

# Generation time of the authentication XML.
generation_time = datetime.datetime.utcnow()

'''
Set the authentication XMLs' expiration time to time which is enough to acquire a license - a minute
or even less. For tests you can use bigger values like a month or a year. Please note that in production
large values expose you to a risk of your authentication XML being re-used over and over again
to get a license.
'''
expiration_time = generation_time + datetime.timedelta(minutes = 100)

# The format of the date should be the following: YYYY-MM-DD HH:mm:ss.SSS
ET.SubElement(data_el, 'GenerationTime').text = generation_time.strftime('%Y-%m-%d %H:%M:%S.%f')[0:23]
ET.SubElement(data_el, 'ExpirationTime').text = expiration_time.strftime('%Y-%m-%d %H:%M:%S.%f')[0:23]

# Make sure that every authentication XML is unique.
ET.SubElement(data_el, 'UniqueId').text = str(uuid.uuid4())

# The name of your PEM file.
ET.SubElement(data_el, 'RSAPubKeyId').text = key_name

# Add Widevine license related properties.
ET.SubElement(data_el, 'WidevinePolicy', { 'fl_CanPlay': 'true' })

wv_content_key_spec_el = ET.SubElement(data_el, 'WidevineContentKeySpec', { 'TrackType': 'HD' })
ET.SubElement(wv_content_key_spec_el, 'SecurityLevel').text = '1'

# Add PlayReady license related properties.
ET.SubElement(data_el, 'License', { 'type': 'simple' })

# Add FairPlay license related properties.
ET.SubElement(data_el, 'FairPlayPolicy')

'''
Sign the authentication XML (only the Data tag and its contents are signed). The signature uniquely
identifies your KeyOS account when your clients/players request a license.
'''
digest.update(ET.tostring(data_el))
signature = b64encode(signer.sign(digest));

'''
Finish the authentication XML by adding a signature into it as well as wrapping Data element.
'''
root_el = ET.Element('KeyOSAuthenticationXML')
root_el.append(data_el)

ET.SubElement(root_el, 'Signature').text = signaturema

print b64encode(ET.tostring(root_el))

The result of the script execution will be a Base64 encoded string that you must pass to your clients.

If the script finishes, that means it worked successfully and you should see something like this in Base64 format:

PEtleU9TQXV0aGVudGljYXRpb25YTUw+PERhdGE+PEdlbmVyYXRpb25UaW1lPjIwMTctMDEtMjAgMTc6MzI6MjIuMzExPC9HZW5lcmF0aW9uVGltZT48RXhwaXJhdGlvblRpbWU+MjAxNy0wMS0yMCAxOToxMjoyMi4zMTE8L0V4cGlyYXRpb25UaW1lPjxVbmlxdWVJZD44ZTc4MjI5YjA0OTk0YTRlYTRiNGVkNDM1MWRhZWZmZTwvVW5pcXVlSWQ+PFJTQVB1YktleUlkPjdlMTE0MDBjN2RjY2QyOWQwMTc0YzY3NDM5N2Q5OWRkPC9SU0FQdWJLZXlJZD48V2lkZXZpbmVQb2xpY3kgZmxfQ2FuUGxheT0idHJ1ZSIgZmxfQ2FuUGVyc2lzdD0iZmFsc2UiIC8+PFdpZGV2aW5lQ29udGVudEtleVNwZWMgVHJhY2tUeXBlPSJIRCI+PFNlY3VyaXR5TGV2ZWw+MTwvU2VjdXJpdHlMZXZlbD48L1dpZGV2aW5lQ29udGVudEtleVNwZWM+PEZhaXJQbGF5UG9saWN5IC8+PExpY2Vuc2UgdHlwZT0ic2ltcGxlIiAvPjwvRGF0YT48U2lnbmF0dXJlPm5uVngzYzhtTVI4QjEycUdsZVNYY1VHa0lWWnYyYzVXQTRaOVJ0TFFGR3Y5MHNaRkplTnBFWTQ4c0ExTXgwa2ZDZzg4SUpnZWp2WUgyUVNyQVJXVWEwblhMSjdNSmY4bEptd2VLbEl1dHpRVjBKczZxS3BNWDNGVlBnSURSVnBwRlVRWnFRd1d5RkxDcy9haDJiVk4xTk9hYXpSSmpWQk42ZmFLcVM1QzlFMlhrNzVSTllQTHE1N2JwSDgrMStZYlQ2MERmK0swaFdQMCtZYTBRWWx2L0ZjM25vUGJvUGJPSlg3czZxNlM0SlVBS0NNQTU3cWw2RGF4NjZ4YjJ2VlZlTTVwYzgrMzNyYnptR081VFhOM1BlTXZPbmIzYXdxTlA3RW9JQzIrWi9mWmovNW5vMWJ1TU44eTRNeFVNd1F1R0Z3dTlKOVFBUkRzbkJqZytBaHlQZz09PC9TaWduYXR1cmU+PC9LZXlPU0F1dGhlbnRpY2F0aW9uWE1MPg==

Here is the decoded version. This version is not used anywhere in your client, just a Base64 version above:

<KeyOSAuthenticationXML>
  <Data>
    <GenerationTime>2017-01-20 17:32:22.311</GenerationTime>
    <ExpirationTime>2017-01-20 19:12:22.311</ExpirationTime>
    <UniqueId>8e78229b04994a4ea4b4ed4351daeffe</UniqueId>
    <RSAPubKeyId>97d999d0174c6743dd7e11400c7dccd2</RSAPubKeyId>
    <WidevinePolicy fl_CanPlay="true" fl_CanPersist="false" />
    <WidevineContentKeySpec TrackType="HD">
      <SecurityLevel>1</SecurityLevel>
    </WidevineContentKeySpec>
    <FairPlayPolicy />
    <License type="simple" />
  </Data>
  <Signature>nnVx3c8mMR8B12qGleSXcUGkIVZv2c5…+AhyPg==</Signature>
</KeyOSAuthenticationXML>

Setting up the client

Please follow your client’s instructions on how to set it up to play back DRM protected content. From the list below, you should be able to define the available license acquisition URLs.

You should also be able to define the custom data that should contain the Authentication XML you generated. If your client does not allow setting the custom data specifically, you can set the customdata header and pass your Authentication XML in it.

As an example, we will set up Bitmovin HTML5 player.

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="//path to your playerbitmovinplayer.min.js"></script>
  <meta charset="UTF-8">
  <title>Sample Bitmovin Setup</title>
</head>
<body>
  <div id="player"></div>

  <script type="text/javascript">
    var conf = {
        // This needs to be set to your player key
        key: '12345678-1234-1234-1234-123456789012',
        source: {
          // Path to your dash content.
          dash: "//yourdomain.com/your/usp/server/manifest.ism/.mpd"

          // Or path to m3u8 in case of HLS with applied FairPlay DRM.
          //hls: "//yourdomain.com/your/usp/server/manifest.ism/.m3u8"
        },

        // Setting up DRM
        drm: {
          playready: {
            LA_URL: 'https://pr-keyos.licensekeyserver.com/core/rightsmanager.asmx',

            // You need to add actual authentication XML here in base64 encoded format.
            customData: 'PEtleU9TQX...TD4='
          },

          widevine: {
            LA_URL: 'https://wv-keyos.licensekeyserver.com/',
            headers : [{
              // Same as customdata
              name: 'customdata',

              // You need to add actual authentication XML here in base64 encoded format.
              value: 'PEtleU9TQX...TD4='
            }]

          },

          fairplay: {
            LA_URL: 'https://fp-keyos.licensekeyserver.com/getkey',

            // FairPlay's certificate. Please ask BuyDRM Support team for the value.
            certificateURL: 'https://fp-keyos.licensekeyserver.com/cert/[Name of the RSA key for Authentication XML signature].der',

            headers: [{
              // Same as customdata
              name: 'customdata',

              // You need to add actual authentication XML here in base64 encoded format.
              value: 'PEtleU9TQX...TD4='
            }]
          }
        }
    };
     // Init player.
    var player = bitmovin.player("player");

    // Set it up with configuration object we declared before.
    player.setup(conf).then(function(value) {
        // Success