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.
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.
public static async TaskAddTokenRestrictedAuthorizationPolicy(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 restrictionList = new List { 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)
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.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 (“http://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.
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.
This post first appeared on MSDN Blogs | Get The Latest Information, Insights, Announcements, And News From Microsoft Experts And Developers In The MSDN Blogs., please read the originial post: here