feat: add signtool to plugin repository
- Migrate signing tool from GoTunnel main project - Self-contained, no external dependencies - Updated CI workflow to build locally
This commit is contained in:
106
tools/signtool/main.go
Normal file
106
tools/signtool/main.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gotunnel/plugins/tools/signtool/sign"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "keygen":
|
||||
cmdKeygen()
|
||||
case "sign":
|
||||
cmdSign(os.Args[2:])
|
||||
default:
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("GoTunnel Plugin Sign Tool")
|
||||
fmt.Println()
|
||||
fmt.Println("Usage:")
|
||||
fmt.Println(" signtool keygen")
|
||||
fmt.Println(" signtool sign -key KEY -name NAME -version VER FILE")
|
||||
}
|
||||
|
||||
func cmdKeygen() {
|
||||
kp, err := sign.GenerateKeyPair()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "生成密钥失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("=== 密钥对已生成 ===")
|
||||
fmt.Println()
|
||||
fmt.Println("私钥 (请妥善保管):")
|
||||
fmt.Println(sign.EncodePrivateKey(kp.PrivateKey))
|
||||
fmt.Println()
|
||||
fmt.Println("公钥 (内置到客户端):")
|
||||
fmt.Println(sign.EncodePublicKey(kp.PublicKey))
|
||||
}
|
||||
|
||||
func cmdSign(args []string) {
|
||||
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||
keyFile := fs.String("key", "", "私钥文件路径")
|
||||
name := fs.String("name", "", "插件名称")
|
||||
version := fs.String("version", "", "插件版本")
|
||||
keyID := fs.String("keyid", "official-v1", "密钥 ID")
|
||||
fs.Parse(args)
|
||||
|
||||
if *keyFile == "" || *name == "" || *version == "" || fs.NArg() < 1 {
|
||||
fmt.Println("用法: signtool sign -key KEY -name NAME -version VER FILE")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 读取私钥
|
||||
keyData, err := os.ReadFile(*keyFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "读取私钥失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
privKey, err := sign.DecodePrivateKey(strings.TrimSpace(string(keyData)))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "解析私钥失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 读取插件文件
|
||||
pluginFile := fs.Arg(0)
|
||||
source, err := os.ReadFile(pluginFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "读取文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 创建载荷并签名
|
||||
payload := sign.CreatePayload(*name, *version, string(source), *keyID)
|
||||
signed, err := sign.SignPlugin(privKey, payload)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "签名失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 写入签名文件
|
||||
sigData, _ := sign.EncodeSignedPlugin(signed)
|
||||
sigFile := pluginFile + ".sig"
|
||||
if err := os.WriteFile(sigFile, []byte(sigData), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "写入签名失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("签名成功: %s\n", sigFile)
|
||||
fmt.Printf(" 插件: %s v%s\n", *name, *version)
|
||||
fmt.Printf(" 哈希: %s\n", payload.SourceHash)
|
||||
}
|
||||
70
tools/signtool/sign/payload.go
Normal file
70
tools/signtool/sign/payload.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PluginPayload 插件签名载荷
|
||||
type PluginPayload struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
SourceHash string `json:"source_hash"`
|
||||
KeyID string `json:"key_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// SignedPlugin 已签名的插件
|
||||
type SignedPlugin struct {
|
||||
Payload PluginPayload `json:"payload"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// NormalizeSource 规范化源码
|
||||
func NormalizeSource(source string) string {
|
||||
normalized := strings.ReplaceAll(source, "\r\n", "\n")
|
||||
normalized = strings.ReplaceAll(normalized, "\r", "\n")
|
||||
return strings.TrimRight(normalized, " \t\n")
|
||||
}
|
||||
|
||||
// HashSource 计算源码哈希
|
||||
func HashSource(source string) string {
|
||||
normalized := NormalizeSource(source)
|
||||
hash := sha256.Sum256([]byte(normalized))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// CreatePayload 创建签名载荷
|
||||
func CreatePayload(name, version, source, keyID string) *PluginPayload {
|
||||
return &PluginPayload{
|
||||
Name: name,
|
||||
Version: version,
|
||||
SourceHash: HashSource(source),
|
||||
KeyID: keyID,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// SignPlugin 签名插件
|
||||
func SignPlugin(priv ed25519.PrivateKey, payload *PluginPayload) (*SignedPlugin, error) {
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal payload: %w", err)
|
||||
}
|
||||
sig := SignBase64(priv, data)
|
||||
return &SignedPlugin{Payload: *payload, Signature: sig}, nil
|
||||
}
|
||||
|
||||
// EncodeSignedPlugin 编码为 JSON
|
||||
func EncodeSignedPlugin(sp *SignedPlugin) (string, error) {
|
||||
data, err := json.Marshal(sp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
68
tools/signtool/sign/sign.go
Normal file
68
tools/signtool/sign/sign.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrInvalidPublicKey = errors.New("invalid public key")
|
||||
ErrInvalidPrivateKey = errors.New("invalid private key")
|
||||
)
|
||||
|
||||
// KeyPair Ed25519 密钥对
|
||||
type KeyPair struct {
|
||||
PublicKey ed25519.PublicKey
|
||||
PrivateKey ed25519.PrivateKey
|
||||
}
|
||||
|
||||
// GenerateKeyPair 生成新的密钥对
|
||||
func GenerateKeyPair() (*KeyPair, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate key: %w", err)
|
||||
}
|
||||
return &KeyPair{PublicKey: pub, PrivateKey: priv}, nil
|
||||
}
|
||||
|
||||
// Sign 使用私钥签名数据
|
||||
func Sign(privateKey ed25519.PrivateKey, data []byte) []byte {
|
||||
return ed25519.Sign(privateKey, data)
|
||||
}
|
||||
|
||||
// Verify 使用公钥验证签名
|
||||
func Verify(publicKey ed25519.PublicKey, data, signature []byte) bool {
|
||||
return ed25519.Verify(publicKey, data, signature)
|
||||
}
|
||||
|
||||
// SignBase64 签名并返回 Base64 编码
|
||||
func SignBase64(privateKey ed25519.PrivateKey, data []byte) string {
|
||||
sig := Sign(privateKey, data)
|
||||
return base64.StdEncoding.EncodeToString(sig)
|
||||
}
|
||||
|
||||
// EncodePublicKey 编码公钥为 Base64
|
||||
func EncodePublicKey(pub ed25519.PublicKey) string {
|
||||
return base64.StdEncoding.EncodeToString(pub)
|
||||
}
|
||||
|
||||
// EncodePrivateKey 编码私钥为 Base64
|
||||
func EncodePrivateKey(priv ed25519.PrivateKey) string {
|
||||
return base64.StdEncoding.EncodeToString(priv)
|
||||
}
|
||||
|
||||
// DecodePrivateKey 从 Base64 解码私钥
|
||||
func DecodePrivateKey(s string) (ed25519.PrivateKey, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) != ed25519.PrivateKeySize {
|
||||
return nil, ErrInvalidPrivateKey
|
||||
}
|
||||
return ed25519.PrivateKey(data), nil
|
||||
}
|
||||
Reference in New Issue
Block a user