Подбор ключа по kid, alg, use при верификации

В спецификации JSON Web Signature (JWS) и JSON Web Encryption (JWE), используемых в библиотеке jose, ключевая задача при верификации токена — корректно определить криптографический ключ, который был использован при подписании. Ошибка на этом этапе приводит либо к невозможности проверить подпись, либо к критическим уязвимостям, когда злоумышленник может подменить ключ или алгоритм.

Механизм выбора ключа в процессе проверки основывается на трёх полях заголовка JWT: kid, alg, use. Эти параметры формируют стратегию поиска ключа в хранилище и определяют, какой именно ключ должен быть использован для проверки подписи.


JWT-заголовок содержит метаданные, влияющие на выбор криптографического материала:

  • alg — алгоритм подписи (например, HS256, RS256, ES256)
  • kid — идентификатор ключа
  • use — назначение ключа (подпись или шифрование)

Каждое из этих полей играет отдельную роль, и их совместная обработка формирует безопасную стратегию верификации.


Приоритет поля kid при выборе ключа

kid (Key ID) — это основной механизм идентификации ключа в наборах JSON Web Key Set (JWKS).

Поведение библиотеки при наличии kid

При вызове проверки подписи библиотека выполняет следующую логику:

  1. Извлекает kid из заголовка JWT.
  2. Ищет ключ в JWKS, где kid совпадает.
  3. Проверяет соответствие алгоритма (alg).
  4. Использует найденный ключ для проверки подписи.

Пример логики поиска

const key = jwks.keys.find(k => k.kid === decoded.header.kid);

Важные особенности

  • kid не гарантирует безопасность сам по себе
  • При отсутствии проверки alg возможны атаки подмены алгоритма
  • Несуществующий kid должен приводить к ошибке верификации

Роль alg в защите от атак на алгоритм

Поле alg определяет алгоритм криптографической операции:

  • HS256 — HMAC с SHA-256 (симметричный)
  • RS256 — RSA с SHA-256 (асимметричный)
  • ES256 — ECDSA с SHA-256

Значение при выборе ключа

При проверке библиотека обязана:

  • Сравнить alg из JWT с допустимыми алгоритмами ключа
  • Исключить использование ключа, несовместимого с алгоритмом

Критическая уязвимость при игнорировании alg

Если алгоритм не проверяется, возможны атаки типа:

  • RS256 → HS256 downgrade attack
  • Подмена публичного ключа как HMAC secret

Поле use как ограничитель назначения ключа

Поле use в JWK указывает назначение ключа:

  • sig — подпись
  • enc — шифрование

Поведение при верификации

При подборе ключа библиотека должна учитывать:

  • Для проверки JWT используется только ключи с use: sig
  • Ключи с use: enc игнорируются

Типичная ошибка реализации

Игнорирование use приводит к ситуации, когда:

  • ключ для шифрования используется для подписи
  • нарушается модель доверия ключей

Совместная логика kid, alg, use

В реальной реализации jose используется комбинированный подход:

Шаг 1. Фильтрация по kid

JWKS → фильтрация по kid

Шаг 2. Проверка назначения (use)

оставить только ключи с use = sig

Шаг 3. Проверка алгоритма (alg)

сравнение JWT.alg с key.alg

Шаг 4. Финальный выбор ключа

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

Поведение при отсутствии kid

Если kid отсутствует:

  • библиотека может перебрать все ключи JWKS
  • либо использовать ключ по умолчанию (если задан вручную)

Риски отсутствия kid

  • увеличивается поверхность атаки
  • возможна попытка подбора ключа через алгоритм

Приоритеты и конфликтные ситуации

Ситуация 1: несколько ключей с одинаковым kid

Поведение:

  • используется первый совпавший ключ
  • возможна ошибка конфигурации JWKS

Ситуация 2: kid найден, но alg не совпадает

Результат:

  • немедленный отказ в верификации

Ситуация 3: use не соответствует

Результат:

  • ключ исключается из выборки

Алгоритмическая модель выбора ключа

Упрощённая модель внутри библиотеки:

function selectKey(jwks, header) {
  return jwks.keys
    .filter(k => k.use === 'sig')
    .filter(k => k.kid === header.kid)
    .filter(k => k.alg === header.alg)
    .shift();
}

Безопасная стратегия реализации

При работе с jose критично соблюдать следующие правила:

1. Всегда проверять alg

Алгоритм нельзя доверять входящему JWT.

2. Использовать строгий kid match

Не допускается частичное совпадение или fallback без контроля.

3. Фильтровать по use

Ключи должны быть строго разделены по назначению.

4. Отключать автоматический fallback

Перебор всех ключей без kid должен быть контролируемым.


Типичные ошибки реализации

Ошибка 1: доверие alg из токена

Приводит к атаке подмены алгоритма.

Ошибка 2: игнорирование kid

Приводит к выбору неправильного ключа при ротации.

Ошибка 3: отсутствие фильтрации use

Приводит к смешению ключей подписи и шифрования.


Практическая модель верификации

Полный процесс проверки JWT в контексте ключевого выбора:

  1. Декодирование заголовка
  2. Извлечение kid, alg
  3. Запрос JWKS
  4. Фильтрация по use
  5. Поиск ключа по kid
  6. Проверка alg
  7. Верификация подписи

Поведение при ротации ключей

При смене ключей (key rotation):

  • старые ключи остаются с тем же kid до истечения срока
  • новые ключи получают новый kid
  • клиент должен всегда использовать актуальный JWKS

Закрытая логика доверия к ключу

Ключ считается валидным только при выполнении трёх условий одновременно:

  • совпадение kid
  • совпадение alg
  • корректное значение use

Любое нарушение этих условий приводит к отклонению токена без исключений.