Разбор и верификация ответа TSA

Формат RFC3161 TimeStampToken основан на структуре CMS (Cryptographic Message Syntax) и включает в себя подписанное доверенным TSA (Time Stamp Authority) сообщение, содержащее хеш исходных данных и метку времени. В библиотеке jsrsasign работа с TSA-ответами реализуется через разбор ASN.1 структуры, проверку подписи и сопоставление хеша с исходным запросом.

TSA-ответ представляет собой CMS SignedData, внутри которого находится:

  • EncapsulatedContentInfo с типом TSTInfo
  • сертификат TSA
  • подпись TSA
  • набор атрибутов, включая messageImprint, policy, nonce и genTime

Ключевым элементом является TSTInfo, который кодируется в ASN.1 и содержит:

  • version
  • policy
  • messageImprint (хеш исходного документа)
  • serialNumber
  • genTime (время штампа)
  • accuracy (точность времени, опционально)
  • nonce (если использовался в запросе)

Извлечение TSA-ответа в jsrsasign

Работа начинается с декодирования CMS структуры через ASN.1 парсер:

const cms = new KJUR.asn1.cms.CMS();
cms.parse(tsaResponseHex);

После парсинга становится доступен SignedData, из которого извлекается EncapsulatedContentInfo:

const signedData = cms.getContentInfo();
const encapContent = signedData.eContent.valueHex;

Далее содержимое TSTInfo декодируется отдельно:

const tstInfo = KJUR.asn1.ASN1Util.getTLVforItem(encapContent);
const parsedTSTInfo = KJUR.asn1.cades.TSTInfo.getInstanceFromHex(encapContent);

Проверка соответствия хеша

Основная логика верификации заключается в сравнении messageImprint из TSA-ответа с хешем исходных данных.

Формирование ожидаемого хеша:

const md = new KJUR.crypto.MessageDigest({alg: "sha256"});
md.updateString(data);
const digest = md.digest();

Из TSA-ответа извлекается messageImprint:

const tsHash = parsedTSTInfo.messageImprint.hashedMessage;

Сравнение выполняется побайтово:

if (tsHash !== digest) {
    throw new Error("Hash mismatch: TSA response is not valid for provided data");
}

Несовпадение означает либо подмену данных, либо ошибку формирования запроса TSA.

Проверка подписи TSA

Подпись TSA проверяется через сертификат, встроенный в SignedData. В jsrsasign используется KJUR.crypto.Signature:

const sig = new KJUR.crypto.Signature({alg: "SHA256withRSA"});
sig.init(certs[0]);
sig.updateHex(encapContent);
const isValid = sig.verify(signatureHex);

Где:

  • certs[0] — сертификат TSA
  • encapContent — закодированный TSTInfo
  • signatureHex — подпись из SignedData

Если подпись недействительна, доверие к TSA полностью теряется независимо от совпадения хеша.

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

TSA-сертификат должен быть проверен по цепочке доверия до корневого CA. В jsrsasign используется X509 и Validator:

const x509 = new X509();
x509.readCertHex(tsaCertHex);

const isChainValid = KJUR.crypto.CertChain.verify({
    certs: [tsaCert, intermediateCert],
    trustedCerts: [rootCert]
});

Проверка включает:

  • валидность подписи сертификата TSA
  • непросроченность сертификатов
  • соответствие политике TSA (Extended Key Usage: timeStamping)

Проверка nonce и политики

Если в запросе TSA использовался nonce, его необходимо сверить:

if (parsedTSTInfo.nonce !== requestNonce) {
    throw new Error("Nonce mismatch in TSA response");
}

Политика TSA проверяется через OID:

const policy = parsedTSTInfo.policy;

Несовпадение политики может означать использование другого TSA или отклонение от требуемого профиля RFC3161.

Проверка времени штампа

Поле genTime определяет момент, когда TSA зафиксировал хеш:

const genTime = parsedTSTInfo.genTime;
const timestamp = new Date(genTime);

Дополнительно может учитываться accuracy:

  • seconds
  • millis
  • micros

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

Полный цикл верификации TSA-ответа

Логическая последовательность проверки:

  1. Декодирование CMS структуры
  2. Извлечение TSTInfo
  3. Проверка messageImprint
  4. Верификация подписи TSA
  5. Проверка цепочки сертификатов
  6. Сопоставление nonce (если используется)
  7. Проверка политики TSA
  8. Анализ genTime и accuracy

Типичные ошибки при разборе TSA

На практике часто встречаются следующие проблемы:

  • некорректная ASN.1 декодировка из-за DER/BER несоответствия
  • использование неправильного алгоритма хеширования (SHA-1 вместо SHA-256)
  • отсутствие доверенного корневого сертификата TSA
  • игнорирование nonce, приводящее к replay-атакам
  • попытка проверки подписи без учета EncapsulatedContentInfo

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

jsrsasign предоставляет низкоуровневый доступ к ASN.1 структурам, поэтому:

  • большинство проверок выполняется вручную
  • разработчик сам управляет разбором CMS
  • требуется явная работа с hex-данными
  • нет автоматической абстракции TSA-клиента

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

Взаимодействие с RFC3161 на уровне библиотеки

RFC3161 определяет строгий формат:

  • TimeStampReq (запрос)
  • TimeStampResp (ответ)
  • TSTInfo (внутренний контейнер)

jsrsasign реализует не протокол TSA, а инструменты для:

  • генерации хеша
  • формирования и разбора ASN.1 структур
  • проверки криптографической подписи

Поэтому логика протокола полностью собирается из отдельных компонентов библиотеки.

Практическая модель проверки целостности данных

С точки зрения безопасности TSA-ответ выполняет роль криптографического доказательства существования данных в определенный момент времени. Проверка в jsrsasign сводится к установлению трех свойств:

  • неизменность данных (через messageImprint)
  • подлинность источника (через подпись TSA)
  • корректность доверенной цепочки (через сертификаты)

Нарушение любого из этих свойств делает TSA-ответ недействительным как доказательство времени.