Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jwt-verify

Проверка подписи JWT/JWS на чистом OneScript: RS256 (RSASSA-PKCS1-v1_5 с SHA-256), открытые ключи из JWKS или DER, длинная арифметика и умножение Монтгомери — без внешних криптобиблиотек и без вызовов openssl из процесса.

Библиотека появилась ради одной задачи: принять OIDC id-token из CI (GitHub Actions, GitLab CI) и доказать, что его выпустил именно тот провайдер, чьи ключи лежат в его JWKS. Всё остальное — следствие.

Публичный интерфейс — два класса. Всё остальное лежит в src/internal и меняется без оглядки на обратную совместимость.

Кэш         = Новый КэшJWKS(Издатель);        // ключи: discovery, JWKS, кэш, ротация
Проверяющий = Новый ПроверяющийJWS(Кэш);      // подпись и сроки
Итог        = Проверяющий.Проверить(Токен);   // → исход проверки, без исключений

Зависимость одна: 1connector (транспорт по умолчанию). Транспорт и часы внедряются, поэтому тесты идут без сети и без ожидания реального времени.

Граница ответственности

Библиотека доказывает происхождение токена — и только. Право предъявителя что-либо сделать из верной подписи не следует, а знать, кому что можно, может лишь приложение:

Приложение Библиотека
каким издателям верить и где взять токен подпись RS256 по ключу с нужным kid
iss, aud, sub, repository, job_workflow_ref и прочие клеймы alg из белого списка, а не из токена
что делать с отказом: отклонить, повторить, показать exp/nbf с допуском на расхождение часов
где живут настройки провайдера получение и кэш ключей, поведение при ротации
логирование ничего не логирует

Проверенные клеймы приложение разбирает само:

Клеймы = Итог.Клеймы;
Если Клеймы["iss"] <> "https://token.actions.githubusercontent.com"
	Или Клеймы["aud"] <> "https://hub.example.com"
	Или Клеймы["repository"] <> ОжидаемыйРепозиторий Тогда
	ВызватьИсключение "Токен подписан верно, но выпущен не для нас";
КонецЕсли;

Модель безопасности (важно, прочитайте)

  • alg берётся из белого списка (RS256), а не из токена. Токен с alg: none или alg: HS256 отвергается до того, как кто-либо посмотрит на подпись, — классическая подмена алгоритма не работает.
  • Ключ выбирается только по kid. Токен без kid отвергается: угадывать, каким из ключей провайдера он подписан, библиотека не станет.
  • Токен без exp отвергается: срок «никогда» — не срок. Если ваш провайдер такие выпускает, снимите требование явно: УстановитьТребованиеExp(Ложь).
  • Модуль короче 2048 бит — отказ, а не предупреждение. Верхняя граница — 8192 бита. Чётный модуль, чётная или единичная открытая экспонента — тоже отказ.
  • DigestInfo проверяется строго. Восстановленное EM сравнивается целиком с эталоном 0x00 0x01 0xFF… 0x00 || DigestInfo(SHA-256) || хеш, поэтому BER-разночтения (DigestInfo без NULL-параметров), лишние байты после хеша, чужой OID, укороченный padding и ноль внутри padding отвергаются. Это закрыто отдельным набором тестов: подписи для таких EM собраны сырой операцией RSA закрытым ключом — обычным подписыванием их не получить.
  • Вырожденные подписи (s = 0, s = 1, s ≥ n, длина ≠ длине модуля) отвергаются без переполнений. Требование s < n — не формальность: без него к любой годной подписи можно прибавить n и получить вторую годную (ковкость подписи, RFC 8017 §5.2.2); это закрыто отдельным вектором.
  • Отказ вместо исключения. Недоступный JWKS, таймаут, мусор вместо JSON, документ без нужного kid — всё это исходы проверки (Успех = Ложь с кодом), а не исключение наружу.
  • Клеймы отдаются только при успехе. У отказа Итог.КлеймыНеопределено, чтобы непроверенную нагрузку нельзя было использовать по невнимательности.
  • Ограничения на разрастание: предел длины токена (64 КиБ), размера полученного тела JWKS (256 КиБ, считается в байтах UTF-8) и количества ключей в документе (20). Предел тела применяется к уже полученному ответу: он защищает разбор и кэш, но не канал — транспорт скачивает ответ целиком, и если вам нужен обрыв на лету, подставьте свой транспорт.
  • Адрес ключей обязан быть https и жить на хосте издателя. Иначе тот, кто подсунул документ discovery, уводит проверку на свои ключи.

Библиотека ничего не логирует. Ни токен, ни его клеймы не попадают ни в лог, ни в текст исключений: протокольные исходы уезжают структурой, которую пишет (или не пишет) приложение.

Установка

opm install jwt-verify
.ЗависитОт("jwt-verify", "0.2.0")

Проверить id-token GitHub Actions — пять строк

#Использовать jwt-verify

Кэш = Новый КэшJWKS("https://token.actions.githubusercontent.com");
Проверяющий = Новый ПроверяющийJWS(Кэш);
Итог = Проверяющий.Проверить(Токен);
Если Не Итог.Успех Тогда
	ВызватьИсключение "Токен отклонён: " + Итог.Код + " — " + Итог.Сообщение;
КонецЕсли;
Сообщить("Пакет публикует " + Итог.Клеймы["repository"]);   // acme/widgets

КэшJWKS сам сходит за /.well-known/openid-configuration, возьмёт оттуда jwks_uri, прочитает ключи и запомнит подготовленный ключ. Для GitLab всё то же самое с издателем https://gitlab.com.

Лицензия

MIT — см. LICENSE.

About

Проверка подписи JWT/JWS на чистом OneScript: RS256 по ключам из JWKS или DER, без внешних криптобиблиотек

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages