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));
}
}
}
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));
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
I have my two functions, the access attempt and the HMAC signing. It runs and returns an error(401) unauthorized, however in addition I feel my code is longer than it needs to be or redundant somehow, pointing that out would be very helpful to me, thanks in advance!
void AccessAttempt(){
var message = Epoch.ToString () + "GET" + "/v2/payment-methods";
const string WEBSERVICE_URL = "https://api.coinbase.com/v2/payment-methods";
try
{
var webRequest = System.Net.WebRequest.Create(WEBSERVICE_URL);
if (webRequest != null)
{
webRequest.Method = "POST";
webRequest.ContentType = "application/json";
webRequest.Headers.Add("CB-ACCESS-SIGN", genHMAC(message));
webRequest.Headers.Add("CB-ACCESS-TIMESTAMP", Epoch.ToString());
webRequest.Headers.Add("CB-ACCESS-KEY", _apiKey);
webRequest.Headers.Add("CB-VERSION",_apiVersion);
using (System.IO.Stream s = webRequest.GetResponse().GetResponseStream())
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(s))
{
var jsonResponse = sr.ReadToEnd();
OutputText.text = jsonResponse.ToString();
}
}
}
}
catch (Exception ex)
{
OutputText.text = ex.ToString();
}
}
Below is the HMAC signing function called within main function above:
private string genHMAC(string message)
{
byte [] APISecret_Bytes = System.Text.Encoding.UTF8.GetBytes(_apiSecret);
HMACSHA256 hmac = new HMACSHA256(APISecret_Bytes);
hmac.Initialize ();
byte [] MESSAGE_Bytes = System.Text.Encoding.UTF8.GetBytes(message);
var rawHmac = hmac.ComputeHash(MESSAGE_Bytes);
string rawHmacString = string.Empty;
for (int i=0; i<rawHmac.Length; i++)
{
rawHmacString += rawHmac[i];
}
string hexString = string.Empty;
for (int i=0; i<rawHmac.Length; i++)
{
hexString += rawHmac[i].ToString("X2");
}
return hexString;
}
This is a pretty old question, but in case you don't have an answer yet, it looks like there are a few things wrong with your request - here is some code that works for me
public class CoinbaseV2
{
private string APIKey;
private string Secret;
private const string URL_BASE = "https://api.coinbase.com";
private const string URL_BASE_VERSION = URL_BASE + "/v2/";
private const String GET = "GET";
private const String POST = "POST";
private const String PUT = "PUT";
private const String DELETE = "DELETE";
public CoinbaseV2(string inAPIKey, string inSecret)
{
APIKey = inAPIKey;
Secret = inSecret;
}
public string GetUser()
{
return JsonRequest(URL_BASE_VERSION + "user", GET);
}
public string GetUserAccounts()
{
return JsonRequest(URL_BASE_VERSION + "accounts", GET);
}
private string JsonRequest(string url, string method)
{
// take care of any spaces in params
url = Uri.EscapeUriString(url);
string returnData = String.Empty;
var webRequest = HttpWebRequest.Create(url) as HttpWebRequest;
if (webRequest != null)
{
webRequest.Method = method;
webRequest.ContentType = "application/json";
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(CultureInfo.CurrentCulture);
string body = "";
string sigurl = url.Replace(URL_BASE,"");
string signature = GenerateSignature(timestamp,method,sigurl,body,Secret);
var whc = new WebHeaderCollection();
whc.Add("CB-ACCESS-SIGN", signature);
whc.Add("CB-ACCESS-TIMESTAMP", timestamp);
whc.Add("CB-ACCESS-KEY", APIKey);
whc.Add("CB-VERSION", "2017-08-07");
webRequest.Headers = whc;
using (WebResponse response = webRequest.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(stream);
returnData = reader.ReadToEnd();
}
}
}
return returnData;
}
//https://github.com/bchavez/Coinbase
public static string GenerateSignature(string timestamp, string method, string url, string body, string appSecret)
{
return GetHMACInHex(appSecret, timestamp + method + url + body).ToLower();
}
internal static string GetHMACInHex(string key, string data)
{
var hmacKey = Encoding.UTF8.GetBytes(key);
var dataBytes = Encoding.UTF8.GetBytes(data);
using (var hmac = new HMACSHA256(hmacKey))
{
var sig = hmac.ComputeHash(dataBytes);
return ByteToHexString(sig);
}
}
//https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/14333437#14333437
static string ByteToHexString(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(87 + b + (((b - 10) >> 31) & -39));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(87 + b + (((b - 10) >> 31) & -39));
}
return new string(c);
}
}
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 am using Omniture api to download a report. The report is completed when I checked the status with DataWarehouseCheckRequest method. Now when I try to fetch the report using DataWarehouseGetReportData method, I get
CommunicationException Error in deserializing body of reply message for operation 'DataWarehouseGetReportData
Inner exception says
The specified type was not recognized: name='data_warehouse_report_row', namespace='http://www.omniture.com/', at <rows xmlns=''>
I am new with C# and the API both. Got no idea how to resolve this. Please help.
Thanks
When you want to download a DW report the best option is to do it over http. This the standard way and is much more efficient.
The response to CheckRequest contains a DataURL. Use that to download the data.
Here is some c# sample code I am using for an almost identical API (Partner vs you Enterprise API) (note I'm no c# expert either, so you will need to do a code review on this).
HttpWebResponse statusResponse = null;
string response = "";
StringBuilder sbUrl = new StringBuilder(dwrq.data_url); // hardcode to variable "rest_url" for testing.
HttpWebRequest omniRequest = (HttpWebRequest)WebRequest.Create(sbUrl.ToString());
string timecreated = generateTimestamp();
string nonce = generateNonce();
string digest = getBase64Digest(nonce + timecreated + secret);
nonce = base64Encode(nonce);
omniRequest.Headers.Add("X-WSSE: UsernameToken Username=\"" + username + "\", PasswordDigest=\"" + digest + "\", Nonce=\"" + nonce + "\", Created=\"" + timecreated + "\"");
omniRequest.Method = "GET"; // Switched from POST as GET is the right HTTP verb in this case
try
{
statusResponse = (HttpWebResponse)omniRequest.GetResponse();
using (Stream receiveStream = statusResponse.GetResponseStream())
{
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
response = readStream.ReadToEnd();
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
Console.WriteLine("Response is a TAB delimeted CSV structure. Printing to screen.");
Console.WriteLine(response);
Console.WriteLine("Ending REST...");
Console.WriteLine("Ending ExportRequestSegmentedData...");
and the supporting methods
/*** Here are the private functions ***/
// Encrypting passwords with SHA1 in .NET and Java
// http://authors.aspalliance.com/thycotic/articles/view.aspx?id=2
private static string getBase64Digest(string input)
{
SHA1 sha = new SHA1Managed();
ASCIIEncoding ae = new ASCIIEncoding();
byte[] data = ae.GetBytes(input);
byte[] digest = sha.ComputeHash(data);
return Convert.ToBase64String(digest);
}
// generate random nonce
private static string generateNonce()
{
Random random = new Random();
int len = 24;
string chars = "0123456789abcdef";
string nonce = "";
for (int i = 0; i < len; i++)
{
nonce += chars.Substring(Convert.ToInt32(Math.Floor(random.NextDouble() * chars.Length)), 1);
}
return nonce;
}
// Time stamp in UTC string
private static string generateTimestamp()
{
return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
}
// C#-Base64 Encoding
// http://www.vbforums.com/showthread.php?t=287324
public static string base64Encode(string data)
{
byte[] encData_byte = new byte[data.Length];
encData_byte = System.Text.Encoding.UTF8.GetBytes(data);
string encodedData = Convert.ToBase64String(encData_byte);
return encodedData;
}
Best of Luck! C.