diff --git a/cmd/client/main.go b/cmd/client/main.go index 418e746..f0c4c32 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -5,13 +5,11 @@ import ( "log" "os" "path/filepath" - "time" "github.com/gotunnel/internal/client/tunnel" "github.com/gotunnel/pkg/crypto" "github.com/gotunnel/pkg/plugin" "github.com/gotunnel/pkg/plugin/builtin" - "github.com/gotunnel/pkg/plugin/sign" ) func main() { @@ -42,9 +40,6 @@ func main() { } } - // 初始化安全配置 - initSecurityConfig() - // 初始化插件系统 registry := plugin.NewRegistry() for _, h := range builtin.GetClientPlugins() { @@ -57,38 +52,3 @@ func main() { client.Run() } - -// 官方安全配置 URL(与服务端保持一致) -const ( - officialRevocationURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/revocation.json" - officialKeyListURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/keys.json" -) - -// initSecurityConfig 初始化安全配置 -func initSecurityConfig() { - // 配置撤销列表 - sign.SetRevocationConfig(sign.RevocationConfig{ - RemoteURL: officialRevocationURL, - FetchInterval: 1 * time.Hour, - RequestTimeout: 10 * time.Second, - VerifySignature: true, - }) - - // 配置公钥列表 - sign.SetKeyListConfig(sign.KeyListConfig{ - RemoteURL: officialKeyListURL, - FetchInterval: 24 * time.Hour, - RequestTimeout: 10 * time.Second, - }) - - // 启动后台刷新 - stopCh := make(chan struct{}) - go sign.StartRevocationRefresher(stopCh) - - // 立即拉取一次 - if err := sign.FetchRemoteKeyList(); err != nil { - log.Printf("[Security] Fetch key list failed: %v", err) - } - - log.Printf("[Security] Initialized") -} diff --git a/cmd/server/main.go b/cmd/server/main.go index f65b04f..839452f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -59,9 +59,6 @@ func main() { log.Printf("[Server] TLS enabled") } - // 初始化安全配置(撤销列表和公钥列表) - initSecurityConfig() - // 初始化插件系统 registry := plugin.NewRegistry() if err := registry.RegisterAllServer(builtin.GetServerPlugins()); err != nil { @@ -167,16 +164,6 @@ func verifyPluginSignature(name, source, signature string) error { return fmt.Errorf("decode signature: %w", err) } - // 检查插件是否被撤销 - if revoked, reason := sign.IsPluginRevoked(name, signed.Payload.Version); revoked { - return fmt.Errorf("plugin revoked: %s", reason) - } - - // 检查密钥是否已吊销 - if sign.IsKeyRevoked(signed.Payload.KeyID) { - return fmt.Errorf("signing key revoked: %s", signed.Payload.KeyID) - } - // 获取公钥 pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID) if err != nil { @@ -191,32 +178,3 @@ func verifyPluginSignature(name, source, signature string) error { // 验证签名 return sign.VerifyPlugin(pubKey, signed, source) } - -// initSecurityConfig 初始化安全配置 -func initSecurityConfig() { - // 配置撤销列表 - sign.SetRevocationConfig(sign.RevocationConfig{ - RemoteURL: config.OfficialRevocationURL, - FetchInterval: 1 * time.Hour, - RequestTimeout: 10 * time.Second, - VerifySignature: true, - }) - - // 配置公钥列表 - sign.SetKeyListConfig(sign.KeyListConfig{ - RemoteURL: config.OfficialKeyListURL, - FetchInterval: 24 * time.Hour, - RequestTimeout: 10 * time.Second, - }) - - // 启动后台刷新 - stopCh := make(chan struct{}) - go sign.StartRevocationRefresher(stopCh) - - // 立即拉取一次公钥列表 - if err := sign.FetchRemoteKeyList(); err != nil { - log.Printf("[Security] Fetch key list failed: %v", err) - } - - log.Printf("[Security] Initialized with remote revocation and key list") -} diff --git a/internal/client/tunnel/client.go b/internal/client/tunnel/client.go index 5553ae7..cf11a02 100644 --- a/internal/client/tunnel/client.go +++ b/internal/client/tunnel/client.go @@ -578,17 +578,6 @@ func (c *Client) verifyJSPluginSignature(pluginName, source, signature string) e 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 { diff --git a/internal/server/config/config.go b/internal/server/config/config.go index db0a75c..5104940 100644 --- a/internal/server/config/config.go +++ b/internal/server/config/config.go @@ -26,19 +26,21 @@ type JSPluginConfig struct { AutoStart bool `yaml:"auto_start,omitempty"` // 是否自动启动 } -// PluginStoreSettings 扩展商店设置 +// PluginStoreSettings 插件仓库设置 type PluginStoreSettings struct { - // 保留结构体以便未来扩展,但不暴露 URL 配置 + URL string `yaml:"url"` // 插件仓库 URL,为空则使用默认值 } -// 官方插件商店(不可配置) -const OfficialPluginStoreURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/store.json" +// 默认插件仓库 URL +const DefaultPluginStoreURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/store.json" -// 官方安全配置 URL -const ( - OfficialRevocationURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/revocation.json" - OfficialKeyListURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/keys.json" -) +// GetPluginStoreURL 获取插件仓库 URL +func (s *PluginStoreSettings) GetPluginStoreURL() string { + if s.URL != "" { + return s.URL + } + return DefaultPluginStoreURL +} // ServerSettings 服务端设置 type ServerSettings struct { diff --git a/internal/server/router/api.go b/internal/server/router/api.go index af1bd6f..acedf10 100644 --- a/internal/server/router/api.go +++ b/internal/server/router/api.go @@ -600,8 +600,7 @@ func (h *APIHandler) handleStorePlugins(rw http.ResponseWriter, r *http.Request) } cfg := h.app.GetConfig() - storeURL := config.OfficialPluginStoreURL - _ = cfg // 保留以便未来扩展 + storeURL := cfg.PluginStore.GetPluginStoreURL() // 从远程URL获取插件列表 client := &http.Client{Timeout: 10 * time.Second} diff --git a/pkg/plugin/sign/official.go b/pkg/plugin/sign/official.go index 61fdc81..60f0aa2 100644 --- a/pkg/plugin/sign/official.go +++ b/pkg/plugin/sign/official.go @@ -2,217 +2,30 @@ package sign import ( "crypto/ed25519" - "encoding/json" - "fmt" - "io" - "log" - "net/http" "sync" - "time" ) -// KeyEntry 密钥条目 -type KeyEntry struct { - ID string `json:"id"` - PublicKey string `json:"public_key"` - ValidFrom time.Time `json:"valid_from"` - RevokedAt time.Time `json:"revoked_at,omitempty"` -} - -// 官方公钥列表(支持密钥轮换) -var officialKeys = []KeyEntry{ - { - ID: "official-v1", - PublicKey: "0A0xRthj0wgPg8X8GJZ6/EnNpAUw5v7O//XLty+P5Yw=", - ValidFrom: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), - }, - // 添加新密钥时,在此处追加 -} +// 官方固定公钥(客户端内置) +const OfficialPublicKeyBase64 = "0A0xRthj0wgPg8X8GJZ6/EnNpAUw5v7O//XLty+P5Yw=" var ( - keyCache map[string]ed25519.PublicKey - keyCacheOnce sync.Once - keyMu sync.RWMutex - remoteKeys []KeyEntry + officialPubKey ed25519.PublicKey + officialPubKeyOnce sync.Once + officialPubKeyErr error ) -// KeyListConfig 远程公钥列表配置 -type KeyListConfig struct { - RemoteURL string - FetchInterval time.Duration - RequestTimeout time.Duration +// initOfficialKey 初始化官方公钥 +func initOfficialKey() { + officialPubKey, officialPubKeyErr = DecodePublicKey(OfficialPublicKeyBase64) } -var keyListConfig = KeyListConfig{ - RemoteURL: "", - FetchInterval: 24 * time.Hour, - RequestTimeout: 10 * time.Second, -} - -// SetKeyListConfig 设置远程公钥列表配置 -func SetKeyListConfig(cfg KeyListConfig) { - keyMu.Lock() - defer keyMu.Unlock() - keyListConfig = cfg -} - -// initKeyCache 初始化密钥缓存 -func initKeyCache() { - keyCache = make(map[string]ed25519.PublicKey) - for _, entry := range officialKeys { - if pub, err := DecodePublicKey(entry.PublicKey); err == nil { - keyCache[entry.ID] = pub - } - } -} - -// GetOfficialPublicKey 获取默认官方公钥(兼容旧接口) +// GetOfficialPublicKey 获取官方公钥 func GetOfficialPublicKey() (ed25519.PublicKey, error) { - return GetPublicKeyByID("official-v1") + officialPubKeyOnce.Do(initOfficialKey) + return officialPubKey, officialPubKeyErr } -// GetPublicKeyByID 根据 ID 获取公钥 +// GetPublicKeyByID 根据 ID 获取公钥(兼容旧接口,忽略 keyID) func GetPublicKeyByID(keyID string) (ed25519.PublicKey, error) { - keyCacheOnce.Do(initKeyCache) - - keyMu.RLock() - pub, ok := keyCache[keyID] - keyMu.RUnlock() - - if !ok { - return nil, fmt.Errorf("unknown key ID: %s", keyID) - } - return pub, nil -} - -// IsKeyRevoked 检查密钥是否已吊销 -func IsKeyRevoked(keyID string) bool { - // 先检查内置密钥 - for _, entry := range officialKeys { - if entry.ID == keyID { - return !entry.RevokedAt.IsZero() - } - } - // 再检查远程密钥 - keyMu.RLock() - defer keyMu.RUnlock() - for _, entry := range remoteKeys { - if entry.ID == keyID { - return !entry.RevokedAt.IsZero() - } - } - return true // 未知密钥视为已吊销 -} - -// GetKeyEntry 获取密钥条目 -func GetKeyEntry(keyID string) *KeyEntry { - for i := range officialKeys { - if officialKeys[i].ID == keyID { - return &officialKeys[i] - } - } - keyMu.RLock() - defer keyMu.RUnlock() - for i := range remoteKeys { - if remoteKeys[i].ID == keyID { - return &remoteKeys[i] - } - } - return nil -} - -// KeyList 远程公钥列表结构 -type KeyList struct { - Version int `json:"version"` - UpdatedAt int64 `json:"updated_at"` - Keys []KeyEntry `json:"keys"` - Signature string `json:"signature"` -} - -// FetchRemoteKeyList 从远程拉取公钥列表 -func FetchRemoteKeyList() error { - keyMu.RLock() - cfg := keyListConfig - keyMu.RUnlock() - - if cfg.RemoteURL == "" { - return nil - } - - client := &http.Client{Timeout: cfg.RequestTimeout} - resp, err := client.Get(cfg.RemoteURL) - if err != nil { - return fmt.Errorf("fetch key list: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("fetch key list: status %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("read key list: %w", err) - } - - var list KeyList - if err := json.Unmarshal(body, &list); err != nil { - return fmt.Errorf("parse key list: %w", err) - } - - // 验证签名(使用内置公钥验证) - if err := verifyKeyListSignature(&list); err != nil { - return fmt.Errorf("verify key list: %w", err) - } - - return updateKeyCache(&list) -} - -// verifyKeyListSignature 验证公钥列表签名 -func verifyKeyListSignature(list *KeyList) error { - if list.Signature == "" { - return fmt.Errorf("missing signature") - } - - // 使用内置公钥验证(必须用内置密钥签名) - pubKey, err := GetOfficialPublicKey() - if err != nil { - return err - } - - signData := struct { - Version int `json:"version"` - UpdatedAt int64 `json:"updated_at"` - Keys []KeyEntry `json:"keys"` - }{ - Version: list.Version, - UpdatedAt: list.UpdatedAt, - Keys: list.Keys, - } - - data, err := json.Marshal(signData) - if err != nil { - return err - } - - return VerifyBase64(pubKey, data, list.Signature) -} - -// updateKeyCache 更新公钥缓存 -func updateKeyCache(list *KeyList) error { - keyMu.Lock() - defer keyMu.Unlock() - - // 保存远程密钥列表 - remoteKeys = list.Keys - - // 更新缓存 - for _, entry := range list.Keys { - if pub, err := DecodePublicKey(entry.PublicKey); err == nil { - keyCache[entry.ID] = pub - } - } - - log.Printf("[KeyList] Updated with %d keys", len(list.Keys)) - return nil + return GetOfficialPublicKey() } diff --git a/pkg/plugin/sign/revocation.go b/pkg/plugin/sign/revocation.go deleted file mode 100644 index 2fcbe98..0000000 --- a/pkg/plugin/sign/revocation.go +++ /dev/null @@ -1,242 +0,0 @@ -package sign - -import ( - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "sync" - "time" -) - -// RevocationEntry 撤销条目 -type RevocationEntry struct { - PluginName string `json:"plugin_name"` // 插件名称 - Version string `json:"version,omitempty"` // 特定版本(空表示所有版本) - Reason string `json:"reason"` // 撤销原因 - RevokedAt int64 `json:"revoked_at"` // 撤销时间戳 -} - -// RevocationList 撤销列表 -type RevocationList struct { - Version int `json:"version"` // 列表版本 - UpdatedAt int64 `json:"updated_at"` // 更新时间 - Entries []RevocationEntry `json:"entries"` // 撤销条目 - Signature string `json:"signature"` // 列表签名 -} - -// 内置撤销列表(编译时确定,作为 fallback) -var builtinRevocations = []RevocationEntry{ - // 示例:{PluginName: "malicious-plugin", Reason: "security vulnerability"} -} - -var ( - revocationCache map[string][]RevocationEntry // pluginName -> entries - revocationCacheOnce sync.Once - revocationMu sync.RWMutex - currentListVersion int - lastFetchTime time.Time -) - -// RevocationConfig 远程撤销列表配置 -type RevocationConfig struct { - RemoteURL string // 远程撤销列表 URL - FetchInterval time.Duration // 拉取间隔 - RequestTimeout time.Duration // 请求超时 - VerifySignature bool // 是否验证签名 -} - -var defaultRevocationConfig = RevocationConfig{ - RemoteURL: "", // 默认为空,不启用远程拉取 - FetchInterval: 1 * time.Hour, - RequestTimeout: 10 * time.Second, - VerifySignature: true, -} - -var revocationConfig = defaultRevocationConfig - -// SetRevocationConfig 设置远程撤销列表配置 -func SetRevocationConfig(cfg RevocationConfig) { - revocationMu.Lock() - defer revocationMu.Unlock() - revocationConfig = cfg -} - -// GetRevocationConfig 获取当前配置 -func GetRevocationConfig() RevocationConfig { - revocationMu.RLock() - defer revocationMu.RUnlock() - return revocationConfig -} - -// initRevocationCache 初始化撤销缓存 -func initRevocationCache() { - revocationCache = make(map[string][]RevocationEntry) - for _, entry := range builtinRevocations { - revocationCache[entry.PluginName] = append( - revocationCache[entry.PluginName], entry) - } -} - -// IsPluginRevoked 检查插件是否被撤销 -func IsPluginRevoked(name, version string) (bool, string) { - revocationCacheOnce.Do(initRevocationCache) - - revocationMu.RLock() - defer revocationMu.RUnlock() - - entries, ok := revocationCache[name] - if !ok { - return false, "" - } - - for _, entry := range entries { - // 空版本表示所有版本都被撤销 - if entry.Version == "" || entry.Version == version { - return true, entry.Reason - } - } - return false, "" -} - -// FetchRemoteRevocationList 从远程拉取撤销列表 -func FetchRemoteRevocationList() error { - cfg := GetRevocationConfig() - if cfg.RemoteURL == "" { - return nil // 未配置远程 URL,跳过 - } - - // 检查是否需要刷新 - revocationMu.RLock() - if time.Since(lastFetchTime) < cfg.FetchInterval { - revocationMu.RUnlock() - return nil - } - revocationMu.RUnlock() - - // 发起 HTTP 请求 - client := &http.Client{Timeout: cfg.RequestTimeout} - resp, err := client.Get(cfg.RemoteURL) - if err != nil { - return fmt.Errorf("fetch revocation list: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("fetch revocation list: status %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("read revocation list: %w", err) - } - - var list RevocationList - if err := json.Unmarshal(body, &list); err != nil { - return fmt.Errorf("parse revocation list: %w", err) - } - - // 验证签名 - if cfg.VerifySignature { - if err := verifyRevocationListSignature(&list); err != nil { - return fmt.Errorf("verify revocation list: %w", err) - } - } - - // 更新缓存 - return updateRevocationCache(&list) -} - -// verifyRevocationListSignature 验证撤销列表签名 -func verifyRevocationListSignature(list *RevocationList) error { - if list.Signature == "" { - return fmt.Errorf("missing signature") - } - - // 获取官方公钥 - pubKey, err := GetOfficialPublicKey() - if err != nil { - return fmt.Errorf("get public key: %w", err) - } - - // 构造待签名数据(不含签名字段) - signData := struct { - Version int `json:"version"` - UpdatedAt int64 `json:"updated_at"` - Entries []RevocationEntry `json:"entries"` - }{ - Version: list.Version, - UpdatedAt: list.UpdatedAt, - Entries: list.Entries, - } - - data, err := json.Marshal(signData) - if err != nil { - return fmt.Errorf("marshal sign data: %w", err) - } - - return VerifyBase64(pubKey, data, list.Signature) -} - -// updateRevocationCache 更新撤销缓存 -func updateRevocationCache(list *RevocationList) error { - revocationMu.Lock() - defer revocationMu.Unlock() - - // 检查版本号,防止回滚攻击 - if list.Version < currentListVersion { - return fmt.Errorf("revocation list version rollback: %d < %d", list.Version, currentListVersion) - } - - // 重建缓存:先加载内置列表,再合并远程列表 - newCache := make(map[string][]RevocationEntry) - for _, entry := range builtinRevocations { - newCache[entry.PluginName] = append(newCache[entry.PluginName], entry) - } - for _, entry := range list.Entries { - newCache[entry.PluginName] = append(newCache[entry.PluginName], entry) - } - - revocationCache = newCache - currentListVersion = list.Version - lastFetchTime = time.Now() - - log.Printf("[Revocation] Updated to version %d with %d entries", list.Version, len(list.Entries)) - return nil -} - -// StartRevocationRefresher 启动后台刷新协程 -func StartRevocationRefresher(stopCh <-chan struct{}) { - cfg := GetRevocationConfig() - if cfg.RemoteURL == "" { - return - } - - // 立即执行一次 - if err := FetchRemoteRevocationList(); err != nil { - log.Printf("[Revocation] Initial fetch failed: %v", err) - } - - ticker := time.NewTicker(cfg.FetchInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if err := FetchRemoteRevocationList(); err != nil { - log.Printf("[Revocation] Refresh failed: %v", err) - } - case <-stopCh: - log.Printf("[Revocation] Refresher stopped") - return - } - } -} - -// GetRevocationListVersion 获取当前撤销列表版本 -func GetRevocationListVersion() int { - revocationMu.RLock() - defer revocationMu.RUnlock() - return currentListVersion -}