Bitstamp Authenticated API hmac - node.js

I hope you have more luck on this than I have.
As the title suggests I am trying to hit a private API endpoint on bitstamp.net. https://www.bitstamp.net/api/#api-authentication try as I might I cannot get the signature to make sense. I have followed the docs as close as I know how, I have spent hours trying to learn as much python as possible and decyphering the docs, as there is no JavaScript example.
The signature is supposed to be a sha256 hmac of the API secret and the message variable which is a backslash seperated string that I cannot find anything wrong with anymore.
I use the below link to query the server, the apikey and secret will be encoded and decoded in production when this all works.
http://localhost:3001/sha256/jorw1007/QGcJKrhenfqOML5cOpTsLOe9IbEPsJnN/bKXiqtYHawJ7DAUZIHviAPoRrp0zhfIv
const crypto = require('crypto');
app.get("/sha256/:id/:key/:secret", cors(), (request, response, next) => {
const APIkey = "BITSTAMP" + " " + request.params.key;
const APIsecret = request.params.secret;
const urlHost = "192.168.0.120:3001"; // where is querying the API from bitstamp? in this case its the server localhost:3001
const urlPath = "/api/v2/balance/";
const urlQuery = "";
const UTC = Date.now().toString();
const nonce = randomBytes(18).toString("hex").toLowerCase();
const nonce2 = "f93c979d-b00d-43a9-9b9c-fd4cd9547fa6"
const contentType = "application/x-www-form-urlencoded";
const version = "v2";
const payload = urlencoded({"offset": "1"})
let message = `${APIkey}\\POST\\${urlHost}\\${urlPath}\\${urlQuery}\\${nonce}\\${UTC}\\${version}\\`
// const encodedMsg = encodeURI(message) ///why are we encoding?
// const signature = createHmac("sha256", APIsecret ).update(JSON.stringify(encodedMsg) );
// const sigDigested = signature.digest('hex')
var signature = crypto.createHmac('sha256', APIsecret).update(encodeURI(message));
// to lowercase hexits
const digestedHash = signature.digest('hex');
console.log(message)
const headers = {
"X-Auth": APIkey,
"X-Auth-Signature": digestedHash,
"X-Auth-Nonce": nonce,
"X-Auth-Timestamp": UTC,
"X-Auth-Version": version,
};
fetch("https://www.bitstamp.net/api/v2/balance/", {
headers,
method: "POST",
data : payload
})
.then((res) => res.json())
.then((json) => response.send(json))
.catch((err) => console.error(err));
});
I keep getting error 'API0005' which means the signature is invalid.
Please could someone point me in the right direction?
thank you

After countless effort I finally managed to get it work. The code I will be posting is in C# but it very much resembles that of JavaScript.
protected async Task<string> SendRequest(string #base, string endpoint, EndpointHttpMethod method, Dictionary<string, object> parameters = null, bool signed = true)
{
var resiliencyStrategy = ExchangeTools.DefineAndRetrieveResiliencyStrategy("Bitstamp");
parameters ??= new Dictionary<string, object>();
var payloadString = string.Empty;
var queryParameters = string.Empty;
var contentType = string.Empty;
if (method == EndpointHttpMethod.GET)
{
queryParameters = ExchangeTools.BuildQuery(parameters);
endpoint += !string.IsNullOrWhiteSpace(queryParameters) ? $"?{queryParameters}" : string.Empty;
}
else
{
payloadString = ExchangeTools.BuildQuery(parameters, true);
contentType = !string.IsNullOrWhiteSpace(payloadString) ? "application/x-www-form-urlencoded" : string.Empty;
}
using var client = new HttpClient();
using var httpRequestMessage = new HttpRequestMessage(new HttpMethod(method.ToString()), endpoint);
client.BaseAddress = new Uri(Endpoints.BasePrefix + #base);
if (signed)
{
var apiKey = $"BITSTAMP {publicKey}";
var version = "v2";
var nonce = Guid.NewGuid();
var timestamp = ExchangeTools.GetUnixTimestamp(1000);
var msg = $"{apiKey}{method}{#base}{endpoint}{queryParameters}{contentType}{nonce}{timestamp}{version}{payloadString}";
var signature = ExchangeTools.Sign(msg, privateKey, HashingAlgorithm.HMACSHA256, ByteEncoding.ASCII, SignatureEncoding.HexString);
httpRequestMessage.Headers.Add("X-Auth", apiKey);
httpRequestMessage.Headers.Add("X-Auth-Signature", signature);
httpRequestMessage.Headers.Add("X-Auth-Nonce", nonce.ToString()); ;
httpRequestMessage.Headers.Add("X-Auth-Timestamp", timestamp.ToString()); ;
httpRequestMessage.Headers.Add("X-Auth-Version", version);
if(parameters.Count > 0)
{
httpRequestMessage.Content = new FormUrlEncodedContent(parameters.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()));
}
}
var response = await resiliencyStrategy.ExecuteAsync(() => client.SendAsync(httpRequestMessage));
var result = await response.Content.ReadAsStringAsync();
return result;
}
Let me know if you need further clarification. My best guess as to why yours is not working comes down to conditionally setting the payload and queryParam depending on what parameters you have and whether the request is a GET or POST.

Related

Mandrill webhook authentication-signature not matched

Mandrill webhook authentication-verify signature
For node js
example verify signature
please check the code below
But its working only for event types like send, reject. Not working for event types like open, click & others
function generateSignature(webhook_key, url, params) {
var signed_data = url;
const param_keys = Object.keys(params);
param_keys.sort();
param_keys.forEach(function (key) {
signed_data += key + params[key];
});
hmac = crypto.createHmac('sha1', webhook_key);
hmac.update(signed_data);
return hmac.digest('base64');
}
let url = "https://your-app-domain.com/default/MandrillXP-new";
let key = "abcd1234"; //your mandrill webhook api key
let bodyPayload;
if(event.isBase64Encoded){
bodyPayload = Buffer.from(event.body, 'base64').toString()
}else{
bodyPayload = event.body
}
let splitData = req.body.split("=")
let decodeData = decodeURIComponent(splitData[1]);
var generatedSignature = generateSignature(key, url, { "mandrill_events": decodeData })
if (req.headers["x-mandrill-signature"]!== generatedSignature) {
console.log("signature mismatch")
}else{
console.log("signature matched")
}
replace '+' with ' '(space) in string before encode
var decodeData = decodeData .replace(/[+]/g, ' ');

Is it possible to create a JSON Web Token using Azure Key Vault's Keys API?

I am currently using the jsonwebtokens package for generating signed JWTs with a RSA signing key. I was experimenting to see how I can move to Azure Key Vault. One way I can do is to store the private and public keys as Vault Secrets. But I noticed that there's also a Vault Keys API which generates a key in Vault and signs data which I provide it.
I've been trying with the #azure/keyvault-keys package and this is where I've got:
async signJsonWebToken(data: object, expiry: string): Promise<string> {
const headerB64 = this.base64url(JSON.stringify(this.keyHeader), 'binary');
const payloadB64 = this.base64url(this.getTokenData(data, expiry), 'utf8');
const payload = `${headerB64}.${payloadB64}`;
const key = await this.keyClient.getKey(this.KEY_NAME);
const cryptClient = new CryptographyClient(key, new DefaultAzureCredential());
const hash = crypto.createHash('sha256');
const digest = hash.update(payload).digest();
const signResult = await cryptClient.sign('RS256', digest);
const signResultB64 = this.base64url(signResult.result.toString(), 'utf8');
const result = `${payload}.${signResultB64}`;
this.logger.log('Key: ' + key.key);
this.logger.log('Sign result: ' + result);
return result;
}
private base64url(data: string, encoding: string) {
return SBuffer
.from(data, encoding)
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
private getTokenData(data: object, expiry: string): string {
const now = Date.now();
const expiresIn = new Date();
if (expiry.endsWith('d')) {
expiresIn.setDate(expiresIn.getDate() + parseInt(expiry));
} else if (expiry.endsWith('h')) {
expiresIn.setHours(expiresIn.getHours() + parseInt(expiry));
} else if (expiry.endsWith('m')) {
expiresIn.setMinutes(expiresIn.getMinutes() + parseInt(expiry));
}
const tokenData = Object.assign({
iat: now,
exp: expiresIn.getTime()
}, data);
return JSON.stringify(tokenData);
}
The generated signed signature do not look anywhere close to what I'd usually be getting with the jsonwebtoken package. My intention is that I'd like to sign my tokens with Vault but would like to verify with jsonwebtoken.verify(). Is this even possible? What am I doing wrong in my code?
Since you use the Azure kay vault to sign jwt, we also can use Azure key vault to Verify the jwt
For example
const key = await this.keyClient.getKey(this.KEY_NAME);
const cryptClient = new CryptographyClient(key, new DefaultAzureCredential());
const util =require('util')
const base64 = require('base64url');
const JWT=""
const jwtHeader = JWT.split('.')[0];
const jwtPayload = JWT.split('.')[1];
const jwtSignature = JWT.split('.')[2];
const signature = base64.toBuffer(jwtSignature)
const data = util.format('%s.%s', jwtHeader, jwtPayload);
const hash = crypto.createHash('sha256');
const digest = hash.update(data).digest()
const verified =await cryptClient.verify("RS256",digest,signature)
Besides If you want to use jsonwebtoken package to verify jet, please refer to the following code
const util =require('util')
const base64 = require('base64url');
const forge = require('node-forge');
const jwt = require('jsonwebtoken')
async Function test{
// gerenrate jwt
const headerObj = {
alg: 'RS256',
typ: 'JWT'
};
const payloadObj = {
sub: '1234567890',
name: 'John Doe'
};
const encodedHeader = base64(JSON.stringify(headerObj));
const encodedPayload = base64(JSON.stringify(payloadObj));
const data = util.format('%s.%s', encodedHeader, encodedPayload);
const hash = crypto.createHash('sha256');
const digest = hash.update(data).digest()
const keyClient =new KeyClient("https://testsql.vault.azure.net/",new DefaultAzureCredential());
const key =await keyClient.getKey("test");
const cryptClient = new CryptographyClient(key.id, new DefaultAzureCredential());
const signResult = await cryptClient.sign("RS256",digest)
const jwts =util.format('%s.%s.%s', encodedHeader, encodedPayload,base64(signResult.result));
console.log(jwts)
// verify
// convert azure key vault ket to public key
var n =Buffer.from(key.key.n).toString("base64")
var e =Buffer.from(key.key.e).toString("base64")
var publicKey = forge.pki.setRsaPublicKey(
base64urlToBigInteger(n),
base64urlToBigInteger(e));
// convert public key to pem file
var pem = forge.pki.publicKeyToPem(publicKey);
var d= jwt.decode(jwts,pem.toString())
console.log(d)
}
function base64urlToBigInteger(str) {
var bytes = forge.util.decode64(
(str + '==='.slice((str.length + 3) % 4))
.replace(/\-/g, '+')
.replace(/_/g, '/'));
return new forge.jsbn.BigInteger(forge.util.bytesToHex(bytes), 16);
};

How to scheduling push notifications using azure sdk for node

I know it is possible in .net, i can see the reference over here https://learn.microsoft.com/en-us/azure/notification-hubs/notification-hubs-send-push-notifications-scheduled. But I want to know how to do that in node. Can any one guide me on this.
You can send a scheduled notification in Node using the REST API. Use the specification for sending a normal notification and replace /messages with /schedulednotifications. You will also need to add a header specifying the datetime named ServiceBusNotification-ScheduleTime.
For an example using the template schema:
var CryptoJS = require("crypto-js");
var axios = require("axios");
var getSelfSignedToken = function(targetUri, sharedKey, keyName,
expiresInMins) {
targetUri = encodeURIComponent(targetUri.toLowerCase()).toLowerCase();
// Set expiration in seconds
var expireOnDate = new Date();
expireOnDate.setMinutes(expireOnDate.getMinutes() + expiresInMins);
var expires = Date.UTC(expireOnDate.getUTCFullYear(), expireOnDate
.getUTCMonth(), expireOnDate.getUTCDate(), expireOnDate
.getUTCHours(), expireOnDate.getUTCMinutes(), expireOnDate
.getUTCSeconds()) / 1000;
var tosign = targetUri + '\n' + expires;
// using CryptoJS
var signature = CryptoJS.HmacSHA256(tosign, sharedKey);
var base64signature = signature.toString(CryptoJS.enc.Base64);
var base64UriEncoded = encodeURIComponent(base64signature);
// construct autorization string
var token = "SharedAccessSignature sr=" + targetUri + "&sig="
+ base64UriEncoded + "&se=" + expires + "&skn=" + keyName;
// console.log("signature:" + token);
return token;
};
var keyName = "<mykeyName>";
var sharedKey = "<myKey>";
var uri = "https://<mybus>.servicebus.windows.net/<myhub>";
var expiration = 10;
var token = getSelfSignedToken(uri, sharedKey, keyName, expiration);
const instance = axios.create({
baseURL: uri,
timeout: 100000,
headers: {
'Content-Type': 'application/octet-stream',
'X-WNS-Type': 'wns/raw',
'ServiceBusNotification-Format' : 'template',
'ServiceBusNotification-ScheduleTime': '2019-07-19T17:13',
'authorization': token}
});
var payload = {
"alert" : " This is my test notification!"
};
instance.post('/schedulednotifications?api-version=2016-07', payload)
.then(function (response) {
console.log(response);
}).catch(function (error) {
// handle error
console.log(error);
});

Node API Request using AES-ECB Encryption : Fail (The header content contains invalid characters)

fairly noobish at aes-ecb encryption, apologies in advance.
I am trying to make an API call to a server that requires a encrypted payload.
I'm having a little trouble in the sequence in which i encrypt my data converting between base64 and ascii.
When i run the below code, i keep getting
The header content contains invalid characters.
I suspect it may be the way i'm converting the encrypted data between types but not entirely sure. Any thoughts would be really appreciated.
var request = require("request");
var aesjs = require('aes-js');
var apiKey = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var privateKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var method = 'GET';
var url = 'https://apiurl.com';
var agent = "app /1.0 Android/8.1.0 (Android)";
var options = {
method: method,
url: url,
headers:
{ 'X-API-KEY': apiKey,
'X-CSN-USER-AGENT': agent,
'apiData' : GetAndEncryptApiData(method, url)
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
function GetAndEncryptApiData(method, url){
var isoTimestamp = new Date().toISOString();;
var text = `uri:${url} \nmethod:${method}\ntimestamp:${isoTimestamp}`;
var key = Buffer.from(privateKey, 'base64');
var aesEcb = new aesjs.ModeOfOperation.ecb(key);
var textToBytes = aesjs.utils.utf8.toBytes(text);
var paddedData = aesjs.padding.pkcs7.pad(textToBytes);
var encryptedData = aesEcb.encrypt(paddedData);
var output = Buffer.from(encryptedData).toString('ascii');
return output;
}

How to use Azure storage emulator blob endpoint to get a blob?

The following code, when pointed to a real Azure storage account will successfully return the blob content:
var path = $"{container}/{blob}";
var rfcDate = DateTime.UtcNow.ToString("R");
var headers = "GET\n\n\n\n\n\n\n\n\n\n\n\n" +
"x-ms-blob-type:Block\n" +
$"x-ms-date:{rfcDate}\n" +
$"x-ms-version:{ServiceVersion}\n" +
$"/{AccountName}/{path}";
var uri = new Uri(BlobEndpoint + path);
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("x-ms-blob-type", "Block");
request.Headers.Add("x-ms-date", rfcDate);
request.Headers.Add("x-ms-version", ServiceVersion);
string signature = "";
using (var sha = new HMACSHA256(System.Convert.FromBase64String(AccountKey)))
{
var data = Encoding.UTF8.GetBytes(headers);
signature = System.Convert.ToBase64String(sha.ComputeHash(data));
}
var authHeader = $"SharedKey {AccountName}:{signature}";
request.Headers.Add("Authorization", authHeader);
using (var client = new HttpClient())
{
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
However, if I configure it to use the Azure emulator where:
AccountName = devstoreaccount1
AccountKey = Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
BlobEndpoint = http://127.0.0.1:10000/
ServiceVersion = 2009-09-19
I always get 404. I'm using Azure Storage Emulator v4.6. Is the code or config incorrect or is this not supported with the emulator?
There are two issues with your code:
Blob Service in Storage Emulator listens at http://127.0.0.1:1000 however the base URI is http://127.0.0.1:1000/devstoreaccount1.
In computation of Signature String (header variable in your code), account name must appear twice. This is because the account name is part of your resource's URI path (URL for the blob would be http://127.0.0.1:1000/devstoreaccount1/container-name/blob-name).
Based on these, please try the following code:
static async Task<string> ReadBlobFromDevStorage()
{
var container = "temp";
var blob = "test.txt";
var ServiceVersion = "2009-09-19";
var AccountName = "devstoreaccount1";
var BlobEndpoint = "http://127.0.0.1:10000/devstoreaccount1";
var path = $"{container}/{blob}";
var AccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
var rfcDate = DateTime.UtcNow.ToString("R");
var headers = "GET\n\n\n\n\n\n\n\n\n\n\n\n" +
"x-ms-blob-type:Block\n" +
$"x-ms-date:{rfcDate}\n" +
$"x-ms-version:{ServiceVersion}\n" +
$"/{AccountName}/{AccountName}/{path}";
var uri = new Uri(BlobEndpoint + "/" + path);
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("x-ms-blob-type", "Block");
request.Headers.Add("x-ms-date", rfcDate);
request.Headers.Add("x-ms-version", ServiceVersion);
string signature = "";
using (var sha = new HMACSHA256(System.Convert.FromBase64String(AccountKey)))
{
var data = Encoding.UTF8.GetBytes(headers);
signature = System.Convert.ToBase64String(sha.ComputeHash(data));
}
var authHeader = $"SharedKey {AccountName}:{signature}";
request.Headers.Add("Authorization", authHeader);
using (var client = new HttpClient())
{
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
return content;
}
}

Resources