SignedData в CMS/PKCS
SignedData содержит несколько ключевых компонентов:
Верификация заключается в том, чтобы:
Jsrsasign предоставляет объект KJUR.asn1.cms.SignedData,
который позволяет декодировать CMS-структуру из PEM или DER.
Основной сценарий начинается с преобразования входных данных:
const cms = new KJUR.asn1.cms.SignedData({
schema: "-----BEGIN CMS----- ... -----END CMS-----"
});
Либо при работе с PKCS#7:
const p7 = new KJUR.asn1.cms.CMSProcessor();
const parsed = p7.parseCMS(signedDataPem);
После парсинга становятся доступны:
Каждый объект signerInfo содержит идентификатор
подписанта и параметры подписи:
const signerInfo = parsed.signerInfos[0];
const certs = parsed.certificates;
Для сопоставления сертификата используется issuer и serialNumber:
const signerCert = certs.find(cert => {
return cert.serialNumber === signerInfo.sid.serialNumber &&
cert.issuer.getString() === signerInfo.sid.issuer.getString();
});
Jsrsasign использует криптографический модуль
KJUR.crypto.Signature для проверки подписи.
Алгоритм проверки:
Пример:
const sig = new KJUR.crypto.Signature({
alg: signerInfo.signatureAlgorithm,
prov: "cryptojs/jsrsa"
});
sig.init(signerCert.getPublicKey());
sig.updateHex(parsed.signedContent.hex);
const isValid = sig.verify(signerInfo.signature);
Если результат true, подпись считается корректной на
криптографическом уровне.
SignedData может использовать различные алгоритмы:
Jsrsasign автоматически сопоставляет OID алгоритма с реализацией:
KJUR.crypto.Util.hashString("SHA256", data);
При верификации важно, чтобы алгоритм хеширования совпадал с
указанным в digestAlgorithm.
Криптографическая валидность подписи не гарантирует доверие к подписанту. Для этого проверяется цепочка сертификатов.
В Jsrsasign можно использовать:
const x509 = new X509();
x509.readCertPEM(signerCert.pem);
const issuerCert = new X509();
issuerCert.readCertPEM(issuerPem);
const isChainValid = x509.verifySignature(issuerCert.getPublicKey());
Цепочка строится рекурсивно до корневого сертификата.
SignedData бывает двух типов:
При detached-сценарии необходимо вручную передать исходный контент:
sig.updateString(originalData);
При embedded:
const content = parsed.signedContent.content;
sig.updateHex(content.hex);
Ошибка выбора режима приводит к неверной верификации даже при корректной подписи.
SignerInfo может содержать:
Jsrsasign позволяет извлекать атрибуты:
const attrs = signerInfo.signedAttrs;
const signingTime = attrs["signingTime"];
При наличии timestamp важно сверять его с доверенным источником времени, если используется политика долгосрочной валидации.
На практике чаще всего встречаются следующие проблемы:
Несовпадение алгоритма хеширования
Подпись может быть создана с SHA-1, а проверка выполняется с SHA-256.
Неверный контент
При detached подписи даже один байт отличия делает подпись недействительной.
Неправильный сертификат
Использование промежуточного или просроченного сертификата приводит к ошибке проверки цепочки.
Повреждённая CMS структура
Некорректное PEM-кодирование или обрезанные данные ломают парсинг ASN.1.
const cms = new KJUR.asn1.cms.SignedData({
schema: signedDataPem
});
const signerInfo = cms.signerInfos[0];
const signerCert = cms.certificates.find(cert =>
cert.serialNumber === signerInfo.sid.serialNumber
);
const sig = new KJUR.crypto.Signature({
alg: signerInfo.signatureAlgorithm,
prov: "cryptojs/jsrsa"
});
sig.init(signerCert.getPublicKey());
if (cms.signedContent.type === "detached") {
sig.updateString(originalContent);
} else {
sig.updateHex(cms.signedContent.hex);
}
const verified = sig.verify(signerInfo.signature);
Факт успешной проверки подписи означает:
Однако это не гарантирует доверие к субъекту подписи без проверки сертификатной инфраструктуры.
Jsrsasign работает полностью в JavaScript, что накладывает особенности:
При работе с большими CMS контейнерами важно учитывать нагрузку на парсинг ASN.1 и операции хеширования.