BuyDRM

Using the 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://packager.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

Adding 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 | python3 -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 | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("KeyID").text)'`
cek=`echo $key_request_clean | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("ContentKey").text)'`
laurl=`echo $key_request_clean | python3 -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=`python3 -c "import uuid,base64; print(base64.b16encode(uuid.UUID($kid).bytes).decode("utf-8"));"`
cek16=`python3 -c "import uuid,base64; print(base64.b16encode(base64.b64decode($cek)).decode("utf-8"));"`

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

Adding 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 | python3 -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 | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("KeyID").text)'`
cek=`echo $key_request_clean | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("ContentKey").text)'`
pssh=`echo $key_request_clean | python3 -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=`python3 -c "import uuid,base64; print(base64.b16encode(uuid.UUID($kid).bytes.decode("utf-8")));"`
cek16=`python3 -c "import uuid,base64; print(base64.b16encode(base64.b64decode($cek)).decode("utf-8"));"`

# apply Widevine DRM
mp4split -o output.ism \
  --widevine.key=${kid16}:${cek16} \
  --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.

Adding 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 | python3 -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 | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("KeyID").text)'`
cek=`echo $key_request_clean | python3 -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=`python3 -c "import uuid,base64; print(base64.b16encode(uuid.UUID($kid).bytes).decode("utf-8"));"`
cek16=`python3 -c "import uuid,base64; print(base64.b16encode(base64.b64decode($cek)).decode("utf-8"));"`

# 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.

Static Packaging

The above examples all apply to the Origin and describe how to call the BuyDRM API to create a server manifest to be used with the Origin, for dynamic packaging.

But what if the use is case is Using late binding of audio/video groups with SAMPLE-AES for Apple FairPlay? The example there describes the use of a 32 byte file containing a 128-bit CEK (Content Encryption Key) and 128-bit IV (Initialization Vector).

For this case the same API call can be used, but the results are not passed on the command line as with previous example: the cek16 and kid16 need to be concatenated instead.

#!/bin/bash

# API call and key extraction as in previous example.

echo ${cek16}${kid16} > presentation.key

# Followed by use of mp4split as per packaging example mentioned in the next
# paragraph.

At this point you will have a 'presentation.key' file containing 32 bytes, the first 16 being the 128-bit CEK, the second 16 bytes being the 128-bit IV. Next step is to follow the example as presented in Using late binding of audio/video groups with SAMPLE-AES for Apple FairPlay.

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 | python3 -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 | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("KeyID").text)'`
cek=`echo $key_request_clean | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("ContentKey").text)'`
pr_laurl=`echo $key_request_clean | python3 -c 'import sys, xml.etree.cElementTree as et; tree = et.parse(sys.stdin); print(tree.find("LicAcqURL").text)'`
pssh=`echo $key_request_clean | python3 -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=`python3 -c "import uuid,base64; print(base64.b16encode(uuid.UUID($kid).bytes).decode("utf-8"));"`
cek16=`python3 -c "import uuid,base64; print(base64.b16encode(base64.b64decode($cek)).decode("utf-8"));"`

# 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.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 3.6 or higher, and you have pycryptodomex 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

pycryptodomex 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

# pycryptodomex
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.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