【新增】私有ca

1.1.0
v-me-50 2025-08-26 16:52:07 +08:00
parent e53a88e4dd
commit c9cb3fa5e1
10 changed files with 1520 additions and 0 deletions

View File

@ -0,0 +1,227 @@
package private_ca
import (
"ALLinSSL/backend/internal/private_ca"
"ALLinSSL/backend/public"
"archive/zip"
"bytes"
"github.com/gin-gonic/gin"
"strings"
)
func CreateRootCA(c *gin.Context) {
var form private_ca.CAConfig
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
err = private_ca.CreateRootCA(form.Name, form.CN, form.O, form.OU, form.C, form.Province, form.Locality, form.Algorithm, form.KeyLength, form.ValidDays)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "根证书创建成功")
return
}
func CreateIntermediateCA(c *gin.Context) {
var form private_ca.CAConfig
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
err = private_ca.CreateIntermediateCA(form.Name, form.CN, form.O, form.OU, form.C, form.Province, form.Locality, form.RootId, form.KeyLength, form.ValidDays)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "中间证书创建成功")
return
}
func GetCAList(c *gin.Context) {
var form struct {
Search string `form:"search"`
Page int64 `form:"p"`
Limit int64 `form:"limit"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
data, count, err := private_ca.ListCAs(form.Search, form.Page, form.Limit)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessData(c, data, count)
return
}
func DeleteCA(c *gin.Context) {
var form struct {
Id int64 `form:"id"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
if form.Id <= 0 {
public.FailMsg(c, "ID不能为空")
return
}
err = private_ca.DeleteCA(form.Id)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "删除成功")
return
}
func CreateLeafCert(c *gin.Context) {
var form private_ca.LeafCertConfig
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
_, err = private_ca.CreateLeafCert(form.CaId, form.Usage, form.KeyLength, form.ValidDays, form.CN, form.SAN)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "证书创建成功")
return
}
func GetLeafCertList(c *gin.Context) {
var form struct {
CaId int64 `form:"ca_id"`
Search string `form:"search"`
Page int64 `form:"p"`
Limit int64 `form:"limit"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
data, count, err := private_ca.ListLeafCerts(form.CaId, form.Search, form.Page, form.Limit)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessData(c, data, count)
return
}
func DeleteLeafCert(c *gin.Context) {
var form struct {
Id int64 `form:"id"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
if form.Id <= 0 {
public.FailMsg(c, "ID不能为空")
return
}
err = private_ca.DeleteLeafCert(form.Id)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "删除成功")
return
}
func DownloadCert(c *gin.Context) {
var form struct {
Id int64 `form:"id"`
Type string `form:"type"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
if form.Id <= 0 {
public.FailMsg(c, "ID不能为空")
return
}
certData, err := private_ca.GetCert(form.Id, form.Type)
if err != nil {
public.FailMsg(c, err.Error())
return
}
// 构建 zip 包(内存中)
buf := new(bytes.Buffer)
zipWriter := zip.NewWriter(buf)
certStr := certData["cert"].(string)
certWriter, err := zipWriter.Create("cert.pem")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := certWriter.Write([]byte(certStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
// key.pem
keyStr := certData["key"].(string)
keyWriter, err := zipWriter.Create("key.pem")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := keyWriter.Write([]byte(keyStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
if certData["en_cert"] != nil && certData["en_key"] != nil {
// en_cert.pem
enCertStr := certData["en_cert"].(string)
enCertWriter, err := zipWriter.Create("en_cert.pem")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := enCertWriter.Write([]byte(enCertStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
// en_key.pem
enKeyStr := certData["en_key"].(string)
enKeyWriter, err := zipWriter.Create("en_key.pem")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := enKeyWriter.Write([]byte(enKeyStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
}
// 关闭 zipWriter
if err := zipWriter.Close(); err != nil {
public.FailMsg(c, err.Error())
return
}
// 设置响应头
zipName := strings.ReplaceAll(certData["cn"].(string), ".", "_")
zipName = strings.ReplaceAll(zipName, ",", "-")
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename="+zipName+".zip")
c.Data(200, "application/zip", buf.Bytes())
return
}

View File

@ -0,0 +1,480 @@
package private_ca
import (
"ALLinSSL/backend/public"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"time"
"github.com/tjfoc/gmsm/sm2"
gmx509 "github.com/tjfoc/gmsm/x509"
)
func GetSqlite() (*public.Sqlite, error) {
s, err := public.NewSqlite("data/private_ca.db", "")
if err != nil {
return nil, err
}
s.TableName = "ca"
return s, nil
}
// ----------- 标准算法 Root CA -----------
func GenerateRootCAStandard(name, commonName, organization, organizationalUnit, country, province, locality string, keyType KeyType, keyBits, validDays int) (*CAConfig, error) {
if keyType == KeySM2 {
return nil, errors.New("use GenerateRootCASM2 for SM2")
}
priv, err := generatePrivateKey(keyType, keyBits)
if err != nil {
return nil, err
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(10, 0, 0)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
MaxPathLen: 2,
}
cert, err := signCert(tmpl, tmpl, priv, priv, keyType)
if err != nil {
return nil, err
}
cert.KeyType = keyType
return &CAConfig{
Name: name,
CN: commonName,
O: organization,
C: country,
Cert: string(cert.CertPEM),
Key: string(cert.KeyPEM),
Algorithm: string(keyType),
KeyLength: int64(keyBits),
NotBefore: now.Format("2006-01-02 15:04:05"),
NotAfter: expire.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
// ----------- SM2 Root CA (双证书体系) -----------
func GenerateRootCASM2(name, commonName, organization, organizationalUnit, country, province, locality string, validDays int) (*CAConfig, error) {
// 1. 为签名和加密功能分别生成私钥
signPriv, err := generatePrivateKey(KeySM2, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 root signing key: %w", err)
}
encryptPriv, err := generatePrivateKey(KeySM2, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 root encryption key: %w", err)
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(10, 0, 0)
}
// 2. 创建根签名证书模板
signTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: gmx509.KeyUsageCertSign | gmx509.KeyUsageCRLSign,
BasicConstraintsValid: true,
MaxPathLen: 2,
}
// 3. 创建根加密证书模板
encryptTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano() + 1),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: gmx509.KeyUsageKeyEncipherment | gmx509.KeyUsageDataEncipherment,
BasicConstraintsValid: true,
MaxPathLen: 2,
}
// 4. 自签名根签名证书 (使用自己的签名私钥)
signCert, err := signSM2Cert(signTmpl, signTmpl, signPriv.(*sm2.PrivateKey), signPriv.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to self-sign SM2 root signing cert: %w", err)
}
// 5. 使用根签名证书签发根加密证书
encryptCert, err := signSM2Cert(encryptTmpl, signTmpl, encryptPriv.(*sm2.PrivateKey), signPriv.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to sign SM2 root encryption cert: %w", err)
}
// 6. 组装返回结果
return &CAConfig{
Name: name,
CN: commonName,
O: organization,
C: country,
Cert: string(signCert.CertPEM),
Key: string(signCert.KeyPEM),
EnCert: string(encryptCert.CertPEM),
EnKey: string(encryptCert.KeyPEM),
Algorithm: "sm2",
KeyLength: 256,
NotBefore: now.Format("2006-01-02 15:04:05"),
NotAfter: expire.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
// ----------- 标准算法 Intermediate CA -----------
func GenerateIntermediateCAStandard(name, commonName, organization, organizationalUnit, country, province, locality, cert, key, algorithm string, keyBits, validDays int) (*CAConfig, error) {
keyType := KeyType(algorithm)
issuer, err := NewCertificateFromPEMStandard([]byte(cert), []byte(key), keyType)
if err != nil {
return nil, fmt.Errorf("failed to parse issuer certificate: %w", err)
}
if issuer == nil {
return nil, errors.New("issuer is nil")
}
if issuer.KeyType != keyType {
return nil, fmt.Errorf("issuer and intermediate key type mismatch: %s != %s", issuer.KeyType, keyType)
}
if keyType == KeySM2 {
return nil, errors.New("use GenerateIntermediateCASM2 for SM2")
}
priv, err := generatePrivateKey(keyType, keyBits)
if err != nil {
return nil, err
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(5, 0, 0)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
MaxPathLen: 1,
}
certObj, err := signCert(tmpl, issuer.Cert, priv, issuer.Key, keyType)
if err != nil {
return nil, err
}
certObj.KeyType = keyType
return &CAConfig{
Name: name,
CN: commonName,
O: organization,
C: country,
Cert: string(certObj.CertPEM),
Key: string(certObj.KeyPEM),
Algorithm: string(keyType),
KeyLength: int64(keyBits),
NotBefore: now.Format("2006-01-02 15:04:05"),
NotAfter: expire.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
// ----------- SM2 Intermediate CA (双证书体系) -----------
func GenerateIntermediateCASM2(name, commonName, organization, organizationalUnit, country, province, locality, cert, key, enCert, enKey string, validDays int) (*CAConfig, error) {
issuer, err := NewCertificateFromPEMSM2([]byte(cert), []byte(key), []byte(enCert), []byte(enKey))
if err != nil {
return nil, fmt.Errorf("failed to parse issuer certificate: %w", err)
}
if issuer == nil {
return nil, errors.New("issuer is nil")
}
if issuer.KeyType != KeySM2 || issuer.SignGmCert == nil {
return nil, errors.New("issuer must be a valid SM2 dual-certificate CA")
}
// 1. 为签名和加密功能分别生成私钥
signPriv, err := generatePrivateKey(KeySM2, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 intermediate signing key: %w", err)
}
encryptPriv, err := generatePrivateKey(KeySM2, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 intermediate encryption key: %w", err)
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(5, 0, 0)
}
// 2. 创建中间签名证书模板
signTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: gmx509.KeyUsageCertSign | gmx509.KeyUsageCRLSign,
BasicConstraintsValid: true,
MaxPathLen: 1,
}
// 3. 创建中间加密证书模板
encryptTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano() + 1),
Subject: pkix.Name{
// 通用名称
CommonName: commonName,
// 组织名称
Organization: []string{organization},
// 组织单位名称
OrganizationalUnit: []string{organizationalUnit},
// 国家代码
Country: []string{country},
// 省份名称
Province: []string{province},
// 城市名称
Locality: []string{locality},
},
NotBefore: now,
NotAfter: expire,
IsCA: true,
KeyUsage: gmx509.KeyUsageKeyEncipherment | gmx509.KeyUsageDataEncipherment,
BasicConstraintsValid: true,
MaxPathLen: 1,
}
// 4. 使用颁发者的签名证书和私钥,签发中间签名证书
signCert, err := signSM2Cert(signTmpl, issuer.SignGmCert, signPriv.(*sm2.PrivateKey), issuer.Key.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to sign SM2 intermediate signing cert: %w", err)
}
// 5. 使用颁发者的签名证书和私钥,签发中间加密证书
encryptCert, err := signSM2Cert(encryptTmpl, issuer.SignGmCert, encryptPriv.(*sm2.PrivateKey), issuer.Key.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to sign SM2 intermediate encryption cert: %w", err)
}
// 6. 组装返回结果
return &CAConfig{
Name: name,
CN: commonName,
O: organization,
C: country,
Cert: string(signCert.CertPEM),
Key: string(signCert.KeyPEM),
EnCert: string(encryptCert.CertPEM),
EnKey: string(encryptCert.KeyPEM),
Algorithm: "sm2",
KeyLength: 256,
NotBefore: now.Format("2006-01-02 15:04:05"),
NotAfter: expire.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
// NewCertificateFromPEMStandard 从PEM格式的标准算法证书和私钥创建Certificate对象
func NewCertificateFromPEMStandard(certPEM, keyPEM []byte, keyType KeyType) (*Certificate, error) {
if keyType == KeySM2 {
return nil, errors.New("use NewCertificateFromPEMSM2 for SM2 certificates")
}
certBlock, _ := pem.Decode(certPEM)
if certBlock == nil || certBlock.Type != "CERTIFICATE" {
return nil, errors.New("failed to decode certificate PEM")
}
keyBlock, _ := pem.Decode(keyPEM)
if keyBlock == nil {
return nil, errors.New("failed to decode key PEM")
}
cert := &Certificate{
CertPEM: certPEM,
KeyPEM: keyPEM,
KeyType: keyType,
}
var err error
cert.Cert, err = x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse standard certificate: %w", err)
}
switch keyType {
case KeyRSA:
cert.Key, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
pkcs8Key, err2 := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
if err2 != nil {
return nil, fmt.Errorf("failed to parse rsa private key (PKCS1 and PKCS8): %w", err)
}
cert.Key = pkcs8Key
err = nil
}
case KeyECDSA:
cert.Key, err = x509.ParseECPrivateKey(keyBlock.Bytes)
if err != nil {
pkcs8Key, err2 := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
if err2 != nil {
return nil, fmt.Errorf("failed to parse ecdsa private key (EC and PKCS8): %w", err)
}
cert.Key = pkcs8Key
err = nil
}
default:
return nil, fmt.Errorf("unsupported key type: %s", keyType)
}
return cert, nil
}
// NewCertificateFromPEMSM2 从PEM格式的国密双证书和双私钥创建Certificate对象
func NewCertificateFromPEMSM2(signCertPEM, signKeyPEM, encryptCertPEM, encryptKeyPEM []byte) (*Certificate, error) {
// 解析签名证书
signCertBlock, _ := pem.Decode(signCertPEM)
if signCertBlock == nil || signCertBlock.Type != "CERTIFICATE" {
return nil, errors.New("failed to decode signing certificate PEM")
}
signGmCert, err := gmx509.ParseCertificate(signCertBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse sm2 signing certificate: %w", err)
}
// 解析签名私钥
signKeyBlock, _ := pem.Decode(signKeyPEM)
if signKeyBlock == nil {
return nil, errors.New("failed to decode signing key PEM")
}
signKey, err := gmx509.ParsePKCS8PrivateKey(signKeyBlock.Bytes, nil)
if err != nil {
return nil, fmt.Errorf("failed to parse sm2 signing private key: %w", err)
}
// 解析加密证书
encryptCertBlock, _ := pem.Decode(encryptCertPEM)
if encryptCertBlock == nil || encryptCertBlock.Type != "CERTIFICATE" {
return nil, errors.New("failed to decode encryption certificate PEM")
}
encryptGmCert, err := gmx509.ParseCertificate(encryptCertBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse sm2 encryption certificate: %w", err)
}
// 解析加密私钥
encryptKeyBlock, _ := pem.Decode(encryptKeyPEM)
if encryptKeyBlock == nil {
return nil, errors.New("failed to decode encryption key PEM")
}
encryptKey, err := gmx509.ParsePKCS8PrivateKey(encryptKeyBlock.Bytes, nil)
if err != nil {
return nil, fmt.Errorf("failed to parse sm2 encryption private key: %w", err)
}
return &Certificate{
SignCertPEM: signCertPEM,
SignKeyPEM: signKeyPEM,
EncryptCertPEM: encryptCertPEM,
EncryptKeyPEM: encryptKeyPEM,
Key: signKey,
EncryptKey: encryptKey,
SignGmCert: signGmCert,
EncryptGmCert: encryptGmCert,
KeyType: KeySM2,
}, nil
}

View File

@ -0,0 +1,40 @@
package private_ca
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"fmt"
gm "github.com/tjfoc/gmsm/sm2"
)
func generatePrivateKey(keyType KeyType, keyBits int) (interface{}, error) {
switch keyType {
case KeyRSA:
if keyBits == 0 {
keyBits = 2048
}
return rsa.GenerateKey(rand.Reader, keyBits)
case KeyECDSA:
var c elliptic.Curve
switch keyBits {
case 224:
c = elliptic.P224()
case 256:
c = elliptic.P256()
case 384:
c = elliptic.P384()
case 521:
c = elliptic.P521()
default:
c = elliptic.P384()
}
return ecdsa.GenerateKey(c, rand.Reader)
case KeySM2:
return gm.GenerateKey(rand.Reader)
default:
return nil, fmt.Errorf("unsupported key type: %s", keyType)
}
}

View File

@ -0,0 +1,141 @@
package private_ca
import (
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"github.com/tjfoc/gmsm/sm2"
gmx509 "github.com/tjfoc/gmsm/x509"
"math/big"
"time"
)
// GenerateLeafCertificate 生成叶子证书(服务器/客户端证书/邮件证书)
func GenerateLeafCertificate(commonName string, san SAN, issuer *Certificate, keyType KeyType, usage int, keyBits int, validDays int) (*LeafCertConfig, error) {
if issuer == nil {
return nil, errors.New("issuer is nil")
}
if issuer.KeyType != keyType {
return nil, errors.New("issuer key type mismatch")
}
if keyType == KeySM2 {
if issuer.SignGmCert == nil {
return nil, errors.New("issuer must be a valid SM2 dual-certificate CA")
}
// 国密SM2证书 - 生成签名和加密双证书, 使用不同私钥
// 1. 为签名和加密证书分别生成私钥
signPriv, err := generatePrivateKey(keyType, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 signing key: %w", err)
}
encryptPriv, err := generatePrivateKey(keyType, 0)
if err != nil {
return nil, fmt.Errorf("failed to generate SM2 encryption key: %w", err)
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(1, 0, 0)
}
// 2. 创建签名证书模板
signTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{CommonName: commonName},
NotBefore: now,
NotAfter: expire,
IsCA: false,
KeyUsage: gmx509.KeyUsageDigitalSignature, // 仅用于签名
ExtKeyUsage: []gmx509.ExtKeyUsage{gmx509.ExtKeyUsage(usage)},
DNSNames: san.DNSNames,
IPAddresses: san.IPAddresses,
EmailAddresses: san.EmailAddresses,
}
// 3. 创建加密证书模板
encryptTmpl := &gmx509.Certificate{
SerialNumber: big.NewInt(now.UnixNano() + 1), // 使用不同的序列号
Subject: pkix.Name{CommonName: commonName},
NotBefore: now,
NotAfter: expire,
IsCA: false,
KeyUsage: gmx509.KeyUsageKeyEncipherment, // 仅用于加密
ExtKeyUsage: []gmx509.ExtKeyUsage{gmx509.ExtKeyUsage(usage)},
DNSNames: san.DNSNames,
IPAddresses: san.IPAddresses,
EmailAddresses: san.EmailAddresses,
}
// 4. 使用颁发者密钥签发签名证书
signCert, err := signSM2Cert(signTmpl, issuer.SignGmCert, signPriv.(*sm2.PrivateKey), issuer.Key.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to sign SM2 signing cert: %w", err)
}
// 5. 使用颁发者密钥签发加密证书
encryptCert, err := signSM2Cert(encryptTmpl, issuer.EncryptGmCert, encryptPriv.(*sm2.PrivateKey), issuer.Key.(*sm2.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to sign SM2 encryption cert: %w", err)
}
// 6. 组装返回结果
return &LeafCertConfig{
CN: commonName,
Usage: int64(usage),
Cert: string(signCert.CertPEM),
Key: string(signCert.KeyPEM),
EnCert: string(encryptCert.CertPEM),
EnKey: string(encryptCert.KeyPEM),
Algorithm: "sm2",
KeyLength: 256,
NotAfter: expire.Format("2006-01-02 15:04:05"),
NotBefore: now.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
// 标准算法证书
priv, err := generatePrivateKey(keyType, keyBits)
if err != nil {
return nil, err
}
now := time.Now()
expire := now.AddDate(0, 0, validDays)
if validDays <= 0 {
expire = now.AddDate(1, 0, 0)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(now.UnixNano()),
Subject: pkix.Name{CommonName: commonName},
NotBefore: now,
NotAfter: expire,
IsCA: false,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsage(usage)},
}
tmpl.DNSNames = san.DNSNames
tmpl.IPAddresses = san.IPAddresses
cert, err := signCert(tmpl, issuer.Cert, priv, issuer.Key, keyType)
if err != nil {
return nil, err
}
cert.KeyType = keyType
return &LeafCertConfig{
CN: commonName,
Usage: int64(usage),
Cert: string(cert.CertPEM),
Key: string(cert.KeyPEM),
Algorithm: string(keyType),
KeyLength: int64(keyBits),
NotAfter: expire.Format("2006-01-02 15:04:05"),
NotBefore: now.Format("2006-01-02 15:04:05"),
CreateTime: now.Format("2006-01-02 15:04:05"),
}, nil
}

View File

@ -0,0 +1,399 @@
package private_ca
import (
"ALLinSSL/backend/public"
"encoding/json"
"fmt"
"time"
)
func CreateRootCA(name, commonName, organization, organizationalUnit, country, province, locality, keyType string, keyBits, validDays int64) error {
var err error
var data *CAConfig
if keyType == "sm2" {
// 国密SM2根证书 - 生成签名和加密双证书, 使用不同私钥
data, err = GenerateRootCASM2(name, commonName, organization, organizationalUnit, country, province, locality, int(validDays))
} else {
// 标准根证书 - 生成单一证书
data, err = GenerateRootCAStandard(name, commonName, organization, organizationalUnit, country, province, locality, KeyType(keyType), int(keyBits), int(validDays))
}
if err != nil {
return err
}
// 保存到数据库
s, err := GetSqlite()
if err != nil {
return err
}
_, err = s.Insert(public.StructToMap(data, true))
if err != nil {
return err
}
return nil
}
func CreateIntermediateCA(name, commonName, organization, organizationalUnit, country, province, locality string, rootId, keyBits, validDays int64) error {
s, err := GetSqlite()
if err != nil {
return err
}
issuers, err := s.Where("id=?", []interface{}{rootId}).Select()
if err != nil {
return err
}
if len(issuers) == 0 {
return fmt.Errorf("issuer with id %d not found", rootId)
}
issuer := issuers[0]
keyType := issuer["algorithm"].(string)
cert := issuer["cert"].(string)
key := issuer["key"].(string)
var data *CAConfig
if keyType == "sm2" {
// 国密SM2中级证书 - 生成签名和加密双证书, 使用不同私钥
enCert := issuer["en_cert"].(string)
enKey := issuer["en_key"].(string)
data, err = GenerateIntermediateCASM2(name, commonName, organization, organizationalUnit, country, province, locality, cert, key, enCert, enKey, int(validDays))
} else {
// 标准中级证书 - 生成单一证书
data, err = GenerateIntermediateCAStandard(name, commonName, organization, organizationalUnit, country, province, locality, cert, key, keyType, int(keyBits), int(validDays))
}
if err != nil {
return err
}
// 保存到数据库
insertData := public.StructToMap(data, true)
insertData["root_id"] = rootId
_, err = s.Insert(insertData)
if err != nil {
return err
}
return nil
}
func DeleteCA(id int64) error {
s, err := GetSqlite()
if err != nil {
return err
}
// 检查是否有子证书
children, err := s.Where("root_id=?", []interface{}{id}).Select()
if err != nil {
return err
}
if len(children) > 0 {
return fmt.Errorf("cannot delete CA with id %d: it has child CAs", id)
}
_, err = s.Where("root_id=?", []interface{}{id}).Delete()
if err != nil {
return err
}
return nil
}
func ListCAs(search string, p, limit int64) ([]map[string]interface{}, int, error) {
s, err := GetSqlite()
if err != nil {
return nil, 0, err
}
var data []map[string]any
var count int64
var limits []int64
if p >= 0 && limit >= 0 {
limits = []int64{0, limit}
if p > 1 {
limits[0] = (p - 1) * limit
limits[1] = limit
}
}
if search != "" {
data, err = s.Where("name like ? or cn like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Select()
count, err = s.Where("name like ? or cn like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Count()
} else {
data, err = s.Limit(limits).Order("create_time", "desc").Select()
count, err = s.Limit(limits).Order("create_time", "desc").Count()
}
if err != nil {
return data, int(count), err
}
return data, int(count), nil
}
func CreateLeafCert(caId, usage, keyBits, validDays int64, cn, san string) (*LeafCertConfig, error) {
if caId <= 0 {
return nil, fmt.Errorf("CA ID不能为空")
}
if san == "" {
return nil, fmt.Errorf("备用名称不能为空")
}
var sans SAN
err := json.Unmarshal([]byte(san), &sans)
if err != nil {
return nil, fmt.Errorf("备用名称格式错误: %v", err)
}
if cn == "" {
if len(sans.DNSNames) > 0 {
cn = sans.DNSNames[0]
} else {
cn = string(sans.IPAddresses[0])
}
}
s, err := GetSqlite()
if err != nil {
return nil, err
}
defer s.Close()
issuers, err := s.Where("id=?", []interface{}{caId}).Select()
if err != nil {
return nil, err
}
if len(issuers) == 0 {
return nil, fmt.Errorf("issuer with id %d not found", caId)
}
issuer := issuers[0]
if issuer["root_id"] == "" || issuer["root_id"] == nil {
return nil, fmt.Errorf("不允许使用根证书直接签发叶子证书,请先创建中间证书")
}
keyType := issuer["algorithm"].(string)
cert := issuer["cert"].(string)
key := issuer["key"].(string)
var issuerObj *Certificate
if keyType == "sm2" {
enCert := issuer["en_cert"].(string)
enKey := issuer["en_key"].(string)
issuerObj, err = NewCertificateFromPEMSM2([]byte(cert), []byte(key), []byte(enCert), []byte(enKey))
} else {
issuerObj, err = NewCertificateFromPEMStandard([]byte(cert), []byte(key), KeyType(keyType))
}
leafObj, err := GenerateLeafCertificate(cn, sans, issuerObj, KeyType(keyType), int(usage), int(keyBits), int(validDays))
if err != nil {
return nil, err
}
s.TableName = "leaf"
// 保存到数据库
leafObj.SAN = san
leafObj.CaId = caId
insertData := public.StructToMap(leafObj, true)
_, err = s.Insert(insertData)
if err != nil {
return nil, err
}
return leafObj, nil
}
func ListLeafCerts(caId int64, search string, p, limit int64) ([]map[string]interface{}, int, error) {
s, err := GetSqlite()
if err != nil {
return nil, 0, err
}
defer s.Close()
s.TableName = "leaf"
var data []map[string]any
var count int64
var limits []int64
if p >= 0 && limit >= 0 {
limits = []int64{0, limit}
if p > 1 {
limits[0] = (p - 1) * limit
limits[1] = limit
}
}
if caId > 0 && search != "" {
data, err = s.Where("ca_id=? and (cn like ? or san like ?)", []interface{}{caId, "%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Select()
count, err = s.Where("ca_id=? and (cn like ? or san like ?)", []interface{}{caId, "%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Count()
} else if caId > 0 {
data, err = s.Where("ca_id=?", []interface{}{caId}).Limit(limits).Order("create_time", "desc").Select()
count, err = s.Where("ca_id=?", []interface{}{caId}).Limit(limits).Order("create_time", "desc").Count()
} else if search != "" {
data, err = s.Where("cn like ? or san like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Select()
count, err = s.Where("cn like ? or san like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Limit(limits).Order("create_time", "desc").Count()
} else {
data, err = s.Limit(limits).Order("create_time", "desc").Select()
count, err = s.Limit(limits).Order("create_time", "desc").Count()
}
if err != nil {
return data, int(count), err
}
return data, int(count), nil
}
func DeleteLeafCert(id int64) error {
s, err := GetSqlite()
if err != nil {
return err
}
defer s.Close()
s.TableName = "leaf"
_, err = s.Where("id=?", []interface{}{id}).Delete()
if err != nil {
return err
}
return nil
}
func GetCert(id int64, certType string) (map[string]any, error) {
s, err := GetSqlite()
if err != nil {
return nil, err
}
defer s.Close()
s.TableName = certType
leafs, err := s.Where("id=?", []interface{}{id}).Select()
if err != nil {
return nil, err
}
if len(leafs) == 0 {
return nil, fmt.Errorf("leaf cert with id %d not found", id)
}
return leafs[0], nil
}
func WorkflowCreateLeafCert(params map[string]any, logger *public.Logger) (map[string]any, error) {
caId, ok := params["ca_id"].(float64)
if !ok || caId <= 0 {
return nil, fmt.Errorf("ca_id参数错误")
}
keyBits, ok := params["key_length"].(float64)
if !ok {
return nil, fmt.Errorf("key_length参数错误")
}
validDays, ok := params["valid_days"].(float64)
if !ok {
return nil, fmt.Errorf("valid_days参数错误")
}
endDay, ok := params["end_day"].(float64)
if !ok {
endDay = 0
}
cn, ok := params["cn"].(string)
if !ok {
cn = ""
}
san, ok := params["san"].(string)
if !ok || san == "" {
return nil, fmt.Errorf("san参数错误")
}
// 先获取ca信息确认是中间证书且不能是国密
s, err := GetSqlite()
if err != nil {
return nil, err
}
defer s.Close()
issuers, err := s.Where("id=?", []interface{}{caId}).Select()
if err != nil {
return nil, err
}
if len(issuers) == 0 {
return nil, fmt.Errorf("issuer with id %d not found", caId)
}
issuer := issuers[0]
if issuer["root_id"] == "" || issuer["root_id"] == nil {
return nil, fmt.Errorf("不允许使用根证书直接签发叶子证书,请先创建中间证书")
}
keyType := issuer["algorithm"].(string)
if keyType == "sm2" {
return nil, fmt.Errorf("暂不兼容国密证书,请使用标准证书")
}
// 判断中间证书不能为已过期,过期时间不能超过中间证书
caNotAfter, err := time.Parse("2006-01-02 15:04:05", issuer["not_after"].(string))
if err != nil {
return nil, fmt.Errorf("解析中间证书过期时间失败: %v", err)
}
maxValidDays := caNotAfter.Sub(time.Now()).Hours() / 24
if maxValidDays <= 0 {
return nil, fmt.Errorf("中间证书已过期,不能签发叶子证书")
}
if validDays > maxValidDays {
return nil, fmt.Errorf("叶子证书的有效期不能超过中间证书的有效期,中间证书将在 %s 过期,距离今天还有 %d 天", caNotAfter.Format("2006-01-02"), int(maxValidDays))
}
// 解析san判断数据库中是否已存在相同的cn和san
var sans SAN
err = json.Unmarshal([]byte(san), &sans)
if err != nil {
return nil, fmt.Errorf("备用名称格式错误: %v", err)
}
if cn == "" {
if len(sans.DNSNames) > 0 {
cn = sans.DNSNames[0]
} else {
cn = string(sans.IPAddresses[0])
}
}
s.TableName = "leaf"
// 获取所有未过期的证书判断是否有相同的cn和san
leafs, err := s.Where("ca_id=? and not_after>? and usage=1", []interface{}{caId, time.Now().Format("2006-01-02 15:04:05")}).Select()
if err != nil {
return nil, err
}
var certificate map[string]any
for _, v := range leafs {
// 判断剩余天数是否满足要求
if endDay > 0 {
notAfter, err := time.Parse("2006-01-02 15:04:05", v["not_after"].(string))
if err != nil {
continue
}
remainDays := notAfter.Sub(time.Now()).Hours() / 24
if remainDays < endDay {
continue
}
}
// 判断cn和san是否相同
if v["cn"] == cn {
var existingSAN SAN
err = json.Unmarshal([]byte(v["san"].(string)), &existingSAN)
if err != nil {
continue
}
if public.ContainsAllIgnoreBRepeats(existingSAN.DNSNames, sans.DNSNames) {
var existingIPs, newIPs []string
for _, ip := range existingSAN.IPAddresses {
existingIPs = append(existingIPs, ip.String())
}
for _, ip := range sans.IPAddresses {
newIPs = append(newIPs, ip.String())
}
if public.ContainsAllIgnoreBRepeats(existingIPs, newIPs) {
// 找到相同的证书,标记为跳过
logger.Debug("找到相同的证书,跳过创建", "id", v["id"])
certificate = map[string]any{
"cert": v["cert"],
"key": v["key"],
"skip": true,
}
break
}
}
}
}
if certificate == nil {
leaf, err := CreateLeafCert(int64(caId), 1, int64(keyBits), int64(validDays), cn, san)
if err != nil {
return nil, err
}
certificate = map[string]any{
"cert": leaf.Cert,
"key": leaf.Key,
}
}
return certificate, nil
}

View File

@ -0,0 +1,56 @@
package private_ca
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"github.com/tjfoc/gmsm/sm2"
gmx509 "github.com/tjfoc/gmsm/x509"
)
func signCert(tmpl, parent *x509.Certificate, priv, parentPriv interface{}, keyType KeyType) (*Certificate, error) {
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, parent, priv.(crypto.Signer).Public(), parentPriv.(crypto.Signer))
if err != nil {
return nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
var keyPEM []byte
switch k := priv.(type) {
case *rsa.PrivateKey:
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)})
case *ecdsa.PrivateKey:
b, _ := x509.MarshalECPrivateKey(k)
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
}
//tlsCert, _ := tls.X509KeyPair(certPEM, keyPEM)
return &Certificate{CertPEM: certPEM, KeyPEM: keyPEM, KeyType: keyType}, nil
}
func signSM2Cert(tmpl, parent *gmx509.Certificate, priv, parentPriv *sm2.PrivateKey) (*Certificate, error) {
certDER, err := gmx509.CreateCertificate(tmpl, parent, &priv.PublicKey, parentPriv)
if err != nil {
return nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyBytes, err := gmx509.MarshalSm2PrivateKey(priv, nil)
if err != nil {
return nil, err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "SM2 PRIVATE KEY", Bytes: keyBytes})
//tlsCert, _ := tls.X509KeyPair(certPEM, keyPEM)
return &Certificate{
CertPEM: certPEM,
KeyPEM: keyPEM,
KeyType: KeySM2,
}, nil
}

View File

@ -0,0 +1,90 @@
package private_ca
import (
"crypto/tls"
"crypto/x509"
gmx509 "github.com/tjfoc/gmsm/x509"
"net"
)
type KeyType string
const (
KeySM2 KeyType = "sm2"
KeyECDSA KeyType = "ecdsa"
KeyRSA KeyType = "rsa"
)
type Certificate struct {
// 对于标准算法,使用这两个字段
CertPEM []byte `json:"cert_pem,omitempty"`
KeyPEM []byte `json:"key_pem,omitempty"`
// 对于国密双证书,使用这些字段
SignCertPEM []byte `json:"sign_cert_pem,omitempty"`
SignKeyPEM []byte `json:"sign_key_pem,omitempty"`
EncryptCertPEM []byte `json:"encrypt_cert_pem,omitempty"`
EncryptKeyPEM []byte `json:"encrypt_key_pem,omitempty"`
// --- 以下为内部使用字段 ---
TLSCert *tls.Certificate `json:"-"`
// 标准算法证书对象
Cert *x509.Certificate `json:"-"`
// 国密证书对象
SignGmCert *gmx509.Certificate `json:"-"`
EncryptGmCert *gmx509.Certificate `json:"-"`
// 私钥对象
Key interface{} `json:"-"` // 标准算法私钥 或 国密签名私钥
EncryptKey interface{} `json:"-"` // 国密加密私钥
KeyType KeyType `json:"-"`
}
type SAN struct {
DNSNames []string `json:"dns_names"`
IPAddresses []net.IP `json:"ip_addresses"`
EmailAddresses []string `json:"email_addresses"`
}
type CAConfig struct {
Id int64 `json:"id" form:"id"`
RootId int64 `json:"root_id" form:"root_id"`
Name string `json:"name" form:"name"`
CN string `json:"cn" form:"cn"`
O string `json:"o" form:"o"`
C string `json:"c" form:"c"`
OU string `json:"ou" form:"ou"`
Province string `json:"province" form:"province"`
Locality string `json:"locality" form:"locality"`
Cert string `json:"cert" form:"cert"`
Key string `json:"key" form:"key"`
EnCert string `json:"en_cert" form:"en_cert"`
EnKey string `json:"en_key" form:"en_key"`
Algorithm string `json:"algorithm" form:"algorithm"`
KeyLength int64 `json:"key_length" form:"key_length"`
NotAfter string `json:"not_after" form:"not_after"`
NotBefore string `json:"not_before" form:"not_before"`
CreateTime string `json:"create_time" form:"create_time"`
ValidDays int64 `json:"valid_days" form:"valid_days"`
}
type LeafCertConfig struct {
Id int64 `json:"id" form:"id"`
CaId int64 `json:"ca_id" form:"ca_id"`
CN string `json:"cn" form:"cn"`
SAN string `json:"san" form:"san"`
Usage int64 `json:"usage" form:"usage"`
Cert string `json:"cert" form:"cert"`
Key string `json:"key" form:"key"`
EnCert string `json:"en_cert" form:"en_cert"`
EnKey string `json:"en_key" form:"en_key"`
Algorithm string `json:"algorithm" form:"algorithm"`
KeyLength int64 `json:"key_length" form:"key_length"`
NotAfter string `json:"not_after" form:"not_after"`
NotBefore string `json:"not_before" form:"not_before"`
ValidDays int64 `json:"valid_days" form:"valid_days"`
CreateTime string `json:"create_time" form:"create_time"`
}

View File

@ -4,6 +4,7 @@ import (
"ALLinSSL/backend/internal/cert"
certApply "ALLinSSL/backend/internal/cert/apply"
certDeploy "ALLinSSL/backend/internal/cert/deploy"
"ALLinSSL/backend/internal/private_ca"
"ALLinSSL/backend/internal/report"
"ALLinSSL/backend/public"
"errors"
@ -27,11 +28,26 @@ func Executors(exec string, params map[string]any) (any, error) {
return upload(params)
case "notify":
return notify(params)
case "private_ca":
return privateCa(params)
default:
return nil, nil
}
}
func privateCa(params map[string]any) (any, error) {
logger := params["logger"].(*public.Logger)
logger.Info("=============私有CA签发证书=============")
certificate, err := private_ca.WorkflowCreateLeafCert(params, logger)
if err != nil {
logger.Error(err.Error())
logger.Info("=============签发失败=============")
return nil, err
}
logger.Info("=============签发成功=============")
return certificate, nil
}
func apply(params map[string]any) (any, error) {
logger := params["logger"].(*public.Logger)

View File

@ -10,6 +10,7 @@ import (
"net"
"net/http"
"os/exec"
"reflect"
"runtime"
"strings"
)
@ -231,3 +232,60 @@ func CheckIPType(address string) string {
}
return "IPv6"
}
// StructToMap 将结构体转换为 map[string]interface{}
// 如果 ignoreZero 为 true则忽略零值字段
func StructToMap(obj interface{}, ignoreZero bool) map[string]interface{} {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
m := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
// 跳过未导出的字段
if !value.CanInterface() {
continue
}
// 获取 json tag若为空则用字段名
tag := field.Tag.Get("json")
if tag == "" {
tag = field.Name
}
// 忽略 0 值
if ignoreZero && isZero(value) {
continue
}
m[tag] = value.Interface()
}
return m
}
// 判断是否为零值
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
}
// 其他情况用反射自带的 IsZero
return v.IsZero()
}

View File

@ -3,6 +3,7 @@ package route
import (
"ALLinSSL/backend/app/api"
"ALLinSSL/backend/app/api/monitor"
"ALLinSSL/backend/app/api/private_ca"
"ALLinSSL/backend/public"
"ALLinSSL/static"
"github.com/gin-gonic/gin"
@ -105,6 +106,18 @@ func Register(r *gin.Engine) {
{
overview.POST("/get_overviews", api.GetOverview)
}
privateCa := v1.Group("/private_ca")
{
privateCa.POST("/create_root_ca", private_ca.CreateRootCA)
privateCa.POST("/create_intermediate_ca", private_ca.CreateIntermediateCA)
privateCa.POST("/get_ca_list", private_ca.GetCAList)
privateCa.POST("/del_ca", private_ca.DeleteCA)
privateCa.POST("/create_leaf_cert", private_ca.CreateLeafCert)
privateCa.POST("/get_leaf_cert_list", private_ca.GetLeafCertList)
privateCa.POST("/del_leaf_cert", private_ca.DeleteLeafCert)
privateCa.GET("/download_cert", private_ca.DownloadCert)
}
// 静态资源:/static -> build/static
staticFS, _ := fs.Sub(static.BuildFS, "build/static")