Error: "response":"X006","description":"Invalid hash" - c#

I made a call with Restsharp with the following code in Gridview row command in ASP.net C#:
const string task = "pay";
const string command_api_token = "D3Kc4vMatqQ7pQtU39D22j35aKqy8";
const string merchant_email_on_voguepay = "abc#wyz.com";
Random rnd = new Random();
string refl = DateTime.Now + rnd.Next(1, 9999999).ToString();
byte[] hash_target = Encoding.UTF8.GetBytes(command_api_token + task + merchant_email_on_voguepay + refl);
string hash = BitConverter.ToString(new SHA512CryptoServiceProvider().ComputeHash(hash_target)).Replace("-", string.Empty).ToUpper();
// //load all fields as json and serialize
var keyValues = new Dictionary<string, string>
{
{ "task", "pay"},
{ "merchant", "1234-4567"},
{ "ref",refl},
{ "hash",hash},
{ "remarks", "secure trade"},
{ "seller", "seller#website.com"},
{ "cur", "usd"},
{ "amount", "20"}
};
//serialization using Newtonsoft JSON
string json = JsonConvert.SerializeObject(keyValues);
//url encode the json
var postString = HttpUtility.UrlEncode(json);
//calling API with Restsharp
var client = new RestClient("https://voguepay.com/api/");
var request = new RestRequest(Method.POST);
request.AddParameter("json", json);
IRestResponse response = client.Execute(request);
the call returns the following error when I use the real parameters for email_on_voguepay and command_api_token.
this is what it returns:
{"response":"X006","description":"Invalid hash","values":"","salt":"58656ba1d162a","hash":"2638fb45f266d35bc78524490a3329a3fecf7b5189c473e82e45d7d6a22f07656743e2cb9ae28dfc81e77eef49eb24d46e0fd4a71cab16847a7afd225310feb5","username":"MyName"}
Please I need help here, what did I do wrong and how do I correct that?

Seems to be your request has a invalid hash (token). I suggest you to check the string refl = DateTime.Now + rnd.Next(1, 9999999).ToString();
The time function in php (according with voguePay API, returns the unix timestamp.
So in C# please consider using DateTimeOffset.ToUnixTimeSeconds or (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds
For more info, please consider checking the generated token was following instructions:
Required Security code generated by concatenating the following and passing the result through a sha512 encryption
your command api token
task specified above
your voguepay email
ref specified above
ref Unique id for each request (Required) $field['ref'] = time().mt_rand(0,999999999);
https://voguepay.com/developers

Related

Access the Coinbase Candles url

I have successfully managed to call some of Coinbase API's end points, but struggling on calling the Candles Url:
using RestSharp;
using System.Globalization;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
var symbol = "BTC-GBP";
var _privateApiKey = "xxxx";
var _secret = "xxxxxxxxxx";
var _timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var _requestPath = "/api/v3/brokerage/products/" + symbol + "/candles";
var _method = "GET";
var client = new RestClient(#"https://api.coinbase.com" + _requestPath + "?granularity=ONE_DAY&start=" + DateTime.Now.AddMilliseconds(-3000).ToUniversalTime().Ticks + "&end=" + DateTime.Now.ToUniversalTime().Ticks + "");
var request = new RestRequest();
request.Method = Method.Get;
request.AddHeader("Content-Type", "application/json");
request.AddHeader("CB-ACCESS-KEY", _privateApiKey);
request.AddHeader("CB-ACCESS-TIMESTAMP", _timestamp);
request.AddHeader("CB-ACCESS-SIGN", GetAccessSign(_timestamp, _method, _requestPath, "", _secret));
request.AddHeader("CB-VERSION", "2023-01-18");
try
{
var response = client.Execute(request);
Console.WriteLine(response.Content);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
string GetAccessSign(string timestamp, string method, string path, string body, string apiSecret)
{
var convertedString = Convert.FromBase64String(apiSecret);
var hmaccsha = new HMACSHA256(convertedString);
string timestampStr = timestamp;
string httpMethodStr = method;
var prehash = timestampStr + httpMethodStr + path + body;
// Convert the input string and key to byte arrays
byte[] prehashBytes = Encoding.UTF8.GetBytes(prehash);
byte[] keyBytes = Encoding.UTF8.GetBytes(apiSecret);
// Compute the SHA-256 hash of the input data using the key
HMACSHA256 hmac = new HMACSHA256(keyBytes);
byte[] hash2 = hmac.ComputeHash(prehashBytes);
// Convert the hash to a hexadecimal string
string signedSignature = BitConverter.ToString(hash2).Replace("-", "").ToLower();
return signedSignature;
}
But i keep getting this error:
{"error":"INVALID_ARGUMENT","error_details":"start and end argument is invalid - number of candles requested should be less than 300 ","message":"start and end argument is invalid - number of candles requested should be less than 300 "}
You are missing the required arguments:
https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getcandles
Path Params
product_id string required
The trading pair.
Query Params
start string required
Timestamp for starting range of aggregations, in UNIX time.
end string required
Timestamp for ending range of aggregations, in UNIX time.
granularity string required
The time slice value for each candle.
The API probably just has a bad error message and treats undefined start and end range query params as out of range values.
I managed to get the results back using this in the RestClient and to Tomáš Hübelbauer link:
var client = new RestClient(string.Format(#"https://api.coinbase.com" + _requestPath + "?granularity=ONE_MINUTE&start={0}&end={1}", DateTimeOffset.Now.AddSeconds(-6000).ToUnixTimeSeconds(), DateTimeOffset.Now.ToUnixTimeSeconds()));
Which gave me about 30 to 35 elements of data back

Create hmacHash with stream result differs for requests with formatted json

I want to regenerate a token to verify the request using hmacHash
The resource documentation is in javascript and I have to implement it in C#.
here what I found in their documentation on generating the token.
routes.post("/webhook", (request) => {
const body = request.rawBody;
const signatureTime = "2022-05-17T09:47:51.935Z";
const signingKey = "localyStoredKey";
const hmac = createHmac("sha256", signingKey);
hmac.update(`${signatureTime}${body}`);
const token = hmac.digest("hex");
}
here is what I have done in C#
public async Task<string> GetWebhookAuthenticationTokenAsync(Stream requestBody) // requestBody is from HttpContext.Request.Body
{
var signingKey = "localyStoredKey"; // should be from db
var signatureTime = "2022-05-17T09:47:51.935Z"; // a timestamp
var reader = new StreamReader(requestBody, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
var byteArray1 = Encoding.UTF8.GetBytes($"{signatureTime}");
var byteArray2 = Encoding.UTF8.GetBytes(body);
var concatenatedByteArray = byteArray1.Concat(byteArray2).ToArray();
var stringInMemoryStream = new MemoryStream(concatenatedByteArray);
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingKey));
var hashBytes = hmac.ComputeHash(stringInMemoryStream);
var token = BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
return token;
}
This works fine when I send the request body(json) without newline characters(\n).
when I send the formatted json body with the request, the token is different from javascript generated token. What am I missing?
simply if json is {"pro": "duct"} the tokens are matched but when json {\n "pro": "duct"\n} its different.
PS: I found the issue is due to Carriage Return \r in json body. Any help to handle this independently from the platform?

Generate request for Nominex using HMAC384

I am trying to authenticate to the Nominex API, I cannot find any C# examples of this request to Nominex. Documentation only has node.js example. I'm trying to convert it to C# but not having much luck, not sure what I'm doing wrong. The result I get is invalid credentials
Example in JavaScript
const crypto = require('crypto')
const request = require('request')
const apiKey = '...'
const apiSecret = '...'
const apiPath = '/api/rest/v1/private/wallets'
const nonce = Date.now().toString()
const queryParams = ''
const body = undefined
let signature = `/api${apiPath}${nonce}${body ? JSON.stringify(body) : ''}`
const sig = crypto.createHmac('sha384', apiSecret).update(signature)
const shex = sig.digest('hex')
const options = {
url: `https://nominex.io/${apiPath}?${queryParams}`,
headers: {
'nominex-nonce': nonce,
'nominex-apikey': apiKey,
'nominex-signature': shex
},
body: body,
json: true
}
request.get(options, (error, response, body) => {
console.log(body);
})
My attempt in C#
private async Task<string> GetRequest()
{
HttpClient client = new HttpClient();
string secretKey = apiSecret;
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
long nonce = (long)t.TotalMilliseconds;
string body = "";
string requestPath = "/api/rest/v1/private/orders/BTC/USDT";
string prehashString = string.Join("", requestPath, nonce.ToString(), body);
string digest = "";
using (var hmacsha384 = new HMACSHA384(Encoding.Default.GetBytes(secretKey)))
{
byte[] hashmessage = hmacsha384.ComputeHash(Encoding.Default.GetBytes(prehashString));
bool upperCase = false;
StringBuilder result = new StringBuilder(hashmessage.Length * 2);
for (int i = 0; i < hashmessage.Length; i++)
result.Append(hashmessage[i].ToString(upperCase ? "X2" : "x2"));
digest = result.ToString();
}
HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}{requestPath}");
msg.Content = new StringContent(body, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("nominex-apikey", apiKey);
client.DefaultRequestHeaders.Add("nominex-nonce",nonce.ToString());
client.DefaultRequestHeaders.Add("nominex-signature", digest);
var response = await client.SendAsync(msg);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
The signature is:
let signature = `/api${apiPath}${nonce}${body ? JSON.stringify(body) : ''}`
so they add a /api before the apiPath that in the nodejs example is /api/rest/v1/private/wallets, so in that case the signature would be (with a nonce of 1609874861073):
/api/api/rest/v1/private/wallets1609874861073
so
string prehashString = string.Concat("/api", requestPath, nonce.ToString(), body);
And replace all the Encoding.Default to Encoding.UTF8. They shouldn't give you any problem, but something wrong is something wrong.
Next time, if you have a nodejs installation on your pc, you can try their code on your nodejs, adding freely some console.log, otherwise there is a site, repl.it, where you can try some node.js code (that is what I did to solve your problem).

Facebook data deletion callback gives "Unable to confirm request was received"

I just convert PHP sample code to C#. This is my endpoint:
public async Task<IHttpActionResult> Delete([FromBody] FbModel fbModel)
{
var res = fbModel.signed_request.Split(new char[] { '.' }, 2);
var secret = "ababababababababababababa";
if (res.Length > 1)
{
var sig = res[0];
var json = base64decode(res[1]);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookDeletionDto>(json);
if (string.IsNullOrEmpty(data.algorithm) || data.algorithm.ToUpper() != "HMAC-SHA256")
throw new Exception("Unknown algorithm:" + data.algorithm + ". Expected HMAC-SHA256");
var expected_sig = hmacSHA256(res[1], secret);
if (expected_sig != sig)
throw new Exception("Invalid signature:" + sig + ". Expected" + expected_sig);
var returnJson = new { Url = $"https://myperfectsite.com/fb/info/{data.user_id}", confirmation_code = $"{data.user_id}" };
return Ok(returnJson);
}
return null;
}
This code running perfectly and gives me json.
My endpoint return URL and confirmation code in JSON. But in facebook confirmation page it gives me this error :"Unable to confirm request was received
"App name" sent an invalid response to your request. Contact "App name" directly to request it delete info it has about you."
Facebook provides only the following info:
Return a JSON response that contains a URL where the user can check the status
of their deletion request and an alphanumeric confirmation code.
The JSON response has the following form:
{ url: '<url>', confirmation_code: '<code>' }
I ran into the same problem where FB would not accept our server response.
We ultimately fixed the problem by outputting the JSON response in EXACTLY the same sample format. Property names lowercase and not quoted, single quotes around the values, and a value that was alphanumeric (no symbols) and not too long (20 char length worked).
EDIT: Looking back at it, looks like we also used a dynamic type for the response.
dynamic response = new ExpandoObject();
response.url = "<url>";
response.confirmation_code = "<code>";
return response;
I notice that while in facebook documentation "expected_sig" must be equal to "sig" which must be the result of base64_url decoding of $encoded_sig in your code "expected_sig" is compared to the sign not decoded.
Hope this could be helpful.
var sig = res[0];
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)
Correct should be as follow:
var sig = base64decode(res[0]);
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)
Instead of:
Delete([FromBody] FbModel fbModel)
I use:
string signed_request = Request.Form["signed_request"];
Here is my working code:
public async Task<IActionResult> Delete()
{
string signed_request = Request.Form["signed_request"];
if (!String.IsNullOrEmpty(signed_request))
{
string[] split = signed_request.Split('.');
string signatureRaw = base64decode(split[0]);
string dataRaw = base64decode(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
var json = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes("SecretKey");
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (!expectedHash.SequenceEqual(signature))
{
throw new Exception("Invalid signature");
}
var fbUser = JsonConvert.DeserializeObject<FacebookUserDTO>(json);
return Ok(new { url = $"https://myperfectsite.com/fb/info/{fbUser.user_id}", confirmation_code = $"{fbUser.user_id}" });
}
}

Google OAuth2 Service Account Access Token Request gives 'Invalid Request' Response

I'm trying to communicate with my app's enabled BigQuery API via the server to server method.
I've ticked all the boxes on this Google guide for constructing my JWT as best I can in C#.
And I've Base64Url encoded everything that was necessary.
However, the only response I get from google is a 400 Bad Request
"error" : "invalid_request"
I've made sure of all of the following from these other SO questions:
The signature is properly encrypted using RSA and SHA256
I am using POST and using application/x-www-form-urlencoded content type
Escaped all the backslashes in the claim set
Tried various grant_type and assertion values in the POST data
I get the same result when I use Fiddler. The error message is frustratingly lacking in detail! What else can I try?! Here's my code:
class Program
{
static void Main(string[] args)
{
// certificate
var certificate = new X509Certificate2(#"<Path to my certificate>.p12", "notasecret");
// header
var header = new { typ = "JWT", alg = "RS256" };
// claimset
var times = GetExpiryAndIssueDate();
var claimset = new
{
iss = "<email address of the client id of my app>",
scope = "https://www.googleapis.com/auth/bigquery",
aud = "https://accounts.google.com/o/oauth2/token",
iat = times[0],
exp = times[1],
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = Base64UrlEncode(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = Base64UrlEncode(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = Base64UrlEncode(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
Console.WriteLine(jwt);
var client = new HttpClient();
var uri = "https://accounts.google.com/o/oauth2/token";
var post = new Dictionary<string, string>
{
{"assertion", jwt},
{"grant_type", "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"}
};
var content = new FormUrlEncodedContent(post);
var result = client.PostAsync(uri, content).Result;
Console.WriteLine(result);
Console.WriteLine(result.Content.ReadAsStringAsync().Result);
Console.ReadLine();
}
private static int[] GetExpiryAndIssueDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.Now;
var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;
return new[]{iat, exp};
}
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
}
Looks like my guess in the comment above was correct. I got your code working by changing:
"urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"
to:
"urn:ietf:params:oauth:grant-type:jwt-bearer"
Looks like you were accidentally double-encoding it.
I now get a response which looks something like:
{
"access_token" : "1/_5pUwJZs9a545HSeXXXXXuNGITp1XtHhZXXxxyyaacqkbc",
"token_type" : "Bearer",
"expires_in" : 3600
}
Edited Note: please make sure to have the correct date/time/timezone/dst configuration on your server. Having the clock off by even a few seconds will result in an invalid_grant error. http://www.time.gov will give the official time from the US govt, including in UTC.
Be sure to use DateTime.UtcNow instead of DateTime.Now in the GetExpiryAndIssueDate method.

Categories

Resources