From c9cb3fa5e10e719de589066699cfc0be4eaf080d Mon Sep 17 00:00:00 2001 From: v-me-50 Date: Tue, 26 Aug 2025 16:52:07 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=E7=A7=81?= =?UTF-8?q?=E6=9C=89ca?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/private_ca/private_ca.go | 227 ++++++++++ backend/internal/private_ca/ca.go | 480 ++++++++++++++++++++++ backend/internal/private_ca/keygen.go | 40 ++ backend/internal/private_ca/leaf.go | 141 +++++++ backend/internal/private_ca/private_ca.go | 399 ++++++++++++++++++ backend/internal/private_ca/sign.go | 56 +++ backend/internal/private_ca/types.go | 90 ++++ backend/internal/workflow/executor.go | 16 + backend/public/utils.go | 58 +++ backend/route/route.go | 13 + 10 files changed, 1520 insertions(+) create mode 100644 backend/app/api/private_ca/private_ca.go create mode 100644 backend/internal/private_ca/ca.go create mode 100644 backend/internal/private_ca/keygen.go create mode 100644 backend/internal/private_ca/leaf.go create mode 100644 backend/internal/private_ca/private_ca.go create mode 100644 backend/internal/private_ca/sign.go create mode 100644 backend/internal/private_ca/types.go diff --git a/backend/app/api/private_ca/private_ca.go b/backend/app/api/private_ca/private_ca.go new file mode 100644 index 0000000..ac13d0b --- /dev/null +++ b/backend/app/api/private_ca/private_ca.go @@ -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 +} diff --git a/backend/internal/private_ca/ca.go b/backend/internal/private_ca/ca.go new file mode 100644 index 0000000..a15646f --- /dev/null +++ b/backend/internal/private_ca/ca.go @@ -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 +} diff --git a/backend/internal/private_ca/keygen.go b/backend/internal/private_ca/keygen.go new file mode 100644 index 0000000..8f96d31 --- /dev/null +++ b/backend/internal/private_ca/keygen.go @@ -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) + } +} diff --git a/backend/internal/private_ca/leaf.go b/backend/internal/private_ca/leaf.go new file mode 100644 index 0000000..8f58d7f --- /dev/null +++ b/backend/internal/private_ca/leaf.go @@ -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 +} diff --git a/backend/internal/private_ca/private_ca.go b/backend/internal/private_ca/private_ca.go new file mode 100644 index 0000000..062a089 --- /dev/null +++ b/backend/internal/private_ca/private_ca.go @@ -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 +} diff --git a/backend/internal/private_ca/sign.go b/backend/internal/private_ca/sign.go new file mode 100644 index 0000000..db840c3 --- /dev/null +++ b/backend/internal/private_ca/sign.go @@ -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 +} diff --git a/backend/internal/private_ca/types.go b/backend/internal/private_ca/types.go new file mode 100644 index 0000000..6613dd4 --- /dev/null +++ b/backend/internal/private_ca/types.go @@ -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"` +} diff --git a/backend/internal/workflow/executor.go b/backend/internal/workflow/executor.go index cf43d6a..06987ae 100644 --- a/backend/internal/workflow/executor.go +++ b/backend/internal/workflow/executor.go @@ -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) diff --git a/backend/public/utils.go b/backend/public/utils.go index 7ba8f6c..452fb50 100644 --- a/backend/public/utils.go +++ b/backend/public/utils.go @@ -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() +} diff --git a/backend/route/route.go b/backend/route/route.go index 30867c6..3bd4841 100644 --- a/backend/route/route.go +++ b/backend/route/route.go @@ -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")