From f9d920334912122673e56fda68c4636a36f736a4 Mon Sep 17 00:00:00 2001 From: cheney Date: Sat, 4 Apr 2026 17:45:53 +0800 Subject: [PATCH] =?UTF-8?q?sm2=20=E9=AA=8C=E7=AD=BE=E4=B8=8D=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kit/src/crypto.js | 121 ++++++++++++++++++++------ kit/test/debug_sm2_api.js | 25 ++++++ kit/test/debug_sm2_verify.js | 80 +++++++++++++++++ kit/test/verify_sm2_with_real_data.js | 32 +++++++ 4 files changed, 229 insertions(+), 29 deletions(-) create mode 100644 kit/test/debug_sm2_api.js create mode 100644 kit/test/debug_sm2_verify.js create mode 100644 kit/test/verify_sm2_with_real_data.js diff --git a/kit/src/crypto.js b/kit/src/crypto.js index 276539f..4001895 100644 --- a/kit/src/crypto.js +++ b/kit/src/crypto.js @@ -253,24 +253,25 @@ function extractInfoFromJsrsasignP7(base64Data) { */ module.exports.verifyPKCS7 = function (p7sign, originalData) { // 处理输入数据 - let pemData + let base64Data if (Buffer.isBuffer(p7sign)) { - pemData = p7sign.toString('ascii') + base64Data = p7sign.toString('base64') } else if (typeof p7sign === 'string') { // 判断是否是 PEM 格式 if (p7sign.includes('-----BEGIN PKCS7-----')) { - pemData = p7sign + // 提取 base64 部分 + base64Data = p7sign + .replace(/-----BEGIN PKCS7-----\n?/, '') + .replace(/-----END PKCS7-----\n?/, '') + .replace(/\n/g, '') } else { - // 如果不是 PEM 格式,假设是 base64,包装成 PEM - pemData = `-----BEGIN PKCS7-----\n${p7sign}\n-----END PKCS7-----` + // 如果不是 PEM 格式,假设是 base64 + base64Data = p7sign } } else { throw new Error('Invalid input type for p7sign') } - // 解析 PKCS7 数据 - const p7 = forge.pkcs7.messageFromPem(pemData) - // 准备验证所需的数据 let verifyData if (originalData) { @@ -282,17 +283,22 @@ module.exports.verifyPKCS7 = function (p7sign, originalData) { } else { throw new Error('Invalid input type for originalData') } - } else { - // 当没有提供原始数据时,从 PKCS7 中提取数据进行验证(attach 模式) - if (p7.content) { - verifyData = Buffer.from(p7.content.getBytes(), 'binary') - } else { - throw new Error('No content found in PKCS7 signature for attach mode') - } } - // 验证签名 + // 尝试使用 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 @@ -309,22 +315,79 @@ module.exports.verifyPKCS7 = function (p7sign, originalData) { // 如果 RSA 验证失败或不适用,尝试使用 node-forge 的 verify(如果支持) return p7.verify() - } catch (error) { - // 如果验证失败,尝试使用 SM2 验证 + } catch (forgeError) { + // 如果 node-forge 解析失败,尝试使用 jsrsasign 解析和验证(支持 SM2) try { - // 提取签名值和公钥 - const signInfo = module.exports.getInfoFromPKCS7(p7sign) - if (!signInfo.publicKey || !signInfo.signature) { - throw new Error('Cannot extract public key or signature from PKCS7') - } - - // 使用 SM2 验证签名 - const hash = sm3(verifyData.toString()) - return sm2.doVerify(hash, signInfo.signature, signInfo.publicKey) - } catch (sm2Error) { - console.error('SM2 verification error:', sm2Error) + 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]) + const publicKeyHex = publicKeyBitString.substring(6) // 跳过 tag, length, unused bits + + // 计算 SM3 哈希 + const hash = sm3(verifyData.toString()) + + // 使用 SM2 验证签名 (注意:使用 doVerifySignature,不是 doVerify) + return sm2.doVerifySignature(hash, signer.sighex, publicKeyHex) + } 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) + } + } +} + diff --git a/kit/test/debug_sm2_api.js b/kit/test/debug_sm2_api.js new file mode 100644 index 0000000..10e2232 --- /dev/null +++ b/kit/test/debug_sm2_api.js @@ -0,0 +1,25 @@ +const smCrypto = require('sm-crypto'); + +console.log('=== 调试 sm-crypto API ==='); +console.log('sm-crypto 模块:', Object.keys(smCrypto)); +console.log('sm2 对象:', Object.keys(smCrypto.sm2)); +console.log('sm3 对象:', typeof smCrypto.sm3); + +// 测试 SM3 +const testData = 'test'; +const hash = smCrypto.sm3(testData); +console.log('\nSM3 哈希:', hash); + +// 生成密钥对 +const keypair = smCrypto.sm2.generateKeyPairHex(); +console.log('\n生成的密钥对:'); +console.log(' 私钥:', keypair.privateKey.substring(0, 20) + '...'); +console.log(' 公钥:', keypair.publicKey.substring(0, 20) + '...'); + +// 签名 +const sig = smCrypto.sm2.doSignature(hash, keypair.privateKey); +console.log('\n签名:', sig); + +// 验证签名 +const verifyResult = smCrypto.sm2.doVerifySignature(hash, sig, keypair.publicKey); +console.log('验证结果:', verifyResult); diff --git a/kit/test/debug_sm2_verify.js b/kit/test/debug_sm2_verify.js new file mode 100644 index 0000000..f046413 --- /dev/null +++ b/kit/test/debug_sm2_verify.js @@ -0,0 +1,80 @@ +const rs = require('jsrsasign'); +const sm2 = require('sm-crypto').sm2; +const sm3 = require('sm-crypto').sm3; + +// 用户提供的 SM2 PKCS7 签名数据 +const sm2P7Base64 = 'MIIDKgYKKoEcz1UGAQQCAqCCAxowggMWAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICNzCCAjMwggHWoAMCAQICBgD1tfRoKTAMBggqgRzPVQGDdQUAMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBMB4XDTI2MDQwMjA4MDEzOVoXDTM2MDMzMDA4MDEzOVowTTELMAkGA1UEBhMCQ04xDzANBgNVBAoMBlBCQ0NSQzEtMCsGA1UEAwwkOTRmMWY5MzgtZjBiZC00NzYyLTkyOGUtZjY4YTdjY2UxNDFkMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEu3dU5Vn20VjVL9xGCCRPB/EpATY6HW24+zIZv46Cfv0R2PGM+ZJs/dYarw7qJCnpIbe5T0PKmyyJITNVrVnnMaOBrDCBqTAfBgNVHSMEGDAWgBRUqfVQHMpD+o+YdrPK1JlRJL757zAJBgNVHRMEAjAAME8GA1UdHwRIMEYwRKBCoECkPjA8MQ4wDAYDVQQDDAVjcmw3MTEMMAoGA1UECwwDY3JsMQ8wDQYDVQQKDAZQQkNDUkMxCzAJBgNVBAYTAkNOMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU5bG6U0lYzWtkPJE74M/3HdafKKcwDAYIKoEcz1UBg3UFAANJADBGAiEAuLTE36/Eobcui23bFiPvOU4MOT0O0r9SyyHZ1JuvLwMCIQCejuSk03jyUAkbh1SaPx5n7WlDjI6tx9v9RJ7iw5rlojGBtzCBtAIBATBKMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBAgYA9bX0aCkwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARGMEQCIALTgWMD7io1JHfzInBwzDQpe9bonchuLFSED//JLDtnAiBmEUtr+ssAQIzFG4wY0CAj9QZxhENyCU2UYL1fhaysUA=='; + +// 测试用的原始数据 +const mockOriginalData = 'QDJNHHVB_FWQ'; + +console.log('=== 调试 SM2 PKCS7 验证 ==='); +console.log('原始数据:', mockOriginalData); + +const parser = new rs.KJUR.asn1.cms.CMSParser(); +const p7 = parser.getCMSSignedData(rs.b64tohex(sm2P7Base64)); + +console.log('\nPKCS7 信息:'); +console.log('版本:', p7.version); +console.log('哈希算法:', p7.hashalgs); +console.log('签名者数量:', p7.sinfos ? p7.sinfos.length : 0); + +if (p7.sinfos && p7.sinfos.length > 0) { + const signer = p7.sinfos[0]; + console.log('\n签名者信息:'); + console.log(' 签名算法:', signer.sigalg); + console.log(' 签名值 (hex):', signer.sighex); + console.log(' 签名值长度:', signer.sighex ? signer.sighex.length / 2 : 0, '字节'); +} + +// 提取原始数据(econtent) +if (p7.econtent && p7.econtent.hex) { + const extractedData = Buffer.from(p7.econtent.hex, 'hex').toString(); + console.log('\n从 PKCS7 提取的原始数据:', extractedData); + console.log('与提供的原始数据匹配:', extractedData === mockOriginalData); +} + +// 提取公钥 +if (p7.certs && p7.certs.array && p7.certs.array.length > 0) { + const certPem = p7.certs.array[0]; + 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]); + const publicKeyHex = publicKeyBitString.substring(6); + + console.log('\n公钥信息:'); + console.log(' 公钥 (hex):', publicKeyHex.substring(0, 50) + '...'); + console.log(' 公钥长度:', publicKeyHex.length / 2, '字节'); + + // 尝试验证签名 + if (p7.sinfos && p7.sinfos.length > 0) { + const signer = p7.sinfos[0]; + + // 计算 SM3 哈希 + const hash = sm3(mockOriginalData); + console.log('\nSM3 哈希:', hash); + + // 尝试验证 + console.log('\n尝试使用 sm-crypto 验证:'); + try { + // sm-crypto 的 doVerify 需要 hash 和公钥 + // 注意:sm-crypto 的 API 可能需要不同的格式 + const result = sm2.doVerify(hash, signer.sighex, publicKeyHex); + console.log('验证结果:', result); + } catch (e) { + console.error('验证错误:', e.message); + + // 尝试其他格式 + console.log('\n尝试其他格式...'); + try { + // 可能需要去掉 04 前缀 + const pubKeyWithoutPrefix = publicKeyHex.substring(2); + const result = sm2.doVerify(hash, signer.sighex, pubKeyWithoutPrefix); + console.log('验证结果 (去掉前缀):', result); + } catch (e2) { + console.error('验证错误 (去掉前缀):', e2.message); + } + } + } +} diff --git a/kit/test/verify_sm2_with_real_data.js b/kit/test/verify_sm2_with_real_data.js new file mode 100644 index 0000000..9032a35 --- /dev/null +++ b/kit/test/verify_sm2_with_real_data.js @@ -0,0 +1,32 @@ +const crypto = require('../src/crypto.js'); + +// 用户提供的 SM2 PKCS7 签名数据 +const sm2P7Base64 = 'MIIDKgYKKoEcz1UGAQQCAqCCAxowggMWAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICNzCCAjMwggHWoAMCAQICBgD1tfRoKTAMBggqgRzPVQGDdQUAMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBMB4XDTI2MDQwMjA4MDEzOVoXDTM2MDMzMDA4MDEzOVowTTELMAkGA1UEBhMCQ04xDzANBgNVBAoMBlBCQ0NSQzEtMCsGA1UEAwwkOTRmMWY5MzgtZjBiZC00NzYyLTkyOGUtZjY4YTdjY2UxNDFkMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEu3dU5Vn20VjVL9xGCCRPB/EpATY6HW24+zIZv46Cfv0R2PGM+ZJs/dYarw7qJCnpIbe5T0PKmyyJITNVrVnnMaOBrDCBqTAfBgNVHSMEGDAWgBRUqfVQHMpD+o+YdrPK1JlRJL757zAJBgNVHRMEAjAAME8GA1UdHwRIMEYwRKBCoECkPjA8MQ4wDAYDVQQDDAVjcmw3MTEMMAoGA1UECwwDY3JsMQ8wDQYDVQQKDAZQQkNDUkMxCzAJBgNVBAYTAkNOMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU5bG6U0lYzWtkPJE74M/3HdafKKcwDAYIKoEcz1UBg3UFAANJADBGAiEAuLTE36/Eobcui23bFiPvOU4MOT0O0r9SyyHZ1JuvLwMCIQCejuSk03jyUAkbh1SaPx5n7WlDjI6tx9v9RJ7iw5rlojGBtzCBtAIBATBKMEAxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZQQkNDUkMxIDAeBgNVBAMMF1BCQ0NSQyBTTTIgT3BlcmF0aW9uIENBAgYA9bX0aCkwDAYIKoEcz1UBgxEFADANBgkqgRzPVQGCLQEFAARGMEQCIALTgWMD7io1JHfzInBwzDQpe9bonchuLFSED//JLDtnAiBmEUtr+ssAQIzFG4wY0CAj9QZxhENyCU2UYL1fhaysUA=='; + +const rs = require('jsrsasign'); + +console.log('=== 使用真实原始数据验证 SM2 PKCS7 ==='); + +// 从 PKCS7 中提取原始数据 +const parser = new rs.KJUR.asn1.cms.CMSParser(); +const p7 = parser.getCMSSignedData(rs.b64tohex(sm2P7Base64)); + +let realOriginalData = null; +if (p7.econtent && p7.econtent.hex) { + realOriginalData = Buffer.from(p7.econtent.hex, 'hex').toString(); + console.log('从 PKCS7 提取的原始数据:', realOriginalData); +} + +// 使用正确的原始数据进行验证 +if (realOriginalData) { + console.log('\n使用提取的原始数据进行验证:'); + const result = crypto.verifyPKCS7(sm2P7Base64, realOriginalData); + console.log('验证结果:', result); +} else { + console.log('无法从 PKCS7 中提取原始数据'); +} + +// 也测试一下不提供原始数据的情况(attach 模式) +console.log('\n使用 attach 模式(不提供原始数据):'); +const result2 = crypto.verifyPKCS7(sm2P7Base64); +console.log('验证结果:', result2);