Аутентификация HMAC для исходящих вебхуков MS Teams

Вопрос или проблема

Я пытаюсь настроить исходящий веб-хук для MS Teams с помощью golang и Google App Engine (развернутого с помощью gcloud app deploy):

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strings"
)

var secret string = "THIS_IS_A_SECRET"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Точка входа веб-хука вызвана")
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Не удается прочитать тело запроса", http.StatusBadRequest)
        return
    }
    fmt.Println("Получен запрос веб-хука:", string(body))

    // Логируем тело запроса для отладки
    fmt.Printf("Тело запроса: %s\n", string(body))

    // Генерируем HMAC токен из тела запроса
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expectedMAC := mac.Sum(nil)
    expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMAC)

    // Получаем HMAC токен из заголовка запроса
    authHeader := r.Header.Get("Authorization")
    if !strings.HasPrefix(authHeader, "HMAC ") {
        fmt.Println("Недействительный заголовок авторизации")
        http.Error(w, "Недействительный заголовок авторизации", http.StatusUnauthorized)
        return
    }
    providedMACBase64 := strings.TrimPrefix(authHeader, "HMAC ")

    // Сравниваем сгенерированный HMAC токен с предоставленным
    if !hmac.Equal([]byte(providedMACBase64), []byte(expectedMACBase64)) {
        fmt.Println("Недействительный HMAC токен")
        fmt.Println("Ожидаемый HMAC токен:", expectedMACBase64)
        fmt.Println("Предоставленный HMAC токен:", providedMACBase64)
        http.Error(w, "Недействительный HMAC токен", http.StatusUnauthorized)
        return
    } else {
        fmt.Println("Аутентификация: действительный HMAC токен")
    }

    // Создаем ответ в формате, ожидаемом Microsoft Teams
    response := map[string]string{
        "type": "message",
        "text": "Веб-хук успешно получен",
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)

    json.NewEncoder(w).Encode(response)
}

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

Тем не менее, ожидаемый HMAC и предоставленный HMAC всегда разные. Мне кажется, что что-то не так с моим алгоритмом HMAC. Кто-нибудь знает, в чем здесь проблема?

Для справки: я следую инструкции на https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-outgoing-webhook?tabs=verifyhmactoken%2Cdotnet

Хорошо… оказывается, я забыл декодировать ключ, закодированный в Base64:

[]byte(secret) > base64.StdEncoding.DecodeString(secret)

Ответ или решение

Конечно! Давайте разберёмся с проблемой, с которой вы столкнулись при настройке HMAC-аутентификации для исходящего вебхука Microsoft Teams.

Проблема

Вы правильно заметили, что ожидаемый HMAC и предоставленный HMAC всегда различаются. Как выяснилось, это связано с тем, что вы не декодировали ключ, закодированный в Base64, перед использованием его в вычислении HMAC.

Решение

Для корректного вычисления HMAC вы должны декодировать секрет с помощью base64.StdEncoding.DecodeString(secret), прежде чем использовать его при создании HMAC. Давайте обновим ваш код соответствующим образом:

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strings"
)

var secret string = "ВАШ_BASE64_ЗАШИФРОВАННЫЙ_КЛЮЧ"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Webhook endpoint hit")
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Can't read request body", http.StatusBadRequest)
        return
    }
    fmt.Println("Received webhook request:", string(body))

    // Генерация HMAC токена из тела запроса
    decodedSecret, err := base64.StdEncoding.DecodeString(secret)
    if err != nil {
        http.Error(w, "Invalid secret key", http.StatusInternalServerError)
        return
    }

    mac := hmac.New(sha256.New, decodedSecret)
    mac.Write(body)
    expectedMAC := mac.Sum(nil)
    expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMAC)

    // Получение HMAC токена из заголовка запроса
    authHeader := r.Header.Get("Authorization")
    if !strings.HasPrefix(authHeader, "HMAC ") {
        fmt.Println("Invalid Authorization header")
        http.Error(w, "Invalid Authorization header", http.StatusUnauthorized)
        return
    }
    providedMACBase64 := strings.TrimPrefix(authHeader, "HMAC ")

    // Сравнение сгенерированного HMAC токена с предоставленным
    if !hmac.Equal([]byte(providedMACBase64), []byte(expectedMACBase64)) {
        fmt.Println("Invalid HMAC token")
        fmt.Println("Expected HMAC token:", expectedMACBase64)
        fmt.Println("Provided HMAC token:", providedMACBase64)
        http.Error(w, "Invalid HMAC token", http.StatusUnauthorized)
        return
    } else {
        fmt.Println("Authenticated: Valid HMAC token")
    }

    // Создание ответа в формате, ожидаемом Microsoft Teams
    response := map[string]string{
        "type": "message",
        "text": "Webhook received successfully",
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)

    json.NewEncoder(w).Encode(response)
}

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

Описание изменений

  1. Декодирование секрета: Я добавил строку, которая декодирует ваш секрет с использованием базы64 перед тем, как использовать его для вычисления HMAC.
  2. Обработка ошибок: Добавлен блок для обработки возможной ошибки при декодировании секрета.

Заключение

Теперь ваш код корректно обрабатывает HMAC-аутентификацию. Убедитесь, что значение переменной secret содержит правильный Base64-кодированный ключ. Запустите ваше приложение и протестируйте интеграцию с Microsoft Teams, чтобы убедиться, что все работает как положено. Если возникнут дополнительные вопросы, не стесняйтесь обращаться!

Оцените материал
Добавить комментарий

Капча загружается...