mirror of https://github.com/mitchell/selfpass.git
161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package commands
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/mitchellh/go-homedir"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"gopkg.in/AlecAivazis/survey.v1"
|
|
|
|
"github.com/mitchell/selfpass/credentials/commands"
|
|
credtypes "github.com/mitchell/selfpass/credentials/types"
|
|
"github.com/mitchell/selfpass/crypto"
|
|
)
|
|
|
|
func Execute(initClient credtypes.CredentialClientInit) {
|
|
rootCmd := &cobra.Command{
|
|
Use: "spc",
|
|
Short: "This is the CLI client for Selfpass.",
|
|
Long: `This is the CLI client for Selfpass, the self-hosted password manager. With this tool you
|
|
can interact with the entire Selfpass API.`,
|
|
Version: "v0.1.0",
|
|
}
|
|
rootCmd.InitDefaultHelpFlag()
|
|
rootCmd.InitDefaultVersionFlag()
|
|
|
|
cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)")
|
|
decryptCfg := rootCmd.Flags().Bool("decrypt-cfg", false, "decrypt config file")
|
|
check(rootCmd.ParseFlags(os.Args))
|
|
encryptCfg := !*decryptCfg
|
|
|
|
var masterpass string
|
|
var cfg *viper.Viper
|
|
needsCfg := (len(os.Args) > 1 && !strings.Contains(strings.Join(os.Args, "--"), "--help")) || *decryptCfg
|
|
|
|
if needsCfg {
|
|
masterpass, cfg = openConfig(*cfgFile)
|
|
if encryptCfg && masterpass != "" {
|
|
defer encryptConfig(masterpass, cfg)
|
|
}
|
|
if *decryptCfg {
|
|
fmt.Println("Decrypting config file. It will auto-encrypt when you next run of spc.")
|
|
return
|
|
}
|
|
}
|
|
|
|
rootCmd.AddCommand(makeInit(cfg))
|
|
rootCmd.AddCommand(makeEncrypt(masterpass, cfg))
|
|
rootCmd.AddCommand(makeDecrypt(masterpass, cfg))
|
|
rootCmd.AddCommand(commands.MakeList(makeInitClient(cfg, initClient)))
|
|
rootCmd.AddCommand(commands.MakeCreate(masterpass, cfg, makeInitClient(cfg, initClient)))
|
|
rootCmd.AddCommand(commands.MakeGet(masterpass, cfg, makeInitClient(cfg, initClient)))
|
|
rootCmd.AddCommand(commands.MakeDelete(makeInitClient(cfg, initClient)))
|
|
|
|
check(rootCmd.Execute())
|
|
}
|
|
|
|
func makeInitClient(cfg *viper.Viper, initClient credtypes.CredentialClientInit) commands.CredentialClientInit {
|
|
return func(ctx context.Context) credtypes.CredentialClient {
|
|
connConfig := cfg.GetStringMapString(keyConnConfig)
|
|
|
|
client, err := initClient(
|
|
ctx,
|
|
connConfig["target"],
|
|
connConfig["ca"],
|
|
connConfig["cert"],
|
|
connConfig["key"],
|
|
)
|
|
if err != nil {
|
|
fmt.Printf("Please run 'init' command before running API commands.\nError Message: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return client
|
|
}
|
|
}
|
|
|
|
func openConfig(cfgFile string) (masterpass string, v *viper.Viper) {
|
|
v = viper.New()
|
|
v.SetConfigType("toml")
|
|
|
|
if cfgFile != "" {
|
|
// Use config file from the flag.
|
|
v.SetConfigFile(cfgFile)
|
|
} else {
|
|
// Find home directory.
|
|
home, err := homedir.Dir()
|
|
check(err)
|
|
|
|
// Search config in home directory with name ".spc" (without extension).
|
|
v.AddConfigPath(home)
|
|
v.SetConfigName(".spc")
|
|
|
|
cfgFile = home + "/.spc.toml"
|
|
}
|
|
|
|
if _, err := os.Open(cfgFile); !os.IsNotExist(err) {
|
|
prompt := &survey.Password{Message: "Master password:"}
|
|
check(survey.AskOne(prompt, &masterpass, nil))
|
|
|
|
decryptConfig(masterpass, cfgFile)
|
|
}
|
|
|
|
//v.AutomaticEnv() // read in environment variables that match
|
|
|
|
// If a config file is found, read it in.
|
|
if err := v.ReadInConfig(); err == nil {
|
|
fmt.Println("Using config file:", v.ConfigFileUsed())
|
|
}
|
|
|
|
return masterpass, v
|
|
}
|
|
|
|
func decryptConfig(masterpass string, cfgFile string) {
|
|
contents, err := ioutil.ReadFile(cfgFile)
|
|
check(err)
|
|
|
|
passkey, err := crypto.GenerateKeyFromPassword([]byte(masterpass))
|
|
check(err)
|
|
|
|
contents, err = crypto.CBCDecrypt(passkey, contents)
|
|
if err != nil && err.Error() == "Padding incorrect" {
|
|
fmt.Println("incorrect master password")
|
|
os.Exit(1)
|
|
} else if err != nil && err.Error() == "ciphertext is not a multiple of the block size" {
|
|
fmt.Println("Config wasn't encrypted.")
|
|
return
|
|
}
|
|
check(err)
|
|
|
|
check(ioutil.WriteFile(cfgFile, contents, 0600))
|
|
}
|
|
|
|
func encryptConfig(masterpass string, cfg *viper.Viper) {
|
|
contents, err := ioutil.ReadFile(cfg.ConfigFileUsed())
|
|
if os.IsNotExist(err) {
|
|
return
|
|
}
|
|
|
|
keypass, err := crypto.GenerateKeyFromPassword([]byte(masterpass))
|
|
check(err)
|
|
|
|
contents, err = crypto.CBCEncrypt(keypass, contents)
|
|
check(err)
|
|
|
|
check(ioutil.WriteFile(cfg.ConfigFileUsed(), contents, 0600))
|
|
}
|
|
|
|
func check(err error) {
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
const keyConnConfig = "connection"
|