selfpass/sp/repositories/config.go

161 lines
3.2 KiB
Go

package repositories
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/mitchell/selfpass/sp/crypto"
"github.com/mitchell/selfpass/sp/types"
)
var ErrNoConfigFound = errors.New("no config found, run 'init' command")
func NewConfigManager(cfgFile *string) *ConfigManager {
return &ConfigManager{
cfgFile: cfgFile,
}
}
type ConfigManager struct {
masterpass string
cfgFile *string
v *viper.Viper
}
func (mgr *ConfigManager) SetMasterpass(masterpass string) {
mgr.masterpass = masterpass
}
func (mgr *ConfigManager) OpenConfig() (output string, v *viper.Viper, err error) {
if mgr.masterpass != "" {
return mgr.masterpass, mgr.v, nil
}
cfg := *mgr.cfgFile
mgr.v = viper.New()
mgr.v.SetConfigType("toml")
if cfg == "" {
home, err := homedir.Dir()
if err != nil {
return output, nil, err
}
cfg = home + "/.sp.toml"
}
mgr.v.SetConfigFile(cfg)
mgr.cfgFile = &cfg
var contents []byte
var configDecrypted bool
if _, err := os.Open(cfg); os.IsNotExist(err) {
return output, mgr.v, ErrNoConfigFound
}
prompt := &survey.Password{Message: "Master password:"}
if err = survey.AskOne(prompt, &mgr.masterpass, nil); err != nil {
return output, nil, err
}
contents, err = decryptConfig(mgr.masterpass, cfg)
if err == errConfigDecrypted {
configDecrypted = true
} else if err != nil && err.Error() == crypto.ErrAuthenticationFailed.Error() {
return output, nil, errors.New("incorrect masterpass")
} else if err != nil {
return output, nil, err
}
if err = mgr.v.ReadConfig(bytes.NewBuffer(contents)); err != nil {
return output, nil, err
}
if configDecrypted {
fmt.Println("Config wasn't encrypted, or has been compromised.")
if err = mgr.WriteConfig(); err != nil {
return output, nil, err
}
}
return mgr.masterpass, mgr.v, nil
}
var errConfigDecrypted = errors.New("config is decrypted")
func decryptConfig(masterpass string, cfgFile string) (contents []byte, err error) {
contents, err = ioutil.ReadFile(cfgFile)
if err != nil {
return contents, err
}
if string(contents[:len(types.KeyPrivateKey)]) == types.KeyPrivateKey {
return contents, errConfigDecrypted
}
salt := contents[:saltSize]
contents = contents[saltSize:]
passkey := crypto.GeneratePBKDF2Key([]byte(masterpass), salt)
plaintext, err := crypto.GCMDecrypt(passkey, contents)
if err != nil {
return contents, err
}
return plaintext, nil
}
func (mgr ConfigManager) DecryptConfig() error {
if err := mgr.v.WriteConfig(); err != nil {
return err
}
return nil
}
func (mgr ConfigManager) WriteConfig() (err error) {
if err := mgr.v.WriteConfigAs(*mgr.cfgFile); err != nil {
return err
}
contents, err := ioutil.ReadFile(mgr.v.ConfigFileUsed())
if err != nil {
return err
}
salt := make([]byte, saltSize)
_, err = rand.Read(salt)
if err != nil {
return err
}
keypass := crypto.GeneratePBKDF2Key([]byte(mgr.masterpass), salt)
contents, err = crypto.GCMEncrypt(keypass, contents)
if err != nil {
return err
}
contents = append(salt, contents...)
err = ioutil.WriteFile(mgr.v.ConfigFileUsed(), contents, 0600)
if err != nil {
return err
}
return nil
}
const saltSize = 16