From e8951fbabfcfc53e8b69470d4712549a52f9a8bb Mon Sep 17 00:00:00 2001 From: nite Date: Thu, 5 Feb 2026 03:39:21 +0800 Subject: [PATCH] add: encrypted transmission --- frontend/src/components/TransferItem.vue | 6 +- internal/security/cert.go | 81 ++++++++++++++++++++++++ internal/transfer/client.go | 8 +-- internal/transfer/service.go | 30 ++++++++- 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 internal/security/cert.go diff --git a/frontend/src/components/TransferItem.vue b/frontend/src/components/TransferItem.vue index 9ddd5c2..cb60746 100644 --- a/frontend/src/components/TransferItem.vue +++ b/frontend/src/components/TransferItem.vue @@ -253,7 +253,11 @@ const handleCopy = async () => { Accept - + Save to Folder diff --git a/internal/security/cert.go b/internal/security/cert.go new file mode 100644 index 0000000..563b4c3 --- /dev/null +++ b/internal/security/cert.go @@ -0,0 +1,81 @@ +package security + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "time" +) + +// EnsureCertificates 检查证书和密钥文件是否存在。 +// 如果不存在,则生成新的自签名证书和密钥。 +func EnsureCertificates(certPath, keyPath string) error { + if _, err := os.Stat(certPath); err == nil { + if _, err := os.Stat(keyPath); err == nil { + return nil + } + } + + return generateSelfSignedCert(certPath, keyPath) +} + +func generateSelfSignedCert(certPath, keyPath string) error { + // 生成私钥 + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + + // 创建证书模板 + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"MeshDrop"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // 将 IP 地址添加到 SAN(主题备用名称)中,用于本地发现 + // 添加常见的本地接口 IP 地址和 localhost + template.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")} + + // 在实际的动态环境中,我们可能希望添加所有当前接口的 IP 地址 + // 实际上,在客户端跳过 IP 验证对于本地 P2P 来说是很常见的。 + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return err + } + + // 写入证书文件 + certOut, err := os.Create(certPath) + if err != nil { + return err + } + defer certOut.Close() + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return err + } + + // 写入密钥文件 + keyOut, err := os.Create(keyPath) + if err != nil { + return err + } + defer keyOut.Close() + if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { + return err + } + + return nil +} diff --git a/internal/transfer/client.go b/internal/transfer/client.go index eba1f3f..b644d03 100644 --- a/internal/transfer/client.go +++ b/internal/transfer/client.go @@ -201,14 +201,14 @@ func (s *Service) ask(ctx context.Context, target *discovery.Peer, targetIP stri // 发送请求 askBody, _ := json.Marshal(task) - askUrl := fmt.Sprintf("http://%s:%d/transfer/ask", targetIP, target.Port) + askUrl := fmt.Sprintf("https://%s:%d/transfer/ask", targetIP, target.Port) req, err := http.NewRequestWithContext(ctx, http.MethodPost, askUrl, bytes.NewReader(askBody)) req.Header.Set("Content-Type", "application/json") if err != nil { return TransferAskResponse{}, err } - resp, err := http.DefaultClient.Do(req) + resp, err := s.httpClient.Do(req) if err != nil { return TransferAskResponse{}, err } @@ -234,7 +234,7 @@ func (s *Service) processTransfer(ctx context.Context, askResp TransferAskRespon if err := ctx.Err(); err != nil { return } - uploadUrl, _ := url.Parse(fmt.Sprintf("http://%s:%d/transfer/upload/%s", targetIP, target.Port, task.ID)) + uploadUrl, _ := url.Parse(fmt.Sprintf("https://%s:%d/transfer/upload/%s", targetIP, target.Port, task.ID)) query := uploadUrl.Query() query.Add("token", askResp.Token) uploadUrl.RawQuery = query.Encode() @@ -260,7 +260,7 @@ func (s *Service) processTransfer(ctx context.Context, askResp TransferAskRespon req.ContentLength = task.FileSize req.Header.Set("Content-Type", "application/octet-stream") - resp, err := http.DefaultClient.Do(req) + resp, err := s.httpClient.Do(req) if err != nil { if errors.Is(err, context.Canceled) { task.Status = TransferStatusCanceled diff --git a/internal/transfer/service.go b/internal/transfer/service.go index 8e227d0..18897d3 100644 --- a/internal/transfer/service.go +++ b/internal/transfer/service.go @@ -2,10 +2,14 @@ package transfer import ( "context" + "crypto/tls" "fmt" "log/slog" "mesh-drop/internal/config" "mesh-drop/internal/discovery" + "mesh-drop/internal/security" + "net/http" + "path/filepath" "sync" "github.com/gin-gonic/gin" @@ -28,17 +32,29 @@ type Service struct { // cancelMap 存储取消操作的通道 // Key: TransferID, Value: context.CancelFunc cancelMap sync.Map + + httpClient *http.Client } func NewService(config *config.Config, app *application.App, notifier *notifications.NotificationService, port int, discoveryService *discovery.Service) *Service { gin.SetMode(gin.ReleaseMode) + // 配置自定义 HTTP 客户端以跳过自签名证书验证 + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + httpClient := &http.Client{ + Transport: tr, + Timeout: 0, + } + return &Service{ app: app, notifier: notifier, port: port, discoveryService: discoveryService, config: config, + httpClient: httpClient, } } @@ -59,9 +75,19 @@ func (s *Service) Start() { } go func() { + configDir := config.GetConfigDir() + certPath := filepath.Join(configDir, "server.crt") + keyPath := filepath.Join(configDir, "server.key") + + if err := security.EnsureCertificates(certPath, keyPath); err != nil { + slog.Error("Failed to generate certificates", "error", err, "component", "transfer") + return + } + addr := fmt.Sprintf(":%d", s.port) - slog.Info("Transfer service listening", "address", addr, "component", "transfer") - if err := r.Run(addr); err != nil { + slog.Info("Transfer service listening (HTTPS)", "address", addr, "component", "transfer") + + if err := r.RunTLS(addr, certPath, keyPath); err != nil { slog.Error("Transfer service error", "error", err, "component", "transfer") } }()