AWS Signature v4 key value store KVS in C# - c#

I'm trying to integrate "Signing AWS Requests with Signature Version 4" in C#. Unfortunately, Amazon has not given much detail into implementation in C#. I've tried my luck in implementing. But its throwing this error Message
"The remote server returned an error: (403) Forbidden.
Note: I've got this working on Postman. But not able to make it work in C#.
Error receiving: Message = "The remote server returned an error: (403) Forbidden.
I've used this documentation for signing https://docs.amazonaws.cn/en_us/general/latest/gr/sigv4_signing.html
public WebRequest RequestPost(string canonicalUri, string canonicalQueriString, string jsonString)
{
string hashedRequestPayload = CreateRequestPayload(jsonString);
string authorization = Sign(hashedRequestPayload, "PUT", canonicalUri, canonicalQueriString);
string requestDate = DateTime.ParseExact(DateTime.UtcNow.ToString(), "YYYYMMDD'T'HHMMSS'Z'", CultureInfo.InvariantCulture).ToString();
WebRequest webRequest = WebRequest.Create(canonicalUri + canonicalQueriString);
webRequest.Method = "PUT";
webRequest.ContentType = ContentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
webRequest.ContentLength = jsonString.Length;
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] data = encoding.GetBytes(jsonString);
Stream newStream = webRequest.GetRequestStream();
newStream.Write(data, 0, data.Length);
using (WebResponse response = webRequest.GetResponse())
{
StreamReader responseReader = new StreamReader(response.GetResponseStream());
var responseJson = responseReader.ReadToEnd();
}
return webRequest;
}
const string RegionName = "eu-west-1"; //This is the regionName
const string ServiceName = "execute-api";
const string Algorithm = "AWS4-HMAC-SHA256";
const string ContentType = "application/json";
const string Host = "<hostname>";
const string SignedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date";
private static string CreateRequestPayload(string jsonString)
{
//Here should be JSON object of the model we are sending with POST request
//var jsonToSerialize = new { Data = String.Empty };
//We parse empty string to the serializer if we are makeing GET request
//string requestPayload = new JavaScriptSerializer().Serialize(jsonToSerialize);
string hashedRequestPayload = HexEncode(Hash(ToBytes(jsonString)));
return hashedRequestPayload;
}
private static string Sign(string hashedRequestPayload, string requestMethod, string canonicalUri, string canonicalQueryString)
{
var currentDateTime = DateTime.UtcNow;
var accessKey = "";
var secretKey = "";
var dateStamp = currentDateTime.ToString("yyyyMMdd");
//var requestDate = currentDateTime.ToString("YYYYMMDD'T'HHMMSS'Z");
var requestDate = DateTime.ParseExact(currentDateTime.ToString(), "YYYYMMDD'T'HHMMSS'Z'", CultureInfo.InvariantCulture);
var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, RegionName, ServiceName);
var headers = new SortedDictionary<string, string> {
{ "content-type", ContentType },
{ "host", Host },
{ "x-amz-date", requestDate.ToString() }
};
string canonicalHeaders = string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
// Task 1: Create a Canonical Request For Signature Version 4
string canonicalRequest = requestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + SignedHeaders + "\n" + hashedRequestPayload;
string hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
// Task 2: Create a String to Sign for Signature Version 4
string stringToSign = Algorithm + "\n" + requestDate + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
// Task 3: Calculate the AWS Signature Version 4
byte[] signingKey = GetSignatureKey(secretKey, dateStamp, RegionName, ServiceName);
string signature = HexEncode(HmacSha256(stringToSign, signingKey));
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
string authorization = string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
Algorithm, accessKey, dateStamp, RegionName, ServiceName, SignedHeaders, signature);
return authorization;
}
private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
{
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
byte[] kRegion = HmacSha256(regionName, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] Hash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(string data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}

I had the same problem , the solution is using RestClient rather than HttpClient or WebRequest .

Would you be willing to use the HttpClient instead of WebRequest? In that case I have created a NuGet package called AwsSignatureVersion4 which has been verified to work with the AWS API Gateway.

Related

Orderhive AWS4 Signature not match

I'm tring to connect to AWS4 Signature method for authentication.
(https://orderhive.docs.apiary.io/#introduction/api-requirements/end-point)
My id_token and refresh_token retreive the access_key_id, secret_key, and session_token.
But when I try to retreive some information like the warehouse, I receive each time:
"message":"The 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.
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20211217T160055Z
20211217/us-east-1/execute-api/aws4_request
8e3dbc663f97508406c4825b74a647765022ae021fa224754701722b7bcf2288'
And I am using this code like others have done before me in some example.
public const string SCHEME = "AWS4";
public const string ALGORITHM = "HMAC-SHA256";
public const string TERMINATOR = "aws4_request";
public const string ISO8601BasicFormat = "yyyyMMddTHHmmssZ";
public const string DateStringFormat = "yyyyMMdd";
public const string X_Amz_Date = "X-Amz-Date";
public const string RegionName = "us-east-1";
public const string ServiceName = "execute-api";
public const string ContentType = "application/json";
public const string SignedHeaders = "content-type;host;id_token;x-amz-date;x-amz-security-token";
public const string X_Amz_Content_SHA256 = "X-Amz-Content-SHA256";
private Account account;
public void GetWarehouse()
{
try
{
WebRequest webRequest = RequestGet("/setup/warehouse", "", "");
using (WebResponse response = webRequest.GetResponse())
{
StreamReader responseReader = new StreamReader(response.GetResponseStream());
string jsonResponse = responseReader.ReadToEnd();
}
}
catch (Exception ex)
{}
}
public WebRequest RequestGet(string canonicalUri, string canonicalQueriString, string jsonString)
{
string hashedRequestPayload = CreateRequestPayload("");
string authorization = Sign(hashedRequestPayload, "GET", canonicalUri, canonicalQueriString);
string requestDate = DateTime.UtcNow.ToString(ISO8601BasicFormat, CultureInfo.InvariantCulture);
WebRequest webRequest = WebRequest.Create("https://" + Host + canonicalUri);
webRequest.Method = "GET";
webRequest.ContentType = ContentType;
webRequest.Headers.Add("id_token", account.id_token);
webRequest.Headers.Add("X-Amz-Security-Token", account.session_token);
webRequest.Headers.Add(X_Amz_Date, requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add(X_Amz_Content_SHA256, hashedRequestPayload);
return webRequest;
}
private string Sign(string hashedRequestPayload, string requestMethod, string canonicalUri, string canonicalQueryString)
{
var currentDateTime = DateTime.UtcNow;
var accessKey = account.access_key_id;
var secretKey = account.secret_key;
var dateStamp = currentDateTime.ToString(DateStringFormat);
var requestDate = currentDateTime.ToString(ISO8601BasicFormat, CultureInfo.InvariantCulture);
var credentialScope = string.Format("{0}/{1}/{2}/{3}", dateStamp, RegionName, ServiceName, TERMINATOR);
var headers = new SortedDictionary<string, string> {
{ "content-type", ContentType },
{ "host", Host },
{ X_Amz_Date, requestDate.ToString() }
};
string canonicalHeaders = string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
// Task 1: Create a Canonical Request For Signature Version 4
string canonicalRequest = requestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + SignedHeaders + "\n" + hashedRequestPayload;
Console.WriteLine("\nCanonicalRequest:\n{0}", canonicalRequest);
string hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
// Task 2: Create a String to Sign for Signature Version 4
string stringToSign = SCHEME + "-" + ALGORITHM + "\n" + requestDate + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
Console.WriteLine("\nStringToSign:\n{0}", stringToSign);
// Task 3: Calculate the AWS Signature Version 4
byte[] signingKey = GetSignatureKey(secretKey, dateStamp, RegionName, ServiceName);
string signature = HexEncode(HmacSHA256(stringToSign, signingKey));
Console.WriteLine("\nSignature:\n{0}", signature);
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
string authorization = string.Format("{0} Credential={1}/{2}/{3}/{4}/{5}, SignedHeaders={6}, Signature={7}", SCHEME + "-" + ALGORITHM, accessKey, dateStamp, RegionName, ServiceName, TERMINATOR, SignedHeaders, signature);
Console.WriteLine("\nAuthorization:\n{0}", authorization);
return authorization;
}
private byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
{
byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + key).ToCharArray());
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256(TERMINATOR, kService);
return kSigning;
}
static byte[] HmacSHA256(String data, byte[] key)
{
string algorithm = "HmacSHA256";
KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
kha.Key = key;
return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static byte[] Hash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
Anyone have any idea what I'm doing wrong?
Thank you!
Finally, I changed my Sign method by this:
public async Task<HttpRequestMessage> Sign(HttpRequestMessage request, string service, string region, TimeSpan? timeOffset = null)
{
if (!IsValidRequest(request, service, region))
throw new Exception("Invalid request!");
if (request.Headers.Host == null)
{
request.Headers.Host = request.RequestUri.Host;
}
var payloadHash = await GetPayloadHash(request.Content);
if (request.Headers.Contains("x-amz-content-sha256") == false)
request.Headers.Add("x-amz-content-sha256", payloadHash);
if (request.Headers.Contains("x-amz-security-token") == false)
request.Headers.Add("x-amz-security-token", Account.session_token);
var t = DateTimeOffset.UtcNow;
if (timeOffset.HasValue)
t = t.Add(timeOffset.Value);
var amzDate = t.ToString(ISO8601BasicFormat);
var dateStamp = t.ToString(DateStringFormat);
request.Headers.Add("x-amz-date", amzDate);
var canonicalUri = string.Join("/", request.RequestUri.AbsolutePath.Split('/').Select(Uri.EscapeDataString));
var canonicalQueryParams = GetCanonicalQueryParams(request);
var signedHeadersList = new List<string>();
var canonicalHeaders = new StringBuilder();
foreach (var header in request.Headers.OrderBy(a => a.Key.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase))
{
var headerValue = (header.Key == "Host") ? EndPoint : string.Join(",", header.Value.Select(s => s.Trim()));
canonicalHeaders.Append($"{header.Key.ToLowerInvariant()}:{headerValue}\n");
signedHeadersList.Add(header.Key.ToLowerInvariant());
}
var signedHeaders = string.Join(";", signedHeadersList);
string canonicalRequest = $"{request.Method}\n{canonicalUri}\n{canonicalQueryParams}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
string hashedCanonicalRequest = Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString()));
var credentialScope = $"{dateStamp }/{region}/{service}/{Terminator}";
var signingKey = GetSignatureKey(Account.secret_key, dateStamp, region, service);
var stringToSign = $"{AWS4AlgorithmTag}\n{amzDate}\n{credentialScope}\n{hashedCanonicalRequest}";
var signature = ToHexString(HmacSha256(signingKey, stringToSign));
string authorization = $"{AWS4AlgorithmTag} Credential={Account.access_key_id}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}";
request.Headers.TryAddWithoutValidation("Authorization", authorization);
return request;
}
Now it's working perfectly. A thing really important is: In test, the Host need to be the good on, https://api.orderhive.com/. The problem of my previous question is, when I build the header, for an obscure reason, he have something wrong when the AWS calcule the signature. Hope this helps others who encounter this problem.

Write content to a file on azure storage with using PUT request in C#

I am trying to make a put request to Azure storage file, where I want to add some simple contents. I change the URL and add ?comp=range at the end of the url but I get 403 error in response. I have created a basic console application in .net.
My Header is :
const string requestMethod = "PUT";
string urlPath = strShareName + "/" + "rahila.csv?comp=range";//+ "?comp=range HTTP/1.1";
String canonicalizedResource = String.Format("/{0}/{1}/{2}", StorageAccountName, strShareName, strFileName);
try
{
//GetWebRequest(requestMethod, urlPath, canonicalizedResource, "CreateFile");
HttpWebRequest request = null;
try
{
const string type = "file";
string MethodType = "CreateFile";
const string msVersion = "2015-04-05";
String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
String canonicalizedHeaders = "";
string data = "rahila sted";
canonicalizedHeaders = String.Format("x-ms-date:{0}\nx-ms-version:{1}", dateInRfc1123Format, msVersion);
if (MethodType == "CreateFile")
{
canonicalizedHeaders = String.Format("x-ms-content-length:65536\nx-ms-date:{0}\nx-ms-type:file\nx-ms-version:{1}", dateInRfc1123Format, msVersion);
}
String stringToSign = "";
stringToSign = String.Format("{0}\n\n\n\n\n\n\n\n\n\n\n\n{1}\n{2}", requestMethod, canonicalizedHeaders, canonicalizedResource);
if (String.IsNullOrEmpty(stringToSign))
{
throw new ArgumentNullException("canonicalizedString");
}
String signature;
if (String.IsNullOrEmpty(stringToSign))
{
throw new ArgumentNullException("unsignedString");
}
if (Convert.FromBase64String(StorageKey) == null)
{
throw new ArgumentNullException("key");
}
Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(stringToSign);
using (HMACSHA256 hmacSha256 = new HMACSHA256(Convert.FromBase64String(StorageKey)))
{
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
String authorizationHeader = String.Format(CultureInfo.InvariantCulture, "{0} {1}:{2}",
StorageScheme,
StorageAccountName, signature);
Uri uri = new Uri(FileEndPoint + urlPath);
request = (HttpWebRequest)WebRequest.Create(uri);
if (requestMethod != "Get")
{
request.ContentLength = data.Length;
}
// string data = "Hello testing";
//int a= ((data.Length) + 1);
request.Method = "PUT";//requestMethod;
request.Headers.Add("x-ms-write", "update");
request.Headers.Add("x-ms-date", dateInRfc1123Format);
request.Headers.Add("x-ms-version", msVersion);
request.Headers.Add("x-ms-range", "bytes=0-65535"); // + ((data.Length) - 1));
request.Headers.Add("Authorization", authorizationHeader);
the line where i get the exception is in the bold format.
HttpWebResponse response = null;
response = (HttpWebResponse)request.GetResponse();
string returnString = response.StatusCode.ToString();
Can anyone help me to resolve this issue or just guide me how to write content to a simple file on azure storage without using the azure client API.
update 12/19:
When using Put Range to upload content to azure file, you can follow the following code(I assume you have already created a file on the azure file share, and it's content length is larger than the content being uploaded):
static void UploadText()
{
string Account = "xxxx";
string Key = "xxxx";
string FileShare = "test1";
string FileName = "11.txt";
string apiversion = "2019-02-02";
//the string to be uploaded to azure file, note that the length of the uploaded string should less than the azure file length
string upload_text = "bbbbbbbbbbbbbbbbbbbbbbbb.";
Console.WriteLine("the string length: " + upload_text.Length);
DateTime dt = DateTime.UtcNow;
string StringToSign = String.Format("PUT\n"
+ "\n" // content encoding
+ "\n" // content language
+ upload_text.Length + "\n" // content length
+ "\n" // content md5
+ "\n" // content type
+ "\n" // date
+ "\n" // if modified since
+ "\n" // if match
+ "\n" // if none match
+ "\n" // if unmodified since
+ "\n"//+ "bytes=0-" + (upload_text.Length - 1) + "\n" // range
+"x-ms-date:" + dt.ToString("R") + "\nx-ms-range:bytes=0-"+(upload_text.Length-1) + "\nx-ms-version:" + apiversion + "\nx-ms-write:update\n" // headers
+ "/{0}/{1}/{2}\ncomp:range", Account, FileShare, FileName);
string auth = SignThis(StringToSign, Key, Account);
string method = "PUT";
string urlPath = string.Format("https://{0}.file.core.windows.net/{1}/{2}?comp=range", Account, FileShare,FileName);
Uri uri = new Uri(urlPath);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = method;
request.ContentLength = upload_text.Length;
request.Headers.Add("x-ms-range", "bytes=0-"+(upload_text.Length-1));
request.Headers.Add("x-ms-write", "update");
request.Headers.Add("x-ms-date", dt.ToString("R"));
request.Headers.Add("x-ms-version", apiversion);
request.Headers.Add("Authorization", auth);
//request.Headers.Add("Content-Length", upload_text.Length.ToString());
var bytes = System.Text.Encoding.ASCII.GetBytes(upload_text);
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
//read the content
Console.WriteLine("the response is:" + response.StatusCode);
}
}
private static String SignThis(String StringToSign, string Key, string Account)
{
String signature = string.Empty;
byte[] unicodeKey = Convert.FromBase64String(Key);
using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
{
Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
String authorizationHeader = String.Format(
CultureInfo.InvariantCulture,
"{0} {1}:{2}",
"SharedKey",
Account,
signature);
return authorizationHeader;
}
Then in the Main() method, you can call UploadText() method, it works at my side.
old:
guide me how to write content to a simple file on azure storage
without using the azure client API.
For this, you can directly use Azure File Storage SDK Microsoft.Azure.Storage.File, version 11.1.1. And we always recommend using SDK instead of using rest api, because the SDK is easy to use.
Here is an example of using this SDK.
First, create a console project of .NET framework in visual studio. Then install this nuget package Microsoft.Azure.Storage.File, version 11.1.1.
The code:
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Auth;
using Microsoft.Azure.Storage.File;
using System;
namespace AzureFileTest2
{
class Program
{
static void Main(string[] args)
{
string accountName = "xxx";
string accountKey = "xxx";
CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
CloudFileClient cloudFileClient = storageAccount.CreateCloudFileClient();
//make sure the file share named test1 exists.
CloudFileShare fileShare = cloudFileClient.GetShareReference("test1");
CloudFileDirectory fileDirectory = fileShare.GetRootDirectoryReference();
CloudFile myfile = fileDirectory.GetFileReference("test123.txt");
if (!myfile.Exists())
{
//if the file does not exists, then create the file and set the file max size to 100kb.
myfile.Create(100 * 1024 * 1024);
}
//upload text to the file
//Besides using UploadText() method to directly upload text, you can also use UploadFromFile() / UploadFromByteArray() / UploadFromStream() methods as per your need.
myfile.UploadText("hello, it is using azure storage SDK");
Console.WriteLine("**completed**");
Console.ReadLine();
}
}
}

AWS API Gateway Signature

I am trying to sign my requests to the amazon gateway. But every time when I try to send a POST request it tells me that my signature has been expired. Any ideas will be appreciated.
You have some problem with getting the time or something like that. I had the problem with the payload. So if you are making GET request your payload is an EMPTY STRING. Otherwise it should be hashed Json object. Here is example of how I do it in my application. The code can be raw, but I am 100000% it work, because I am using it every day.
const string RegionName = "eu-west-1"; //This is the regionName
const string ServiceName = "apigateway";
const string Algorithm = "AWS4-HMAC-SHA256";
const string ContentType = "application/json";
const string Host = "apigateway.eu-west-1.amazonaws.com";
const string SignedHeaders = "content-type;host;x-amz-date";
public static WebRequest RequestGet(string canonicalUri, string canonicalQueriString, string jsonString) {
string hashedRequestPayload = CreateRequestPayload("");
string authorization = Sign(hashedRequestPayload, "GET", canonicalUri, canonicalQueriString);
string requestDate = DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + "Z";
WebRequest webRequest = WebRequest.Create("https://" + Host + canonicalUri);
webRequest.Method = "GET";
webRequest.ContentType = ContentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
return webRequest;
}
public static WebRequest RequestPost(string canonicalUri, string canonicalQueriString, string jsonString)
{
string hashedRequestPayload = CreateRequestPayload(jsonString);
string authorization = Sign(hashedRequestPayload, "POST", canonicalUri, canonicalQueriString);
string requestDate = DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + "Z";
WebRequest webRequest = WebRequest.Create("https://" + Host + canonicalUri);
webRequest.Timeout = 20000;
webRequest.Method = "POST";
webRequest.ContentType = ContentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
webRequest.ContentLength = jsonString.Length;
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] data = encoding.GetBytes(jsonString);
Stream newStream = webRequest.GetRequestStream();
newStream.Write(data, 0, data.Length);
return webRequest;
}
private static string CreateRequestPayload(string jsonString) {
//Here should be JSON object of the model we are sending with POST request
//var jsonToSerialize = new { Data = String.Empty };
//We parse empty string to the serializer if we are makeing GET request
//string requestPayload = new JavaScriptSerializer().Serialize(jsonToSerialize);
string hashedRequestPayload = HexEncode(Hash(ToBytes(jsonString)));
return hashedRequestPayload;
}
private static string Sign(string hashedRequestPayload, string requestMethod, string canonicalUri, string canonicalQueryString) {
var currentDateTime = DateTime.UtcNow;
var accessKey = //Here place your app ACCESS_KEY
var secretKey = //Here is a place for you app SECRET_KEY
var dateStamp = currentDateTime.ToString("yyyyMMdd");
var requestDate = currentDateTime.ToString("yyyyMMddTHHmmss") + "Z";
var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, RegionName, ServiceName);
var headers = new SortedDictionary < string, string > {
{ "content-type", ContentType },
{ "host", Host },
{ "x-amz-date", requestDate }
};
string canonicalHeaders = string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
// Task 1: Create a Canonical Request For Signature Version 4
string canonicalRequest = requestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + SignedHeaders + "\n" + hashedRequestPayload;
string hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
// Task 2: Create a String to Sign for Signature Version 4
string stringToSign = Algorithm + "\n" + requestDate + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
// Task 3: Calculate the AWS Signature Version 4
byte[] signingKey = GetSignatureKey(secretKey, dateStamp, RegionName, ServiceName);
string signature = HexEncode(HmacSha256(stringToSign, signingKey));
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
string authorization = string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
Algorithm, accessKey, dateStamp, RegionName, ServiceName, SignedHeaders, signature);
return authorization;
}
private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName) {
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
byte[] kRegion = HmacSha256(regionName, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static byte[] ToBytes(string str) {
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static string HexEncode(byte[] bytes) {
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] Hash(byte[] bytes) {
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(string data, byte[] key) {
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
So for example if I want to get all the APIs that are deployed in the Gateway I am doing like this:
using(WebResponse response = webRequest.GetResponse()) {
StreamReader responseReader = new StreamReader(response.GetResponseStream());
string responseJson = responseReader.ReadToEnd();
} catch (WebException) {
//Doing something when exception has been thrown
}
Here is the interesting part of creating a API Key. First you need to make your raw payload and then pass it to the methods I gave you above:
string payload = "{ \"name\" : \"" + name + "\", \"description\" : \"" + description.Trim() + "\", \"enabled\" : \"True\", \"stageKeys\" : [ ] }";
WebRequest webRequest = RequestSignerAWS.RequestPost("/apikeys", "", payload);
And make sure you are getting the time of the creating the request, because this will cause you the problem you are having.
You can look or use the code in this project for sending requests to API gateway:
https://github.com/ronenfe/Addons.AwsSdk
It uses this code for the signing:
https://github.com/tsibelman/aws-signer-v4-dot-net .

How do I call Amazon's Book API in C#?

I am trying to query Amazon's Web Service (AWS) Books API in C#. I read the related posts on Stackoverflow with no luck. I still get the error: "The remote server returned an error: (403) Forbidden". I encoded all +'s and ='s. What am I doing wrong here please?
public const string ACCESSKEY = "xxxxx"; // Replace with Actual Access Key
public const string SECRETKEY = "zzzzz"; // Replace with Actual Secret Key
public void ProcessRequest(HttpContext context)
{
string ts = DateTime.UtcNow.ToString("s");
string url = "http://webservices.amazon.com/onca/xml?";
string req = "AWSAccessKeyId=" + ACCESSKEY + "&Condition=All&IdType=ASIN&ItemId=B00008OE6I&Operation=ItemLookup&ResponseGroup=OfferFull&Service=AWSECommerceService";
req = req + "&Timestamp=" + URLEncode(ts + "Z").ToUpper();
string s = "GET\nwebservices.amazon.com\n/onca/xml\n" + req;
Util.Write(s);
Util.Write("");
string hash = HashString(s);
req = req + "&Signature=" + hash;
url = url + req;
Util.Write(url);
Util.Write("");
string blob = XGetBlobAtURL(url);
Util.Write(blob);
}
private string URLEncode(string s)
{
return(HttpContext.Current.Server.UrlEncode(s));
}
private string XGetBlobAtURL(string url, string useragent = "")
{
string blob = "";
try
{
WebClient wc = new WebClient();
if (!Util.IsEmpty(useragent)) { wc.Headers["User-Agent"] = useragent; }
blob = wc.DownloadString(url);
wc.Dispose();
}
catch(Exception e)
{
Util.Write(e.ToString());
}
return (blob);
}
private string HashString(string s)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(URLEncode(SECRETKEY));
HMACSHA256 hmasha256 = new HMACSHA256(keyByte);
byte[] messageBytes = encoding.GetBytes(s);
byte[] hashmessage = hmasha256.ComputeHash(messageBytes);
string s2 = ByteToString(hashmessage);
return (URLEncode(s2));
}
private string ByteToString(byte[] buff)
{
string sbinary = "";
for (int i = 0; i < buff.Length; i++)
{
sbinary += buff[i].ToString("X2"); // hex format
}
return (sbinary);
}
Solution by OP.
I figured out what I was doing wrong. I had to: Base64 encode the HMASHA256 hash, add missing fields on the query string to Amazon's Book API, and uppercase the URLEncoded %XX without uppercasing the rest of the string. The code posted below now works and matches Amazon's documentation at: http://docs.aws.amazon.com/AWSECommerceService/latest/DG/rest-signature.html
public const string ACCESSKEY = "xxxxx";
public const string SECRETKEY = "zzzzz";
public const string TAG = "ccccc";
private XmlDocument XML;
public void ProcessRequest(HttpContext context)
{
string ts = DateTime.UtcNow.ToString("s");
string url = "http://webservices.amazon.com/onca/xml?";
string req = "AWSAccessKeyId=" + ACCESSKEY + "&AssociateTag=" + TAG;
// Search by ISBN:
req = req + "&Condition=All&IdType=ISBN&ItemId=0596004923&Operation=ItemLookup&ResponseGroup=Small&SearchIndex=Books&Service=AWSECommerceService";
string tsq = "&Timestamp=" + URLEncode(ts + "Z").ToUpper();
string s = "GET\nwebservices.amazon.com\n/onca/xml\n" + req + tsq;
string hash = HashString(s);
req = req + tsq + "&Signature=" + hash;
url = url + req;
ReadXML(url);
WriteXML();
}
private void ReadXML(string url)
{
XML = new XmlDocument();
XML.Load(url);
}
private void WriteXML()
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "text/xml";
HttpContext.Current.Response.CacheControl = "no-cache";
HttpContext.Current.Response.Write(XML.OuterXml);
HttpContext.Current.Response.End();
}
private string UCaseUrlEncode(string s)
{
char[] x = HttpUtility.UrlEncode(s).ToCharArray();
for (int i = 0; i < x.Length - 2; i++)
{
if (x[i] == '%')
{
x[i+1] = char.ToUpper(x[i+1]);
x[i+2] = char.ToUpper(x[i+2]);
}
}
return new string(x);
}
private string URLEncode(string s)
{
return(HttpContext.Current.Server.UrlEncode(s));
}
private string URLDecode(string s)
{
return (HttpContext.Current.Server.UrlDecode(s));
}
private string HashString(string s)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(SECRETKEY);
byte[] messageBytes = encoding.GetBytes(s);
HMACSHA256 hmasha256 = new HMACSHA256(keyByte);
byte[] hashmessage = hmasha256.ComputeHash(messageBytes);
return (UCaseUrlEncode(Convert.ToBase64String(hashmessage)));
}
private string ByteToString(byte[] buff)
{
string sbinary = "";
for (int i = 0; i < buff.Length; i++)
{
sbinary += buff[i].ToString("X2"); // hex format
}
return (sbinary);
}

Ivona Request Signing Issue - signature does not match (AWS Signature Version 4)

I am trying to implement Ivona request signing based on this documnent
Everything works good and all the results match to the example value, except Signature result. So my result for the signature is cf1141e33a8fbba23913f8f36f29faa524a57db37690a1b819f43bbeaabf3b76 but in the document it is equal to 2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc
Is it my mistake or a mistake in the Ivona document?
Below is the C# code I am using:
class Program
{
static void Main()
{
try
{
Console.WriteLine(SendIvonaRequest());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static string SendIvonaRequest()
{
var date = new DateTime(2013, 09, 13, 09, 20, 54, DateTimeKind.Utc);
const string algorithm = "AWS4-HMAC-SHA256";
const string regionName = "eu-west-1";
const string serviceName = "tts";
const string method = "POST";
const string canonicalUri = "/CreateSpeech";
const string canonicalQueryString = "";
const string contentType = "application/json";
const string accessKey = "MyAccessKey";
const string secretKey = "MySecretKey";
const string host = serviceName + "." + regionName + ".ivonacloud.com";
const string requestPayload = "{\"Input\":{\"Data\":\"Hello world\"}}";
var hashedRequestPayload = HexEncode(Hash(ToBytes(requestPayload)));
Debug.Assert(hashedRequestPayload.Equals("f43e25253839f2c3feae433c5e477d79f7dfafdc0e4af19a952adb44a60265ba"));
var dateStamp = date.ToString("yyyyMMdd");
var requestDate = date.ToString("yyyyMMddTHHmmss") + "Z";
var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, regionName, serviceName);
var headers = new SortedDictionary<string, string>
{
{"content-type", "application/json"},
{"host", "tts.eu-west-1.ivonacloud.com"},
{"x-amz-content-sha256", hashedRequestPayload},
{"x-amz-date", requestDate}
};
string canonicalHeaders =
string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
const string signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date";
// Task 1: Create a Canonical Request For Signature Version 4
var canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQueryString +
'\n' + canonicalHeaders + '\n' + signedHeaders + '\n' + hashedRequestPayload;
var hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
Debug.Assert(hashedCanonicalRequest.Equals("73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));
// Task 2: Create a String to Sign for Signature Version 4
// StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest
var stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", algorithm, requestDate, credentialScope,
hashedCanonicalRequest);
Debug.Assert(stringToSign.Equals("AWS4-HMAC-SHA256" + "\n" +
"20130913T092054Z" + "\n" +
"20130913/eu-west-1/tts/aws4_request" + "\n" +
"73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));
// Task 3: Calculate the AWS Signature Version 4
// HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20130913"),"eu-west-1"),"tts"),"aws4_request")
byte[] signingKey = GetSignatureKey(secretKey, dateStamp, regionName, serviceName);
// signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
var signature = HexEncode(HmacSha256(stringToSign, signingKey));
Debug.Assert(signature.Equals("2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc"));
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
var authorization =
string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
algorithm, accessKey, dateStamp, regionName, serviceName, signedHeaders, signature);
// Send the request
var webRequest = WebRequest.Create("https://" + host + canonicalUri);
webRequest.Method = method;
webRequest.Timeout = 2000;
webRequest.ContentType = contentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
webRequest.ContentLength = requestPayload.Length;
using (Stream newStream = webRequest.GetRequestStream())
{
newStream.Write(ToBytes(requestPayload), 0, requestPayload.Length);
}
var response = (HttpWebResponse) webRequest.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (var streamReader = new StreamReader(responseStream))
{
return streamReader.ReadToEnd();
}
}
}
return string.Empty;
}
private static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName)
{
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
byte[] kRegion = HmacSha256(regionName, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] Hash(byte[] bytes)
{
var sha256 = SHA256.Create();
return sha256.ComputeHash(bytes);
}
private static byte[] HmacSha256(String data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
}
UPD1:
I have also tried the examples from http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html and noticed that my code generate the same signature as in those examples. So I am assuming that there is an issue in Ivona document...
UPD2:
Everything works fine! I have implemented CreateSpeech method according to the description and uploaded a full example of the usage to GitHub
https://github.com/MalyutinS/DotNetIvonaAPI
Solved! Actually there is an issue in documentation example. So the code works fine.
It's a bit old now, but it may be relevant to others using this code;
The code works fine as long as you only stick to ASCII-characters. For languages other than english, you must first convert the string to a UTF-8 byte array. That array may be longer than the number of characters in the string, consequently the length of the byte array sent through the WebRequest must reflect the length of the array itself, and not the character count.
Really a classic, but relevant to point out anyway..
This error results in the signature not being correct, hence an authentication error. The reason may not be obvious to everyone.
So the existing code;
webRequest.Method = method;
webRequest.Timeout = 2000;
webRequest.ContentType = contentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
webRequest.ContentLength = requestPayload.Length;
using (Stream newStream = webRequest.GetRequestStream())
{
newStream.Write(ToBytes(requestPayload), 0, requestPayload.Length);
}
Becomes:
var bytes = ToBytes(requestPayload);
webRequest.Method = method;
webRequest.Timeout = 2000;
webRequest.ContentType = contentType;
webRequest.Headers.Add("X-Amz-date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
webRequest.ContentLength = bytes.Length;
using (Stream newStream = webRequest.GetRequestStream())
{
newStream.Write(bytes, 0, bytes.Length);
newStream.Flush();
}
Also; for a more proper and reusable way of doing AWS-4 signatures, see Amazons own: http://aws.amazon.com/code

Categories