统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// Package secret encrypts provider credentials at rest using an environment-owned key.
|
||||
package secret
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
|
||||
)
|
||||
|
||||
type Vault struct{ aead cipher.AEAD }
|
||||
|
||||
func NewVault(encodedKey string) (*Vault, error) {
|
||||
if encodedKey == "" {
|
||||
return &Vault{}, nil
|
||||
}
|
||||
key, err := base64.StdEncoding.DecodeString(encodedKey)
|
||||
if err != nil || len(key) != 32 {
|
||||
return nil, fmt.Errorf("SECRET_ENCRYPTION_KEY must be base64 of 32 bytes")
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initialize secret cipher")
|
||||
}
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initialize secret encryption")
|
||||
}
|
||||
return &Vault{aead: aead}, nil
|
||||
}
|
||||
func (v *Vault) Encrypt(value, binding string) ([]byte, error) {
|
||||
if v == nil || v.aead == nil {
|
||||
return nil, apperror.ErrUnavailable
|
||||
}
|
||||
nonce := make([]byte, v.aead.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, apperror.ErrUnavailable
|
||||
}
|
||||
return v.aead.Seal(nonce, nonce, []byte(value), []byte(binding)), nil
|
||||
}
|
||||
func (v *Vault) Decrypt(encrypted []byte, binding string) (string, error) {
|
||||
if len(encrypted) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if v == nil || v.aead == nil || len(encrypted) < v.aead.NonceSize() {
|
||||
return "", apperror.ErrUnavailable
|
||||
}
|
||||
value, err := v.aead.Open(nil, encrypted[:v.aead.NonceSize()], encrypted[v.aead.NonceSize():], []byte(binding))
|
||||
if err != nil {
|
||||
return "", apperror.ErrUnavailable
|
||||
}
|
||||
return string(value), nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVaultAuthenticatedEncryption(t *testing.T) {
|
||||
v, err := NewVault(base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{7}, 32)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first, err := v.Encrypt("private-api-key", "model_primary")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := v.Encrypt("private-api-key", "model_primary")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Equal(first, second) || bytes.Contains(first, []byte("private-api-key")) {
|
||||
t.Fatal("encryption lacks nonce or exposes secret")
|
||||
}
|
||||
value, err := v.Decrypt(first, "model_primary")
|
||||
if err != nil || value != "private-api-key" {
|
||||
t.Fatal("decrypt failed")
|
||||
}
|
||||
if _, err := v.Decrypt(first, "model_backup"); err == nil {
|
||||
t.Fatal("cross-configuration ciphertext accepted")
|
||||
}
|
||||
first[len(first)-1] ^= 1
|
||||
if _, err := v.Decrypt(first, "model_primary"); err == nil {
|
||||
t.Fatal("tampered ciphertext accepted")
|
||||
}
|
||||
empty, _ := NewVault("")
|
||||
if _, err := empty.Encrypt("secret", "model_primary"); err == nil {
|
||||
t.Fatal("missing master key accepted")
|
||||
}
|
||||
if _, err := NewVault("invalid"); err == nil {
|
||||
t.Fatal("invalid master key accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user