Использование нескольких подписей в JSON Serialization

В спецификации JSON Web Signature (JWS) предусмотрено два основных формата сериализации: компактный и JSON Serialization. Именно второй вариант используется, когда требуется работа с несколькими подписями для одного и того же полезного содержимого. В библиотеке jsrsasign эта возможность реализована через структуру General JWS JSON Serialization.

В отличие от Compact Serialization, где возможна только одна подпись, JSON Serialization позволяет привязать к одному payload несколько независимых подписей, каждая из которых может быть сформирована с использованием разных ключей, алгоритмов или удостоверяющих центров.


Структура General JWS JSON Serialization

Формат представляется в виде JSON-объекта следующей структуры:

  • payload — закодированные данные (Base64URL)
  • signatures — массив объектов подписей

Каждый элемент массива signatures содержит:

  • protected — защищённый заголовок (Base64URL)
  • header — незащищённый заголовок (опционально)
  • signature — подпись (Base64URL)

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


Создание нескольких подписей в jsrsasign

В jsrsasign работа с General JSON Serialization строится через объект KJUR.jws.JWS.

Основная идея заключается в последовательном добавлении подписей к одному payload.

Пример логики формирования:

  • создаётся payload
  • формируются заголовки для каждой подписи
  • выполняется криптографическое подписание для каждого ключа отдельно
  • результаты объединяются в общий JSON объект

Пример генерации JWS с несколькими подписями

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.

Логика проверки:

  1. Извлекается payload
  2. Для каждой подписи извлекается header
  3. Выбирается соответствующий публичный ключ
  4. Выполняется проверка подписи

Пример:

const result = KJUR.jws.JWS.verifyGeneral(
  generalJWS,
  publicKey
);

В реальных сценариях проверка часто выполняется выборочно — не обязательно все подписи должны быть валидны, если бизнес-логика допускает частичное доверие.


Практическое применение нескольких подписей

Использование множественных подписей возникает в случаях:

  • параллельная подпись документа несколькими участниками
  • миграция криптографических алгоритмов (старый + новый ключ одновременно)
  • распределённые системы доверия
  • юридически значимые документы, требующие нескольких подписантов

Каждая подпись становится независимым доказательством неизменности данных.


Совместимость и ограничения

Несмотря на гибкость формата, существуют ограничения:

  • увеличивается размер итогового JSON объекта
  • усложняется проверка цепочки доверия
  • не все JWT-библиотеки поддерживают General Serialization
  • требуется явная работа с массивом подписей

Compact Serialization при этом не может использоваться в сценариях с несколькими подписями.


Работа с ключами в multi-signature сценарии

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

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

В jsrsasign часто используется сопоставление kid → public key, что позволяет масштабировать систему проверки.


Ошибки при реализации множественных подписей

Типичные проблемы:

  • несоответствие payload между подписями (разный whitespace или сериализация)
  • неправильная кодировка Base64URL
  • потеря protected header при ручной сборке JSON
  • попытка смешать Compact и JSON Serialization

Любое изменение payload приводит к полной инвалидности всех подписей.


Сравнение одиночной и множественной подписи

Одиночная подпись:

  • компактный формат
  • высокая совместимость
  • простая проверка

Множественная подпись:

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

Внутренняя модель jsrsasign для JWS JSON

Библиотека рассматривает General Serialization как контейнер, в котором:

  • payload является центральным неизменяемым элементом
  • каждая подпись — отдельный криптографический объект
  • проверка выполняется независимо по каждой записи массива

Такой подход позволяет интегрировать jsrsasign в распределённые системы подписи без изменения базовой модели JWS.