Практика: формирование CAdES-BES

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

Основные характеристики CAdES-BES

CAdES-BES (Basic Electronic Signature) строится на CMS SignedData и включает:

  • криптографическую подпись содержимого (обычно RSA или ECDSA)
  • сертификат подписанта
  • набор подписанных атрибутов (signed attributes)
  • отсутствие временных меток и архивных атрибутов (в отличие от CAdES-T и выше)

Ключевая особенность — целостность и идентификация подписанта без необходимости внешних сервисов времени.

В jsrsasign работа с CAdES-BES реализуется через пространство имён KJUR.crypto.CMS.


Подготовка окружения jsrsasign

Библиотека может быть подключена через CDN или npm:

import * as jsrsasign from "jsrsasign";

или в браузере:

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

После подключения становятся доступны:

  • KEYUTIL — работа с ключами
  • X509 — обработка сертификатов
  • KJUR.crypto.CMS — создание CMS SignedData

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

Для формирования CAdES-BES требуется закрытый ключ и X.509 сертификат.

Пример генерации ключа RSA:

const kp = KEYUTIL.generateKeypair("RSA", 2048);
const prvKey = kp.prvKeyObj;
const pubKey = kp.pubKeyObj;

Создание самоподписанного сертификата (для демонстрации):

const cert = KJUR.asn1.x509.X509Util.newCertPEM({
  serial: { int: 1 },
  sigalg: "SHA256withRSA",
  notbefore: "20250101000000Z",
  notafter: "20300101000000Z",
  issuer: { str: "/CN=Test CA" },
  subject: { str: "/CN=Signer" },
  sbjpubkey: pubKey,
  ext: [
    { basicConstraints: { cA: false } }
  ],
  cakey: prvKey
});

В реальных системах сертификат поступает из PKI и не генерируется в приложении.


Подготовка данных для подписи

CAdES-BES подписывает произвольные бинарные данные, чаще всего:

  • документы PDF (в виде байтов)
  • JSON-сообщения
  • XML
  • хеши файлов

Пример текстового сообщения:

const data = "Confidential message";

Для CMS структура требует кодирования данных:

const contentInfo = {
  contentType: "data",
  content: data
};

Формирование подписанных атрибутов

CAdES-BES требует минимум набора signed attributes:

  • content-type
  • message-digest
  • signing-time

jsrsasign формирует их автоматически при использовании CMS API, но возможно ручное управление.


Создание CMS SignedData (CAdES-BES)

Основной механизм формирования подписи реализуется через:

KJUR.crypto.CMS.sign

Пример формирования CAdES-BES:

const cms = new KJUR.crypto.CMS();

const signedData = cms.sign({
  content: {
    str: data
  },
  certs: [cert],
  signerPrvKey: prvKey,
  signerCert: cert,
  hashAlg: "sha256",
  econtentType: "data"
});

Результат — CMS SignedData в формате DER/PEM.


Формирование DER и PEM представлений

По умолчанию jsrsasign может возвращать PEM:

console.log(signedData);

Для DER:

const signedDer = cms.sign({
  content: { str: data },
  certs: [cert],
  signerPrvKey: prvKey,
  signerCert: cert,
  hashAlg: "sha256",
  econtentType: "data",
  detached: false,
  asn1obj: true
}).getContentInfo().getContent().getContentInfo().toHex();

Detached и attached подпись

CAdES-BES поддерживает два режима:

Attached signature

Подписанные данные включены в CMS:

detached: false

Detached signature

Подпись хранится отдельно:

detached: true

Пример:

const cmsDetached = cms.sign({
  content: { str: data },
  certs: [cert],
  signerPrvKey: prvKey,
  signerCert: cert,
  hashAlg: "sha256",
  detached: true
});

Detached используется в системах документооборота, где оригинал и подпись хранятся отдельно.


Разбор структуры CAdES-BES в jsrsasign

Сгенерированная CMS структура включает:

  • SignedData
  • EncapsulatedContentInfo
  • SignerInfo
  • SignedAttributes
  • CertificateSet

Разбор SignerInfo:

const si = cms.signerInfoList[0];
console.log(si.signerCert.issuer.str);
console.log(si.signerCert.subject.str);

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

Проверка осуществляется через verify:

const isValid = cms.verify({
  cms: signedData,
  certs: [cert]
});

При успешной проверке подтверждается:

  • целостность данных
  • соответствие сертификата подписи
  • корректность хеширования

Работа с внешними сертификатами

Загрузка PEM сертификата:

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

Извлечение ключа:

const pubKey = KEYUTIL.getKey(certPem);

Использование хеширования в CAdES-BES

Алгоритм хеширования критичен для подписи:

  • SHA-256 — стандарт де-факто
  • SHA-384 — для усиленной безопасности
  • SHA-512 — в высоконагруженных системах защиты

В jsrsasign:

hashAlg: "sha256"

Особенности атрибутов подписи

В CAdES-BES автоматически включаются:

  • Signing Time (signingTime)
  • Content Type (contentType)
  • Message Digest (messageDigest)

Расширенные атрибуты (не обязательные для BES):

  • Signing Certificate V2
  • Commitment Type Indication

Валидация целостности данных

Механизм проверки включает:

  1. вычисление digest исходного контента
  2. сравнение с messageDigest в signed attributes
  3. проверку подписи через публичный ключ сертификата
  4. проверку цепочки сертификатов (если предоставлена)

Практическая структура CMS SignedData

Типичная структура:

SignedData
 ├── version
 ├── digestAlgorithms
 ├── encapContentInfo
 ├── certificates
 ├── signerInfos
      ├── version
      ├── signerIdentifier
      ├── digestAlgorithm
      ├── signedAttributes
      ├── signatureAlgorithm
      ├── signatureValue

jsrsasign абстрагирует ASN.1, но позволяет извлекать любые узлы.


Обработка ошибок формирования подписи

Типовые ошибки:

  • неправильный формат ключа (PEM vs DER)
  • несоответствие алгоритма подписи и ключа
  • отсутствие сертификата в CMS
  • неверное кодирование входных данных

Диагностика:

try {
  cms.sign({...});
} catch(e) {
  console.log(e.message);
}

Практика безопасного хранения ключей

При работе с CAdES-BES в браузере ключи часто оказываются в памяти приложения. Это создаёт риски:

  • утечка через XSS
  • доступ через devtools
  • невозможность контроля окружения

В серверной реализации предпочтительно:

  • хранение ключей в HSM
  • использование PKCS#11
  • изоляция криптографического слоя

Интероперабельность CAdES-BES

Подписи, созданные jsrsasign, совместимы с:

  • ETSI CAdES-стеками
  • Java BouncyCastle CMS
  • OpenSSL PKCS#7 verify
  • .NET SignedCms

При условии корректного выбора алгоритмов и кодировки (DER/PEM).


Типовой сценарий формирования подписи документа

  1. получение документа в бинарном виде
  2. вычисление hash SHA-256
  3. формирование CMS SignedData
  4. добавление сертификата подписанта
  5. сериализация в DER/PEM
  6. передача в систему хранения или проверки

Взаимодействие с PKCS#7

CAdES-BES базируется на PKCS#7 SignedData, где jsrsasign предоставляет низкоуровневый доступ:

const pkcs7 = new KJUR.crypto.PKCS7();

Однако для CAdES предпочтительнее CMS API, так как он корректнее обрабатывает атрибуты.


Использование в браузерных приложениях

Сценарии:

  • электронные договоры
  • подпись форм
  • подтверждение транзакций
  • локальная подпись JSON-запросов

Особенность — выполнение криптографии на клиенте без передачи приватного ключа на сервер.


Расширение до CAdES-T и выше

CAdES-BES часто используется как базовый уровень перед добавлением:

  • CAdES-T (добавление timestamp)
  • CAdES-C (ссылки на OCSP/CRL)
  • CAdES-X Long Term (архивная устойчивость)

jsrsasign сам по себе не реализует полноценный ETSI lifecycle, но служит фундаментом для BES-уровня.