Compare commits

...

2 Commits

Author SHA1 Message Date
cheney
2a2e9e0d6b 新增功能
All checks were successful
Build and Deploy / build (push) Successful in 5m41s
2026-07-01 15:27:28 +08:00
cheney
951819fb96 改个箭头 2026-06-30 15:08:49 +08:00
4 changed files with 446 additions and 71 deletions

View File

@ -2,4 +2,5 @@
- [x] CI 集成,将 dist 传输到指定服务器指定目录。
- [x] 添加 SM2 算法
- [x] 证书工具支持输入逗号分隔证书 id 并下载 DER 格式 CRL 文件。
- [x] 根证书生成后支持下载 CRT 文件和根证书私钥。

View File

@ -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, sm2DerToC1C3C2, sm2C1C3C2ToDer } from './utils/algorithm/sm2'
import { parseSm2Cert, parseCertFromFile, generateRootCert, generateAppCert } from './utils/algorithm/cert'
import { parseSm2Cert, parseCertFromFile, generateRootCert, generateAppCert, generateCrlFile, loadRootPrivateKeyFromCache } from './utils/algorithm/cert'
import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert'
import { getInfoFromPKCS7, verifyPKCS7 } from './utils/crypto.js'
@ -182,6 +182,7 @@ function App() {
const [appCert, setAppCert] = useState<string>('')
const [commonName, setCommonName] = useState<string>('test.example.com')
const [certError, setCertError] = useState<string>('')
const [crlCertificateIds, setCrlCertificateIds] = useState<string>('')
// PKCS7 签名工具状态
const [pkcs7Signature, setPkcs7Signature] = useState<string>('')
@ -335,6 +336,46 @@ function App() {
setCertError((error as Error).message)
}
}
// 下载文本文件
// 参数content 为文件内容fileName 为下载文件名mimeType 为文件类型。
// 返回值:无。
const downloadTextFile = (content: string, fileName: string, mimeType: string) => {
const blob = new Blob([content], { type: mimeType })
const downloadUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(downloadUrl)
}
// 处理根证书文件下载
// 参数:无。
// 返回值:无。
const handleDownloadRootCert = () => {
if (!rootCert) {
setCertError('请先生成根证书')
return
}
downloadTextFile(rootCert, 'root-cert.crt', 'application/x-x509-ca-cert')
setCertError('')
}
// 处理根证书私钥下载
// 参数:无。
// 返回值:无。
const handleDownloadRootPrivateKey = () => {
const rootPrivateKey = loadRootPrivateKeyFromCache()
if (!rootPrivateKey) {
setCertError('根证书私钥不存在,请重新生成根证书')
return
}
downloadTextFile(rootPrivateKey, 'root-private-key.key', 'application/x-pem-file')
setCertError('')
}
// 处理生成应用证书
const handleGenerateAppCert = () => {
@ -346,6 +387,28 @@ function App() {
setCertError((error as Error).message)
}
}
// 处理 CRL 文件下载
// 参数:无。
// 返回值:无。
// 注意事项:证书 id 按证书序列号处理,多个 id 使用英文逗号分隔。
const handleDownloadCrlFile = () => {
try {
const crlFile = generateCrlFile(crlCertificateIds)
const blob = new Blob([crlFile], { type: 'application/pkix-crl' })
const downloadUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = 'certificate-revocation-list.crl'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(downloadUrl)
setCertError('')
} catch (error) {
setCertError((error as Error).message)
}
}
// 处理 PKCS7 签名信息提取
const handleGetInfoFromPKCS7 = () => {
@ -1521,7 +1584,7 @@ function App() {
transition: 'background 0.3s ease'
}}
>
Base64 Hex
Hex Base64
</button>
</div>
</div>
@ -1678,6 +1741,40 @@ function App() {
minHeight: '150px'
}}
/>
<div style={{ display: 'flex', gap: '10px', marginTop: '10px', flexWrap: 'wrap' }}>
<button
onClick={handleDownloadRootCert}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
CRT
</button>
<button
onClick={handleDownloadRootPrivateKey}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
</button>
</div>
</div>
)}
</div>
@ -1755,9 +1852,56 @@ function App() {
)}
</div>
{/* 生成 CRL 文件 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginBottom: '20px' }}>
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>3. CRL </h3>
<div className="form-group" style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}> id</label>
<textarea
value={crlCertificateIds}
onChange={(e) => setCrlCertificateIds(e.target.value)}
onContextMenu={(e) => openMenu(e, crlCertificateIds, setCrlCertificateIds, false)}
onTouchStart={(e) => {
const touchTimer = setTimeout(() => {
openMenu(e, crlCertificateIds, setCrlCertificateIds, false)
}, 500)
return () => clearTimeout(touchTimer)
}}
placeholder="可选,输入多个逗号分隔的证书 id例如01,02,0a"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '80px'
}}
/>
</div>
<button
onClick={handleDownloadCrlFile}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
CRL
</button>
</div>
{/* 证书解析 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px' }}>
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>3. </h3>
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>4. </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%' }}>
@ -1856,7 +2000,7 @@ function App() {
{/* PKCS7 签名工具 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginTop: '20px' }}>
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>4. PKCS7 </h3>
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>5. PKCS7 </h3>
<div className="form-group" style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>PKCS7 </label>
@ -2066,4 +2210,4 @@ function App() {
)
}
export default App
export default App

View File

@ -0,0 +1,42 @@
import * as forge from 'node-forge'
import { generateCrlFile, generateRootCert, loadRootPrivateKeyFromCache, parseCrlCertificateIds } from '../../utils/algorithm/cert'
describe('证书 CRL 工具测试', () => {
test('1-1 空证书 id 应生成无吊销条目的 CRL 文件', () => {
const crlFile = generateCrlFile('')
const crlAsn1 = forge.asn1.fromDer(forge.util.createBuffer(crlFile))
const crlItems = crlAsn1.value as forge.asn1.Asn1[]
const tbsCertListItems = crlItems[0].value as forge.asn1.Asn1[]
expect(crlItems.length).toBe(3)
expect(tbsCertListItems.length).toBe(5)
})
test('1-2 多个逗号分隔证书 id 应生成多个吊销条目', () => {
const crlFile = generateCrlFile('01, 02, 0a')
const crlAsn1 = forge.asn1.fromDer(forge.util.createBuffer(crlFile))
const crlItems = crlAsn1.value as forge.asn1.Asn1[]
const tbsCertListItems = crlItems[0].value as forge.asn1.Asn1[]
const revokedCertificates = tbsCertListItems[5].value as forge.asn1.Asn1[]
expect(revokedCertificates.length).toBe(3)
})
test('1-3 重复证书 id 应去重', () => {
const certificateIds = parseCrlCertificateIds('01,1,02,02')
expect(certificateIds).toEqual(['01', '02'])
})
test('1-4 非十六进制证书 id 应抛出错误', () => {
expect(() => parseCrlCertificateIds('01, zz')).toThrow('证书 id')
})
test('1-5 生成根证书后应能读取根证书私钥', () => {
const rootCert = generateRootCert()
const rootPrivateKey = loadRootPrivateKeyFromCache()
expect(rootCert).toContain('BEGIN CERTIFICATE')
expect(rootPrivateKey).toContain('BEGIN RSA PRIVATE KEY')
})
})

View File

@ -1,4 +1,15 @@
import forge from 'node-forge'
import * as forge from 'node-forge'
const ROOT_CERT_CACHE_KEY = 'rootCert'
const ROOT_PRIVATE_KEY_CACHE_KEY = 'rootPrivateKey'
const SHA256_WITH_RSA_OID = forge.pki.oids.sha256WithRSAEncryption
type RootCertificateBundle = {
certPem: string
privateKeyPem: string
}
let temporaryCrlSigner: RootCertificateBundle | null = null
// 将ArrayBuffer转换为Base64字符串浏览器兼容
const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
@ -28,11 +39,15 @@ const getCacheDirectory = (): string => {
}
}
// 保存根证书到本地缓存
const saveRootCertToCache = (certPem: string): void => {
// 保存根证书和私钥到本地缓存
// 参数certPem 为 PEM 格式根证书privateKeyPem 为 PEM 格式根证书私钥。
// 返回值:无。
// 注意事项:浏览器环境保存到 localStorageNode.js 环境保存到 cache 目录。
const saveRootCertToCache = (certPem: string, privateKeyPem: string): void => {
if (typeof window !== 'undefined') {
// 浏览器环境使用localStorage
localStorage.setItem('rootCert', certPem)
localStorage.setItem(ROOT_CERT_CACHE_KEY, certPem)
localStorage.setItem(ROOT_PRIVATE_KEY_CACHE_KEY, privateKeyPem)
} else {
// Node.js环境使用文件系统
const fs = require('fs')
@ -46,15 +61,19 @@ const saveRootCertToCache = (certPem: string): void => {
// 保存证书到文件
const certPath = path.join(cacheDir, 'rootCert.pem')
const privateKeyPath = path.join(cacheDir, 'rootPrivateKey.pem')
fs.writeFileSync(certPath, certPem)
fs.writeFileSync(privateKeyPath, privateKeyPem)
}
}
// 从本地缓存读取根证书
// 参数:无。
// 返回值:存在时返回 PEM 格式根证书,否则返回 null。
const loadRootCertFromCache = (): string | null => {
if (typeof window !== 'undefined') {
// 浏览器环境从localStorage读取
return localStorage.getItem('rootCert')
return localStorage.getItem(ROOT_CERT_CACHE_KEY)
} else {
// Node.js环境从文件系统读取
const fs = require('fs')
@ -69,68 +88,100 @@ const loadRootCertFromCache = (): string | null => {
}
}
// 从本地缓存读取根证书私钥
// 参数:无。
// 返回值:存在时返回 PEM 格式根证书私钥,否则返回 null。
export const loadRootPrivateKeyFromCache = (): string | null => {
if (typeof window !== 'undefined') {
// 浏览器环境从localStorage读取
return localStorage.getItem(ROOT_PRIVATE_KEY_CACHE_KEY)
} else {
// Node.js环境从文件系统读取
const fs = require('fs')
const path = require('path')
const cacheDir = getCacheDirectory()
const privateKeyPath = path.join(cacheDir, 'rootPrivateKey.pem')
if (fs.existsSync(privateKeyPath)) {
return fs.readFileSync(privateKeyPath, 'utf8')
}
return null
}
}
// 创建根证书和根证书私钥
// 参数:无。
// 返回值:返回 PEM 格式根证书和 PEM 格式私钥。
const createRootCertificateBundle = (): RootCertificateBundle => {
// 生成密钥对
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())
return {
certPem: forge.pki.certificateToPem(cert),
privateKeyPem: forge.pki.privateKeyToPem(keys.privateKey)
}
}
// 生成根证书
// 参数:无。
// 返回值:返回 PEM 格式根证书,并将根证书和私钥写入缓存。
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)
const rootCertificateBundle = createRootCertificateBundle()
// 保存到本地缓存
saveRootCertToCache(certPem)
saveRootCertToCache(rootCertificateBundle.certPem, rootCertificateBundle.privateKeyPem)
return certPem
return rootCertificateBundle.certPem
} catch (error) {
throw new Error('Failed to generate root certificate: ' + (error instanceof Error ? error.message : error))
}
@ -144,6 +195,10 @@ export const generateAppCert = (commonName: string): string => {
if (!rootCertPem) {
throw new Error('Root certificate not found in cache. Please generate root certificate first.')
}
const rootPrivateKeyPem = loadRootPrivateKeyFromCache()
if (!rootPrivateKeyPem) {
throw new Error('Root private key not found in cache. Please regenerate root certificate first.')
}
// 解析根证书
const rootCert = forge.pki.certificateFromPem(rootCertPem)
@ -203,10 +258,8 @@ export const generateAppCert = (commonName: string): string => {
}
])
// 使用根证书的私钥签名(注意:这里需要根证书的私钥,实际应用中需要安全存储)
// 由于我们没有保存根证书的私钥,这里使用模拟的私钥签名
// 实际应用中,应该在生成根证书时同时保存私钥
const rootPrivateKey = forge.pki.privateKeyFromPem(rootCertPem)
// 使用根证书的私钥签名
const rootPrivateKey = forge.pki.privateKeyFromPem(rootPrivateKeyPem)
appCert.sign(rootPrivateKey, forge.md.sha256.create())
// 转换为PEM格式
@ -218,6 +271,141 @@ export const generateAppCert = (commonName: string): string => {
}
}
// 将证书 id 规范化为 ASN.1 INTEGER 使用的十六进制序列号
// 参数certificateId 为用户输入的单个证书 id。
// 返回值:返回偶数长度、正数编码的十六进制序列号。
// 注意事项:证书 id 按证书序列号处理,仅支持十六进制字符。
const normalizeCrlCertificateId = (certificateId: string): string => {
const cleanedCertificateId = certificateId.trim().replace(/^0x/i, '')
if (!/^[0-9a-fA-F]+$/.test(cleanedCertificateId)) {
throw new Error('证书 id 只能包含 0-9、a-f、A-F多个 id 请使用逗号分隔')
}
let serialNumberHex = cleanedCertificateId.replace(/^0+/, '') || '0'
if (serialNumberHex.length % 2 === 1) {
serialNumberHex = '0' + serialNumberHex
}
if (parseInt(serialNumberHex.slice(0, 2), 16) >= 0x80) {
serialNumberHex = '00' + serialNumberHex
}
return serialNumberHex.toLowerCase()
}
// 解析逗号分隔的证书 id
// 参数certificateIds 为用户输入的逗号分隔证书 id 字符串。
// 返回值:返回去重后的十六进制证书序列号数组。
export const parseCrlCertificateIds = (certificateIds: string): string[] => {
const serialNumbers = certificateIds
.split(',')
.map(certificateId => certificateId.trim())
.filter(certificateId => certificateId.length > 0)
.map(normalizeCrlCertificateId)
return Array.from(new Set(serialNumbers))
}
// 将日期转换为 CRL 使用的 ASN.1 Time 对象
// 参数date 为需要写入 CRL 的日期。
// 返回值:返回 UTCTime 或 GeneralizedTime ASN.1 对象。
const dateToCrlTimeAsn1 = (date: Date): forge.asn1.Asn1 => {
const utcTimeStart = new Date('1950-01-01T00:00:00Z')
const utcTimeEnd = new Date('2050-01-01T00:00:00Z')
if (date >= utcTimeStart && date < utcTimeEnd) {
return forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.UTCTIME, false, forge.asn1.dateToUtcTime(date))
}
return forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.GENERALIZEDTIME, false, forge.asn1.dateToGeneralizedTime(date))
}
// 创建 SHA256withRSA 算法标识
// 参数:无。
// 返回值:返回 CRL 签名算法使用的 ASN.1 AlgorithmIdentifier。
const createSha256WithRsaAlgorithmIdentifier = (): forge.asn1.Asn1 => {
return forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer(SHA256_WITH_RSA_OID).getBytes()),
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.NULL, false, '')
])
}
// 创建 CRL 的 TBSCertList 结构
// 参数issuerCert 为签发者证书serialNumbers 为吊销证书序列号数组thisUpdate 为本次更新时间nextUpdate 为下次更新时间。
// 返回值:返回用于签名的 TBSCertList ASN.1 对象。
const createTbsCertList = (issuerCert: forge.pki.Certificate, serialNumbers: string[], thisUpdate: Date, nextUpdate: Date): forge.asn1.Asn1 => {
const tbsCertListItems: forge.asn1.Asn1[] = [
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false, forge.asn1.integerToDer(1).getBytes()),
createSha256WithRsaAlgorithmIdentifier(),
forge.pki.distinguishedNameToAsn1(issuerCert.subject),
dateToCrlTimeAsn1(thisUpdate),
dateToCrlTimeAsn1(nextUpdate)
]
if (serialNumbers.length > 0) {
tbsCertListItems.push(forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, serialNumbers.map(serialNumber => {
return forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false, forge.util.hexToBytes(serialNumber)),
dateToCrlTimeAsn1(thisUpdate)
])
})))
}
return forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, tbsCertListItems)
}
// 将二进制字符串转换为 ArrayBuffer
// 参数binary 为 node-forge 输出的二进制字符串。
// 返回值:返回可用于 Blob 下载的字节数组。
const binaryStringToArrayBuffer = (binary: string): ArrayBuffer => {
const buffer = new ArrayBuffer(binary.length)
const bytes = new Uint8Array(buffer)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return buffer
}
// 获取 CRL 签发者证书和私钥
// 参数:无。
// 返回值:优先返回缓存根证书和私钥;不存在时返回临时签发者证书和私钥。
const getCrlSigner = (): RootCertificateBundle => {
const rootCertPem = loadRootCertFromCache()
const rootPrivateKeyPem = loadRootPrivateKeyFromCache()
if (rootCertPem && rootPrivateKeyPem) {
return {
certPem: rootCertPem,
privateKeyPem: rootPrivateKeyPem
}
}
if (!temporaryCrlSigner) {
temporaryCrlSigner = createRootCertificateBundle()
}
return temporaryCrlSigner
}
// 生成 DER 格式 CRL 文件内容
// 参数certificateIds 为可选的逗号分隔证书 id按证书序列号写入吊销列表。
// 返回值:返回 DER 格式 CRL 文件的字节数组。
export const generateCrlFile = (certificateIds: string): ArrayBuffer => {
try {
const serialNumbers = parseCrlCertificateIds(certificateIds)
const crlSigner = getCrlSigner()
const issuerCert = forge.pki.certificateFromPem(crlSigner.certPem)
const issuerPrivateKey = forge.pki.privateKeyFromPem(crlSigner.privateKeyPem)
const thisUpdate = new Date()
const nextUpdate = new Date()
nextUpdate.setDate(thisUpdate.getDate() + 30)
const tbsCertList = createTbsCertList(issuerCert, serialNumbers, thisUpdate, nextUpdate)
const tbsCertListDer = forge.asn1.toDer(tbsCertList).getBytes()
const md = forge.md.sha256.create()
md.update(tbsCertListDer)
const signature = issuerPrivateKey.sign(md)
const crlAsn1 = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
tbsCertList,
createSha256WithRsaAlgorithmIdentifier(),
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.BITSTRING, false, String.fromCharCode(0) + signature)
])
return binaryStringToArrayBuffer(forge.asn1.toDer(crlAsn1).getBytes())
} catch (error) {
throw new Error('Failed to generate CRL file: ' + (error instanceof Error ? error.message : error))
}
}
// 解析SM2证书信息
export const parseSm2Cert = (certData: string): any => {
try {