Hey guys!
I’m trying to write a facebook data deletion callback and I have this problem of generating hmacs using the crypto module… I know already about the limitations but facebook is using base64 and a weird pattern I just can’t figure out, could use some help…
Trying to convert this:
const expectedSig = crypto.createHmac('sha256', config.facebookAppSecret).update(payload).digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace('=', '');
To this:
const expectedSig = await crypto.subtle.importKey('raw', config.facebookAppSecret, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']).digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace('=', '');
But I’m not even close to getting it to work
I also tried this:
const verified = await crypto.subtle.verify(
'HMAC',
expectedSig,
data,
encoder.encode(dataToAuthenticate)
);
and I also have this:
const encoder = new TextEncoder();
Facebook gave it for me in PHP so I’ve been converting it to js for quite a while:
<?php
header('Content-Type: application/json');
$signed_request = $_POST['signed_request'];
$data = parse_signed_request($signed_request);
$user_id = $data['user_id'];
// Start data deletion
$status_url = 'https://www.<your_website>.com/deletion?id=abc123'; // URL to track the deletion
$confirmation_code = 'abc123'; // unique code for the deletion request
$data = array(
'url' => $status_url,
'confirmation_code' => $confirmation_code
);
echo json_encode($data);
function parse_signed_request($signed_request) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
$secret = "appsecret"; // Use your app secret here
// decode the data
$sig = base64_url_decode($encoded_sig);
$data = json_decode(base64_url_decode($payload), true);
// confirm the signature
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
if ($sig !== $expected_sig) {
error_log('Bad Signed JSON signature!');
return null;
}
return $data;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
?>
I ended up with this:
// NodeJS:
function parse_signed_request(signed_request, secret) {
encoded_data = signed_request.split('.',2);
// decode the data
sig = encoded_data[0];
json = base64url.decode(encoded_data[1]);
data = JSON.parse(json); // ERROR Occurs Here!
// check algorithm - not relevant to error
if (!data.algorithm || data.algorithm.toUpperCase() != 'HMAC-SHA256') {
console.error('Unknown algorithm. Expected HMAC-SHA256');
return null;
}
// check sig - not relevant to error
expected_sig = crypto.createHmac('sha256',secret).update(encoded_data[1]).digest('base64').replace(/\+/g,'-').replace(/\//g,'_').replace('=','');
if (sig !== expected_sig) {
console.error('Bad signed JSON Signature!');
return null;
}
return data;
}
but that didn’t work in Cloudflare so I also tried this:
// No base64
// Fixed an error with character replacement, removed a dependency, included imports, throw errors, and fixed global variables
var crypto = require('crypto');
//remove a dependency on b64url
function atob(str) {
return new Buffer(str, 'base64').toString('binary');
}
//this is not used here, but to leave it out would be like passing the salt without the pepper.
function btoa(str) {
return new Buffer(str, 'utf8').toString('base64');
}
function parseSignedRequest(signedRequest, secret) {
var encodedData = signedRequest.split('.');
// decode the data
var sig = encodedData[0];
var json = atob(encodedData[1]);
var data = JSON.parse(json); // ERROR Occurs Here!
// check algorithm - not relevant to error
if (!data.algorithm || data.algorithm.toUpperCase() != 'HMAC-SHA256') {
throw Error('Unknown algorithm: ' + data.algorithm + '. Expected HMAC-SHA256');
}
// check sig - not relevant to error
var expectedSig = crypto.createHmac('sha256', secret).update(encodedData[1]).digest('base64').replace(/\+/g,'-').replace(/\//g,'_').replace('=','');
if (sig !== expectedSig) {
throw Error('Invalid signature: ' + sig + '. Expected ' + expectedSig);
}
return data;
}
but yeah this didn’t work either…
So I’m currently with this:
import config from './config';
const encoder = new TextEncoder();
// SHA-512 Hashing Algorithm
async function hash(inputText, usePrivateSalt = true, algorithm = 'SHA-512') {
const myText = encoder.encode(usePrivateSalt ? config.privateSalt + inputText + config.privateSalt : inputText);
const myDigest = await crypto.subtle.digest({ name: algorithm }, myText);
const myUint8Array = new Uint8Array(myDigest);
return myUint8Array.toString();
}
// Decodes a String
function atob(str) {
return new Buffer.from(str, 'base64').toString('binary');
}
// Encodes a String
function btoa(str) {
return new Buffer.from(str, 'utf8').toString('base64');
}
// Parse Facebook Request
async function parseSignedRequest(signedRequest) {
const encodedData = signedRequest.split('.');
const signature = encodedData[0];
const payload = encodedData[1];
const data = JSON.parse(atob(payload));
if (!data.algorithm || data.algorithm.toLowerCase() != 'hmac-sha256') return false; // Unknown algorithm. Expected HMAC-SHA256
//const expectedSig = crypto.createHmac('sha256', config.facebookAppSecret).update(payload).digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace('=', '');
const expectedSig = await crypto.subtle.importKey('raw', config.facebookAppSecret, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']).digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace('=', '');
/*const verified = await crypto.subtle.verify(
'HMAC',
key,
receivedMac,
encoder.encode(dataToAuthenticate)
);*/
if (signature !== expectedSig) return false; // Bad signed JSON Signature!
return data;
}
export default {
hash,
atob,
btoa,
parseSignedRequest
};
console.log('Initialized tools.js');
if anybody knows anything I will be super thankful. thank you for readin.