package api import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "math/big" "strconv" "time" "next-terminal/server/common" "next-terminal/server/common/maps" "next-terminal/server/model" "next-terminal/server/repository" "next-terminal/server/utils" "github.com/labstack/echo/v4" ) type CertificateApi struct{} func (api CertificateApi) AllEndpoint(c echo.Context) error { items, err := repository.CertificateRepository.FindAll(context.TODO()) if err != nil { return err } return Success(c, items) } func (api CertificateApi) PagingEndpoint(c echo.Context) error { pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) keyword := c.QueryParam("keyword") items, total, err := repository.CertificateRepository.Find(context.TODO(), pageIndex, pageSize, keyword) if err != nil { return err } return Success(c, maps.Map{ "total": total, "items": items, }) } func parseCertificate(certPEM string) (commonName, subject, issuer string, notBefore, notAfter time.Time, err error) { block, _ := pem.Decode([]byte(certPEM)) if block == nil { return "", "", "", time.Time{}, time.Time{}, nil } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return "", "", "", time.Time{}, time.Time{}, err } commonName = cert.Subject.CommonName if commonName == "" && len(cert.Subject.Names) > 0 { commonName = cert.Subject.Names[0].Value.(string) } subject = cert.Subject.String() issuer = cert.Issuer.String() notBefore = cert.NotBefore notAfter = cert.NotAfter return commonName, subject, issuer, notBefore, notAfter, nil } func generateSelfSignedCertificate(commonName string) (certPEM, keyPEM string, notBefore, notAfter time.Time, err error) { priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", "", time.Time{}, time.Time{}, err } notBefore = time.Now() notAfter = notBefore.Add(365 * 24 * time.Hour) serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return "", "", time.Time{}, time.Time{}, err } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: commonName, }, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return "", "", time.Time{}, time.Time{}, err } certPEM = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})) keyPEM = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})) return certPEM, keyPEM, notBefore, notAfter, nil } func (api CertificateApi) CreateEndpoint(c echo.Context) error { var req struct { CommonName string `json:"commonName"` Certificate string `json:"certificate"` PrivateKey string `json:"privateKey"` Type string `json:"type"` RequireClientAuth bool `json:"requireClientAuth"` } if err := c.Bind(&req); err != nil { return err } item := &model.Certificate{ ID: utils.UUID(), CommonName: req.CommonName, Certificate: req.Certificate, PrivateKey: req.PrivateKey, Type: req.Type, RequireClientAuth: req.RequireClientAuth, IssuedStatus: "success", Created: common.NowJsonTime(), UpdatedAt: common.NowJsonTime(), } if item.Type == "" { item.Type = "imported" } if req.Type == "self-signed" && req.Certificate == "" { certPEM, keyPEM, notBefore, notAfter, err := generateSelfSignedCertificate(req.CommonName) if err == nil { item.Certificate = certPEM item.PrivateKey = keyPEM item.NotBefore = common.NewJsonTime(notBefore) item.NotAfter = common.NewJsonTime(notAfter) item.Subject = "CN=" + req.CommonName item.Issuer = "CN=" + req.CommonName } } else if req.Certificate != "" { commonName, subject, issuer, notBefore, notAfter, err := parseCertificate(req.Certificate) if err == nil { if item.CommonName == "" { item.CommonName = commonName } item.Subject = subject item.Issuer = issuer item.NotBefore = common.NewJsonTime(notBefore) item.NotAfter = common.NewJsonTime(notAfter) } } if err := repository.CertificateRepository.Create(context.TODO(), item); err != nil { return err } return Success(c, "") } func (api CertificateApi) UpdateEndpoint(c echo.Context) error { id := c.Param("id") var req struct { CommonName string `json:"commonName"` Certificate string `json:"certificate"` PrivateKey string `json:"privateKey"` RequireClientAuth bool `json:"requireClientAuth"` } if err := c.Bind(&req); err != nil { return err } item := &model.Certificate{ ID: id, CommonName: req.CommonName, Certificate: req.Certificate, PrivateKey: req.PrivateKey, RequireClientAuth: req.RequireClientAuth, UpdatedAt: common.NowJsonTime(), } if req.Certificate != "" { commonName, subject, issuer, notBefore, notAfter, err := parseCertificate(req.Certificate) if err == nil { if item.CommonName == "" { item.CommonName = commonName } item.Subject = subject item.Issuer = issuer item.NotBefore = common.NewJsonTime(notBefore) item.NotAfter = common.NewJsonTime(notAfter) } } if err := repository.CertificateRepository.UpdateById(context.TODO(), item, id); err != nil { return err } return Success(c, nil) } func (api CertificateApi) DeleteEndpoint(c echo.Context) error { id := c.Param("id") if err := repository.CertificateRepository.DeleteById(context.TODO(), id); err != nil { return err } return Success(c, nil) } func (api CertificateApi) GetEndpoint(c echo.Context) error { id := c.Param("id") item, err := repository.CertificateRepository.FindById(context.TODO(), id) if err != nil { return err } return Success(c, item) } func (api CertificateApi) UpdateDefaultEndpoint(c echo.Context) error { id := c.Param("id") err := repository.CertificateRepository.UpdateById(context.TODO(), &model.Certificate{IsDefault: false}, id) if err != nil { return err } return Success(c, nil) } func (api CertificateApi) DownloadEndpoint(c echo.Context) error { return Success(c, nil) } func (api CertificateApi) RenewEndpoint(c echo.Context) error { return Success(c, maps.Map{ "success": true, "warning": false, "error": "", }) }