mirror of https://github.com/mitchell/selfpass.git
Major refactor of config encryption strategy
This commit is contained in:
parent
e404a7ab31
commit
cde1d118fc
|
@ -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))
|
||||||
|
|
|
@ -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.")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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)
|
||||||
|
mgr.cfgFile = &cfg
|
||||||
|
|
||||||
|
var contents []byte
|
||||||
|
var wasNotEncrypted bool
|
||||||
|
|
||||||
|
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:"}
|
prompt := &survey.Password{Message: "Master password:"}
|
||||||
if err = survey.AskOne(prompt, &mgr.masterpass, nil); err != nil {
|
if err = survey.AskOne(prompt, &mgr.masterpass, nil); err != nil {
|
||||||
return output, nil, err
|
return output, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr.decrypted, err = decryptConfig(mgr.masterpass, cfg)
|
contents, err = mgr.decryptConfig(mgr.masterpass, cfg)
|
||||||
if err != nil {
|
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
|
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
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return decrypted, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ioutil.WriteFile(cfgFile, contents, 0600); err != nil {
|
return plaintext, nil
|
||||||
return decrypted, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mgr *ConfigManager) DecryptConfig() {
|
func (mgr ConfigManager) DecryptConfig() error {
|
||||||
mgr.decrypt = true
|
if err := mgr.v.WriteConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mgr *ConfigManager) CloseConfig() {
|
func (mgr ConfigManager) WriteConfig() (err error) {
|
||||||
if !mgr.decrypt && mgr.decrypted {
|
if err := mgr.v.WriteConfigAs(*mgr.cfgFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(mgr.v.ConfigFileUsed())
|
contents, err := ioutil.ReadFile(mgr.v.ConfigFileUsed())
|
||||||
if os.IsNotExist(err) {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keypass, err := crypto.GenerateKeyFromPassword([]byte(mgr.masterpass))
|
keypass, err := crypto.GenerateKeyFromPassword([]byte(mgr.masterpass))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err = crypto.CBCEncrypt(keypass, contents)
|
contents, err = crypto.CBCEncrypt(keypass, contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(mgr.v.ConfigFileUsed(), contents, 0600)
|
err = ioutil.WriteFile(mgr.v.ConfigFileUsed(), contents, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
if mgr.decrypt {
|
|
||||||
fmt.Println("Decrypting config file. It will auto-encrypt when you next run spc.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -16,5 +16,3 @@ func check(err error) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyPrivateKey = "private_key"
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue