mirror of
https://github.com/mitchell/selfpass.git
synced 2025-12-15 21:47:23 +00:00
Move service related filed to services folder
This commit is contained in:
parent
347fbe7268
commit
7d770ef150
41 changed files with 50 additions and 50 deletions
50
services/cli/commands/decrypt.go
Normal file
50
services/cli/commands/decrypt.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
"github.com/mitchell/selfpass/services/crypto"
|
||||
)
|
||||
|
||||
func makeDecrypt(repo types.ConfigRepo) *cobra.Command {
|
||||
decryptCmd := &cobra.Command{
|
||||
Use: "decrypt [file]",
|
||||
Short: "Decrypt a file using your masterpass and secret key",
|
||||
Long: `Decrypt a file using your masterpass and secret key, and replace the old file with
|
||||
the new file.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
masterpass, cfg, err := repo.OpenConfig()
|
||||
check(err)
|
||||
|
||||
file := args[0]
|
||||
fileout := file
|
||||
|
||||
if file[len(file)-4:] == ".enc" {
|
||||
fileout = file[:len(file)-4]
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(file)
|
||||
check(err)
|
||||
|
||||
key := cfg.GetString(types.KeyPrivateKey)
|
||||
passkey := crypto.GeneratePBKDF2Key([]byte(masterpass), []byte(key))
|
||||
|
||||
contents, err = crypto.GCMDecrypt(passkey, contents)
|
||||
check(err)
|
||||
|
||||
check(ioutil.WriteFile(fileout, contents, 0600))
|
||||
check(os.Remove(file))
|
||||
|
||||
fmt.Println("Decrypted file: ", fileout)
|
||||
},
|
||||
}
|
||||
|
||||
return decryptCmd
|
||||
}
|
||||
28
services/cli/commands/decrypt_cfg.go
Normal file
28
services/cli/commands/decrypt_cfg.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
)
|
||||
|
||||
func makeDecryptCfg(repo types.ConfigRepo) *cobra.Command {
|
||||
decryptCfg := &cobra.Command{
|
||||
Use: "decrypt-cfg",
|
||||
Short: "Decrypt your config file",
|
||||
Long: "Decrypt your config file, so you can access your private key, host, and certs.",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_, _, err := repo.OpenConfig()
|
||||
check(err)
|
||||
|
||||
check(repo.DecryptConfig())
|
||||
|
||||
fmt.Println("Config decrypted. It will automatically encrypt next run of spc.")
|
||||
},
|
||||
}
|
||||
|
||||
return decryptCfg
|
||||
}
|
||||
46
services/cli/commands/encrypt.go
Normal file
46
services/cli/commands/encrypt.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
"github.com/mitchell/selfpass/services/crypto"
|
||||
)
|
||||
|
||||
func makeEncrypt(repo types.ConfigRepo) *cobra.Command {
|
||||
encryptCmd := &cobra.Command{
|
||||
Use: "encrypt [file]",
|
||||
Short: "Encrypt a file using your masterpass and secret key",
|
||||
Long: `Encrypt a file using your masterpass and secret key, and replace the old file with the
|
||||
new file.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
masterpass, cfg, err := repo.OpenConfig()
|
||||
check(err)
|
||||
|
||||
file := args[0]
|
||||
fileEnc := file + ".enc"
|
||||
|
||||
contents, err := ioutil.ReadFile(file)
|
||||
check(err)
|
||||
|
||||
key := cfg.GetString(types.KeyPrivateKey)
|
||||
passkey := crypto.GeneratePBKDF2Key([]byte(masterpass), []byte(key))
|
||||
|
||||
contents, err = crypto.GCMEncrypt(passkey, contents)
|
||||
check(err)
|
||||
|
||||
check(ioutil.WriteFile(fileEnc, contents, 0600))
|
||||
check(os.Remove(file))
|
||||
|
||||
fmt.Println("Encrypted file: ", fileEnc)
|
||||
},
|
||||
}
|
||||
|
||||
return encryptCmd
|
||||
}
|
||||
87
services/cli/commands/init.go
Normal file
87
services/cli/commands/init.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
)
|
||||
|
||||
func makeInit(repo types.ConfigRepo) *cobra.Command {
|
||||
initCmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "This command initializes SPC for the first time",
|
||||
Long: `This command initializes SPC for the first time. Writing to the user configuration
|
||||
the users private key, and server certificates. (All of which will be encrypted)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
hasPK bool
|
||||
masterpass string
|
||||
cmasterpass string
|
||||
target string
|
||||
caFile string
|
||||
certFile string
|
||||
keyFile string
|
||||
prompt survey.Prompt
|
||||
privateKey = strings.Replace(uuid.New().String(), "-", "", -1)
|
||||
)
|
||||
_, cfg, _ := repo.OpenConfig()
|
||||
|
||||
prompt = &survey.Password{Message: "New master password:"}
|
||||
check(survey.AskOne(prompt, &masterpass, nil))
|
||||
|
||||
prompt = &survey.Password{Message: "Confirm master password:"}
|
||||
check(survey.AskOne(prompt, &cmasterpass, nil))
|
||||
if masterpass != cmasterpass {
|
||||
check(fmt.Errorf("master passwords didn't match"))
|
||||
}
|
||||
|
||||
repo.SetMasterpass(masterpass)
|
||||
|
||||
prompt = &survey.Input{Message: "Selfpass server address:"}
|
||||
check(survey.AskOne(prompt, &target, nil))
|
||||
|
||||
prompt = &survey.Confirm{Message: "Do you have a private key?"}
|
||||
check(survey.AskOne(prompt, &hasPK, nil))
|
||||
|
||||
if hasPK {
|
||||
prompt = &survey.Password{Message: "Private key:"}
|
||||
check(survey.AskOne(prompt, &privateKey, nil))
|
||||
privateKey = strings.Replace(privateKey, "-", "", -1)
|
||||
}
|
||||
|
||||
prompt = &survey.Input{Message: "CA certificate file:"}
|
||||
check(survey.AskOne(prompt, &caFile, nil))
|
||||
ca, err := ioutil.ReadFile(caFile)
|
||||
check(err)
|
||||
|
||||
prompt = &survey.Input{Message: "Client certificate file:"}
|
||||
check(survey.AskOne(prompt, &certFile, nil))
|
||||
cert, err := ioutil.ReadFile(certFile)
|
||||
check(err)
|
||||
|
||||
prompt = &survey.Input{Message: "Client key file:"}
|
||||
check(survey.AskOne(prompt, &keyFile, nil))
|
||||
key, err := ioutil.ReadFile(keyFile)
|
||||
check(err)
|
||||
|
||||
cfg.Set(types.KeyConnConfig, map[string]string{
|
||||
"target": target,
|
||||
"ca": string(ca),
|
||||
"cert": string(cert),
|
||||
"key": string(key),
|
||||
})
|
||||
|
||||
cfg.Set(types.KeyPrivateKey, privateKey)
|
||||
|
||||
check(repo.WriteConfig())
|
||||
},
|
||||
}
|
||||
|
||||
return initCmd
|
||||
}
|
||||
72
services/cli/commands/root.go
Normal file
72
services/cli/commands/root.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/repositories"
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
"github.com/mitchell/selfpass/services/credentials/commands"
|
||||
credrepos "github.com/mitchell/selfpass/services/credentials/repositories"
|
||||
credtypes "github.com/mitchell/selfpass/services/credentials/types"
|
||||
)
|
||||
|
||||
func Execute() {
|
||||
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",
|
||||
}
|
||||
|
||||
cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)")
|
||||
|
||||
mgr := repositories.NewConfigManager(cfgFile)
|
||||
clientInit := credrepos.NewCredentialServiceClient
|
||||
|
||||
rootCmd.AddCommand(
|
||||
makeInit(mgr),
|
||||
makeEncrypt(mgr),
|
||||
makeDecrypt(mgr),
|
||||
makeDecryptCfg(mgr),
|
||||
commands.MakeList(makeInitClient(mgr, clientInit)),
|
||||
commands.MakeCreate(mgr, makeInitClient(mgr, clientInit)),
|
||||
commands.MakeUpdate(mgr, makeInitClient(mgr, clientInit)),
|
||||
commands.MakeGet(mgr, makeInitClient(mgr, clientInit)),
|
||||
commands.MakeDelete(makeInitClient(mgr, clientInit)),
|
||||
commands.MakeGCMToCBC(mgr, makeInitClient(mgr, clientInit)),
|
||||
)
|
||||
|
||||
check(rootCmd.Execute())
|
||||
}
|
||||
|
||||
func makeInitClient(repo types.ConfigRepo, initClient credtypes.CredentialClientInit) commands.CredentialClientInit {
|
||||
return func(ctx context.Context) credtypes.CredentialClient {
|
||||
_, cfg, err := repo.OpenConfig()
|
||||
check(err)
|
||||
|
||||
connConfig := cfg.GetStringMapString(types.KeyConnConfig)
|
||||
|
||||
client, err := initClient(
|
||||
ctx,
|
||||
connConfig["target"],
|
||||
connConfig["ca"],
|
||||
connConfig["cert"],
|
||||
connConfig["key"],
|
||||
)
|
||||
check(err)
|
||||
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
159
services/cli/repositories/config.go
Normal file
159
services/cli/repositories/config.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package repositories
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/mitchell/selfpass/services/cli/types"
|
||||
"github.com/mitchell/selfpass/services/crypto"
|
||||
)
|
||||
|
||||
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 + "/.spc.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, 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 = decryptConfig(mgr.masterpass, cfg)
|
||||
if err != nil && err == errConfigDecrypted {
|
||||
configDecrypted = true
|
||||
} else if err != nil {
|
||||
return output, nil, err
|
||||
}
|
||||
|
||||
if err = mgr.v.ReadConfig(bytes.NewBuffer(contents)); err != nil && strings.HasPrefix(err.Error(), "While parsing config") {
|
||||
return output, nil, fmt.Errorf("incorrect master password")
|
||||
} else if 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
|
||||
13
services/cli/types/types.go
Normal file
13
services/cli/types/types.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package types
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type ConfigRepo interface {
|
||||
OpenConfig() (masterpass string, cfg *viper.Viper, err error)
|
||||
DecryptConfig() (err error)
|
||||
SetMasterpass(masterpass string)
|
||||
WriteConfig() (err error)
|
||||
}
|
||||
|
||||
const KeyPrivateKey = "private_key"
|
||||
const KeyConnConfig = "connection"
|
||||
Loading…
Add table
Add a link
Reference in a new issue