Files
Havline/internal/acme/download.go
T
mrlee 278f8267b7 chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听)
- 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP)
- 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点)
- 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
2026-09-23 09:40:59 +08:00

118 lines
2.9 KiB
Go

package acme
import (
"archive/zip"
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
certstore "github.com/fonu/fonu/internal/certificate"
)
type DownloadPart string
const (
DownloadZIP DownloadPart = "zip"
DownloadCert DownloadPart = "cert"
DownloadKey DownloadPart = "key"
)
func (s *Service) Download(ctx context.Context, domain string, part string) (filename string, contentType string, data []byte, err error) {
domain = strings.ToLower(strings.TrimSpace(domain))
if domain == "" {
return "", "", nil, fmt.Errorf("域名不能为空")
}
rec, err := s.store.GetByDomain(ctx, domain)
if err != nil {
return "", "", nil, err
}
if rec.CertPath == "" || rec.KeyPath == "" {
return "", "", nil, fmt.Errorf("证书文件不存在,可能尚未申请成功")
}
if err := validateCertFilePath(s.cfg.CertsDir(), rec.CertPath); err != nil {
return "", "", nil, err
}
if err := validateCertFilePath(s.cfg.CertsDir(), rec.KeyPath); err != nil {
return "", "", nil, err
}
certPEM, err := os.ReadFile(rec.CertPath)
if err != nil {
return "", "", nil, fmt.Errorf("读取证书失败")
}
keyPEM, err := os.ReadFile(rec.KeyPath)
if err != nil {
return "", "", nil, fmt.Errorf("读取私钥失败")
}
base := downloadBaseName(rec)
switch DownloadPart(strings.ToLower(strings.TrimSpace(part))) {
case "", DownloadZIP:
data, err = zipCertBundle(base, certPEM, keyPEM)
if err != nil {
return "", "", nil, err
}
return base + ".zip", "application/zip", data, nil
case DownloadCert:
return base + "-fullchain.pem", "application/x-pem-file", certPEM, nil
case DownloadKey:
return base + "-privatekey.pem", "application/x-pem-file", keyPEM, nil
default:
return "", "", nil, fmt.Errorf("不支持的下载类型")
}
}
func downloadBaseName(rec certstore.Record) string {
name := strings.TrimPrefix(rec.Domain, "*.")
name = strings.ReplaceAll(name, "*", "wildcard")
name = strings.ReplaceAll(name, "/", "_")
if name == "" {
name = "certificate"
}
return name
}
func validateCertFilePath(certsDir, path string) error {
absCerts, err := filepath.Abs(certsDir)
if err != nil {
return fmt.Errorf("证书目录无效")
}
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("证书路径无效")
}
rel, err := filepath.Rel(absCerts, absPath)
if err != nil || strings.HasPrefix(rel, "..") {
return fmt.Errorf("证书路径无效")
}
return nil
}
func zipCertBundle(base string, certPEM, keyPEM []byte) ([]byte, error) {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
if err := writeZipEntry(zw, "fullchain.pem", certPEM); err != nil {
return nil, err
}
if err := writeZipEntry(zw, "privatekey.pem", keyPEM); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func writeZipEntry(zw *zip.Writer, name string, data []byte) error {
w, err := zw.Create(name)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}