Alexa TopSites - Continuous Signature Failures - C# Implementation

550 views Asked by At

I've asked this on the AWS Forums but getting plenty of views but no comments, I wonder if anyone here can shed any light on it?

Hi, I've been trying to write a simple c# console application to call the topsites service for two days now and still get issues with the signature generation.

I've tested using a java sample in the gallery and can successfully query using my accesskeyid and secret. I've then used my C# code to prove I can generate the same signature and my code will do so, however when I then craft a request and issue it against the api every single one returns a 403 status - signaturedoesnotmatch - please can someone help me find out what the issue is? I'm tearing my hair out with this.

C# Code:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace ConsoleApplication1
{
    class Program
    {
        private static string baseUrl = ConfigurationManager.AppSettings["baseUrl"];
        private static string accessKeyId = ConfigurationManager.AppSettings["accessKeyId"];
        private static string accessKey = ConfigurationManager.AppSettings["accessKey"];
        private static string serviceVersion = ConfigurationManager.AppSettings["serviceVersion"];
        static void Main(string[] args)
        {
            HttpClient client = new HttpClient();
            string requestParameters = "AWSAccessKeyId=" + accessKeyId + "&Action=TopSites&Count=10&CountryCode=&ResponseGroup=Country&SignatureMethod=HmacSHA256&SignatureVersion=2&Start=1001&Timestamp=" + Amazon.Util.AWSSDKUtils.FormattedCurrentTimestampISO8601;
            var signature = generateSignature(requestParameters);
            var url = "http://" + baseUrl + "?" + requestParameters + "&Signature=" + signature;
            HttpResponseMessage message = client.GetAsync(url).Result;

            Console.ReadKey();
        }
        private static string generateSignature(string queryParameters)
        {
            string stringToSign = "GET\n" + baseUrl + "\n/\n" + queryParameters;
            var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
            var secretKeyBytes = Encoding.UTF8.GetBytes(accessKey);
            var hmacSha256 = new HMACSHA256(secretKeyBytes);
            var hashBytes = hmacSha256.ComputeHash(bytesToSign);
            var signature = System.Net.WebUtility.UrlEncode(Convert.ToBase64String(hmacSha256.Hash));
            Trace.Write("String to sign:{0}", signature);
            return signature;
        }
    }
}

Request generated (from Fiddler): GET http://ats.amazonaws.com/?AWSAccessKeyId=REMOVED&Action=TopSites&Count=10&CountryCode=&ResponseGroup=Country&SignatureMethod=HmacSHA256&SignatureVersion=2&Start=1001&Timestamp=2014-11-20T16:57:52.422Z&Signature=vdKOQYRmoJJL3ecY9GAzmGKHAXevoli6rGcEotGFaNY%3D HTTP/1.1 Host: ats.amazonaws.com Connection: Keep-Alive

Response: HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Transfer-Encoding: chunked Date: Thu, 20 Nov 2014 16:57:52 GMT

16d SignatureDoesNotMatchThe request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.84291dc8-a35e-7dc3-7cc1-56fe20b5b236 0

1

There are 1 answers

0
Andrew Westgarth On BEST ANSWER

Based on Darrel's comment and extensive comparisons between requests from the Java app and my sample app I've been able to correctly query the services using a number of requests including the sample one above. It would appear to have been a problem whereby the request string which is signed had an erroneous space character in front of the hostname, for added resiliency I am using the Amazon AWS SDK for .Net to perform the Url Encoding against their requirements to ensure the encoding is correct.

Here's the working sample code:

using System;
using System.Configuration;
using System.Diagnostics;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;   

namespace ConsoleApplication1
{
    class Program
    {
        private static string baseUrl = ConfigurationManager.AppSettings["AlexaServiceUrl"];
        private static string accessKeyId = ConfigurationManager.AppSettings["AlexaAccessKeyId"];
        private static string accessKey = ConfigurationManager.AppSettings["AlexaAccessKey"];
        private static string serviceVersion = ConfigurationManager.AppSettings["AlexaServiceVersion"];
        static void Main(string[] args)
        {
            HttpClient client = new HttpClient();
            string requestParameters = "AWSAccessKeyId=" + accessKeyId + "&Action=TopSites&Count=10&CountryCode=&ResponseGroup=Country&SignatureMethod=HmacSHA256&SignatureVersion=2&Start=1001&Timestamp=" + Amazon.Util.AWSSDKUtils.UrlEncode(Amazon.Util.AWSSDKUtils.FormattedCurrentTimestampISO8601, false);
            var signature = generateSignature(requestParameters);
            var url = "http://" + baseUrl + "/?" + requestParameters + "&Signature=" + signature;
            HttpResponseMessage message = client.GetAsync(url).Result;

            Console.ReadKey();
        }
        private static string generateSignature(string queryParameters)
        {
            string stringToSign = String.Format("GET{0}{1}{2}/{3}{4}", "\n", baseUrl, "\n", "\n", queryParameters);
            var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
            var secretKeyBytes = Encoding.UTF8.GetBytes(accessKey);
            var hmacSha256 = new HMACSHA256(secretKeyBytes);
            var hashBytes = hmacSha256.ComputeHash(bytesToSign);
            var signature = Amazon.Util.AWSSDKUtils.UrlEncode(Convert.ToBase64String(hmacSha256.Hash), false);
            Trace.Write("String to sign:{0}", signature);
            return signature;
        }
    }
}

Hope this helps someone else too.