Webhooks. Проверка достоверности хука чат-бота

Webhooks. Проверка достоверности хука чат-бота

Чтобы подтвердить, что хук отправлен из чат-бота в заголовок HTTP запроса встроен код для проверки.

При создании нового вебхука можно задать поле "секретная фраза" (Чат-бот-> Настройки). Если для вебхука был задан секрет, то POST-запрос вебхука будет содержать подпись в заголовке X-MtsLink-Signature. Подпись представляет собой код аутентификации (проверки подлинности) сообщения на основе хэша (HMAC) c алгоритмом SHA-256.

Для определения подлинности вебхука необходимо выполнить следующие действия:

  1. Извлечь подпись из заголовка запроса.
  2. Определить ожидаемую подпись для тела запроса, преобразовав его в JSON и вычислив HMAC SHA-256.
  3. Сравнить подпись из заголовка запроса с ожидаемой подписью .


Пример кода

PHP

<?php

function verifySignature($payload, $headerSignature, $secretKey) {
$expectedSignature = hash_hmac('sha256', $payload, $secretKey);
return hash_equals($expectedSignature, $headerSignature);
}

$headers = getallheaders();
$headerSignature = $headers['X-MtsLink-Signature'] ?? '';
$payload = file_get_contents("php://input");
$secretKey = 'SuperSecret';
if (verifySignature($payload, $headerSignature, $secretKey)) {
echo "Webhook signature is valid";
} else {
echo "Invalid webhook signature";
}

Go

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
)

func verifySignature(payload []byte, headerSignature string, secretKey string) bool {
h := hmac.New(sha256.New, []byte(secretKey))
h.Write(payload)
expectedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(expectedSignature), []byte(headerSignature))
}

func handler(w http.ResponseWriter, r *http.Request) {
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Unable to read request body", http.StatusBadRequest)
return
}

headerSignature := r.Header.Get("X-MtsLink-Signature")
secretKey := "SuperSecret"

if verifySignature(payload, headerSignature, secretKey) {
fmt.Fprintln(w, "Webhook signature is valid")
} else {
http.Error(w, "Invalid webhook signature", http.StatusUnauthorized)
}
}

func main() {
http.HandleFunc("/webhook", handler)
http.ListenAndServe(":8080", nil)
}

Ruby

def verify_signature(payload_body)
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Invalid webhook signature" unless Rack::Utils.secure_compare(signature, request.env['X-MtsLink-Signature'])
end

post '/webhook' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
"I got some JSON: #{push.inspect}"
end

JavaScript

let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
let parts = header.split("=");
let sigHex = parts[1];

let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

let keyBytes = encoder.encode(secret);
let extractable = false;
let key = await crypto.subtle.importKey(
"raw",
keyBytes,
algorithm,
extractable,
[ "sign", "verify" ],
);

let sigBytes = hexToBytes(sigHex);
let dataBytes = encoder.encode(payload);
let equal = await crypto.subtle.verify(
algorithm.name,
key,
sigBytes,
dataBytes,
);

return equal;
}

function hexToBytes(hex) {
let len = hex.length / 2;
let bytes = new Uint8Array(len);

let index = 0;
for (let i = 0; i < hex.length; i += 2) {
let c = hex.slice(i, i + 2);
let b = parseInt(c, 16);
bytes[index] = b;
index += 1;
}

return bytes;
}

TypeScript

import { Webhooks } from "@octokit/webhooks";

const webhooks = new Webhooks({
secret: process.env.WEBHOOK_SECRET,
});

const handleWebhook = async (req, res) => {
const signature = req.headers["X-MtsLink-Signature"];
const body = await req.text();

if (!(await webhooks.verify(body, signature))) {
res.status(403).send("Forbidden");
return;
}

// Остальная логика здесь
};

Python

import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
"""Убедитесь, что вебхук был отправлен с МТС.Линк, выполнив проверку SHA256.

В случае неудачной проверки возвращается ошибка 403.

Аргументы:
payload_body: исходное тело запроса для проверки (request.body())
secret_token: секрет вебхука (SECRET_TOKEN)
signature_header: заголовок с подписью (X-MtsLink-Signature)
"""
if not signature_header:
raise HTTPException(status_code=403, detail="X-MtsLink-Signature header is missing!")
hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
expected_signature = hash_object.hexdigest()
if not hmac.compare_digest(expected_signature, signature_header):
raise HTTPException(status_code=403, detail="Invalid webhook signature")
👆 На этом пока всё