You are on page 1of 10

[CmdletBinding()]

Param(
# Shaka Packager input video file path.
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$InputVideo,

# Key Server Widevine CENC API signing key.


[Parameter(Mandatory = $true)]
[ValidateLength(64,64)]
[string]$SigningKeyAsHex,

# Key Server Widevine CENC API signing IV.


[Parameter(Mandatory = $true)]
[ValidateLength(32,32)]
[string]$SigningIvAsHex,

# Key Server Widevine CENC API signer name.


[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Signer,

# Key Server Widevine CENC API URL (testing environment).


[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$KeyServerUrl =
"https://key-server-management.axtest.net/api/WidevineProtectionInfo",

# Key ID.
[Parameter(Mandatory = $false)]
[System.Guid]$KeyId = [System.Guid]::NewGuid()
)

Set-StrictMode -Version Latest


$ErrorActionPreference = "Stop"

#########
# About #
#########

<#

This script demonstrates how to use Axinom Key Server's Widevine CENC API
together with Shaka Packager to package encrypted DASH and HLS content that
can be played back using Widevine, Playready and FairPlay DRMs.
Encrypted content is created using CENC and CBCS encryption schemes. Clear
content is also created.

For more information consult Axinom Key Server documentation and Shaka Packager
online documentation.

#>

###############
# Main script #
###############

$main =
{
# The Shaka Packager executable is expected to be in the current directory.
if ((Get-Variable IsLinux -Scope Global -ErrorAction Ignore) -and ($IsLinux -eq $true))
{
$shakaPackagerExecutable = "./packager-linux";
}
else
{
$shakaPackagerExecutable = "./packager-win.exe";
}

if (!(Test-Path $shakaPackagerExecutable))
{
throw "Shaka Packager executable was not found at: $($shakaPackagerExecutable)"
}

# Shaka Packager output directory.


$outputPath = "output";

# Step 1. Get information from the Key Server.

# Let's create two content key requests: one with the CBCS scheme and the other
# with the CENC scheme, to get encryption-scheme specific PSSHs for use with
# the CBCS and CENC content respectively. Since in the current demo we'll use
# the same keys for both content, we can parse the rest of the data we from
# either response.

$cbcsContentKeyRequestJson = CreateContentKeyRequest $KeyId "CBCS";


$cencContentKeyRequestJson = CreateContentKeyRequest $KeyId "CENC";

$cbcsKeyServerRequestJson = CreateKeyServerRequest $cbcsContentKeyRequestJson


$SigningKeyAsHex $SigningIvAsHex $Signer;
$cencKeyServerRequestJson = CreateKeyServerRequest $cencContentKeyRequestJson
$SigningKeyAsHex $SigningIvAsHex $Signer;

# Post the requests to the key server.


$cbcsContentKeyResponseObject =
PostKeyServerRequestAndExtractContentKeyResponse $cbcsKeyServerRequestJson
$KeyServerUrl;
$cencContentKeyResponseObject =
PostKeyServerRequestAndExtractContentKeyResponse $cencKeyServerRequestJson
$KeyServerUrl;

# Parse the data that will be fed to Shaka Packager. From the "CENC" response
# we're currently only interested in the PSSH.
$cencTrack = $cencContentKeyResponseObject.tracks[0];
$cbcsTrack = $cbcsContentKeyResponseObject.tracks[0];

$keyIdAsBase64 = $cbcsTrack.key_id;
$keyAsBase64 = $cbcsTrack.key;
$ivAsBase64 = $cbcsTrack.iv;

$keyIdAsHex = Base64StringToHexString $keyIdAsBase64;


$keyAsHex = Base64StringToHexString $keyAsBase64;
$ivAsHex = Base64StringToHexString $ivAsBase64;

$fairPlaySkdUri = $cbcsTrack.skd_uri;

$widevineCbcsPsshBoxAsHex = "";
$widevineCencPsshBoxAsHex = "";
$playReadyCbcsPsshBoxAsHex = "";
$playReadyCencPsshBoxAsHex = "";

foreach ($pssh in $cbcsTrack.pssh)


{
if ($pssh.drm_type -eq "WIDEVINE") { $widevineCbcsPsshBoxAsHex =
PsshDataAsBase64ToPsshBoxAsHex $pssh.data $pssh.drm_type; }
if ($pssh.drm_type -eq "PLAYREADY") { $playReadyCbcsPsshBoxAsHex =
PsshDataAsBase64ToPsshBoxAsHex $pssh.data $pssh.drm_type; }
}

foreach ($pssh in $cencTrack.pssh)


{
if ($pssh.drm_type -eq "WIDEVINE") { $widevineCencPsshBoxAsHex =
PsshDataAsBase64ToPsshBoxAsHex $pssh.data $pssh.drm_type; }
if ($pssh.drm_type -eq "PLAYREADY") { $playReadyCencPsshBoxAsHex =
PsshDataAsBase64ToPsshBoxAsHex $pssh.data $pssh.drm_type; }
}
Write-Host
Write-Host "Data parsed from content key responses"
Write-Host "======================================"
Write-Host
Write-Host "Key ID (hex): $($keyIdAsHex)";
Write-Host "Key ID (b64): $($keyIdAsBase64)";
Write-Host "Key ID (Guid): $(([System.Guid]::Parse("$keyIdAsHex")))";
Write-Host "Key (hex): $($keyAsHex)";
Write-Host "Key (b64): $($keyAsBase64)";
Write-Host "IV (hex): $($ivAsHex)";
Write-Host "IV (b64): $($ivAsBase64)";
Write-Host
Write-Host "FairPlay SKD URI: $($fairPlaySkdUri)";
Write-Host
Write-Host "Widevine CBCS PSSH box:";
Write-Host $widevineCbcsPsshBoxAsHex;
Write-Host
Write-Host "Widevine CENC PSSH box:";
Write-Host $widevineCencPsshBoxAsHex;
Write-Host
Write-Host "PlayReady CBCS PSSH box:"
Write-Host $playReadyCbcsPsshBoxAsHex;
Write-Host
Write-Host "PlayReady CENC PSSH box:"
Write-Host $playReadyCencPsshBoxAsHex;

# Step 2. Use information from the Key Server to construct Shaka Packager
# arguments and call the packager.

Write-Host
Write-Host "Running Shaka Packager"
Write-Host "======================"

# Shaka takes several input PSSH boxes as one long concatenated hex string.
$cbcsPsshs = $widevineCbcsPsshBoxAsHex + $playReadyCbcsPsshBoxAsHex;
$cencPsshs = $widevineCencPsshBoxAsHex + $playReadyCencPsshBoxAsHex;

# Create clear (unencrypted) content (DASH and HLS).


PackageClearContent $InputVideo $outputPath;

# Create encrypted content using the CBCS encryption scheme (DASH and HLS).
PackageEncryptedContent $InputVideo $outputPath $keyIdAsHex $keyAsHex $cbcsPsshs
"cbcs" $ivAsHex $fairPlaySkdUri;

# Create encrypted content using the CENC encryption scheme (DASH and HLS).
PackageEncryptedContent $InputVideo $outputPath $keyIdAsHex $keyAsHex $cencPsshs
"cenc" $ivAsHex $fairPlaySkdUri;
Write-Host
Write-Host "All done!";
Write-Host
}

#############
# Functions #
#############

function StringToBase64String($string)
{
return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($string));
}

function Base64StringToString($base64)
{
return [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($base64));
}

function HexStringToBytes($hexString)
{
$bytes = [byte[]]::new($hexString.Length / 2)

for ($i=0; $i -lt $hexString.Length; $i+=2)


{
$bytes[$i/2] = [convert]::ToByte($hexString.Substring($i, 2), 16)
}

return $bytes;
}

function BytesToHexString($bytes)
{
return ($bytes | ForEach-Object ToString x2) -join '';
}

function Base64StringToHexString($base64)
{
return BytesToHexString ([Convert]::FromBase64String($base64));
}

function CreateSignature($contentKeyRequestJson, $signingKeyAsHex, $signingIvAsHex)


{
$requestBytes = [System.Text.Encoding]::UTF8.GetBytes($contentKeyRequestJson);
# Take SHA1 hash of the content key request bytes.
$sha1 = New-Object System.Security.Cryptography.SHA1Managed;
$requestHash = $sha1.ComputeHash($requestBytes);
$sha1.Dispose();

# Encrypt the hash with AES-CBC (PKCS7 padding) using the signing key and IV.
# The result is the signature.
$aes = New-Object System.Security.Cryptography.AesManaged;
$aes.KeySize = 256;
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC;
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
$aes.Key = HexStringToBytes $signingKeyAsHex;
$aes.Iv = HexStringToBytes $signingIvAsHex;

$encryptor = $aes.CreateEncryptor();
$signature = $encryptor.TransformFinalBlock($requestHash, 0, $requestHash.Length);

$encryptor.Dispose();
$aes.Dispose();

$signatureAsBase64 = [Convert]::ToBase64String($signature);

return $signatureAsBase64;
}

# As the Key Server provides "PSSH data", but Shaka Packager accepts "PSSH boxes",
# conversions must be made.
function PsshDataAsBase64ToPsshBoxAsHex($psshDataAsBase64, $drmType)
{
switch ($drmType)
{
"WIDEVINE" { $systemIdBigEndianBytes = [Byte[]] (0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed); }
"PLAYREADY" { $systemIdBigEndianBytes = [Byte[]] (0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40,
0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95); }
default { throw "PsshDataAsBase64ToPsshBoxAsHex() supports only WIDEVINE and
PLAYREADY DRM types";}
}

$dataBytes = [Convert]::FromBase64String($psshDataAsBase64);

$boxLengthBigEndianBytes = [System.BitConverter]::GetBytes(32 + $dataBytes.Length); #


Header + data.
[array]::Reverse($boxLengthBigEndianBytes);

$dataLengthBigEndianBytes = [System.BitConverter]::GetBytes($dataBytes.Length);
[array]::Reverse($dataLengthBigEndianBytes);
$boxBytes = New-Object System.Collections.Generic.List[byte];
$boxBytes.AddRange($boxLengthBigEndianBytes);
$boxBytes.AddRange([Byte[]] (0x70, 0x73, 0x73, 0x68)); # Box type ('p', 's', 's', 'h').
$boxBytes.AddRange([Byte[]] (0x00, 0x00, 0x00, 0x00)); # Version and flags.
$boxBytes.AddRange($systemIdBigEndianBytes);
$boxBytes.AddRange($dataLengthBigEndianBytes);
$boxBytes.AddRange($dataBytes);

$boxAsHex = BytesToHexString $boxBytes;

return $boxAsHex;
}

function PostKeyServerRequestAndExtractContentKeyResponse($keyServerRequestJson,
$keyServerUrl)
{
# Post the Key Server request to the Key Server.
$httpResponse = Invoke-WebRequest $keyServerUrl -Method "POST" -ContentType
"application/json" -Body $keyServerRequestJson;

# Get the key server response.


$keyServerResponseObject = $httpResponse.Content | ConvertFrom-Json;

Write-Host
Write-Host "Key server response"
Write-Host "==================="
Write-Host
Write-Host ($keyServerResponseObject | ConvertTo-Json -Depth 100)

# Get the content key response from the "response" member of the key server response.
$contentKeyResponseObject = (Base64StringToString
$keyServerResponseObject.response) | ConvertFrom-Json;

Write-Host
Write-Host "Content key response"
Write-Host "===================="
Write-Host
Write-Host ($contentKeyResponseObject | ConvertTo-Json -Depth 100)

$status = $contentKeyResponseObject.status;

if ($status -ne "OK")


{
throw "Key server request failed: $($status).";
}
return $contentKeyResponseObject;
}

function CreateContentKeyRequest($keyId, $scheme)


{
$contentKeyRequestJson =
@{
"content_id" = StringToBase64String $keyId;
"tracks" = @( @{ "type" = "SD" }; );
"drm_types" = @( "WIDEVINE", "PLAYREADY", "FAIRPLAY" );
"protection_scheme" = $scheme;
} | ConvertTo-Json -Depth 100;

Write-Host
Write-Host "Content key request"
Write-Host "==================="
Write-Host
Write-Host $contentKeyRequestJson

return $contentKeyRequestJson;
}

function CreateKeyServerRequest($contentKeyRequestJson, $signingKeyAsHex,


$signingIvAsHex, $signer)
{
$keyServerRequestJson =
@{
"request" = StringToBase64String $contentKeyRequestJson;
"signature" = CreateSignature $contentKeyRequestJson $signingKeyAsHex
$signingIvAsHex;
"signer" = $signer;
} | ConvertTo-Json -Depth 100

Write-Host
Write-Host "Key server request"
Write-Host "=================="
Write-Host
Write-Host $keyServerRequestJson

return $keyServerRequestJson;
}

function PackageClearContent($inputFilePath, $outputPath)


{
$outputPath += "/clear";

$shakaArguments =
@(
"in=$($inputFilePath),stream=audio,output=$($outputPath)/audio.mp4"
"in=$($inputFilePath),stream=video,output=$($outputPath)/video.mp4"
"--mpd_output=$($outputPath)/manifest.mpd"
"--hls_master_playlist_output=$($outputPath)/manifest.m3u8"
);

Write-Host
Write-Host "Packaging clear content...";
Write-Host "--------------------------"
Write-Host
Write-Host "Packager arguments:";
Write-Host
$shakaArguments;
Write-Host

&$shakaPackagerExecutable $shakaArguments;
}

function PackageEncryptedContent($inputFilePath, $outputPath, $keyIdAsHex, $keyAsHex,


$psshsAsHex, $scheme, $ivAsHex, $skdUri)
{
$outputPath += "/$($scheme)";

$shakaArguments =
@(
"in=$($inputFilePath),stream=audio,output=$($outputPath)/audio.mp4"
"in=$($inputFilePath),stream=video,output=$($outputPath)/video.mp4"
"--enable_raw_key_encryption"
"--keys=key_id=$($keyIdAsHex):key=$($keyAsHex)"
"--pssh=$($psshsAsHex)"
"--protection_scheme=$($scheme)"
"--mpd_output=$($outputPath)/manifest.mpd"
"--hls_master_playlist_output=$($outputPath)/manifest.m3u8"
"--clear_lead=0"
);

# These are needed only for FairPlay and FairPlay only supports CBCS.
if ($scheme -eq "cbcs")
{
$shakaArguments += "--protection_systems=FairPlay";
$shakaArguments += "--iv=$($ivAsHex)"
$shakaArguments += "--hls_key_uri=$($skdUri)"
}

Write-Host
Write-Host "Packaging encrypted '$($scheme)' content...";
Write-Host "-------------------------------------"
Write-Host
Write-Host "Packager arguments:";
Write-Host
$shakaArguments;
Write-Host

&$shakaPackagerExecutable $shakaArguments;
}

&$main;

You might also like