El secreto de GitHub Webhook nunca valida

Estoy usando un webhook de GitHub para canalizar events a una aplicación mía (una instancia del Hubot de GitHub) y está protegido con un secreto sha1.

Estoy usando el siguiente código para validar hash en webhooks entrantes

crypto = require('crypto') signature = "sha1=" + crypto.createHmac('sha1', process.env.HUBOT_GITHUB_SECRET).update( new Buffer request.body ).digest('hex') unless request.headers['x-hub-signature'] is signature response.send "Signature not valid" return 

El encabezado X-Hub-Signature que se transmite en el webhook se parece a esto

Firma X-Hub: sha1 = 1cffc5d4c77a3f696ecd9c19dbc2575d22ffebd4

Estoy pasando la key y los datos con precisión según la documentation de GitHub, pero el hash siempre termina diferente.

Aquí está la documentation de GitHub. https://developer.github.com/v3/repos/hooks/#example

y esta es la sección que probablemente estoy malinterpretando

secreto: una cadena opcional que se pasa con las requestes HTTP como encabezado X-Hub-Signature. El valor de este encabezado se calcula como el resumen hexadecimal HMAC del cuerpo, utilizando el secreto como la key.

¿Alguien puede ver dónde me estoy equivocando?

Parece que no funciona con un Buffer, pero JSON.stringify (); Aquí está mi código de trabajo:

 var hmac, calculatedSignature, payload = req.body; hmac = crypto.createHmac('sha1', config.github.secret); hmac.update(JSON.stringify(payload)); calculatedSignature = 'sha1=' + hmac.digest('hex'); if (req.headers['x-hub-signature'] === calculatedSignature) { console.log('all good'); } else { console.log('not good'); } 

Añadiendo a la respuesta de Patrick . Es bueno usar crypo.timingSafeEqual para comparar resúmenes de HMAC o valores secretos. Así es cómo:

 const blob = JSON.stringify(req.body); const hmac = crypto.createHmac('sha1', process.env.GITHUB_WEBHOOK_SECRET); const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`; const theirSignature = req.get('X-Hub-Signature'); const bufferA = Buffer.from(ourSignature, 'utf8'); const bufferB = Buffer.from(theirSignature, 'utf8'); const safe = crypto.timingSafeEqual(bufferA, bufferB); if (safe) { console.log('Valid signature'); } else { console.log('Invalid signature'); } 

Para saber más sobre la diferencia entre una comparación segura como timingEqual y una simple ===, consulte este hilo aquí .

crypto.timingSafeEqual fue agregado en Node.js v6.6.0

También agregando a la respuesta de Patrick, recomiendo usar Express junto con su analizador corporal. Complete el ejemplo a continuación. Esto funciona con Express 4.x, Node 8.x (lo más reciente hasta la date).

Reemplace YOUR_WEBHOOK_SECRET_HERE y haga algo en la function de authorizationSuccessful YOUR_WEBHOOK_SECRET_HERE .

 // Imports const express = require('express'); const bodyParser = require('body-parser'); const crypto = require('crypto'); const app = express(); // The GitHub webhook MUST be configunetworking to be sent as "application/json" app.use(bodyParser.json()); // Verification function to check if it is actually GitHub who is POSTing here const verifyGitHub = (req) => { if (!req.headers['user-agent'].includes('GitHub-Hookshot')) { return false; } // Compare their hmac signature to our hmac signature // (hmac = hash-based message authentication code) const theirSignature = req.headers['x-hub-signature']; const payload = JSON.stringify(req.body); const secret = 'YOUR_WEBHOOK_SECRET_HERE'; // TODO: Replace me const ourSignature = `sha1=${crypto.createHmac('sha1', secret).update(payload).digest('hex')}`; return crypto.timingSafeEqual(Buffer.from(theirSignature), Buffer.from(ourSignature)); }; const notAuthorized = (req, res) => { console.log('Someone who is NOT GitHub is calling, networkingirect them'); res.networkingirect(301, '/'); // Redirect to domain root }; const authorizationSuccessful = () => { console.log('GitHub is calling, do something here'); // TODO: Do something here }; app.post('*', (req, res) => { if (verifyGitHub(req)) { // GitHub calling authorizationSuccessful(); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Thanks GitHub <3'); } else { // Someone else calling notAuthorized(req, res); } }); app.all('*', notAuthorized); // Only webhook requests allowed at this address app.listen(3000); console.log('Webhook service running at http://localhost:3000');