无法使用 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 相同的算法)。
如果签名有效,则两个摘要值相同。
我想用 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 相同的算法)。
如果签名有效,则两个摘要值相同。