A self-contained Azure Media Service (AMS) end-to-end szenario with dynamic AES128 encryption
Microsoft Azure Media Services enables you to deliver Http-Live-Streaming (HLS) and Smooth Streams encrypted with AES128 (Advanced Encryption Standard using 128-bit encryption keys). To easily demonstrate a simple szenario for using AES123 encryption, I developed a self-contained solution that you can find in my GitHub repository. The use case is an intranet video portal to show training videos to employees. Depending on their role in a directory service (e.g. Active Directory) they can see different content. This demo can be started easily, without the need of setting up a directory service. Here you can see an overview of the architecture:
I want to give a short description of what happens:
- An employee logs in to the CMS using corp credentials.
- The CMS will redirect the user to the ADFS (in this demo: a mockup using IdentityServer) for authentication.
- If the validation of the client credentials is successful, ADFS returns a bearer token and redirects the user back to the CMS.
- The user is now signed in to the intranet application. In this demo, this is a simple website showing the videos dedicated to this user. At this point, the video is encrypted.
- CMS requests a JWT Token from the Identity Provider (in this demo: a class in my CMS project (JWT Helper)). The Identity Provider checks the rights of the user and queries the database (in this demo: database mockup using a simple JSON file) for available videos for this user. A JWT Token is generated using the primary verification key of the video.
- JWT Token is returned.
- The video player shows the JWT token to Azure Media Key Services and gets the decryption key for the videos, if valid.
- The video fragments are decrypted dynamically in the browser.
A more detailed description of the components of the solution can be found in the GitHub Readme. In the following article, I will focus on how to do the encryption of the assets.
So, what do I have to do to encrypt my assets?
To encrypt an asset, you need to associate an content key with the asset, configure a authorization policy for the key and define a delivery policy for the asset:
- Content key
Is used to dynamically encrypt your content using AES encryption when a stream is requested. - Authorization Policy
Or: how do you get the encryption key (from a service specified in the Delivery Policy), via a token or PlayReady?
If token: TokenRestrictionTemplate
- Authorization via JWT or SWT (JSON Web Token or Simple Web Token)?
- Who is the issuer of the token?
- Who has access (audience)? - Delivery Policy
The Delivery Policy for an asset specifies how this asset will be delivered:
- Key Acquisition URL (where can I get the encryption key for this asset? (AMS Key Services || PlayReady || Widevine)
- Into which streaming protocol should your asset be packaged? (HLS, DASH, Smooth Streaming)
- Should the asset be dynamically encrypted? And how? (envelope || common encryption)
In my demo, the AuthorizationPolicy is set to a token-based authorization. To get the encryption key, the player will request it from the Azure Media Services Key Delivery Service using a JWT Token:
Got it! And how does it look like in code?
private static async void SetupAESEncryptionAsync(CloudMediaContext context, IAsset encodedAsset, string audience, Settings settings)
{
//1.Create a content key and associate it with the encoded asset
IContentKey key = CreateEnvelopeTypeContentKey(encodedAsset, context);
//2.Configure the content keys authorization policy
string tokenTemplateString = await AddTokenRestrictedAuthorizationPolicy(context: context, contentKey: key, audience: audience, contentKeyIdentifierClaim: true, issuer: settings.Issuer, primaryVerificationKey: settings.primaryVerificationKey);
//3.Create Asset Delivery Policy (Dynamic or non-dynamic encryption)
CreateAssetDeliveryPolicy(encodedAsset, key, context);
}
Let's step through this!
1.Create a content key and associate it with the encoded asset
You have to use ContentKeyType.EnvelopeEncryption in order to use AES-128 encryption.
[code highlight="13" language="csharp"]
static public IContentKey CreateEnvelopeTypeContentKey(IAsset asset, CloudMediaContext context)
{
//Check if there is already a content key associated with the asset
IContentKey contentKey = asset.ContentKeys.FirstOrDefault(k => k.ContentKeyType == ContentKeyType.EnvelopeEncryption);
// Create envelope encryption content key, Associate the key with the asset
if (contentKey == null)
{
contentKey = context.ContentKeys.Create(
keyId: Guid.NewGuid(),
contentKey: GetKeyBytes(16),
name: "ContentKey",
contentKeyType: ContentKeyType.EnvelopeEncryption);
asset.ContentKeys.Add(contentKey);
}
return contentKey;
}
2.Configure the Authorization Policy for the content key
ContentKeyDeliveryType.BaselineHttp specifies to use the AES key server from AMS.
[code highlight="6,7,23" language="csharp"]
public static async Task<string> AddTokenRestrictedAuthorizationPolicy(CloudMediaContext context, IContentKey contentKey, string issuer, string audience, bool contentKeyIdentifierClaim, byte[] primaryVerificationKey)
{
string tokenTemplateString = GenerateTokenRequirements(
issuer: issuer,
audience: audience,
contentKeyIdentifierClaim: contentKeyIdentifierClaim,
primaryVerificationKey: primaryVerificationKey);
IContentKeyAuthorizationPolicy policy = await context.ContentKeyAuthorizationPolicies.CreateAsync(name: "Token restricted authorization policy");
List<ContentKeyAuthorizationPolicyRestriction> restrictionList = new List<ContentKeyAuthorizationPolicyRestriction>
{
new ContentKeyAuthorizationPolicyRestriction
{
Name = "Token Authorization Policy",
KeyRestrictionType = (int)ContentKeyRestrictionType.TokenRestricted,
Requirements = tokenTemplateString
}
};
//You could have multiple options; BaselineHttp specifies that we use the AES key server from AMS
IContentKeyAuthorizationPolicyOption policyOption = context.ContentKeyAuthorizationPolicyOptions.CreateAsync(
name: "Token Authorization policy option",
deliveryType: ContentKeyDeliveryType.BaselineHttp,
restrictions: restrictionList,
keyDeliveryConfiguration: null).Result; // no key delivery data is needed for HLS
policy.Options.Add(policyOption);
// Add ContentKeyAutorizationPolicy to ContentKey
contentKey.AuthorizationPolicyId = policy.Id;
IContentKey updatedKey = await contentKey.UpdateAsync();
return tokenTemplateString;
}
3.Create Asset Delivery Policy (Dynamic or non-dynamic encryption)
[code highlight="3,14,15" language="csharp"]
static public async void CreateAssetDeliveryPolicy(IAsset asset, IContentKey key, CloudMediaContext context)
{
Uri keyAcquisitionUri = await key.GetKeyDeliveryUrlAsync(ContentKeyDeliveryType.BaselineHttp);
const string assetDeliveryPolicyName = "AssetDeliveryPolicy for HLS, SmoothStreaming and MPEG-DASH";
IAssetDeliveryPolicy assetDeliveryPolicy = context.AssetDeliveryPolicies
.Where(p => p.Name == assetDeliveryPolicyName)
.ToList().FirstOrDefault();
if (assetDeliveryPolicy == null)
{
assetDeliveryPolicy = await context.AssetDeliveryPolicies.CreateAsync(
name: assetDeliveryPolicyName,
policyType: AssetDeliveryPolicyType.DynamicEnvelopeEncryption,
deliveryProtocol: AssetDeliveryProtocol.SmoothStreaming | AssetDeliveryProtocol.HLS | AssetDeliveryProtocol.Dash,
configuration: new Dictionary<AssetDeliveryPolicyConfigurationKey, string> {{
AssetDeliveryPolicyConfigurationKey.EnvelopeBaseKeyAcquisitionUrl,
keyAcquisitionUri.AbsoluteUri }});
// Add AssetDelivery Policy to the asset
asset.DeliveryPolicies.Add(assetDeliveryPolicy);
}
}
In the TokenRestrictionTemplate you define three things:
- The type of the Token (JWT || SWT)
- The issuer of the Token. In my case the issuer is IdentityServer (ADFS-mockup in my solution), running locally ("https://localhost:5000/identity"). In a production scenario, this could be any IdentityProvider.
- The audience of the token - the people who are able to view the video. I defined two audiences in IdentityServer: staff and management. In a production scenario, this could be e.g. groups in your Active Directory.
[code highlight="3,11" language="csharp"]
static private string GenerateTokenRequirements(string issuer, string audience, bool contentKeyIdentifierClaim, byte[] primaryVerificationKey)
{
var template = new TokenRestrictionTemplate(TokenType.JWT)
{
PrimaryVerificationKey = new SymmetricVerificationKey(primaryVerificationKey),
Issuer = issuer,
Audience = audience
};
if (contentKeyIdentifierClaim)
template.RequiredClaims.Add(TokenClaim.ContentKeyIdentifierClaim);
//You can create a test token, useful to debug your final token if anything is not working as you want
string testToken = TokenRestrictionTemplateSerializer.GenerateTestToken(template);
return TokenRestrictionTemplateSerializer.Serialize(template);
}
You can specify if your token should include the ContentKeyIdentifierClaim. Here is a picture of a sample JWT token (grabbed using Fiddler), using https://jwt.io/ to take a closer look at the token of the Azure Media Player Sample with AES128 encryption sample with ContentKeyIdentifierClaim (urn:microsoft: ...):
The complete solution and code can be found on GitHub. Please reach out to me if you have any questions!
I'm also on Twitter: @blaujule.