Добавление сертификатов в SignedData

В формате CMS (Cryptographic Message Syntax) объект SignedData содержит не только подписи, но и дополнительную криптографическую инфраструктуру, позволяющую проверяющей стороне восстановить цепочку доверия. Ключевым элементом этой структуры являются X.509 сертификаты, которые могут быть включены непосредственно внутрь SignedData.

Сертификаты в SignedData выполняют несколько функций:

  • предоставляют публичные ключи подписантов;
  • позволяют валидировать подписи без обращения к внешним хранилищам;
  • обеспечивают переносимость подписанных данных между системами;
  • поддерживают построение цепочки доверия (certificate chain).

В библиотеке Jsrsasign работа с сертификатами в SignedData реализуется через объектную модель CMS, где сертификаты включаются в поле certificates.


Структура SignedData в Jsrsasign

Внутреннее представление SignedData в Jsrsasign строится вокруг основных компонентов CMS:

  • contentInfo — данные, которые подписываются
  • signerInfos — информация о подписи (подписанты)
  • certificates — список X.509 сертификатов
  • crls — списки отзыва сертификатов (опционально)

Добавление сертификатов происходит на этапе генерации CMS-структуры или при ручной сборке объекта.


Добавление сертификатов при создании SignedData

В Jsrsasign формирование SignedData обычно выполняется через KJUR.asn1.cms.SignedData.

Основной механизм включения сертификатов — передача массива PEM/DER сертификатов в параметр certs.

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

const cms = new KJUR.asn1.cms.SignedData({
  content: {
    type: "data",
    data: "Hello CMS"
  },
  certs: [
    "-----BEGIN CERTIFICATE-----\nMIIC...IDAQAB\n-----END CERTIFICATE-----"
  ],
  signerInfos: [{
    sid: {
      issuer: {str: "/C=US/O=Example/CN=Test CA"},
      serial: {hex: "01"}
    },
    hashAlg: "sha256",
    sattrs: {
      signingTime: new Date()
    },
    sigAlg: "SHA256withRSA",
    prvkey: privateKeyPem
  }]
});

const cmsOutput = cms.getContentInfo();

Включение цепочки сертификатов

В реальных сценариях в SignedData включается не один сертификат, а целая цепочка:

  • сертификат подписанта
  • промежуточные сертификаты
  • корневой сертификат (не всегда включается)

Пример добавления цепочки

const cms = new KJUR.asn1.cms.SignedData({
  content: {
    type: "data",
    data: "Document payload"
  },
  certs: [
    signerCertPem,
    intermediateCertPem,
    rootCertPem
  ],
  signerInfos: [{
    sid: {
      issuer: {str: "/C=US/O=Example/CN=Intermediate CA"},
      serial: {hex: "02"}
    },
    hashAlg: "sha256",
    sigAlg: "SHA256withECDSA",
    prvkey: privateKeyPem
  }]
});

Формат хранения сертификатов внутри CMS

В CMS SignedData сертификаты кодируются как набор ASN.1 объектов типа CertificateSet.

Jsrsasign автоматически преобразует PEM в DER и помещает их в структуру:

  • CertificateSet ::= SET OF Certificate

Каждый сертификат хранится независимо от подписантов, что позволяет:

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

Связывание сертификатов и подписантов

Подписант (SignerInfo) не содержит сам сертификат. Вместо этого используется идентификатор:

  • issuer (DN издателя)
  • serial number (серийный номер)

Этот механизм называется Signer Identifier (SID).

sid: {
  issuer: {str: "/C=US/O=Example/CN=CA"},
  serial: {hex: "0A12BC"}
}

При проверке подписи система ищет соответствующий сертификат в массиве certs.


Работа с PEM и DER при добавлении сертификатов

Jsrsasign поддерживает оба формата:

  • PEM — текстовый формат Base64
  • DER — бинарный ASN.1

При передаче в certs:

  • PEM автоматически декодируется
  • DER используется напрямую

Пример преобразования:

const x509 = new X509();
x509.readCertPEM(certPem);

const derHex = x509.hex;

Добавление сертификатов после создания SignedData

В некоторых сценариях SignedData уже сформирован, но требуется дополнить его сертификатами. Это реализуется через декодирование и повторную сборку структуры CMS.

const cmsObj = KJUR.asn1.cms.CMSUtil.parse(cmsPem);

cmsObj.contentInfo.content.certificates.push(newCertPem);

const updatedCms = cmsObj.getContentInfo();

Поведение при отсутствии сертификатов

Если поле certs не задано:

  • SignedData остаётся валидным
  • проверка подписи возможна только при наличии внешнего хранилища сертификатов
  • невозможна автономная верификация

Это характерно для минималистичных подписей, где сертификаты передаются отдельно.


Несколько подписантов и общий набор сертификатов

SignedData может содержать несколько SignerInfo. В этом случае:

  • все сертификаты помещаются в общий массив certs
  • каждый подписант ссылается на свой сертификат через SID
  • порядок сертификатов не критичен
signerInfos: [
  signerA,
  signerB
],
certs: [
  certA,
  certB,
  intermediateCA
]

Особенности совместимости CMS и Jsrsasign

При работе с внешними CMS-системами важно учитывать:

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

Jsrsasign не накладывает ограничений и позволяет включать произвольный набор сертификатов в соответствии с требованиями протокола.


Ошибки при добавлении сертификатов

Типовые проблемы возникают при:

  • несоответствии SID и сертификата
  • повреждённом PEM (лишние пробелы, неверные заголовки)
  • смешении алгоритмов (RSA/ECDSA mismatch)
  • отсутствии промежуточных сертификатов при строгой проверке цепочки

В таких случаях проверка подписи завершается ошибкой построения цепочки доверия, а не ошибкой криптографической операции.