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"
|
||||
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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue