mirror of https://github.com/allinssl/allinssl
【新增】私有ca
parent
e53a88e4dd
commit
c9cb3fa5e1
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"ALLinSSL/backend/internal/cert"
|
"ALLinSSL/backend/internal/cert"
|
||||||
certApply "ALLinSSL/backend/internal/cert/apply"
|
certApply "ALLinSSL/backend/internal/cert/apply"
|
||||||
certDeploy "ALLinSSL/backend/internal/cert/deploy"
|
certDeploy "ALLinSSL/backend/internal/cert/deploy"
|
||||||
|
"ALLinSSL/backend/internal/private_ca"
|
||||||
"ALLinSSL/backend/internal/report"
|
"ALLinSSL/backend/internal/report"
|
||||||
"ALLinSSL/backend/public"
|
"ALLinSSL/backend/public"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -27,11 +28,26 @@ func Executors(exec string, params map[string]any) (any, error) {
|
||||||
return upload(params)
|
return upload(params)
|
||||||
case "notify":
|
case "notify":
|
||||||
return notify(params)
|
return notify(params)
|
||||||
|
case "private_ca":
|
||||||
|
return privateCa(params)
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
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) {
|
func apply(params map[string]any) (any, error) {
|
||||||
logger := params["logger"].(*public.Logger)
|
logger := params["logger"].(*public.Logger)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -231,3 +232,60 @@ func CheckIPType(address string) string {
|
||||||
}
|
}
|
||||||
return "IPv6"
|
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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package route
|
||||||
import (
|
import (
|
||||||
"ALLinSSL/backend/app/api"
|
"ALLinSSL/backend/app/api"
|
||||||
"ALLinSSL/backend/app/api/monitor"
|
"ALLinSSL/backend/app/api/monitor"
|
||||||
|
"ALLinSSL/backend/app/api/private_ca"
|
||||||
"ALLinSSL/backend/public"
|
"ALLinSSL/backend/public"
|
||||||
"ALLinSSL/static"
|
"ALLinSSL/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -105,6 +106,18 @@ func Register(r *gin.Engine) {
|
||||||
{
|
{
|
||||||
overview.POST("/get_overviews", api.GetOverview)
|
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
|
// 静态资源:/static -> build/static
|
||||||
staticFS, _ := fs.Sub(static.BuildFS, "build/static")
|
staticFS, _ := fs.Sub(static.BuildFS, "build/static")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue