From 88acc24e8622598736c64ede4f88289bf9219534 Mon Sep 17 00:00:00 2001 From: cheney Date: Sat, 18 Apr 2026 15:14:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=81=E4=B9=A6=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 22 ++++- package.json | 2 + src/App.tsx | 134 +++++++++++++++++++++++++++++++ src/__tests__/certs/oca11sm2.cer | Bin 0 -> 691 bytes src/__tests__/certs/sm2.cer | Bin 0 -> 497 bytes src/utils/algorithm/cert.ts | 74 +++++++++++++++++ src/version.js | 2 +- 7 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 src/__tests__/certs/oca11sm2.cer create mode 100644 src/__tests__/certs/sm2.cer create mode 100644 src/utils/algorithm/cert.ts diff --git a/package-lock.json b/package-lock.json index ff0da87..dd47761 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "0.0.0", "dependencies": { "@peculiar/webcrypto": "^1.5.0", + "@types/node-forge": "^1.3.14", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "gm-crypto": "*", "gmsm-sm2js": "^0.7.1", "gmsm-sm3js": "^0.2.0", "gmsm-sm4js": "^0.7.0", + "node-forge": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sjcl-with-all": "^1.0.8", @@ -2417,12 +2419,20 @@ "version": "25.6.0", "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.19.0" } }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmmirror.com/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -6386,6 +6396,15 @@ "dev": true, "license": "MIT" }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", @@ -7792,7 +7811,6 @@ "version": "7.19.2", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { diff --git a/package.json b/package.json index ec8dbf8..1d8b953 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ }, "dependencies": { "@peculiar/webcrypto": "^1.5.0", + "@types/node-forge": "^1.3.14", "buffer": "^6.0.3", "crypto-browserify": "^3.12.1", "gm-crypto": "*", "gmsm-sm2js": "^0.7.1", "gmsm-sm3js": "^0.2.0", "gmsm-sm4js": "^0.7.0", + "node-forge": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sjcl-with-all": "^1.0.8", diff --git a/src/App.tsx b/src/App.tsx index f713853..bcf5d15 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { VERSION } from './version' import { sm3Digest, sm3Hmac } from './utils/algorithm/sm3' import { sm4Encrypt, sm4Decrypt } from './utils/algorithm/sm4' import { sm2GenerateKeyPair, sm2GetPublicKeyFromPrivateKey, sm2Encrypt, sm2Decrypt, sm2Sign, sm2Verify } from './utils/algorithm/sm2' +import { parseSm2Cert, parseCertFromFile } from './utils/algorithm/cert' import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert' function App() { @@ -68,6 +69,9 @@ function App() { const [hexInput, setHexInput] = useState('') const [utf8Input, setUtf8Input] = useState('') const [base64Input, setBase64Input] = useState('') + + // 证书工具状态 + const [certInfo, setCertInfo] = useState(null) @@ -151,6 +155,33 @@ function App() { } } + // 处理证书文件上传 + const handleCertFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + + const reader = new FileReader() + reader.onload = (event) => { + const result = event.target?.result as ArrayBuffer + if (result) { + const base64Cert = parseCertFromFile(result) + handleParseCert(base64Cert) + } + } + reader.readAsArrayBuffer(file) + } + + // 处理证书解析 + const handleParseCert = (certContent: string) => { + try { + const info = parseSm2Cert(certContent) + setCertInfo(info) + } catch (error) { + alert('证书解析失败: ' + (error instanceof Error ? error.message : error)) + setCertInfo(null) + } + } + // SM2 生成密钥对 const handleGenerateSm2KeyPair = () => { try { @@ -307,6 +338,24 @@ function App() { > 转换工具 + )} @@ -334,6 +383,7 @@ function App() { + )} @@ -1119,6 +1169,90 @@ function App() { )} + {/* 证书工具面板 */} + {activeTab === 'cert' && ( +
+

SM2 证书信息解析

+ +
+ + +
+ + {certInfo && ( +
+

证书信息

+ +
+ +
{certInfo.version}
+
+ +
+ +
{certInfo.serialNumber}
+
+ +
+ +
+ {Object.entries(certInfo.subject).map(([key, value]) => ( +
{key}: {String(value)}
+ ))} +
+
+ +
+ +
+ {Object.entries(certInfo.issuer).map(([key, value]) => ( +
{key}: {String(value)}
+ ))} +
+
+ +
+ +
+
开始: {certInfo.notBefore}
+
结束: {certInfo.notAfter}
+
+
+ +
+ +
{certInfo.publicKey}
+
+ +
+ +
+ {certInfo.keyUsage.length > 0 ? ( + certInfo.keyUsage.map((usage: string, index: number) => ( +
{usage}
+ )) + ) : ( +
+ )} +
+
+
+ )} +
+ )} + {/* 页脚 */}