const sm4 = require('sm-crypto').sm4 const sm3 = require('sm-crypto').sm3 const sm2 = require('sm-crypto').sm2 const crypto = require('crypto') const forge = require('node-forge') const rs = require('jsrsasign') module.exports.sm4en3 = function (key, data){ let sm3key = sm3(key) sm3key = sm3key.substring(0, 32) let encryptData = sm4.encrypt(data, sm3key) return encryptData } module.exports.sm4de3 = function (key, data){ let sm3key = sm3(key) sm3key = sm3key.substring(0, 32) let decryptData = sm4.decrypt(data, sm3key) return decryptData } /** * 从 PKCS7 签名中获取信息(包含证书、公钥和签名值) * 支持 RSA 和 SM2 算法 * @param {Buffer|string} p7sign - PKCS7 签名数据 * @returns {Object} 包含证书、公钥和签名值的对象 */ module.exports.getInfoFromPKCS7 = function (p7sign) { // 处理输入数据 let base64Data if (Buffer.isBuffer(p7sign)) { base64Data = p7sign.toString('base64') } else if (typeof p7sign === 'string') { // 判断是否是 PEM 格式 if (p7sign.includes('-----BEGIN PKCS7-----')) { // 提取 base64 部分 base64Data = p7sign .replace(/-----BEGIN PKCS7-----\n?/, '') .replace(/-----END PKCS7-----\n?/, '') .replace(/\n/g, '') } else { // 如果不是 PEM 格式,假设是 base64 base64Data = p7sign } } else { throw new Error('Invalid input type for p7sign') } // 尝试使用 node-forge 解析(支持 RSA) try { const pemData = `-----BEGIN PKCS7-----\n${base64Data}\n-----END PKCS7-----` const p7 = forge.pkcs7.messageFromPem(pemData) return extractInfoFromForgeP7(p7) } catch (forgeError) { // 如果 node-forge 解析失败,尝试使用 jsrsasign(支持 SM2) try { return extractInfoFromJsrsasignP7(base64Data) } catch (jsrsasignError) { throw new Error(`Failed to parse PKCS7: ${forgeError.message}, ${jsrsasignError.message}`) } } } /** * 从 node-forge 解析的 PKCS7 中提取信息(RSA) */ function extractInfoFromForgeP7(p7) { // 辅助函数:将属性数组转换为 DN 字符串 function attrsToDN(attrs) { if (!attrs || attrs.length === 0) return '' return attrs.map(attr => `${attr.name}=${attr.value}`).join(', ') } // 提取证书 let cert = null if (p7.certificates && p7.certificates.length > 0) { const certObj = p7.certificates[0] cert = { subject: certObj.subject.attributes.map(attr => ({ name: attr.name, value: attr.value })), subjectDN: attrsToDN(certObj.subject.attributes), issuer: certObj.issuer.attributes.map(attr => ({ name: attr.name, value: attr.value })), issuerDN: attrsToDN(certObj.issuer.attributes), serialNumber: certObj.serialNumber, notBefore: certObj.validity.notBefore, notAfter: certObj.validity.notAfter, pem: forge.pki.certificateToPem(certObj) } } // 提取公钥 - 从证书中提取公钥对象并转换为 PEM 和 Hex let publicKey = null let publicKeyHex = null if (p7.certificates && p7.certificates.length > 0) { const certObj = p7.certificates[0] // 提取公钥对象并转换为 PEM 格式 if (certObj.publicKey) { publicKey = forge.pki.publicKeyToPem(certObj.publicKey) // 将公钥转换为 hex 格式 const publicKeyAsn1 = forge.pki.publicKeyToAsn1(certObj.publicKey) const publicKeyDer = forge.asn1.toDer(publicKeyAsn1) publicKeyHex = forge.util.bytesToHex(publicKeyDer.getBytes()) } } // 提取签名值 - 从 rawCapture 中获取(已经是原始签名值) let signature = null let signatureHex = null if (p7.rawCapture && p7.rawCapture.signature) { // rawCapture.signature 已经是原始签名值字符串 signature = forge.util.encode64(p7.rawCapture.signature) // 同时提供 hex 格式 signatureHex = forge.util.bytesToHex(p7.rawCapture.signature) } return { cert, publicKey, publicKeyHex, signature, signatureHex } } /** * 从 jsrsasign 解析的 PKCS7 中提取信息(支持 SM2) */ function extractInfoFromJsrsasignP7(base64Data) { const parser = new rs.KJUR.asn1.cms.CMSParser() const p7 = parser.getCMSSignedData(rs.b64tohex(base64Data)) // 提取证书 let cert = null let publicKey = null let publicKeyHex = null // p7.certs 是一个对象,包含 array 属性 const certs = p7.certs && p7.certs.array ? p7.certs.array : [] if (certs.length > 0) { const certPem = certs[0] // 解析证书获取详细信息 const x509 = new rs.X509() x509.readCertPEM(certPem) // 获取主题和颁发者 const subjectStr = x509.getSubjectString() const issuerStr = x509.getIssuerString() // 将字符串转换为数组格式 function parseDNString(dnStr) { if (!dnStr || dnStr === '/') return [] const parts = dnStr.substring(1).split('/') return parts.map(part => { const [name, value] = part.split('=') return { name, value } }) } const subjectAttrs = parseDNString(subjectStr) const issuerAttrs = parseDNString(issuerStr) // 转换为 DN 字符串 function attrsToDN(attrs) { if (!attrs || attrs.length === 0) return '' return attrs.map(attr => `${attr.name}=${attr.value}`).join(', ') } cert = { subject: subjectAttrs, subjectDN: attrsToDN(subjectAttrs), issuer: issuerAttrs, issuerDN: attrsToDN(issuerAttrs), serialNumber: x509.getSerialNumberHex(), notBefore: x509.getNotBefore(), notAfter: x509.getNotAfter(), pem: certPem } // 提取公钥 - 使用 ASN.1 解析提取原始公钥字节 try { // 将 PEM 转换为 hex const certHex = rs.pemtohex(certPem) // 获取 tbsCertificate const tbsCert = rs.ASN1HEX.getTLVbyList(certHex, 0, [0]) // 获取 subjectPublicKeyInfo (在 tbsCertificate 中的索引是 6) const spki = rs.ASN1HEX.getTLVbyList(tbsCert, 0, [6]) // 提取公钥值 (BIT STRING) const publicKeyBitString = rs.ASN1HEX.getTLVbyList(spki, 0, [1]) // BIT STRING 的内容:跳过 tag 03 (2 hex), 跳过 length (2 hex), 跳过 unused bits (2 hex) // 所以从第 6 个字符开始 const publicKeyHexContent = publicKeyBitString.substring(6) // 提取算法 OID 来检测是否是 SM2 const algorithmSeq = rs.ASN1HEX.getTLVbyList(spki, 0, [0]) const oidTlv = rs.ASN1HEX.getTLVbyList(algorithmSeq, 0, [0]) const oidHex = oidTlv.substring(4) // SM2 OID: 1.2.156.10197.1.301 -> hex 2A811CCF550101301 const isSM2 = oidHex.includes('2A811CCF55') || oidHex.includes('2a811ccf55') // 创建完整的 subjectPublicKeyInfo PEM publicKey = rs.KJUR.asn1.ASN1Util.getPEMStringFromHex(spki, 'PUBLIC KEY') // publicKeyHex 就是 subjectPublicKeyInfo 的完整 hex publicKeyHex = spki } catch (e) { // SM2 公钥可能无法通过标准方式获取 console.error('提取 SM2 公钥错误:', e.message) publicKey = null publicKeyHex = null } } // 提取签名值 let signature = null let signatureHex = null if (p7.sinfos && p7.sinfos.length > 0) { const signer = p7.sinfos[0] if (signer.sighex) { signatureHex = signer.sighex signature = rs.hextob64(signer.sighex) } } return { cert, publicKey, publicKeyHex, signature, signatureHex } } /** * 验签 PKCS7 签名(自动判断 RSA 或 SM2 算法) * @param {Buffer|string} p7sign - PKCS7 签名数据 * @param {Buffer|string} [originalData] - 原始数据,当签名是 detech 时必须,当签名是 attach 时不传 * @returns {boolean} 验签结果 */ module.exports.verifyPKCS7 = function (p7sign, originalData) { // 处理输入数据 let base64Data if (Buffer.isBuffer(p7sign)) { base64Data = p7sign.toString('base64') } else if (typeof p7sign === 'string') { // 判断是否是 PEM 格式 if (p7sign.includes('-----BEGIN PKCS7-----')) { // 提取 base64 部分 base64Data = p7sign .replace(/-----BEGIN PKCS7-----\n?/, '') .replace(/-----END PKCS7-----\n?/, '') .replace(/\n/g, '') } else { // 如果不是 PEM 格式,假设是 base64 base64Data = p7sign } } else { throw new Error('Invalid input type for p7sign') } // 准备验证所需的数据 let verifyData if (originalData) { // 当提供了原始数据时,使用原始数据进行验证(detch 模式) if (Buffer.isBuffer(originalData)) { verifyData = originalData } else if (typeof originalData === 'string') { verifyData = Buffer.from(originalData) } else { throw new Error('Invalid input type for originalData') } } // 尝试使用 node-forge 解析和验证(支持 RSA) try { const pemData = `-----BEGIN PKCS7-----\n${base64Data}\n-----END PKCS7-----` const p7 = forge.pkcs7.messageFromPem(pemData) // 如果没有提供原始数据,从 PKCS7 中提取(attach 模式) if (!verifyData) { if (p7.content) { verifyData = Buffer.from(p7.content.getBytes(), 'binary') } else { throw new Error('No content found in PKCS7 signature for attach mode') } } // 对于 RSA 签名,使用公钥手动验证 if (p7.certificates && p7.certificates.length > 0) { const publicKey = p7.certificates[0].publicKey if (publicKey && p7.rawCapture && p7.rawCapture.signature) { // 计算数据的摘要 const md = forge.md.sha256.create() md.update(verifyData.toString(), 'utf8') const digest = md.digest().getBytes() // 使用公钥验证签名 return publicKey.verify(digest, p7.rawCapture.signature) } } // 如果 RSA 验证失败或不适用,尝试使用 node-forge 的 verify(如果支持) return p7.verify() } catch (forgeError) { // 如果 node-forge 解析失败,尝试使用 jsrsasign 解析和验证(支持 SM2) try { return verifyPKCS7WithJsrsasign(base64Data, verifyData) } catch (jsrsasignError) { // 静默处理错误,直接返回 false return false } } } /** * 使用 jsrsasign 验证 PKCS7 签名(支持 SM2) */ function verifyPKCS7WithJsrsasign(base64Data, verifyData) { const parser = new rs.KJUR.asn1.cms.CMSParser() const p7 = parser.getCMSSignedData(rs.b64tohex(base64Data)) // 如果没有提供原始数据,从 PKCS7 中提取(attach 模式) if (!verifyData) { if (p7.econtent && p7.econtent.hex) { // econtent.hex 是原始数据的 hex 编码 verifyData = Buffer.from(p7.econtent.hex, 'hex') } else { throw new Error('No content found in PKCS7 signature for attach mode') } } // 获取签名信息 if (!p7.sinfos || p7.sinfos.length === 0) { throw new Error('No signer info found in PKCS7') } const signer = p7.sinfos[0] // 获取证书和公钥 if (!p7.certs || !p7.certs.array || p7.certs.array.length === 0) { throw new Error('No certificate found in PKCS7') } const certPem = p7.certs.array[0] const x509 = new rs.X509() x509.readCertPEM(certPem) // 判断签名算法 const sigAlg = signer.sigalg // SM2 签名算法 OID: 1.2.156.10197.1.301.1 或 1.2.156.10197.1.501 if (sigAlg === '1.2.156.10197.1.301.1' || sigAlg === '1.2.156.10197.1.501' || sigAlg.includes('sm2')) { // SM2 验证 // 提取公钥 hex const certHex = rs.pemtohex(certPem) const tbsCert = rs.ASN1HEX.getTLVbyList(certHex, 0, [0]) const spki = rs.ASN1HEX.getTLVbyList(tbsCert, 0, [6]) const publicKeyBitString = rs.ASN1HEX.getTLVbyList(spki, 0, [1]) let publicKeyHex = publicKeyBitString.substring(6) // 跳过 tag, length, unused bits // 确保公钥有 04 前缀(sm-crypto 0.3.13 需要) if (!publicKeyHex.startsWith('04')) { publicKeyHex = '04' + publicKeyHex } // 计算 SM3 哈希 const hash = sm3(verifyData.toString()) // 签名格式:用户提供的是 ASN.1 DER 格式,需要使用 der: true 选项 let signatureHex = signer.sighex // 使用 SM2 验证签名 (注意:使用 doVerifySignature,不是 doVerify) // 尝试不同的选项组合,包括 userId const userId = '1234567812345678' // 默认 userId try { // 尝试 1: 使用原始数据 + DER + SM3 + userId const result1 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true, hash: true, userId }) if (result1) return true // 尝试 2: 使用原始数据 + DER + SM3 const result2 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true, hash: true }) if (result2) return true // 尝试 3: 使用原始数据 + DER const result3 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { der: true }) if (result3) return true // 尝试 4: 使用哈希 + DER + SM3 + userId const result4 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true, hash: true, userId }) if (result4) return true // 尝试 5: 使用哈希 + DER + SM3 const result5 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true, hash: true }) if (result5) return true // 尝试 6: 使用哈希 + DER const result6 = sm2.doVerifySignature(hash, signatureHex, publicKeyHex, { der: true }) if (result6) return true // 尝试 7: 不使用 der 选项(直接使用签名) const result7 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex) if (result7) return true // 尝试 8: 不使用 der 选项 + SM3 + userId const result8 = sm2.doVerifySignature(verifyData.toString(), signatureHex, publicKeyHex, { hash: true, userId }) if (result8) return true // 尝试 9: 转换签名格式为原始 r|s 格式 if (signatureHex.startsWith('30')) { try { // 解析 ASN.1 DER 格式 // 格式: 30 len 02 len r 02 len s const rStart = 8 // 30440220 const r = signatureHex.substring(rStart, rStart + 64) // 32字节 = 64 hex 字符 const sStart = rStart + 64 + 4 // 0220 const s = signatureHex.substring(sStart, sStart + 64) // 32字节 = 64 hex 字符 const rawSignature = r + s // 尝试使用原始 r|s 格式 const result9 = sm2.doVerifySignature(verifyData.toString(), rawSignature, publicKeyHex) if (result9) return true // 尝试使用原始 r|s 格式 + SM3 + userId const result10 = sm2.doVerifySignature(verifyData.toString(), rawSignature, publicKeyHex, { hash: true, userId }) if (result10) return true } catch (e) { // 忽略错误,继续尝试 } } } catch (e) { // 忽略错误,继续尝试其他选项 } return false } else { // RSA 验证(通过 jsrsasign) try { const pubKey = x509.getPublicKey() const sig = new rs.KJUR.crypto.Signature({ alg: 'SHA256withRSA' }) sig.init(pubKey) sig.updateString(verifyData.toString()) return sig.verify(signer.sighex) } catch (e) { throw new Error('RSA verification failed: ' + e.message) } } }