Alexa TopSites - Continuous Signature Failures - C# Implementation

563 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 best solutions below

0
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.