Я использую следующий подход для создания и подписания 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.
Если опубликовать открытый ключ (принадлежащий размещенным подписям), можно будет проверить это предположение.
@Topaco опубликовал открытый ключ
С помощью этого открытого ключа я не могу проверить ни одну из двух подписей даже с помощью jsrsasign. Поскольку это работает для подписей, когда я использую свою собственную пару ключей, это указывает на то, что опубликованный открытый ключ не соответствует.
Я обновил его. Наверное я неправильно вставил. Это открытый ключ в формате PEM.
Нет, опубликованные подписи невозможно проверить с помощью этого ключа, убедитесь сами в Интернете jsfiddle.net/b9g8q64f: Хотя ваши подписи невозможно проверить, подписи (как формально действительные 132, так и недействительные 130 байт) можно проверить с помощью моего ключевая пара. Таким образом, опубликованный вами открытый ключ, похоже, не принадлежит ни одной из подписей. Поэтому я не могу проверить, работает ли приведенное выше исправление для подписей размером 130 байт. Но вы можете попробовать сами.
Теперь они проверены в jsfiddle.net/h1nztydc/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). Если вы не можете найти его для своей среды, альтернативой может быть исправление вручную, описанное выше.
Мне кажется это ошибка в библиотеке jsrsasign (по крайней мере для P-521), где две части подписи r и s неправильно доводятся до необходимой длины в 66 (для P-521) байт каждая (заполнение ведущими 0x00 значения, если значения слишком короткие, усекая ведущий байт знака 0x00, если значения слишком длинные). Вы можете попытаться исправить это для неправильных подписей, но из-за отсутствия уникальности относительно r и s это, вероятно, не всегда будет работать.