Підпис і верифікація запитів
Підпис запиту доводить справжність відправника і цілісність вмісту. На відміну від TLS (який захищає канал), прикладний підпис робить кожне повідомлення перевіряється і стійким до проксі, кешу і відкладеної доставки.
Цілі:1. Автентичність (хто відправив) і цілісність (не змінювалося).
2. Неповторюваність (захист від реплеїв).
3. Відв'язка від транспорту (працює поверх HTTP, черг, вебхуків).
4. Аудиторійність (відтворювана перевірка через місяці).
1) Модель загроз (мінімум)
Підміна тіла/заголовків на шляху прямування.
Реплей (повтор легітимного запиту).
Downgrade/strip заголовків підпису.
Крадіжка секретів інтеграції.
Несинхронний годинник (clock skew) і довгі черги.
2) Вибір примітиву
HMAC (симетрія): простий і швидкий, ключ зберігається в обох сторін. Підходить для B2B-вебхуків і внутрішніх API.
RSA/ECDSA (асиметрія): ключ приватний у відправника, публічний у одержувача. Підходить для відкритих інтеграцій і коли важливо не ділитися секретом.
mTLS: взаємна автентифікація на транспортному рівні; часто комбінується з НМАС/підписом тіла.
JWT/JWS: зручно для bearer-токенів і самодостатніх клеймів; для підпису тіла краще використовувати канонікалізацію + JWS Detached/HTTP Message Signatures.
HTTP Message Signatures (підпис вибраних частин запиту): сучасний підхід для REST.
Рекомендація: для вебхуків - HMAC + timestamp + nonce + канонікалізація тіла; для публічного API - HTTP Message Signatures або JWS; при високих ризиках - додайте mTLS.
3) Канонікалізація (що саме підписуємо)
Підписувати потрібно детермінований рядок, однаково відновлюваний обома сторонами.
Референсний склад:
method \n path_with_query_normalized \n content-type \n digest: SHA-256=BASE64(SHA256(body)) \n x-ts: <unix iso> \n x-nonce: <uuid> \n host \n x-tenant: <tenant_id> \n
Підсумковий рядок:
canonical = join("\n", fields)
signature = HMAC(secret, canonical) # или ECDSA_sign(private_key, canonical)
Правила:
- Нормалізуйте шлях і порядок query-параметрів.
- Пробіли/юнікод/регістр - фіксуйте (наприклад, lower-case заголовків, trim).
- Великі тіла - хешуйте (Digest), а не вмикайте «як є».
4) Формат заголовків
Приклад для HMAC:
X-Signature-Alg: hmac-sha256
X-Signature: v1=hex(hmac),ts=1730379005,nonce=550e8400-e29b-41d4-a716-446655440000,kid=prov_42
Digest: SHA-256=BASE64(SHA256(body))
X-Tenant: brand_eu
Приклад для асиметрії (ECDSA P-256):
Signature: keyId="prov_42", alg="ecdsa-p256-sha256",
ts="2025-10-31T12:30:05Z", nonce="550e...", headers="(request-target) host digest x-tenant",
sig="BASE64(raw_signature)"
Де'kid '/' keyId'дозволяє вибрати ключ з реєстру (див. ротацію).
5) Верифікація на приймальній стороні
Псевдокод:python def verify(request):
1) Basic assert abs (now () - request. ts) <= ALLOWED_SKEW # напр., 300 с assert not replayed(request. nonce, window = TTL) # store nonce/ts in KV
2) Restore canonical canonical = build_canonical (
method=request. method,
path=normalize_path(request. path, request. query),
content_type=request. headers["content-type"],
digest=hash_body(request. body),
ts=request. ts,
nonce=request. nonce,
host=request. headers["host"],
tenant=request. headers. get("x-tenant")
)
3) Get the key key = key_registry. get(request. kid) # secret (HMAC) или public key (ECDSA)
4) Verify if request signature. alg. startswith("hmac"):
ok = hmac_compare(key. secret, canonical, request. signature)
else:
ok = asym_verify(key. public, canonical, request. signature)
5) Solution if not ok: return 401, "SIGNATURE_INVALID"
return 200, "OK"
Constant-time порівняння HMAC, зберігання'nonce '/' (ts, event_id)'в швидких KV (TTL ≥ вікно доставки).
6) Анти-реплей і вікна
Timestamp + Nonce: відхиляйте запити старше «± Δ» (напр., 5 хв) і повтори nonce в цьому вікні.
Для вебхуків: використовуйте стабільний'event _ id'і inbox-таблицю - це надійніше, ніж тільки nonce.
Повторна доставка (ретраї) повинна використовувати ті ж ts/nonce/event_id, а не генерувати нові.
7) Мульти-тенант і регіони
Зберігайте ключі per tenant/region: `kid = <tenant>:<region>:<key_id>`.
Розділяйте пули секретів і ліміти; дотримуйтесь data residency.
У заголовках/канонікалізації вказуйте'X-Tenant'і регіон - це частина перевіряється контексту.
8) Управління ключами та ротація
Реєстр ключів (KMS/Vault): 'kid', тип, алгоритм, статус ('active','deprecating','retired'),'valid _ from/valid _ to'.
Dual-secret: тримайте одночасно поточний і наступний ключ (приймач приймає обидва).
Ротація за розкладом і за подією (компрометація).
Key pinning (по можливості) і обмеження доступу до матеріалів ключів.
Логи доступу до ключів і дій з ними.
9) Поєднання з mTLS і OAuth
mTLS перевіряє канал і «хто ви» на рівні сертифіката.
Підпис захищає повідомлення (корисна через проксі/кеші/черги).
OAuth/JWT доповнює автентифікацію/авторизацію, але сам по собі не гарантує цілісність тіла (якщо його не підписувати в канонікалізації).
Кращі практики: mTLS + підпис тіла (Digest) + HMAC/ECDSA + короткий'ts'-інтервал.
10) Помилки та коди відповідей
'401 SIGNATURE_INVALID' - невірний підпис/алгоритм.
'401 KEY_REVOKED' -'kid'недійсний/прострочений.
'400 TIMESTAMP_OUT_OF_RANGE' - годинник/вікно.
'409 NONCE_REPLAYED' - виявлено повтор.
400 DIGEST_MISMATCH' - тіло змінено.
'415 UNSUPPORTED_ALGORITHM' - недозволений'alg'.
'429 TOO_MANY_ATTEMPTS' - тротлінг за ключем/тенантом.
Штовхайте точну причину в машинно-читаному'error _ code'; не повертайте секрети/канонікалізацію «як є».
11) Спостережуваність і аудит
Метрики:- `verify_p95_ms`, `verify_error_rate`, `digest_mismatch_rate`, `replay_blocked_rate`, `alg_usage{hmac,ecdsa}`, `clock_skew_ms`.
- Логи (структурні): `kid`, `alg`, `tenant`, `region`, `ts`, `nonce`, `digest_hash`, `decision`, `reason`.
- Трейсинг: атрибути'signature. kid`, `signature. alg`, `signature. ts_skew`.
- Аудит: незмінний журнал ротацій, використання ключів і прапорів допуску.
12) Продуктивність
Хешируйте тіло стримінгом (не тримайте в пам'яті).
Кешуйте публічні ключі по'kid'з коротким TTL і інвалідацією по події.
На edge/gateway виносьте попередні перевірки (ts/nonce/формат).
HMAC швидше ECDSA; ECDSA зручніше для зовнішніх інтеграцій і «нероздільних» ключів.
13) Тестування
Набори фікстур: однакові запити → однакова канонікалізація/підпис; «брудні» прогалини/порядок query/заголовків → стійкі.
Negative: невірний'kid/alg', модифіковане тіло/host, повтор nonce, застарілий ts, clock skew.
Property-based: будь-які еквівалентні запити дають один canonical-рядок.
Interop: крос-мовні перевірки (Go/Java/Node/Python).
Chaos: затримки, ретраї, зміна ключа «на льоту».
14) Плейбуки (runbooks)
1. Сплеск'SIGNATURE _ INVALID '
Перевірити ротацію ключів, розсинхрон clock, зміни канонікалізації у відправника.
Тимчасово включити'dual-accept'для старого'kid', повідомити партнера.
2. Зростання'REPLAYED '
Збільшити TTL зберігання nonce, звірити ретрайнери у відправника, перевірити clock skew.
Перекрити зловживає IP/ASN на edge.
3.'DIGEST _ MISMATCH'масово
Перевірити проксі/компресію/перезапис заголовків; зафіксувати версію канонікалізації.
Відключити посередників, що порушують тіло/заголовки.
4. Компрометація ключа
Негайно revoke'kid', перекласти на'next _ kid', регенерувати всі секрети/токени, аудит доступу.
15) Типові помилки
Підписувати «частину тіла» або JSON без фіксації порядку → вразливість до перестановки полів.
Відсутність «Digest» → проксі може змінити тіло непомітно.
Довге вікно'ts'без nonce → відкритий реплей.
Зберігати секрети в змінних оточення без KMS/Vault.
Порівнювати підпис не constant-time.
Ігнорувати'host '/' path'в канонікалізації → атаки на переадресацію.
Змішувати'kid'різних тенантів і регіонів.
16) Чек-лист перед продом
Визначено формат канонікалізації (method, path + query, content-type, Digest, ts, nonce, host, tenant).
- Реалізовані HMAC/ECDSA з'kid', реєстр ключів і dual-secret.
- Включені анти-реплей (nonce + ts) і зберігання inbox/event_id для вебхуків.
- Налаштовані коди помилок/політика ретраїв і троттлінг per tenant/key.
- Спостережуваність: метрики verify, логи, трасування, алерти на сплески.
- Ротація ключів автоматизована; аудит і права доступу обмежені.
- Тест-набори на канонікалізацію і міжмовну сумісність.
- Документація для інтеграторів з прикладом на 3-4 мовах і фікстурами.
- mTLS включений для чутливих інтеграцій; JWT використовується тільки як доповнення, не заміна підпису тіла.
Висновок
Підпис і верифікація запитів - це не «один заголовок», а дисципліна: чітка канонікалізація, короткі вікна часу, анти-реплей, ротація ключів і спостережуваність. Побудуйте єдиний стандарт для всіх інтеграцій (API і вебхуки), використовуйте'kid '/KMS, приймайте два ключі при ротації, і ваші контури стануть стійкими до підмінам, передбачуваними і зручними для аудиту.