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

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

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

При создании нового вебхука можно задать поле "секретная фраза". Если для вебхука был задан секрет, то POST-запрос вебхука будет содержать подпись в заголовке X-Webhook-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-Webhook-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-Webhook-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-Webhook-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-Webhook-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-Webhook-Signature)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="X-Webhook-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")
👆 На этом пока всё