CMS (Cryptographic Message Syntax) в контексте JavaScript чаще всего реализуется через библиотеку Jsrsasign, где поддерживаются как базовые операции подписи, так и расширенные механизмы ASN.1 структур, включая добавление атрибутов и интеграцию временных меток RFC3161.
CMS SignedData представляет собой контейнер, включающий:
Метка времени в криптографическом смысле обычно реализуется через RFC3161 Time-Stamp Token (TST), который добавляется в CMS как unsigned attribute. Это принципиально важно: временная метка не изменяет подпись, а лишь подтверждает факт существования подписи в конкретный момент времени.
Библиотека Jsrsasign предоставляет пространство имён
KJUR.crypto и KJUR.asn1.cms, через которые
формируются структуры CMS.
Типовая инициализация:
const jsrsasign = require("jsrsasign");
const KJUR = jsrsasign.KJUR;
const hextool = jsrsasign.hextool;
Для CMS используется модуль:
KJUR.asn1.cms.SignedDataПервым этапом создаётся стандартная CMS структура без временной метки.
const msg = "Document for signing";
const cmsSigned = KJUR.asn1.cms.CMSUtil.sign(
msg,
{
cert: CERT_PEM,
key: PRIVATE_KEY_PEM,
alg: "SHA256withRSA"
}
);
Результат — PKCS#7/CMS SignedData, содержащий подпись и сертификат.
Внутри структуры формируется:
Для добавления timestamp требуется TSA (Time Stamping Authority). В
Jsrsasign реализована работа через KJUR.asn1.tsp.
Запрос формируется как TSP Request:
const tspReq = new KJUR.asn1.tsp.TimeStampReq({
hashAlg: "sha256",
imprint: KJUR.crypto.Util.hashString(msg, "sha256")
}).getContentInfo();
Далее запрос отправляется на TSA сервер:
const xhr = new XMLHttpRequest();
xhr.open("POST", TSA_URL, false);
xhr.setRequestHeader("Content-Type", "application/timestamp-query");
xhr.send(tspReq);
Ответ представляет собой DER-структуру TimeStampResp.
Полученный ответ декодируется:
const tspResp = new KJUR.asn1.tsp.TimeStampResp({
str: jsrsasign.ASN1HEX.parseHex(xhr.responseText)
});
const token = tspResp.getTimeStampToken();
TimeStampToken содержит:
CMS поддерживает добавление unsigned attributes. В Jsrsasign это реализуется через модификацию структуры SignedData.
Ключевой атрибут:
id-aa-signatureTimeStampToken (1.2.840.113549.1.9.16.2.14)
Формирование ASN.1 структуры атрибута:
const sigTstAttr = {
type: "1.2.840.113549.1.9.16.2.14",
value: token
};
Далее атрибут добавляется в CMS:
const cms = new KJUR.asn1.cms.SignedData({
content: msg,
certs: [CERT_PEM],
signerInfos: [
{
signKey: PRIVATE_KEY_PEM,
hashAlg: "sha256",
unsignedAttrs: [sigTstAttr]
}
]
});
В результате формируется CMS, где подпись дополнена временной меткой без нарушения целостности signed attributes.
Важно понимать порядок вычислений:
Таким образом, временная метка подтверждает существование подписи после её формирования.
Проверка выполняется через CMS.verify:
const result = KJUR.asn1.cms.CMSUtil.verify({
cms: cmsSigned,
certs: [CERT_PEM]
});
Дополнительно извлекается unsigned attribute:
const attrs = result.signerInfos[0].unsignedAttrs;
Декодирование timestamp token:
const tst = new KJUR.asn1.tsp.TimeStampToken({
hex: attrs[0].value
});
Проверяются:
В прикладных системах CMS с timestamp используется в следующих сценариях:
Типичная схема обработки:
[Документ]
↓
[CMS подпись]
↓
[Хеш отправляется в TSA]
↓
[Получение TimeStampToken]
↓
[Добавление в CMS unsigned attributes]
↓
[Финальный CMS контейнер]
Jsrsasign не абстрагирует TSA как высокоуровневый сервис, поэтому:
Ключевые OID:
1.2.840.113549.1.7.21.2.840.113549.1.9.16.2.142.16.840.1.101.3.4.2.1В CMS допускается несколько unsigned attributes, что позволяет:
При длительном хранении документа добавляются:
Типичные проблемы:
Корректная реализация требует строгого соблюдения:
Алгоритм проверки:
Любое расхождение делает CMS недействительным в юридическом смысле.