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")
}
}()