package config import ( "encoding/json" "errors" "fmt" "os" "gopkg.in/yaml.v3" "git.wxccs.org/iceking2nd/winauth-go/internal/crypto" "git.wxccs.org/iceking2nd/winauth-go/internal/global" ) // Sentinel errors returned by LoadYAML for the encrypted-config password // path. Callers use errors.Is to distinguish them from generic I/O / parse // failures. var ( ErrPasswordRequired = errors.New("config: passphrase required") ErrPasswordWrong = errors.New("config: wrong passphrase") ) // SaveYAML writes the receiver as YAML to path. If passphrase is non-empty // and cfg.Encrypted is true, the entries slice is serialized to JSON, // encrypted, and stored as EncryptedBlob — entries are NOT written in // plaintext in that case. func SaveYAML(cfg *Config, path string, passphrase []byte) error { const fn = "internal.config.SaveYAML" logger := global.Log.WithField("func", fn).WithField("path", path) out := *cfg if cfg.Encrypted && len(passphrase) > 0 { raw, err := json.Marshal(cfg.Entries) if err != nil { return err } blob, err := crypto.EncryptModern(raw, passphrase) if err != nil { return err } out.EncryptedBlob = blob out.Entries = nil } data, err := yaml.Marshal(&out) if err != nil { return err } if err := EnsureDir(path); err != nil { return err } tmp := path + ".tmp" if err := os.WriteFile(tmp, data, 0o600); err != nil { return err } if err := os.Rename(tmp, path); err != nil { _ = os.Remove(tmp) return err } logger.Debug("config saved") return nil } // LoadYAML reads the YAML config at path. If the file is encrypted, // passphrase is required and the EncryptedBlob is decrypted into Entries. func LoadYAML(path string, passphrase []byte) (*Config, error) { const fn = "internal.config.LoadYAML" logger := global.Log.WithField("func", fn).WithField("path", path) data, err := os.ReadFile(path) if err != nil { return nil, err } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("config: parse YAML: %w", err) } if cfg.Encrypted && cfg.EncryptedBlob != "" { if len(passphrase) == 0 { return &cfg, ErrPasswordRequired } raw, err := crypto.DecryptModern(cfg.EncryptedBlob, passphrase) if err != nil { return &cfg, ErrPasswordWrong } if err := json.Unmarshal(raw, &cfg.Entries); err != nil { return nil, fmt.Errorf("config: decode entries: %w", err) } } logger.WithField("entries", len(cfg.Entries)).Debug("config loaded") return &cfg, nil }