Реализация клиентской аутентификации по сертификату

Клиентская аутентификация на основе сертификатов строится вокруг криптографической пары ключей и X.509-сертификата, в котором публичный ключ связывается с идентичностью пользователя или устройства. В классическом сценарии TLS (mTLS) сервер запрашивает сертификат клиента на уровне транспортного соединения, после чего выполняется взаимная проверка доверия.

В браузерной среде прямой контроль над TLS-рукопожатием ограничен, поэтому библиотека Jsrsasign используется в основном для реализации прикладной (application-layer) аутентификации, где криптографическая подпись запросов заменяет или дополняет mTLS.

Криптографическая основа Jsrsasign

Jsrsasign предоставляет набор инструментов для работы с PKI:

  • парсинг и генерация X.509-сертификатов
  • работа с RSA и ECDSA ключами
  • обработка PKCS#1, PKCS#8 и PKCS#12 контейнеров
  • вычисление цифровых подписей (SHA-256, SHA-384, SHA-512)
  • кодирование ASN.1 структур

Ключевые пространства имён:

  • KEYUTIL — импорт и экспорт ключей
  • X509 — работа с сертификатами
  • KJUR.crypto — криптографические операции
  • KJUR.asn1 — построение ASN.1 структур

Загрузка клиентского сертификата из PKCS#12

Распространённый формат для клиентской аутентификации — .p12 (или .pfx), содержащий приватный ключ и сертификат.

const p12Der = ...; // ArrayBuffer или base64
const password = "secret";

const keyObj = KEYUTIL.getKey(p12Der, password);

const certB64 = KEYUTIL.getPEM(keyObj.getCertificate());
const privateKey = keyObj;

В результате получается:

  • приватный ключ для подписи
  • сертификат клиента для идентификации

Извлечение и анализ X.509 сертификата

const cert = new X509();
cert.readCertPEM(certB64);

const subject = cert.getSubjectString();
const issuer = cert.getIssuerString();
const serial = cert.getSerialNumberHex();

Ключевые элементы сертификата:

  • Subject (владелец)
  • Issuer (центр сертификации)
  • Serial Number
  • Public Key
  • Validity period

Формирование прикладной аутентификации через подпись запроса

Поскольку браузер не управляет TLS-рукопожатием, используется схема challenge-response.

Генерация nonce на сервере

Сервер отправляет случайное значение:

{
  "nonce": "8f3a91c2b7d4..."
}

Подпись nonce с использованием приватного ключа

const sig = new KJUR.crypto.Signature({ "alg": "SHA256withRSA" });

sig.init(privateKey);
sig.updateString(nonce);

const signature = sig.sign();
const signatureBase64 = hextob64(signature);

Отправка подписанного запроса

fetch("/auth/verify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    nonce,
    signature: signatureBase64,
    certificate: certB64
  })
});

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

Сервер выполняет:

  1. парсинг сертификата клиента
  2. извлечение публичного ключа
  3. проверку подписи nonce
const x509 = new X509();
x509.readCertPEM(certB64);

const pubKey = x509.getPublicKey();

const sig = new KJUR.crypto.Signature({ "alg": "SHA256withRSA" });
sig.init(pubKey);
sig.updateString(nonce);

const isValid = sig.verify(b64tohex(signature));

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

Аутентификация считается полной только при проверке цепочки доверия:

  • проверка корневого CA
  • проверка промежуточных сертификатов
  • проверка срока действия
  • проверка отзыва (CRL / OCSP)

Jsrsasign позволяет частично валидировать структуру сертификата, но проверка цепочки доверия обычно реализуется на сервере через PKI-библиотеки.


Работа с ECDSA сертификатами

Jsrsasign поддерживает ECDSA подписи:

const sig = new KJUR.crypto.Signature({ "alg": "SHA256withECDSA" });
sig.init(privateKey);
sig.updateString(data);

const sigValue = sig.sign();

ECDSA используется в современных мобильных и аппаратных сертификатах.


Генерация CSR (Certificate Signing Request)

При регистрации клиента может формироваться запрос на сертификат.

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

const csr = new KJUR.asn1.csr.CSR({
  subject: { str: "/CN=user@example.com" },
  sbjpubkey: kp.pubKeyObj,
  sigalg: "SHA256withRSA",
  sbjprvkey: kp.prvKeyObj
});

const pem = csr.getPEM();

CSR передаётся в центр сертификации для выпуска клиентского сертификата.


Использование сертификата как идентификатора пользователя

После успешной проверки сертификата его данные используются как стабильный идентификатор:

  • Subject CN или email
  • Serial Number
  • Thumbprint (SHA-1 / SHA-256 хэш)
const thumbprint = KJUR.crypto.Util.hashHex(certB64, "sha256");

Привязка сессии к сертификату

Сервер может привязывать токен к отпечатку сертификата:

session_id + cert_fingerprint

Это предотвращает повторное использование токенов без приватного ключа.


Гибридная модель: сертификат + JWT

Часто применяется комбинированная схема:

  1. клиент подписывает nonce сертификатом
  2. сервер выдаёт JWT
  3. дальнейшие запросы идут по JWT
  4. JWT связан с сертификатом

Ограничения браузерной среды

Работа с сертификатами в JavaScript имеет ограничения:

  • невозможность доступа к системному хранилищу сертификатов
  • невозможность участия в TLS handshake
  • необходимость импорта PKCS#12 вручную
  • риск утечки приватного ключа в памяти

Безопасное хранение приватного ключа

Jsrsasign не предоставляет аппаратной защиты ключей, поэтому применяются подходы:

  • хранение в защищённом контексте (Secure Context HTTPS)
  • использование временного импорта ключа
  • очистка памяти после использования
  • использование WebCrypto как альтернативы для генерации ключей

Использование WebCrypto совместно с Jsrsasign

WebCrypto может генерировать ключи, а Jsrsasign — обрабатывать сертификаты:

const keyPair = await crypto.subtle.generateKey(
  { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, hash: "SHA-256" },
  true,
  ["sign", "verify"]
);

Экспорт ключа для Jsrsasign:

const exported = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
const key = KEYUTIL.getKey(exported);

Подпись HTTP-запросов на уровне приложения

Расширенная модель включает подпись параметров запроса:

const data = method + url + body + timestamp;

sig.init(privateKey);
sig.updateString(data);

const signature = sig.sign();

Заголовки:

X-Signature: ...
X-Certificate: ...
X-Timestamp: ...

Проверка целостности запроса

Сервер повторяет вычисление строки:

method + url + body + timestamp

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


Защита от replay-атак

Используются:

  • nonce
  • timestamp
  • одноразовые токены
  • кэширование использованных подписей

Разбор цепочки сертификатов

Jsrsasign позволяет анализировать структуру цепочки:

const chain = cert.getExtSubjectAltName();
const basicConstraints = cert.getExtBasicConstraints();

Эти данные используются для определения:

  • допустимости сертификата клиента
  • уровня доверия
  • назначения (clientAuth / serverAuth)

Ошибки валидации и диагностика

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

  • Invalid signature — несоответствие ключа
  • Expired certificate — истёк срок действия
  • Unknown CA — не доверенный центр сертификации
  • Malformed PKCS#12 — повреждённый контейнер

Применение в корпоративных системах

Сертификатная аутентификация через Jsrsasign часто используется в:

  • корпоративных порталах
  • VPN-подобных веб-интерфейсах
  • банковских системах
  • API с повышенными требованиями безопасности

Модель позволяет отказаться от паролей в пользу криптографической идентичности пользователя.