Major refactor of config encryption strategy

This commit is contained in:
Mitchell 2019-06-04 23:40:06 -07:00
parent e404a7ab31
commit cde1d118fc
10 changed files with 91 additions and 101 deletions

View File

@ -9,7 +9,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/mitchell/selfpass/cli/types" "github.com/mitchell/selfpass/cli/types"
"github.com/mitchell/selfpass/credentials/commands"
"github.com/mitchell/selfpass/crypto" "github.com/mitchell/selfpass/crypto"
) )
@ -35,7 +34,7 @@ the new file.`,
contents, err := ioutil.ReadFile(file) contents, err := ioutil.ReadFile(file)
check(err) check(err)
key, err := hex.DecodeString(cfg.GetString(commands.KeyPrivateKey)) key, err := hex.DecodeString(cfg.GetString(types.KeyPrivateKey))
check(err) check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key)) passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))

View File

@ -1,6 +1,8 @@
package commands package commands
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/mitchell/selfpass/cli/types" "github.com/mitchell/selfpass/cli/types"
@ -16,7 +18,9 @@ func makeDecryptCfg(repo types.ConfigRepo) *cobra.Command {
_, _, err := repo.OpenConfig() _, _, err := repo.OpenConfig()
check(err) check(err)
repo.DecryptConfig() check(repo.DecryptConfig())
fmt.Println("Config decrypted. It will automatically encrypt next run of spc.")
}, },
} }

View File

@ -9,7 +9,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/mitchell/selfpass/cli/types" "github.com/mitchell/selfpass/cli/types"
"github.com/mitchell/selfpass/credentials/commands"
"github.com/mitchell/selfpass/crypto" "github.com/mitchell/selfpass/crypto"
) )
@ -31,7 +30,7 @@ new file.`,
contents, err := ioutil.ReadFile(file) contents, err := ioutil.ReadFile(file)
check(err) check(err)
key, err := hex.DecodeString(cfg.GetString(commands.KeyPrivateKey)) key, err := hex.DecodeString(cfg.GetString(types.KeyPrivateKey))
check(err) check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key)) passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))

View File

@ -6,12 +6,10 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/AlecAivazis/survey.v1" "gopkg.in/AlecAivazis/survey.v1"
"github.com/mitchell/selfpass/cli/types" "github.com/mitchell/selfpass/cli/types"
"github.com/mitchell/selfpass/credentials/commands"
) )
func makeInit(repo types.ConfigRepo) *cobra.Command { func makeInit(repo types.ConfigRepo) *cobra.Command {
@ -72,23 +70,16 @@ the users private key, and server certificates. (All of which will be encrypted)
key, err := ioutil.ReadFile(keyFile) key, err := ioutil.ReadFile(keyFile)
check(err) check(err)
cfg.Set(keyConnConfig, map[string]string{ cfg.Set(types.KeyConnConfig, map[string]string{
"target": target, "target": target,
"ca": string(ca), "ca": string(ca),
"cert": string(cert), "cert": string(cert),
"key": string(key), "key": string(key),
}) })
cfg.Set(commands.KeyPrivateKey, privateKey) cfg.Set(types.KeyPrivateKey, privateKey)
if err := cfg.WriteConfig(); err != nil { check(repo.WriteConfig())
home, err := homedir.Dir()
check(err)
check(cfg.WriteConfigAs(home + "/.spc.toml"))
cfg.SetConfigFile(home + "/.spc.toml")
fmt.Println("Wrote new config to: " + home + "/.spc.toml")
}
}, },
} }

View File

@ -26,8 +26,6 @@ can interact with the entire Selfpass API.`,
cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)") cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)")
mgr := repositories.NewConfigManager(cfgFile) mgr := repositories.NewConfigManager(cfgFile)
defer mgr.CloseConfig()
clientInit := credrepos.NewCredentialServiceClient clientInit := credrepos.NewCredentialServiceClient
rootCmd.AddCommand(makeInit(mgr)) rootCmd.AddCommand(makeInit(mgr))
@ -47,7 +45,7 @@ func makeInitClient(repo types.ConfigRepo, initClient credtypes.CredentialClient
_, cfg, err := repo.OpenConfig() _, cfg, err := repo.OpenConfig()
check(err) check(err)
connConfig := cfg.GetStringMapString(keyConnConfig) connConfig := cfg.GetStringMapString(types.KeyConnConfig)
client, err := initClient( client, err := initClient(
ctx, ctx,
@ -68,5 +66,3 @@ func check(err error) {
os.Exit(1) os.Exit(1)
} }
} }
const keyConnConfig = "connection"

View File

@ -1,6 +1,7 @@
package repositories package repositories
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -20,8 +21,6 @@ func NewConfigManager(cfgFile *string) *ConfigManager {
type ConfigManager struct { type ConfigManager struct {
masterpass string masterpass string
decrypted bool
decrypt bool
cfgFile *string cfgFile *string
v *viper.Viper v *viper.Viper
} }
@ -36,110 +35,110 @@ func (mgr *ConfigManager) OpenConfig() (output string, v *viper.Viper, err error
} }
cfg := *mgr.cfgFile cfg := *mgr.cfgFile
v = viper.New() mgr.v = viper.New()
mgr.v = v mgr.v.SetConfigType("toml")
v.SetConfigType("toml") if cfg == "" {
if cfg != "" {
// Use config file from the flag.
v.SetConfigFile(cfg)
} else {
// Find home directory.
home, err := homedir.Dir() home, err := homedir.Dir()
if err != nil { if err != nil {
return output, nil, err return output, nil, err
} }
// Search config in home directory with name ".spc" (without extension).
v.AddConfigPath(home)
v.SetConfigName(".spc")
cfg = home + "/.spc.toml" cfg = home + "/.spc.toml"
} }
if _, err := os.Open(cfg); !os.IsNotExist(err) { mgr.v.SetConfigFile(cfg)
prompt := &survey.Password{Message: "Master password:"} mgr.cfgFile = &cfg
if err = survey.AskOne(prompt, &mgr.masterpass, nil); err != nil {
return output, nil, err
}
mgr.decrypted, err = decryptConfig(mgr.masterpass, cfg) var contents []byte
if err != nil { var wasNotEncrypted bool
return output, nil, err
} if _, err := os.Open(cfg); os.IsNotExist(err) {
return output, mgr.v, fmt.Errorf("no config found, run 'init' command")
}
prompt := &survey.Password{Message: "Master password:"}
if err = survey.AskOne(prompt, &mgr.masterpass, nil); err != nil {
return output, nil, err
}
contents, err = mgr.decryptConfig(mgr.masterpass, cfg)
if err != nil && err.Error() == "ciphertext is not a multiple of the block size" {
fmt.Println("Config wasn't encrypted.")
wasNotEncrypted = true
} else if err != nil {
return output, nil, err
} }
// v.AutomaticEnv() // read in environment variables that match // v.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in. // If a config file is found, read it in.
if err = v.ReadInConfig(); err != nil { if err = mgr.v.ReadConfig(bytes.NewBuffer(contents)); err != nil {
mgr.decrypted = true return output, mgr.v, err
return output, v, fmt.Errorf("no config found, run 'init' command") }
if wasNotEncrypted {
if err = mgr.WriteConfig(); err != nil {
return output, nil, err
}
} }
return mgr.masterpass, mgr.v, nil return mgr.masterpass, mgr.v, nil
} }
func decryptConfig(masterpass string, cfgFile string) (decrypted bool, err error) { func (mgr ConfigManager) decryptConfig(masterpass string, cfgFile string) (contents []byte, err error) {
contents, err := ioutil.ReadFile(cfgFile) contents, err = ioutil.ReadFile(cfgFile)
if err != nil { if err != nil {
return decrypted, err return contents, err
} }
passkey, err := crypto.GenerateKeyFromPassword([]byte(masterpass)) passkey, err := crypto.GenerateKeyFromPassword([]byte(masterpass))
if err != nil { if err != nil {
return decrypted, err return contents, err
} }
contents, err = crypto.CBCDecrypt(passkey, contents) plaintext, err := crypto.CBCDecrypt(passkey, contents)
if err != nil && err.Error() == "Padding incorrect" { if err != nil && err.Error() == "Padding incorrect" {
return decrypted, fmt.Errorf("incorrect master password") return contents, fmt.Errorf("incorrect master password")
} else if err != nil && err.Error() == "ciphertext is not a multiple of the block size" { } else if err != nil {
fmt.Println("Config wasn't encrypted.") return contents, err
return true, nil
} }
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 { if err != nil {
return decrypted, err return err
} }
if err = ioutil.WriteFile(cfgFile, contents, 0600); err != nil { keypass, err := crypto.GenerateKeyFromPassword([]byte(mgr.masterpass))
return decrypted, err if err != nil {
return err
} }
return true, nil contents, err = crypto.CBCEncrypt(keypass, contents)
} if err != nil {
return err
func (mgr *ConfigManager) DecryptConfig() { }
mgr.decrypt = true
} err = ioutil.WriteFile(mgr.v.ConfigFileUsed(), contents, 0600)
if err != nil {
func (mgr *ConfigManager) CloseConfig() { return err
if !mgr.decrypt && mgr.decrypted { }
contents, err := ioutil.ReadFile(mgr.v.ConfigFileUsed())
if os.IsNotExist(err) { return nil
return
}
keypass, err := crypto.GenerateKeyFromPassword([]byte(mgr.masterpass))
if err != nil {
panic(err)
}
contents, err = crypto.CBCEncrypt(keypass, contents)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(mgr.v.ConfigFileUsed(), contents, 0600)
if err != nil {
panic(err)
}
return
}
if mgr.decrypt {
fmt.Println("Decrypting config file. It will auto-encrypt when you next run spc.")
}
} }

View File

@ -4,6 +4,10 @@ import "github.com/spf13/viper"
type ConfigRepo interface { type ConfigRepo interface {
OpenConfig() (masterpass string, cfg *viper.Viper, err error) OpenConfig() (masterpass string, cfg *viper.Viper, err error)
DecryptConfig() DecryptConfig() (err error)
SetMasterpass(masterpass string) SetMasterpass(masterpass string)
WriteConfig() (err error)
} }
const KeyPrivateKey = "private_key"
const KeyConnConfig = "connection"

View File

@ -16,5 +16,3 @@ func check(err error) {
os.Exit(1) os.Exit(1)
} }
} }
const KeyPrivateKey = "private_key"

View File

@ -65,7 +65,7 @@ password.`,
check(survey.Ask(mdqs, &ci.MetadataInput)) check(survey.Ask(mdqs, &ci.MetadataInput))
check(survey.Ask(cqs, &ci)) check(survey.Ask(cqs, &ci))
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey)) key, err := hex.DecodeString(cfg.GetString(clitypes.KeyPrivateKey))
check(err) check(err)
keypass, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key)) keypass, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))

View File

@ -107,7 +107,7 @@ decrypting password.`,
fmt.Println("Wrote primary user key to clipboard.") fmt.Println("Wrote primary user key to clipboard.")
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey)) key, err := hex.DecodeString(cfg.GetString(clitypes.KeyPrivateKey))
check(err) check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), key) passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), key)