Как использовать Buffer.from() с crypto.timingSafeEqual()?

#javascript #node.js #ecmascript-6

#javascript #node.js #ecmascript-6

Вопрос:

По какой-то причине я получаю

 TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined
 

из обоих аргументов в crypto.timingSafeEqual(a, b) .

Я тоже пробовал

 const a = Buffer.from(signature, 'utf8').toString('base64');
const b = Buffer.from(expectedSignature, 'utf8').toString('base64');
 

и я получаю ту же ошибку.

Вопрос

Кто-нибудь может понять, почему аргументы не являются буферами?

 const express = require("express");
const bodyParser = require("body-parser");
const crypto = require('crypto');
const secret = "x";

const app = express();
const PORT = 8080;

app.use(bodyParser.json());

function isSigOk(request, secret) {
    // calculate the signature
    const expectedSignature = "sha256="  
        crypto.createHmac("sha256", secret)
            .update(JSON.stringify(request.body))
            .digest("hex");

    // compare the signature against the one in the request
    const signature = request.headers["X-Hub-Signature-256"];
    const a = Buffer.from(signature);
    const b = Buffer.from(expectedSignature);
    return crypto.timingSafeEqual(a, b);
};

app.post("/", (req, res) => {
  if (isSigOk(req, secret)) {
    // Do stuff here
  } else {
    console.log('Error: Signatures does not match. Return res.status(401)');
  };
  res.status(200).end();
});

// Start express on the defined port
app.listen(PORT, () => console.log(`Github wekhook listening on port ${PORT}`));
 

Комментарии:

1. Я не думаю, что ошибка из timingSafeEqual , я думаю, что это из Buffer.from . (Вы получите точно такую же ошибку, если вы это сделаете Buffer.from(undefined) .) Это говорит мне, что request.headers["X-Hub-Signature-256"] это undefined так.

2. @T.J.Crowder Когда я console.log это const signature = request.headers["x-hub-signature-256"]; получаю sha256=01df1ffc00107ab2e8782ba7983bb7245df79b6b414d4e89f8357144d61613cb , значит, я получаю данные?

Ответ №1:

Я вижу две проблемы:

  1. Первый и основной заключается в том, что isSigOk предполагается, что для заголовка будет значение "X-Hub-Signature-256" :
     const signature = request.headers["X-Hub-Signature-256"];
    const a = Buffer.from(signature);
     

    Этот Buffer.from вызов выдаст ошибку, которую вы указали, signature undefined потому что заголовка там нет. Вероятно, вы захотите вернуться false в этом случае (и, вероятно, пропустить накладные расходы на разработку ожидаемой подписи, немного изменив порядок вещей), см. *** Комментарии и связанные строки:

     function isSigOk(request, secret) {
        // *** get the signature on this message, if any
        const signature = request.headers["X-Hub-Signature-256"];
        if (!signature) {
            // *** none
            return false;
        }
        // calculate the signature
        const expectedSignature = "sha256="  
            crypto.createHmac("sha256", secret)
                .update(JSON.stringify(request.body))
                .digest("hex");
    
        // compare the signature against the one in the request
        const a = Buffer.from(signature);
        const b = Buffer.from(expectedSignature);
        return crypto.timingSafeEqual(a, b);
    };
     
  2. Значение заглавных букв имеет значение. Согласно Node.js документация ( Requset объект Express наследуется от Node.js ‘s IncomingMessage ), названия заголовков в headers строчных буквах. Так request.headers["X-Hub-Signature-256"] и должно быть request.headers["x-hub-signature-256"] . (В комментарии вы говорите, что получаете значение, но в комментарии используются все строчные буквы, тогда как в коде используется смешанный регистр.) Итак:
     function isSigOk(request, secret) {
        // *** get the signature on this message, if any
        const signature = request.headers["x-hub-signature-256"]; // *** Lowercase
        if (!signature) {
            // *** none
            return false;
        }
        // calculate the signature
        const expectedSignature = "sha256="  
            crypto.createHmac("sha256", secret)
                .update(JSON.stringify(request.body))
                .digest("hex");
    
        // compare the signature against the one in the request
        const a = Buffer.from(signature);
        const b = Buffer.from(expectedSignature);
        return a.length === b.length amp;amp; crypto.timingSafeEqual(a, b);
    };
     

    Обратите внимание на эту a.length === b.length amp;amp; часть. timingSafeEqual выдаст ошибку, если буферы разной длины, но вместо этого мы хотим вернуть false в этой ситуации.

Комментарии:

1. Лучше сравните байтовую длину в возвращаемом like return Buffer.byteLength(a) === Buffer.byteLength(b) amp;amp; crypto.timingSafeEqual(a, b); , иначе это может привести RangeError к появлению nodejs, если два буфера имеют разную длину.

2. @ikhvjs — Хороший улов, спасибо! Я удивлен false , что в этом случае он не просто возвращается, но документы подтверждают то, что вы сказали: он выдает ошибку. 🙂

3. Спасибо — это очень четкий ответ и отличный код.