Проверка подписи PKCS#7 (PEM)/распаковка данных в node.js

Я получаю криптопакет PKCS#7 из сторонней системы. Пакет не сжат и не зашифрован, PEM-кодирован, подписан сертификатом X.509. У меня также есть файл сертификата PEM от провайдера.

Данные внутри XML

Мне нужно сделать следующее в Node.JS:

  • извлечь данные
  • проверить подпись

Пример пакета (нет конфиденциальной информации, данные относятся к нашей системе контроля качества) http://pastebin.com/7ay7F99e


person Guard    schedule 12.04.2013    source источник


Ответы (3)


Ваш ответ — большой шаг в правильном направлении. Однако вы упускаете важную часть проверки!

Вы должны сверить хэш данных с дайджестом, содержащимся в подписанных атрибутах. В противном случае кто-то может заменить содержимое вредоносными данными. Попробуйте, например, проверить следующий «пакет» с помощью своего кода (и взгляните на содержимое): http://pastebin.com/kaZ2XQQc

Я не большой разработчик NodeJS (на самом деле это моя первая попытка :p), но вот предложение, которое поможет вам начать работу.

var fs = require('fs');
var crypto = require('crypto');
var pkcs7 = require('./js/pkcs7'); // forge from my own fork
var asn1 = require('./js/asn1');

var folder = '';
var pkg = fs.readFileSync(folder + 'package').toString();
var cert = fs.readFileSync(folder + 'cert.pem').toString();

try {
    var msg = pkcs7.messageFromPem(pkg);
    var attrs = msg.rawCapture.authenticatedAttributes; // got the list of auth attrs
    var set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs); // packed them inside of the SET object
    var buf = new Buffer(asn1.toDer(set).data, 'binary'); // DO NOT forget 'binary', otherwise it tries to interpret bytes as UTF-8 chars

    var sig = msg.rawCapture.signature;

    var shasum = crypto.createHash('sha1'); // better be based on msg.rawCapture.digestAlgorithms
    shasum.update(msg.rawCapture.content.value[0].value[0].value);

    for(var n in attrs) {
        var attrib = attrs[n].value;
        var attrib_type = attrib[0].value;
        var attrib_value = attrib[1].value[0].value;
        if(attrib_type == "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04") { // better would be to use the OID (1.2.840.113549.1.9.4)
            if(shasum.digest('binary') == attrib_value) {
                console.log('hash matches');

                var v = crypto.createVerify('RSA-SHA1');
                v.update(buf);
                console.log(v.verify(cert, sig)); // -> should type true
            } else {
                console.log('hash mismatch');
            }
        }
    }

}
catch (_e) {
    console.dir(_e);
}
person qistoph    schedule 22.04.2013

Хорошо, наконец понял.

Прежде всего, сообщения PKCS представляют собой сложные структуры, закодированные в двоичном виде с использованием ASN1.

Во-вторых, их можно сериализовать в двоичные файлы (кодировка DER) или текстовые файлы PEM с использованием Кодировка Base64.

В-третьих, формат PKCS#7 определяет несколько типов пакетов, из которых my называется Signed Data. Эти форматы отличаются значением OBJECT IDENTIFIER в начале объекта ASN1 (1-й элемент последовательности обертки) — вы можете перейти к http://lapo.it/asn1js/ и вставьте текст пакета для полностью проанализированного структура.

Далее нам нужно разобрать пакет (Base64 -> ASN1 -> некоторое представление объекта). К сожалению, для этого нет пакета npm. Я нашел неплохой проект forge, который не опубликован в реестре npm (хотя и совместим с npm). Он анализировал формат PEM, но результирующее дерево довольно неприятно для обхода. Основываясь на их реализациях Encrypted Data и Enveloped Data, я создал частичную реализацию подписанных данных в своем собственном форке. UPD: мой запрос на включение позже был объединен с проектом forge.

Теперь, наконец, мы разобрали все это. В этот момент я нашел отличную (и, вероятно, единственную во всей сети) пояснительную статью о проверке подписанного PKCS#7: http://qistoph.blogspot.com/2012/01/manual-verify-pkcs7-signed-data-with.html

Мне удалось извлечь и успешно расшифровать подпись из файла, но хеш внутри отличался от хэша данных. Боже, благослови Криса, который объяснил, что на самом деле происходит.

Процесс подписи данных состоит из двух шагов:

  1. вычисляется хеш исходного контента
  2. строится набор «Авторизованных атрибутов», включающий: тип подписываемых данных, время подписи и хэш данных

Затем набор из шага 2 подписывается с использованием закрытого ключа подписывающей стороны.

Из-за особенностей PKCS#7 этот набор атрибутов хранится внутри сконструированного типа, зависящего от контекста (класс = 0x80, тип = 0), но должен быть подписан и проверен как обычный SET (класс = 0, тип = 17).

Как упоминает Крис (https://stackoverflow.com/a/16154756/108533), это только проверяет, что атрибуты в пакет действительны. Мы также должны проверить фактический хэш данных по атрибуту дайджеста.

Итак, наконец, вот код, выполняющий проверку (cert.pem — это файл сертификата, который мне прислал провайдер, package — это закодированное в PEM сообщение, которое я получил от них через HTTP POST):

var fs = require('fs');
var crypto = require('crypto');
var forge = require('forge');
var pkcs7 = forge.pkcs7; 
var asn1 = forge.asn1;
var oids = forge.pki.oids;

var folder = '/a/path/to/files/';
var pkg = fs.readFileSync(folder + 'package').toString();
var cert = fs.readFileSync(folder + 'cert.pem').toString();


var res = true;

try {
    var msg = pkcs7.messageFromPem(pkg);
    var attrs = msg.rawCapture.authenticatedAttributes;
    var set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
    var buf = Buffer.from(asn1.toDer(set).data, 'binary');

    var sig = msg.rawCapture.signature;

    var v = crypto.createVerify('RSA-SHA1');
    v.update(buf);
    if (!v.verify(cert, sig)) {
        console.log('Wrong authorized attributes!');
        res = false;
    }

    var h = crypto.createHash('SHA1');
    var data = msg.rawCapture.content.value[0].value[0].value;
    h.update(data);

    var attrDigest = null;
    for (var i = 0, l = attrs.length; i < l; ++i) {
        if (asn1.derToOid(attrs[i].value[0].value) === oids.messageDigest) {
            attrDigest = attrs[i].value[1].value[0].value;
        }
    }

    var dataDigest = h.digest();
    if (dataDigest !== attrDigest) {
        console.log('Wrong content digest');
        res = false;
    }

}
catch (_e) {
    console.dir(_e);
    res = false;
}

if (res) {
    console.log("It's OK");
}
person Community    schedule 22.04.2013
comment
Спасибо большое - person Mohammed Essehemy; 26.09.2018

на основе формы вдохновения этот ответ , я реализовал пример для подписи и проверки файлов PDF с помощью node-signpdf и подделка узла.

person Mohammed Essehemy    schedule 27.09.2018
comment
Вы должны были похвастаться этим больше, так как ваш вклад высоко ценится здесь: github.com/vbuch/ node-signpdf#verifying, и это может быть обновление принятого ответа. - person Valery; 25.01.2019
comment
После этого я отправил запрос на вытягивание, в любом случае спасибо. - person Mohammed Essehemy; 27.01.2019