Files
MyGoNavi/internal/secretstore/keyring_store.go
tianqijiuyun-latiao 82e06bd94d 🐛 fix(security): 完善密文升级导入覆盖与安全更新链路
- 完善连接恢复包与 legacy 导入覆盖语义及密文兼容处理

- 修复安全更新详情高亮反馈与相关前后端链路

- 补强 keyring 误判边界与安全更新回归测试
2026-04-11 16:53:03 +08:00

121 lines
2.7 KiB
Go

package secretstore
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"syscall"
"github.com/99designs/keyring"
)
type keyringClient interface {
Get(key string) (keyring.Item, error)
Set(item keyring.Item) error
Remove(key string) error
}
type keyringStore struct {
ring keyringClient
}
type keyringOpener func(cfg keyring.Config) (keyring.Keyring, error)
func NewKeyringStore() SecretStore {
return newKeyringStoreWithOpener(runtime.GOOS, keyring.Open)
}
func newKeyringStoreWithOpener(goos string, open keyringOpener) SecretStore {
cfg, err := keyringConfigFor(goos)
if err != nil {
return NewUnavailableStore(err.Error())
}
ring, err := open(cfg)
if err != nil {
return NewUnavailableStore(err.Error())
}
return &keyringStore{ring: ring}
}
func (s *keyringStore) Put(ref string, payload []byte) error {
return wrapKeyringError(s.ring.Set(keyring.Item{Key: ref, Data: payload}))
}
func (s *keyringStore) Get(ref string) ([]byte, error) {
item, err := s.ring.Get(ref)
if err != nil {
return nil, wrapKeyringError(err)
}
return item.Data, nil
}
func (s *keyringStore) Delete(ref string) error {
return wrapKeyringError(s.ring.Remove(ref))
}
func (s *keyringStore) HealthCheck() error {
_, err := s.ring.Get(healthCheckRef)
if err == nil || isKeyringSecretNotFound(err) {
return nil
}
return wrapKeyringError(err)
}
func wrapKeyringError(err error) error {
if err == nil || IsUnavailable(err) {
return err
}
if isKeyringSecretNotFound(err) {
return os.ErrNotExist
}
return &UnavailableError{Reason: err.Error()}
}
func isKeyringSecretNotFound(err error) bool {
if err == nil {
return false
}
if errors.Is(err, keyring.ErrKeyNotFound) || errors.Is(err, syscall.Errno(1168)) {
return true
}
return strings.EqualFold(strings.TrimSpace(err.Error()), keyring.ErrKeyNotFound.Error())
}
func keyringConfigFor(goos string) (keyring.Config, error) {
backends := allowedBackendsFor(goos)
if len(backends) == 0 {
return keyring.Config{}, fmt.Errorf("unsupported keyring platform: %s", goos)
}
return keyring.Config{
ServiceName: serviceName,
AllowedBackends: backends,
KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true,
LibSecretCollectionName: "default",
KeyCtlScope: "user",
WinCredPrefix: serviceName,
}, nil
}
func allowedBackendsFor(goos string) []keyring.BackendType {
switch goos {
case "windows":
return []keyring.BackendType{keyring.WinCredBackend}
case "darwin":
return []keyring.BackendType{keyring.KeychainBackend}
case "linux":
return []keyring.BackendType{
keyring.SecretServiceBackend,
keyring.KWalletBackend,
keyring.KeyCtlBackend,
}
default:
return nil
}
}