Questions? Feedback? powered by Olark live chat software
Přeskočit navigaci

An End-to-End Prototype of AES Encryption with ACS Authentication and ACS Token Authorization

Publikováno dne 26 listopadu, 2014

Principal Program Manager, Consumer & Media

Introduction

This is a “sequel” to the author’s previous post An End-to-End Prototype of PlayReady Protection with ACS Authentication and ACS Token Authorization. In certain cases, full DRM protection may not be mandated or make economic sense. Instead, AES encryption can be an alternative. However, keep in mind that AES encryption is very different from and is not DRM. The differences between DRM and AES encryption can be summarized by the following table.
Compare DRM such as PlayReady Encryption such as AES-128
Protect content vs. protect file Content Content
Is an authorized user trusted? No, What an Authorized user can do with the protected content is restricted by Restrictions and Rights contained in a DRM license. Yes, Authorized users can do anything with the content: copy, store, share, and/or export to any display devices
Nature of content to be protected Applicable to content of shared interests, such as videos Applicable to personal content, users are trusted not to share.
Is content protected after being decrypted on client devices? Yes No
Can content access be restricted by date/time? Yes No
Application whitelisting Yes No
Domain binding and management Yes No
Encryption key rotation Yes No
Blackout Yes No
Output protection Yes No
License chaining Yes No
Azure Media Services Content Protection also makes “1-Click” AES encryption a reality thanks to the dynamic AES encryption feature. In this blog, we will present an end-to-end prototype of AES encryption of smooth streaming asset with token authorization. The authorization token is issued by the same ACS 2.0 namespace as used in the end-to-end prototype for PlayReady protection.  

The End-to-End Prototype

Design and Functionality

The goal of this effort is to provide an end-to-end prototype covering the following
  • AES-128 dynamic encryption with Token Restriction for an asset in AMS;
  • Azure Media Services key delivery service for delivering decryption key;
  • Azure ACS (Microsoft Azure Active Directory Access Control) as an STS to issue SWT authorization tokens;
  • An OSMF player
    1. gets authenticated by ACS Service Identity,
    2. gets authorization token from ACS,
    3. acquires decryption key from AMS key delivery service with ACS token and
    4. decryption and video playback.
The design of this end-to-end prototype is illustrated by the following diagram. Blog_Secure_Delivery_AES   The end-to-end prototype is hosted in Azure and Azure Media Services. Below is the information related to the prototype.  

How to Run it?

  1. Browse to the OSMF player built with Adaptive Streaming Plugin for OSMF. The needed information is already on the player page UI.
  2. Notice that each click of "Play" button results in a request of new authorization token from ACS and the new authorization token is then used by the OSMF plugin to request for the decryption key for playback. The authorization token from ACS 2.0 namespace is shown on the player page. Below is the player screenshot.
OMFPlayer
 

The Implementation

The implementation involves the following:
  1. Configure dynamic AES-128 encryption for an smooth streaming asset using Azure Media Services .NET API;
    • Generate content key ID and content key;
    • Configure decryption key delivery service;
    • Configure dynamic AES encryption via asset delivery policy;
    • Publish the asset.
  2. Set up an Azure ACS 2.0 namespace to authenticate the player client and issue authorization tokens;
  3. Develop a OSMF player which handles ACS authentication, authorization token request, decryption key request and video playback.
We chose to use OSMF player for this prototype because Microsoft Adaptive Streaming Plugin for OSMF supports AES encryption of smooth streaming content for both on-demand and live.  

Configuring Dynamic AES Encryption

The first step is to create envelope type content key. The code is below.
static public IContentKey CreateEnvelopeTypeContentKey(CloudMediaContext objCloudMediaContext)
{
    // Create envelope encryption content key
    Guid keyId = Guid.NewGuid();
    byte[] contentKey = CryptoUtils.GenerateCryptographicallyStrongRandomBytes(16);

    IContentKey objIContentKey = objCloudMediaContext.ContentKeys.Create(keyId, contentKey, "myContentKey", ContentKeyType.EnvelopeEncryption);

    return objIContentKey;
}
Next, we create the authorization policy and add it to the content key created above. As shown in this diagram, each IContentKey has a single instance of IContentKeyAuthorizationPolicy. The following code creates an IContentKeyAuthorizationPolicy and attach to the IContentKey.
public static IContentKey AddAuthorizationPolicyToContentKey(CloudMediaContext objCloudMediaContext, IContentKey objIContentKey)
{
    // Create ContentKeyAuthorizationPolicy with restrictions and create authorization policy             
    IContentKeyAuthorizationPolicy policy = objCloudMediaContext.ContentKeyAuthorizationPolicies.CreateAsync("Open Authorization Policy").Result;

    List<ContentKeyAuthorizationPolicyRestriction> restrictions = new List<ContentKeyAuthorizationPolicyRestriction>();

    ContentKeyAuthorizationPolicyRestriction restriction = new ContentKeyAuthorizationPolicyRestriction
    {
        Name = "Authorization Policy with Token Restriction",
        KeyRestrictionType = (int)ContentKeyRestrictionType.TokenRestricted,
        Requirements = ContentKeyAuthorizationHelper.CreateRestrictionRequirements()
    };

    restrictions.Add(restriction);

    IContentKeyAuthorizationPolicyOption policyOption = objCloudMediaContext.ContentKeyAuthorizationPolicyOptions.Create("myDynamicEncryptionPolicy", ContentKeyDeliveryType.BaselineHttp, restrictions, "");

    policy.Options.Add(policyOption);

    // Add ContentKeyAutorizationPolicy to ContentKey
    objIContentKey.AuthorizationPolicyId = policy.Id;
    IContentKey IContentKeyUpdated = objIContentKey.UpdateAsync().Result;

    return IContentKeyUpdated;
}
  In the above, the method ContentKeyAuthorizationHelper.CreateRestrictionRequirements() is defined as below.
public static string CreateRestrictionRequirements()
{
    string primarySymmetricKey   = System.Configuration.ConfigurationManager.AppSettings["PrimarySymmetricKey"];
    string secondarySymmetricKey = System.Configuration.ConfigurationManager.AppSettings["SecondarySymmetricKey"];
    string scope                 = System.Configuration.ConfigurationManager.AppSettings["AcsScope"];
    string issuer                = System.Configuration.ConfigurationManager.AppSettings["AcsIssuer"];

    TokenRestrictionTemplate objTokenRestrictionTemplate = new TokenRestrictionTemplate();

    objTokenRestrictionTemplate.PrimaryVerificationKey = new SymmetricVerificationKey(Convert.FromBase64String(primarySymmetricKey));
    objTokenRestrictionTemplate.AlternateVerificationKeys.Add(new SymmetricVerificationKey(Convert.FromBase64String(secondarySymmetricKey)));
    objTokenRestrictionTemplate.Audience               = new Uri(scope);
    objTokenRestrictionTemplate.Issuer                 = new Uri(issuer);

    return TokenRestrictionTemplateSerializer.Serialize(objTokenRestrictionTemplate);
}
  NOTE: Please make sure that the same (primary) symmetric hash key used in ACS 2.0 namespace is also used in configuring (dynamic) AES encryption authorization policy. Next, we need to create an IAssetDeliveryPolicy which will be used for the delivery of the asset:
public static IAssetDeliveryPolicy CreateAssetDeliveryPolicy(CloudMediaContext objCloudMediaContext, IContentKey objIContentKey)
{
    Uri keyAcquisitionUri = objIContentKey.GetKeyDeliveryUrl(ContentKeyDeliveryType.BaselineHttp);

    string envelopeEncryptionIV = Convert.ToBase64String(CryptoUtils.GenerateCryptographicallyStrongRandomBytes(16));

    // The following policy configuration specifies: 
    //   key url that will have KID=<Guid> appended to the envelope and
    //   the Initialization Vector (IV) to use for the envelope encryption.
    Dictionary<AssetDeliveryPolicyConfigurationKey, string> assetDeliveryPolicyConfiguration = new Dictionary<AssetDeliveryPolicyConfigurationKey, string> 
            {
                {AssetDeliveryPolicyConfigurationKey.EnvelopeKeyAcquisitionUrl, keyAcquisitionUri.ToString()},
                {AssetDeliveryPolicyConfigurationKey.EnvelopeEncryptionIVAsBase64, envelopeEncryptionIV}
            };

    IAssetDeliveryPolicy objIAssetDeliveryPolicy = objCloudMediaContext.AssetDeliveryPolicies.Create(
                "SmoothHLSDynamicEncryptionAssetDeliveryPolicy",
                AssetDeliveryPolicyType.DynamicEnvelopeEncryption,
                AssetDeliveryProtocol.SmoothStreaming | AssetDeliveryProtocol.HLS,
                assetDeliveryPolicyConfiguration);

    Console.WriteLine();
    Console.WriteLine("Adding Asset Delivery Policy: " + objIAssetDeliveryPolicy.AssetDeliveryPolicyType);
    Console.WriteLine("Key Delivery URL = {0}", keyAcquisitionUri.ToString());

    return objIAssetDeliveryPolicy;
}
  Finally, we combine all of above to form the following overall flow of adding AES dynamic encryption for an asset:
public static void DynamicAesEncryptionFlow(CloudMediaContext objCloudMediaContext, IAsset objIAsset)
{
    //Create IContentKey
    IContentKey objIContentKey = CreateEnvelopeTypeContentKey(objCloudMediaContext);

    //add AuthorizationPolicy to IContentKey
    objIContentKey = AddAuthorizationPolicyToContentKey(objCloudMediaContext, objIContentKey);

    //create asset delivery policy
    IAssetDeliveryPolicy objIAssetDeliveryPolicy = CreateAssetDeliveryPolicy(objCloudMediaContext, objIContentKey);

    //Associate IContentKey with IAsset
    objIAsset.ContentKeys.Add(objIContentKey);

    // Add AssetDelivery Policy to the asset
    objIAsset.DeliveryPolicies.Add(objIAssetDeliveryPolicy);
}
After running this, you need to publish the asset, either programmatically via API or using Azure portal.  

ACS Setup

We can simply use the same ACS 2.0 namespace we set up in the blog An End-to-End Prototype of PlayReady Protection with ACS Authentication and ACS Token Authorization, with the same symmetric verification key, same Service Identity, same issuer and scope, etc.. When you set up ACS Service Identity, you may choose either Password or Symmetric Key credential types. Both cases are supported by the prototype (token request code below).  

Player Code

Microsoft Adaptive Streaming Plugin for OSMF supports both on-demand and live playback of AES-128 encrypted smooth streaming. Therefore we use an OSMF player for our purpose. The flow of an OSMF player is as below:
  1. Download smooth streaming manifest which indicates AES-128 encrypted content and contains the key delivery URL, as seen in the client manifest of our test asset.
  2. Request for authorization token;
  3. Request for decryption key with the authorization token;
  4. Decrypt and playback.
First the code to request authorization token from ACS namespace is as below.
public string GetAcsToken()
{
    string issuer   = System.Configuration.ConfigurationManager.AppSettings["AcsIssuer"];
    string scope    = System.Configuration.ConfigurationManager.AppSettings["AcsScope"];
    string username = System.Configuration.ConfigurationManager.AppSettings["Username"];
    string password = System.Configuration.ConfigurationManager.AppSettings["Password"];

    string tokenToReturn = null;

    using (WebClient client = new WebClient())
    {
        //  Create the authentication request to get a token
        client.BaseAddress = (new Uri(issuer)).AbsoluteUri;

        NameValueCollection objNameValueCollection = null;
        switch (System.Configuration.ConfigurationManager.AppSettings["CredentialType"].ToLower())
        {
            case "password":
                objNameValueCollection = new NameValueCollection
                {
                    {"grant_type",    "client_credentials"},
                    {"client_id",     username},
                    {"client_secret", password},
                    {"scope",         scope}
                };
                break;
            case "symmetrickey":
                objNameValueCollection = new NameValueCollection
                {
                    {"grant_type",    "http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0"},
                    {"assertion",     this.CreateToken(username, password)},
                    {"scope",         scope}
                };
                break;
            default:
                break;
        }

        byte[] responseBytes = null;

        try
        {
            responseBytes = client.UploadValues(new Uri(issuer + "/v2/OAuth2-13/"), "POST", objNameValueCollection);
        }
        catch (WebException we)
        {
            Stream stream = we.Response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);

            throw;
        }

        using (var responseStream = new MemoryStream(responseBytes))
        {
            OAuth2TokenResponse tokenResponse = (OAuth2TokenResponse)new DataContractJsonSerializer(typeof(OAuth2TokenResponse)).ReadObject(responseStream);
            tokenToReturn = tokenResponse.AccessToken;
        }
    }

    return tokenToReturn;
}

public string CreateToken(string issuer, string signingKey)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    // add the issuer name 
    sb.AppendFormat("Issuer={0}", System.Web.HttpUtility.UrlEncode(issuer));
    string signature = this.GenerateSignature(sb.ToString(), signingKey);
    sb.AppendFormat("&HMACSHA256={0}", signature);
    return sb.ToString();
} 

private string GenerateSignature(string unsignedToken, string signingKey)
{
    System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(signingKey));
    byte[] locallyGeneratedSignatureInBytes = hmac.ComputeHash(System.Text.Encoding.ASCII.GetBytes(unsignedToken));
    string locallyGeneratedSignature = System.Web.HttpUtility.UrlEncode(Convert.ToBase64String(locallyGeneratedSignatureInBytes));
    return locallyGeneratedSignature;
}
  The type OAuth2TokenResponse is defined as:
[DataContract]
public class OAuth2TokenResponse
{
    [DataMember(Name = "access_token")]
    public string AccessToken { get; set; }

    [DataMember(Name = "expires_in")]
    public int ExpirationInSeconds { get; set; }
}
  The ASP.NET web page hosting the player is as below:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Aes128OSMFPlayer.aspx.cs" Inherits="SilverlightApplication.Web.OSMF.Aes128OSMFPlayer" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>AES Encryption | OSMF Player</title>
    <style type="text/css">
        .auto-style1 {
            height: 17px;
        }
    </style>
</head>
<body style="font-family:Verdana;font-size:11px">
    <form id="form1" runat="server">
        <div>
            <h3>End-to-End Prototype of AES Encryption with ACS Authorization Token for Smooth Streaming</h3>
            <table><tr><td>Source URL: </td><td>
                <asp:TextBox ID="txtSrcUrl" runat="server" Width="913px">http://willzhanmediaservice.origin.mediaservices.windows.net/474c4840-4753-48ec-b3aa-9d05daef612a/LyncSkypeSizzleVideo750k.ism/manifest</asp:TextBox>
            </td></tr>
            <tr><td>Key Delivery URL:</td><td><a href="https://willzhanmediaservice.keydelivery.mediaservices.windows.net/?KID=1814f449-0550-4977-abd5-fe3efeaf6086" target="_blank">https://willzhanmediaservice.keydelivery.mediaservices.windows.net/?KID=1814f449-0550-4977-abd5-fe3efeaf6086</a>
            </td></tr>
                <tr><td class="auto-style1">Authorization Token:</td><td class="auto-style1"><%=authorizationToken %></td></tr>
            <tr><td>Token Issuer: </td><td><a href="https://willzhanacs.accesscontrol.windows.net" target="_blank">https://willzhanacs.accesscontrol.windows.net</a></td></tr>
            <tr><td></td><td>
                <asp:Button ID="cmdPlay" runat="server" OnClick="cmdPlay_Click" Text="Play" />
            </td></tr>
            <tr><td></td><td>
                    <object width="920" height="640">
                        <param name="movie" value="StrobeMediaPlayback.2.0.swf"></param>
                        <param name="flashvars" value="src=<%=srcUrl %>&autoPlay=<%=autoPlay %>&plugin_AdaptiveStreamingPlugin=MSAdaptiveStreamingPlugin-v1.0.12-osmf2.0.swf&AdaptiveStreamingPlugin_retryLive=true&AdaptiveStreamingPlugin_retryInterval=10&AdaptiveStreamingPlugin_encryptionKeyToken=<%=authorizationToken %>"></param>
                        <param name="allowFullScreen" value="true"></param>
                        <param name="allowscriptaccess" value="always"></param>
                        <param name="wmode" value="direct"></param>
                        <embed src="StrobeMediaPlayback.2.0.swf"
                            type="application/x-shockwave-flash"
                            allowscriptaccess="always"
                            allowfullscreen="true"
                            wmode="direct"
                            width="920"
                            height="640"
                            flashvars="src=<%=srcUrl %>&autoPlay=<%=autoPlay %>&plugin_AdaptiveStreamingPlugin=MSAdaptiveStreamingPlugin-v1.0.12-osmf2.0.swf&AdaptiveStreamingPlugin_retryLive=true&AdaptiveStreamingPlugin_retryInterval=10&AdaptiveStreamingPlugin_encryptionKeyToken=<%=authorizationToken %>">
                        </embed>
                    </object>
                </td></tr>
                </table>
        </div>
    </form>
</body>
</html>
  And the corresponding code-behind is below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace SilverlightApplication.Web.OSMF
{
    public partial class Aes128OSMFPlayer : System.Web.UI.Page
    {
        protected string authorizationToken;
        protected string autoPlay;
        protected string srcUrl;
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                this.Play();
            }
        }

        protected void cmdPlay_Click(object sender, EventArgs e)
        {
            this.Play();
        }

        protected void Play()
        {
            //Request authorization token from ACS 2.0 namespace
            WCFService objWCFService = new WCFService();
            string token = objWCFService.GetAcsToken();
            token = string.Format("Bearer={0}", token);

            //URL-encode the token before using it
            authorizationToken = System.Web.HttpUtility.UrlEncode(token);

            srcUrl = txtSrcUrl.Text;
            autoPlay = true.ToString();
        }
    }
}
 

Wrap-up

We have presented an end-to-end prototype of Azure Media Services AES encryption solution which includes all of the key components:
  1. Content key ID and content key generation;
  2. Streaming origin in Azure Media Services;
  3. AES encryption via Azure Media Services Content Protection;
  4. AES key delivery via Azure Media Services Content Protection;
  5. STS (Secure Token Service) via Azure ACS 2.0 to authenticate player client and issue authorization tokens;
  6. Video player application hosted in Azure IaaS VM, which handles ACS authentication, ACS authorization, decryption key acquisition and video playback.
  UPDATES: On 1/6/2015: The OSMF-plugin player has been enhanced for testing AES encryption in more general scenarios: It now works with any Azure ACS namespace instead of just the ACS namespace used for the end-to-end implementation discussed in this blog. You can just replace my ACS namespace parameters/secrets by yours to test. On 1/23/2015: With the release of JWT support in AMS Content Protection, this prototype has been expanded to include token restriction with JWT by using Azure Active Directory (AAD) as both STS and IdP. AMS batch job (for setting up dynamic PlayReady protection or AES encryption): knows AAD tenant, but nothing about player app (any player is fine). AAD tenant: knows player app, but nothing about the AMS batch job. Player app: knows AAD tenant, but nothing about AMS or AMS batch job. In order words, AAD tenant and player app know each other. AMS batch job knows AAD tenant, but does not care what player consumes the contents.   ACKNOWLEDGMENT: Special thanks to Quintin Burns, George Trifonov and Mingfei Yan of Microsoft Azure Media Services Team, who have provided significant help in this effort.