1111
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 47s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 47s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m3s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 51s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m6s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s

This commit is contained in:
Flik
2025-12-29 19:23:09 +08:00
parent 4116d8934c
commit ab81e08100
16 changed files with 846 additions and 36 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/gotunnel/pkg/plugin"
"github.com/gotunnel/pkg/plugin/script"
"github.com/gotunnel/pkg/plugin/sign"
"github.com/gotunnel/pkg/protocol"
"github.com/gotunnel/pkg/relay"
"github.com/hashicorp/yamux"
@@ -35,11 +36,13 @@ type Client struct {
ID string
TLSEnabled bool
TLSConfig *tls.Config
DataDir string // 数据目录
session *yamux.Session
rules []protocol.ProxyRule
mu sync.RWMutex
pluginRegistry *plugin.Registry
runningPlugins map[string]plugin.ClientPlugin // 运行中的客户端插件
runningPlugins map[string]plugin.ClientPlugin
versionStore *PluginVersionStore
pluginMu sync.RWMutex
}
@@ -48,14 +51,30 @@ func NewClient(serverAddr, token, id string) *Client {
if id == "" {
id = loadClientID()
}
// 默认数据目录
home, _ := os.UserHomeDir()
dataDir := filepath.Join(home, ".gotunnel")
return &Client{
ServerAddr: serverAddr,
Token: token,
ID: id,
DataDir: dataDir,
runningPlugins: make(map[string]plugin.ClientPlugin),
}
}
// InitVersionStore 初始化版本存储
func (c *Client) InitVersionStore() error {
store, err := NewPluginVersionStore(c.DataDir)
if err != nil {
return err
}
c.versionStore = store
return nil
}
// getIDFilePath 获取 ID 文件路径
func getIDFilePath() string {
home, err := os.UserHomeDir()
@@ -478,6 +497,14 @@ func (c *Client) handleJSPluginInstall(stream net.Conn, msg *protocol.Message) {
log.Printf("[Client] Installing JS plugin: %s", req.PluginName)
// 验证官方签名
if err := c.verifyJSPluginSignature(req.PluginName, req.Source, req.Signature); err != nil {
log.Printf("[Client] JS plugin %s signature verification failed: %v", req.PluginName, err)
c.sendJSPluginResult(stream, req.PluginName, false, "signature verification failed: "+err.Error())
return
}
log.Printf("[Client] JS plugin %s signature verified", req.PluginName)
// 创建 JS 插件
jsPlugin, err := script.NewJSPlugin(req.PluginName, req.Source)
if err != nil {
@@ -493,6 +520,14 @@ func (c *Client) handleJSPluginInstall(stream net.Conn, msg *protocol.Message) {
log.Printf("[Client] JS plugin %s installed", req.PluginName)
c.sendJSPluginResult(stream, req.PluginName, true, "")
// 保存版本信息(防止降级攻击)
if c.versionStore != nil {
signed, _ := sign.DecodeSignedPlugin(req.Signature)
if signed != nil {
c.versionStore.SetVersion(req.PluginName, signed.Payload.Version)
}
}
// 自动启动
if req.AutoStart {
c.startJSPlugin(jsPlugin, req)
@@ -530,3 +565,58 @@ func (c *Client) startJSPlugin(handler plugin.ClientPlugin, req protocol.JSPlugi
log.Printf("[Client] JS plugin %s started at %s", req.PluginName, localAddr)
}
// verifyJSPluginSignature 验证 JS 插件签名
func (c *Client) verifyJSPluginSignature(pluginName, source, signature string) error {
if signature == "" {
return fmt.Errorf("missing signature")
}
// 解码签名
signed, err := sign.DecodeSignedPlugin(signature)
if err != nil {
return fmt.Errorf("decode signature: %w", err)
}
// 检查插件是否被撤销
if revoked, reason := sign.IsPluginRevoked(pluginName, signed.Payload.Version); revoked {
return fmt.Errorf("plugin %s v%s has been revoked: %s",
pluginName, signed.Payload.Version, reason)
}
// 检查密钥是否已吊销
if sign.IsKeyRevoked(signed.Payload.KeyID) {
return fmt.Errorf("signing key %s has been revoked", signed.Payload.KeyID)
}
// 根据 KeyID 获取对应公钥
pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID)
if err != nil {
return fmt.Errorf("get public key: %w", err)
}
// 验证插件名称匹配
if signed.Payload.Name != pluginName {
return fmt.Errorf("plugin name mismatch: expected %s, got %s",
pluginName, signed.Payload.Name)
}
// 验证签名和源码哈希
if err := sign.VerifyPlugin(pubKey, signed, source); err != nil {
return err
}
// 检查版本降级攻击
if c.versionStore != nil {
currentVer := c.versionStore.GetVersion(pluginName)
if currentVer != "" {
cmp := sign.CompareVersions(signed.Payload.Version, currentVer)
if cmp < 0 {
return fmt.Errorf("version downgrade rejected: %s < %s",
signed.Payload.Version, currentVer)
}
}
}
return nil
}

View File

@@ -0,0 +1,67 @@
package tunnel
import (
"encoding/json"
"os"
"path/filepath"
"sync"
)
// PluginVersionStore 插件版本存储
type PluginVersionStore struct {
path string
versions map[string]string // pluginName -> version
mu sync.RWMutex
}
// NewPluginVersionStore 创建版本存储
func NewPluginVersionStore(dataDir string) (*PluginVersionStore, error) {
store := &PluginVersionStore{
path: filepath.Join(dataDir, "plugin_versions.json"),
versions: make(map[string]string),
}
if err := store.load(); err != nil {
return nil, err
}
return store, nil
}
// load 从文件加载版本信息
func (s *PluginVersionStore) load() error {
data, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return err
}
return json.Unmarshal(data, &s.versions)
}
// save 保存版本信息到文件
func (s *PluginVersionStore) save() error {
dir := filepath.Dir(s.path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(s.versions, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.path, data, 0600)
}
// GetVersion 获取插件版本
func (s *PluginVersionStore) GetVersion(name string) string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.versions[name]
}
// SetVersion 设置插件版本
func (s *PluginVersionStore) SetVersion(name, version string) error {
s.mu.Lock()
defer s.mu.Unlock()
s.versions[name] = version
return s.save()
}