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
+139
View File
@@ -0,0 +1,139 @@
package vault
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"os"
"path/filepath"
)
// Vault 凭据保险库:AES-256-GCM 加密的 JSON 文件(主密钥单独 0600 文件)。
// 二期:Windows 上主密钥改用 DPAPI 保护,文件格式不变。
type Vault struct {
dir string
key []byte
}
type vaultFile struct {
Version int `json:"version"`
Entries map[string]string `json:"entries"`
}
// Open 打开/创建保险库(主密钥 data/master.key,32 字节)
func Open(dir string) (*Vault, error) {
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}
keyPath := filepath.Join(dir, "master.key")
var key []byte
if raw, err := os.ReadFile(keyPath); err == nil {
key = make([]byte, base64.RawStdEncoding.DecodedLen(len(raw)))
if _, err = base64.RawStdEncoding.Decode(key, raw); err != nil || len(key) != 32 {
return nil, errors.New("master key corrupted")
}
} else {
key = make([]byte, 32)
if _, err = rand.Read(key); err != nil {
return nil, err
}
if err = os.WriteFile(keyPath, []byte(base64.RawStdEncoding.EncodeToString(key)), 0o600); err != nil {
return nil, err
}
}
return &Vault{dir: dir, key: key}, nil
}
func (v *Vault) path() string { return filepath.Join(v.dir, "vault.enc") }
// load 读取并解密全部条目
func (v *Vault) load() (map[string]string, error) {
raw, err := os.ReadFile(v.path())
if err != nil {
if os.IsNotExist(err) {
return map[string]string{}, nil
}
return nil, err
}
var f vaultFile
if err = json.Unmarshal(raw, &f); err != nil {
return nil, err
}
if f.Entries == nil {
f.Entries = map[string]string{}
}
return f.Entries, nil
}
// Save 加密保存单条凭据(cookie/token 等,值为 JSON 字符串)
func (v *Vault) Save(accountID string, plaintext []byte) error {
entries, err := v.load()
if err != nil {
return err
}
block, err := aes.NewCipher(v.key)
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return err
}
sealed := gcm.Seal(nonce, nonce, plaintext, nil)
entries[accountID] = base64.StdEncoding.EncodeToString(sealed)
return v.persist(entries)
}
// Load 解密读取单条凭据
func (v *Vault) Load(accountID string) ([]byte, error) {
entries, err := v.load()
if err != nil {
return nil, err
}
sealedB64, ok := entries[accountID]
if !ok {
return nil, errors.New("entry not found")
}
sealed, err := base64.StdEncoding.DecodeString(sealedB64)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(v.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(sealed) < gcm.NonceSize() {
return nil, errors.New("entry corrupted")
}
nonce, ciphertext := sealed[:gcm.NonceSize()], sealed[gcm.NonceSize():]
return gcm.Open(nil, nonce, ciphertext, nil)
}
// Delete 删除单条凭据
func (v *Vault) Delete(accountID string) error {
entries, err := v.load()
if err != nil {
return err
}
delete(entries, accountID)
return v.persist(entries)
}
func (v *Vault) persist(entries map[string]string) error {
raw, err := json.MarshalIndent(vaultFile{Version: 1, Entries: entries}, "", " ")
if err != nil {
return err
}
return os.WriteFile(v.path(), raw, 0o600)
}