how to get public key from getSigningKey function of jwks-rsa - node.js

i have tried two library of nodejs "jwks-rsa" and "jwks-client" to get public key to authenticate Apple login. But its giving error in getPublicKey is not a function.
Then i console the key which is return by getSigningKey is giving me -1.
i don't know why this is behaving like that.
const header = token.split(".");
const jwtHead = Buffer.from(header[0], "base64").toString();
const kid = JSON.parse(jwtHead).kid;
const jwksClient = require('jwks-client');
const client = jwksClient({
strictSsl: true, // Default value
jwksUri: process.env.APPLE_URL
});
client.getSigningKey(kid, (err, key) => {
console.log(key)
const signingKey = key.publicKey;
console.log(signingKey);
});

For me it works as expected I've try it with your configuration:
const kid = '86D88Kf'; // specify kid explicitly
const client = jwksClient({
strictSsl: true,
jwksUri: 'https://appleid.apple.com/auth/keys'
});
client.getSigningKey(kid, (err, key) => {
console.log(key)
const signingKey = key.publicKey;
console.log(signingKey);
});
And I get a legit public key in the response:
Probably you have some misconfiguration.

The problem is with the find() method used inside getSigningKey(kid).find() method has compatibility issues find() compatibility issue. We can use filter() method instead.
Instead of using client.getSigningKey(kid) try using keys = client.getSigningKeys() then filter the response const key = keys.filter(k => k.kid === kid);

Related

KuCoin API - TypeError: request.charAt is not a function

I'm trying to make a request to the KuCoin API to query the balance. I'm using the NodeJS API found here but I keep getting the error whenever I execute the code.
And here's the code snippet
data().then(api => {
const apiKey = api.api_key;
const apiSecretKey = api.api_secret;
const contactId = api.contact_id;
const exchange = api.exchange;
const passphrase = 'Passphrase';
/** Init Configure */
const config =
{
key: apiKey, // KC-API-KEY
secret: apiSecretKey, // API-Secret
passphrase: passphrase, // KC-API-PASSPHRASE
environment: "live"
}
API.init(require(config));
if (apiKey && exchange === "KuCoin-Futures") {
console.log("KuCoin Balance")
async function getBalance() {
try {
let r = await API.getAccountOverview()
console.log(r.data)
} catch(err) {
console.log(err)
}
}
return getBalance()
}
});
I the console log I get the following error
TypeError: request.charAt is not a function
at Function.Module._resolveLookupPaths (internal/modules/cjs/loader.js:617:15)
Does anyone know how I can fix this??
There are couple of things which look weird in the code snippet you provided, but the sample code from the kucoin-node-api library you linked should work perfectly fine. In case you are using that one, try this snippet which should show your account info:
const api = require('kucoin-node-api');
const config = {
apiKey: 'YOUR_KUCOIN_API_KEY',
secretKey: 'YOUR_KUCOIN_API_SECRET',
passphrase: 'YOUR_KUCOIN_API_PASSPHRASE',
environment: 'live'
};
api.init(config);
api.getAccounts().then((r) => {
console.log(r.data);
}).catch((e) => {
console.log(e);
});
In case you're using a different library, kucoin-node-sdk maybe (judging by your code snippet), then try to configure it correctly:
config.js file:
module.exports = {
baseUrl: 'https://api.kucoin.com',
apiAuth: {
key: 'YOUR_KUCOIN_API_KEY',
secret: 'YOUR_KUCOIN_API_SECRET',
passphrase: 'YOUR_KUCOIN_API_PASSPHRASE'
},
authVersion: 2
}
and your main.js (or whatever the name is):
const API = require('kucoin-node-sdk');
API.init(require('./config'));
const main = async () => {
const getTimestampRl = await API.rest.Others.getTimestamp();
console.log(getTimestampRl.data);
};
main();
The code above will show you KuCoin server timestamp only, but should be enough to keep going.
Good luck with trading!

Nodejs Coinbase V2 REST endpoint returns invalid signature

Could not figure out why the coinbase v2 REST endpoint returns an invalid signature error, maybe someone see what I'm doing wrong. Everything I have found relates to the use of an old NPM package that is no longer maintained. There is still a Coinbase Pro package, but I don't want to communicate with the Pro API.
const { createHmac } = require('crypto');
const axios = require('axios');
(async () => {
const cbApiKey = 'xxx';
const apiSecret = 'xxx';
const method = 'GET';
const path = '/v2/user';
const body = '';
const timestamp = Math.floor(new Date().getTime() * 1e-3);
const message = timestamp + method + path + body;
const key = Buffer.from(apiSecret, 'base64');
const cbAccessSign = createHmac('sha256', key).update(message).digest('base64');
const instance = axios.create();
try {
const user = await instance.request({
method,
url: `https://api.coinbase.com${path}`,
headers: {
'CB-ACCESS-KEY': `${cbApiKey}`,
'CB-ACCESS-SIGN': `${cbAccessSign}`,
'CB-ACCESS-TIMESTAMP': `${timestamp}`,
"Content-Type": 'application/json',
},
});
console.log(user);
} catch (error) {
console.log(error);
}
})();
I'm going to add something here because it was driving me crazy earlier, ie. trying crypto-js to no avail, then having to fight a good amount and put several workarounds in place to make 'crypto' as it's used in the tutorial work with all the impediments it currently enjoys.
Most of the invalid signatures as far as I can tell go back to the CB-ACCESS-SIGN and the biggest challenge was figuring out what a working equivalent in crypto-js looked like and getting all of this working properly in Angular 10.
A stripped-down version of the API call and the hash string creation for the access sign:
import * as CryptoJS from 'crypto-js';
async getUserCreds(apk: string, aps: string): Promise<any> {
let access_sign = Access_Sign(getUnixTimestamp(), 'GET', '/v2/user','',aps)
let httpOptions = {
headers: new HttpHeaders({
"CB-ACCESS-KEY": apk,
"CB-ACCESS-SIGN": access_sign,
"CB-ACCESS-TIMESTAMP": getUnixTimestamp().toString(),
"Content-Type": "application/json"
})
}
return this.http.get<any>('https://api.coinbase.com/v2/user',httpOptions)
.pipe(shareReplay(), catchError((x) => { return this.handleErrorLog(x)
})).toPromise();
}
export function Access_Sign(timestamp: number, method: string, requestPath: string, body: string, secret: string) {
let prehash = timestamp + method.toUpperCase() + requestPath + body;
return CryptoJS.HmacSHA256(prehash, secret).toString(CryptoJS.enc.Hex);
}
export function getUnixTimestamp() {
return Math.floor(Date.now() / 1000)
}
I found the answer here https://github.com/coinbase/coinbase-node/blob/master/lib/ClientBase.js#L101
The correct code for the signature is
var signature = crypto.createHmac('sha256', this.apiSecret).update(message).digest('hex');
If you're still facing this issue, make sure the path includes /
I was facing this issue because the path portion, that's used on message was accounts instead of /accounts

How to verify Patreon's webhook message?

From Patreon documentation:
the message signature is the HEX digest of the message body HMAC signed (with MD5) using your webhook's secret viewable on the webhooks page. You can use this to verify us as the sender of the message.
This is how I have tried to verify the message in my Express server:
import express from 'express';
import CryptoJS from 'crypto-js';
const router = express();
router.post('/webhook', function (req, res) {
const secret = 'Secret from https://www.patreon.com/portal/registration/register-webhooks';
console.log(CryptoJS.HmacMD5(req.body, secret).toString(CryptoJS.enc.Hex))
console.log(CryptoJS.HmacMD5(JSON.stringify(req.body), secret).toString(CryptoJS.enc.Hex))
const wordArray = CryptoJS.enc.Utf8.parse(req.body)
const hexString = CryptoJS.enc.Hex.stringify(wordArray);
console.log(CryptoJS.HmacMD5(hexString, secret).toString(CryptoJS.enc.Hex))
res.send();
});
But all these results that I am logging are not the same compared to the X-Patreon-Signature value that I am getting from the header.
Make sure your secret is correct in your code first then go ahead and create a new file that uses crypto to hash payloads.
I started with making a hashing module file with the funtion ComputeHash. This function takes a secret and payload for arguments:
const crypto = require('crypto');
/**
* #return {string}
*/
exports.ComputeHash = function (secret, payload)
{
// string to be hashed
const str = JSON.stringify(payload);
// create a md5 hasher
const md5Hasher = crypto.createHmac("md5", secret);
// hash the string
// and set the output format
const hash = md5Hasher.update(str).digest("hex");
return(hash);
};
Now this is how it should be Implemented to work with patreon in your API file:
DO note: in order to verify, you have to compare the SIGNATURE and HASH variables. If those are equal, it's verified.
I would suggest to implement this with middleware, but for demonstational purposes I've done it like this.
const Hasher = require('./../modules/Hasher');
//more code
.post('/', async (req, res) => {
try {
const secret = config.token;
const signature = req.headers["x-patreon-signature"];
const Hash = Hasher.ComputeHash(secret, req.body);
console.log(signature);
console.log(Hash);
const verified = (signature === Hash);
} catch (e) {
res.status(401);
res.end();
}
// more code

How to parse the attestationObject in Node.js

I have mocked up the response from the front end in Node.js as seen below.
the attestationObject parameter is what is returned once the Yubikey has signed the challenge and its been converted to base64 for transport to the node server.
What i'm getting is an ArrayBuffer { byteLength: 226 } but I have no idea what to do with it.
I know i need to check the domain name the signed it and I need to store something with the users credentials so they can login again.
I understand there is loads of options, I just want to get a bare minimum passwordless register and login working.
const cbor = require("cbor");
const attestationObject = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjE4mQ5WmgO3yl24XjxRqkP9LjqRYP-GsIubALB-5K_CK5FXMrOUa3OAAI1vMYKZIsLJfHwVQMAQABcapsmHtrsLJtfZ7RDcRm0iDgMlc5-CuP2XcNOwDy0uU2mU44ENk-EqtthH7huq8AipYfY0EvmfPRqQI-zI5GlAQIDJiABIVggZplpmQSKsJvg78INyrQUgBo9dv0vaZL6Qp15rOd6wMQiWCAx-ZeQ6T_xTMlY9cG3EWY54wT9Hd6EX7P7Ak-9uwauCA"
const clientDataJSON = "eyJjaGFsbGVuZ2UiOiJlVGR1TjJGaGFIaHhhRFJzT0RsdU1qTnRhMjgiLCJvcmlnaW4iOiJodHRwczovL2UzMDI3MTU3Lm5ncm9rLmlvIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9"
const id = "AFxqmyYe2uwsm19ntENxGbSIOAyVzn4K4_Zdw07APLS5TaZTjgQ2T4Sq22EfuG6rwCKlh9jQS-Z89GpAj7MjkQ"
const rawid = "AFxqmyYe2uwsm19ntENxGbSIOAyVzn4K4_Zdw07APLS5TaZTjgQ2T4Sq22EfuG6rwCKlh9jQS-Z89GpAj7MjkQ"
convertToBuffer(attestationObject)
.then((buffer) => {
return parseAttestationObject(buffer)
})
.then((json) => {
console.log(json)
})
.catch((err) => {
console.log(err)
})
function convertToBuffer(base64) {
return new Promise((resolve, reject) => {
if (typeof base64 === "string") {
base64 = base64.replace(/-/g, "+").replace(/_/g, "/");
base64 = Buffer.from(base64, "base64");
base64 = new Uint8Array(base64);
resolve(base64.buffer);
}
})
}
function parseAttestationObject(attestationObject){
return new Promise((resolve, reject) => {
const authData = cbor.decodeAllSync(Buffer.from(attestationObject));
const authnrDataArrayBuffer = authData[0].authData.buffer;
console.log(authnrDataArrayBuffer)
// What do I do with this authnrDataArrayBuffer? What needs saving to the database?
})
}
It would be helpful if you would be a bit more exact about the exact issue, but in a nutshell:
You'd want to store the rawI This is the identifier that you need to pass in the allowCredentials object in the authentication step, so you'll need it.
The attestationobject is a CBOR encoded value. After some manipulations you should be able to extract a public key from this. You'll be able to to use this certificate to verify the response from the authenticator in the authentication step.
I'm leaving out any specific implementation steps, but please have a look at https://github.com/fido-alliance/webauthn-demo as this project implements webauthn for node.js as well, so you should be able to extract all relevant code from it.
// this is your attestationObject which is web safe base64 encode string
var attestationObject = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjE4mQ5WmgO3yl24XjxRqkP9LjqRYP-GsIubALB-5K_CK5FXMrOUa3OAAI1vMYKZIsLJfHwVQMAQABcapsmHtrsLJtfZ7RDcRm0iDgMlc5-CuP2XcNOwDy0uU2mU44ENk-EqtthH7huq8AipYfY0EvmfPRqQI-zI5GlAQIDJiABIVggZplpmQSKsJvg78INyrQUgBo9dv0vaZL6Qp15rOd6wMQiWCAx-ZeQ6T_xTMlY9cG3EWY54wT9Hd6EX7P7Ak-9uwauCA";
// need to convert to base64 encode string
attestationObject = attestationObject.replace(/\-/g, '+').replace(/_/g, '/') + '=='.substring(0, (3*attestationObject.length)%4);
// do a base64 decode
var attCbor = Buffer.from(attestationObject, 'base64');
// decode to have CBOR object, using cbor module
const cbor = require("cbor");
var attCborObj = cbor.decodeAllSync(attCbor)[0];
console.log(attCborObj);

Global scope variable initialization in google cloud function

I want to store a secret key using Google Cloud KMS and use it in Google Cloud Function. First I will encrypt my key and store it in environment variable
If I decrypt my secret key like link, it returns Promise.
Is my variable guaranteed to be finished initializing when my function is deployed and called?
I'm the author of that code snippet and the corresponding blog post. For the post history, here's the full snippet the OP is referring to:
const cryptoKeyID = process.env.KMS_CRYPTO_KEY_ID;
const kms = require('#google-cloud/kms');
const client = new kms.v1.KeyManagementServiceClient();
let username;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_USER,
}).then(res => {
username = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
let password;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_PASS,
}).then(res => {
password = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
exports.F = (req, res) => {
res.send(`${username}:${password}`)
}
Because Node is an asynchronous language, there is no guarantee that the variables username and password are fully initialized before function invocation as-written. In that snippet, I optimized for "decrypt at function boot so each function invocation runs in constant time". In your example, you want to optimize for "the function is fully initialized before invocation" which requires some re-organization of the code.
One possible solution is to move the lookup into a Node function that is invoked when the GCF function is called. For example:
const cryptoKeyID = process.env.KMS_CRYPTO_KEY_ID;
const kms = require('#google-cloud/kms');
const client = new kms.v1.KeyManagementServiceClient();
let cache = {};
const decrypt = async (ciphertext) => {
if (!cache[ciphertext]) {
const [result] = await client.decrypt({
name: cryptoKeyID,
ciphertext: ciphertext,
});
cache[ciphertext] = result.plaintext;
}
return cache[ciphertext];
}
exports.F = async (req, res) => {
const username = await decrypt(process.env.DB_USER);
const password = await decrypt(process.env.DB_PASS);
res.send(`${username}:${password}`)
}
Note that I added a caching layer here, since you probably don't want to decrypt the encrypted blob on each invocation of the function.

Resources