Unable to verify x-slack-signature - javascript

I've been having a slack app for a few years already. It was used exclusively in only one workspace.
On receiving a slack command I run a regular auth check as it described in slack API.
My code:
const requestSignatureHeader = req.headers["x-slack-signature"] as string;
const requestTimestamp = req.headers["x-slack-request-timestamp"];
const hmac = crypto.createHmac("sha256", this.signingSecret);
const [version, requestSignature] = requestSignatureHeader.split("=");
const rawBody = stringify(req.body)
.replace(/\+/g, "%2B")
.replace(/%20/g, "+")
.replace(/!/g, "%21")
.replace(/'/g, "%27")
.replace(/:/g, "%3A")
.replace(/,/g, "%2C")
.replace(/#/g, "%40")
.replace(/\//g, "%2F");
const stringToSign = `${version}:${requestTimestamp}:${rawBody}`;
hmac.update(stringToSign);
const calculatedSignature = hmac.digest("hex");
const isValid = requestSignature === calculatedSignature;
It was working correctly until a few weeks ago we've moved to the enterprise workspace (it maybe just a coincidence though).
Right now x-slack-signature is always different from the calculated value. I've checked multiple times: signing secret is the same that specified in app's settings. Any ideas what could be the problem?

Related

nodejs - escaping variable character within .env

My .env has, say, two simple variables:
USERNAME:"myusername"
PASSWORD:"mypassword&7"
The thing is, when I try to use shell.exec to pass a git clone command, it seems to be ignoring the '&7'from my password variable.
shell.exec(`git clone https://${process.env.USERNAME}:${process.env.USERNAME}#github.com/my-repo/xyz-git-ops.git`);
it outputs:
/bin/sh: 7#gmy-repo/xyz-git-ops.git: No such file or directory
Cloning into 'mypassword'...
fatal: unable to access 'https://myusername:mypassword/': URL using bad/illegal format or missing URL
I notice a few weird stuff:
1 - it ignores the last 2 characters of my password value, the '&7'and the git clone output replaces it with a '/'instead.
2 - if I do console.log(process.env.USERNAME), it prints the value perfectly: mypassword&7
All that makes me wonder if is there a way of either escaping the '&' char from the password value or if my approach to pass credential via shell.exec() is absolutely mistaken. Bellow is the full content of my .js file
const nodeCron = require("node-cron");
const shell = require('shelljs');
const rpath = '/Users/myuser/Documents/Git Ops Cron/repos';
require('dotenv').config();
const start = Date.now();
const username = process.env.USERNAME
const password = process.env.PASSWORD
async function xyzGitOps(){
console.log("Running scheduled job", start);
shell.cd(rpath);
shell.exec(`git clone https://${username}:${password}#github.com/my-repo/xyz-git-ops.git`);
return console.log("Job finished");
}
const job = nodeCron.schedule("* * * * *", xyzGitOps);
The username/password component of a URL should be percent encoded.
The node:url URL class will do this for you
const repo = new URL(`https://github.com/my-repo/xyz-git-ops.git`)
repo.username = process.env.USERNAME
repo.password = process.env.PASSWORD
The URL's .toString() encodes the values:
> String(repo)
'https://userw:pass%%2F%40%23$#github.com/my-repo/xyz-git-ops.git'
> `${repo}`
'https://userw:pass%%2F%40%23$#github.com/my-repo/xyz-git-ops.git'

Crypto hmac conversion from Python to Javascript

I'm trying to convert the following function from Python to Javascript.
Currently concentrating on the generation of the signature.
def prepare_url(self, segments, params={}):
if self.token is not None:
sig = hmac.new(self.token.key.encode(), digestmod=hashlib.sha1)
sig.update(self.method.lower().encode())
sig.update(self.service.lower().encode())
ts = self.get_timestamp(self.token.server_offset)
sig.update(ts.encode())
params["timestamp"] = ts
sig.update(self.token.identifier.lower().encode())
params["signature-method"] = "auth"
params["signature-version"] = self.version
params["signature"] = base64.standard_b64encode(sig.digest())
self.url = "%s/%s/%d/" % (self.base_path, self.service, self.version)
self.url += "/".join([urllib.parse.quote(s) for s in segments])
if params:
self.url += "?%s" % urllib.parse.urlencode(params)
In Javascript I'm using the crypto library and have the following code:
import crypto from 'crypto';
const KEY = '5d016f32-452f-4b9b-8e81-641e14d4d98c';
const METHOD = 'get';
const SERVICE = 'data';
const date = (new Date()).toISOString().slice(0, 19);
const encoded = crypto.createHash('sha1')
.update(KEY)
.update(METHOD.toLowerCase())
.update(SERVICE.toLowerCase())
.update(date)
.digest('hex');
console.log(encoded);
const baseEncoded = btoa(encoded);
console.log(baseEncoded);
However the end codes are not comparable.
Python generates using the given inputs: b'oV4RJ6pAz+hxZsxeQthx8gZrhAY='
Javascript generates: YTZjMmIyYjQzOGEwZGUxZTU1YTNjMWVlYjA3MTA3NTFmODc0MDM3ZQ==
I googled around but could not find what to change to make this work.
In python the digest is hashlib.sha1, this is not available in crypto.
Any pointers?
Below are the codes Python generates at each of the update steps.
-----> key: b'bnAcIlriz+t4hTQLBrnjI1aeXBI='
-----> method: b'rc1Y6wKZo8pDKHmhjVNDkhcVNKM='
-----> Service: b'/urBh6Yqk6QI39JhYtSMI9P9QS8='
-----> Time: 5d016f32-452f-4b9b-8e81-641e14d4d98c
-----> Identifier: b'oV4RJ6pAz+hxZsxeQthx8gZrhAY='
-----> FINAL: b'oV4RJ6pAz+hxZsxeQthx8gZrhAY='
Solution:
const encoded = crypto.createHmac('sha1', key)
.update(METHOD.toLowerCase())
.update(SERVICE.toLowerCase())
.update(date)
.update(identify)
.digest('base64');
Based on the feedback from #PresidentJamesK.Polk I was able to figure this out.
const encoded = crypto.createHmac('sha1', key)
.update(METHOD.toLowerCase())
.update(SERVICE.toLowerCase())
.update(date)
.update(identify)
.digest('base64');

Generate encoded docket number from two integers and decode it

I am trying to generate encoded docket number from storeId and transactionId. Encoded docket number has to be unique, length should be <=9 and easy to read/copy for users as well.
The maximum length of storeId is 3 and maximum length of transactionId is 5.
How can I improve my code so that my docket number will be unbreakable?
Here is my code:
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
//reverse the ids and then add the respective key
var SID = storeId.toString().split("").reverse().join("");
SID = parseInt(SID) + myStoreKey;
var TID = transactionId.toString().split("").reverse().join("");
TID = parseInt(TID) + myTransKey;
var docketNum = `${SID}-${TID}`;
return docketNum;
}
function decodeShortCode(shortCode) {
shortCode = shortCode.split("-");
var storeID = shortCode[0];
var transactionID = shortCode[1];
//subtract the same key and then reverse the ids again
storeID = parseInt(storeID.toString()) - myStoreKey;
storeID = storeID.toString().split("").reverse().join("");
transactionID = parseInt(transactionID.toString()) - myTransKey;
transactionID = transactionID.toString().split("").reverse().join("");
return {
storeId: parseInt(storeID), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(transactionID) // transaction id goes here
};
}
Is there any better way to do this? I need to encode docket number in a way which will be really hard to decode by any third person.
Every encrypted message can be broken if an attacker tries every possible decryption key (this is called a brute-force attack). With modern computers, this is really easy to do. The way that you are encoding data is very easy to break (within seconds). However, there are encryption methods that take very long to break (like millions of years long).
One of the more popular encryption algorithms is AES. Because it is so popular, there are also many easy-to-use libraries for JavaScript. Here's an example with CryptoJS:
const KEY = "a super secret password";
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
const docketNum = `${storeId}-${transactionId}`;
return CryptoJS.AES.encrypt(docketNum, KEY).toString().replace("=", "");
}
function decodeShortCode(shortCode) {
const docketNum = CryptoJS.AES.decrypt(shortCode, KEY).toString(CryptoJS.enc.Utf8);
const parts = docketNum.split("-");
return {
storeId: parseInt(parts[0]), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(parts[1]) // transaction id goes here
};
}
const s1 = generateShortCode(myStoreKey, myTransKey);
console.log("Short Code: " + s1);
console.log("Decrypted Short Code:", decodeShortCode(s1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js" integrity="sha256-/H4YS+7aYb9kJ5OKhFYPUjSJdrtV6AeyJOtTkw6X72o=" crossorigin="anonymous"></script>
This shortcode is longer than 9 characters, but it very secure and nearly unbreakable. This is really just the tradeoff. If you reduce the length of the shortcode, then you won't be able to have a secure shortcode. Users can still easily copy and paste the code though. If you absolutely need a shorter cipher, then try looking at Skip32.
Be sure to change KEY to a secret key that isn't shared with anyone. Also, be sure not to run this code client-side. If the encryption key is sent to the client, then they could look at the JavaScript code and then be able to decrypt any message.
well this work for me with visual compser in Wordpress
/[[^[]vc[^]]]/ig

trying to decompress xref stream from pdf - getting "ERROR incorrect header check"

I am trying to parse the xref stream from PDF in JavaScript. I managed to succesfully isolate the stream itself (I checked that it's ok by comparing it in debugging mode with the value between steram. and endstream tags in PDF.
However, when I try to inflate it using pako lib, I get an error saying: ERROR incorrect header check.
The compression method is FlateDecode, which can be seen from the dictionary.
Here is the code in question:
const dict = pdfStr.slice(pdf.startXRef);
const xrefStreamStart = this.getSubstringIndex(dict, 'stream', 1) + 'stream'.length + 2;
const xrefStreamEnd = this.getSubstringIndex(dict, 'endstream', 1) + 1;
const xrefStream = dict.slice(xrefStreamStart, xrefStreamEnd);
const inflatedXrefStream = pako.inflate(this.str2ab(xrefStream), { to: 'string' });
pdfStr is the whole PDF read as a string, while *pdf.startXRef* holds the value of the position of the xref stream object.
Here's the whole PDF if someone wants to have a look: https://easyupload.io/lzf9he
EDIT: As mcernak has suggested I had a problem that I included /r and /n in the stream. However, now that I corrected the code I got a different error: invalid distance too far back
The stream content is located between stream\r\n and \r\nendstream.
You need to take into account those two additional characters (\r\n) at the beginning and at the end to read the correct data:
const dict = pdfStr.slice(pdf.startXRef);
const xrefStreamStart = this.getSubstringIndex(dict, 'stream', 1) + 'stream'.length + 2;
const xrefStreamEnd = this.getSubstringIndex(dict, 'endstream', 1) - 2;
const xrefStream = dict.slice(xrefStreamStart, xrefStreamEnd);
const inflatedXrefStream = pako.inflate(this.str2ab(xrefStream), { to: 'string' });

Python body.encode( ) Javascript alternative

I'm trying to verify a webhook coming from Plaid in NodeJS by calculating the Sha256 of the webhook body and I'm following a Python code here where the code is showing :
# Compute the has of the body.
m = hashlib.sha256()
m.update(body.encode())
body_hash = m.hexdigest()
What's the alternative of body.encode() in Javascript before passing it to the Sha256 function please ? Note that the body I'm getting is an object containing the following data :
{ error: null, item_id: '4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG',
new_transactions: 0, webhook_code: 'DEFAULT_UPDATE', webhook_type:
'TRANSACTIONS' }
However I'm trying to get this hash :
b05ef560b59e8d8e427433c5e0f6a11579b5dfe6534257558b896f858007385a
So, if the body is JSON (NOT JSON STRING) then you need to stringify it and put it in the .update function As the m.body takes a string. If you have your body as STRING then just put it in as is.
This is from the Crypto Example here:
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
const stringBody = JSON.stringify(body);
hash.update(stringBody);
console.log(hash.digest('hex'));
Edit:
If the hash is not same then maybe you need to correct the newlines or whitespaces. You need to make both bodies exactly the same. Here In the below example I am using same exact string and encoding using Python AND NodeJS.
import hashlib
body = '{"error":null,"item_id":"4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG","new_transactions":0,"webhook_code":"DEFAULT_UPDATE","webhook_type":"TRANSACTIONS"}'
m = hashlib.sha256()
m.update(body.encode())
body_hash = m.hexdigest()
print(body_hash)
Output:
python3 file.py
26f1120ccaf99a383b7462b233e18994d0c06d4585e3fe9a91a449e97a1c03ba
And Using NodeJS:
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
const body = {
error: null,
item_id: '4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG',
new_transactions: 0,
webhook_code: 'DEFAULT_UPDATE',
webhook_type: 'TRANSACTIONS'
}
const stringBody = JSON.stringify(body);
hash.update(stringBody);
console.log(hash.digest('hex'));
Output:
node file.js
26f1120ccaf99a383b7462b233e18994d0c06d4585e3fe9a91a449e97a1c03ba

Categories

Resources