Практика: получение метки времени от публичного TSA

Метка времени от доверенного сервиса TSA (Time Stamping Authority) представляет собой криптографически подписанное подтверждение того, что определённые данные существовали не позднее указанного момента времени. Основой служит стандарт RFC 3161, описывающий формат запроса (TimeStampReq) и ответа (TimeStampResp).

В контексте JavaScript библиотека Jsrsasign предоставляет инструменты для формирования запроса временной метки, отправки его на TSA-сервер и проверки полученного ответа.

Ключевая идея процесса:

  • вычисляется хэш исходных данных
  • формируется запрос к TSA
  • TSA подписывает хэш своим приватным ключом
  • возвращается структура TimeStampToken (TST)

Хеширование данных перед запросом TSA

Временная метка никогда не ставится на «сырые» данные. Всегда используется криптографический отпечаток.

В Jsrsasign хэширование выполняется через KJUR.crypto.MessageDigest.

Пример получения SHA-256:

const msg = "Данные, требующие временной метки";

const md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "cryptojs"});
const hashHex = md.digestString(msg);

Важно: TSA работает именно с хэшем, а не с исходным текстом.


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

Запрос TSA включает:

  • алгоритм хэширования
  • сам хэш
  • опциональный nonce (защита от повторов)
  • policy (при необходимости)

В Jsrsasign используется пространство имён KJUR.asn1.tsp.

Формирование структуры запроса

const nonce = new Date().getTime().toString();

const tspReq = new KJUR.asn1.tsp.TimeStampReq({
    hashAlg: "sha256",
    messageImprint: hashHex,
    nonce: nonce
});

Получение бинарного запроса:

const reqHex = tspReq.getEncodedHex();

Отправка запроса на TSA сервер

TSA обычно принимает запросы по HTTP POST с MIME типом:

application/timestamp-query

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

const tsaUrl = "https://freetsa.org/tsr";

const response = await fetch(tsaUrl, {
    method: "POST",
    headers: {
        "Content-Type": "application/timestamp-query",
        "Accept": "application/timestamp-reply"
    },
    body: KJUR.b64utob(reqHex) // перевод в бинарный формат
});

Ответ приходит в бинарном формате DER.


Разбор ответа TSA (TimeStampResp)

Ответ содержит:

  • статус обработки запроса
  • сам токен времени (TST)
  • подпись TSA
  • сертификат TSA

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

const resBuffer = await response.arrayBuffer();
const resHex = Buffer.from(resBuffer).toString("hex");

const tspResp = new KJUR.asn1.tsp.TimeStampResp({data: resHex});

Извлечение временной метки из TST

Самая важная часть — это genTime, реальное время фиксации TSA.

const tstInfo = tspResp.getTSTInfo();

const genTime = tstInfo.genTime; // строка времени
const serialNumber = tstInfo.serialNumber;
const policy = tstInfo.policy;

genTime обычно представлен в формате UTC:

2026-05-02T10:15:30Z

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

Проверка гарантирует, что:

  • ответ действительно от TSA
  • данные не были изменены

Jsrsasign использует X.509 проверку сертификата:

const cert = tspResp.getSignerCert();

const isValid = tspResp.verifySignature({
    cert: cert
});

При необходимости можно дополнительно проверить цепочку доверия через X509.


Работа с TST (TimeStampToken)

TST содержит ASN.1 структуру CMS SignedData.

Основные элементы:

  • signedData
  • signerInfo
  • certificates
  • encapContentInfo

Извлечение:

const tstHex = tspResp.getTimeStampToken();

const tst = new KJUR.asn1.cms.SignedData({data: tstHex});

Использование публичных TSA-серверов

На практике применяются публичные TSA:

  • http://timestamp.digicert.com
  • https://freetsa.org/tsr
  • TSA банковских и гос. PKI инфраструктур

Поведение серверов может различаться:

  • обязательность nonce
  • поддерживаемые алгоритмы (SHA-1 / SHA-256 / SHA-512)
  • требования к policy

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

Несовпадение алгоритма хэша

TSA отклоняет запрос, если алгоритм не поддерживается:

  • SHA-1 устарел
  • предпочтение SHA-256

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

TSPReq должен быть в DER бинарном виде, а не строкой.


Отсутствие nonce

Некоторые TSA требуют nonce для защиты от replay-атак.


Полный цикл получения временной метки

async function getTimestamp(msg) {
    const md = new KJUR.crypto.MessageDigest({alg: "sha256"});
    const hashHex = md.digestString(msg);

    const nonce = Date.now().toString();

    const req = new KJUR.asn1.tsp.TimeStampReq({
        hashAlg: "sha256",
        messageImprint: hashHex,
        nonce: nonce
    });

    const reqHex = req.getEncodedHex();

    const res = await fetch("https://freetsa.org/tsr", {
        method: "POST",
        headers: {
            "Content-Type": "application/timestamp-query"
        },
        body: KJUR.b64utob(reqHex)
    });

    const resBuf = await res.arrayBuffer();
    const resHex = Buffer.from(resBuf).toString("hex");

    const tspResp = new KJUR.asn1.tsp.TimeStampResp({data: resHex});

    const tstInfo = tspResp.getTSTInfo();

    return {
        time: tstInfo.genTime,
        serial: tstInfo.serialNumber
    };
}

Практическое значение временной метки

Криптографическая метка времени используется для:

  • фиксации авторства документа
  • юридически значимых электронных подписей
  • доказательства существования файла в момент времени
  • защиты от ретроспективного изменения данных

Фактически TSA превращает хэш в доказательство существования информации в конкретный момент, не раскрывая саму информацию.