diff --git a/backend/app/api/cert.go b/backend/app/api/cert.go index 1d05588..e54f19c 100644 --- a/backend/app/api/cert.go +++ b/backend/app/api/cert.go @@ -41,7 +41,7 @@ func UploadCert(c *gin.Context) { } form.Key = strings.TrimSpace(form.Key) form.Cert = strings.TrimSpace(form.Cert) - + if form.Key == "" { public.FailMsg(c, "名称不能为空") return @@ -83,7 +83,7 @@ func DelCert(c *gin.Context) { func DownloadCert(c *gin.Context) { ID := c.Query("id") - + if ID == "" { public.FailMsg(c, "ID不能为空") return @@ -93,35 +93,66 @@ func DownloadCert(c *gin.Context) { public.FailMsg(c, err.Error()) return } - + // 构建 zip 包(内存中) buf := new(bytes.Buffer) zipWriter := zip.NewWriter(buf) - - for filename, content := range certData { - if filename == "cert" || filename == "key" { - writer, err := zipWriter.Create(filename + ".pem") - if err != nil { - public.FailMsg(c, err.Error()) - return - } - _, err = writer.Write([]byte(content)) - if err != nil { - public.FailMsg(c, err.Error()) - return - } + + // 写入PEM文件 + // cert.pem + certStr := certData["cert"] + certWriter, err := zipWriter.Create("Nginx/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"] + keyWriter, err := zipWriter.Create("Nginx/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 + } + // cert.pfx + pfxPassword := "allinssl" + pfxData, err := public.PEMToPFX(certStr, keyStr, pfxPassword) + if err == nil && len(pfxData) > 0 { + pfxWriter, err := zipWriter.Create("IIS/cert.pfx") + if err != nil { + public.FailMsg(c, err.Error()) + return + } + if _, err := pfxWriter.Write(pfxData); err != nil { + public.FailMsg(c, err.Error()) + return + } + txtWriter, err := zipWriter.Create("IIS/passwd.txt") + if err != nil { + public.FailMsg(c, err.Error()) + return + } + if _, err := txtWriter.Write([]byte(pfxPassword)); 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["domains"], ".", "_") 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()) diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index 763dede..15ab426 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -53,7 +53,7 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { return Deploy1panelSite(cfg) case "ssh": logger.Debug("使用ssh部署到指定路径...") - return DeploySSH(cfg) + return DeploySSH(cfg, logger) case "aliyun-cdn": logger.Debug("部署到阿里云CDN...") return DeployAliCdn(cfg) diff --git a/backend/internal/cert/deploy/ssh.go b/backend/internal/cert/deploy/ssh.go index 07a953d..018fde0 100644 --- a/backend/internal/cert/deploy/ssh.go +++ b/backend/internal/cert/deploy/ssh.go @@ -30,25 +30,34 @@ func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) { var methods []ssh.AuthMethod if privateKey != "" { - signer, err := ssh.ParsePrivateKey([]byte(privateKey)) - if err != nil { - return nil, fmt.Errorf("unable to parse private key: %v", err) + var signer ssh.Signer + var err error + if password != "" { + signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(password)) + if err != nil { + return nil, fmt.Errorf("无法解析带密码的私钥: %v", err) + } + } else { + signer, err = ssh.ParsePrivateKey([]byte(privateKey)) + if err != nil { + return nil, fmt.Errorf("无法解析私钥: %v", err) + } } methods = append(methods, ssh.PublicKeys(signer)) } - if password != "" { + if password != "" && privateKey == "" { methods = append(methods, ssh.Password(password)) } if len(methods) == 0 { - return nil, fmt.Errorf("no authentication methods provided") + return nil, fmt.Errorf("未提供有效的认证方式") } return methods, nil } -func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error { +func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string, logger *public.Logger) error { var port string switch v := config.Port.(type) { case float64: @@ -91,8 +100,9 @@ func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, post return fmt.Errorf("会话创建失败: %v", err) } defer session.Close() - - var script bytes.Buffer + var script, stdoutBuf, stderrBuf bytes.Buffer + session.Stdout = &stdoutBuf + session.Stderr = &stderrBuf if preCmd != "" { script.WriteString(preCmd + " && ") @@ -115,14 +125,14 @@ func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, post cmd := script.String() - if err := session.Run(cmd); err != nil { - return fmt.Errorf("运行出错: %v", err) - } + err = session.Run(cmd) + logger.Debug("[STDOUT]", stdoutBuf.String()) + logger.Debug("[STDERR]", stderrBuf.String()) - return nil + return err } -func DeploySSH(cfg map[string]any) error { +func DeploySSH(cfg map[string]any, logger *public.Logger) error { cert, ok := cfg["certificate"].(map[string]any) if !ok { return fmt.Errorf("证书不存在") @@ -180,7 +190,7 @@ func DeploySSH(cfg map[string]any) error { {Path: certPath, Content: certPem}, {Path: keyPath, Content: keyPem}, } - err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd) + err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd, logger) if err != nil { return fmt.Errorf("SSH 部署失败: %v", err) } diff --git a/backend/internal/report/workwx.go b/backend/internal/report/workwx.go index c13d75d..6174c96 100644 --- a/backend/internal/report/workwx.go +++ b/backend/internal/report/workwx.go @@ -48,7 +48,7 @@ func NotifyWorkWx(params map[string]any) error { return err } configStr := providerData["config"].(string) - fmt.Println(configStr) + //fmt.Println(configStr) var config map[string]string err = json.Unmarshal([]byte(configStr), &config) if err != nil { diff --git a/backend/public/cert.go b/backend/public/cert.go index 8bee1f8..b813185 100644 --- a/backend/public/cert.go +++ b/backend/public/cert.go @@ -12,6 +12,8 @@ import ( "encoding/pem" "errors" "fmt" + "software.sslmate.com/src/go-pkcs12" + "strings" "time" ) @@ -138,3 +140,47 @@ func GetSHA256(certStr string) (string, error) { sha256Hash := sha256.Sum256(cert.Raw) return hex.EncodeToString(sha256Hash[:]), nil } + +// PEMToPFX 将PEM格式的证书和私钥转换为PFX格式 +func PEMToPFX(certPEM, keyPEM, pfxPassword string) ([]byte, error) { + // 使用默认密码如果未提供 + if pfxPassword == "" { + pfxPassword = "allinssl" + } + + // 解析证书PEM + certBlock, _ := pem.Decode([]byte(certPEM)) + if certBlock == nil || certBlock.Type != "CERTIFICATE" { + return nil, fmt.Errorf("无效的证书PEM格式") + } + + // 解析私钥PEM + keyBlock, _ := pem.Decode([]byte(keyPEM)) + if keyBlock == nil || (!strings.Contains(keyBlock.Type, "PRIVATE KEY")) { + return nil, fmt.Errorf("无效的私钥PEM格式") + } + + // 解析X.509证书 + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("解析证书失败: %v", err) + } + + // 尝试解析私钥(PKCS8或PKCS1格式) + var privKey interface{} + privKey, err = x509.ParsePKCS8PrivateKey(keyBlock.Bytes) + if err != nil { + privKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("解析私钥失败: %v", err) + } + } + + // 编码为PFX格式 + pfxData, err := pkcs12.Encode(rand.Reader, privKey, cert, nil, pfxPassword) + if err != nil { + return nil, fmt.Errorf("编码PFX失败: %v", err) + } + + return pfxData, nil +}