7

I am trying to use Dropbox's API, and I got it to successfully send me alerts via webhooks, but now I want to verify the signatures every time they send me an alert.

From dropbox's documentation, they write:

"Every notification request will include a header called X-Dropbox-Signature that includes an HMAC-SHA256 signature of the request body, using your app secret as the signing key. This lets your app verify that the notification really came from Dropbox."

So I successfully catch that signature, and I use NodeJS built in crypto module to try to create my own signature with HMAC SHA256 and then compare my signature against the signature Dropbox sends me.

Here is my code for doing so:

  var sign = req.get("X-Dropbox-Signature");
  console.log(sign);
  var hmac = crypto.createHmac(algorithm, secret);
  hmac.update(JSON.stringify(req.body));
  hash = hmac.digest('hex');
  console.log(hash);

Where algorithm is just 'sha256' and secret is my secret key that I got from my dropbox apps page. I have to use JSON.stringify(req.body) because req.body is an object and hmac.update takes a string. I am wondering if that is where my error comes from?

I console log the sign which is the signature from dropbox, and then I console log the signature which I created using hmac, but it is a different signature.

Any suggestions to what I may be doing wrong?

skob002
  • 91
  • 1
  • 1
  • 3
  • 2
    At a glance, `JSON.stringify(req.body)` looks suspicious. You need to take the HMAC of the raw request body, so if `req.body` is an `Object`, that's probably not the right value. I recommend checking the documentation for the web framework to see how you can retrieve just the original/raw request body instead. – Greg Feb 18 '18 at 23:21

2 Answers2

5

Greg is right. You need to use the raw body request to check the ingredients of the message. The following code employs the body-parser library to extract the raw body.

var bodyParser = require("body-parser");

app.use(bodyParser.json({verify:function(req,res,buf){req.rawBody=buf}}))

Then for the post method:

app.post('/webhooks', function(req, res) {

    const retrievedSignature = req.get("X-header-Integrity")
    //send this body string for validation with secret
    const bodyString = Buffer.from(req.rawBody, 'utf8')

    let check = integrityCheck(retrievedSignature, bodyString, "secret")

});
Andrew Dibble
  • 782
  • 6
  • 15
Syd Gillani
  • 159
  • 2
  • 5
  • That's great but the question didn't mention the use of an Express server... can you add an example without using an Express server that doesn't have access to the raw body? – MacK Dec 10 '19 at 15:01
  • 1
    I've added an example using Node's native HTTP server, but I'm not sure how the server wouldn't have access to the raw request body. – Andrew Dibble Feb 24 '20 at 14:42
2

The following snippet is a basic example for calculating an HMAC of a request body using Node's native HTTP server. The same concepts can be very easily (and more cleanly) applied in an Express middleware.

const http = require('http');
const crypto = require('crypto');

const APP_SECRET = 'prozr59vkis4454';
const REQUEST_BODY = '{"list_folder": {"accounts": ["dbid:AABL4QRrY7tB9viLgPUqmjkzE6Fe5ujlnlE"]}, "delta": {"users": [22575230]}}';
const SIGNATURE = 'aa2508fb90b757aa382edb0815c7f7df0ce1943c53f28fae96e1dc9eb7f677b1';

const server = http
  .createServer((req, res) => {
    const hmac = crypto.createHmac('sha256', APP_SECRET).setEncoding('hex');
    req
      .pipe(hmac)
      .on('finish', () => {
        console.log('signature from header:', req.headers['x-dropbox-signature']);
        console.log('calculated signature: ', hmac.read());
        res.writeHead(204);
        res.end();
      });
  })
  .listen(1313, () => {
    const request = http.request({
      host: 'localhost',
      port: 1313,
      method: 'POST',
    }, () => {
      server.close();
    });

    request.setHeader('x-dropbox-signature', SIGNATURE);

    request.write(REQUEST_BODY);
    request.end();
  });
Andrew Dibble
  • 782
  • 6
  • 15