refactor(plugin): 简化插件签名验证机制
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Failing after 13m34s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Failing after 13m34s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
- 移除远程密钥撤销检查功能 - 移除远程公钥列表拉取和缓存机制 - 将官方公钥改为客户端内置固定值 - 简化 GetPublicKeyByID 接口实现 - 移除相关的安全配置初始化代码 - 将插件仓库URL配置改为可配置化设置
This commit is contained in:
@@ -5,13 +5,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gotunnel/internal/client/tunnel"
|
"github.com/gotunnel/internal/client/tunnel"
|
||||||
"github.com/gotunnel/pkg/crypto"
|
"github.com/gotunnel/pkg/crypto"
|
||||||
"github.com/gotunnel/pkg/plugin"
|
"github.com/gotunnel/pkg/plugin"
|
||||||
"github.com/gotunnel/pkg/plugin/builtin"
|
"github.com/gotunnel/pkg/plugin/builtin"
|
||||||
"github.com/gotunnel/pkg/plugin/sign"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -42,9 +40,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化安全配置
|
|
||||||
initSecurityConfig()
|
|
||||||
|
|
||||||
// 初始化插件系统
|
// 初始化插件系统
|
||||||
registry := plugin.NewRegistry()
|
registry := plugin.NewRegistry()
|
||||||
for _, h := range builtin.GetClientPlugins() {
|
for _, h := range builtin.GetClientPlugins() {
|
||||||
@@ -57,38 +52,3 @@ func main() {
|
|||||||
|
|
||||||
client.Run()
|
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")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -59,9 +59,6 @@ func main() {
|
|||||||
log.Printf("[Server] TLS enabled")
|
log.Printf("[Server] TLS enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化安全配置(撤销列表和公钥列表)
|
|
||||||
initSecurityConfig()
|
|
||||||
|
|
||||||
// 初始化插件系统
|
// 初始化插件系统
|
||||||
registry := plugin.NewRegistry()
|
registry := plugin.NewRegistry()
|
||||||
if err := registry.RegisterAllServer(builtin.GetServerPlugins()); err != nil {
|
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)
|
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)
|
pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -191,32 +178,3 @@ func verifyPluginSignature(name, source, signature string) error {
|
|||||||
// 验证签名
|
// 验证签名
|
||||||
return sign.VerifyPlugin(pubKey, signed, source)
|
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")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -578,17 +578,6 @@ func (c *Client) verifyJSPluginSignature(pluginName, source, signature string) e
|
|||||||
return fmt.Errorf("decode signature: %w", err)
|
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 获取对应公钥
|
// 根据 KeyID 获取对应公钥
|
||||||
pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID)
|
pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -26,19 +26,21 @@ type JSPluginConfig struct {
|
|||||||
AutoStart bool `yaml:"auto_start,omitempty"` // 是否自动启动
|
AutoStart bool `yaml:"auto_start,omitempty"` // 是否自动启动
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginStoreSettings 扩展商店设置
|
// PluginStoreSettings 插件仓库设置
|
||||||
type PluginStoreSettings struct {
|
type PluginStoreSettings struct {
|
||||||
// 保留结构体以便未来扩展,但不暴露 URL 配置
|
URL string `yaml:"url"` // 插件仓库 URL,为空则使用默认值
|
||||||
}
|
}
|
||||||
|
|
||||||
// 官方插件商店(不可配置)
|
// 默认插件仓库 URL
|
||||||
const OfficialPluginStoreURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/store.json"
|
const DefaultPluginStoreURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/store.json"
|
||||||
|
|
||||||
// 官方安全配置 URL
|
// GetPluginStoreURL 获取插件仓库 URL
|
||||||
const (
|
func (s *PluginStoreSettings) GetPluginStoreURL() string {
|
||||||
OfficialRevocationURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/revocation.json"
|
if s.URL != "" {
|
||||||
OfficialKeyListURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/keys.json"
|
return s.URL
|
||||||
)
|
}
|
||||||
|
return DefaultPluginStoreURL
|
||||||
|
}
|
||||||
|
|
||||||
// ServerSettings 服务端设置
|
// ServerSettings 服务端设置
|
||||||
type ServerSettings struct {
|
type ServerSettings struct {
|
||||||
|
|||||||
@@ -600,8 +600,7 @@ func (h *APIHandler) handleStorePlugins(rw http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := h.app.GetConfig()
|
cfg := h.app.GetConfig()
|
||||||
storeURL := config.OfficialPluginStoreURL
|
storeURL := cfg.PluginStore.GetPluginStoreURL()
|
||||||
_ = cfg // 保留以便未来扩展
|
|
||||||
|
|
||||||
// 从远程URL获取插件列表
|
// 从远程URL获取插件列表
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|||||||
@@ -2,217 +2,30 @@ package sign
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyEntry 密钥条目
|
// 官方固定公钥(客户端内置)
|
||||||
type KeyEntry struct {
|
const OfficialPublicKeyBase64 = "0A0xRthj0wgPg8X8GJZ6/EnNpAUw5v7O//XLty+P5Yw="
|
||||||
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),
|
|
||||||
},
|
|
||||||
// 添加新密钥时,在此处追加
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
keyCache map[string]ed25519.PublicKey
|
officialPubKey ed25519.PublicKey
|
||||||
keyCacheOnce sync.Once
|
officialPubKeyOnce sync.Once
|
||||||
keyMu sync.RWMutex
|
officialPubKeyErr error
|
||||||
remoteKeys []KeyEntry
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyListConfig 远程公钥列表配置
|
// initOfficialKey 初始化官方公钥
|
||||||
type KeyListConfig struct {
|
func initOfficialKey() {
|
||||||
RemoteURL string
|
officialPubKey, officialPubKeyErr = DecodePublicKey(OfficialPublicKeyBase64)
|
||||||
FetchInterval time.Duration
|
|
||||||
RequestTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyListConfig = KeyListConfig{
|
// GetOfficialPublicKey 获取官方公钥
|
||||||
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 获取默认官方公钥(兼容旧接口)
|
|
||||||
func GetOfficialPublicKey() (ed25519.PublicKey, error) {
|
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) {
|
func GetPublicKeyByID(keyID string) (ed25519.PublicKey, error) {
|
||||||
keyCacheOnce.Do(initKeyCache)
|
return GetOfficialPublicKey()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user