Построение сертификата с помощью KJUR.asn1.x509

Библиотека jsrsasign предоставляет набор низкоуровневых инструментов для работы с криптографией в JavaScript, включая генерацию ключей, подпись данных и построение структур X.509. В рамках модуля KJUR.asn1.x509 реализованы классы, позволяющие формировать сертификаты на уровне ASN.1, что даёт полный контроль над их содержимым.

Сертификат X.509 представляет собой ASN.1 структуру, содержащую:

  • версию сертификата
  • серийный номер
  • алгоритм подписи
  • издателя (issuer)
  • срок действия (validity)
  • субъект (subject)
  • открытый ключ субъекта
  • расширения (extensions)
  • подпись сертификата

В jsrsasign эти элементы собираются в объект KJUR.asn1.x509.X509Cert, который инкапсулирует построение ASN.1 дерева и последующую сериализацию в PEM/DER формат.


Базовый класс построения сертификата

Основной инструмент:

  • KJUR.asn1.x509.X509Cert

Он предоставляет методы для задания всех полей сертификата и финальной генерации.

Дополнительно используются:

  • KJUR.crypto.KeyObject или KEYUTIL — для работы с ключами
  • KJUR.asn1.x509.TBSCertificate — для формирования тела сертификата
  • KJUR.asn1.x509.AlgorithmIdentifier — для алгоритма подписи

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

Перед созданием сертификата требуется RSA или EC ключ:

var rsaKey = KEYUTIL.generateKeypair("RSA", 2048);
var privateKey = rsaKey.prvKeyObj;
var publicKey = rsaKey.pubKeyObj;

Открытый ключ будет включён в сертификат, закрытый — использоваться для подписи.


Формирование базового сертификата

Создание объекта сертификата начинается с инициализации:

var cert = new KJUR.asn1.x509.X509Cert();

Далее задаются основные параметры.


Версия и серийный номер

Версия X.509 обычно фиксируется как v3:

cert.setVersionByParam({
  int: 3
});

cert.setSerialNumberByParam({
  hex: "01A23F56BC89"
});

Серийный номер должен быть уникальным в рамках издателя.


Алгоритм подписи

Определяет криптографическую схему:

cert.setSignatureAlgByParam({
  name: "SHA256withRSA"
});

Поддерживаются варианты:

  • SHA1withRSA (устаревший)
  • SHA256withRSA
  • SHA384withRSA
  • ECDSAwithSHA256

Издатель (Issuer)

Issuer представляет центр сертификации (CA) или самоподписанный субъект:

cert.setIssuerByParam({
  str: "/C=RU/O=Example CA/OU=IT Department/CN=Example Root CA"
});

Строка формируется в формате Distinguished Name (DN).


Субъект (Subject)

Субъект — владелец сертификата:

cert.setSubjectByParam({
  str: "/C=RU/O=Example Company/OU=Development/CN=api.example.com"
});

В TLS-сценариях CN часто соответствует доменному имени.


Срок действия сертификата

Период валидности задаётся двумя датами:

cert.setNotBeforeByParam({
  str: "240101000000Z"
});

cert.setNotAfterByParam({
  str: "250101000000Z"
});

Формат — ASN.1 UTCTime или GeneralizedTime в UTC.


Открытый ключ субъекта

Добавление публичного ключа:

cert.setSubjectPublicKeyByGetKey(publicKey);

Этот ключ будет использоваться для проверки подписи, шифрования и TLS-обмена.


Расширения сертификата

Расширения являются критически важной частью X.509 v3.

Basic Constraints

Определяет, является ли сертификат CA:

cert.appendExtensionByParam({
  extname: "basicConstraints",
  cA: false
});

Key Usage

Определяет допустимые операции ключа:

cert.appendExtensionByParam({
  extname: "keyUsage",
  critical: true,
  digitalSignature: true,
  keyEncipherment: true
});

Subject Alternative Name

Используется для доменов и IP:

cert.appendExtensionByParam({
  extname: "subjectAltName",
  array: [
    { dns: "api.example.com" },
    { dns: "example.com" }
  ]
});

Подпись сертификата

Финальный этап — подпись приватным ключом издателя:

cert.signByParam({
  d: privateKey.prvKeyHex,
  alg: "SHA256withRSA"
});

Если создаётся самоподписанный сертификат, используется тот же ключ для issuer и subject.


Генерация PEM-вывода

После подписания сертификат сериализуется:

var pem = cert.getPEM();

Результат:

-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----

Полный пример создания самоподписанного сертификата

var kp = KEYUTIL.generateKeypair("RSA", 2048);

var cert = new KJUR.asn1.x509.X509Cert();

cert.setVersionByParam({ int: 3 });

cert.setSerialNumberByParam({ hex: "01A1B2C3D4E5" });

cert.setSignatureAlgByParam({
  name: "SHA256withRSA"
});

cert.setIssuerByParam({
  str: "/C=RU/O=Local Org/CN=Local CA"
});

cert.setSubjectByParam({
  str: "/C=RU/O=Local Org/CN=localhost"
});

cert.setNotBeforeByParam({
  str: "260101000000Z"
});

cert.setNotAfterByParam({
  str: "270101000000Z"
});

cert.setSubjectPublicKeyByGetKey(kp.pubKeyObj);

cert.appendExtensionByParam({
  extname: "basicConstraints",
  cA: true
});

cert.appendExtensionByParam({
  extname: "keyUsage",
  critical: true,
  keyCertSign: true,
  cRLSign: true
});

cert.signByParam({
  d: kp.prvKeyObj.prvKeyHex,
  alg: "SHA256withRSA"
});

var pemCert = cert.getPEM();

Работа с сертификатом через TBSCertificate

Низкоуровневый вариант предполагает использование TBSCertificate, где вручную формируется структура до подписи:

var tbs = new KJUR.asn1.x509.TBSCertificate();
// установка всех полей аналогично

Затем отдельно создаётся Signature структура и объединяется с TBS.

Такой подход используется при необходимости полного контроля ASN.1 дерева.


Особенности реализации jsrsasign

Механизм построения сертификата в KJUR.asn1.x509 отличается от Node.js crypto API:

  • нет автоматической абстракции CA
  • все поля задаются явно
  • ASN.1 структура формируется вручную
  • подпись выполняется через переданный приватный ключ

Это делает библиотеку пригодной для:

  • генерации тестовых сертификатов
  • реализации собственных CA
  • криптографических учебных задач
  • серверных сценариев без OpenSSL

Типовые ошибки при построении сертификатов

Несоответствие алгоритма подписи и ключа

RSA ключ не совместим с ECDSA алгоритмом подписи.

Неверный формат DN

Строка должна соответствовать RFC2253-подобному формату:

/C=RU/O=Org/CN=Name

Некорректные даты

Формат должен быть строго UTC, иначе сертификат может считаться недействительным.


Внутреннее представление ASN.1

Сертификат в jsrsasign фактически собирается как:

Certificate ::= SEQUENCE {
  tbsCertificate TBSCertificate,
  signatureAlgorithm AlgorithmIdentifier,
  signatureValue BIT STRING
}

KJUR.asn1.x509.X509Cert инкапсулирует именно эту структуру, автоматически сериализуя её в DER перед преобразованием в PEM.


Использование CA-цепочек

При построении цепочки сертификатов:

  1. Root CA создаёт self-signed сертификат
  2. Intermediate CA подписывается Root CA
  3. End-entity сертификат подписывается Intermediate CA

Каждый уровень использует тот же механизм X509Cert, но с различными issuer/subject связями и ключами.


Расширенные сценарии генерации

В сложных системах сертификат может включать:

  • CRL Distribution Points
  • Authority Key Identifier
  • Subject Key Identifier
  • Extended Key Usage (serverAuth, clientAuth)

Пример:

cert.appendExtensionByParam({
  extname: "extKeyUsage",
  array: ["serverAuth", "clientAuth"]
});

Формирование идентификаторов ключей

cert.appendExtensionByParam({
  extname: "subjectKeyIdentifier"
});

Расчёт происходит автоматически из публичного ключа.


Управление криптографическим уровнем

Выбор алгоритма влияет на:

  • размер подписи
  • совместимость с браузерами
  • безопасность TLS-соединений

Рекомендуемые варианты:

  • SHA256withRSA для универсальных систем
  • SHA384withRSA для повышенной безопасности
  • ECDSAwithSHA256 для современных инфраструктур

Сериализация и экспорт

Помимо PEM доступны:

cert.getHex()   // DER hex
cert.getPEM()   // PEM формат

DER используется при низкоуровневой передаче, PEM — при хранении и интеграции с TLS.


Интеграция с PKCS#10 запросами

Сертификаты часто создаются на основе CSR:

var csr = new KJUR.asn1.csr.CSR();

Далее public key извлекается из CSR и вставляется в X509Cert, а подпись выполняется CA ключом.


Внутренний процесс подписи

Алгоритм работы signByParam:

  1. Формируется TBSCertificate
  2. Сериализуется в DER
  3. Хэшируется (SHA-256 и др.)
  4. Подписывается приватным ключом
  5. Подпись кодируется в BIT STRING
  6. Собирается финальный ASN.1 Certificate

Роль KJUR.asn1.x509 в архитектуре jsrsasign

Модуль представляет собой слой абстракции над ASN.1, обеспечивая:

  • построение структур без ручного ASN.1 кодирования
  • поддержку всех стандартных расширений X.509
  • интеграцию с криптографическим ядром jsrsasign
  • совместимость с OpenSSL-экосистемой

Особенности использования в JavaScript окружении

В отличие от серверных языков:

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

Это делает генерацию сертификатов возможной даже на клиентской стороне, хотя такой подход ограничен сценариями тестирования и прототипирования.