client: Windows 客户端(WinUI3 壳 + agent-core;调试用 Electron 壳存档);localserver token 改为常数时间比较

This commit is contained in:
Qiufeng
2026-08-20 20:38:21 +08:00
parent 33b7d57498
commit 9abf7f8213
60 changed files with 10025 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
package device
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"os"
"path/filepath"
)
// Identity 设备身份:Ed25519 密钥对 + 服务器分配的设备 ID
type Identity struct {
DeviceID uint64 `json:"deviceId"`
Name string `json:"name"`
PublicKey string `json:"publicKey"`
priv ed25519.PrivateKey `json:"-"`
}
// LoadOrCreate 加载或生成本机设备身份(私钥 hex 文件 0600)
func LoadOrCreate(dir string, name string) (*Identity, error) {
keyPath := filepath.Join(dir, "device.key")
idPath := filepath.Join(dir, "identity.json")
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}
var priv ed25519.PrivateKey
if raw, err := os.ReadFile(keyPath); err == nil {
seed, err := hex.DecodeString(string(raw))
if err != nil || len(seed) != ed25519.SeedSize {
return nil, errors.New("device key corrupted")
}
priv = ed25519.NewKeyFromSeed(seed)
} else {
_, priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
if err = os.WriteFile(keyPath, []byte(hex.EncodeToString(priv.Seed())), 0o600); err != nil {
return nil, err
}
}
id := &Identity{
Name: name,
PublicKey: base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey)),
priv: priv,
}
if raw, err := os.ReadFile(idPath); err == nil {
_ = json.Unmarshal(raw, id)
id.priv = priv
if id.Name == "" {
id.Name = name
}
}
return id, nil
}
// Save 持久化设备 ID(配对成功后调用)
func (i *Identity) Save(dir string) error {
raw, err := json.MarshalIndent(i, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, "identity.json"), raw, 0o600)
}
// Sign 对载荷签名(hello 握手用)
func (i *Identity) Sign(payload []byte) string {
return base64.StdEncoding.EncodeToString(ed25519.Sign(i.priv, payload))
}
@@ -0,0 +1,38 @@
package device
import (
"crypto/ed25519"
"encoding/base64"
"testing"
)
func TestLoadOrCreatePersist(t *testing.T) {
dir := t.TempDir()
id1, err := LoadOrCreate(dir, "test-device")
if err != nil {
t.Fatalf("create: %v", err)
}
// 再次加载应得到同一公钥
id2, err := LoadOrCreate(dir, "test-device")
if err != nil {
t.Fatalf("reload: %v", err)
}
if id1.PublicKey != id2.PublicKey {
t.Fatal("public key should persist across reloads")
}
}
func TestSignVerify(t *testing.T) {
dir := t.TempDir()
id, _ := LoadOrCreate(dir, "d")
payload := []byte("nonce-1|123456")
sigB64 := id.Sign(payload)
sig, _ := base64.StdEncoding.DecodeString(sigB64)
pub, _ := base64.StdEncoding.DecodeString(id.PublicKey)
if !ed25519.Verify(ed25519.PublicKey(pub), payload, sig) {
t.Fatal("signature should verify")
}
if ed25519.Verify(ed25519.PublicKey(pub), []byte("tampered"), sig) {
t.Fatal("tampered payload should fail verify")
}
}