Проверка JWT, созданного в Postman с использованием ES512, нестабильна

Я использую следующий подход для создания и подписания JWT (ES512) в Postman (предварительный сценарий):

var CryptoJS  = require("crypto-js");
var navigator = {};
var window    = {};

eval(pm.globals.get("jsrsasign-js"));

let issuerId   = pm.environment.get("issuerId");
let audienceId = pm.environment.get("audienceId");
let privateKey = pm.environment.get("privateKey");

let body     = pm.request.body.raw;    
let bodyHash = calculateHash(body);

const pl     = payload(issuerId, audienceId, bodyHash)
const jwt    = signJwt(pl, privateKey)

pm.collectionVariables.set("bearerToken", jwt);

function calculateHash(message) {
  const hash = CryptoJS.SHA256(message??'');
  return hash.toString(CryptoJS.enc.Base64);
}

function payload(iss, aud, bodyHash) {
  return {
    "iss": iss,
    "aud": aud,
    "sub": "api-request",
    "rbh": bodyHash,
    "exp": Math.round(Date.now() / 1000) + 59,
  };
}

function signJwt(payload, privateKey) {
  const alg      = 'ES512';
  const header   = {"alg":alg};
  const sHeader  = JSON.stringify(header);
  const sPayload = JSON.stringify(payload);
  return KJUR.jws.JWS.sign('ES512', sHeader, sPayload, privateKey);
}

Закрытый ключ генерируется:

openssl ecparam -name secp521r1 -genkey -noout -out private.pem

Открытый ключ:

openssl ec -in private.pem -pubin -outform PEM -out public.pem

Сервер использует: https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt/9.40 для проверки JWT:

ECDSAVerifier(parseFromPEMEncodedObjects(publicKey).toECKey())

Проверка Jwt довольно нестабильна. Иногда это удается (80%), иногда нет (20%).
Причина в том, что иногда длина подписи jws составляет 130 байт (не нормально), должна быть 132, иногда 132.

ОК, JWT: eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI3NDc4NTV9.AJTc6e_4ymPhtQPSx6XoeYyOScFIYf5axTrSTXz2rtXuH9KZAFZBuoTMD35siR7-MIpRpIk2QslSNvU4A7KQSUVFAeH0Mb7yfwC2bN5ncJhWxg6j5dh6sJfPwLi3buGuw9i_TViF6mnLs8WClzXw8NqFBoNSnHexQPkWba8J-trAxoKo

Не ОК JWT: eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI3NDc5MTZ9.gcJl3McLyAML3nfbb-ZXXtvfP-TRm0rjpEYX7iiagiFGSNoMi-8yu7jre_QTB0SW2_POrp4OcGg0QY2EeRCf6JnuApCmFTThF3qiwQ82HLS4OjNcHJkKRjxsDVC3aWhcyd7N9N30pWK5vzPCnaxAy1_3u0mHbByY_HMROuixXF07hw

открытый ключ (формат PEM):

-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBm3GiQ0HmPdCyeuy7yWnxjwlQnjn9
dytNIqD/nsuhX1SVuDFH2iD12SM+csYrqLAwZKP4Y3BNIH+5QUFgpbCBwEEBu3w6
FeqM/5rJvxjG2/YJU2p1aUGed8Br/bZVEFVN6ohWgFGrC/hXxYuxVVwIwuzXnd8N
bXCVGZZeVKM5YrbcZQM=
-----END PUBLIC KEY-----

Если я использую Java-клиент (nimbus-jose-jwt) для создания и подписания JWT, проверка будет стабильной.

Я неправильно использую библиотеку jsrsasign?

Есть еще одна библиотека JavaScript: https://github.com/panva/jose.
К сожалению, я не могу импортировать его в Postman.

Мне кажется это ошибка в библиотеке jsrsasign (по крайней мере для P-521), где две части подписи r и s неправильно доводятся до необходимой длины в 66 (для P-521) байт каждая (заполнение ведущими 0x00 значения, если значения слишком короткие, усекая ведущий байт знака 0x00, если значения слишком длинные). Вы можете попытаться исправить это для неправильных подписей, но из-за отсутствия уникальности относительно r и s это, вероятно, не всегда будет работать.

Topaco 04.08.2024 13:57

Если опубликовать открытый ключ (принадлежащий размещенным подписям), можно будет проверить это предположение.

Topaco 04.08.2024 14:43

@Topaco опубликовал открытый ключ

user657009 04.08.2024 16:31

С помощью этого открытого ключа я не могу проверить ни одну из двух подписей даже с помощью jsrsasign. Поскольку это работает для подписей, когда я использую свою собственную пару ключей, это указывает на то, что опубликованный открытый ключ не соответствует.

Topaco 04.08.2024 16:52

Я обновил его. Наверное я неправильно вставил. Это открытый ключ в формате PEM.

user657009 05.08.2024 04:03

Нет, опубликованные подписи невозможно проверить с помощью этого ключа, убедитесь сами в Интернете jsfiddle.net/b9g8q64f: Хотя ваши подписи невозможно проверить, подписи (как формально действительные 132, так и недействительные 130 байт) можно проверить с помощью моего ключевая пара. Таким образом, опубликованный вами открытый ключ, похоже, не принадлежит ни одной из подписей. Поэтому я не могу проверить, работает ли приведенное выше исправление для подписей размером 130 байт. Но вы можете попробовать сами.

Topaco 05.08.2024 08:27

Теперь они проверены в jsfiddle.net/h1nztydc/1. Я поменял подписи.

user657009 05.08.2024 11:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как уже подозревалось в комментарии, в библиотеке jsrsasign, похоже, есть ошибка при преобразовании подписи ECDSA в формат P1363, по крайней мере, для кривой P-521. В формате P1363 значения r и s представлены как беззнаковые, с прямым порядком байтов и объединенные. Для P-521 каждый из r и s имеет размер 66 байт. Если значение короче, оно доводится до 66 байт с ведущими значениями 0x00, так что подпись имеет постоянную длину 132 байта. Это необходимо для того, чтобы значения r и s были четко идентифицируемы.

Для опубликованной подписи размером 130 байт отсутствует заполнение как для значения r, так и для значения s, т. е. r-length/s-length = 65bytes/65bytes, что можно легко проверить, если подпись недействительна (пробелы только для отображения):

Base64url: -GJ6N27KEqRKHgmdyRIApgN6861cFbAXtksTPv3GVDVV-80Jjm266tiQqh3ORfLt_ucijNoKlvdZ8b90H1nb7SQSj3Mo0JZT8-Ht6fP9DA13LsWdTQuX7TTIUEqPegAI9lCmgZMFpbNeGqVarsOfgSIrpFvp7rFCdo7aFM7OaShGKw
hex:       f8627a376eca12a44a1e099dc91200a6037af3ad5c15b017b64b133efdc6543555fbcd098e6dbaead890aa1dce45f2edfee7228cda0a96f759f1bf741f59dbed24 128f7328d09653f3e1ede9f3fd0c0d772ec59d4d0b97ed34c8504a8f7a0008f650a6819305a5b35e1aa55aaec39f81222ba45be9eeb142768eda14cece6928462b

фиксируется вручную:

hex:       00f8627a376eca12a44a1e099dc91200a6037af3ad5c15b017b64b133efdc6543555fbcd098e6dbaead890aa1dce45f2edfee7228cda0a96f759f1bf741f59dbed24 00128f7328d09653f3e1ede9f3fd0c0d772ec59d4d0b97ed34c8504a8f7a0008f650a6819305a5b35e1aa55aaec39f81222ba45be9eeb142768eda14cece6928462b
Base64url: APhiejduyhKkSh4JnckSAKYDevOtXBWwF7ZLEz79xlQ1VfvNCY5tuurYkKodzkXy7f7nIozaCpb3WfG_dB9Z2-0kABKPcyjQllPz4e3p8_0MDXcuxZ1NC5ftNMhQSo96AAj2UKaBkwWls14apVquw5-BIiukW-nusUJ2jtoUzs5pKEYr

Фиксированный токен тогда:

eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI4NTAxMzJ9.APhiejduyhKkSh4JnckSAKYDevOtXBWwF7ZLEz79xlQ1VfvNCY5tuurYkKodzkXy7f7nIozaCpb3WfG_dB9Z2-0kABKPcyjQllPz4e3p8_0MDXcuxZ1NC5ftNMhQSo96AAj2UKaBkwWls14apVquw5-BIiukW-nusUJ2jtoUzs5pKEYr

Этот токен теперь действителен и будет проверен как действительный любой библиотекой, совместимой с JWS, см., например. здесь, на сайте jwt.io.


Однако это исправление не является надежным на 100%. Теоретически также возможны значения r и s разной длины (например, r-length/s-length = 66bytes/64bytes). Из-за отсутствия однозначности невозможно априори определить, какой случай применим, поэтому это необходимо опробовать. Однако предположение о подписи r-length/s-length = 65bytes/65bytes правдоподобно, поскольку вероятность более короткой подписи меньше (поскольку правдоподобной делает следующая оценка: вероятность ведущего значения 0x00: 1/256, двух ведущих значений 0x00: 1/256^2 . ..).
Помимо этого теоретически возможны и другие (недействительные) размеры подписи, например 131 байт. Здесь вы можете только попробовать, какую из частей нужно дополнить.

Однако возникают ли эти теоретически возможные случаи на самом деле, в конечном итоге зависит от ошибки, и на них невозможно ответить без дальнейшего анализа кода. В своих тестах я обнаружил только длины 130 и 132 байта в 1000 сгенерированных подписях. Однако при подписании я время от времени получал сообщение об ошибке «Неизвестная ошибка длины подписи ECDSA», что указывает на большую сложность ошибки.

На мой взгляд, самое разумное решение — перейти на другую библиотеку (хотя бы для P-521). Если вы не можете найти его для своей среды, альтернативой может быть исправление вручную, описанное выше.

Другие вопросы по теме