Reputation: 630
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);
}
Upvotes: 4
Views: 4323
Reputation: 179404
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.
Upvotes: 2