Библиотека jsrsasign предоставляет набор низкоуровневых инструментов
для работы с криптографией в JavaScript, включая генерацию ключей,
подпись данных и построение структур X.509. В рамках модуля
KJUR.asn1.x509 реализованы классы, позволяющие формировать
сертификаты на уровне ASN.1, что даёт полный контроль над их
содержимым.
Сертификат X.509 представляет собой ASN.1 структуру, содержащую:
В 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"
});
Поддерживаются варианты:
Issuer представляет центр сертификации (CA) или самоподписанный субъект:
cert.setIssuerByParam({
str: "/C=RU/O=Example CA/OU=IT Department/CN=Example Root CA"
});
Строка формируется в формате Distinguished Name (DN).
Субъект — владелец сертификата:
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.
Определяет, является ли сертификат CA:
cert.appendExtensionByParam({
extname: "basicConstraints",
cA: false
});
Определяет допустимые операции ключа:
cert.appendExtensionByParam({
extname: "keyUsage",
critical: true,
digitalSignature: true,
keyEncipherment: true
});
Используется для доменов и IP:
cert.appendExtensionByParam({
extname: "subjectAltName",
array: [
{ dns: "api.example.com" },
{ dns: "example.com" }
]
});
Финальный этап — подпись приватным ключом издателя:
cert.signByParam({
d: privateKey.prvKeyHex,
alg: "SHA256withRSA"
});
Если создаётся самоподписанный сертификат, используется тот же ключ для issuer и subject.
После подписания сертификат сериализуется:
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, где вручную формируется структура до
подписи:
var tbs = new KJUR.asn1.x509.TBSCertificate();
// установка всех полей аналогично
Затем отдельно создаётся Signature структура и
объединяется с TBS.
Такой подход используется при необходимости полного контроля ASN.1 дерева.
Механизм построения сертификата в KJUR.asn1.x509
отличается от Node.js crypto API:
Это делает библиотеку пригодной для:
RSA ключ не совместим с ECDSA алгоритмом подписи.
Строка должна соответствовать RFC2253-подобному формату:
/C=RU/O=Org/CN=Name
Формат должен быть строго UTC, иначе сертификат может считаться недействительным.
Сертификат в jsrsasign фактически собирается как:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
KJUR.asn1.x509.X509Cert инкапсулирует именно эту
структуру, автоматически сериализуя её в DER перед преобразованием в
PEM.
При построении цепочки сертификатов:
Каждый уровень использует тот же механизм X509Cert, но с
различными issuer/subject связями и ключами.
В сложных системах сертификат может включать:
Пример:
cert.appendExtensionByParam({
extname: "extKeyUsage",
array: ["serverAuth", "clientAuth"]
});
cert.appendExtensionByParam({
extname: "subjectKeyIdentifier"
});
Расчёт происходит автоматически из публичного ключа.
Выбор алгоритма влияет на:
Рекомендуемые варианты:
Помимо PEM доступны:
cert.getHex() // DER hex
cert.getPEM() // PEM формат
DER используется при низкоуровневой передаче, PEM — при хранении и интеграции с TLS.
Сертификаты часто создаются на основе CSR:
var csr = new KJUR.asn1.csr.CSR();
Далее public key извлекается из CSR и вставляется в
X509Cert, а подпись выполняется CA ключом.
Алгоритм работы signByParam:
Модуль представляет собой слой абстракции над ASN.1, обеспечивая:
В отличие от серверных языков:
Это делает генерацию сертификатов возможной даже на клиентской стороне, хотя такой подход ограничен сценариями тестирования и прототипирования.