Практика: создание CMS-подписи документа

CMS (Cryptographic Message Syntax) представляет собой стандарт, описывающий формат криптографически защищённых сообщений, включающих цифровую подпись, сертификаты и зашифрованные данные. В экосистеме JavaScript библиотека Jsrsasign предоставляет инструменты для формирования PKCS

Подготовка окружения и подключение Jsrsasign

Библиотека может использоваться как в браузере, так и в Node.js. В браузере достаточно подключить UMD-сборку:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.8.6/jsrsasign-all-min.js"></script>

В Node.js установка выполняется через npm:

npm install jsrsasign

И подключение:

const jsrsasign = require("jsrsasign");
const KJUR = jsrsasign.KJUR;

Для работы с CMS важны компоненты:

  • KJUR.asn1.cms — генерация CMS/PKCS#7 структур
  • KEYUTIL — работа с ключами и сертификатами
  • KJUR.crypto — криптографические операции

Структура CMS SignedData

CMS-подпись строится вокруг объекта SignedData, который включает:

  • данные, которые подписываются (content)
  • алгоритм хэширования (SHA-256, SHA-1 и др.)
  • сертификат подписанта
  • криптографическую подпись
  • при необходимости — цепочку сертификатов

Логика формирования CMS в Jsrsasign сводится к последовательному созданию ASN.1-структуры и её подписи приватным ключом.

Подготовка ключей и сертификата

Для создания подписи требуется RSA-ключ и сертификат X.509.

Пример генерации ключевой пары (для тестирования):

const kp = KEYUTIL.generateKeypair("RSA", 2048);
const privateKey = kp.prvKeyObj;
const publicKey = kp.pubKeyObj;

Создание самоподписанного сертификата:

const cert = KJUR.asn1.x509.X509Util.newCertPEM({
    serial: {int: 1},
    sigalg: "SHA256withRSA",
    subject: [{name: "C", value: "KZ"}, {name: "CN", value: "Test User"}],
    sbjpubkey: publicKey,
    cakey: privateKey,
    notbefore: "230101000000Z",
    notafter: "250101000000Z"
});

Сертификат в формате PEM будет использоваться внутри CMS.

Формирование подписываемого документа

CMS может подписывать как текст, так и бинарные данные. В Jsrsasign чаще используется строковый контент.

const data = "Документ для цифровой подписи";

Важно учитывать кодировку: по умолчанию используется UTF-8, но при работе с бинарными файлами данные предварительно кодируются в base64 или hex.

Создание CMS SignedData структуры

Основной объект для формирования подписи — KJUR.asn1.cms.SignedData.

Пример создания подписи:

const cmsSigned = new KJUR.asn1.cms.SignedData({
    content: {
        type: "data",
        content: data
    },
    signerInfo: [{
        version: 1,
        sid: {
            type: "issuserandserialnumber",
            cert: cert
        },
        digestAlgorithm: "sha256",
        signatureAlgorithm: "SHA256withRSA",
        signature: {
            alg: "SHA256withRSA",
            key: privateKey
        }
    }],
    certificates: [cert]
});

После создания объект необходимо сериализовать в DER или PEM формат:

const cmsDer = cmsSigned.getContentInfo({asn1: true});
const cmsPem = KJUR.asn1.ASN1Util.getPEMStringFromHex(cmsDer.toHex(), "CMS");

Подпись данных: механизм работы

Процесс формирования CMS-подписи включает несколько этапов:

  1. Вычисление хэша от исходного документа (SHA-256)
  2. Создание структуры SignedAttributes (если используются атрибуты подписи)
  3. Подпись хэша приватным ключом RSA
  4. Формирование ASN.1 контейнера CMS
  5. Встраивание сертификата подписанта

В упрощённой реализации Jsrsasign автоматически выполняет шаги 1–4 внутри SignedData.

Добавление подписанных атрибутов

Для повышения криптографической устойчивости можно включить атрибуты:

  • время подписи
  • хэш исходного сообщения
  • идентификатор алгоритма

Пример:

signedAttrs: {
    signingTime: new Date(),
    contentType: "data",
    messageDigest: KJUR.crypto.Util.hashString(data, "sha256")
}

Работа с PEM и DER форматами CMS

Jsrsasign позволяет получать результат в нескольких форматах:

  • PEM (человекочитаемый формат)
  • DER (бинарный формат)
  • Hex (для низкоуровневой обработки)

Пример конвертации:

const derHex = cmsSigned.getContentInfo().toHex();
const pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(derHex, "CMS");

Проверка CMS-подписи

Проверка выполняется через извлечение SignedData и валидацию подписи:

const cmsObj = new KJUR.asn1.cms.ContentInfo({pem: pem});
const sd = new KJUR.asn1.cms.SignedData({cmsobj: cmsObj});

const isValid = sd.verify();

В процессе проверки:

  • извлекается сертификат
  • пересчитывается хэш данных
  • проверяется RSA-подпись
  • сравнивается результат

Практический сценарий: подпись документа и передача

В реальных системах CMS используется для:

  • юридически значимого подписания документов
  • обмена подписанными сообщениями между сервисами
  • защиты API-запросов

Пример полного цикла:

const message = JSON.stringify({
    id: 10,
    user: "alice",
    amount: 500
});

const cms = new KJUR.asn1.cms.SignedData({
    content: {type: "data", content: message},
    signerInfo: [{
        digestAlgorithm: "sha256",
        signatureAlgorithm: "SHA256withRSA",
        signature: {
            alg: "SHA256withRSA",
            key: privateKey
        },
        sid: {cert: cert}
    }],
    certificates: [cert]
});

const signed = cms.getContentInfo().toString("hex");

Отправка подписанного сообщения:

fetch("/api/verify", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({cms: signed})
});

Типичные ошибки при работе с CMS в Jsrsasign

Несовпадение алгоритмов

Если алгоритм подписи и хэширования не согласованы, подпись будет невалидной.

Ошибки кодировки данных

Различие между UTF-8 и бинарными данными приводит к некорректному хэшу.

Неверный сертификат

CMS требует корректного X.509 сертификата, иначе структура SignedData не формируется.

Потеря цепочки доверия

Если не включены промежуточные сертификаты, проверка на стороне получателя может провалиться.

Использование CMS вместо PKCS#1 подписи

CMS выгодно отличается от “голой” RSA-подписи тем, что:

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

PKCS#1 используется только для подписи хэша, тогда как CMS — полноценный контейнер.

Интеграционные особенности

При внедрении Jsrsasign CMS в системы документооборота важно учитывать:

  • совместимость с Java BouncyCastle
  • корректность DER-энкодинга
  • соответствие RFC 5652
  • ограничения браузерной криптографии

CMS-подписи, созданные Jsrsasign, обычно совместимы с серверными PKI-системами при корректной настройке алгоритмов и сертификатов.