В спецификации JSON Web Signature (JWS) предусмотрено два основных формата сериализации: компактный и JSON Serialization. Именно второй вариант используется, когда требуется работа с несколькими подписями для одного и того же полезного содержимого. В библиотеке jsrsasign эта возможность реализована через структуру General JWS JSON Serialization.
В отличие от Compact Serialization, где возможна только одна подпись, JSON Serialization позволяет привязать к одному payload несколько независимых подписей, каждая из которых может быть сформирована с использованием разных ключей, алгоритмов или удостоверяющих центров.
Формат представляется в виде JSON-объекта следующей структуры:
payload — закодированные данные (Base64URL)signatures — массив объектов подписейКаждый элемент массива signatures содержит:
protected — защищённый заголовок (Base64URL)header — незащищённый заголовок (опционально)signature — подпись (Base64URL)Таким образом, одна и та же полезная нагрузка может быть подписана несколькими независимыми ключами.
В jsrsasign работа с General JSON Serialization строится через объект
KJUR.jws.JWS.
Основная идея заключается в последовательном добавлении подписей к одному payload.
Пример логики формирования:
const KJUR = require('jsrsasign');
// payload
const payloadObj = { data: "secure message" };
const payload = JSON.stringify(payloadObj);
// ключи (условно разные подписанты)
const key1 = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----";
const key2 = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----";
// общий заголовок
const header1 = { alg: "RS256", kid: "key-1" };
const header2 = { alg: "RS256", kid: "key-2" };
// формирование JWS с несколькими подписями
const jws = KJUR.jws.JWS.sign(
null,
header1,
payload,
key1
);
// добавление второй подписи вручную через структуру General Serialization
const generalJWS = {
payload: KJUR.b64utob64(payload),
signatures: []
};
// первая подпись
generalJWS.signatures.push({
protected: KJUR.b64utoutf8(KJUR.b64uenc(JSON.stringify(header1))),
signature: KJUR.jws.JWS.sign(null, header1, payload, key1).split(".")[2]
});
// вторая подпись
generalJWS.signatures.push({
protected: KJUR.b64utoutf8(KJUR.b64uenc(JSON.stringify(header2))),
signature: KJUR.jws.JWS.sign(null, header2, payload, key2).split(".")[2]
});
Каждая подпись в General Serialization независима и включает:
alg)kid)При этом payload остаётся неизменным и хранится в единственном экземпляре, что критично для согласованности данных.
Каждая подпись формируется по стандартной схеме JWS:
BASE64URL(Protected Header) + "." + BASE64URL(Payload)
После чего результат передаётся в криптографическую функцию, определённую алгоритмом (например, RS256 или ES256).
В случае нескольких подписей этот процесс повторяется независимо для каждой пары ключ–заголовок.
В jsrsasign проверка осуществляется через обход массива
signatures.
Логика проверки:
Пример:
const result = KJUR.jws.JWS.verifyGeneral(
generalJWS,
publicKey
);
В реальных сценариях проверка часто выполняется выборочно — не обязательно все подписи должны быть валидны, если бизнес-логика допускает частичное доверие.
Использование множественных подписей возникает в случаях:
Каждая подпись становится независимым доказательством неизменности данных.
Несмотря на гибкость формата, существуют ограничения:
Compact Serialization при этом не может использоваться в сценариях с несколькими подписями.
Ключи в подобных системах должны быть строго разделены:
kid обязателен для однозначной идентификацииВ jsrsasign часто используется сопоставление
kid → public key, что позволяет масштабировать систему
проверки.
Типичные проблемы:
Любое изменение payload приводит к полной инвалидности всех подписей.
Одиночная подпись:
Множественная подпись:
Библиотека рассматривает General Serialization как контейнер, в котором:
Такой подход позволяет интегрировать jsrsasign в распределённые системы подписи без изменения базовой модели JWS.