I have been working on a project and i used to use the v1 api for coinigy and loved the API. The problem is now with the new v2 API, I have been having an issue with trying to work the private API with the hmac_sha256 signature. I originally contacts coinigy support, but with little luck was not able to get it fully working (they did help, but never solved the issue, which is very weird). I built a simple test project in C# to perform a private call with the documentation they had for their api.
program.cs:
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace coinigy_example
{
class Program
{
static void Main(string[] args)
{
string api_key = "API_KEY";
string api_secret = "API_SECRET";
CoinigyApiPrivateCall call = new CoinigyApiPrivateCall(api_key, api_decret);
string data = call.HttpPostRequest("private/exchanges/" + "BINA" + "/markets/" + "BTC" + "/" + "LTC" + "/ticker", "", null, "GET");
string body = null;
call.xcoinApiCall("private/exchanges/" + "BINA" + "/markets/" + "BTC" + "/" + "LTC" + "/ticker", "", ref body);
try
{
JObject j = JObject.Parse(data);
TickerDataCoinigy tt = new TickerDataCoinigy();
tt.volume = (string)j["volume"];
tt.last = (string)j["last"];
tt.high = (string)j["high"];
tt.low = (string)j["low"];
tt.ask = (string)j["ask"];
tt.bid = (string)j["bid"];
Console.WriteLine("Binance, BTC-LTC ticker data:");
Console.WriteLine("Volume: " + tt.volume);
Console.WriteLine("Last: " + tt.last);
Console.WriteLine("High: " + tt.high);
Console.WriteLine("Low: " + tt.low);
Console.WriteLine("ask: " + tt.ask);
Console.WriteLine("bid: " + tt.bid);
}
catch(Exception i)
{
Console.WriteLine("");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(data);
Console.ForegroundColor = ConsoleColor.White;
}
Console.ReadLine();
}
}
public class TickerDataCoinigy
{
public string volume;
public string last;
public string high;
public string low;
public string ask;
public string bid;
}
}
and also several versions (to see if a different way of calling the api would work) of a private call.
CoinigyApiPrivateCall.cs
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace coinigy_example
{
public class CoinigyApiPrivateCall
{
private string api_key;
private string api_secret;
public CoinigyApiPrivateCall(string api_key, string api_secret)
{
this.api_key = api_key;
this.api_secret = api_secret;
}
public static double ConvertToUnixTimestamp(DateTime date)
{
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
TimeSpan diff = date.ToUniversalTime() - origin;
return Math.Floor(diff.TotalMilliseconds);
}
private string Hash_HMAC(string sKey, string sData)
{
byte[] rgbyKey = Encoding.ASCII.GetBytes(sKey);
using (var hmacsha256 = new HMACSHA256(rgbyKey))
{
byte[] inf = hmacsha256.ComputeHash(Encoding.ASCII.GetBytes(sData));
return (ByteToString(inf));
}
}
private string ByteToString(byte[] rgbyBuff)
{
string sHexStr = "";
for (int nCnt = 0; nCnt < rgbyBuff.Length; nCnt++)
{
sHexStr += rgbyBuff[nCnt].ToString("x2"); // Hex format
}
return (sHexStr);
}
private byte[] StringToByte(string sStr)
{
byte[] rgbyBuff = Encoding.ASCII.GetBytes(sStr);
return (rgbyBuff);
}
public string HttpPostRequest(string url, string au, List<KeyValuePair<string, string>> postdata, string httpType)
{
var client = new HttpClient();
//client.DefaultRequestHeaders.Add("User-Agent", ua);
//client.DefaultRequestHeaders.Add("X-API-KEY", api_key);
//client.DefaultRequestHeaders.Add("X-API-SECRET", api_secret);
string time = Convert.ToString(ConvertToUnixTimestamp(DateTime.Now.ToUniversalTime()));
FormUrlEncodedContent content = null;
string data = null;
if (postdata != null)
{
content = new FormUrlEncodedContent(postdata);
var output = Newtonsoft.Json.JsonConvert.SerializeObject(postdata);
data = api_key + time + httpType.ToUpper() + "/api/v2/" + url + output;
}
else
{
data = api_key + time + httpType.ToUpper() + "/api/v2/" + url;
}
string sign = Hash_HMAC(api_secret, data);
client.DefaultRequestHeaders.Add("X-API-SIGN", sign);
client.DefaultRequestHeaders.Add("X-API-TIMESTAMP", time);
HttpResponseMessage response = null;
if (httpType.ToUpper() == "POST")
{
response = client.PostAsync("https://api.coinigy.com/api/v2/" + url, content).Result;
}
if (httpType.ToUpper() == "GET")
{
response = client.GetAsync("https://api.coinigy.com/api/v2/" + url).Result;
}
if (httpType.ToUpper() == "PUT")
{
response = client.PutAsync("https://api.coinigy.com/api/v2/" + url, content).Result;
}
if (httpType.ToUpper() == "DELETE")
{
response = client.DeleteAsync("https://api.coinigy.com/api/v2/" + url).Result;
}
return response.IsSuccessStatusCode
? response.Content.ReadAsStringAsync().Result
: "ERROR:" + response.StatusCode + " " + response.ReasonPhrase + " | " + response.RequestMessage;
}
public JObject xcoinApiCall(string sEndPoint, string sParams, ref string sRespBodyData)
{
string sAPI_Sign = "";
string sPostData = sParams;
string sHMAC_Key = "";
string sHMAC_Data = "";
string sResult = "";
double nNonce = 0;
HttpStatusCode nCode = 0;
sPostData += "&endpoint=" + Uri.EscapeDataString(sEndPoint);
try
{
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://api.coinigy.com/api/v2/" + sEndPoint);
byte[] rgbyData = Encoding.ASCII.GetBytes(sPostData);
nNonce = ConvertToUnixTimestamp(DateTime.Now.ToUniversalTime());
sHMAC_Key = this.api_secret;
sHMAC_Data = api_key + nNonce.ToString() + "GET" + "/api/v2/" + sEndPoint;
//sHMAC_Data = sEndPoint + (char)0 + sPostData + (char)0 + nNonce.ToString();
sResult = Hash_HMAC(sHMAC_Key, sHMAC_Data);
//sAPI_Sign = Convert.ToBase64String(StringToByte(sResult));
sAPI_Sign = sResult;
Request.Headers.Add("X-API-SIGN", sAPI_Sign);
Request.Headers.Add("X-API-TIMESTAMP", nNonce.ToString());
Request.Method = "GET";
Request.ContentType = "application/x-www-form-urlencoded";
Request.UserAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";
/*Request.ContentLength = rgbyData.Length;
using (var stream = Request.GetRequestStream())
{
stream.Write(rgbyData, 0, rgbyData.Length);
} */
var Response = (HttpWebResponse)Request.GetResponse();
sRespBodyData = new StreamReader(Response.GetResponseStream()).ReadToEnd();
return (JObject.Parse(sRespBodyData));
}
catch (WebException webEx)
{
using (HttpWebResponse Response = (HttpWebResponse)webEx.Response)
{
nCode = Response.StatusCode;
using (StreamReader reader = new StreamReader(Response.GetResponseStream()))
{
sRespBodyData = reader.ReadToEnd();
try
{
return (JObject.Parse(sRespBodyData));
}
catch(Exception i)
{
}
}
}
}
return (null);
}
}
}
If anyone that has any knowledge on this that could help, I would be very grateful.
Edit: I used the code from Joe to see if the unauthorized issue could be fixed and I got this result:
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content:
System.Net.Http.StreamContent, Headers:
{
Date: Sun, 29 Jul 2018 19:19:36 GMT
Content-Length: 0
}}
I am using this code to perform the http call:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Coinigy_v2_api_2018.Functions;
using System.Net.Http;
namespace Coinigy_v2_api_test
{
class Program
{
static void Main(string[] args)
{
ApiRequest req = new ApiRequest();
req.BaseUrl = "https://api.coinigy.com";
req.Body = "";
req.Method = "GET";
req.Secret = "secret";
req.Key = "key";
req.Endpoint = "/api/v2/private/exchanges";
string signature = req.Signature;
HttpResponseMessage response = null;
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("X-API-SIGN", signature);
client.DefaultRequestHeaders.Add("X-API-TIMESTAMP", req.Timestamp);
response = client.GetAsync(req.BaseUrl + req.Endpoint).Result;
}
string r = null;
if (response.IsSuccessStatusCode)
{
r = response.Content.ReadAsStringAsync().Result;
}
Console.WriteLine(r);
Console.ReadLine();
}
}
}
again thank you in advance!
API documentation can be found here: https://api.coinigy.com/api/v2/docs/#/
2019-08-28: Latest update is that the endpoint must require the query parameter unencoded when generating the signature.
The very first thing to check is to make sure you are using a V2 api key/secret. The old V1 keys will not work with the V2 api. If you upgrade your subscription, you will likely need to generate a new key for it to take effect.
Here is a class that should help out if that doesn't solve the problem:
public class ApiRequest
{
public string BaseUrl { get; set; }
public string Endpoint { get; set; }
public string Key { get; set; }
public string Secret { get; set; }
public string Method { get; set; }
public string Body { get; set; }
public string Timestamp { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
/// <summary>
/// Each API request needs to be signed so the sender can be identified.
/// This property generates the signature based on the current request.
/// </summary>
public string Signature
{
get
{
var asciiEncoding = new ASCIIEncoding();
var hmac = new HMACSHA256(asciiEncoding.GetBytes(Secret));
string signature = Key + Timestamp + Method.ToUpper() + Endpoint + (Body ?? string.Empty);
byte[] signatureBytes = asciiEncoding.GetBytes(signature);
byte[] hashedSignatureBytes = hmac.ComputeHash(signatureBytes);
string hexSignature = string.Join(string.Empty, Array.ConvertAll(hashedSignatureBytes, hb => hb.ToString("X2")));
return hexSignature;
}
}
}
If you set those values properly, you should be able to generate the proper signature.
For the parameter values, start with something like this and compare to the signature I've produced to make sure your signature function is working properly:
BaseUrl : https://api.coinigy.com
Endpoint : /api/v2/private/exchanges?pythagoreanTheorem=a%5E2%2Bb%5E2%3Dc%5E2
Key : keykeykeykeykeykeykeykeykeykeyke
Secret : secretsecretsecretsecretsecretse
Method : GET
Timestamp : 1532718830 (which is 2018-07-27T19:13:50.6694555Z)
Body : (empty string)
Signature : B618C0B3C92632C701D7CEFC00AC9C8A0771989B21E00D61D4945F79239D2F87
Here is a bonus python3 version that will perform the entire process: https://gist.github.com/phillijw/1f78c8bafdce3a71a0b2ef9d4f5942a1
Related
I am at a total loss here. Im currently having success doing a GET request against a web service that implements DIGEST MD5-sess authentication. This works fine. I get the expected result so I figure my 'BUILD DIGEST AUTH HEADER' method works as intended.
I then get the requirement to also support POST, PUT and PATCH but now I run into a whole heap of problems. First request obviously returns a 401 and I read the info in the WWW-Authenticate header and make a MD5-sess auth header taking into account that this is now a POST, PUT or PATCH request.
var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));
I keep everything the same but do also obviously add the content to the request on POST, PUT and PATCH. But for some reason I cannot figure out Im locked out of the service. Second request return another 401 even.
Is there something special that needs to be done for DIGEST Auth when method is POST, PUT and PATCH that Im missing?
I have all my requests setup in Postman as well put Postman does not have the same problem. Second call in Postman gets the expected results. A 200 and the expected data on POST, PUT and PATCH.
Im using a slightly modified version DigestAuthFixer.cs that's also available on some other posts here in stackoverflow.
Code below is currently hardcoded to use the MD5-sess method.
public class DigestAuthFixer
{
private static readonly Random random = new Random(DateTime.Now.Millisecond);
private readonly AuthData authData;
readonly UrlResolverStrategyBase urlResolverStrategy;
public HttpStatusCode StatusCode { get; private set; }
public DigestAuthFixer(BasicAuth basicAuth, UrlResolverStrategyBase urlResolverStrategy)
{
// TODO: Complete member initialization
authData = new AuthData
{
Host = urlResolverStrategy.GetHostName(),
User = basicAuth.GetUsername(),
Password = basicAuth.GetPassword(),
};
this.urlResolverStrategy = urlResolverStrategy;
}
private string CalculateMd5Hash(string input)
{
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = MD5.Create().ComputeHash(inputBytes);
var sb = new StringBuilder();
foreach (var b in hash)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
private string GrabHeaderVar(string varName, string header)
{
var regHeader = new Regex(string.Format(#"{0}=""([^""]*)""", varName));
var matchHeader = regHeader.Match(header);
if (matchHeader.Success)
{
return matchHeader.Groups[1].Value;
}
throw new ApplicationException(string.Format("Header {0} not found", varName));
}
private string GetDigestHeader(string dir)
{
authData.NC++;
string ha1;
if (authData.Algorithm == "MD5-sess")
{
var ha0 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", ha0, authData.Nonce, authData.Cnonce));
}
else
{
ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
}
var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));
var digestResponse = CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, authData.Nonce, authData.NC, authData.Cnonce, authData.Qop, ha2));
var authString = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", algorithm=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\", response=\"{8}\"", authData.User, authData.Realm, authData.Nonce, dir, authData.Algorithm, authData.Qop, authData.NC, authData.Cnonce, digestResponse);
return authString;
}
public string GrabResponse(string nUrl, string content)
{
var uri = new Uri(authData.Host + nUrl);
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
// If we've got a recent Auth header, re-use it!
if (!string.IsNullOrEmpty(authData.Cnonce) && DateTime.Now.Subtract(authData.CnonceDate).TotalHours < 1.0)
{
request.Headers.Add("Authorization", GetDigestHeader(nUrl));
}
if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
{
request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
}
AddContentToBody(request, content);
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
StatusCode = response.StatusCode;
}
catch (WebException ex)
{
// Try to fix a 401 exception by adding a Authorization header
if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
throw;
var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"];
authData.Realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
authData.Nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
authData.Qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
authData.Algorithm = "MD5-sess"; // GrabHeaderVar("algorithm", wwwAuthenticateHeader);
authData.NC = 0;
authData.Cnonce = RandomString(8);
authData.CnonceDate = DateTime.Now;
string debug = wwwAuthenticateHeader + Environment.NewLine + nUrl + Environment.NewLine + uri.ToString() + Environment.NewLine + authData.ToString();
var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
AddContentToBody(digestRequest, content);
var authHeader = GetDigestHeader(nUrl);
debug += uri.ToString() + Environment.NewLine;
debug += nUrl + Environment.NewLine;
debug += authHeader + Environment.NewLine;
digestRequest.Headers.Add("Authorization", authHeader);
if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
{
request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
}
HttpWebResponse digestResponse = null;
try
{
//return authHeader;
digestResponse = (HttpWebResponse)digestRequest.GetResponse();
StatusCode = digestResponse.StatusCode;
response = digestResponse;
}
catch (Exception digestRequestEx)
{
if (digestResponse != null)
{
StatusCode = response.StatusCode;
}
else
{
StatusCode = HttpStatusCode.InternalServerError;
}
//return "It broke" + Environment.NewLine + debug;
return "There was a problem with url, username or password (" + digestRequestEx.Message + ")";
}
}
var reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
public string RandomString(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
return new string(stringChars);
}
private void AddContentToBody(HttpWebRequest request, string content)
{
if (string.IsNullOrEmpty(content))
return;
var data = Encoding.Default.GetBytes(content); // note: choose appropriate encoding
request.ContentLength = data.Length;
request.ContentType = HttpHelper.MediaTypes.Json;
request.Accept = "*/*";
request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(content);
}
}
}
internal class AuthData
{
public string Host;
public string User;
public string Password;
public string Realm;
public string Nonce;
public string Qop;
public string Cnonce;
public DateTime CnonceDate;
public int NC;
public string Algorithm;
public override string ToString()
{
string newLine = Environment.NewLine;
string result = Host + newLine;
result += User + newLine;
result += Realm + newLine;
result += Nonce + newLine;
result += Qop + newLine;
result += Cnonce + newLine;
result += CnonceDate + newLine;
result += NC + newLine;
result += Algorithm + newLine;
return result;
}
}
So apparently it matters in which order you add all the headers to the request. With hookbin I was able to detect that even though I have put an Authorization header on my digestRequest object it did not follow through to hookbin.
My placing the Authorization header addition to just below the setting the method line it all works...
I had no idea that that could pose a problem. The reason my GET method work is because is because 'content' is empty so no headers are added.
var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
digestRequest.Headers.Add("Authorization", GetDigestHeader(nUrl));
Actually this is not a question, but an answer for those that are trying to use AWS TranslateText without SDK. I have had many problems until I got a running version.
In my opinion, the AWS documentation for this service is not complete and there are not many examples to check (at least for .Net).
At first, I thought I was not generating the correct V4 signature, but after checking the steps and values once again, I decided to use Postman to call the service. It was very helpful.
Postman can generate the AWS signature! (yeah, I did not know) so finally I noticed the signature value was not the problem.
Checking the request I could see the headers needed and some of its values.
The problem in my case was that I was not sending the "x-amz-target" header.
And by chance I found that the value for this header must be "AWSShineFrontendService_20170701.TranslateText"
(I saw a similar value here https://rdrr.io/cran/aws.translate/src/R/translateHTTP.R)
This other link was also helpful
Ivona Request Signing Issue - signature does not match (AWS Signature Version 4)
So now I have a running version, I want to share my .Net code. I hope it helps :) !!
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;
namespace AWS_TranslateTextTest
{
class AWS_TranslateText
{
// Replace this with your own values
const string AccessKey = "AKI_ADD_YOUR_ACCESSKEY";
const string SecretKey = "ADD_YOUR_SECRETKEY";
static void Main()
{
try
{
string text = "Translate this text from English to German.";
string sourceLang = "en";
string targetLang = "de";
string responseText = TranslateText(text, sourceLang, targetLang);
JObject json = JObject.Parse(responseText);
string translatedText = ""; // to do read response from json or responseText
if (json.ToString().Contains("TranslatedText")){
//To access to the properties in "dot" notation use a dynamic object
dynamic obj = json;
translatedText = obj.TranslatedText.Value;
Console.WriteLine("TranslatedText is: {0}", translatedText);
}
else{
Console.WriteLine("TranslatedText not found in response.");
throw new Exception(json.ToString());
}
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
if (ex.Response != null){
foreach (string header in ex.Response.Headers)
{
Console.WriteLine("{0}: {1}", header, ex.Response.Headers[header]);
}
using (var responseStream = ex.Response.GetResponseStream())
{
if (responseStream != null)
{
using (var streamReader = new StreamReader(responseStream))
{
Console.WriteLine(streamReader.ReadToEnd());
}
}
} }
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static string TranslateText(string text, string sourceLang, string targetLang)
{
var date = DateTime.UtcNow;
const string algorithm = "AWS4-HMAC-SHA256";
const string regionName = "eu-west-1";
const string serviceName = "translate";
const string method = "POST";
const string canonicalUri = "/";
const string canonicalQueryString = "";
const string x_amz_target_header = "AWSShineFrontendService_20170701.TranslateText";
const string contentType = "application/x-amz-json-1.1";
const string host = serviceName + "." + regionName + ".amazonaws.com";
var obj = new
{
SourceLanguageCode = sourceLang,
TargetLanguageCode = targetLang,
Text = text
};
var requestPayload = new JavaScriptSerializer().Serialize(obj);
var hashedRequestPayload = HexEncode(Hash(ToBytes(requestPayload)));
var dateStamp = date.ToString("yyyyMMdd");
var requestDate = date.ToString("yyyyMMddTHHmmss") + "Z";
var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, regionName, serviceName);
var bytes = ToBytes(requestPayload);
var headers = new SortedDictionary<string, string>
{
{"content-length", bytes.Length.ToString()},
{"content-type", contentType},
{"host", host},
{"x-amz-date", requestDate},
{"x-amz-target", x_amz_target_header}
};
string canonicalHeaders =
string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
string signedHeaders =
string.Join(";", headers.Select(x => x.Key.ToLowerInvariant() ));
// 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)));
// 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);
// 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));
// 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
string endpoint = "https://" + host; // + canonicalUri ;
var webRequest = WebRequest.Create(endpoint);
webRequest.Method = method;
webRequest.Timeout = 20000;
webRequest.ContentType = contentType;
webRequest.Headers.Add("X-Amz-Date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("X-Amz-Target", x_amz_target_header);
webRequest.ContentLength = bytes.Length;
using (Stream newStream = webRequest.GetRequestStream())
{
newStream.Write(bytes, 0, bytes.Length);
newStream.Flush();
}
var response = (HttpWebResponse)webRequest.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (var streamReader = new StreamReader(responseStream))
{
string res = streamReader.ReadToEnd();
return res;
}
}
}
return null;
}
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 am completely new to this kind of programming so I don't really know if there is an answer to this already, but I weren't able to find it. So I am testing to see if I can get a dry-run gcm message to work without errors.
The error I get is the error 400 Invalid Request, and it's saying something about the json being invalid, so I have assumed the problem has to do with string manipulation or the definition of postdata, but I can't figure it out. Most of the code is just copy pasted anyway so one could believe that others in a similar situation will get the same error, if they copy from the same source.
And also I have put in actual values for the "lorem"s.
This is the only code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Web.Script.Serialization;
namespace ServerGMC
{
public class ServerGMC
{
static void Main ()
{
// Prepares and calls the function to send message
List<string> RedIdList = new List<string>(1) { "aaaaaaaaaaaaaaaaaaaaaaaa" };
RedIdList.TrimExcess();
Console.WriteLine(SendNotification(RedIdList, "HelloWorld", "test", 220299));
Console.Read();
}
static public string SendNotification(List<string> deviceRegIds, string message, string title, long id)
{
try
{
string regIds = string.Join("\",\"", deviceRegIds);
string AppId = "lorem";
var SenderId = "lorem";
NotificationMessage nm = new NotificationMessage();
nm.Title = title;
nm.Message = message;
nm.ItemId = id;
var value = new JavaScriptSerializer().Serialize(nm);
WebRequest wRequest;
wRequest = WebRequest.Create("https://android.googleapis.com/gcm/send");
wRequest.Method = "post";
wRequest.ContentType = " application/json;charset=UTF-8";
wRequest.Headers.Add(string.Format("Authorization: key={0}", AppId));
wRequest.Headers.Add(string.Format("Sender: id={0}", SenderId));
string postData = "{\"collapse_key\":\"standard\",\"time_to_live\":108,\"delay_while_idle\":true,\"dry_run\":true,\"data\": { \"message\" : " + "\"" + value + "\",\"time\": " + "\"" + System.DateTime.Now.ToString() + "\"},\"registration_ids\":[\"" + regIds + "\"]}";
//string postData = "collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.message=" + value + "&date.time=" + System.DateTime.Now.ToString() + "®istration_ids=" + regIds + "";
Console.WriteLine(postData);
Byte[] bytes = Encoding.UTF8.GetBytes(postData);
wRequest.ContentLength = bytes.Length;
Stream stream = wRequest.GetRequestStream();
stream.Write(bytes, 0, bytes.Length);
stream.Close();
WebResponse wResponse = wRequest.GetResponse();
stream = wResponse.GetResponseStream();
StreamReader reader = new StreamReader(stream);
String response = reader.ReadToEnd();
HttpWebResponse httpResponse = (HttpWebResponse)wResponse;
string status = httpResponse.StatusCode.ToString();
reader.Close();
stream.Close();
wResponse.Close();
if (status == "")
{
return response;
}
else
{
return "";
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine();
return "";
}
}
private class NotificationMessage
{
public string Title;
public string Message;
public long ItemId;
}
}
}
The postData isn't properly formatted in JSON. If you check it out using an online formatting tool, it looks like this
{
"collapse_key":"standard",
"time_to_live":108,
"delay_while_idle":true,
"dry_run":true,
"data":{
"message":"{"Title":"test",
"Message":"HelloWorld",
"ItemId":220299}",
"time":"22/04/2016 13:04:38"
},
"registration_ids":["aaaaaaaaaaaaaaaaaaaaaaaa"]
}
You can either remove the data.message node and place its properties in data, or use a 3rd-party JSON parser or System.Web.Helpers.Json.Decode (which were suggested in this issue)
Hopefully this helps with the issue.
Happy coding!
I'm trying to POST data in JSON format but the server is rejecting it. It works if I POST in XML and it will reply in either XML or JSON.
The contract is
[ServiceContract]
public interface IService
{
// One string data
[OperationContract]
[WebInvoke(
BodyStyle = WebMessageBodyStyle.Bare,
Method = "POST",
ResponseFormat = WebMessageFormat.Xml,
UriTemplate = "data")]
string DataPost(Hidden param);
}
// If you get a compilation error, add
// System.Runtime.Serialization to the references
[DataContract(Name = "Hidden", Namespace = "")]
public class Hidden
{
[DataMember]
public string id { get; set; }
}
The implementation is
public class Service : IService
{
public string DataPost(Hidden param)
{
Console.WriteLine("DataPost " + param.id);
return "DataPost said " + param.id;
}
}
On the client side, it is the run of the mill standard stuff.
namespace client
{
enum HttpVerb
{
GET,
POST,
PUT,
DELETE
};
class RestClient
{
// Properties
public string EndPoint { get; set; }
public HttpVerb Method { get; set; }
public string ContentType { get; set; }
public string ParamData { get; set; }
public string PostData { get; set; }
// Methods
public string MakeRequest()
{
var responseValue = string.Empty;
string ep = EndPoint;
if (!string.IsNullOrEmpty(ParamData))
ep += "/" + ParamData;
var request = (HttpWebRequest)WebRequest.Create(ep);
request.Method = Method.ToString();
request.ContentLength = 0;
request.ContentType = ContentType;
// Postdata parameters
if (!string.IsNullOrEmpty(PostData) && Method == HttpVerb.POST)
{
var encoding = new UTF8Encoding();
var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(PostData);
request.ContentLength = bytes.Length;
using (var postStream = request.GetRequestStream())
{
postStream.Write(bytes, 0, bytes.Length);
}
if (PostData.Substring(0, 1) != "<")
request.ContentType = "application/json";
Console.WriteLine("Content type is " + request.ContentType.ToString());
}
// Send request and get response
using (var response = (HttpWebResponse)request.GetResponse())
{
// Did it work?
if (response.StatusCode != HttpStatusCode.OK)
{
var message = String.Format("Request failed. Received HTTP {0}", response.StatusCode);
throw new ApplicationException(message);
}
// get response
using (var responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (var reader = new StreamReader(responseStream))
{
responseValue = reader.ReadToEnd();
}
}
}
}
return responseValue;
}
void TalkTo(
HttpVerb in_method,
string in_endPoint,
string in_paramdata,
string in_postdata)
{
Method = in_method;
EndPoint = in_endPoint;
ContentType = "text/xml";
ParamData = in_paramdata;
PostData = in_postdata;
try
{
string response = MakeRequest();
Console.WriteLine("Endpoint: " + EndPoint);
Console.WriteLine("Resp : " + response);
Console.WriteLine();
}
catch (System.Net.WebException e)
{
Console.WriteLine("Endpoint: " + EndPoint);
Console.WriteLine("Failed : " + e.Message);
}
}
static void Main(string[] args)
{
RestClient me = new RestClient();
string endPointPrefix = #"http://localhost:8000/";
me.TalkTo(
HttpVerb.POST,
endPointPrefix + "data",
"",
"<Hidden><id>xml works</id></Hidden>");
string post = "{\"id\":\"json works\"}";
Console.WriteLine("Json string is [" + post + "]");
me.TalkTo(
HttpVerb.POST,
endPointPrefix + "data",
"",
post);
Console.WriteLine("Press <Enter> to terminate");
Console.ReadLine();
}
}
}
On the client side, I'm getting
Content type is text/xml
Endpoint: http://localhost:8000/data
Resp : <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">DataPost said xml works</string>
Json string is [{"id":"json works"}]
Content type is application/json
Endpoint: http://localhost:8000/data
Failed : The remote server returned an error: (400) Bad Request.
Press <Enter> to terminate
When I look at the help, it says that JSON format is allowable but when I try it, it falls over. The trace from the sever debug seems to imply that it is ignoring the application/json and just assuming that everything is xml.
Exception: System.Runtime.Serialization.SerializationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message: There was an error checking start element of object of type System.String. The data at the root level is invalid. Line 1, position 1.
Last few lines of stack trace
System.Runtime.Serialization.XmlObjectSerializer.IsStartObjectHandleExceptions(XmlReaderDelegator reader)
System.Runtime.Serialization.DataContractSerializer.IsStartObject(XmlDictionaryReader reader)
System.ServiceModel.Dispatcher.SingleBodyParameterMessageFormatter.ReadObject(Message message)
All the examples I've looked at on SO and other sites don't add anything special so is it not working because I'm using the VS2013 express?
EDIT It also happens on VS2013 Web Express
EDIT2 Doesn't look like it is isolated to express versions - I've just tried it on VS2010 Professional. It is still using the XMLObjectSerializer to parse JSON and failing miserably.
The fix is to no convert the PostData to bytes
if (!string.IsNullOrEmpty(PostData) && Method == HttpVerb.POST)
{
if (PostData.Substring(0, 1) != "<")
request.ContentType = "application/json";
Console.WriteLine("Content type is " + request.ContentType.ToString());
request.ContentLength = PostData.Length;
using (var postStream = new StreamWriter(request.GetRequestStream()))
{
postStream.Write(PostData);
}
}
I have this generic handler in C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace RequestResponse
{
public class Handler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string function = context.Request.QueryString["function"];
Methods m = new Methods();
if(function.Equals("addition"))
{
int num1 = Convert.ToInt32(context.Request.QueryString["num1"]);
int num2 = Convert.ToInt32(context.Request.QueryString["num2"]);
int answer = m.addition(num1, num2);
context.Response.Write(answer);
}
if (function.Equals("subtraction"))
{
int num1 = Convert.ToInt32(context.Request.QueryString["num1"]);
int num2 = Convert.ToInt32(context.Request.QueryString["num2"]);
int answer = m.subtraction(num1, num2);
context.Response.Write(answer);
}
if(function.Equals("reverseString"))
{
string text = context.Request.QueryString["text"];
text = m.reverseString(text);
context.Response.Write(text);
}
if (function.Equals("CalculateMD5Hash"))
{
string text = context.Request.QueryString["text"];
text = m.CalculateMD5Hash(text);
context.Response.Write(text);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
I also have this client application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
namespace Client
{
public partial class HomePage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (TextBox_Function.Text.Equals("addition"))
{
int num1 = Convert.ToInt32(TextBox_Num1.Text);
int num2 = Convert.ToInt32(TextBox_Num2.Text);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:4000/Handler.ashx?function=" + TextBox_Function.Text + "&num1=" + num1 + "&num2=" + num2);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
String answer = response.ToString();
Session["answer"] = answer;
Response.Redirect("Results.aspx");
}
if (TextBox_Function.Text.Equals("subtraction"))
{
int num1 = Convert.ToInt32(TextBox_Num1.Text);
int num2 = Convert.ToInt32(TextBox_Num2.Text);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:4000/Handler.ashx?function=" + TextBox_Function.Text + "&num1=" + num1 + "&num2=" + num2);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
String answer = response.ToString();
Session["answer"] = answer;
Response.Redirect("Results.aspx");
}
if (TextBox_Function.Text.Equals("reverseString"))
{
String text = TextBox_Text.Text;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:4000/Handler.ashx?function=" + TextBox_Function.Text + "&text=" + text);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
String answer = response.ToString();
Session["answer"] = answer;
Response.Redirect("Results.aspx");
}
if (TextBox_Function.Text.Equals("CalculateMD5Hash"))
{
String text = TextBox_Text.Text;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:4000/Handler.ashx?function=" + TextBox_Function.Text + "&text=" + text);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
String answer = response.ToString();
Session["answer"] = answer;
Response.Redirect("Results.aspx");
}
}
}
}
Now, let us assume that in my client application, I provide "addition" as the function, "3" as num1 and "5" as num2.
Why does the answer generated (stored in variable answer) is:
System.Net.HttpWebResponse
What am I doing wrong? Please bear with me as I have never communicated between two applications in this way.
The problem is here:
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
String answer = response.ToString();
You're just calling ToString() on the HttpWebResponse, whereas presumably what you really want to do is get the contents of the body of the response - which you'd do with something like this:
// It's important to dispose of responses...
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
string answer = reader.ReadToEnd();
}
}