Формат RFC3161 TimeStampToken основан на структуре CMS (Cryptographic Message Syntax) и включает в себя подписанное доверенным TSA (Time Stamp Authority) сообщение, содержащее хеш исходных данных и метку времени. В библиотеке jsrsasign работа с TSA-ответами реализуется через разбор ASN.1 структуры, проверку подписи и сопоставление хеша с исходным запросом.
TSA-ответ представляет собой CMS SignedData, внутри которого находится:
Ключевым элементом является TSTInfo, который кодируется в ASN.1 и содержит:
Работа начинается с декодирования 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 проверяется через сертификат, встроенный в 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);
Где:
Если подпись недействительна, доверие к 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 использовался 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:
Фактическая проверка сводится к анализу допустимого диапазона времени относительно бизнес-логики системы.
Логическая последовательность проверки:
На практике часто встречаются следующие проблемы:
jsrsasign предоставляет низкоуровневый доступ к ASN.1 структурам, поэтому:
Это дает гибкость, но увеличивает ответственность за корректность проверки.
RFC3161 определяет строгий формат:
jsrsasign реализует не протокол TSA, а инструменты для:
Поэтому логика протокола полностью собирается из отдельных компонентов библиотеки.
С точки зрения безопасности TSA-ответ выполняет роль криптографического доказательства существования данных в определенный момент времени. Проверка в jsrsasign сводится к установлению трех свойств:
Нарушение любого из этих свойств делает TSA-ответ недействительным как доказательство времени.