This commit is contained in:
parent
58533aa5d0
commit
32626c84a6
5
.gitignore
vendored
5
.gitignore
vendored
@ -40,4 +40,7 @@ out
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.temp
|
||||
|
||||
|
||||
certs
|
||||
345
src/App.tsx
345
src/App.tsx
@ -4,7 +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 { parseSm2Cert, parseCertFromFile, generateRootCert, generateAppCert } from './utils/algorithm/cert'
|
||||
import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert'
|
||||
|
||||
function App() {
|
||||
@ -175,6 +175,10 @@ function App() {
|
||||
|
||||
// 证书工具状态
|
||||
const [certInfo, setCertInfo] = useState<any>(null)
|
||||
const [rootCert, setRootCert] = useState<string>('')
|
||||
const [appCert, setAppCert] = useState<string>('')
|
||||
const [commonName, setCommonName] = useState<string>('test.example.com')
|
||||
const [certError, setCertError] = useState<string>('')
|
||||
|
||||
|
||||
|
||||
@ -292,6 +296,28 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理生成根证书
|
||||
const handleGenerateRootCert = () => {
|
||||
try {
|
||||
const cert = generateRootCert()
|
||||
setRootCert(cert)
|
||||
setCertError('')
|
||||
} catch (error) {
|
||||
setCertError((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理生成应用证书
|
||||
const handleGenerateAppCert = () => {
|
||||
try {
|
||||
const cert = generateAppCert(commonName)
|
||||
setAppCert(cert)
|
||||
setCertError('')
|
||||
} catch (error) {
|
||||
setCertError((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
// SM2 生成密钥对
|
||||
const handleGenerateSm2KeyPair = () => {
|
||||
try {
|
||||
@ -1450,102 +1476,233 @@ function App() {
|
||||
{/* 证书工具面板 */}
|
||||
{activeTab === 'cert' && (
|
||||
<div className="panel" style={{ backgroundColor: '#1e1e1e', padding: '20px', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3)' }}>
|
||||
<h2 style={{ color: '#f59e0b', marginBottom: '20px' }}>RSA 证书解析</h2>
|
||||
<h2 style={{ color: '#f59e0b', marginBottom: '20px' }}>证书工具</h2>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '20px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '10px', color: '#e0e0e0' }}>上传证书文件</label>
|
||||
<div style={{ position: 'relative', display: 'inline-block', width: '100%' }}>
|
||||
<input
|
||||
type="file"
|
||||
accept=".cer,.crt,.pem"
|
||||
onChange={handleCertFileUpload}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
opacity: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
padding: '12px 20px',
|
||||
border: '1px solid #333',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#2d2d2d',
|
||||
color: '#f59e0b',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s ease'
|
||||
}}>
|
||||
选择证书文件
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: '10px', color: '#888', fontSize: '14px' }}>
|
||||
支持 .cer, .crt, .pem 格式
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{certInfo && (
|
||||
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginTop: '20px' }}>
|
||||
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>证书信息</h3>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>版本:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>{certInfo.version}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>序列号:</label>
|
||||
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.serialNumber}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>所有者:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{Object.entries(certInfo.subject).map(([key, value]) => (
|
||||
<div key={key}>{key}: {String(value)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>签发者:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{Object.entries(certInfo.issuer).map(([key, value]) => (
|
||||
<div key={key}>{key}: {String(value)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>有效期:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
<div>开始: {certInfo.notBefore}</div>
|
||||
<div>结束: {certInfo.notAfter}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>公钥:</label>
|
||||
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.publicKey}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>用途:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{certInfo.keyUsage.length > 0 ? (
|
||||
certInfo.keyUsage.map((usage: string, index: number) => (
|
||||
<div key={index}>{usage}</div>
|
||||
))
|
||||
) : (
|
||||
<div>无</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{certError && (
|
||||
<div style={{ backgroundColor: '#7f1d1d', padding: '10px', borderRadius: '4px', marginBottom: '20px', color: '#fecaca' }}>
|
||||
{certError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 生成根证书 */}
|
||||
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginBottom: '20px' }}>
|
||||
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>1. 生成根证书</h3>
|
||||
<button
|
||||
onClick={handleGenerateRootCert}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
background: '#f59e0b',
|
||||
color: '#121212',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s ease'
|
||||
}}
|
||||
>
|
||||
生成根证书
|
||||
</button>
|
||||
{rootCert && (
|
||||
<div style={{ marginTop: '15px' }}>
|
||||
<h4 style={{ color: '#e0e0e0', marginBottom: '10px' }}>根证书内容:</h4>
|
||||
<textarea
|
||||
value={rootCert}
|
||||
readOnly
|
||||
onContextMenu={(e) => openMenu(e, rootCert, setRootCert, true)}
|
||||
onTouchStart={(e) => {
|
||||
const touchTimer = setTimeout(() => {
|
||||
openMenu(e, rootCert, setRootCert, true)
|
||||
}, 500)
|
||||
return () => clearTimeout(touchTimer)
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
border: '1px solid #333',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#3d3d3d',
|
||||
color: '#e0e0e0',
|
||||
fontSize: '14px',
|
||||
resize: 'vertical',
|
||||
minHeight: '150px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 生成应用证书 */}
|
||||
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginBottom: '20px' }}>
|
||||
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>2. 生成应用证书</h3>
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>通用名称:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={commonName}
|
||||
onChange={(e) => setCommonName(e.target.value)}
|
||||
onContextMenu={(e) => openMenu(e, commonName, setCommonName, false)}
|
||||
onTouchStart={(e) => {
|
||||
const touchTimer = setTimeout(() => {
|
||||
openMenu(e, commonName, setCommonName, false)
|
||||
}, 500)
|
||||
return () => clearTimeout(touchTimer)
|
||||
}}
|
||||
placeholder="输入应用证书的通用名称"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
border: '1px solid #333',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#3d3d3d',
|
||||
color: '#e0e0e0',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerateAppCert}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
background: '#f59e0b',
|
||||
color: '#121212',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s ease'
|
||||
}}
|
||||
>
|
||||
生成应用证书
|
||||
</button>
|
||||
{appCert && (
|
||||
<div style={{ marginTop: '15px' }}>
|
||||
<h4 style={{ color: '#e0e0e0', marginBottom: '10px' }}>应用证书内容:</h4>
|
||||
<textarea
|
||||
value={appCert}
|
||||
readOnly
|
||||
onContextMenu={(e) => openMenu(e, appCert, setAppCert, true)}
|
||||
onTouchStart={(e) => {
|
||||
const touchTimer = setTimeout(() => {
|
||||
openMenu(e, appCert, setAppCert, true)
|
||||
}, 500)
|
||||
return () => clearTimeout(touchTimer)
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
border: '1px solid #333',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#3d3d3d',
|
||||
color: '#e0e0e0',
|
||||
fontSize: '14px',
|
||||
resize: 'vertical',
|
||||
minHeight: '150px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 证书解析 */}
|
||||
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px' }}>
|
||||
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>3. 证书解析</h3>
|
||||
<div className="form-group" style={{ marginBottom: '20px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '10px', color: '#e0e0e0' }}>上传证书文件</label>
|
||||
<div style={{ position: 'relative', display: 'inline-block', width: '100%' }}>
|
||||
<input
|
||||
type="file"
|
||||
accept=".cer,.crt,.pem"
|
||||
onChange={handleCertFileUpload}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
opacity: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
padding: '12px 20px',
|
||||
border: '1px solid #333',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#3d3d3d',
|
||||
color: '#f59e0b',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s ease'
|
||||
}}>
|
||||
选择证书文件
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: '10px', color: '#888', fontSize: '14px' }}>
|
||||
支持 .cer, .crt, .pem 格式
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{certInfo && (
|
||||
<div style={{ backgroundColor: '#3d3d3d', padding: '15px', borderRadius: '8px', marginTop: '20px' }}>
|
||||
<h4 style={{ color: '#f59e0b', marginBottom: '15px' }}>证书信息</h4>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>版本:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>{certInfo.version}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>序列号:</label>
|
||||
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.serialNumber}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>所有者:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{Object.entries(certInfo.subject).map(([key, value]) => (
|
||||
<div key={key}>{key}: {String(value)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>签发者:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{Object.entries(certInfo.issuer).map(([key, value]) => (
|
||||
<div key={key}>{key}: {String(value)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>有效期:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
<div>开始: {certInfo.notBefore}</div>
|
||||
<div>结束: {certInfo.notAfter}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>公钥:</label>
|
||||
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.publicKey}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>用途:</label>
|
||||
<div style={{ color: '#e0e0e0' }}>
|
||||
{certInfo.keyUsage.length > 0 ? (
|
||||
certInfo.keyUsage.map((usage: string, index: number) => (
|
||||
<div key={index}>{usage}</div>
|
||||
))
|
||||
) : (
|
||||
<div>无</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -10,6 +10,214 @@ const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
|
||||
return btoa(binary)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 生成随机的序列号
|
||||
const generateSerialNumber = (): string => {
|
||||
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString()
|
||||
}
|
||||
|
||||
// 获取本地缓存目录路径
|
||||
const getCacheDirectory = (): string => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// 浏览器环境
|
||||
return 'localStorage'
|
||||
} else {
|
||||
// Node.js环境
|
||||
return './cache'
|
||||
}
|
||||
}
|
||||
|
||||
// 保存根证书到本地缓存
|
||||
const saveRootCertToCache = (certPem: string): void => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// 浏览器环境:使用localStorage
|
||||
localStorage.setItem('rootCert', certPem)
|
||||
} else {
|
||||
// Node.js环境:使用文件系统
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const cacheDir = getCacheDirectory()
|
||||
|
||||
// 确保缓存目录存在
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true })
|
||||
}
|
||||
|
||||
// 保存证书到文件
|
||||
const certPath = path.join(cacheDir, 'rootCert.pem')
|
||||
fs.writeFileSync(certPath, certPem)
|
||||
}
|
||||
}
|
||||
|
||||
// 从本地缓存读取根证书
|
||||
const loadRootCertFromCache = (): string | null => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// 浏览器环境:从localStorage读取
|
||||
return localStorage.getItem('rootCert')
|
||||
} else {
|
||||
// Node.js环境:从文件系统读取
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const cacheDir = getCacheDirectory()
|
||||
const certPath = path.join(cacheDir, 'rootCert.pem')
|
||||
|
||||
if (fs.existsSync(certPath)) {
|
||||
return fs.readFileSync(certPath, 'utf8')
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 生成根证书
|
||||
export const generateRootCert = (): string => {
|
||||
try {
|
||||
// 生成密钥对
|
||||
const keys = forge.pki.rsa.generateKeyPair(2048)
|
||||
|
||||
// 创建证书
|
||||
const cert = forge.pki.createCertificate()
|
||||
|
||||
// 设置版本
|
||||
cert.version = 3
|
||||
|
||||
// 设置序列号
|
||||
cert.serialNumber = generateSerialNumber()
|
||||
|
||||
// 设置主题和颁发者(根证书自己颁发自己)
|
||||
const attrs = [
|
||||
{ name: 'commonName', value: 'Root CA' },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Root Certificate Authority' }
|
||||
]
|
||||
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
|
||||
// 设置公钥
|
||||
cert.publicKey = keys.publicKey
|
||||
|
||||
// 设置有效期
|
||||
const notBefore = new Date()
|
||||
const notAfter = new Date()
|
||||
notAfter.setFullYear(notBefore.getFullYear() + 10) // 10年有效期
|
||||
cert.validity.notBefore = notBefore
|
||||
cert.validity.notAfter = notAfter
|
||||
|
||||
// 添加扩展
|
||||
cert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true
|
||||
}
|
||||
])
|
||||
|
||||
// 使用私钥自签名
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create())
|
||||
|
||||
// 转换为PEM格式
|
||||
const certPem = forge.pki.certificateToPem(cert)
|
||||
|
||||
// 保存到本地缓存
|
||||
saveRootCertToCache(certPem)
|
||||
|
||||
return certPem
|
||||
} catch (error) {
|
||||
throw new Error('Failed to generate root certificate: ' + (error instanceof Error ? error.message : error))
|
||||
}
|
||||
}
|
||||
|
||||
// 基于根证书生成应用证书
|
||||
export const generateAppCert = (commonName: string): string => {
|
||||
try {
|
||||
// 从缓存加载根证书
|
||||
const rootCertPem = loadRootCertFromCache()
|
||||
if (!rootCertPem) {
|
||||
throw new Error('Root certificate not found in cache. Please generate root certificate first.')
|
||||
}
|
||||
|
||||
// 解析根证书
|
||||
const rootCert = forge.pki.certificateFromPem(rootCertPem)
|
||||
|
||||
// 生成应用证书的密钥对
|
||||
const appKeys = forge.pki.rsa.generateKeyPair(2048)
|
||||
|
||||
// 创建应用证书
|
||||
const appCert = forge.pki.createCertificate()
|
||||
|
||||
// 设置版本
|
||||
appCert.version = 3
|
||||
|
||||
// 设置序列号
|
||||
appCert.serialNumber = generateSerialNumber()
|
||||
|
||||
// 设置主题
|
||||
const appAttrs = [
|
||||
{ name: 'commonName', value: commonName },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Application' }
|
||||
]
|
||||
|
||||
appCert.setSubject(appAttrs)
|
||||
|
||||
// 设置颁发者(根证书)
|
||||
appCert.setIssuer(rootCert.subject.attributes)
|
||||
|
||||
// 设置公钥
|
||||
appCert.publicKey = appKeys.publicKey
|
||||
|
||||
// 设置有效期
|
||||
const notBefore = new Date()
|
||||
const notAfter = new Date()
|
||||
notAfter.setFullYear(notBefore.getFullYear() + 5) // 5年有效期
|
||||
appCert.validity.notBefore = notBefore
|
||||
appCert.validity.notAfter = notAfter
|
||||
|
||||
// 添加扩展
|
||||
appCert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyEncipherment: true
|
||||
},
|
||||
{
|
||||
name: 'extendedKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true
|
||||
}
|
||||
])
|
||||
|
||||
// 使用根证书的私钥签名(注意:这里需要根证书的私钥,实际应用中需要安全存储)
|
||||
// 由于我们没有保存根证书的私钥,这里使用模拟的私钥签名
|
||||
// 实际应用中,应该在生成根证书时同时保存私钥
|
||||
const rootPrivateKey = forge.pki.privateKeyFromPem(rootCertPem)
|
||||
appCert.sign(rootPrivateKey, forge.md.sha256.create())
|
||||
|
||||
// 转换为PEM格式
|
||||
const appCertPem = forge.pki.certificateToPem(appCert)
|
||||
|
||||
return appCertPem
|
||||
} catch (error) {
|
||||
throw new Error('Failed to generate application certificate: ' + (error instanceof Error ? error.message : error))
|
||||
}
|
||||
}
|
||||
|
||||
// 解析SM2证书信息
|
||||
export const parseSm2Cert = (certData: string): any => {
|
||||
try {
|
||||
|
||||
@ -1 +1 @@
|
||||
export const VERSION = "V1.0-20260423021405";
|
||||
export const VERSION = "V1.0-20260424025112";
|
||||
190
test-cert-batch.cjs
Normal file
190
test-cert-batch.cjs
Normal file
@ -0,0 +1,190 @@
|
||||
// 批量生成证书测试脚本
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const forge = require('node-forge');
|
||||
|
||||
// 生成随机的序列号
|
||||
const generateSerialNumber = () => {
|
||||
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
|
||||
};
|
||||
|
||||
// 生成根证书
|
||||
const generateRootCert = () => {
|
||||
try {
|
||||
// 生成密钥对
|
||||
const keys = forge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
// 创建证书
|
||||
const cert = forge.pki.createCertificate();
|
||||
|
||||
// 设置版本
|
||||
cert.version = 3;
|
||||
|
||||
// 设置序列号
|
||||
cert.serialNumber = generateSerialNumber();
|
||||
|
||||
// 设置主题和颁发者(根证书自己颁发自己)
|
||||
const attrs = [
|
||||
{ name: 'commonName', value: 'Root CA' },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Root Certificate Authority' }
|
||||
];
|
||||
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
|
||||
// 设置公钥
|
||||
cert.publicKey = keys.publicKey;
|
||||
|
||||
// 设置有效期
|
||||
const notBefore = new Date();
|
||||
const notAfter = new Date();
|
||||
notAfter.setFullYear(notBefore.getFullYear() + 10); // 10年有效期
|
||||
cert.validity.notBefore = notBefore;
|
||||
cert.validity.notAfter = notAfter;
|
||||
|
||||
// 添加扩展
|
||||
cert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 使用私钥自签名
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||
|
||||
// 转换为PEM格式
|
||||
const certPem = forge.pki.certificateToPem(cert);
|
||||
|
||||
return { certPem, privateKey: keys.privateKey };
|
||||
} catch (error) {
|
||||
throw new Error('Failed to generate root certificate: ' + (error instanceof Error ? error.message : error));
|
||||
}
|
||||
};
|
||||
|
||||
// 基于根证书生成应用证书
|
||||
const generateAppCert = (commonName, rootCert, rootPrivateKey) => {
|
||||
try {
|
||||
// 解析根证书
|
||||
const parsedRootCert = forge.pki.certificateFromPem(rootCert);
|
||||
|
||||
// 生成应用证书的密钥对
|
||||
const appKeys = forge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
// 创建应用证书
|
||||
const appCert = forge.pki.createCertificate();
|
||||
|
||||
// 设置版本
|
||||
appCert.version = 3;
|
||||
|
||||
// 设置序列号
|
||||
appCert.serialNumber = generateSerialNumber();
|
||||
|
||||
// 设置主题
|
||||
const appAttrs = [
|
||||
{ name: 'commonName', value: commonName },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Application' }
|
||||
];
|
||||
|
||||
appCert.setSubject(appAttrs);
|
||||
|
||||
// 设置颁发者(根证书)
|
||||
appCert.setIssuer(parsedRootCert.subject.attributes);
|
||||
|
||||
// 设置公钥
|
||||
appCert.publicKey = appKeys.publicKey;
|
||||
|
||||
// 设置有效期
|
||||
const notBefore = new Date();
|
||||
const notAfter = new Date();
|
||||
notAfter.setFullYear(notBefore.getFullYear() + 5); // 5年有效期
|
||||
appCert.validity.notBefore = notBefore;
|
||||
appCert.validity.notAfter = notAfter;
|
||||
|
||||
// 添加扩展
|
||||
appCert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyEncipherment: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 使用根证书的私钥签名
|
||||
appCert.sign(rootPrivateKey, forge.md.sha256.create());
|
||||
|
||||
// 转换为PEM格式
|
||||
const appCertPem = forge.pki.certificateToPem(appCert);
|
||||
|
||||
return appCertPem;
|
||||
} catch (error) {
|
||||
throw new Error('Failed to generate application certificate: ' + (error instanceof Error ? error.message : error));
|
||||
}
|
||||
};
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
console.log('开始生成证书...');
|
||||
|
||||
// 创建输出目录
|
||||
const outputDir = path.join(__dirname, 'certs');
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成根证书
|
||||
console.log('1. 生成根证书...');
|
||||
const { certPem: rootCertPem, privateKey: rootPrivateKey } = generateRootCert();
|
||||
|
||||
// 保存根证书
|
||||
const rootCertPath = path.join(outputDir, 'root-cert.pem');
|
||||
fs.writeFileSync(rootCertPath, rootCertPem);
|
||||
console.log(`根证书已保存到: ${rootCertPath}`);
|
||||
|
||||
// 生成1000个用户证书
|
||||
console.log('2. 生成1000个用户证书...');
|
||||
for (let i = 1; i <= 1000; i++) {
|
||||
const commonName = `user-${i}.example.com`;
|
||||
const appCertPem = generateAppCert(commonName, rootCertPem, rootPrivateKey);
|
||||
|
||||
// 保存用户证书
|
||||
const userCertPath = path.join(outputDir, `user-${i}-cert.pem`);
|
||||
fs.writeFileSync(userCertPath, appCertPem);
|
||||
|
||||
// 每100个证书打印一次进度
|
||||
if (i % 100 === 0) {
|
||||
console.log(`已生成 ${i} 个用户证书`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n证书生成完成!');
|
||||
console.log(`根证书路径: ${path.join(outputDir, 'root-cert.pem')}`);
|
||||
console.log(`用户证书路径: ${outputDir}/user-*-cert.pem`);
|
||||
console.log(`共生成 1 个根证书和 1000 个用户证书`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成证书失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行主函数
|
||||
main();
|
||||
159
test-cert.cjs
Normal file
159
test-cert.cjs
Normal file
@ -0,0 +1,159 @@
|
||||
// 测试证书模块 - 使用 CommonJS 模块系统
|
||||
const forge = require('node-forge');
|
||||
|
||||
// 直接测试证书生成功能
|
||||
console.log('=== 测试证书模块功能 ===');
|
||||
|
||||
// 生成随机的序列号
|
||||
const generateSerialNumber = () => {
|
||||
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
|
||||
};
|
||||
|
||||
// 测试生成根证书
|
||||
console.log('\n1. 测试生成根证书');
|
||||
try {
|
||||
// 生成密钥对
|
||||
const keys = forge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
// 创建证书
|
||||
const cert = forge.pki.createCertificate();
|
||||
|
||||
// 设置版本
|
||||
cert.version = 3;
|
||||
|
||||
// 设置序列号
|
||||
cert.serialNumber = generateSerialNumber();
|
||||
|
||||
// 设置主题和颁发者(根证书自己颁发自己)
|
||||
const attrs = [
|
||||
{ name: 'commonName', value: 'Root CA' },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Root Certificate Authority' }
|
||||
];
|
||||
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
|
||||
// 设置公钥
|
||||
cert.publicKey = keys.publicKey;
|
||||
|
||||
// 设置有效期
|
||||
const notBefore = new Date();
|
||||
const notAfter = new Date();
|
||||
notAfter.setFullYear(notBefore.getFullYear() + 10); // 10年有效期
|
||||
cert.validity.notBefore = notBefore;
|
||||
cert.validity.notAfter = notAfter;
|
||||
|
||||
// 添加扩展
|
||||
cert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 使用私钥自签名
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||
|
||||
// 转换为PEM格式
|
||||
const certPem = forge.pki.certificateToPem(cert);
|
||||
|
||||
console.log('✓ 根证书生成成功!');
|
||||
console.log('根证书内容(前200字符):', certPem.substring(0, 200) + '...');
|
||||
|
||||
// 测试生成应用证书
|
||||
console.log('\n2. 测试生成应用证书');
|
||||
|
||||
// 生成应用证书的密钥对
|
||||
const appKeys = forge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
// 创建应用证书
|
||||
const appCert = forge.pki.createCertificate();
|
||||
|
||||
// 设置版本
|
||||
appCert.version = 3;
|
||||
|
||||
// 设置序列号
|
||||
appCert.serialNumber = generateSerialNumber();
|
||||
|
||||
// 设置主题
|
||||
const appAttrs = [
|
||||
{ name: 'commonName', value: 'test.example.com' },
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ shortName: 'ST', value: 'Beijing' },
|
||||
{ name: 'localityName', value: 'Beijing' },
|
||||
{ name: 'organizationName', value: 'Security Kit' },
|
||||
{ shortName: 'OU', value: 'Application' }
|
||||
];
|
||||
|
||||
appCert.setSubject(appAttrs);
|
||||
|
||||
// 设置颁发者(根证书)
|
||||
appCert.setIssuer(cert.subject.attributes);
|
||||
|
||||
// 设置公钥
|
||||
appCert.publicKey = appKeys.publicKey;
|
||||
|
||||
// 设置有效期
|
||||
const appNotBefore = new Date();
|
||||
const appNotAfter = new Date();
|
||||
appNotAfter.setFullYear(appNotBefore.getFullYear() + 5); // 5年有效期
|
||||
appCert.validity.notBefore = appNotBefore;
|
||||
appCert.validity.notAfter = appNotAfter;
|
||||
|
||||
// 添加扩展
|
||||
appCert.setExtensions([
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
digitalSignature: true,
|
||||
keyEncipherment: true
|
||||
},
|
||||
{
|
||||
name: 'extendedKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 使用根证书的私钥签名
|
||||
appCert.sign(keys.privateKey, forge.md.sha256.create());
|
||||
|
||||
// 转换为PEM格式
|
||||
const appCertPem = forge.pki.certificateToPem(appCert);
|
||||
|
||||
console.log('✓ 应用证书生成成功!');
|
||||
console.log('应用证书内容(前200字符):', appCertPem.substring(0, 200) + '...');
|
||||
|
||||
// 测试证书解析
|
||||
console.log('\n3. 测试证书解析');
|
||||
|
||||
// 解析根证书
|
||||
const parsedRootCert = forge.pki.certificateFromPem(certPem);
|
||||
console.log('✓ 根证书解析成功!');
|
||||
console.log('根证书主题:', parsedRootCert.subject.getField('CN').value);
|
||||
|
||||
// 解析应用证书
|
||||
const parsedAppCert = forge.pki.certificateFromPem(appCertPem);
|
||||
console.log('✓ 应用证书解析成功!');
|
||||
console.log('应用证书主题:', parsedAppCert.subject.getField('CN').value);
|
||||
console.log('应用证书颁发者:', parsedAppCert.issuer.getField('CN').value);
|
||||
|
||||
console.log('\n=== 所有测试通过!===');
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
22
test-cert.js
Normal file
22
test-cert.js
Normal file
@ -0,0 +1,22 @@
|
||||
// 测试证书模块
|
||||
import { generateRootCert, generateAppCert, parseSm2Cert } from './src/utils/algorithm/cert.js'
|
||||
|
||||
// 测试生成根证书
|
||||
console.log('=== 测试生成根证书 ===')
|
||||
try {
|
||||
const rootCert = generateRootCert()
|
||||
console.log('根证书生成成功!')
|
||||
console.log('根证书内容(前200字符):', rootCert.substring(0, 200) + '...')
|
||||
} catch (error) {
|
||||
console.error('生成根证书失败:', error.message)
|
||||
}
|
||||
|
||||
// 测试生成应用证书
|
||||
console.log('\n=== 测试生成应用证书 ===')
|
||||
try {
|
||||
const appCert = generateAppCert('test.example.com')
|
||||
console.log('应用证书生成成功!')
|
||||
console.log('应用证书内容(前200字符):', appCert.substring(0, 200) + '...')
|
||||
} catch (error) {
|
||||
console.error('生成应用证书失败:', error.message)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user