CAdES-T: подпись с меткой времени

Назначение CAdES-T и место в инфраструктуре электронной подписи

CAdES-T (CMS Advanced Electronic Signatures with Time) представляет собой расширение базового формата CAdES-BES/CAdES-EPES, в котором добавляется доверенная метка времени, подтверждающая существование подписи в конкретный момент.

Ключевая особенность формата заключается в том, что он фиксирует не только факт подписания, но и юридически значимое время, полученное от доверенного сервера меток времени (TSA — Time Stamping Authority).

Основная цель применения CAdES-T:

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

В контексте Jsrsasign этот формат реализуется через комбинирование CMS-подписи и RFC3161 timestamp token.


Архитектура CAdES-T подписи

CAdES-T строится поверх базовой CMS SignedData структуры:

  1. Формируется электронная подпись (CAdES-BES / CAdES-EPES)
  2. Вычисляется хэш SignedData
  3. Хэш отправляется в TSA
  4. TSA возвращает RFC3161 TimeStampToken
  5. Token встраивается в структуру подписи как unsigned attribute

Ключевой элемент — это поле:

  • signatureTimeStampToken

Оно содержит ASN.1 структуру с ответом TSA.


Общий процесс формирования подписи в Jsrsasign

Jsrsasign предоставляет набор классов для работы с CMS и PKI:

  • KJUR.crypto.CMS — формирование CMS SignedData
  • KJUR.asn1.tsp — работа с TimeStamp Protocol
  • KJUR.crypto.TSPUtil — утилиты для TSA

Типичный процесс состоит из двух этапов: создание подписи и добавление timestamp.


Формирование базовой CMS подписи

Перед добавлением метки времени создаётся обычная CMS подпись:

const cms = new KJUR.crypto.CMS();

const signedData = cms.sign({
  content: {
    str: "Hello CAdES-T document"
  },
  certs: [certPEM],
  privateKey: privateKeyPEM,
  hashAlg: "sha256",
  econtentType: "data"
});

На этом этапе формируется CAdES-BES структура.


Вычисление хэша для TSA

Для получения метки времени требуется hash от подписанных данных.

Jsrsasign использует DER-encoded SignedData:

const sigHex = signedData.toHex();
const sigHash = KJUR.crypto.Util.hashHex(sigHex, "sha256");

Этот хэш отправляется в TSA как request message imprint.


Формирование запроса к TSA (RFC3161)

Jsrsasign поддерживает создание TimeStamp Request:

const tspReq = new KJUR.asn1.tsp.TimeStampReq({
  hashAlg: "sha256",
  messageImprint: sigHash,
  nonce: Math.floor(Math.random() * 1000000),
  certReq: true
});

const reqHex = tspReq.getEncodedHex();

Запрос отправляется на TSA сервер через HTTP POST.


Отправка запроса и получение TimeStampToken

Запрос отправляется к TSA endpoint:

const xhr = new XMLHttpRequest();
xhr.open("POST", "https://tsa.example.com/tsp", false);
xhr.setRequestHeader("Content-Type", "application/timestamp-query");
xhr.send(new Uint8Array(KJUR.crypto.Util.hexToBytes(reqHex)));

const tspResponseHex = KJUR.crypto.Util.arrayBufferToHex(xhr.response);

Ответ содержит ASN.1 структуру TimeStampResp.


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

Из ответа извлекается TimeStampToken:

const tspResp = new KJUR.asn1.tsp.TimeStampResp({ 
  hex: tspResponseHex 
});

const tokenHex = tspResp.getTimeStampTokenHex();

Token включает:

  • policy identifier
  • message imprint
  • serial number TSA
  • genTime
  • signature TSA

Встраивание метки времени в CMS (CAdES-T)

После получения токена он добавляется в unsigned attributes подписи:

signedData.addUnsignedAttribute({
  attrType: "signatureTimeStampToken",
  attrValues: [tokenHex]
});

На этом этапе структура становится CAdES-T.


Структура итоговой подписи CAdES-T

CMS структура после добавления timestamp включает:

  • SignedData

    • version

    • digestAlgorithms

    • encapContentInfo

    • certificates

    • signerInfos

      • signedAttributes

      • signature

      • unsignedAttributes

        • signatureTimeStampToken

Внутренний формат TimeStampToken

RFC3161 token включает ASN.1 структуру:

  • TSTInfo

    • version
    • policy
    • messageImprint
    • serialNumber
    • genTime
    • accuracy (опционально)
    • ordering
    • nonce

Особое значение имеет genTime, фиксирующее момент времени, подтверждённый TSA.


Проверка CAdES-T подписи

Проверка включает два уровня:

1. Проверка CMS подписи

const cmsVerifier = new KJUR.crypto.CMSVerifier();
const result = cmsVerifier.verify({
  cms: signedDataHex,
  certs: [certPEM]
});

2. Проверка timestamp token

const tspVerifier = new KJUR.asn1.tsp.TimeStampRespVerifier();

const isValidTSP = tspVerifier.verify({
  tsrHex: tokenHex,
  dataHash: sigHash
});

Проверяется:

  • соответствие hash оригинальной подписи
  • валидность подписи TSA
  • цепочка сертификатов TSA

Роль TSA и доверенная модель

TSA выступает как независимая третья сторона, подтверждающая существование подписи.

Модель доверия:

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

Критически важно, чтобы TSA использовал:

  • синхронизацию времени (NTP)
  • защищённый ключ подписи
  • валидный сертификат от доверенного CA

Типовые ошибки при работе с CAdES-T

Несовпадение хэша

Если изменён SignedData после формирования hash:

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

Использование неподписанного TSA

Если TSA не доверен:

  • проверка timestamp теряет юридическую силу

Повреждение ASN.1 структуры

Jsrsasign чувствителен к:

  • некорректному DER encoding
  • обрезанным hex данным

Практическая структура кода CAdES-T в Jsrsasign

Полный процесс можно свести к следующей последовательности:

// 1. CMS подпись
const cms = new KJUR.crypto.CMS();
const signed = cms.sign({
  content: { str: "Document" },
  certs: [certPEM],
  privateKey: privateKeyPEM,
  hashAlg: "sha256"
});

// 2. Хэш подписи
const hash = KJUR.crypto.Util.hashHex(signed.getContentInfoHex(), "sha256");

// 3. TSA request
const req = new KJUR.asn1.tsp.TimeStampReq({
  hashAlg: "sha256",
  messageImprint: hash
});

// 4. Получение ответа TSA
const responseHex = sendToTSA(req.getEncodedHex());

// 5. Извлечение token
const resp = new KJUR.asn1.tsp.TimeStampResp({ hex: responseHex });
const token = resp.getTimeStampTokenHex();

// 6. Добавление в подпись
signed.addUnsignedAttribute({
  attrType: "signatureTimeStampToken",
  attrValues: [token]
});

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

Jsrsasign реализует CAdES-T не как отдельный объект, а как расширение CMS-структуры.

Это означает:

  • нет отдельного класса CAdEST
  • timestamp добавляется вручную
  • контроль структуры остаётся на разработчике

Такой подход даёт гибкость, но требует точного понимания ASN.1 модели.


Криптографическая устойчивость CAdES-T

Уровень устойчивости определяется комбинацией:

  • алгоритм подписи (RSA/ECDSA)
  • хэш-функция (SHA-256 и выше)
  • доверие к TSA
  • целостность CMS структуры

CAdES-T не защищает от подделки документа, но фиксирует момент существования подписи, что критично для юридических сценариев и архивирования.