Last active
December 18, 2015 11:32
-
-
Save moonmile/16c950174d0c276cc2c3 to your computer and use it in GitHub Desktop.
SignedRequestHelper.cs for WinRT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************************************** | |
* Copyright 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file | |
* except in compliance with the License. A copy of the License is located at | |
* | |
* http://aws.amazon.com/apache2.0/ | |
* | |
* or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" | |
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
* License for the specific language governing permissions and limitations under the License. | |
* | |
* ******************************************************************************************** | |
* | |
* Amazon Product Advertising API | |
* Signed Requests Sample Code | |
* | |
* API Version: 2009-03-31 | |
* | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
//using System.Web; | |
// using System.Security.Cryptography; | |
using Windows.Security.Cryptography.Core; | |
using Windows.Security.Cryptography; | |
using Windows.Storage.Streams; | |
namespace AmazonProductAdvtApi | |
{ | |
class SignedRequestHelper | |
{ | |
private string endPoint; | |
private string akid; | |
private string associateTag; | |
private byte[] secret; | |
private string secretKey; | |
// private HMAC signer; | |
private const string REQUEST_URI = "/onca/xml"; | |
private const string REQUEST_METHOD = "GET"; | |
/* | |
* Use this constructor to create the object. The AWS credentials are available on | |
* http://aws.amazon.com | |
* | |
* The destination is the service end-point for your application: | |
* US: ecs.amazonaws.com | |
* JP: ecs.amazonaws.jp | |
* UK: ecs.amazonaws.co.uk | |
* DE: ecs.amazonaws.de | |
* FR: ecs.amazonaws.fr | |
* CA: ecs.amazonaws.ca | |
*/ | |
public SignedRequestHelper(string awsAccessKeyId, string awsSecretKey, string destination, string associateTag) | |
{ | |
this.endPoint = destination.ToLower(); | |
this.akid = awsAccessKeyId; | |
this.secret = Encoding.UTF8.GetBytes(awsSecretKey); | |
this.associateTag = associateTag; | |
this.secretKey = awsSecretKey; | |
// this.signer = new HMACSHA256(this.secret); | |
} | |
/* | |
* Sign a request in the form of a Dictionary of name-value pairs. | |
* | |
* This method returns a complete URL to use. Modifying the returned URL | |
* in any way invalidates the signature and Amazon will reject the requests. | |
*/ | |
public string Sign(IDictionary<string, string> request) | |
{ | |
// Use a SortedDictionary to get the parameters in naturual byte order, as | |
// required by AWS. | |
ParamComparer pc = new ParamComparer(); | |
SortedDictionary<string, string> sortedMap = new SortedDictionary<string, string>(request, pc); | |
// Add the AWSAccessKeyId and Timestamp to the requests. | |
sortedMap["AWSAccessKeyId"] = this.akid; | |
sortedMap["Timestamp"] = this.GetTimestamp(); | |
sortedMap["AssociateTag"] = this.associateTag; | |
// Get the canonical query string | |
string canonicalQS = this.ConstructCanonicalQueryString(sortedMap); | |
// Derive the bytes needs to be signed. | |
StringBuilder builder = new StringBuilder(); | |
builder.Append(REQUEST_METHOD) | |
.Append("\n") | |
.Append(this.endPoint) | |
.Append("\n") | |
.Append(REQUEST_URI) | |
.Append("\n") | |
.Append(canonicalQS); | |
#if false | |
string stringToSign = builder.ToString(); | |
byte[] toSign = Encoding.UTF8.GetBytes(stringToSign); | |
// Compute the signature and convert to Base64. | |
byte[] sigBytes = signer.ComputeHash(toSign); | |
string signature = Convert.ToBase64String(sigBytes); | |
#else | |
string stringToSign = builder.ToString(); | |
string hashKey = this.secretKey; | |
MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA256"); | |
BinaryStringEncoding encoding = BinaryStringEncoding.Utf8; | |
var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign, encoding); | |
IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey, encoding); | |
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer); | |
IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer); | |
string signature = CryptographicBuffer.EncodeToBase64String(signedMessage); | |
#endif | |
// now construct the complete URL and return to caller. | |
StringBuilder qsBuilder = new StringBuilder(); | |
qsBuilder.Append("http://") | |
.Append(this.endPoint) | |
.Append(REQUEST_URI) | |
.Append("?") | |
.Append(canonicalQS) | |
.Append("&Signature=") | |
.Append(this.PercentEncodeRfc3986(signature)); | |
return qsBuilder.ToString(); | |
} | |
/* | |
* Sign a request in the form of a query string. | |
* | |
* This method returns a complete URL to use. Modifying the returned URL | |
* in any way invalidates the signature and Amazon will reject the requests. | |
*/ | |
public string Sign(string queryString) | |
{ | |
IDictionary<string, string> request = this.CreateDictionary(queryString); | |
return this.Sign(request); | |
} | |
/* | |
* Current time in IS0 8601 format as required by Amazon | |
*/ | |
private string GetTimestamp() | |
{ | |
DateTime currentTime = DateTime.UtcNow; | |
string timestamp = currentTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); | |
return timestamp; | |
} | |
/* | |
* Percent-encode (URL Encode) according to RFC 3986 as required by Amazon. | |
* | |
* This is necessary because .NET's HttpUtility.UrlEncode does not encode | |
* according to the above standard. Also, .NET returns lower-case encoding | |
* by default and Amazon requires upper-case encoding. | |
*/ | |
private string PercentEncodeRfc3986(string str) | |
{ | |
str = System.Net.WebUtility.UrlEncode(str); | |
str = str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~").Replace("+", "%20"); | |
StringBuilder sbuilder = new StringBuilder(str); | |
for (int i = 0; i < sbuilder.Length; i++) | |
{ | |
if (sbuilder[i] == '%') | |
{ | |
if (Char.IsLetter(sbuilder[i + 1]) || Char.IsLetter(sbuilder[i + 2])) | |
{ | |
sbuilder[i + 1] = Char.ToUpper(sbuilder[i + 1]); | |
sbuilder[i + 2] = Char.ToUpper(sbuilder[i + 2]); | |
} | |
} | |
} | |
return sbuilder.ToString(); | |
} | |
/* | |
* Convert a query string to corresponding dictionary of name-value pairs. | |
*/ | |
private IDictionary<string, string> CreateDictionary(string queryString) | |
{ | |
Dictionary<string, string> map = new Dictionary<string, string>(); | |
string[] requestParams = queryString.Split('&'); | |
for (int i = 0; i < requestParams.Length; i++) | |
{ | |
if (requestParams[i].Length < 1) | |
{ | |
continue; | |
} | |
char[] sep = { '=' }; | |
string[] param = requestParams[i].Split(sep, 2); | |
for (int j = 0; j < param.Length; j++) | |
{ | |
param[j] = System.Net.WebUtility.UrlDecode(param[j]); | |
} | |
switch (param.Length) | |
{ | |
case 1: | |
{ | |
if (requestParams[i].Length >= 1) | |
{ | |
if (requestParams[i].ToCharArray()[0] == '=') | |
{ | |
map[""] = param[0]; | |
} | |
else | |
{ | |
map[param[0]] = ""; | |
} | |
} | |
break; | |
} | |
case 2: | |
{ | |
if (!string.IsNullOrEmpty(param[0])) | |
{ | |
map[param[0]] = param[1]; | |
} | |
} | |
break; | |
} | |
} | |
return map; | |
} | |
/* | |
* Consttuct the canonical query string from the sorted parameter map. | |
*/ | |
private string ConstructCanonicalQueryString(SortedDictionary<string, string> sortedParamMap) | |
{ | |
StringBuilder builder = new StringBuilder(); | |
if (sortedParamMap.Count == 0) | |
{ | |
builder.Append(""); | |
return builder.ToString(); | |
} | |
foreach (KeyValuePair<string, string> kvp in sortedParamMap) | |
{ | |
builder.Append(this.PercentEncodeRfc3986(kvp.Key)); | |
builder.Append("="); | |
builder.Append(this.PercentEncodeRfc3986(kvp.Value)); | |
builder.Append("&"); | |
} | |
string canonicalString = builder.ToString(); | |
canonicalString = canonicalString.Substring(0, canonicalString.Length - 1); | |
return canonicalString; | |
} | |
} | |
/* | |
* To help the SortedDictionary order the name-value pairs in the correct way. | |
*/ | |
class ParamComparer : IComparer<string> | |
{ | |
public int Compare(string p1, string p2) | |
{ | |
return string.CompareOrdinal(p1, p2); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment