7

Has anyone using .net actually worked out how to successfully sign a signature to use with CloudFront private content? After a couple of days of attempts all I can get is Access Denied.

I have been working with variations of the following code and also tried using OpenSSL.Net and AWSSDK but that does not have a sign method for RSA-SHA1 yet.

The signature (data) looks like this

{"Statement":[{"Resource":"http://xxxx.cloudfront.net/xxxx.jpg","Condition":​{"DateLessThan":​{"AWS:EpochTime":1266922799}}}]}

Update: Solved all of this by removing a single space in the above signature.

If only I had noticed it earlier!

This method attempts to sign the signature for use in the canned url. So of the variations have included chanding the padding used in the has and also reversing the byte[] before signing as apprently OpenSSL do it this way.

public string Sign(string data)
{
   using (SHA1Managed SHA1 = new SHA1Managed())
   {
      RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
      RSACryptoServiceProvider.UseMachineKeyStore = false;

      // Amazon PEM converted to XML using OpenSslKey
      provider.FromXmlString("<RSAKeyValue><Modulus>....."); 

      byte[] plainbytes = System.Text.Encoding.UTF8.GetBytes(data);

      byte[] hash = SHA1.ComputeHash(plainbytes);
      //Array.Reverse(sig); // I have see some examples that reverse the hash

      byte[] sig = provider.SignHash(hash, "SHA1");

     return Convert.ToBase64String(sig);
   }
}

It's useful to note that I have verified the content is setup correctly in S3 and CloudFront by generating a CloudFront canned policy url using my CloudBerry Explorer. How do they do it?

starball
  • 20,030
  • 7
  • 43
  • 238
Chet
  • 185
  • 1
  • 8
  • Chet, Could elaborate on your solution? I would like to implement private content on the PostSharp download manager (now using S3+CloudFront public content). I am especially intrigued by how you convert the Amazon PEM into XML with OpenSslKey. Could you share a some link to this? Thanks. -gael – Gael Fraiteur Mar 13 '10 at 15:36
  • To convert the PEM to XML you can either grab the source or comiled version of OpenSSLKey from http://www.jensign.com/opensslkey/index.html If you download then just run opensslkey.exe from the cmd line and follow the prompts – Chet Mar 17 '10 at 20:33
  • Thanks - It was not too difficult to implement with that info. However, I recommend testing that S3/CloudFront is properly configured using some GUI, and then only try to do the same thing with code. – Gael Fraiteur Mar 30 '10 at 12:22
  • Thanks, I was stuck with a extra whitespace in my policy statement too -- which came from the example in the AWS documentation, which also said to copy it exactly, including punctuation and other literal values >:( – Tobias Cohen Sep 26 '13 at 07:04

3 Answers3

7

Here is the full code if anyone if interested:

internal class CloudFrontSecurityProvider
{
    private readonly RSACryptoServiceProvider privateKey;
    private readonly string privateKeyId;
    private readonly SHA1Managed sha1 = new SHA1Managed();

    public CloudFrontSecurityProvider(string privateKeyId, string privateKey)
    {
        this.privateKey = new RSACryptoServiceProvider();
        RSACryptoServiceProvider.UseMachineKeyStore = false;

        this.privateKey.FromXmlString( privateKey );
        this.privateKeyId = privateKeyId;
    }
    private static int GetUnixTime(DateTime time)
    {
        DateTime referenceTime = new DateTime(1970, 1,1);
        return (int) (time - referenceTime).TotalSeconds;

    }

    public string GetCannedUrl(string url, DateTime expiration)
    {

        string expirationEpoch = GetUnixTime( expiration ).ToString();

        string policy =
            @"{""Statement"":[{""Resource"":""<url>"",""Condition"":{""DateLessThan"":{""AWS:EpochTime"":<expiration>}}}]}".
                Replace( "<url>", url ).
                Replace( "<expiration>", expirationEpoch );


        string signature = GetUrlSafeString( Sign( policy ) );

        return url + string.Format("?Expires={0}&Signature={1}&Key-Pair-Id={2}", expirationEpoch, signature, this.privateKeyId);
    }

    private static string GetUrlSafeString(byte[] data)
    {
        return Convert.ToBase64String( data ).Replace( '+', '-' ).Replace( '=', '_' ).Replace( '/', '~' );
    }

    private byte[] Sign(string data)
    {
            byte[] plainbytes = Encoding.UTF8.GetBytes(data);

            byte[] hash = sha1.ComputeHash(plainbytes);

            return this.privateKey.SignHash(hash, "SHA1");
    }

}
Gael Fraiteur
  • 6,759
  • 2
  • 24
  • 31
3

You can now do this using the SDK:

http://docs.aws.amazon.com/sdkfornet/v3/apidocs/Index.html

Navigate to Amazon.CloudFront > AmazonCloudFrontUrlSigner

Phill
  • 18,398
  • 7
  • 62
  • 102
David
  • 727
  • 1
  • 6
  • 16
3

I've done up a blog post of what I had to do to get it working:

http://anthonyvscode.com/2011/01/11/using-amazon-s3-cloudfront-to-distribute-your-private-files/

might help others trying to work out the problems that I had getting it up and running

anthonyvscode
  • 995
  • 8
  • 15