4

So I'm trying to do a REST API call to AWS' SNS service, but I keep getting an IncompleteSignature error. I based myself on http://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages/#csharp on how to create the signature and http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/HMACAuth.html to find out what to sign.

Here's the test code I came up with:

    static void Main(string[] args)
    {
        string region = "us-west-2";
        string msg = "This is a test!";
        string secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        string key = "XXXXXXXXXXXXXXX";
        string arn = "arn:aws:sns:us-west-2:xxxxxxxxxx:snstest1";

        string query = "Action=Publish&Message=" + HttpUtility.UrlEncode(msg) + "&MessageStructure=json&TargetArn=" + HttpUtility.UrlEncode(arn) + "&SignatureMethod=HmacSHA256&AWSAccessKeyId=" + key + "&SignatureVersion=2&Timestamp=" + HttpUtility.UrlEncode(DateTime.UtcNow.ToString("o"));
        string tosign = "GET\nsns." + region + ".amazonaws.com\n/\n" + query;

        System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

        byte[] keyByte = encoding.GetBytes(secret);
        byte[] messageBytes = encoding.GetBytes(tosign);

        var hmacsha256 = new HMACSHA256(keyByte);

        byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
        query += "&signature=" + HttpUtility.UrlEncode(Convert.ToBase64String(hashmessage));

        Console.WriteLine("REST Call: https://sns." + region + ".amazonaws.com/?" + query);
    }

Any idea what might be wrong?

EDIT: I tried changing the signature part with the code from http://wiki.alphasoftware.com/~alphafiv/DotNet+Example%3A+Digital+Hash it uses CharArray instead of the byte[], not sure which is right, it produces a different signature but it still doesn't work with AWS.

EDIT2: After long tries I finally figured out that AWS expects Signature= and not signature=, but now I'm getting a SignatureDoesNotMatch error, so I need to figure that out next. Also I don't know why this kind of question would get downvoted. Once I figure out the syntax, an AWS API call would be trivial to do in any app. If you use the AWS .NET SDK you're adding 6 megs to your binary. How is that not a worthwhile endeavor?

SOLUTION:

This code works and will send a SNS notification without the AWS SDK:

    static void Main(string[] args)
    {
        string region = "us-west-2";
        string msg = "Test test: sfdfds\nfsd: sdsda\n";
        string secret = "XXXXXXXXXXXXXXXXXXX";
        string key = "ZZZZZZZZZZZ";
        string arn = "arn:aws:sns:us-west-2:YYYYYYYYYYY:snstest1";

        string query = "AWSAccessKeyId=" + Uri.EscapeDataString(key) + "&Action=Publish&Message=" + Uri.EscapeDataString(msg) + "&SignatureMethod=HmacSHA256&SignatureVersion=2&TargetArn=" + Uri.EscapeDataString(arn) + "&Timestamp=" + Uri.EscapeDataString(System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"));
        string tosign = "GET\nsns." + region + ".amazonaws.com\n/\n" + query;

        Console.WriteLine(tosign + "\n");

        UTF8Encoding encoding = new UTF8Encoding();
        HMACSHA256 hmac = new HMACSHA256(encoding.GetBytes(secret));
        string signature = Convert.ToBase64String(hmac.ComputeHash(encoding.GetBytes(tosign)));

        query += "&Signature=" + Uri.EscapeDataString(signature);

        Console.WriteLine("REST Call: https://sns." + region + ".amazonaws.com/?" + query);
    }
Dendory
  • 620
  • 2
  • 10
  • 21
  • 1
    Why can't you use the SDK directly, which takes care of these parts for you? – Antoine Mar 10 '15 at 14:18
  • I wanted to keep the code as small as possible. This should be doable manually, AWS explains how to do it, I just can't figure out the signature part. – Dendory Mar 10 '15 at 14:20
  • This seems like a lot of pain to 'keep it small'; this is less than 10 lines of code to query it using the .NET SDK for AWS: http://docs.aws.amazon.com/AWSSdkDocsNET/latest/DeveloperGuide/sns-apis-intro.html – George Stocker Mar 10 '15 at 14:55
  • Yes, that's my fallback if this doesn't work. Just seems like a shame that this won't work, when it should. – Dendory Mar 10 '15 at 15:00
  • I'd still like to get this to work. Doing a REST call makes a 5kb binary. Using the AWS SDK requires a 6mb DLL file. – Dendory Mar 10 '15 at 15:12
  • @Dendory If that bugs you, you should see what happens when you run your application. Click on "Task Manager" and look at memory usage. that 5kb binary won't help you. – George Stocker Mar 10 '15 at 15:36
  • Not sure what you mean. The app I'm working on is a service and actually takes ~11kb of memory. I'm sure the 6mb SDK DLL wouldn't stay loaded in memory, but the distribution file for the service still goes from 5kb to 6mb which is significant. – Dendory Mar 10 '15 at 15:39
  • @Dendory the .NET runtime itself takes up memory on load; even if you strip down the binary, you're still not going to keep the runtime from loading when you actually run the program. http://stackoverflow.com/questions/1343374/reducing-memory-usage-of-net-applications Real question, however: are you in such a memory constrained state that 6 MB is the difference between life and death? – George Stocker Mar 10 '15 at 15:41
  • .NET 3.5 (which is what I'm coding this against) is installed on most systems. And no, it wouldn't be the end of the world to add the SDK to support this one API call, but that's not the real point. This piece of code should be working and I'm sure it's just a bit of syntax that needs to be fixed, which is what I'm trying to figure out. Seems like the lazy way out to just give up and accept going from 5kb distribution size to 6mb. – Dendory Mar 10 '15 at 15:49

1 Answers1

2

There is nothing wrong with rolling your own solution rather than using the SDKs. In fact, I prefer it, because in addition to more lightweight code, you are more likely to understand problems with unexpected behavior because you are working with the native interface.

Here's what you are missing:

Add the query string parameters ... sorted using lexicographic byte ordering

http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html

For example, TargetArn should not be before SignatureMethod. They all need to be sorted. There is only one possible correct signature for any given message, so the sort order is critical.

Michael - sqlbot
  • 169,571
  • 25
  • 353
  • 427
  • THANK YOU! You don't know how many hours I spent trying to get this to work. This means the difference between deploying a 10k binary and a 10k binary + 6mb dll + 16mb xml file just for one AWS call. – Dendory Mar 11 '15 at 13:12