【问题标题】:Verify PKCS#7 (PEM) signature / unpack data in node.js验证 PKCS#7 (PEM) 签名/解压 node.js 中的数据
【发布时间】:2013-04-12 11:04:27
【问题描述】:

我从第 3 方系统获得 PKCS#7 加密包。 该包未压缩,未加密,PEM 编码,使用 X.509 证书签名。 我还有来自提供商的 PEM 证书文件。

里面的数据是XML

我需要在 Node.JS 中执行以下操作:

  • 提取数据
  • 验证签名

一个示例包(无敏感信息,数据参考我们的质量保证系统)http://pastebin.com/7ay7F99e

【问题讨论】:

    标签: javascript node.js pem pkcs#7


    【解决方案1】:

    好的,终于明白了。

    首先,PKCS 消息是使用ASN1 二进制编码的复杂结构。

    其次,它们可以序列化为二进制文件 (DER encoding) 或使用Base64 编码的文本 PEM 文件。

    第三,PKCS#7 format 指定了几种包类型,其中 my 被称为签名数据。这些格式通过 ASN1 对象(包装器序列的第一个元素)开头的 OBJECT IDENTIFIER 值来区分 - 您可以转到 http://lapo.it/asn1js/ 并粘贴 package text 以获得完全解析的结构。

    接下来,我们需要解析包(Base64 -> ASN1 -> 一些对象表示)。不幸的是,没有 npm 包。我发现了一个相当不错的项目forge,它没有发布到 npm 注册表(尽管 npm 兼容)。它解析了 PEM 格式,但是遍历生成的树是一件非常不愉快的事情。基于他们的 Encrypted Data 和 Enveloped Data 实现,我在自己的 fork 中创建了 Signed Data 的部分实现。 UPD:我的拉取请求后来被合并到了 forge 项目中。

    现在我们终于解析了整个内容。 那时,我发现了一篇关于签名 PKCS#7 验证的精彩(可能是整个网络上唯一的)解释性文章:http://qistoph.blogspot.com/2012/01/manual-verify-pkcs7-signed-data-with.html

    我能够从文件中提取并成功解码签名,但其中的哈希值与数据的哈希值不同。上帝保佑 explained 实际发生的事情的克里斯。

    数据签名过程分为两步:

    1. 计算原始内容的哈希
    2. 构建了一组“授权属性”,包括:签名数据的类型、签名时间和数据哈希

    然后使用签名者的私钥对步骤 2 中的集合进行签名。

    由于 PKCS#7 的具体规定,这组属性存储在特定于上下文的构造类型 (class=0x80, type=0) 中,但应该像普通 SET (class=0, type=17) 一样进行签名和验证.

    正如 Chris 提到的 (https://stackoverflow.com/a/16154756/108533),这仅验证包中的属性是否有效。我们还应该根据摘要属性验证实际数据哈希。

    所以最后这是一个验证代码(cert.pem 是提供商发送给我的证书文件,package 是我通过 HTTP POST 从他们那里获得的 PEM 编码消息):

    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");
    }
    

    【讨论】:

    • 非常感谢
    【解决方案2】:

    您的回答是朝着正确方向迈出的一大步。但是,您错过了验证的重要部分!

    您应该根据签名属性中包含的摘要验证数据的哈希值。否则,有人可能会用恶意数据替换内容。例如,尝试使用您的代码验证以下“包”(并查看内容):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);
    }
    

    【讨论】:

      【解决方案3】:

      基于灵感表单this answer,我实现了一个sample,用于使用node-signpdfnode-forge 对pdf 文件进行签名和验证。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-03
      • 1970-01-01
      • 2020-03-05
      • 2017-07-07
      • 2015-11-09
      相关资源
      最近更新 更多