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).
Related
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?
I'm trying to connect to Apple's News API. When generating the signature the results all appear the same from each of the different examples. However, I keep getting this error:
{"errors":[{"code":"WRONG_SIGNATURE"}]}
Here is my c# code to generate the auth header.
public class Security
{
public static string AuthHeader(string method, string url, object content=null)
{
var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
var encoding = new ASCIIEncoding();
var dt = DateTime.Now.ToString(Constants.DateFormat);
var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
var key = Convert.FromBase64String(apiKeySecret);
var hmac = new HMACSHA256(key);
var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
var signature = Convert.ToBase64String(hashed);
var authorizaton = string.Format(#"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
return authorizaton;
}
}
Short version of Constants class
public static class Constants
{
public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
public static readonly string BaseUrl = "https://news-api.apple.com";
public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";
}
Short version of Actions class (SendCommand is the method that performs the request)
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
I'm using the ReadChannel method for testing.
I've also tried examples in php and ruby with no luck.
Any ideas how to do this correctly?
Pasted the authorization string generated from the original code on this post into fiddler and I was able to get a successful response from the Apple News API. It seems like HttpWebRequest isn't including the Authorization header correctly and submitting the same request with the property PreAuthenticate = true corrects this issue (HttpWebRequest.PreAuthenticate). Also, with a GET request the ContentType needs to be omitted so I've added a conditional statement to account for this too.
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.PreAuthenticate = true;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase))
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
The error complains something is wrong in how we compute the signature. Let's take a look at the Apple's example code to produces a correct signature, which is here:
https://developer.apple.com/documentation/apple_news/apple_news_api/about_the_news_security_model
Unfortunately I only found Python code. I don't know Python, either, but I can figure out enough to adapt it to just show the signature. We'll also need to know exactly what date value was used.
import base64
from hashlib import sha256
import hmac
from datetime import datetime
channel_id = 'cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_id = '240ab880-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_secret = 'HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF='
url = 'https://news-api.apple.com/channels/%s' % channel_id
date = str(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
canonical_request = 'GET' + url + date
key = base64.b64decode(api_key_secret)
hashed = hmac.new(key, canonical_request, sha256)
signature = hashed.digest().encode("base64").rstrip('\n')
print date
print signature
You can see the result here (the short link points to tutorialspoint.com, which was the first online python interpreter I found in Google):
http://tpcg.io/e1W4p1
If you don't trust the link, just know I was able to use it to figure out the following known-correct signature based on these known inputs:
Method: GET
URL: https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF
DateTime: 2018-06-12T18:15:45Z
API Secret: HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
Signature: f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=
Now we can write C# code we can verity. When we can use those same inputs to produce the same signature result, we will have correct code.
Based on that, I was able to write this C# code:
public static void Main()
{
string method = "GET";
string url = "https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
string dateString = "2018-06-12T18:15:45Z";
string apiKeySecret = "HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=";
var MyResult = GetSignature(method, url, dateString, apiKeySecret);
var DocumentedResult = "f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=";
Console.WriteLine(MyResult);
Console.WriteLine(MyResult == DocumentedResult);
}
public static string GetSignature(string method, string url, string dt, string APISecret)
{
var hmac = new HMACSHA256(Convert.FromBase64String(APISecret));
var hashed = hmac.ComputeHash(Encoding.ASCII.GetBytes(method + url + dt));
return Convert.ToBase64String(hashed);
}
Which you can see in action here:
https://dotnetfiddle.net/PQ73Zv
I don't have my own Apple API key to test with, so that's as far as I can take you.
One thing I did notice from the question is Apple's example has a "Z" at the end of the date string which is missing with original code here.
I have this code which I am attempting to use to communicate an API via RestSharp.
const string task = "pay";
const string command_api_token = "9ufks6FjffGplu9HbaN7uq6XXPPVQXBP";
const string merchant_email_on_voguepay = "mymail#mail.com";
Random rnd = new Random();
string refl = DateTime.Now + rnd.Next(0,9999999).ToString();
byte[] hash_target = Encoding.Default.GetBytes(command_api_token + task + merchant_email_on_voguepay + refl);
string hashD = BitConverter.ToString(new SHA512CryptoServiceProvider().ComputeHash(hash_target)).Replace("-", string.Empty).ToUpper();
var keyValues = new Dictionary<string, string>
{
{ "task", "pay"},
{ "merchant", "3333-4444"},
{ "ref",refl},
{ "hash",hashD},
{ "amount", "20"},
{ "seller", "seller#mail.com"},
{ "remarks", "payment"},
};
//serialization using Newtonsoft JSON
string json = JsonConvert.SerializeObject(keyValues);
//url encode the json
var postString = Server.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);
Textbox1.Text = response.Content;
I think the arrangement of my code is not really ok, because I keep getting error message on each move I make.
If I try to post it as it is above, I get
"response":"X006","description":"Invalid hash"...
If try to get "url encode the json" involved in the "calling API with Restsharp", I get error message as
"response":"X001","description":"Invalid Merchant Id"...
I think I am not placing things right, can someone look at my work and point out what could be the issue with this code?
I am using below code for calling API may this one help u.Here i am passing one class object u replace this by Dictionary and try..
public void insertData(OCMDataClass kycinfo, string clientId, string type)
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["CBService"]);
string postBody = Newtonsoft.Json.JsonConvert.SerializeObject(kycinfo);
var jsonString = JsonConvert.SerializeObject(kycinfo);
var content = new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json");
var myContent = JsonConvert.SerializeObject(kycinfo);
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
var result = client.PostAsync("Bfilvid/api/SvcVId/CreateKYCRepository", content).Result;
if (result.IsSuccessStatusCode)
{
string resultContent = result.Content.ReadAsStringAsync().Result;
}
else
{
string resultContent = result.Content.ReadAsStringAsync().Result;
}
}
}
catch (Exception ex)
{
}
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.
I want to post an image to my Twitter account via C#. I can get access token code, everything is fine but I investigated a PHP code
$tmhOAuth = new tmhOAuth(array(
'consumer_key' => OAUTH_CONSUMER_KEY,
'consumer_secret' => OAUTH_CONSUMER_SECRET,
'user_token' => $oauth_token,
'user_secret' => $oauth_token_secret,
));
$image = "{$_FILES['image']['tmp_name']};type={$_FILES['image']['type']};filename={$_FILES['image']['name']}";
$code = $tmhOAuth->request('POST', 'https://upload.twitter.com/1/statuses/update_with_media.json',
array(
'media[]' => "#{$image}",
'status' => " " . $status, //A space is needed because twitter b0rks if first char is an #
'lat' => $lat,
'long' => $long,
),
true, // use auth
true // multipart
In PHP code, the OAuth class has a request method. In C# side, I used Twitterizer library which hasn't any request method in OAuth class. Then I used Webclient instead of request method. But I need to some Credentials to post data. But I don't know what/why I use username and password. Actually, I don't want to use any credentials. What can I use instead of credentials?
Second problem is, I always get an authorized errors (401) here is code
OAuthTokenResponse responseToken = OAuthUtility.GetAccessToken(ConsumerKey, ConsumerSecret, oauth_token, oauth_verifier);
OAuthTokens accessToken = new OAuthTokens();
accessToken.AccessToken = responseToken.Token;
accessToken.AccessTokenSecret = responseToken.TokenSecret;
accessToken.ConsumerKey = ConsumerKey;
accessToken.ConsumerSecret = ConsumerSecret;
TwitterResponse<TwitterUser> twitterResponse = TwitterAccount.VerifyCredentials(accessToken);
System.Net.ServicePointManager.Expect100Continue = false;
if (twitterResponse.Result != RequestResult.Unauthorized)
{
try
{
string URL = "https://upload.twitter.com/1/statuses/update_with_media.json";
WebClient client = new WebClient();
client.Credentials = new System.Net.NetworkCredential(uName, pass);
NameValueCollection postData = new NameValueCollection();
postData.Add("status", status);
postData.Add("media[]", Encoding.ASCII.GetString(bytesOfImage));
byte[] b = client.UploadValues(URL, "POST", postData); // 401 error.
}
catch (Exception e)
{
return e.Message;
}
So where is the problem in my code?
You can do this in LINQ to Twitter, using the TweetWithMedia method, like this:
static void TweetWithMediaDemo(TwitterContext twitterCtx)
{
string status = "Testing TweetWithMedia #Linq2Twitter " + DateTime.Now.ToString(CultureInfo.InvariantCulture);
const bool possiblySensitive = false;
const decimal latitude = StatusExtensions.NoCoordinate; //37.78215m;
const decimal longitude = StatusExtensions.NoCoordinate; // -122.40060m;
const bool displayCoordinates = false;
const string replaceThisWithYourImageLocation = #"..\..\images\200xColor_2.png";
var mediaItems =
new List<Media>
{
new Media
{
Data = Utilities.GetFileBytes(replaceThisWithYourImageLocation),
FileName = "200xColor_2.png",
ContentType = MediaContentType.Png
}
};
Status tweet = twitterCtx.TweetWithMedia(
status, possiblySensitive, latitude, longitude,
null, displayCoordinates, mediaItems, null);
Console.WriteLine("Media item sent - Tweet Text: " + tweet.Text);
}
Joe