无法使用 webcrypto API 验证 CMS 签名消息

Unable to verify a CMS signed message with webcrypto API

我想用 JavaScript webcrypto API 验证 CMS 签名消息。根据 RFC 3161,该消息是时间戳响应的一部分。

所以我获取签名、内容(来自 encapContentInfo)和 public 密钥并将其传递给以下导入密钥并验证签名的函数:

function verify(signature, data, publicKey, callback) {

  console.log("pubkey: " + publicKey);
  console.log("data: " + data);
  console.log("signature: " + signature);

  crypto.subtle.importKey(
    "spki",
    new Uint8Array(hexToArray(publicKey)),
    {
      name: "RSASSA-PKCS1-v1_5",
      hash: {name: "SHA-1"}
    },
    false,
    ["verify"]
    )
    .then(function(publicKey){

      console.log(publicKey);

      crypto.subtle.verify(
        {
          name: "RSASSA-PKCS1-v1_5",
          hash: {name: "SHA-1"}
        },
        publicKey,
        new Uint8Array(hexToArray(signature)),
        new Uint8Array(hexToArray(data))
        )
        .then(function(isvalid){
          console.log("valid: " + isvalid);
        })
        .catch(function(err){
          throw(err);
        });

    })
    .catch(function(err){
      throw(err);
    });
}

输出如下:

pubkey: 30820122300d06092a864886f70d01010105000382010f003082010a0282010100a9ac33b296da7177999d464f47aa4a40d57d58dcfd93beae68913ab75cb77fe36c4b52b3b55a53cce10f70880a81aba4ffdc1d4826fe645cbabcd1e0b4eceff702f6fb378670128eadbe39a4a9e484c1d01f95fcfcbd44ca091dcc344e0356ca8967f54f7f6acc0dd5af8c1a4f77003fe01c3b98d6611d52b3fe432962544e142cc6f99163ccb7798bb8d4aea948d0cd6f72b740915b87ca2824ac9ec958ab0e5eacb36a7a66be091e826f862849026aa911e3b1a84487f6654aad7f3be4d1d9d312b2f9fcd7c69836ae893060393a47b310a6a4b03eeea6c8659df57782fa75855007d5ffb622ff8d229edd57c0771149b7fc827780fcde0c02f82bc2977d250203010001
data: 306b020101060c2b0601040181ad21822c16013031300d0609608648016503040201050004201b14e43e38297d534d827e351c15347f9eebc973258c8b555c044de46c5a0f02021424e3f636950c119fb3ebdb289d60d7bc637f3bd9180f32303136303431393139333931395a
signature: 7a65069868d97fb0ffcd53bca6b80daa671e1b0ac1a1d262ba2fba1525d0ca8e4998d4f49cba990f9a89c52003457ca1bbb037dee8e5e64c617af0c1ea72215b648477b052165810f4f6eb7f869ac19373b2aad1a2b5a809b8b758bdad540a5cd1f33d3c80870c7ae9c6db61dd7c7f8c346ee3c7aadc16f90ed87833a4ba771cbdc930a6dfb3fd16f5ab57de212deddc4d49c11ef825a8d996ba40e0e07c7c5788000d61169fe7512c97d29f7ff4b8ce2842e5b339dae5cef1eb517457b3e8b98bc887dda952b6346bd8345e5eb2cdd976fe5688d375551bc2a20cd7aafd1bbf6a9d102ad2a8dea620ad3ed6763f0841ec020dc1ad485ed1448ae5f5d511ef8f
CryptoKey {}
valid: false

如你所见,验证失败,但我很确定签名是有效的,因为这个时间戳服务器被很多人使用。

我的实现有什么问题吗?

我尝试在 Firefox 扩展中执行此操作。

编辑:似乎需要使用signedAttrs字段的DER值,对其进行哈希处理,然后使用证书进行验证。

我自己设法做到了:您首先必须使用 public 密钥解密签名以获得包含消息摘要的 ASN.1 对象。

例如openssl rsautl -inkey cert.pem -pubin -in signature -out out

然后你拿signedAttrs对象,把开头的显式标签(0xA0)替换成set标签(0x31),因为RFC中有下面这句话:

The IMPLICIT [0] tag in the signedAttrs is not used for the DER encoding, rather an EXPLICIT SET OF tag is used

然后计算集合对象的摘要(使用与 signerInfo 中的 digestAlgorithm 相同的算法)。

如果签名有效,则两个摘要值相同。