В инфраструктуре открытых ключей (PKI) идентификация субъекта сертификата осуществляется через поля Distinguished Name (DN) и расширения. Исторически основным полем для указания доменного имени выступал Common Name (CN), однако с развитием стандартов и требований безопасности ключевую роль взяло на себя расширение Subject Alternative Name (SAN).
Современные стандарты (например, RFC 6125) предписывают использовать SAN как основной источник проверки имени хоста, а CN рассматривается как устаревший fallback.
В библиотеке Jsrsasign сертификат представляется как ASN.1-структура, закодированная в DER и обычно передаваемая в PEM-формате.
CN находится внутри:
tbsCertificate.subjectSAN располагается в расширениях:
tbsCertificate.extensions.subjectAltNameДля извлечения этих данных используется класс X509.
const { X509 } = require('jsrsasign');
const certPEM = `-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----`;
const x509 = new X509();
x509.readCertPEM(certPEM);
После инициализации объект x509 предоставляет методы для
доступа к различным частям сертификата.
Метод getSubjectString() возвращает строковое
представление DN:
const subject = x509.getSubjectString();
Пример результата:
/C=US/O=Example Corp/CN=www.example.com
Для извлечения CN:
const cnMatch = subject.match(/CN=([^\/]+)/);
const commonName = cnMatch ? cnMatch[1] : null;
Особенности:
Альтернативный способ — использование ASN.1 разбора:
const subjectObj = x509.getSubject();
Этот метод возвращает объект с атрибутами, где CN можно получить напрямую.
Jsrsasign предоставляет метод:
const san = x509.getExtSubjectAltName();
Результат — массив объектов:
[
{ type: 2, value: 'example.com' },
{ type: 2, value: 'www.example.com' },
{ type: 7, ip: '192.168.1.1' }
]
Типы:
2 — DNS имя7 — IP-адрес1 — emailФильтрация DNS-имен:
const dnsNames = san
.filter(entry => entry.type === 2)
.map(entry => entry.value);
Современная логика проверки:
Если SAN присутствует:
Если SAN отсутствует:
Это правило необходимо реализовывать вручную при использовании Jsrsasign, так как библиотека не выполняет автоматическую проверку имени хоста.
Пример функции проверки:
function matchHostname(hostname, cert) {
const x509 = new X509();
x509.readCertPEM(cert);
const san = x509.getExtSubjectAltName();
if (san) {
const dnsNames = san
.filter(e => e.type === 2)
.map(e => e.value);
return dnsNames.some(name => matchPattern(hostname, name));
}
const subject = x509.getSubjectString();
const cnMatch = subject.match(/CN=([^\/]+)/);
const cn = cnMatch ? cnMatch[1] : null;
return cn ? matchPattern(hostname, cn) : false;
}
Wildcard-сертификаты (*.example.com) требуют особой
логики:
function matchPattern(hostname, pattern) {
if (pattern.startsWith('*.')) {
const domain = pattern.slice(2);
return hostname.endsWith(domain) &&
hostname.split('.').length === domain.split('.').length + 1;
}
return hostname === pattern;
}
Особенности:
*.example.com соответствует
sub.example.comdeep.sub.example.comexample.comЕсли подключение осуществляется по IP, необходимо:
const ipEntries = san.filter(e => e.type === 7).map(e => e.ip);
const isValid = ipEntries.includes(targetIP);
CN в этом случае не используется.
Игнорирование SAN
Неправильная обработка wildcard
*.com или *.co.uk
недопустимоСравнение без нормализации
function normalize(host) {
return host.toLowerCase();
}
Для IDN:
const punycode = require('punycode/');
const ascii = punycode.toASCII(hostname);
const hasSAN = !!x509.getExtSubjectAltName();
Если SAN отсутствует — сертификат считается устаревшим, но может использоваться в legacy-сценариях.
Расширение SAN в ASN.1:
SubjectAltName ::= GeneralNames
GeneralNames ::= SEQUENCE OF GeneralName
GeneralName ::= CHOICE {
dNSName [2] IA5String,
iPAddress [7] OCTET STRING,
...
}
Jsrsasign абстрагирует эту структуру, предоставляя удобный JSON-подобный формат.
Проверка CN/SAN — только часть полной валидации:
Jsrsasign поддерживает большинство этих операций, но проверка имени реализуется вручную.
function validateCertificate(certPEM, hostname) {
const x509 = new X509();
x509.readCertPEM(certPEM);
const san = x509.getExtSubjectAltName();
let valid = false;
if (san) {
const dnsNames = san
.filter(e => e.type === 2)
.map(e => e.value.toLowerCase());
valid = dnsNames.some(name => matchPattern(hostname, name));
} else {
const subject = x509.getSubjectString();
const cnMatch = subject.match(/CN=([^\/]+)/);
const cn = cnMatch ? cnMatch[1].toLowerCase() : null;
valid = cn ? matchPattern(hostname, cn) : false;
}
return valid;
}
В Node.js часто используется встроенный TLS, где проверка уже реализована
Jsrsasign применяется для:
В браузере:
Это дает гибкость, но требует строгого соблюдения стандартов при разработке.
SAN может содержать:
type: 6)type: 1)type: 4)Jsrsasign возвращает их в общем массиве, что позволяет реализовать специфическую логику проверки.
X509 снижает
нагрузкуРекомендуется использовать:
Сертификаты с:
Негативные кейсы:
Эти правила критичны для предотвращения атак и соответствия современным стандартам TLS.