Policy Signing in C# for Streaming Private Content From Amazon CloudFront

by Bill Beckelman 30. March 2010 12:29

On March 29, 2010 Amazon announced public availability of CloudFront’s private content streaming feature. When I first looked at Amazon’s CloudFront offering a few weeks ago, this feature was one I knew I needed and it wasn’t available so I didn’t look much further. With this announcement, I decided to look into things a little deeper.

According to the Amazon CloudFront Developer Guide, there are two parts to serving private content:

  1. Securing the content in your bucket so that end users only have access to the content through CloudFront;
  2. Restricting end user access to cached content.

To cover #1, you change the ACL for your S3 bucket so that there is no public access. You then have to add a CloudFront origin access identity and give it permission to access your content. Refer to the developer’s guide for more details.

To cover #2, you have to create urls containing a signed policy for your content and then use these urls when linking to the content. Below, you will find the c# extension methods I came up with to create my urls:

Code Snippet
  1. public static string CloudFrontStreamingUrl(this string path)
  2. {
  3.     return CloudFrontStreamingUrl(path, null);
  4. }
  5.  
  6. public static string CloudFrontStreamingUrl(this string path, int? secondsUntilExpiration)
  7. {
  8.     const string keyPairId = "YOUR-KEY-PAIR";
  9.     var pathWithoutExtension = path.Replace(Path.GetExtension(path), string.Empty);
  10.  
  11.     //Unix date plus seconds until expiration or 1 hour if null
  12.     var expirationDate = GetUnixTimestamp() + (secondsUntilExpiration ?? 3600);
  13.  
  14.     //Policy as required by AWS, ready for signing. Path in this case should have extension
  15.     var policy = "{\"Statement\":[{\"Resource\":\"" + path +
  16.         "\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":" + expirationDate + "}}}]}";
  17.  
  18.     //Signed policy
  19.     var signature = Sign(policy);
  20.  
  21.     //Url encoded querystring to pass with request for file
  22.     var queryString = HttpUtility.UrlEncode(string.Format("?Expires={0}&Signature={1}&Key-Pair-Id={2}",
  23.         expirationDate, signature, keyPairId));
  24.  
  25.     //Path to be used for streaming content. Path to stream in this case does not have extension
  26.     return string.Format("{0}{1}", pathWithoutExtension, queryString);
  27. }

And the two private methods referred to:

Code Snippet
  1. private static int GetUnixTimestamp()
  2. {
  3.     //http://snippets.dzone.com/posts/show/3236
  4.     return (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
  5. }
  6.  
  7. private static string Sign(string policy)
  8. {
  9.     //http://stackoverflow.com/questions/2284206
  10.  
  11.     // Amazon PEM converted to XML using OpenSslKey    
  12.     // http://www.jensign.com/opensslkey/index.html
  13.     const string providerXmlString = "<RSAKeyValue><Modulus>........ YOUR-KEY-CONVERTED-TO-XML";
  14.  
  15.     using (var sha1 = new SHA1Managed())
  16.     {
  17.         var provider = new RSACryptoServiceProvider();
  18.         RSACryptoServiceProvider.UseMachineKeyStore = false;                
  19.         provider.FromXmlString(providerXmlString);
  20.  
  21.         var plainbytes = Encoding.UTF8.GetBytes(policy);
  22.  
  23.         var hash = sha1.ComputeHash(plainbytes);
  24.         var sig = provider.SignHash(hash, "SHA1");
  25.  
  26.         //Replace (+=/) with (-_~) per AWS developer guide
  27.         return Convert.ToBase64String(sig).Replace("+", "-").Replace("=", "_").Replace("/", "~");
  28.     }
  29. }

 

A few important points:

  1. To be able to sign your urls, the key pair needs to be converted to an xml string. This can be done using the converter from http://www.jensign.com/opensslkey/index.html.
  2. When signing a stream url, the full path is not signed like an http url. Only the stream identifier is signed. Be sure to read the documentation.
  3. Understand when you include the steam’s extension. It seems to be included when signing the policy, but not when referring to the stream by actual url. Check the documentation.

 

I hope this helps you, it took me a good half day to figure it all out…

Tags:

ASP.NET MVC | ASP.NET | AWS


About Me

I live and work in Salt Lake City, Utah. My background is in aviation. I have a degree in Aeronautical Science from Embry-Riddle Aeronautical University in Prescott, AZ. I have worked as a commercial airline pilot and most recently as a technical advisor for a charter airline.