Сервер службы доверенного времени (TSA, Time-Stamp Authority)
принимает запросы, соответствующие стандарту RFC 3161. Основной объект
запроса — структура TimeStampReq, формируемая в ASN.1
DER-формате и передаваемая обычно по HTTP POST.
Запрос всегда строится вокруг контрольной суммы исходных данных, а не самих данных. Это ключевой принцип: TSA не получает файл, он получает его хеш.
Центральный элемент запроса — MessageImprint, который
включает:
Формально структура выглядит так:
= (, )
В контексте Jsrsasign алгоритм обычно задаётся строкой
(SHA-256, SHA-1, SHA-512), а хеш
вычисляется заранее через KJUR.crypto.MessageDigest.
Перед созданием TSA-запроса данные приводятся к байтовому представлению, после чего вычисляется digest:
const data = "data to timestamp";
const md = new KJUR.crypto.MessageDigest({ alg: "sha256", prov: "cryptojs" });
md.updateString(data);
const hashHex = md.digest();
Хеш обязательно должен быть в бинарно-hex формате, поскольку ASN.1 структура ожидает OCTET STRING.
Запрос TSA включает несколько полей:
version — версия протокола (обычно 1)messageImprint — хеш и алгоритмnonce — случайное число для защиты от replay-атакcertReq — флаг запроса сертификата TSAОбобщённая структура:
= (v, MI, nonce, certReq)
Jsrsasign предоставляет высокоуровневый конструктор TSA-запросов
через KJUR.asn1.tsp.TimeStampReq.
const tsq = new KJUR.asn1.tsp.TimeStampReq({
hashAlg: "sha256",
hashValue: hashHex,
nonce: "12345678",
certreq: true
});
Внутри библиотека автоматически:
Nonce используется для защиты от повторной отправки старых TSA-ответов. Обычно это случайное 64-битное число.
const nonce = Math.floor(Math.random() * 1000000000).toString();
С точки зрения протокола nonce включается в запрос и должен совпасть в ответе TSA.
После создания объекта выполняется кодирование:
const reqDer = tsq.getContentInfo().getContent().toHex();
Результат — бинарная DER-структура, которую нельзя модифицировать без нарушения ASN.1 целостности.
TSA обычно принимает запросы с MIME-type:
application/timestamp-queryПередача выполняется в бинарном виде или base64 (в зависимости от сервера).
Пример подготовки:
const base64Req = hextob64(reqDer);
HTTP-заголовки:
Content-Type: application/timestamp-query
Accept: application/timestamp-reply
Ответ TSA приходит в формате TimeStampResp, также ASN.1
DER.
fetch("https://tsa.example.com", {
method: "POST",
headers: {
"Content-Type": "application/timestamp-query",
"Accept": "application/timestamp-reply"
},
body: hextorstr(reqDer)
})
.then(res => res.arrayBuffer())
После получения ответа выполняется разбор:
const resp = new KJUR.asn1.tsp.TimeStampResp({
data: arrayBufferToHex(buffer)
});
Ответ TSA включает:
CMS-структура внутри содержит:
Ключевой этап — сравнение:
Несовпадение означает подмену или ошибку формирования запроса.
Если TSA ожидает SHA-256, а отправлен SHA-1, сервер отклоняет запрос без криптографической обработки.
Jsrsasign требует строго hex-строк без префиксов и пробелов.
Некоторые TSA допускают отсутствие nonce, но это снижает криптографическую стойкость ответа.
Ручная модификация байтовой строки приводит к невалидному ASN.1 и отказу TSA.
Jsrsasign скрывает низкоуровневую ASN.1 сложность:
Внутренне библиотека использует KJUR.asn1.ASN1Object для
построения дерева объектов, которое затем сериализуется в DER.
Вместо строки данные могут быть:
Пример для файла:
const fileBuffer = await file.arrayBuffer();
const md = new KJUR.crypto.MessageDigest({ alg: "sha256" });
md.updateHex(arrayBufferToHex(fileBuffer));
const hashHex = md.digest();
Формирование запроса не просто техническая операция, а подготовка доказуемой временной привязки данных:
= H() + T_{TSA} +
где временная метка становится частью криптографического доказательства существования данных в момент времени.