118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
package acme
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
certstore "github.com/havline/havline/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
|
|
}
|