Implemented all but update from cli client to server;

solidified encryption;
setup deployment mechanism for GCP
This commit is contained in:
mitchell 2019-05-27 18:16:50 -07:00
parent cd24f6e848
commit c5ae0b4ddc
28 changed files with 598 additions and 295 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
.idea .idea
bin bin
certs certs
db data
*.tar
*.enc

View File

@ -2,9 +2,10 @@ FROM golang:1.11.5 as build
WORKDIR /go/src/github.com/mitchell/selfpass WORKDIR /go/src/github.com/mitchell/selfpass
COPY . . COPY . .
ENV GO111MODULE=on ENV GO111MODULE=on
RUN make gen-certs-go
RUN make build RUN make build
FROM debian:stable-20190326-slim FROM debian:stable-20190506-slim
WORKDIR /usr/bin WORKDIR /usr/bin
COPY --from=build /go/src/github.com/mitchell/selfpass/bin/server . COPY --from=build /go/src/github.com/mitchell/selfpass/bin/server .
ENTRYPOINT ["server"] ENTRYPOINT ["server"]

View File

@ -1,7 +1,8 @@
.PHONY: all build clean format test docker-build .PHONY: all build clean format test docker-build
build: clean format test build: clean format
go build --o ./bin/server ./cmd/server/server.go env CGO_ENABLED=0 go build -o ./bin/server ./cmd/server
rm ./cmd/server/certs.go
clean: clean:
rm -rf ./bin rm -rf ./bin
@ -10,9 +11,32 @@ clean:
docker: docker:
docker-compose build docker-compose build
start: up:
docker-compose up docker-compose up
upd:
docker-compose up -d
down:
docker-compose down
machine-create-google:
docker-machine create --driver google \
--google-address m-selfpass \
--google-project selfpass-241808 \
--google-machine-type n1-standard-1 \
--google-machine-image https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-9-stretch-v20190514 \
selfpass01
machine-rm:
docker-machine rm selfpass01
machine-ssh:
docker-machine ssh selfpass01
machine-env:
docker-machine env selfpass01
format: format:
gofmt -w -s -l . gofmt -w -s -l .
@ -36,5 +60,8 @@ gen-server-cert:
gen-client-cert: gen-client-cert:
cd certs && cfssl gencert -ca ca.pem -ca-key ca-key.pem csr.json | cfssljson -bare client cd certs && cfssl gencert -ca ca.pem -ca-key ca-key.pem csr.json | cfssljson -bare client
gen-certs-go:
./gen_certs_go.sh > ./cmd/server/certs.go
test: test:
go test -cover ./... go test -cover ./...

52
cli/commands/decrypt.go Normal file
View File

@ -0,0 +1,52 @@
package commands
import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/mitchell/selfpass/credentials/commands"
"github.com/mitchell/selfpass/crypto"
)
func makeDecrypt(masterpass string, cfg *viper.Viper) *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) {
file := args[0]
fileout := file
if file[len(file)-4:] == ".enc" {
fileout = file[:len(file)-4]
}
contents, err := ioutil.ReadFile(file)
check(err)
key, err := hex.DecodeString(cfg.GetString(commands.KeyPrivateKey))
check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))
check(err)
contents, err = crypto.CBCDecrypt(passkey, contents)
check(err)
check(ioutil.WriteFile(fileout, contents, 0600))
check(os.Remove(file))
fmt.Println("Decrypted file: ", fileout)
},
}
return decryptCmd
}

48
cli/commands/encrypt.go Normal file
View File

@ -0,0 +1,48 @@
package commands
import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/mitchell/selfpass/credentials/commands"
"github.com/mitchell/selfpass/crypto"
)
func makeEncrypt(masterpass string, cfg *viper.Viper) *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) {
file := args[0]
fileEnc := file + ".enc"
contents, err := ioutil.ReadFile(file)
check(err)
key, err := hex.DecodeString(cfg.GetString(commands.KeyPrivateKey))
check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))
check(err)
contents, err = crypto.CBCEncrypt(passkey, contents)
check(err)
check(ioutil.WriteFile(fileEnc, contents, 0600))
check(os.Remove(file))
fmt.Println("Encrypted file: ", fileEnc)
},
}
return encryptCmd
}

View File

@ -1,4 +1,4 @@
package cmd package commands
import ( import (
"fmt" "fmt"
@ -11,10 +11,10 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/AlecAivazis/survey.v1" "gopkg.in/AlecAivazis/survey.v1"
"github.com/mitchell/selfpass/credentials/cmds" "github.com/mitchell/selfpass/credentials/commands"
) )
func makeInitCmd(cfg *viper.Viper) *cobra.Command { func makeInit(cfg *viper.Viper) *cobra.Command {
initCmd := &cobra.Command{ initCmd := &cobra.Command{
Use: "init", Use: "init",
Short: "This command initializes SPC for the first time", Short: "This command initializes SPC for the first time",
@ -33,7 +33,7 @@ the users private key, and server certificates. (All of which will be encrypted)
privateKey = strings.Replace(uuid.New().String(), "-", "", -1) privateKey = strings.Replace(uuid.New().String(), "-", "", -1)
) )
prompt = &survey.Password{Message: "Master password:"} prompt = &survey.Password{Message: "New master password:"}
check(survey.AskOne(prompt, &masterpass, nil)) check(survey.AskOne(prompt, &masterpass, nil))
prompt = &survey.Password{Message: "Confirm master password:"} prompt = &survey.Password{Message: "Confirm master password:"}
@ -69,14 +69,14 @@ 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(cmds.KeyConnConfig, map[string]string{ cfg.Set(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(cmds.KeyPrivateKey, privateKey) cfg.Set(commands.KeyPrivateKey, privateKey)
if err := cfg.WriteConfig(); err != nil { if err := cfg.WriteConfig(); err != nil {
home, err := homedir.Dir() home, err := homedir.Dir()

View File

@ -1,37 +1,44 @@
package cmd package commands
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/AlecAivazis/survey.v1" "gopkg.in/AlecAivazis/survey.v1"
"github.com/mitchell/selfpass/credentials/cmds" "github.com/mitchell/selfpass/credentials/commands"
"github.com/mitchell/selfpass/credentials/types" credtypes "github.com/mitchell/selfpass/credentials/types"
"github.com/mitchell/selfpass/crypto" "github.com/mitchell/selfpass/crypto"
) )
func Execute(ctx context.Context, initClient types.CredentialClientInit) { func Execute(initClient credtypes.CredentialClientInit) {
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "spc", Use: "spc",
Short: "This is the CLI client for Selfpass.", 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 Long: `This is the CLI client for Selfpass, the self-hosted password manager. With this tool you
can interact with the entire Selfpass API.`, 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)") cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)")
rootCmd.PersistentFlags().Parse(os.Args) decryptCfg := rootCmd.Flags().Bool("decrypt-cfg", false, "decrypt config file")
check(rootCmd.ParseFlags(os.Args))
decryptCfg := rootCmd.Flags().Bool("decrypt-cfg", false, "unencrypt config file")
rootCmd.Flags().Parse(os.Args)
encryptCfg := !*decryptCfg encryptCfg := !*decryptCfg
masterpass, cfg := openConfig(*cfgFile)
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 != "" { if encryptCfg && masterpass != "" {
defer encryptConfig(masterpass, cfg) defer encryptConfig(masterpass, cfg)
} }
@ -39,18 +46,22 @@ can interact with the entire Selfpass API.`,
fmt.Println("Decrypting config file. It will auto-encrypt when you next run of spc.") fmt.Println("Decrypting config file. It will auto-encrypt when you next run of spc.")
return return
} }
}
rootCmd.AddCommand(makeInitCmd(cfg)) rootCmd.AddCommand(makeInit(cfg))
rootCmd.AddCommand(cmds.MakeListCmd(makeInitClient(cfg, initClient))) rootCmd.AddCommand(makeEncrypt(masterpass, cfg))
rootCmd.AddCommand(cmds.MakeCreateCmd(masterpass, cfg, makeInitClient(cfg, initClient))) rootCmd.AddCommand(makeDecrypt(masterpass, cfg))
rootCmd.AddCommand(cmds.MakeGetCmd(masterpass, cfg, makeInitClient(cfg, initClient))) 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()) check(rootCmd.Execute())
} }
func makeInitClient(cfg *viper.Viper, initClient types.CredentialClientInit) cmds.CredentialClientInit { func makeInitClient(cfg *viper.Viper, initClient credtypes.CredentialClientInit) commands.CredentialClientInit {
return func(ctx context.Context) types.CredentialClient { return func(ctx context.Context) credtypes.CredentialClient {
connConfig := cfg.GetStringMapString(cmds.KeyConnConfig) connConfig := cfg.GetStringMapString(keyConnConfig)
client, err := initClient( client, err := initClient(
ctx, ctx,
@ -145,3 +156,5 @@ func check(err error) {
os.Exit(1) os.Exit(1)
} }
} }
const keyConnConfig = "connection"

View File

@ -5,7 +5,6 @@ import (
"crypto/x509" "crypto/x509"
"flag" "flag"
"io" "io"
"io/ioutil"
stdlog "log" stdlog "log"
"net" "net"
"os" "os"
@ -29,11 +28,8 @@ var logger log.Logger
func main() { func main() {
var ( var (
stop = make(chan os.Signal, 1) stop = make(chan os.Signal, 1)
dev = flag.Bool("dev", false, "enables dev mode logging") jsonLogs = flag.Bool("json-logs", false, "enables json logging")
port = flag.String("port", "8080", "specify the port to listen on") port = flag.String("port", "8080", "specify the port to listen on")
crtFile = flag.String("cert", "/run/secrets/server_cert", "specify the cert file")
keyFile = flag.String("key", "/run/secrets/server_key", "specify the private key file")
caFile = flag.String("ca", "/run/secrets/ca_cert", "specify the ca cert file")
verbose = flag.Bool("v", false, "be more verbose") verbose = flag.Bool("v", false, "be more verbose")
// tableName = flag.String( // tableName = flag.String(
// "credential-table-name", // "credential-table-name",
@ -47,16 +43,13 @@ func main() {
signal.Notify(stop, syscall.SIGKILL) signal.Notify(stop, syscall.SIGKILL)
signal.Notify(stop, syscall.SIGTERM) signal.Notify(stop, syscall.SIGTERM)
logger = newLogger(os.Stdout, *dev) logger = newLogger(os.Stdout, *jsonLogs)
keypair, err := tls.LoadX509KeyPair(*crtFile, *keyFile) keypair, err := tls.X509KeyPair([]byte(cert), []byte(key))
check(err)
ca, err := ioutil.ReadFile(*caFile)
check(err) check(err)
caPool := x509.NewCertPool() caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(ca) caPool.AppendCertsFromPEM([]byte(ca))
creds := credentials.NewTLS(&tls.Config{ creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{keypair}, Certificates: []tls.Certificate{keypair},
@ -85,7 +78,6 @@ func main() {
_ = logger.Log( _ = logger.Log(
"message", "serving", "message", "serving",
"address", addr, "address", addr,
"dev", dev,
) )
go func() { check(srv.Serve(lis)) }() go func() { check(srv.Serve(lis)) }()
@ -95,13 +87,11 @@ func main() {
srv.GracefulStop() srv.GracefulStop()
} }
func newLogger(writer io.Writer, dev bool) log.Logger { func newLogger(writer io.Writer, jsonLogs bool) log.Logger {
var l log.Logger
writer = log.NewSyncWriter(writer) writer = log.NewSyncWriter(writer)
l := log.NewLogfmtLogger(writer)
if dev { if jsonLogs {
l = log.NewLogfmtLogger(writer)
} else {
l = log.NewJSONLogger(writer) l = log.NewJSONLogger(writer)
} }
l = log.WithPrefix(l, "caller", log.Caller(5), "timestamp", log.DefaultTimestamp) l = log.WithPrefix(l, "caller", log.Caller(5), "timestamp", log.DefaultTimestamp)

View File

@ -1,13 +1,10 @@
package main package main
import ( import (
"context" "github.com/mitchell/selfpass/cli/commands"
"github.com/mitchell/selfpass/cmd/spc/cmd"
"github.com/mitchell/selfpass/credentials/repositories" "github.com/mitchell/selfpass/credentials/repositories"
) )
func main() { func main() {
ctx := context.Background() commands.Execute(repositories.NewCredentialServiceClient)
cmd.Execute(ctx, repositories.NewCredentialServiceClient)
} }

View File

@ -1,63 +0,0 @@
package cmds
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"github.com/atotto/clipboard"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/mitchell/selfpass/crypto"
)
func MakeGetCmd(masterpass string, cfg *viper.Viper, initClient CredentialClientInit) *cobra.Command {
getCmd := &cobra.Command{
Use: "get [id]",
Short: "Get a credential info and copy password to clipboard",
Long: `Get a credential's info and copy password to clipboard, from Selfpass server, after
decrypting password.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
cbcontents, err := clipboard.ReadAll()
check(err)
restore := func(cbcontents string) {
time.Sleep(time.Second * 5)
clipboard.WriteAll(cbcontents)
}
cred, err := initClient(ctx).Get(ctx, args[0])
check(err)
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey))
check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), key)
check(err)
passbytes, err := base64.StdEncoding.DecodeString(cred.Password)
check(err)
plainpass, err := crypto.CBCDecrypt(passkey, passbytes)
check(clipboard.WriteAll(string(plainpass)))
go restore(cbcontents)
cjson, err := json.MarshalIndent(cred, "", " ")
check(err)
fmt.Println(string(cjson))
fmt.Println("Wrote password to clipboard.")
},
}
return getCmd
}

View File

@ -1,4 +1,4 @@
package cmds package commands
import ( import (
"context" "context"
@ -17,5 +17,4 @@ func check(err error) {
} }
} }
const KeyConnConfig = "connection"
const KeyPrivateKey = "private_key" const KeyPrivateKey = "private_key"

View File

@ -1,16 +1,15 @@
package cmds package commands
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"math/rand"
"os" "os"
"time" "time"
"github.com/atotto/clipboard" "github.com/atotto/clipboard"
"github.com/pquerna/otp/totp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/AlecAivazis/survey.v1" "gopkg.in/AlecAivazis/survey.v1"
@ -19,7 +18,11 @@ import (
"github.com/mitchell/selfpass/crypto" "github.com/mitchell/selfpass/crypto"
) )
func MakeCreateCmd(masterpass string, cfg *viper.Viper, initClient CredentialClientInit) *cobra.Command { func MakeCreate(masterpass string, cfg *viper.Viper, initClient CredentialClientInit) *cobra.Command {
var length uint
var numbers bool
var specials bool
createCmd := &cobra.Command{ createCmd := &cobra.Command{
Use: "create", Use: "create",
Short: "Create a credential in Selfpass", Short: "Create a credential in Selfpass",
@ -56,16 +59,21 @@ password.`,
}, },
} }
var ci types.CredentialInput var ci types.CredentialInput
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))
check(err)
keypass, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))
check(err)
var newpass bool var newpass bool
prompt := &survey.Confirm{Message: "Do you want a random password?", Default: true} prompt := &survey.Confirm{Message: "Do you want a random password?", Default: true}
check(survey.AskOne(prompt, &newpass, nil)) check(survey.AskOne(prompt, &newpass, nil))
if newpass { if newpass {
ci.Password = generatePassword(16, true, true) ci.Password = crypto.GeneratePassword(int(length), numbers, specials)
var copypass bool var copypass bool
prompt = &survey.Confirm{Message: "Copy new pass to clipboard?", Default: true} prompt = &survey.Confirm{Message: "Copy new pass to clipboard?", Default: true}
@ -79,7 +87,7 @@ password.`,
check(survey.AskOne(prompt, &ci.Password, nil)) check(survey.AskOne(prompt, &ci.Password, nil))
var cpass string var cpass string
prompt = &survey.Password{Message: "Confirm assword: "} prompt = &survey.Password{Message: "Confirm password: "}
check(survey.AskOne(prompt, &cpass, nil)) check(survey.AskOne(prompt, &cpass, nil))
if ci.Password != cpass { if ci.Password != cpass {
@ -88,59 +96,58 @@ password.`,
} }
} }
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey))
check(err)
keypass, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))
check(err)
cipherpass, err := crypto.CBCEncrypt(keypass, []byte(ci.Password)) cipherpass, err := crypto.CBCEncrypt(keypass, []byte(ci.Password))
check(err) check(err)
ci.Password = base64.StdEncoding.EncodeToString(cipherpass) ci.Password = base64.StdEncoding.EncodeToString(cipherpass)
var otp bool
prompt = &survey.Confirm{Message: "Do you have an OTP/MFA secret?", Default: true}
check(survey.AskOne(prompt, &otp, nil))
if otp {
var secret string
prompt := &survey.Password{Message: "OTP secret:"}
check(survey.AskOne(prompt, &secret, nil))
ciphersecret, err := crypto.CBCEncrypt(keypass, []byte(secret))
check(err)
ci.OTPSecret = base64.StdEncoding.EncodeToString(ciphersecret)
var copyotp bool
prompt2 := &survey.Confirm{Message: "Copy new OTP to clipboard?", Default: true}
check(survey.AskOne(prompt2, &copyotp, nil))
if copyotp {
otp, err := totp.GenerateCode(secret, time.Now())
check(err)
check(clipboard.WriteAll(otp))
}
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
c, err := initClient(ctx).Create(ctx, ci) c, err := initClient(ctx).Create(ctx, ci)
check(err) check(err)
mdjson, err := json.MarshalIndent(c.Metadata, "", " ") fmt.Println(c)
check(err)
fmt.Println(string(mdjson)) var cleancb bool
prompt = &survey.Confirm{Message: "Do you want to clear the clipboard?", Default: true}
check(survey.AskOne(prompt, &cleancb, nil))
if cleancb {
check(clipboard.WriteAll(" "))
}
}, },
} }
createCmd.Flags().BoolVarP(&numbers, "numbers", "n", true, "use numbers in the generated password")
createCmd.Flags().BoolVarP(&specials, "specials", "s", false, "use special characters in the generated password")
createCmd.Flags().UintVarP(&length, "length", "l", 32, "length of the generated password")
return createCmd return createCmd
} }
const alphas = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV"
const alphanumerics = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890"
const alphasAndSpecials = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890!@#$%^&*()"
const alphanumericsAndSpecials = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890!@#$%^&*()"
func generatePassword(length int, numbers, specials bool) string {
rand.Seed(time.Now().UnixNano())
pass := make([]byte, length)
switch {
case numbers && specials:
for idx := 0; idx < length; idx++ {
pass[idx] = alphanumericsAndSpecials[rand.Int63()%int64(len(alphanumericsAndSpecials))]
}
case numbers:
for idx := 0; idx < length; idx++ {
pass[idx] = alphanumerics[rand.Int63()%int64(len(alphanumerics))]
}
case specials:
for idx := 0; idx < length; idx++ {
pass[idx] = alphasAndSpecials[rand.Int63()%int64(len(alphasAndSpecials))]
}
default:
for idx := 0; idx < length; idx++ {
pass[idx] = alphas[rand.Int63()%int64(len(alphas))]
}
}
return string(pass)
}

View File

@ -0,0 +1,33 @@
package commands
import (
"context"
"time"
"github.com/spf13/cobra"
"gopkg.in/AlecAivazis/survey.v1"
)
func MakeDelete(initConfig CredentialClientInit) *cobra.Command {
deleteCmd := &cobra.Command{
Use: "delete [id]",
Short: "Delete a credential using the given ID",
Long: `Delete a credential using the given ID, permanently. THERE IS NO UNDOING THIS ACTION.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var confirmed bool
prompt := &survey.Confirm{Message: "Are you sure you want to permanently delete this credential?"}
check(survey.AskOne(prompt, &confirmed, nil))
if confirmed {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
check(initConfig(ctx).Delete(ctx, args[0]))
}
},
}
return deleteCmd
}

View File

@ -0,0 +1,92 @@
package commands
import (
"context"
"encoding/base64"
"encoding/hex"
"fmt"
"time"
"github.com/atotto/clipboard"
"github.com/pquerna/otp/totp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/mitchell/selfpass/crypto"
)
func MakeGet(masterpass string, cfg *viper.Viper, initClient CredentialClientInit) *cobra.Command {
getCmd := &cobra.Command{
Use: "get [id]",
Short: "Get a credential info and copy password to clipboard",
Long: `Get a credential's info and copy password to clipboard, from Selfpass server, after
decrypting password.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
cred, err := initClient(ctx).Get(ctx, args[0])
check(err)
fmt.Println(cred)
check(clipboard.WriteAll(string(cred.Primary)))
fmt.Println("Wrote primary user key to clipboard.")
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey))
check(err)
passkey, err := crypto.CombinePasswordAndKey([]byte(masterpass), key)
check(err)
var copyPass bool
prompt := &survey.Confirm{Message: "Copy password to clipboard?", Default: true}
check(survey.AskOne(prompt, &copyPass, nil))
if copyPass {
passbytes, err := base64.StdEncoding.DecodeString(cred.Password)
check(err)
plainpass, err := crypto.CBCDecrypt(passkey, passbytes)
check(clipboard.WriteAll(string(plainpass)))
fmt.Println("Wrote password to clipboard.")
}
if cred.OTPSecret != "" {
var newOTP bool
prompt = &survey.Confirm{Message: "Generate one time password and copy to clipboard?", Default: true}
check(survey.AskOne(prompt, &newOTP, nil))
if newOTP {
secretbytes, err := base64.StdEncoding.DecodeString(cred.OTPSecret)
check(err)
plainsecret, err := crypto.CBCDecrypt(passkey, secretbytes)
otp, err := totp.GenerateCode(string(plainsecret), time.Now())
check(err)
check(clipboard.WriteAll(otp))
fmt.Println("Wrote one time password to clipboard.")
}
}
var cleancb bool
prompt = &survey.Confirm{Message: "Do you want to clear the clipboard?", Default: true}
check(survey.AskOne(prompt, &cleancb, nil))
if cleancb {
check(clipboard.WriteAll(" "))
}
},
}
return getCmd
}

View File

@ -1,15 +1,14 @@
package cmds package commands
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func MakeListCmd(initClient CredentialClientInit) *cobra.Command { func MakeList(initClient CredentialClientInit) *cobra.Command {
var sourceHost string var sourceHost string
listCmd := &cobra.Command{ listCmd := &cobra.Command{
@ -38,11 +37,11 @@ includes almost all the information but the most sensitive.`,
break receive break receive
} }
mdjson, err := json.MarshalIndent(md, "", " ") fmt.Println(md)
check(err)
fmt.Println(string(mdjson))
} }
} }
fmt.Println("Done listing.")
}, },
} }

View File

@ -357,6 +357,7 @@ type Credential struct {
SourceHost string `protobuf:"bytes,8,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` SourceHost string `protobuf:"bytes,8,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"`
LoginUrl string `protobuf:"bytes,9,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"` LoginUrl string `protobuf:"bytes,9,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"`
Tag string `protobuf:"bytes,10,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,10,opt,name=tag,proto3" json:"tag,omitempty"`
OtpSecret string `protobuf:"bytes,11,opt,name=otp_secret,json=otpSecret,proto3" json:"otp_secret,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -457,6 +458,13 @@ func (m *Credential) GetTag() string {
return "" return ""
} }
func (m *Credential) GetOtpSecret() string {
if m != nil {
return m.OtpSecret
}
return ""
}
type CredentialRequest struct { type CredentialRequest struct {
Primary string `protobuf:"bytes,1,opt,name=primary,proto3" json:"primary,omitempty"` Primary string `protobuf:"bytes,1,opt,name=primary,proto3" json:"primary,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
@ -465,6 +473,7 @@ type CredentialRequest struct {
SourceHost string `protobuf:"bytes,5,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` SourceHost string `protobuf:"bytes,5,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"`
LoginUrl string `protobuf:"bytes,6,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"` LoginUrl string `protobuf:"bytes,6,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"`
Tag string `protobuf:"bytes,7,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,7,opt,name=tag,proto3" json:"tag,omitempty"`
OtpSecret string `protobuf:"bytes,8,opt,name=otp_secret,json=otpSecret,proto3" json:"otp_secret,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -544,6 +553,13 @@ func (m *CredentialRequest) GetTag() string {
return "" return ""
} }
func (m *CredentialRequest) GetOtpSecret() string {
if m != nil {
return m.OtpSecret
}
return ""
}
func init() { func init() {
proto.RegisterType((*DeleteResponse)(nil), "selfpass.credentials.DeleteResponse") proto.RegisterType((*DeleteResponse)(nil), "selfpass.credentials.DeleteResponse")
proto.RegisterType((*GetAllMetadataRequest)(nil), "selfpass.credentials.GetAllMetadataRequest") proto.RegisterType((*GetAllMetadataRequest)(nil), "selfpass.credentials.GetAllMetadataRequest")
@ -559,41 +575,43 @@ func init() {
func init() { proto.RegisterFile("credentials/protobuf/service.proto", fileDescriptor_ad34efc7bbd96e69) } func init() { proto.RegisterFile("credentials/protobuf/service.proto", fileDescriptor_ad34efc7bbd96e69) }
var fileDescriptor_ad34efc7bbd96e69 = []byte{ var fileDescriptor_ad34efc7bbd96e69 = []byte{
// 535 bytes of a gzipped FileDescriptorProto // 561 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x94, 0xcf, 0x6e, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x95, 0x4d, 0x6f, 0xd3, 0x4c,
0x10, 0xc6, 0xe5, 0xa4, 0x4d, 0xed, 0x69, 0x88, 0x60, 0x55, 0x24, 0xcb, 0x95, 0x48, 0x64, 0x90, 0x10, 0xc7, 0xe5, 0xa4, 0x4d, 0xed, 0x69, 0x9f, 0xe8, 0x61, 0x55, 0x24, 0xcb, 0x15, 0x24, 0x32,
0xa8, 0x8a, 0xe4, 0xa2, 0x72, 0x81, 0x63, 0x68, 0x51, 0xca, 0x01, 0x21, 0xb9, 0xf4, 0xc2, 0x25, 0x48, 0x54, 0x45, 0x72, 0x51, 0xb9, 0xc0, 0x31, 0xb4, 0x28, 0xe5, 0x80, 0x90, 0x1c, 0x7a, 0xe1,
0xda, 0xda, 0xd3, 0xd4, 0x92, 0xed, 0x35, 0xbb, 0x63, 0x50, 0x1f, 0x8c, 0x97, 0x80, 0x37, 0xe2, 0x12, 0x6d, 0xed, 0x69, 0x6a, 0xc9, 0xf6, 0x9a, 0xdd, 0x31, 0xa8, 0x1f, 0x8c, 0x0f, 0x05, 0x1f,
0x84, 0xbc, 0xfe, 0x93, 0x3f, 0x75, 0x9a, 0x48, 0x5c, 0xb8, 0xed, 0xac, 0xbf, 0x59, 0xcd, 0xef, 0x81, 0x13, 0xf2, 0xfa, 0x25, 0x2f, 0x75, 0x9a, 0x48, 0x5c, 0xb8, 0xed, 0x8c, 0xff, 0xb3, 0x9a,
0xdb, 0xf5, 0x07, 0x6e, 0x20, 0x31, 0xc4, 0x94, 0x22, 0x1e, 0xab, 0x93, 0x4c, 0x0a, 0x12, 0xd7, 0xdf, 0xec, 0xee, 0xdf, 0xe0, 0x06, 0x12, 0x43, 0x4c, 0x29, 0xe2, 0xb1, 0x3a, 0xcd, 0xa4, 0x20,
0xf9, 0xcd, 0x89, 0x42, 0xf9, 0x3d, 0x0a, 0xd0, 0xd3, 0x1b, 0xec, 0x40, 0x61, 0x7c, 0x93, 0x71, 0x71, 0x9d, 0xdf, 0x9c, 0x2a, 0x94, 0xdf, 0xa2, 0x00, 0x3d, 0x9d, 0x60, 0x87, 0x0a, 0xe3, 0x9b,
0xa5, 0xbc, 0x05, 0xb1, 0x33, 0x9c, 0x09, 0x31, 0x8b, 0x71, 0xde, 0x44, 0x51, 0x82, 0x8a, 0x78, 0x8c, 0x2b, 0xe5, 0x2d, 0x88, 0x9d, 0xc1, 0x4c, 0x88, 0x59, 0x8c, 0xf3, 0x22, 0x8a, 0x12, 0x54,
0x92, 0x95, 0x6d, 0xee, 0x31, 0x0c, 0xce, 0x31, 0x46, 0x42, 0x1f, 0x55, 0x26, 0x52, 0x85, 0xcc, 0xc4, 0x93, 0xac, 0x2c, 0x73, 0x4f, 0xa0, 0x7f, 0x81, 0x31, 0x12, 0xfa, 0xa8, 0x32, 0x91, 0x2a,
0x86, 0x3d, 0x95, 0x07, 0x01, 0x2a, 0x65, 0x1b, 0x23, 0xe3, 0xc8, 0xf4, 0xeb, 0xd2, 0x7d, 0x0b, 0x64, 0x36, 0xec, 0xa9, 0x3c, 0x08, 0x50, 0x29, 0xdb, 0x18, 0x1a, 0xc7, 0xa6, 0x5f, 0x87, 0xee,
0x4f, 0x27, 0x48, 0xe3, 0x38, 0xfe, 0x84, 0xc4, 0x43, 0x4e, 0xdc, 0xc7, 0x6f, 0x39, 0x2a, 0x62, 0x1b, 0x78, 0x3c, 0x46, 0x1a, 0xc5, 0xf1, 0x47, 0x24, 0x1e, 0x72, 0xe2, 0x3e, 0x7e, 0xcd, 0x51,
0x43, 0xd8, 0x57, 0x22, 0x97, 0x01, 0x4e, 0x6f, 0x85, 0x22, 0xdd, 0x66, 0xf9, 0x50, 0x6e, 0x5d, 0x11, 0x1b, 0xc0, 0xbe, 0x12, 0xb9, 0x0c, 0x70, 0x7a, 0x2b, 0x14, 0xe9, 0x32, 0xcb, 0x87, 0x32,
0x08, 0x45, 0xee, 0x21, 0x58, 0x1f, 0xc3, 0x5a, 0x3d, 0x80, 0x4e, 0x14, 0x56, 0xa2, 0x4e, 0x14, 0x75, 0x29, 0x14, 0xb9, 0x47, 0x60, 0x7d, 0x08, 0x6b, 0x75, 0x1f, 0x3a, 0x51, 0x58, 0x89, 0x3a,
0xba, 0xb7, 0xf0, 0xe8, 0x2a, 0x0b, 0x79, 0x31, 0x42, 0xab, 0x80, 0x4d, 0x00, 0xe6, 0x4c, 0x76, 0x51, 0xe8, 0xde, 0xc2, 0x7f, 0x57, 0x59, 0xc8, 0x8b, 0x16, 0x5a, 0x05, 0x6c, 0x0c, 0x30, 0x67,
0x67, 0x64, 0x1c, 0xed, 0x9f, 0xbe, 0xf4, 0xda, 0x78, 0xbd, 0xb3, 0x66, 0x5d, 0x1d, 0xe6, 0x2f, 0xb2, 0x3b, 0x43, 0xe3, 0x78, 0xff, 0xec, 0x85, 0xd7, 0xc6, 0xeb, 0x9d, 0x37, 0xeb, 0x6a, 0x33,
0xb4, 0xba, 0xc7, 0xd0, 0x3f, 0xcf, 0x93, 0xac, 0x41, 0x75, 0xc0, 0x0c, 0x44, 0x4a, 0x98, 0x52, 0x7f, 0xa1, 0xd4, 0x3d, 0x81, 0x83, 0x8b, 0x3c, 0xc9, 0x1a, 0x54, 0x07, 0xcc, 0x40, 0xa4, 0x84,
0xc9, 0xda, 0xf7, 0x9b, 0xda, 0x1d, 0x40, 0xff, 0x43, 0x92, 0xd1, 0x5d, 0x75, 0x8e, 0xfb, 0xc7, 0x29, 0x95, 0xac, 0x07, 0x7e, 0x13, 0xbb, 0x7d, 0x38, 0x78, 0x9f, 0x64, 0x74, 0x57, 0xed, 0xe3,
0x00, 0xb3, 0xe6, 0xbe, 0x37, 0xe1, 0x3b, 0x3d, 0x21, 0x27, 0x0c, 0xa7, 0x9c, 0xaa, 0x09, 0x1d, 0xfe, 0x36, 0xc0, 0xac, 0xb9, 0xef, 0x75, 0xf8, 0x56, 0x77, 0xc8, 0x09, 0xc3, 0x29, 0xa7, 0xaa,
0xaf, 0xf4, 0xde, 0xab, 0xbd, 0xf7, 0xbe, 0xd4, 0xde, 0xfb, 0x56, 0xa5, 0x1e, 0x53, 0xd1, 0x9a, 0x43, 0xc7, 0x2b, 0x67, 0xef, 0xd5, 0xb3, 0xf7, 0x3e, 0xd7, 0xb3, 0xf7, 0xad, 0x4a, 0x3d, 0xa2,
0x6b, 0x7a, 0xdd, 0xda, 0xdd, 0xdc, 0x5a, 0xa9, 0xc7, 0x54, 0xdc, 0x54, 0x26, 0xa3, 0x84, 0xcb, 0xa2, 0x34, 0xd7, 0xf4, 0xba, 0xb4, 0xbb, 0xb9, 0xb4, 0x52, 0x8f, 0xa8, 0x38, 0xa9, 0x4c, 0x46,
0x3b, 0x7b, 0x47, 0x8f, 0x52, 0x97, 0xab, 0x17, 0xb2, 0xbb, 0x7a, 0x21, 0xec, 0x10, 0xac, 0x58, 0x09, 0x97, 0x77, 0xf6, 0x8e, 0x6e, 0xa5, 0x0e, 0x57, 0x0f, 0x64, 0x77, 0xf5, 0x40, 0xd8, 0x11,
0xcc, 0xa2, 0x74, 0x9a, 0xcb, 0xd8, 0xee, 0xe9, 0xcf, 0xa6, 0xde, 0xb8, 0x92, 0x31, 0x7b, 0x0c, 0x58, 0xb1, 0x98, 0x45, 0xe9, 0x34, 0x97, 0xb1, 0xdd, 0xd3, 0x9f, 0x4d, 0x9d, 0xb8, 0x92, 0x31,
0x5d, 0xe2, 0x33, 0x7b, 0x4f, 0x6f, 0x17, 0x4b, 0xf7, 0x57, 0x07, 0x60, 0x6e, 0xed, 0x7f, 0x8f, 0xfb, 0x1f, 0xba, 0xc4, 0x67, 0xf6, 0x9e, 0x4e, 0x17, 0x4b, 0xf7, 0x67, 0x07, 0x60, 0x3e, 0xda,
0xef, 0x80, 0x99, 0x2b, 0x94, 0x29, 0x4f, 0xb0, 0x62, 0x6f, 0x6a, 0x76, 0x00, 0xbb, 0x98, 0xf0, 0x7f, 0x1e, 0xdf, 0x01, 0x33, 0x57, 0x28, 0x53, 0x9e, 0x60, 0xc5, 0xde, 0xc4, 0xec, 0x10, 0x76,
0xa8, 0xa6, 0x2e, 0x8b, 0xa2, 0xa3, 0x78, 0x4b, 0x3f, 0x84, 0x0c, 0x2b, 0xee, 0xa6, 0x5e, 0x35, 0x31, 0xe1, 0x51, 0x4d, 0x5d, 0x06, 0x45, 0x45, 0x71, 0x97, 0xbe, 0x0b, 0x19, 0x56, 0xdc, 0x4d,
0xd3, 0x7c, 0xd8, 0x4c, 0xab, 0xdd, 0x4c, 0x98, 0x9b, 0xf9, 0xdb, 0x80, 0x27, 0xf7, 0xde, 0xe9, 0xbc, 0x3a, 0x4c, 0xf3, 0xe1, 0x61, 0x5a, 0xed, 0xc3, 0x84, 0x66, 0x98, 0xec, 0x09, 0x80, 0xa0,
0x22, 0x8d, 0xb1, 0x9e, 0xa6, 0xb3, 0x8e, 0xa6, 0xbb, 0x8e, 0x66, 0xe7, 0x61, 0x9a, 0x7f, 0x7d, 0x6c, 0xaa, 0x30, 0x90, 0x48, 0xf6, 0xbe, 0xfe, 0x60, 0x09, 0xca, 0x26, 0x3a, 0xe1, 0xfe, 0x32,
0x1a, 0xa7, 0x3f, 0xbb, 0x8b, 0x34, 0x97, 0x65, 0x26, 0xb1, 0x29, 0x0c, 0x96, 0xa3, 0x82, 0xbd, 0xe0, 0xd1, 0xbd, 0x6b, 0xbc, 0x08, 0x6b, 0xac, 0x87, 0xed, 0xac, 0x83, 0xed, 0xae, 0x83, 0xdd,
0x6a, 0xff, 0x61, 0x5b, 0x03, 0xc5, 0x79, 0xd6, 0x2e, 0xae, 0x65, 0xaf, 0x0d, 0x76, 0x01, 0xdd, 0x79, 0x18, 0xf6, 0x6f, 0x6f, 0xce, 0x0a, 0xac, 0xb9, 0x02, 0x7b, 0xf6, 0xa3, 0xbb, 0x08, 0x3b,
0x09, 0x12, 0x1b, 0xb6, 0x0b, 0x9b, 0xb0, 0x71, 0x46, 0x9b, 0x72, 0x82, 0x5d, 0x42, 0xef, 0x4c, 0x29, 0x1d, 0x8d, 0x4d, 0xa1, 0xbf, 0x6c, 0x34, 0xec, 0x65, 0xfb, 0x73, 0x6f, 0xb5, 0x23, 0xe7,
0x3f, 0x47, 0xb6, 0x6d, 0xa6, 0x6c, 0x71, 0xe8, 0x67, 0xe8, 0x95, 0x99, 0xc6, 0x9e, 0xb7, 0x6b, 0x69, 0xbb, 0xb8, 0x96, 0xbd, 0x32, 0xd8, 0x25, 0x74, 0xc7, 0x48, 0x6c, 0xd0, 0x2e, 0x6c, 0xac,
0x97, 0x12, 0x6f, 0xbb, 0x03, 0xcb, 0x9c, 0xde, 0x8c, 0xfc, 0xa2, 0x5d, 0xb0, 0x1c, 0xf3, 0xef, 0xca, 0x19, 0x6e, 0x72, 0x19, 0x36, 0x81, 0xde, 0xb9, 0xbe, 0xcc, 0x6c, 0x5b, 0x47, 0xda, 0x62,
0xe1, 0xab, 0x59, 0xff, 0x5e, 0xd7, 0x3d, 0xbd, 0x7a, 0xf3, 0x37, 0x00, 0x00, 0xff, 0xff, 0x22, 0xd3, 0x4f, 0xd0, 0x2b, 0x1d, 0x91, 0x3d, 0x6b, 0xd7, 0x2e, 0xf9, 0xe5, 0x76, 0x1b, 0x96, 0x2e,
0xb6, 0x48, 0x2d, 0x68, 0x06, 0x00, 0x00, 0xbf, 0x19, 0xf9, 0x79, 0xbb, 0x60, 0xf9, 0x27, 0xf1, 0x0e, 0xbe, 0x98, 0xf5, 0xe3, 0xbc, 0xee,
0xe9, 0xd5, 0xeb, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xde, 0x03, 0x4f, 0xb2, 0xa6, 0x06, 0x00,
0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@ -60,6 +60,7 @@ message Credential {
string source_host = 8; string source_host = 8;
string login_url = 9; string login_url = 9;
string tag = 10; string tag = 10;
string otp_secret = 11;
} }
message CredentialRequest { message CredentialRequest {
@ -70,4 +71,5 @@ message CredentialRequest {
string source_host = 5; string source_host = 5;
string login_url = 6; string login_url = 6;
string tag = 7; string tag = 7;
string otp_secret = 8;
} }

View File

@ -7,12 +7,13 @@ import (
"fmt" "fmt"
"io" "io"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/mitchell/selfpass/credentials/endpoints" "github.com/mitchell/selfpass/credentials/endpoints"
"github.com/mitchell/selfpass/credentials/protobuf" "github.com/mitchell/selfpass/credentials/protobuf"
"github.com/mitchell/selfpass/credentials/transport" "github.com/mitchell/selfpass/credentials/transport"
"github.com/mitchell/selfpass/credentials/types" "github.com/mitchell/selfpass/credentials/types"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
) )
func NewCredentialServiceClient(ctx context.Context, target, ca, cert, key string) (types.CredentialClient, error) { func NewCredentialServiceClient(ctx context.Context, target, ca, cert, key string) (types.CredentialClient, error) {
@ -34,16 +35,16 @@ func NewCredentialServiceClient(ctx context.Context, target, ca, cert, key strin
return nil, err return nil, err
} }
return CredentialServiceClient{ return credentialServiceClient{
client: protobuf.NewCredentialServiceClient(conn), client: protobuf.NewCredentialServiceClient(conn),
}, nil }, nil
} }
type CredentialServiceClient struct { type credentialServiceClient struct {
client protobuf.CredentialServiceClient client protobuf.CredentialServiceClient
} }
func (c CredentialServiceClient) GetAllMetadata(ctx context.Context, sourceHost string) (output <-chan types.Metadata, errch chan error) { func (c credentialServiceClient) GetAllMetadata(ctx context.Context, sourceHost string) (output <-chan types.Metadata, errch chan error) {
pbmdch := make(chan protobuf.Metadata, 1) pbmdch := make(chan protobuf.Metadata, 1)
errch = make(chan error, 1) errch = make(chan error, 1)
@ -84,7 +85,7 @@ func (c CredentialServiceClient) GetAllMetadata(ctx context.Context, sourceHost
return stream.Metadata, stream.Errors return stream.Metadata, stream.Errors
} }
func (c CredentialServiceClient) Get(ctx context.Context, id string) (output types.Credential, err error) { func (c credentialServiceClient) Get(ctx context.Context, id string) (output types.Credential, err error) {
req := transport.EncodeIdRequest(endpoints.IDRequest{ID: id}) req := transport.EncodeIdRequest(endpoints.IDRequest{ID: id})
res, err := c.client.Get(ctx, &req) res, err := c.client.Get(ctx, &req)
@ -95,7 +96,7 @@ func (c CredentialServiceClient) Get(ctx context.Context, id string) (output typ
return transport.DecodeCredential(*res) return transport.DecodeCredential(*res)
} }
func (c CredentialServiceClient) Create(ctx context.Context, ci types.CredentialInput) (output types.Credential, err error) { func (c credentialServiceClient) Create(ctx context.Context, ci types.CredentialInput) (output types.Credential, err error) {
req := transport.EncodeCredentialRequest(ci) req := transport.EncodeCredentialRequest(ci)
res, err := c.client.Create(ctx, &req) res, err := c.client.Create(ctx, &req)
@ -106,10 +107,26 @@ func (c CredentialServiceClient) Create(ctx context.Context, ci types.Credential
return transport.DecodeCredential(*res) return transport.DecodeCredential(*res)
} }
func (c CredentialServiceClient) Update(ctx context.Context, id string, ci types.CredentialInput) (output types.Credential, err error) { func (c credentialServiceClient) Update(ctx context.Context, id string, ci types.CredentialInput) (output types.Credential, err error) {
panic("implement me") req := transport.EncodeUpdateRequest(endpoints.UpdateRequest{ID: id, Credential: ci})
res, err := c.client.Update(ctx, &req)
if err != nil {
return output, err
}
return transport.DecodeCredential(*res)
} }
func (c CredentialServiceClient) Delete(ctx context.Context, id string) (err error) { func (c credentialServiceClient) Delete(ctx context.Context, id string) (err error) {
panic("implement me") req := transport.EncodeIdRequest(endpoints.IDRequest{ID: id})
res, err := c.client.Delete(ctx, &req)
if err != nil {
return err
}
if !res.Success {
return fmt.Errorf("delete unsuccessful")
}
return nil
} }

View File

@ -46,6 +46,7 @@ func (svc Credentials) Create(ctx context.Context, ci types.CredentialInput) (ou
c.Username = ci.Username c.Username = ci.Username
c.Email = ci.Email c.Email = ci.Email
c.Password = ci.Password c.Password = ci.Password
c.OTPSecret = ci.OTPSecret
c.Tag = ci.Tag c.Tag = ci.Tag
err = svc.repo.Put(ctx, c) err = svc.repo.Put(ctx, c)
@ -97,6 +98,7 @@ func (svc Credentials) Update(ctx context.Context, id string, ci types.Credentia
c.LoginURL = ci.LoginURL c.LoginURL = ci.LoginURL
c.SourceHost = ci.SourceHost c.SourceHost = ci.SourceHost
c.Password = ci.Password c.Password = ci.Password
c.OTPSecret = ci.OTPSecret
c.Email = ci.Email c.Email = ci.Email
c.Username = ci.Username c.Username = ci.Username
c.Tag = ci.Tag c.Tag = ci.Tag

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
"github.com/mitchell/selfpass/credentials/endpoints" "github.com/mitchell/selfpass/credentials/endpoints"
"github.com/mitchell/selfpass/credentials/protobuf" "github.com/mitchell/selfpass/credentials/protobuf"
"github.com/mitchell/selfpass/credentials/types" "github.com/mitchell/selfpass/credentials/types"
@ -124,6 +125,7 @@ func decodeCredentialRequest(ctx context.Context, request interface{}) (interfac
Username: r.Username, Username: r.Username,
Email: r.Email, Email: r.Email,
Password: r.Password, Password: r.Password,
OTPSecret: r.OtpSecret,
}, nil }, nil
} }
@ -133,6 +135,7 @@ func EncodeCredentialRequest(r types.CredentialInput) protobuf.CredentialRequest
Username: r.Username, Username: r.Username,
Email: r.Email, Email: r.Email,
Password: r.Password, Password: r.Password,
OtpSecret: r.OTPSecret,
SourceHost: r.SourceHost, SourceHost: r.SourceHost,
LoginUrl: r.LoginURL, LoginUrl: r.LoginURL,
Tag: r.Tag, Tag: r.Tag,
@ -163,6 +166,7 @@ func encodeCredentialResponse(ctx context.Context, response interface{}) (interf
Username: r.Username, Username: r.Username,
Email: r.Email, Email: r.Email,
Password: r.Password, Password: r.Password,
OtpSecret: r.OTPSecret,
}, nil }, nil
} }
@ -191,6 +195,7 @@ func DecodeCredential(r protobuf.Credential) (c types.Credential, err error) {
Username: r.Username, Username: r.Username,
Email: r.Email, Email: r.Email,
Password: r.Password, Password: r.Password,
OTPSecret: r.OtpSecret,
}, nil }, nil
} }
@ -209,13 +214,12 @@ func decodeUpdateRequest(ctx context.Context, request interface{}) (interface{},
Username: r.Credential.Username, Username: r.Credential.Username,
Email: r.Credential.Email, Email: r.Credential.Email,
Password: r.Credential.Password, Password: r.Credential.Password,
OTPSecret: r.Credential.OtpSecret,
}, },
}, nil }, nil
} }
func EncodeUpdateRequest(ctx context.Context, request interface{}) (interface{}, error) { func EncodeUpdateRequest(r endpoints.UpdateRequest) protobuf.UpdateRequest {
r := request.(endpoints.UpdateRequest)
c := r.Credential c := r.Credential
return protobuf.UpdateRequest{ return protobuf.UpdateRequest{
Id: r.ID, Id: r.ID,
@ -224,11 +228,12 @@ func EncodeUpdateRequest(ctx context.Context, request interface{}) (interface{},
Username: c.Username, Username: c.Username,
Email: c.Email, Email: c.Email,
Password: c.Password, Password: c.Password,
OtpSecret: c.OTPSecret,
SourceHost: c.SourceHost, SourceHost: c.SourceHost,
LoginUrl: c.LoginURL, LoginUrl: c.LoginURL,
Tag: c.Tag, Tag: c.Tag,
}, },
}, nil }
} }
func decodeIdRequest(ctx context.Context, request interface{}) (interface{}, error) { func decodeIdRequest(ctx context.Context, request interface{}) (interface{}, error) {

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"fmt"
"time" "time"
) )
@ -11,6 +12,14 @@ type Credential struct {
Username string Username string
Email string Email string
Password string `json:"-"` Password string `json:"-"`
OTPSecret string `json:"-"`
}
func (c Credential) String() string {
return fmt.Sprintf(
"username = %s\nemail = %s\n%s",
c.Username, c.Email, c.Metadata,
)
} }
type CredentialInput struct { type CredentialInput struct {
@ -18,6 +27,7 @@ type CredentialInput struct {
Username string Username string
Email string Email string
Password string Password string
OTPSecret string
} }
type Metadata struct { type Metadata struct {
@ -30,6 +40,13 @@ type Metadata struct {
Tag string Tag string
} }
func (m Metadata) String() string {
return fmt.Sprintf(
"id = %s\nsourceHost = %s\ncreatedAt = %s\nupdatedAt = %s\nprimary = %s\nloginUrl = %s\ntag = %s\n",
m.ID, m.SourceHost, m.CreatedAt, m.UpdatedAt, m.Primary, m.LoginURL, m.Tag,
)
}
type MetadataInput struct { type MetadataInput struct {
Primary string Primary string
SourceHost string SourceHost string

View File

@ -10,37 +10,6 @@ import (
"github.com/cloudflare/redoctober/padding" "github.com/cloudflare/redoctober/padding"
) )
func GenerateKeyFromPassword(pass []byte) ([]byte, error) {
if len(pass) < 8 {
return nil, fmt.Errorf("master password must be at least 8 characters")
}
for idx := 0; len(pass) < 32; idx++ {
pass = append(pass, pass[idx])
if idx == len(pass) {
idx = 0
}
}
return pass, nil
}
func CombinePasswordAndKey(pass, key []byte) ([]byte, error) {
if len(pass) < 8 {
return nil, fmt.Errorf("master password must be at least 8 characters")
}
if len(key) != 16 {
return nil, fmt.Errorf("key was not of length 16")
}
for idx := 0; len(pass) < 16; idx++ {
pass = append(pass, pass[idx])
}
return append(pass[:16], key...), nil
}
func CBCEncrypt(key []byte, plaintext []byte) ([]byte, error) { func CBCEncrypt(key []byte, plaintext []byte) ([]byte, error) {
if len(key) != 32 { if len(key) != 32 {
return nil, fmt.Errorf("key is not 32 bytes") return nil, fmt.Errorf("key is not 32 bytes")

68
crypto/helpers.go Normal file
View File

@ -0,0 +1,68 @@
package crypto
import (
"fmt"
"math/rand"
"time"
)
func GenerateKeyFromPassword(pass []byte) ([]byte, error) {
if len(pass) < 8 {
return nil, fmt.Errorf("master password must be at least 8 characters")
}
for idx := 0; len(pass) < 32; idx++ {
pass = append(pass, pass[idx])
if idx == len(pass) {
idx = 0
}
}
return pass, nil
}
func CombinePasswordAndKey(pass, key []byte) ([]byte, error) {
if len(pass) < 8 {
return nil, fmt.Errorf("master password must be at least 8 characters")
}
if len(key) != 16 {
return nil, fmt.Errorf("key was not of length 16")
}
for idx := 0; len(pass) < 16; idx++ {
pass = append(pass, pass[idx])
}
return append(pass[:16], key...), nil
}
func GeneratePassword(length int, numbers, specials bool) string {
const alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
const alphasAndSpecials = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()"
const alphanumericsAndSpecials = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()"
rand.Seed(time.Now().UnixNano())
pass := make([]byte, length)
switch {
case numbers && specials:
for idx := 0; idx < length; idx++ {
pass[idx] = alphanumericsAndSpecials[rand.Int63()%int64(len(alphanumericsAndSpecials))]
}
case numbers:
for idx := 0; idx < length; idx++ {
pass[idx] = alphanumerics[rand.Int63()%int64(len(alphanumerics))]
}
case specials:
for idx := 0; idx < length; idx++ {
pass[idx] = alphasAndSpecials[rand.Int63()%int64(len(alphasAndSpecials))]
}
default:
for idx := 0; idx < length; idx++ {
pass[idx] = alphas[rand.Int63()%int64(len(alphas))]
}
}
return string(pass)
}

View File

@ -1,21 +1,16 @@
version: "3.7" version: "3.7"
services: services:
redis: redis:
image: "redis:alpine" image: "redis:5.0.5"
selfpass: volumes:
- "./data:/data"
server:
build: . build: .
entrypoint: restart: always
- server
ports: ports:
- "8080:8080" - "8080:8080"
secrets: entrypoint:
- ca_cert - server
- server_cert - -v
- server_key depends_on:
secrets: - redis
ca_cert:
file: ./certs/ca.pem
server_cert:
file: ./certs/server.pem
server_key:
file: ./certs/server-key.pem

13
gen_certs_go.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env sh
ca=$(cat ./certs/ca.pem)
cert=$(cat ./certs/server.pem)
key=$(cat ./certs/server-key.pem)
cat << EOM
// Code generated by inject-certs.sh, DO NOT EDIT.
package main
const ca = \`${ca}\`
const cert = \`${cert}\`
const key = \`${key}\`
EOM

4
go.mod
View File

@ -4,7 +4,9 @@ require (
github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect
github.com/atotto/clipboard v0.1.2 github.com/atotto/clipboard v0.1.2
github.com/aws/aws-sdk-go-v2 v0.7.0 github.com/aws/aws-sdk-go-v2 v0.7.0
github.com/boombuler/barcode v1.0.0 // indirect
github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692 github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692
github.com/davecgh/go-spew v1.1.1
github.com/go-kit/kit v0.8.0 github.com/go-kit/kit v0.8.0
github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect github.com/go-stack/stack v1.8.0 // indirect
@ -17,8 +19,8 @@ require (
github.com/mattn/go-isatty v0.0.7 // indirect github.com/mattn/go-isatty v0.0.7 // indirect
github.com/mediocregopher/radix/v3 v3.2.3 github.com/mediocregopher/radix/v3 v3.2.3
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/pquerna/otp v1.1.0
github.com/spf13/cobra v0.0.3 github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2 github.com/spf13/viper v1.3.2
github.com/stretchr/testify v1.3.0 // indirect github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect

4
go.sum
View File

@ -10,6 +10,8 @@ github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzb
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v0.7.0 h1:a5xRI/tBmUFKuAA0SOyEY2P1YhQb+jVOEI9P/7KfrP0= github.com/aws/aws-sdk-go-v2 v0.7.0 h1:a5xRI/tBmUFKuAA0SOyEY2P1YhQb+jVOEI9P/7KfrP0=
github.com/aws/aws-sdk-go-v2 v0.7.0/go.mod h1:17MaCZ9g0q5BIMxwzRQeiv8M3c8+W7iuBnlWAEprcxE= github.com/aws/aws-sdk-go-v2 v0.7.0/go.mod h1:17MaCZ9g0q5BIMxwzRQeiv8M3c8+W7iuBnlWAEprcxE=
github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692 h1:eVvf+nrm0mXV3JOh2c9vt+Pemh/TAUOjqRNrp1eyPmk= github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692 h1:eVvf+nrm0mXV3JOh2c9vt+Pemh/TAUOjqRNrp1eyPmk=
github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= github.com/cloudflare/redoctober v0.0.0-20180928214028-3f826eedb692/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
@ -81,6 +83,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.1.0 h1:q2gMsMuMl3JzneUaAX1MRGxLvOG6bzXV51hivBaStf0=
github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=

View File

@ -1,6 +0,0 @@
grpcurl -cacert ./certs/ca.pem \
-cert ./certs/client.pem \
-key ./certs/client-key.pem \
-proto ./credentials/protobuf/service.proto \
localhost:8080 \
selfpass.credentials.CredentialService/GetAllMetadata