Тест вебхука проверяет подпись после разбора и повторной сериализации JSON. Какой дефект такая проверка может скрыть?
Такая проверка может скрыть ошибку верификации подписи по исходным байтам запроса. После разбора и повторной сериализации изменяются пробелы, порядок полей, представление чисел или экранирование, поэтому тест способен пройти, хотя реальный обработчик отклонит корректно подписанный запрос или, что хуже, проверит не те данные.
Подписи вебхуков появились как способ подтвердить подлинность отправителя и целостность сообщения, передаваемого через недоверенную сеть. Получателю нужно проверить не только структуру JSON, но и то, что тело не изменилось после формирования подписи.
Криптографическая подпись обычно вычисляется над точной последовательностью байтов тела. Поэтому проверка должна соответствовать фактическому протоколу поставщика: какие байты подписываются, какой алгоритм используется и из каких заголовков берутся дополнительные параметры.
JSON допускает несколько текстовых представлений одного смыслового объекта. В них могут отличаться пробелы, порядок полей, экранирование символов и формат чисел, хотя после разбора получается одинаковая структура.
Если тест сначала разбирает тело, затем сериализует его заново и только после этого проверяет подпись, он проверяет искусственно нормализованное представление. В рабочем запросе подпись может быть рассчитана по исходным байтам, поэтому такой тест не обнаружит несовместимость между тестовым кодом и реальным механизмом проверки.
Последствие — ложноположительный тест: интеграция считается рабочей, но запросы от поставщика отвергаются в production. Обратная ошибка также опасна: если приложение проверяет подпись не над исходным телом, а над изменённым представлением, границы защищаемого сообщения становятся неочевидными.
Тест должен передавать обработчику исходное тело как последовательность байтов и проверять подпись до разбора JSON. После успешной проверки тело можно разобрать и валидировать по схеме. Отдельно следует проверять неверную подпись, изменение одного байта и отсутствие обязательных заголовков.
Важно не путать два разных протокола. Если поставщик явно определил каноникализацию JSON, например специальное однозначное представление перед подписанием, тест должен воспроизводить именно эту каноникализацию. Самостоятельная повторная сериализация без такого соглашения не является эквивалентной проверкой.
Мок поставщика не должен автоматически пересчитывать подпись после изменения тела: иначе он может скрыть ошибку в формировании запроса. Полезно иметь фиксированные тестовые векторы — тело, заголовки и заранее известную подпись, подготовленные независимо от тестируемого кода.
Проверка подписи подтверждает целостность и знание секрета, но не заменяет проверку схемы, размера тела и бизнес-ограничений. Защита от повторной доставки требует отдельного контроля свежести, например временной метки или идентификатора события с политикой хранения обработанных идентификаторов.
Поставщик отправлял JSON с подписью над исходным телом. В тестах тело преобразовывали в объект и сериализовали компактно, после чего вычисляли ожидаемую подпись. Все проверки проходили, но в рабочей среде запросы с пробелами и другим порядком полей иногда отклонялись.
Рассматривались три варианта. Проверять только разобранный объект было проще, но это не тестировало криптографическую границу. Каноникализация тела делала бы представление однозначным, однако потребовала бы согласованного протокола с поставщиком. Проверять исходные байты было немного сложнее из-за необходимости сохранить тело до разбора, зато этот вариант точно соответствовал контракту.
Выбрали третий вариант: тестовый транспорт сохранял исходные байты, валидатор проверял подпись до разбора, а затем отдельные проверки контролировали JSON-схему. После этого тесты начали выявлять изменения форматирования и подмену тела до того, как проблема доходила до production.
Почему изменение только пробелов в JSON должно влиять на результат проверки подписи?
Если протокол подписывает исходные байты, пробелы являются частью подписываемого сообщения. Поэтому тело с теми же смысловыми данными, но другой последовательностью байтов, должно иметь другую подпись и не должно приниматься со старой подписью. Это свойство целостности сообщения, а не особенность JSON-парсера.
Достаточно ли проверить подпись, чтобы считать вебхук безопасным?
Нет. Подпись не обязательно защищает от повторной отправки ранее корректного запроса. Нужны дополнительные правила свежести: проверка временной метки, уникального идентификатора события или одноразового значения, а также политика хранения уже обработанных идентификаторов.
Почему нельзя строить ожидаемую подпись тем же кодом, который тестируется?
Общая реализация может воспроизвести одну и ту же ошибку в production-коде и тесте. Например, оба компонента могут подписывать повторно сериализованный JSON вместо исходных байтов. Независимый фиксированный тестовый вектор или отдельная эталонная реализация проверяет результат с другой стороны и снижает риск такого совпадения ошибок.