Практика: проверка статуса сертификата через OCSP

OCSP (Online Certificate Status Protocol) применяется для проверки актуального состояния X.509 сертификата в режиме реального времени. В отличие от CRL (Certificate Revocation List), где требуется загружать список отозванных сертификатов целиком, OCSP позволяет получить ответ только по конкретному сертификату, что делает проверку быстрее и эффективнее в прикладных системах.

В JavaScript библиотека Jsrsasign предоставляет инструменты для работы с криптографией, X.509 сертификатами и OCSP-запросами через пространство имён KJUR. Практическая реализация проверки статуса сертификата через OCSP обычно состоит из трёх этапов: формирование OCSP-запроса, отправка запроса на OCSP-responder и разбор OCSP-ответа.


Для OCSP-запроса требуются:

  • сертификат, статус которого проверяется (end-entity certificate)
  • сертификат удостоверяющего центра (issuer certificate)
  • идентификатор сертификата (serial number, issuer name hash, issuer key hash)

В Jsrsasign работа с сертификатом начинается через класс X509.

const certPEM = `-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----`;

const x509 = new X509();
x509.readCertPEM(certPEM);

const serialNumber = x509.getSerialNumberHex();
const issuer = x509.getIssuerHex();
const subject = x509.getSubjectHex();

Для OCSP важен именно issuer сертификат, так как он используется для построения идентификаторов запроса.


Формирование OCSP запроса

Jsrsasign предоставляет инструменты в пространстве KJUR.asn1.ocsp для создания OCSPRequest.

Основной объект — OCSPReqBuilder.

const ocsp = KJUR.asn1.ocsp;

const reqBuilder = new ocsp.OCSPReqBuilder();

// добавление идентификатора сертификата
reqBuilder.addCertID({
  hashAlg: "sha1",
  issuerCert: issuerCertPEM,
  subjectCert: certPEM
});

const ocspReq = reqBuilder.build();
const ocspReqB64 = hextob64(ocspReq);

OCSP-запрос формируется в ASN.1 структуру и затем кодируется в DER, после чего может быть отправлен на OCSP responder.


Получение OCSP URL из сертификата

Адрес OCSP responder обычно содержится в расширениях сертификата Authority Information Access (AIA).

Jsrsasign позволяет извлечь его следующим образом:

const aia = x509.getExtAIA();
console.log(aia);

В AIA ищется запись вида:

OCSP - URI:http://ocsp.example.com

Этот URL используется для отправки POST-запроса.


Отправка OCSP запроса

OCSP-запрос передаётся по HTTP POST с типом application/ocsp-request.

Пример отправки через fetch:

const ocspUrl = "http://ocsp.example.com";

const response = await fetch(ocspUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/ocsp-request"
  },
  body: hextorstr(ocspReq)
});

const arrayBuffer = await response.arrayBuffer();
const ocspRespHex = hextob64(sab2hex(arrayBuffer));

Разбор OCSP ответа

Ответ OCSP кодируется в ASN.1 структуру. Jsrsasign предоставляет парсер OCSPResp.

const ocspResp = new ocsp.OCSPResp(ocspRespHex);
const status = ocspResp.getStatus();

Статус ответа может быть:

  • 0 — SUCCESSFUL
  • 1 — MALFORMED REQUEST
  • 2 — INTERNAL ERROR
  • 3 — TRY LATER
  • 5 — SIG REQUIRED
  • 6 — UNAUTHORIZED

Если статус успешный, можно извлечь информацию о сертификате:

const basicResp = ocspResp.getBasicOCSPResp();

const responses = basicResp.getResponses();

responses.forEach(r => {
  const certStatus = r.getCertStatus();
  const serial = r.getCertID().getSerialNumber();
});

Интерпретация статуса сертификата

Статус сертификата определяется через поле certStatus.

Возможные значения:

  • GOOD — сертификат действителен
  • REVOKED — сертификат отозван
  • UNKNOWN — статус неизвестен OCSP responder

Пример обработки:

const certStatus = r.getCertStatus();

if (certStatus === "good") {
  console.log("Сертификат действителен");
}

if (certStatus === "revoked") {
  console.log("Сертификат отозван");
}

if (certStatus === "unknown") {
  console.log("Статус неизвестен");
}

Полный пример проверки сертификата через OCSP

async function checkOCSP(certPEM, issuerCertPEM) {
  const x509 = new X509();
  x509.readCertPEM(certPEM);

  const aia = x509.getExtAIA();
  const ocspUrl = aia.match(/http[^\\s]+ocsp[^\\s]+/i)[0];

  const reqBuilder = new KJUR.asn1.ocsp.OCSPReqBuilder();

  reqBuilder.addCertID({
    hashAlg: "sha1",
    issuerCert: issuerCertPEM,
    subjectCert: certPEM
  });

  const ocspReq = reqBuilder.build();

  const response = await fetch(ocspUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/ocsp-request"
    },
    body: hextorstr(ocspReq)
  });

  const buffer = await response.arrayBuffer();
  const ocspRespHex = hextob64(sab2hex(buffer));

  const ocspResp = new KJUR.asn1.ocsp.OCSPResp(ocspRespHex);

  if (ocspResp.getStatus() !== 0) {
    return { error: "OCSP responder error" };
  }

  const basic = ocspResp.getBasicOCSPResp();
  const res = basic.getResponses()[0];

  return {
    serial: res.getCertID().getSerialNumber(),
    status: res.getCertStatus()
  };
}

Особенности работы OCSP в браузере и Node.js

В реальных проектах возникают ограничения:

CORS

OCSP responder часто не разрешает кросс-доменные запросы, что делает прямой вызов из браузера невозможным. В таких случаях используется backend-прокси.

Производительность

OCSP-запросы выполняются для каждого сертификата отдельно, что может создавать нагрузку при массовой проверке.

Безопасность ответа

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


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

Для дополнительной безопасности можно проверить подпись ответа:

const isValid = basic.verifySignature(ocspSignerCertPEM);

Если подпись не проходит проверку, результат OCSP нельзя считать доверенным.


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

Неверный issuer сертификат

OCSP-запрос будет отклонён или вернёт UNKNOWN, если issuer не совпадает с цепочкой сертификата.

Отсутствие AIA расширения

Некоторые сертификаты не содержат OCSP URL, и запрос невозможно сформировать автоматически.

Неправильное кодирование запроса

OCSP требует DER-формат, ошибки часто возникают при неверном преобразовании Base64/HEX.


Практическая архитектура использования OCSP

В прикладных системах проверка OCSP обычно строится следующим образом:

  • клиент получает сертификат
  • извлекается OCSP URL
  • формируется OCSP request
  • запрос отправляется через backend
  • backend кеширует ответы
  • статус возвращается клиенту

Такой подход снижает нагрузку и обходится без CORS-ограничений.


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

OCSP ответы имеют поле nextUpdate, определяющее срок их валидности. Jsrsasign позволяет извлекать временные метки:

const thisUpdate = res.getThisUpdate();
const nextUpdate = res.getNextUpdate();

На основании этих данных можно реализовать локальный кеш, уменьшающий количество запросов к OCSP responder.