diff --git a/.goreleaser.yml b/.goreleaser.yml index 6c4a352afd..151f47afe4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -38,90 +38,6 @@ builds: - 6 - 7 mod_timestamp: '{{ .CommitTimestamp }}' - - id: gopass-git-credentials - dir: cmd/gopass-git-credentials - binary: gopass-git-credentials - flags: - - -trimpath - - -tags=netgo - env: - - CGO_ENABLED=0 - asmflags: - - all=-trimpath={{.Env.GOPATH}} - gcflags: - - all=-trimpath={{.Env.GOPATH}} - ldflags: | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -extldflags '-static' - goos: - - darwin - - linux - - windows - goarch: - - amd64 - mod_timestamp: '{{ .CommitTimestamp }}' - - id: gopass-hibp - dir: cmd/gopass-hibp - binary: gopass-hibp - flags: - - -trimpath - - -tags=netgo - env: - - CGO_ENABLED=0 - asmflags: - - all=-trimpath={{.Env.GOPATH}} - gcflags: - - all=-trimpath={{.Env.GOPATH}} - ldflags: | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -extldflags '-static' - goos: - - darwin - - linux - - windows - goarch: - - amd64 - mod_timestamp: '{{ .CommitTimestamp }}' - - id: gopass-jsonapi - dir: cmd/gopass-jsonapi - binary: gopass-jsonapi - flags: - - -trimpath - - -tags=netgo - env: - - CGO_ENABLED=0 - asmflags: - - all=-trimpath={{.Env.GOPATH}} - gcflags: - - all=-trimpath={{.Env.GOPATH}} - ldflags: | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -extldflags '-static' - goos: - - darwin - - linux - - windows - goarch: - - amd64 - mod_timestamp: '{{ .CommitTimestamp }}' - - id: gopass-summon-provider - dir: cmd/gopass-summon-provider - binary: gopass-summon-provider - flags: - - -trimpath - - -tags=netgo - env: - - CGO_ENABLED=0 - asmflags: - - all=-trimpath={{.Env.GOPATH}} - gcflags: - - all=-trimpath={{.Env.GOPATH}} - ldflags: | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -extldflags '-static' - goos: - - darwin - - linux - - windows - goarch: - - amd64 - mod_timestamp: '{{ .CommitTimestamp }}' archives: - id: gopass name_template: "{{.Binary}}-{{.Version}}-{{.Os}}-{{.Arch}}{{ if .Arm }}v{{.Arm }}{{ end }}" diff --git a/Makefile b/Makefile index 555ad815a9..797f1cde3f 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ export GO111MODULE=on OK := $(shell tput setaf 6; echo ' [OK]'; tput sgr0;) all: build completion -build: $(GOPASS_OUTPUT) gopass-git-credentials gopass-hibp gopass-jsonapi gopass-summon-provider +build: $(GOPASS_OUTPUT) completion: $(BASH_COMPLETION_OUTPUT) $(FISH_COMPLETION_OUTPUT) $(ZSH_COMPLETION_OUTPUT) travis: sysinfo crosscompile build install fulltest codequality completion full travis-osx: sysinfo build install test completion full @@ -76,34 +76,10 @@ $(GOPASS_OUTPUT): $(GOFILES_BUILD) @$(GO) build -o $@ $(BUILDFLAGS) @printf '%s\n' '$(OK)' -gopass-git-credentials: $(GOFILES_BUILD) - @echo -n ">> BUILD, version = $(GOPASS_VERSION)/$(GOPASS_REVISION), output = $@" - @cd cmd/gopass-git-credentials && $(GO) build -o gopass-git-credentials $(BUILDFLAGS) - @printf '%s\n' '$(OK)' - -gopass-hibp: $(GOFILES_BUILD) - @echo -n ">> BUILD, version = $(GOPASS_VERSION)/$(GOPASS_REVISION), output = $@" - @cd cmd/gopass-hibp && $(GO) build -o gopass-hibp $(BUILDFLAGS) - @printf '%s\n' '$(OK)' - -gopass-jsonapi: $(GOFILES_BUILD) - @echo -n ">> BUILD, version = $(GOPASS_VERSION)/$(GOPASS_REVISION), output = $@" - @cd cmd/gopass-jsonapi && $(GO) build -o gopass-jsonapi $(BUILDFLAGS) - @printf '%s\n' '$(OK)' - -gopass-summon-provider: $(GOFILES_BUILD) - @echo -n ">> BUILD, version = $(GOPASS_VERSION)/$(GOPASS_REVISION), output = $@" - @cd cmd/gopass-summon-provider && $(GO) build -o gopass-summon-provider $(BUILDFLAGS) - @printf '%s\n' '$(OK)' - install: all install-completion @echo -n ">> INSTALL, version = $(GOPASS_VERSION)" @install -m 0755 -d $(DESTDIR)$(BINDIR) @install -m 0755 $(GOPASS_OUTPUT) $(DESTDIR)$(BINDIR)/gopass - @install -m 0755 cmd/gopass-git-credentials/gopass-git-credentials $(DESTDIR)$(BINDIR)/gopass-git-credentials - @install -m 0755 cmd/gopass-hibp/gopass-hibp $(DESTDIR)$(BINDIR)/gopass-hibp - @install -m 0755 cmd/gopass-jsonapi/gopass-jsonapi $(DESTDIR)$(BINDIR)/gopass-jsonapi - @install -m 0755 cmd/gopass-summon-provider/gopass-summon-provider $(DESTDIR)$(BINDIR)/gopass-summon-provider @printf '%s\n' '$(OK)' fulltest: $(GOPASS_OUTPUT) diff --git a/cmd/gopass-git-credentials/.gitignore b/cmd/gopass-git-credentials/.gitignore deleted file mode 100644 index fb9333c016..0000000000 --- a/cmd/gopass-git-credentials/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gopass-git-credentials diff --git a/cmd/gopass-git-credentials/git-credential.go b/cmd/gopass-git-credentials/git-credential.go deleted file mode 100644 index be54b13571..0000000000 --- a/cmd/gopass-git-credentials/git-credential.go +++ /dev/null @@ -1,254 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "io" - "log" - "os" - "os/exec" - "strings" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/debug" - "github.com/gopasspw/gopass/pkg/fsutil" - "github.com/gopasspw/gopass/pkg/gopass" - "github.com/gopasspw/gopass/pkg/gopass/secrets" - "github.com/gopasspw/gopass/pkg/termio" - "github.com/urfave/cli/v2" -) - -var ( - // Stdout is exported for tests - Stdout io.Writer = os.Stdout -) - -type gitCredentials struct { - Protocol string - Host string - Path string - Username string - Password string -} - -// WriteTo writes the given credentials to the given io.Writer in the git-credential format -func (c *gitCredentials) WriteTo(w io.Writer) (int64, error) { - var n int64 - if c.Protocol != "" { - i, err := io.WriteString(w, "protocol="+c.Protocol+"\n") - n += int64(i) - if err != nil { - return n, err - } - } - if c.Host != "" { - i, err := io.WriteString(w, "host="+c.Host+"\n") - n += int64(i) - if err != nil { - return n, err - } - } - if c.Path != "" { - i, err := io.WriteString(w, "path="+c.Path+"\n") - n += int64(i) - if err != nil { - return n, err - } - } - if c.Username != "" { - i, err := io.WriteString(w, "username="+c.Username+"\n") - n += int64(i) - if err != nil { - return n, err - } - } - if c.Password != "" { - i, err := io.WriteString(w, "password="+c.Password+"\n") - n += int64(i) - if err != nil { - return n, err - } - } - return n, nil -} - -func parseGitCredentials(r io.Reader) (*gitCredentials, error) { - rd := bufio.NewReader(r) - c := &gitCredentials{} - for { - key, err := rd.ReadString('=') - if err != nil { - if err == io.EOF { - if key == "" { - return c, nil - } - return nil, io.ErrUnexpectedEOF - } - return nil, err - } - key = strings.TrimSuffix(key, "=") - val, err := rd.ReadString('\n') - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - if err != nil { - return nil, err - } - val = strings.TrimSuffix(val, "\n") - switch key { - case "protocol": - c.Protocol = val - case "host": - c.Host = val - case "path": - c.Path = val - case "username": - c.Username = val - case "password": - c.Password = val - } - } -} - -type gc struct { - gp gopass.Store -} - -// Before is executed before another git-credential command -func (s *gc) Before(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - ctx = ctxutil.WithInteractive(ctx, false) - if !ctxutil.IsStdin(ctx) { - return fmt.Errorf("missing stdin from git") - } - return nil -} - -func filter(ls []string, prefix string) []string { - out := make([]string, 0, len(ls)) - for _, e := range ls { - if !strings.HasPrefix(e, prefix) { - continue - } - out = append(out, e) - } - return out -} - -// Get returns a credential to git -func (s *gc) Get(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - ctx = ctxutil.WithNoNetwork(ctx, true) - cred, err := parseGitCredentials(termio.Stdin) - if err != nil { - return fmt.Errorf("error: %v while parsing git-credential", err) - } - // try git/host/username... If username is empty, simply try git/host - path := "git/" + fsutil.CleanFilename(cred.Host) + "/" + fsutil.CleanFilename(cred.Username) - if _, err := s.gp.Get(ctx, path, "latest"); err != nil { - // if the looked up path is a directory with only one entry (e.g. one user per host), take the subentry instead - ls, err := s.gp.List(ctx) - if err != nil { - return fmt.Errorf("error: %v while listing the storage", err) - } - entries := filter(ls, path) - if len(entries) < 1 { - // no entry found, this is not an error - return nil - } - if len(entries) > 1 { - fmt.Fprintln(os.Stderr, "gopass error: too many entries") - return nil - } - path = entries[0] - } - secret, err := s.gp.Get(ctx, path, "latest") - if err != nil { - return err - } - cred.Password = secret.Password() - if username, _ := secret.Get("login"); username != "" { - // leave the username as is otherwise - cred.Username = username - } - - _, err = cred.WriteTo(Stdout) - if err != nil { - return fmt.Errorf("could not write to stdout: %s", err) - } - return nil -} - -// Store stores a credential got from git -func (s *gc) Store(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - cred, err := parseGitCredentials(termio.Stdin) - if err != nil { - return fmt.Errorf("error: %v while parsing git-credential", err) - } - path := "git/" + fsutil.CleanFilename(cred.Host) + "/" + fsutil.CleanFilename(cred.Username) - // This should never really be an issue because git automatically removes invalid credentials first - if _, err := s.gp.Get(ctx, path, "latest"); err == nil { - debug.Log(""+ - "gopass: did not store \"%s\" because it already exists. "+ - "If you want to overwrite it, delete it first by doing: "+ - "\"gopass rm %s\"\n", - path, path, - ) - return nil - } - secret := secrets.New() - secret.SetPassword(cred.Password) - if cred.Username != "" { - secret.Set("login", cred.Username) - } - - if err := s.gp.Set(ctx, path, secret); err != nil { - fmt.Fprintf(os.Stderr, "gopass error: error while writing to store: %v\n", err) - } - return nil -} - -// Erase removes a credential got from git -func (s *gc) Erase(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - cred, err := parseGitCredentials(termio.Stdin) - if err != nil { - return fmt.Errorf("error: %v while parsing git-credential", err) - } - - path := "git/" + fsutil.CleanFilename(cred.Host) + "/" + fsutil.CleanFilename(cred.Username) - if err := s.gp.Remove(ctx, path); err != nil { - fmt.Fprintln(os.Stderr, "gopass error: error while writing to store") - } - return nil -} - -// Configure configures gopass as git's credential.helper -func (s *gc) Configure(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - flags := 0 - flag := "--global" - if c.Bool("local") { - flag = "--local" - flags++ - } - if c.Bool("global") { - flag = "--global" - flags++ - } - if c.Bool("system") { - flag = "--system" - flags++ - } - if flags >= 2 { - return fmt.Errorf("only specify one target of installation") - } - if flags == 0 { - log.Println("No target given, assuming --global.") - } - cmd := exec.CommandContext(ctx, "git", "config", flag, "credential.helper", `"!gopass-git-credentials $@"`) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/cmd/gopass-git-credentials/git-credential_test.go b/cmd/gopass-git-credentials/git-credential_test.go deleted file mode 100644 index 8ceb26b34a..0000000000 --- a/cmd/gopass-git-credentials/git-credential_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package main - -import ( - "bytes" - "context" - "io" - "os" - "strings" - "testing" - - "github.com/fatih/color" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass/apimock" - "github.com/gopasspw/gopass/pkg/termio" - "github.com/gopasspw/gopass/tests/gptest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGitCredentialFormat(t *testing.T) { - data := []io.Reader{ - strings.NewReader("" + - "protocol=https\n" + - "host=example.com\n" + - "username=bob\n" + - "foo=bar\n" + - "path=test\n" + - "password=secr3=t\n", - ), - strings.NewReader("" + - "protocol=https\n" + - "host=example.com\n" + - "username=bob\n" + - "foo=bar\n" + - "password=secr3=t\n" + - "test=", - ), - strings.NewReader("" + - "protocol=https\n" + - "host=example.com\n" + - "username=bob\n" + - "foo=bar\n" + - "password=secr3=t\n" + - "test", - ), - } - results := []gitCredentials{ - { - Host: "example.com", - Password: "secr3=t", - Path: "test", - Protocol: "https", - Username: "bob", - }, - {}, - {}, - } - expectsErr := []bool{false, true, true} - for i := range data { - result, err := parseGitCredentials(data[i]) - if expectsErr[i] { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - if err != nil { - continue - } - assert.Equal(t, results[i], *result) - buf := &bytes.Buffer{} - n, err := result.WriteTo(buf) - assert.NoError(t, err, "could not serialize credentials") - assert.Equal(t, buf.Len(), int(n)) - parseback, err := parseGitCredentials(buf) - assert.NoError(t, err, "failed parsing my own output") - assert.Equal(t, results[i], *parseback, "failed parsing my own output") - } -} - -func TestGitCredentialHelper(t *testing.T) { - ctx := context.Background() - act := &gc{ - gp: apimock.New(), - } - require.NoError(t, act.gp.Set(ctx, "foo", &apimock.Secret{Buf: []byte("bar")})) - - stdout := &bytes.Buffer{} - Stdout = stdout - color.NoColor = true - defer func() { - Stdout = os.Stdout - termio.Stdin = os.Stdin - }() - - c := gptest.CliCtx(ctx, t) - - // before without stdin - assert.Error(t, act.Before(c)) - - // before with stdin - ctx = ctxutil.WithStdin(ctx, true) - c.Context = ctx - assert.NoError(t, act.Before(c)) - - s := "protocol=https\n" + - "host=example.com\n" + - "username=bob\n" - - termio.Stdin = strings.NewReader(s) - assert.NoError(t, act.Get(c)) - assert.Equal(t, "", stdout.String()) - - termio.Stdin = strings.NewReader(s + "password=secr3=t\n") - assert.NoError(t, act.Store(c)) - stdout.Reset() - - termio.Stdin = strings.NewReader(s) - assert.NoError(t, act.Get(c)) - read, err := parseGitCredentials(stdout) - assert.NoError(t, err) - assert.Equal(t, "secr3=t", read.Password) - stdout.Reset() - - termio.Stdin = strings.NewReader("host=example.com\n") - assert.NoError(t, act.Get(c)) - read, err = parseGitCredentials(stdout) - assert.NoError(t, err) - assert.Equal(t, "secr3=t", read.Password) - assert.Equal(t, "bob", read.Username) - stdout.Reset() - - termio.Stdin = strings.NewReader(s) - assert.NoError(t, act.Erase(c)) - assert.Equal(t, "", stdout.String()) - - termio.Stdin = strings.NewReader(s) - assert.NoError(t, act.Get(c)) - assert.Equal(t, "", stdout.String()) - - termio.Stdin = strings.NewReader("a") - assert.Error(t, act.Get(c)) - termio.Stdin = strings.NewReader("a") - assert.Error(t, act.Store(c)) - termio.Stdin = strings.NewReader("a") - assert.Error(t, act.Erase(c)) -} diff --git a/cmd/gopass-git-credentials/main.go b/cmd/gopass-git-credentials/main.go deleted file mode 100644 index 1bd6ba8c03..0000000000 --- a/cmd/gopass-git-credentials/main.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass/api" - "github.com/urfave/cli/v2" -) - -const ( - name = "gopass-git-credentials" -) - -var ( - // Version is the released version of gopass - version string -) - -func main() { - ctx := context.Background() - - // trap Ctrl+C and call cancel on the context - ctx, cancel := context.WithCancel(ctx) - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) - defer func() { - signal.Stop(sigChan) - cancel() - }() - go func() { - select { - case <-sigChan: - cancel() - case <-ctx.Done(): - } - }() - - // reading from stdin? - if info, err := os.Stdin.Stat(); err == nil && info.Mode()&os.ModeCharDevice == 0 { - ctx = ctxutil.WithInteractive(ctx, false) - ctx = ctxutil.WithStdin(ctx, true) - } - - gp, err := api.New(ctx) - if err != nil { - fmt.Printf("Failed to initialize gopass API: %s\n", err) - os.Exit(1) - } - - gc := &gc{ - gp: gp, - } - - app := cli.NewApp() - app.Name = name - app.Version = version - app.Usage = `Use "!gopass-git-credentials $@" as git's credential.helper` - app.Description = "" + - "This command allows you to cache your git-credentials with gopass." + - "Activate by using `git config --global credential.helper \"!gopass-git-credentials $@\"`" - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "get", - Hidden: true, - Action: gc.Get, - Before: gc.Before, - }, - { - Name: "store", - Hidden: true, - Action: gc.Store, - Before: gc.Before, - }, - { - Name: "erase", - Hidden: true, - Action: gc.Erase, - Before: gc.Before, - }, - { - Name: "configure", - Description: "This command configures gopass-git-credential as git's credential.helper", - Action: gc.Configure, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "Install for current user", - }, - &cli.BoolFlag{ - Name: "local", - Usage: "Install for current repository only", - }, - &cli.BoolFlag{ - Name: "system", - Usage: "Install for all users, requires superuser rights", - }, - }, - }, - } - - if err := app.RunContext(ctx, os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/gopass-hibp/.gitignore b/cmd/gopass-hibp/.gitignore deleted file mode 100644 index d67bb37f60..0000000000 --- a/cmd/gopass-hibp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gopass-hibp diff --git a/cmd/gopass-hibp/hibp.go b/cmd/gopass-hibp/hibp.go deleted file mode 100644 index 6e67702b1b..0000000000 --- a/cmd/gopass-hibp/hibp.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - "context" - "crypto/sha1" - "fmt" - "sort" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/debug" - "github.com/gopasspw/gopass/pkg/gopass" - hibpapi "github.com/gopasspw/gopass/pkg/hibp/api" - hibpdump "github.com/gopasspw/gopass/pkg/hibp/dump" - "github.com/gopasspw/gopass/pkg/termio" - - "github.com/fatih/color" -) - -type hibp struct { - gp gopass.Store -} - -// CheckAPI checks your secrets against the HIBPv2 API -func (s *hibp) CheckAPI(ctx context.Context, force bool) error { - if !force && !termio.AskForConfirmation(ctx, "This command is checking all your secrets against the haveibeenpwned.com API.\n\nThis will send five bytes of each passwords SHA1 hash to an untrusted server!\n\nYou will be asked to unlock all your secrets!\nDo you want to continue?") { - return fmt.Errorf("user aborted") - } - - shaSums, sortedShaSums, err := s.precomputeHashes(ctx) - if err != nil { - return err - } - - fmt.Println("Checking pre-computed SHA1 hashes against the HIBP API ...") - - // compare the prepared list against all provided files - matchList := make([]string, 0, len(sortedShaSums)) - for _, shaSum := range sortedShaSums { - freq, err := hibpapi.Lookup(shaSum) - if err != nil { - fmt.Printf("Failed to check HIBP API: %s\n", err) - continue - } - if freq < 1 { - continue - } - if pw, found := shaSums[shaSum]; found { - matchList = append(matchList, pw) - } - } - - return s.printMatches(matchList) -} - -// CheckDump checks your secrets against the provided HIBPv2 Dumps -func (s *hibp) CheckDump(ctx context.Context, force bool, dumps []string) error { - fmt.Println("Using the HIBPv2 dumps is very expensive. If you can condone leaking a few bits of entropy per secret you should probably use the '--api' flag.") - - if !force && !termio.AskForConfirmation(ctx, fmt.Sprintf("This command is checking all your secrets against the haveibeenpwned.com hashes in %+v.\nYou will be asked to unlock all your secrets!\nDo you want to continue?", dumps)) { - return fmt.Errorf("user aborted") - } - - shaSums, sortedShaSums, err := s.precomputeHashes(ctx) - if err != nil { - return err - } - - scanner, err := hibpdump.New(dumps...) - if err != nil { - return fmt.Errorf("failed to create new HIBP Dump scanner: %s", err) - } - - matchedSums := scanner.LookupBatch(ctx, sortedShaSums) - debug.Log("In: %+v - Out: %+v", sortedShaSums, matchedSums) - matchList := make([]string, 0, len(matchedSums)) - for _, matchedSum := range matchedSums { - if pw, found := shaSums[matchedSum]; found { - matchList = append(matchList, pw) - } - } - - return s.printMatches(matchList) -} - -func (s *hibp) precomputeHashes(ctx context.Context) (map[string]string, []string, error) { - // build a map of all secrets sha sums to their names and also build a sorted (!) - // list of this shasums. As the hibp dump is already sorted this allows for - // a very efficient stream compare in O(n) - pwList, err := s.gp.List(ctx) - if err != nil { - return nil, nil, err - } - // map sha1sum back to secret name for reporting - shaSums := make(map[string]string, len(pwList)) - // build list of sha1sums (must be sorted later!) for stream comparison - sortedShaSums := make([]string, 0, len(shaSums)) - // display progress bar - bar := termio.NewProgressBar(int64(len(pwList)), ctxutil.IsHidden(ctx)) - - fmt.Println("Computing SHA1 hashes of all your secrets ...") - for _, secret := range pwList { - // check for context cancelation - select { - case <-ctx.Done(): - return nil, nil, fmt.Errorf("user aborted") - default: - } - - bar.Inc() - - // only handle secrets / passwords, never the body - // comparing the body is super hard, as every user may choose to use - // the body of a secret differently. In the future we may support - // go templates to extract and compare data from the body - sec, err := s.gp.Get(ctx, secret, "latest") - if err != nil { - fmt.Printf("\n" + color.YellowString("Failed to retrieve secret '%s': %s\n", secret, err)) - continue - } - - pw := sec.Password() - // do not check empty passwords, there should be caught by `gopass audit` - // anyway - if len(pw) < 1 { - continue - } - sum := sha1hex(pw) - shaSums[sum] = secret - sortedShaSums = append(sortedShaSums, sum) - } - bar.Done() - // IMPORTANT: sort after all entries have been added. without the sort - // the stream compare will not work - sort.Strings(sortedShaSums) - - return shaSums, sortedShaSums, nil -} - -func (s *hibp) printMatches(matchList []string) error { - if len(matchList) < 1 { - fmt.Println("Good news - No matches found!") - return nil - } - - sort.Strings(matchList) - fmt.Println("Oh no - Found some matches:") - for _, m := range matchList { - fmt.Printf("\t- %s\n", m) - } - fmt.Println("The passwords in the listed secrets were included in public leaks in the past. This means they are likely included in many word-list attacks and provide only very little security. Strongly consider changing those passwords!") - return fmt.Errorf("weak passwords found") -} - -func sha1hex(data string) string { - h := sha1.New() - _, _ = h.Write([]byte(data)) - return fmt.Sprintf("%X", h.Sum(nil)) -} diff --git a/cmd/gopass-hibp/hibp_test.go b/cmd/gopass-hibp/hibp_test.go deleted file mode 100644 index 1d87c6398a..0000000000 --- a/cmd/gopass-hibp/hibp_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package main - -import ( - "compress/gzip" - "context" - "flag" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass/apimock" - hibpapi "github.com/gopasspw/gopass/pkg/hibp/api" - "github.com/gopasspw/gopass/tests/gptest" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" -) - -const testHibpSample = `000000005AD76BD555C1D6D771DE417A4B87E4B4 -00000000A8DAE4228F821FB418F59826079BF368:42 -00000000DD7F2A1C68A35673713783CA390C9E93:42 -00000001E225B908BAC31C56DB04D892E47536E0:42 -00000008CD1806EB7B9B46A8F87690B2AC16F617:42 -0000000A0E3B9F25FF41DE4B5AC238C2D545C7A8:42 -0000000A1D4B746FAA3FD526FF6D5BC8052FDB38:42 -0000000CAEF405439D57847A8657218C618160B2:42 -0000000FC1C08E6454BED24F463EA2129E254D43:42 -00000010F4B38525354491E099EB1796278544B1` - -func TestHIBPDump(t *testing.T) { - dir, err := ioutil.TempDir("", "gopass-hibp") - if err != nil { - t.Fatalf("failed to create temp dir: %s", err) - } - defer os.RemoveAll(dir) - - ctx := context.Background() - ctx = ctxutil.WithAlwaysYes(ctx, true) - - act := &hibp{ - gp: apimock.New(), - } - - app := cli.NewApp() - fs := flag.NewFlagSet("default", flag.ContinueOnError) - c := cli.NewContext(app, fs, nil) - c.Context = ctx - - // setup file and env - fn := filepath.Join(dir, "dump.txt") - fs = flag.NewFlagSet("default", flag.ContinueOnError) - bf := cli.StringSliceFlag{ - Name: "dumps", - Usage: "dumps", - } - assert.NoError(t, bf.Apply(fs)) - assert.NoError(t, fs.Parse([]string{"--dumps=" + fn})) - c = cli.NewContext(app, fs, nil) - c.Context = ctx - - assert.NoError(t, ioutil.WriteFile(fn, []byte(testHibpSample), 0644)) - assert.NoError(t, act.CheckDump(c.Context, false, []string{fn})) - - // gzip - fn = filepath.Join(dir, "dump.txt.gz") - fs = flag.NewFlagSet("default", flag.ContinueOnError) - bf = cli.StringSliceFlag{ - Name: "dumps", - Usage: "dumps", - } - assert.NoError(t, bf.Apply(fs)) - assert.NoError(t, fs.Parse([]string{"--dumps=" + fn})) - - c = cli.NewContext(app, fs, nil) - c.Context = ctx - - assert.NoError(t, testWriteGZ(fn, []byte(testHibpSample))) - assert.NoError(t, act.CheckDump(c.Context, false, []string{fn})) -} - -func testWriteGZ(fn string, buf []byte) error { - fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer func() { - _ = fh.Close() - }() - - gzw := gzip.NewWriter(fh) - defer func() { - _ = gzw.Close() - }() - - _, err = gzw.Write(buf) - return err -} - -func TestHIBPAPI(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - ctx := context.Background() - ctx = ctxutil.WithAlwaysYes(ctx, true) - - act := &hibp{ - gp: apimock.New(), - } - - c := gptest.CliCtxWithFlags(ctx, t, map[string]string{"api": "true"}) - - reqCnt := 0 - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - reqCnt++ - if reqCnt < 2 { - http.Error(w, "fake error", http.StatusInternalServerError) - return - } - if strings.TrimPrefix(r.URL.String(), "/range/") == "8843D" { - fmt.Fprintf(w, "8843D:1\n") // invalid - fmt.Fprintf(w, "7F92416211DE9EBB963FF4CE2812593287:3234879\n") // invalid - fmt.Fprintf(w, "7F92416211DE9EBB963FF4CE28125932878:\n") // invalid - fmt.Fprintf(w, "7F92416211DE9EBB963FF4CE28125932878\n") // invalid - fmt.Fprintf(w, "7F92416211DE9EBB963FF4CE28125932878:3234879\n") // valid - return - } - http.Error(w, "not found", http.StatusNotFound) - })) - defer ts.Close() - hibpapi.URL = ts.URL - - // test with one entry - assert.NoError(t, act.CheckAPI(c.Context, false)) - - // add another one - assert.NoError(t, act.gp.Set(ctx, "baz", &apimock.Secret{Buf: []byte("foobar")})) - assert.Error(t, act.CheckAPI(c.Context, false)) -} diff --git a/cmd/gopass-hibp/main.go b/cmd/gopass-hibp/main.go deleted file mode 100644 index 34a74011dc..0000000000 --- a/cmd/gopass-hibp/main.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - - "github.com/gopasspw/gopass/pkg/gopass/api" - "github.com/urfave/cli/v2" -) - -const ( - name = "gopass-hibp" -) - -var ( - // Version is the released version of gopass - version string -) - -func main() { - ctx := context.Background() - - // trap Ctrl+C and call cancel on the context - ctx, cancel := context.WithCancel(ctx) - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) - defer func() { - signal.Stop(sigChan) - cancel() - }() - go func() { - select { - case <-sigChan: - cancel() - case <-ctx.Done(): - } - }() - - gp, err := api.New(ctx) - if err != nil { - fmt.Printf("Failed to initialize gopass API: %s\n", err) - os.Exit(1) - } - - hibp := &hibp{ - gp: gp, - } - - app := cli.NewApp() - app.Name = name - app.Version = version - app.Usage = "haveibeenpwned.com leak checker for gopass" - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "api", - Usage: "Detect leaked passwords using the HIBPv2 API", - Description: "" + - "This command will decrypt all secrets and check the passwords against the public " + - "havibeenpwned.com v2 API.", - Action: func(c *cli.Context) error { - return hibp.CheckAPI(c.Context, c.Bool("force")) - }, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "Force checking secrets against the public API", - }, - }, - }, - { - Name: "dump", - Usage: "Detect leaked passwords using the HIBP SHA-1 dumps", - Description: "" + - "This command will decrypt all secrets and check the passwords against the " + - "havibeenpwned.com SHA-1 dumps (ordered by hash). " + - "To use the dumps you need to download the dumps from https://haveibeenpwned.com/passwords first. Be sure to grab the one that says '(ordered by hash)'. " + - "This is a very expensive operation, for advanced users. " + - "Most users should probably use the API. " + - "If you want to use the dumps you need to use 7z to extract the dump: 7z x pwned-passwords-ordered-2.0.txt.7z.", - Action: func(c *cli.Context) error { - return hibp.CheckDump(c.Context, c.Bool("force"), c.StringSlice("files")) - }, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "Force checking secrets against the dumps", - }, - &cli.StringSliceFlag{ - Name: "files", - Usage: "One or more HIBP v1/v2 dumps", - }, - }, - }, - } - - if err := app.RunContext(ctx, os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/gopass-jsonapi/.gitignore b/cmd/gopass-jsonapi/.gitignore deleted file mode 100644 index 14c71f39c5..0000000000 --- a/cmd/gopass-jsonapi/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -gopass-jsonapi -gopass-jsonapi-*-amd64 diff --git a/cmd/gopass-jsonapi/Makefile b/cmd/gopass-jsonapi/Makefile deleted file mode 100644 index 40c9c71d41..0000000000 --- a/cmd/gopass-jsonapi/Makefile +++ /dev/null @@ -1,195 +0,0 @@ -FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH))) -PKGS := $(shell go list ./... | grep -v /tests | grep -v /xcpb | grep -v /gpb) -GOFILES_NOVENDOR := $(shell find . -name vendor -prune -o -type f -name '*.go' -not -name '*.pb.go' -print) -GOFILES_BUILD := $(shell find . -type f -name '*.go' -not -name '*_test.go') -PROTOFILES := $(shell find . -name vendor -prune -o -type f -name '*.proto' -print) -GOPASS_VERSION ?= $(shell cat VERSION) -GOPASS_OUTPUT ?= gopass-jsonapi -GOPASS_REVISION := $(shell cat COMMIT 2>/dev/null || git rev-parse --short=8 HEAD) -# Support reproducible builds by embedding date according to SOURCE_DATE_EPOCH if present -DATE := $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" '+%FT%T%z' 2>/dev/null || date -u '+%FT%T%z') -BUILDFLAGS_NOPIE := -ldflags="-s -w -X main.version=$(GOPASS_VERSION) -X main.commit=$(GOPASS_REVISION) -X main.date=$(DATE)" -gcflags="-trimpath=$(GOPATH)" -asmflags="-trimpath=$(GOPATH)" -BUILDFLAGS ?= $(BUILDFLAGS_NOPIE) -buildmode=pie -TESTFLAGS ?= -PWD := $(shell pwd) -PREFIX ?= $(GOPATH) -BINDIR ?= $(PREFIX)/bin -GO := GO111MODULE=on go -GOOS ?= $(shell go version | cut -d' ' -f4 | cut -d'/' -f1) -GOARCH ?= $(shell go version | cut -d' ' -f4 | cut -d'/' -f2) -TAGS ?= netgo -export GO111MODULE=on - -OK := $(shell tput setaf 6; echo ' [OK]'; tput sgr0;) - -all: build completion -build: $(GOPASS_OUTPUT) -completion: $(BASH_COMPLETION_OUTPUT) $(FISH_COMPLETION_OUTPUT) $(ZSH_COMPLETION_OUTPUT) -travis: sysinfo crosscompile build install fulltest codequality completion manifests full -travis-osx: sysinfo build install fulltest completion manifests full -travis-windows: sysinfo build install fulltest-nocover completion manifests full - -sysinfo: - @echo ">> SYSTEM INFORMATION" - @echo -n " PLATFORM: $(shell uname -a)" - @printf '%s\n' '$(OK)' - @echo -n " PWD: : $(shell pwd)" - @printf '%s\n' '$(OK)' - @echo -n " GO : $(shell go version)" - @printf '%s\n' '$(OK)' - @echo -n " BUILDFLAGS: $(BUILDFLAGS)" - @printf '%s\n' '$(OK)' - @echo -n " GIT : $(shell git version)" - @printf '%s\n' '$(OK)' - @echo -n " GPG1 : $(shell which gpg) $(shell gpg --version | head -1)" - @printf '%s\n' '$(OK)' - @echo -n " GPG2 : $(shell which gpg2) $(shell gpg2 --version | head -1)" - @printf '%s\n' '$(OK)' - @echo -n " GPG-Agent : $(shell which gpg-agent) $(shell gpg-agent --version | head -1)" - @printf '%s\n' '$(OK)' - -clean: - @echo -n ">> CLEAN" - @$(GO) clean -i ./... - @rm -f ./coverage-all.html - @rm -f ./coverage-all.out - @rm -f ./coverage.out - @find . -type f -name "coverage.out" -delete - @rm -f gopass_*.deb - @rm -f gopass-*.pkg.tar.xz - @rm -f gopass-*.rpm - @rm -f gopass-*.tar.bz2 - @rm -f gopass-*.tar.gz - @rm -f gopass-*-* - @rm -f tests/tests - @rm -rf dist/* - @rm -f *.completion - @printf '%s\n' '$(OK)' - -$(GOPASS_OUTPUT): $(GOFILES_BUILD) - @echo -n ">> BUILD, version = $(GOPASS_VERSION)/$(GOPASS_REVISION), output = $@" - @$(GO) build -o $@ $(BUILDFLAGS) - @printf '%s\n' '$(OK)' - -fulltest: $(GOPASS_OUTPUT) - @echo ">> TEST, \"full-mode\": race detector off, build tags: xc" - @echo "mode: atomic" > coverage-all.out - @$(foreach pkg, $(PKGS),\ - echo -n " ";\ - go test -run '(Test|Example)' $(BUILDFLAGS) $(TESTFLAGS) -coverprofile=coverage.out -covermode=atomic $(pkg) -tags 'xc' || exit 1;\ - tail -n +2 coverage.out >> coverage-all.out;) - @$(GO) tool cover -html=coverage-all.out -o coverage-all.html - -fulltest-nocover: $(GOPASS_OUTPUT) - @echo ">> TEST, \"full-mode-no-coverage\": race detector off, build tags: xc" - @echo "mode: atomic" > coverage-all.out - @$(foreach pkg, $(PKGS),\ - echo -n " ";\ - go test -run '(Test|Example)' $(BUILDFLAGS) $(TESTFLAGS) $(pkg) -tags 'xc' || exit 1;) - -racetest: $(GOPASS_OUTPUT) - @echo ">> TEST, \"full-mode\": race detector on" - @echo "mode: atomic" > coverage-all.out - @$(foreach pkg, $(PKGS),\ - echo -n " ";\ - go test -run '(Test|Example)' $(BUILDFLAGS) $(TESTFLAGS) -race -coverprofile=coverage.out -covermode=atomic $(pkg) -tags 'xc' || exit 1;\ - tail -n +2 coverage.out >> coverage-all.out;) - @$(GO) tool cover -html=coverage-all.out -o coverage-all.html - -test: $(GOPASS_OUTPUT) - @echo ">> TEST, \"fast-mode\": race detector off" - @$(foreach pkg, $(PKGS),\ - echo -n " ";\ - $(GO) test -test.short -run '(Test|Example)' $(BUILDFLAGS) $(TESTFLAGS) $(pkg) -tags 'xc' || exit 1) - -crosscompile: - @echo -n ">> CROSSCOMPILE linux/amd64" - @GOOS=linux GOARCH=amd64 $(GO) build -o $(GOPASS_OUTPUT)-linux-amd64 - @printf '%s\n' '$(OK)' - @echo -n ">> CROSSCOMPILE darwin/amd64" - @GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOPASS_OUTPUT)-darwin-amd64 - @printf '%s\n' '$(OK)' - @echo -n ">> CROSSCOMPILE windows/amd64" - @GOOS=windows GOARCH=amd64 $(GO) build -o $(GOPASS_OUTPUT)-windows-amd64 - @printf '%s\n' '$(OK)' - -full: - @echo -n ">> COMPILE linux/amd64 xc" - $(GO) build -o $(GOPASS_OUTPUT)-linux-amd64-full -tags "xc" - -manifests: $(GOPASS_OUTPUT) - @./gopass-jsonapi --yes jsonapi configure --path=. --manifest-path=manifest-chrome.json --browser=chrome --gopass-path=gopass --print=false - @./gopass-jsonapi --yes jsonapi configure --path=. --manifest-path=manifest-chromium.json --browser=chromium --gopass-path=gopass --print=false - @./gopass-jsonapi --yes jsonapi configure --path=. --manifest-path=manifest-firefox.json --browser=firefox --gopass-path=gopass --print=false - -codequality: - @echo ">> CODE QUALITY" - - @echo -n " REVIVE " - @which revive > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/mgechev/revive; \ - fi - @revive -formatter friendly -exclude vendor/... ./... - @printf '%s\n' '$(OK)' - - @echo -n " FMT " - @$(foreach gofile, $(GOFILES_NOVENDOR),\ - out=$$(gofmt -s -l -d -e $(gofile) | tee /dev/stderr); if [ -n "$$out" ]; then exit 1; fi;) - @printf '%s\n' '$(OK)' - - @echo -n " CLANGFMT " - @$(foreach pbfile, $(PROTOFILES),\ - if [ $$(clang-format -output-replacements-xml $(pbfile) | wc -l) -gt 3 ]; then exit 1; fi;) - @printf '%s\n' '$(OK)' - - @echo -n " VET " - @$(GO) vet ./... - @printf '%s\n' '$(OK)' - - @echo -n " CYCLO " - @which gocyclo > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/fzipp/gocyclo; \ - fi - @$(foreach gofile, $(GOFILES_NOVENDOR),\ - gocyclo -over 22 $(gofile) || exit 1;) - @printf '%s\n' '$(OK)' - - @echo -n " LINT " - @which golint > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u golang.org/x/lint/golint; \ - fi - @$(foreach pkg, $(PKGS),\ - golint -set_exit_status $(pkg) || exit 1;) - @printf '%s\n' '$(OK)' - - @echo -n " INEFF " - @which ineffassign > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/gordonklaus/ineffassign; \ - fi - @ineffassign . || exit 1 - @printf '%s\n' '$(OK)' - - @echo -n " SPELL " - @which misspell > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/client9/misspell/cmd/misspell; \ - fi - @$(foreach gofile, $(GOFILES_NOVENDOR),\ - misspell --error $(gofile) || exit 1;) - @printf '%s\n' '$(OK)' - - @echo -n " STATICCHECK " - @which staticcheck > /dev/null; if [ $$? -ne 0 ]; then \ - $(GO) get -u honnef.co/go/tools/cmd/staticcheck; \ - fi - @staticcheck $(PKGS) || exit 1 - @printf '%s\n' '$(OK)' - -fmt: - @gofmt -s -l -w $(GOFILES_NOVENDOR) - -fuzz-jsonapi: - mkdir -p workdir/jsonapi/corpus - go-fuzz-build github.com/gopasspw/gopass/utils/jsonapi - go-fuzz -bin=jsonapi-fuzz.zip -workdir=workdir/jsonapi - -.PHONY: clean build completion install sysinfo crosscompile test codequality release diff --git a/cmd/gopass-jsonapi/internal/jsonapi/api.go b/cmd/gopass-jsonapi/internal/jsonapi/api.go deleted file mode 100644 index 35f23c404d..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/api.go +++ /dev/null @@ -1,36 +0,0 @@ -package jsonapi - -import ( - "context" - "io" - - "github.com/blang/semver" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass" -) - -// API type holding store and context -type API struct { - Store gopass.Store - Reader io.Reader - Writer io.Writer - Version semver.Version -} - -// ReadAndRespond a single message -func (api *API) ReadAndRespond(ctx context.Context) error { - ctx = ctxutil.WithHidden(ctx, true) - message, err := readMessage(api.Reader) - if message == nil || err != nil { - return err - } - - return api.respondMessage(ctx, message) -} - -// RespondError sends err as JSON response -func (api *API) RespondError(err error) error { - return sendSerializedJSONMessage(errorResponse{ - Error: err.Error(), - }, api.Writer) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/api_test.go b/cmd/gopass-jsonapi/internal/jsonapi/api_test.go deleted file mode 100644 index 5244ec95a2..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/api_test.go +++ /dev/null @@ -1,385 +0,0 @@ -package jsonapi - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "regexp" - "strings" - "testing" - - "github.com/blang/semver" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/debug" - "github.com/gopasspw/gopass/pkg/gopass" - "github.com/gopasspw/gopass/pkg/gopass/apimock" - "github.com/gopasspw/gopass/pkg/gopass/secrets/secparse" - "github.com/gopasspw/gopass/pkg/otp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type storedSecret struct { - Name []string - Secret gopass.Secret -} - -func TestRespondMessageBrokenInput(t *testing.T) { - // Garbage input - runRespondRawMessage(t, "1234Xabcd", "", "incomplete message read", []storedSecret{}) - - // Too short to determine message size - runRespondRawMessage(t, " ", "", "not enough bytes read to determine message size", []storedSecret{}) - - // Empty message - runRespondMessage(t, "", "", "failed to unmarshal JSON message: unexpected end of JSON input", []storedSecret{}) - - // Empty object - runRespondMessage(t, "{}", "", "unknown message of type ", []storedSecret{}) -} - -func TestRespondGetVersion(t *testing.T) { - runRespondMessage(t, - `{"type": "getVersion"}`, - `{"version":"1.2.3-test","major":1,"minor":2,"patch":3}`, - "", - nil) -} - -func newSec(t *testing.T, in string) gopass.Secret { - debug.Log("in: %s", in) - sec, err := secparse.Parse([]byte(in)) - require.NoError(t, err) - return sec -} - -func TestRespondMessageQuery(t *testing.T) { - secrets := []storedSecret{ - {[]string{"awesomePrefix", "foo", "bar"}, newSec(t, "20\n")}, - {[]string{"awesomePrefix", "fixed", "secret"}, newSec(t, "moar\n")}, - {[]string{"awesomePrefix", "fixed", "yamllogin"}, newSec(t, "thesecret\n---\nlogin: muh")}, - {[]string{"awesomePrefix", "fixed", "yamlother"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"awesomePrefix", "some.other.host", "other"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"awesomePrefix", "b", "some.other.host"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"awesomePrefix", "evilsome.other.host"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"evilsome.other.host", "something"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"awesomePrefix", "other.host", "other"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"somename", "github.com"}, newSec(t, "thesecret\n---\nother: meh")}, - {[]string{"login_entry"}, newSec(t, `thepass ---- -login: thelogin -ignore: me -login_fields: - first: 42 - second: ok -nologin_fields: - subentry: 123`)}, - {[]string{"invalid_login_entry"}, newSec(t, `thepass ---- -login: thelogin -ignore: me -login_fields: "invalid"`)}, - } - - for _, tc := range []struct { - desc string - in string - out string - }{ - { - desc: "query for keys without any matches", - in: `{"type":"query","query":"notfound"}`, - out: `\[\]`, - }, - { - desc: "query for keys with matching one", - in: `{"type":"query","query":"foo"}`, - out: `\["awesomePrefix/foo/bar"\]`, - }, - { - desc: "query for keys with matching multiple", - in: `{"type":"query","query":"yaml"}`, - out: `\["awesomePrefix/fixed/yamllogin","awesomePrefix/fixed/yamlother"\]`, - }, - { - desc: "query for host", - in: `{"type":"queryHost","host":"find.some.other.host"}`, - out: `\["awesomePrefix/b/some.other.host","awesomePrefix/some.other.host/other"\]`, - }, - { - desc: "query for host not matches parent domain", - in: `{"type":"queryHost","host":"other.host"}`, - out: `\["awesomePrefix/other.host/other"\]`, - }, - { - desc: "query for host is query has different domain appended does not return partial match", - in: `{"type":"queryHost","host":"some.other.host.different.domain"}`, - out: `\[\]`, - }, - { - desc: "query returns result with public suffix at the end", - in: `{"type":"queryHost","host":"github.com"}`, - out: `\["somename/github.com"\]`, - }, - { - desc: "get username and password for key without value in yaml", - in: `{"type":"getLogin","entry":"awesomePrefix/fixed/secret"}`, - out: `{"username":"secret","password":"moar"}`, - }, - { - desc: "get username and password for key with login in yaml", - in: `{"type":"getLogin","entry":"awesomePrefix/fixed/yamllogin"}`, - out: `{"username":"muh","password":"thesecret"}`, - }, - { - desc: "get username and password for key with no login in yaml (fallback)", - in: `{"type":"getLogin","entry":"awesomePrefix/fixed/yamlother"}`, - out: `{"username":"yamlother","password":"thesecret"}`, - }, - { - desc: "get entry with invalid login fields", - in: `{"type":"getLogin","entry":"invalid_login_entry"}`, - out: `{"username":"thelogin","password":"thepass"}`, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - runRespondMessage(t, tc.in, tc.out, "", secrets) - }) - } - -} - -func TestRespondMessageGetData(t *testing.T) { - totpSuffix := "//totp/github-fake-account?secret=rpna55555qyho42j" - totpURL := "otpauth:" + totpSuffix - totpSecret := newSec(t, "totp_are_cool\n"+totpURL) - - secrets := []storedSecret{ - {[]string{"totp"}, totpSecret}, - {[]string{"foo"}, newSec(t, "20\nhallo: welt")}, - {[]string{"bar"}, newSec(t, "20\n---\nlogin: muh")}, - {[]string{"complex"}, newSec(t, `20 ---- -login: hallo -number: 42 -sub: - subentry: 123 -`)}, - } - - totp, _, err := otp.Calculate("_", totpSecret) - if err != nil { - assert.NoError(t, err) - } - expectedTotp := totp.OTP() - - runRespondMessage(t, - `{"type":"getData","entry":"foo"}`, - `{"hallo":"welt"}`, - "", secrets) - - runRespondMessage(t, - `{"type":"getData","entry":"bar"}`, - `{"login":"muh"}`, - "", secrets) - runRespondMessage(t, - `{"type":"getData","entry":"complex"}`, - `{"login":"hallo","number":"42","sub":"map.subentry:123."}`, - "", secrets) - - runRespondMessage(t, - `{"type":"getData","entry":"totp"}`, - fmt.Sprintf(`{"current_totp":"%s"}`, expectedTotp), - "", secrets) -} - -func TestRespondMessageCreate(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - runRespondMessages(t, nil, nil) - - t.Skip("broken") // TODO fix this - - secrets := []storedSecret{ - {[]string{"awesomePrefix", "overwrite", "me"}, newSec(t, "20\n")}, - } - - // store new secret with given password - runRespondMessages(t, []verifiedRequest{ - { - `{"type":"create","entry_name":"prefix/stored","login":"myname","password":"mypass","length":16,"generate":false,"use_symbols":true}`, - `{"username":"myname","password":"mypass"}`, - "", - }, - { - `{"type":"getLogin","entry":"prefix/stored"}`, - `{"username":"myname","password":"mypass"}`, - "", - }, - }, secrets) - - // generate new secret with given length and without symbols - runRespondMessages(t, []verifiedRequest{ - { - `{"type":"create","entry_name":"prefix/generated","login":"myname","password":"","length":12,"generate":true,"use_symbols":false}`, - `{"username":"myname","password":"\w{12}"}`, - "", - }, - { - `{"type":"getLogin","entry":"prefix/generated"}`, - `{"username":"myname","password":"\w{12}"}`, - "", - }, - }, secrets) - - // generate new secret with given length and with symbols - runRespondMessages(t, []verifiedRequest{ - { - `{"type":"create","entry_name":"prefix/generated","login":"myname","password":"","length":12,"generate":true,"use_symbols":true}`, - `{"username":"myname","password":".{12,}"}`, - "", - }, - { - `{"type":"getLogin","entry":"prefix/generated"}`, - `{"username":"myname","password":".{12,}"}`, - "", - }, - }, secrets) - - // store already existing secret - runRespondMessage(t, - `{"type":"create","entry_name":"awesomePrefix/overwrite/me","login":"myname","password":"mypass","length":16,"generate":false,"use_symbols":true}`, - "", - "secret awesomePrefix/overwrite/me already exists", - secrets) -} - -func TestCopyToClipboard(t *testing.T) { - secrets := []storedSecret{ - {[]string{"foo", "bar"}, newSec(t, "20\n")}, - {[]string{"yamllogin"}, newSec(t, "thesecret\n---\nlogin: muh")}, - } - - // copy nonexisting entry returns error - runRespondMessage(t, - `{"type": "copyToClipboard","entry":"doesnotexist"}`, - ``, - "failed to get secret: Entry is not in the password store", - secrets) - - // copy existing entry - runRespondMessage(t, - `{"type": "copyToClipboard","entry":"foo/bar"}`, - `{"status":"ok"}`, - "", - secrets) - - // copy nonexisting subkey - runRespondMessage(t, - `{"type": "copyToClipboard","entry":"foo/bar","key":"baz"}`, - ``, - "failed to get secret sub entry: key not found in YAML document", - secrets) - - // copy existing subkey - runRespondMessage(t, - `{"type": "copyToClipboard","entry":"yamllogin","key":"login"}`, - `{"status":"ok"}`, - "", - secrets) -} - -func writeMessageWithLength(message string) string { - buffer := bytes.NewBuffer([]byte{}) - _ = binary.Write(buffer, binary.LittleEndian, uint32(len(message))) - _, _ = buffer.WriteString(message) - return buffer.String() -} - -func runRespondMessage(t *testing.T, inputStr, outputRegexpStr, errorStr string, secrets []storedSecret) { - inputMessageStr := writeMessageWithLength(inputStr) - runRespondRawMessage(t, inputMessageStr, outputRegexpStr, errorStr, secrets) -} - -func runRespondRawMessage(t *testing.T, inputStr, outputRegexpStr, errorStr string, secrets []storedSecret) { - runRespondRawMessages(t, []verifiedRequest{{inputStr, outputRegexpStr, errorStr}}, secrets) -} - -type verifiedRequest struct { - InputStr string - OutputRegexpStr string - ErrorStr string -} - -func runRespondMessages(t *testing.T, requests []verifiedRequest, secrets []storedSecret) { - for i, request := range requests { - requests[i].InputStr = writeMessageWithLength(request.InputStr) - } - runRespondRawMessages(t, requests, secrets) -} - -func runRespondRawMessages(t *testing.T, requests []verifiedRequest, secrets []storedSecret) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - ctx = ctxutil.WithNotifications(ctx, false) - - store := apimock.New() - require.NotNil(t, store) - assert.NoError(t, populateStore(ctx, store, secrets)) - - for _, request := range requests { - var inbuf bytes.Buffer - var outbuf bytes.Buffer - - api := API{ - store, - &inbuf, - &outbuf, - semver.MustParse("1.2.3-test"), - } - - _, err := inbuf.Write([]byte(request.InputStr)) - assert.NoError(t, err) - - err = api.ReadAndRespond(ctx) - if len(request.ErrorStr) > 0 { - require.Error(t, err) - assert.Equal(t, len(outbuf.String()), 0) - continue - } - assert.NoError(t, err) - outputMessage := readAndVerifyMessageLength(t, outbuf.Bytes()) - assert.NotEqual(t, "", request.OutputRegexpStr, "Empty string would match any output") - assert.Regexp(t, regexp.MustCompile(request.OutputRegexpStr), outputMessage) - } -} - -func populateStore(ctx context.Context, s gopass.Store, secrets []storedSecret) error { - for _, sec := range secrets { - if err := s.Set(ctx, strings.Join(sec.Name, "/"), sec.Secret); err != nil { - return err - } - } - return nil -} - -func readAndVerifyMessageLength(t *testing.T, rawMessage []byte) string { - input := bytes.NewReader(rawMessage) - lenBytes := make([]byte, 4) - - _, err := input.Read(lenBytes) - assert.NoError(t, err) - - length, err := getMessageLength(lenBytes) - assert.NoError(t, err) - assert.Equal(t, len(rawMessage)-4, length) - - msgBytes := make([]byte, length) - _, err = input.Read(msgBytes) - assert.NoError(t, err) - return string(msgBytes) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/helpers.go b/cmd/gopass-jsonapi/internal/jsonapi/helpers.go deleted file mode 100644 index 248c056f9b..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/helpers.go +++ /dev/null @@ -1,41 +0,0 @@ -package jsonapi - -import ( - "regexp" - "strings" - - "golang.org/x/net/publicsuffix" -) - -// isPublicSuffix returns true if this host is one users can or could directly -// register names -func isPublicSuffix(host string) bool { - suffix, _ := publicsuffix.PublicSuffix(host) - return host == suffix -} - -func regexSafeLower(str string) string { - return regexp.QuoteMeta(strings.ToLower(str)) -} - -func convertMixedMapInterfaces(i interface{}) interface{} { - switch x := i.(type) { - case map[string]interface{}: - stringMap := map[string]interface{}{} - for k, v := range x { - stringMap[k] = convertMixedMapInterfaces(v) - } - return stringMap - case map[interface{}]interface{}: - stringMap := map[string]interface{}{} - for k, v := range x { - stringMap[k.(string)] = convertMixedMapInterfaces(v) - } - return stringMap - case []interface{}: - for i, v := range x { - x[i] = convertMixedMapInterfaces(v) - } - } - return i -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/helpers_test.go b/cmd/gopass-jsonapi/internal/jsonapi/helpers_test.go deleted file mode 100644 index f7083a46be..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/helpers_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package jsonapi - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_checkIsPublicSuffix(t *testing.T) { - a := assert.New(t) - - a.True(isPublicSuffix("co.uk")) - a.False(isPublicSuffix("amazon.co.uk")) - a.True(isPublicSuffix("dyndns.org")) - a.False(isPublicSuffix("foo.dyndns.org")) -} - -func Test_regexSafeLower(t *testing.T) { - a := assert.New(t) - a.Equal("blabla", regexSafeLower("BlaBLA")) - a.Equal("\\[injected\\]\\*", regexSafeLower("[injected]*")) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/manifest.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/manifest.go deleted file mode 100644 index 04e1e6db8d..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/manifest.go +++ /dev/null @@ -1,87 +0,0 @@ -package manifest - -import ( - "encoding/json" - "fmt" -) - -var ( - // DefaultBrowser to select when no browser is specified - DefaultBrowser = "chrome" - - // Name is the name of the manifest - Name = "com.justwatch.gopass" - description = "Gopass wrapper to search and return passwords" - connectionType = "stdio" - chromeOrigins = []string{ - "chrome-extension://kkhfnlkhiapbiehimabddjbimfaijdhk/", // gopassbridge - } - firefoxOrigins = []string{ - "{eec37db0-22ad-4bf1-9068-5ae08df8c7e9}", // gopassbridge - } -) - -func getManifestContent(browser, wrapperPath string) ([]byte, error) { - switch browser { - case "firefox": - return newFirefoxManifest(wrapperPath).Format() - case "chrome": - fallthrough - case "brave": - fallthrough - case "vivaldi": - fallthrough - case "iridium": - fallthrough - case "slimjet": - fallthrough - case "chromium": - return newChromeManifest(wrapperPath).Format() - default: - return nil, fmt.Errorf("no manifest template for browser %s", browser) - } -} - -type chromeManifest struct { - Name string `json:"name"` - Description string `json:"description"` - Path string `json:"path"` - Type string `json:"type"` - AllowedOrigins []string `json:"allowed_origins"` -} - -func newChromeManifest(path string) *chromeManifest { - return &chromeManifest{ - Name: Name, - Type: connectionType, - Path: path, - Description: description, - AllowedOrigins: chromeOrigins, - } -} - -func (c *chromeManifest) Format() ([]byte, error) { - return json.MarshalIndent(c, "", " ") -} - -type firefoxManifest struct { - Name string `json:"name"` - Description string `json:"description"` - Path string `json:"path"` - Type string `json:"type"` - AllowedExtensions []string `json:"allowed_extensions"` -} - -func newFirefoxManifest(path string) *firefoxManifest { - return &firefoxManifest{ - Name: Name, - Type: connectionType, - Path: path, - Description: description, - AllowedExtensions: firefoxOrigins, - } -} - -func (f *firefoxManifest) Format() ([]byte, error) { - return json.MarshalIndent(f, "", " ") -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others.go deleted file mode 100644 index 1c0432cd4c..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others.go +++ /dev/null @@ -1,122 +0,0 @@ -// +build !windows - -package manifest - -import ( - "fmt" - "path/filepath" - "runtime" - "sort" - - "github.com/mitchellh/go-homedir" -) - -var ( - // WrapperName is the name of the gopass wrapper - WrapperName = "gopass_wrapper.sh" - - globalManifestPath = map[string]map[string]string{ - "darwin": { - "firefox": "/Library/Application Support/Mozilla/NativeMessagingHosts", - "chrome": "/Library/Google/Chrome/NativeMessagingHosts", - "chromium": "/Library/Application Support/Chromium/NativeMessagingHosts", - "brave": "/Library/Application Support/Brave/NativeMessagingHosts", - "vivaldi": "/Library/Application Support/Vivaldi/NativeMessagingHosts", - "iridium": "/Library/Application Support/Iridium/NativeMessagingHosts", - "slimjet": "/Library/Application Support/Slimjet/NativeMessagingHosts", - }, - "linux": { - "firefox": "mozilla/native-messaging-hosts", // will be prefixed with the appropriate lib path - "chrome": "/etc/opt/chrome/native-messaging-hosts", - "chromium": "/etc/chromium/native-messaging-hosts", - "brave": "/etc/opt/chrome/native-messaging-hosts", - "vivaldi": "/etc/opt/vivaldi/native-messaging-hosts", - "iridium": "/etc/iridium-browser/native-messaging-hosts", - "slimjet": "/etc/opt/slimjet/native-messaging-hosts", - }, - } - - manifestPath = map[string]map[string]string{ - "darwin": { - "firefox": "~/Library/Application Support/Mozilla/NativeMessagingHosts", - "chrome": "~/Library/Application Support/Google/Chrome/NativeMessagingHosts", - "chromium": "~/Library/Application Support/Chromium/NativeMessagingHosts", - "brave": "~/Library/Application Support/Brave/NativeMessagingHosts", - "vivaldi": "~/Library/Application Support/Vivaldi/NativeMessagingHosts", - "iridium": "~/Library/Application Support/Iridium/NativeMessagingHosts", - "slimjet": "~/Library/Application Support/Slimjet/NativeMessagingHosts", - }, - "linux": { - "firefox": "~/.mozilla/native-messaging-hosts", - "chrome": "~/.config/google-chrome/NativeMessagingHosts", - "chromium": "~/.config/chromium/NativeMessagingHosts", - "brave": "~/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts", - "vivaldi": "~/.config/vivaldi/NativeMessagingHosts", - "iridium": "~/.config/iridium/NativeMessagingHosts", - "slimjet": "~/.config/slimjet/NativeMessagingHosts", - }, - } -) - -// ValidBrowser returns true if the given browser is supported on this platform -func ValidBrowser(name string) bool { - _, found := manifestPath[runtime.GOOS][name] - return found -} - -// ValidBrowsers are all browsers for which the manifest can be currently installed -func ValidBrowsers() []string { - keys := make([]string, 0, len(manifestPath[runtime.GOOS])) - for k := range manifestPath[runtime.GOOS] { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - -// Path returns the manifest file path -func Path(browser, libpath string, globalInstall bool) (string, error) { - location, err := getLocation(browser, libpath, globalInstall) - if err != nil { - return "", err - } - - expanded, err := homedir.Expand(location) - if err != nil { - return "", err - } - - return filepath.Join(expanded, Name+".json"), nil -} - -// getLocation returns only the manifest path -func getLocation(browser, libpath string, globalInstall bool) (string, error) { - if globalInstall { - return getGlobalLocation(browser, libpath) - } - - pm, found := manifestPath[runtime.GOOS] - if !found { - return "", fmt.Errorf("platform %s is currently not supported", runtime.GOOS) - } - path, found := pm[browser] - if !found { - return "", fmt.Errorf("browser %s on %s is currently not supported", browser, runtime.GOOS) - } - return path, nil -} - -func getGlobalLocation(browser, libpath string) (string, error) { - pm, found := globalManifestPath[runtime.GOOS] - if !found { - return "", fmt.Errorf("platform %s is currently not supported", runtime.GOOS) - } - path, found := pm[browser] - if !found { - return "", fmt.Errorf("browser %s on %s is currently not supported", browser, runtime.GOOS) - } - if browser == "firefox" { - path = libpath + "/" + path - } - return path, nil -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others_test.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others_test.go deleted file mode 100644 index 629eaaea2b..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_others_test.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !windows - -package manifest - -import "testing" - -func TestManifest(t *testing.T) { - if _, err := getLocation("foobar", "", false); err == nil { - t.Error("browser should not exist") - } -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_test.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_test.go deleted file mode 100644 index 6755cdb36a..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package manifest - -import ( - "os" - "runtime" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRender(t *testing.T) { - // windows: C:\Users\johndoe\AppData... - // *nix: /tmp - binDir := os.TempDir() - - manifestGolden := `{ - "name": "com.justwatch.gopass", - "description": "Gopass wrapper to search and return passwords", - "path": "` + strings.Replace(binDir, "\\", "\\\\", -1) + `", - "type": "stdio", - "allowed_origins": [ - "chrome-extension://kkhfnlkhiapbiehimabddjbimfaijdhk/" - ] -}` - w, m, err := Render("chrome", binDir, "gopass-jsonapi", true) - assert.NoError(t, err) - assert.Equal(t, wrapperGolden, string(w)) - assert.Equal(t, manifestGolden, string(m)) -} - -func TestValidBrowser(t *testing.T) { - for _, b := range []string{"chrome", "chromium", "firefox"} { - assert.Equal(t, true, ValidBrowser(b)) - } -} - -func TestValidBrowsers(t *testing.T) { - if runtime.GOOS == "windows" { - assert.Equal(t, []string{"chrome", "chromium", "firefox"}, ValidBrowsers()) - } else { - assert.Equal(t, []string{"brave", "chrome", "chromium", "firefox", "iridium", "slimjet", "vivaldi"}, ValidBrowsers()) - } - -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_windows.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_windows.go deleted file mode 100644 index 2aa325741f..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/setup_windows.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build windows - -package manifest - -import ( - "fmt" - "runtime" - "sort" -) - -var ( - // NativeHostExeName is the name of the gopass wrapper binary - NativeHostExeName = "gopass_native_host.exe" - - // windows stores the path to the manifests in the registry - registryPaths = map[string]string{ - "firefox": `Software\Mozilla\NativeMessagingHosts\` + Name, - "chrome": `Software\Google\Chrome\NativeMessagingHosts\` + Name, - "chromium": `Software\Google\Chrome\NativeMessagingHosts\` + Name, - } -) - -// ValidBrowser returns true if the given browser is supported on this platform -func ValidBrowser(name string) bool { - _, found := registryPaths[name] - return found -} - -// ValidBrowsers are all browsers for which the manifest can be currently installed -func ValidBrowsers() []string { - keys := make([]string, 0, len(registryPaths)) - for k := range registryPaths { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - -// GetRegistryPath returns the relative registry path to use in the windows registry key -func GetRegistryPath(browser string) (string, error) { - path, found := registryPaths[browser] - if !found { - return "", fmt.Errorf("browser %s on %s is currently not supported", browser, runtime.GOOS) - } - return path, nil -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper.go deleted file mode 100644 index 14b3477e7b..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper.go +++ /dev/null @@ -1,79 +0,0 @@ -package manifest - -import ( - "bytes" - "html/template" - "os" - "os/exec" - "strings" - - "github.com/mitchellh/go-homedir" -) - -const wrapperTemplate = `#!/bin/sh - -if [ -f ~/.gpg-agent-info ] && [ -n "$(pgrep gpg-agent)" ]; then - source ~/.gpg-agent-info - export GPG_AGENT_INFO -else - eval $(gpg-agent --daemon) -fi - -export PATH="$PATH:/usr/local/bin" # required on MacOS/brew -export PATH="$PATH:/usr/local/MacGPG2/bin" # required on MacOS/GPGTools GPGSuite -export GPG_TTY="$(tty)" - -{{ .Gopass }} listen - -exit $? -` - -// Render returns the rendered wrapper and manifest -func Render(browser, wrapperPath, binPath string, global bool) ([]byte, []byte, error) { - mf, err := getManifestContent(browser, wrapperPath) - if err != nil { - return nil, nil, err - } - - if binPath == "" { - binPath = gopassPath(global) - } - wrap, err := getWrapperContent(binPath) - if err != nil { - return nil, nil, err - } - - return wrap, mf, nil -} - -func getWrapperContent(gopassPath string) ([]byte, error) { - tmpl, err := template.New("").Parse(wrapperTemplate) - if err != nil { - return nil, err - } - - buf := &bytes.Buffer{} - err = tmpl.Execute( - buf, - struct { - Gopass string - }{ - Gopass: gopassPath, - }, - ) - return buf.Bytes(), err -} - -func gopassPath(global bool) string { - if !global { - if hd, err := homedir.Dir(); err == nil { - if gpp, err := os.Executable(); err == nil && strings.HasPrefix(gpp, hd) { - return gpp - } - } - } - if gpp, err := exec.LookPath("gopass-jsonapi"); err == nil { - return gpp - } - return "gopass-jsonapi" -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper_test.go b/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper_test.go deleted file mode 100644 index c4c9133bb8..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/manifest/wrapper_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package manifest - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - wrapperGolden = `#!/bin/sh - -if [ -f ~/.gpg-agent-info ] && [ -n "$(pgrep gpg-agent)" ]; then - source ~/.gpg-agent-info - export GPG_AGENT_INFO -else - eval $(gpg-agent --daemon) -fi - -export PATH="$PATH:/usr/local/bin" # required on MacOS/brew -export PATH="$PATH:/usr/local/MacGPG2/bin" # required on MacOS/GPGTools GPGSuite -export GPG_TTY="$(tty)" - -gopass-jsonapi listen - -exit $? -` -) - -func TestWrapperContent(t *testing.T) { - b, err := getWrapperContent("gopass-jsonapi") - require.NoError(t, err) - assert.Equal(t, wrapperGolden, string(b)) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/messages.go b/cmd/gopass-jsonapi/internal/jsonapi/messages.go deleted file mode 100644 index 35dbd8148d..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/messages.go +++ /dev/null @@ -1,142 +0,0 @@ -package jsonapi - -import ( - "bufio" - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" -) - -type messageType struct { - Type string `json:"type"` -} - -type queryMessage struct { - Query string `json:"query"` -} - -type queryHostMessage struct { - Host string `json:"host"` -} - -type getLoginMessage struct { - Entry string `json:"entry"` -} - -type copyToClipboard struct { - Entry string `json:"entry"` - Key string `json:"key"` -} - -type loginResponse struct { - Username string `json:"username"` - Password string `json:"password"` - LoginFields map[string]interface{} `json:"login_fields,omitempty"` -} - -type getDataMessage struct { - Entry string `json:"entry"` -} - -type getVersionMessage struct { - Version string `json:"version"` - Major uint64 `json:"major"` - Minor uint64 `json:"minor"` - Patch uint64 `json:"patch"` -} - -type createEntryMessage struct { - Name string `json:"entry_name"` - Login string `json:"login"` - Password string `json:"password"` - PasswordLength int `json:"length"` - Generate bool `json:"generate"` - UseSymbols bool `json:"use_symbols"` -} - -type statusResponse struct { - Status string `json:"status"` -} - -type errorResponse struct { - Error string `json:"error"` -} - -func readMessage(r io.Reader) ([]byte, error) { - input := bufio.NewReader(r) - lenBytes := make([]byte, 4) - count, err := input.Read(lenBytes) - if err != nil { - return nil, eofReturn(err) - } - if count != 4 { - return nil, fmt.Errorf("not enough bytes read to determine message size") - } - - length, err := getMessageLength(lenBytes) - if err != nil { - return nil, err - } - - msgBytes := make([]byte, length) - count, err = input.Read(msgBytes) - if err != nil { - return nil, eofReturn(err) - } - if count != length { - return nil, fmt.Errorf("incomplete message read") - } - - return msgBytes, nil -} - -func getMessageLength(msg []byte) (int, error) { - var length uint32 - buf := bytes.NewBuffer(msg) - if err := binary.Read(buf, binary.LittleEndian, &length); err != nil { - return 0, err - } - - return int(length), nil -} - -func eofReturn(err error) error { - if err == io.EOF { - return nil - } - return err -} - -func sendSerializedJSONMessage(message interface{}, w io.Writer) error { - // we can't use json.NewEncoder(w).Encode because we need to send the final - // message length before the actual JSON - serialized, err := json.Marshal(message) - if err != nil { - return err - } - - if err := writeMessageLength(serialized, w); err != nil { - return err - } - - var msgBuf bytes.Buffer - count, err := msgBuf.Write(serialized) - if err != nil { - return err - } - if count != len(serialized) { - return fmt.Errorf("message not fully written") - } - - wcount, err := msgBuf.WriteTo(w) - if wcount != int64(len(serialized)) { - return fmt.Errorf("message not fully written") - } - return err -} - -func writeMessageLength(msg []byte, w io.Writer) error { - return binary.Write(w, binary.LittleEndian, uint32(len(msg))) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/messages_test.go b/cmd/gopass-jsonapi/internal/jsonapi/messages_test.go deleted file mode 100644 index 1da2ffccb1..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/messages_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package jsonapi - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRoundTrip(t *testing.T) { - a := assert.New(t) - var receivedMessage queryMessage - - message := queryMessage{Query: "holla"} - var buffer bytes.Buffer - - err := sendSerializedJSONMessage(message, &buffer) - a.NoError(err) - - received, err := readMessage(&buffer) - a.NoError(err) - - a.NoError(json.Unmarshal(received, &receivedMessage)) - a.Equal(message.Query, receivedMessage.Query) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/responses.go b/cmd/gopass-jsonapi/internal/jsonapi/responses.go deleted file mode 100644 index 2e52d9eb39..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/responses.go +++ /dev/null @@ -1,245 +0,0 @@ -package jsonapi - -import ( - "context" - "encoding/json" - "fmt" - "path" - "regexp" - "strings" - - "github.com/gopasspw/gopass/pkg/clipboard" - "github.com/gopasspw/gopass/pkg/gopass" - "github.com/gopasspw/gopass/pkg/gopass/secrets" - "github.com/gopasspw/gopass/pkg/otp" - "github.com/gopasspw/gopass/pkg/pwgen" - - "github.com/pkg/errors" -) - -var ( - sep = "/" -) - -func (api *API) respondMessage(ctx context.Context, msgBytes []byte) error { - var message messageType - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - switch message.Type { - case "query": - return api.respondQuery(ctx, msgBytes) - case "queryHost": - return api.respondHostQuery(ctx, msgBytes) - case "getLogin": - return api.respondGetLogin(ctx, msgBytes) - case "getData": - return api.respondGetData(ctx, msgBytes) - case "create": - return api.respondCreateEntry(ctx, msgBytes) - case "copyToClipboard": - return api.respondCopyToClipboard(ctx, msgBytes) - case "getVersion": - return api.respondGetVersion() - default: - return fmt.Errorf("unknown message of type %s", message.Type) - } -} - -func (api *API) respondHostQuery(ctx context.Context, msgBytes []byte) error { - var message queryHostMessage - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - l, err := api.Store.List(ctx) - if err != nil { - return errors.Wrapf(err, "failed to list store") - } - choices := make([]string, 0, 10) - - for !isPublicSuffix(message.Host) { - // only query for paths and files in the store fully matching the hostname. - reQuery := fmt.Sprintf("(^|.*/)%s($|/.*)", regexSafeLower(message.Host)) - if err := searchAndAppendChoices(reQuery, l, &choices); err != nil { - return errors.Wrapf(err, "failed to append search results") - } - if len(choices) > 0 { - break - } else { - message.Host = strings.SplitN(message.Host, ".", 2)[1] - } - } - - return sendSerializedJSONMessage(choices, api.Writer) -} - -func (api *API) respondQuery(ctx context.Context, msgBytes []byte) error { - var message queryMessage - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - l, err := api.Store.List(ctx) - if err != nil { - return errors.Wrapf(err, "failed to list store") - } - - choices := make([]string, 0, 10) - reQuery := fmt.Sprintf(".*%s.*", regexSafeLower(message.Query)) - if err := searchAndAppendChoices(reQuery, l, &choices); err != nil { - return errors.Wrapf(err, "failed to append search results") - } - - return sendSerializedJSONMessage(choices, api.Writer) -} - -func searchAndAppendChoices(reQuery string, list []string, choices *[]string) error { - re, err := regexp.Compile(reQuery) - if err != nil { - return errors.Wrapf(err, "failed to compile regexp '%s': %s", reQuery, err) - } - - for _, value := range list { - if re.MatchString(strings.ToLower(value)) { - *choices = append(*choices, value) - } - } - return nil -} - -func (api *API) respondGetLogin(ctx context.Context, msgBytes []byte) error { - var message getLoginMessage - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - sec, err := api.Store.Get(ctx, message.Entry, "latest") - if err != nil { - return errors.Wrapf(err, "failed to get secret") - } - - return sendSerializedJSONMessage(loginResponse{ - Username: api.getUsername(message.Entry, sec), - Password: sec.Password(), - }, api.Writer) -} - -func (api *API) respondGetData(ctx context.Context, msgBytes []byte) error { - var message getDataMessage - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - sec, err := api.Store.Get(ctx, message.Entry, "latest") - if err != nil { - return errors.Wrapf(err, "failed to get secret") - } - - keys := sec.Keys() - responseData := make(map[string]string, len(keys)) - for _, k := range keys { - // we ignore the otpauth key - if k == "otpauth" { - continue - } - v, ok := sec.Get(k) - if !ok { - continue - } - responseData[k] = v - } - currentTotp, _, err := otp.Calculate("_", sec) - if err == nil { - responseData["current_totp"] = currentTotp.OTP() - } - - converted := convertMixedMapInterfaces(interface{}(responseData)) - return sendSerializedJSONMessage(converted, api.Writer) -} - -func (api *API) getUsername(name string, sec gopass.Secret) string { - // look for a meta-data entry containing the username first - for _, key := range []string{"login", "username", "user"} { - if v, ok := sec.Get(key); ok && v != "" { - return v - } - } - - // if no meta-data was found return the name of the secret itself - // as the username, e.g. providers/amazon.com/foobar -> foobar - if strings.Contains(name, sep) { - return path.Base(name) - } - - return "" -} - -func (api *API) respondCreateEntry(ctx context.Context, msgBytes []byte) error { - var message createEntryMessage - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - if _, err := api.Store.Get(ctx, message.Name, "latest"); err == nil { - return fmt.Errorf("secret %s already exists", message.Name) - } - - if message.Generate { - message.Password = pwgen.GeneratePassword(message.PasswordLength, message.UseSymbols) - } - - sec := secrets.New() - sec.SetPassword(message.Password) - if len(message.Login) > 0 { - sec.Set("user", message.Login) - } - if err := api.Store.Set(ctx, message.Name, sec); err != nil { - return errors.Wrapf(err, "failed to store secret") - } - - return sendSerializedJSONMessage(loginResponse{ - Username: message.Login, - Password: message.Password, - }, api.Writer) -} - -func (api *API) respondGetVersion() error { - return sendSerializedJSONMessage(getVersionMessage{ - Version: api.Version.String(), - Major: api.Version.Major, - Minor: api.Version.Minor, - Patch: api.Version.Patch, - }, api.Writer) -} - -func (api *API) respondCopyToClipboard(ctx context.Context, msgBytes []byte) error { - var message copyToClipboard - if err := json.Unmarshal(msgBytes, &message); err != nil { - return errors.Wrapf(err, "failed to unmarshal JSON message") - } - - sec, err := api.Store.Get(ctx, message.Entry, "latest") - if err != nil { - return errors.Wrapf(err, "failed to get secret") - } - var val string - if message.Key == "" { - val = sec.Password() - } else { - val, _ = sec.Get(message.Key) - } - - if val == "" { - return fmt.Errorf("entry not found") - } - - if err := clipboard.CopyTo(ctx, message.Entry, []byte(val)); err != nil { - return errors.Wrapf(err, "failed to copy to clipboard") - } - - return sendSerializedJSONMessage(statusResponse{ - Status: "ok", - }, api.Writer) -} diff --git a/cmd/gopass-jsonapi/internal/jsonapi/responses_test.go b/cmd/gopass-jsonapi/internal/jsonapi/responses_test.go deleted file mode 100644 index 645043231a..0000000000 --- a/cmd/gopass-jsonapi/internal/jsonapi/responses_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package jsonapi - -import ( - "testing" - - "github.com/gopasspw/gopass/pkg/gopass" -) - -func TestGetUsername(t *testing.T) { - a := &API{} - for _, tc := range []struct { - Name string - Sec gopass.Secret - Out string - }{ - { - Name: "some/fixed/yamlother", - Sec: newSec(t, "thesecret\n---\nother: meh"), - Out: "yamlother", - }, - { - Name: "some/key/withaname", - Sec: newSec(t, "thesecret\n---\nlogin: foo"), - Out: "foo", - }, - } { - got := a.getUsername(tc.Name, tc.Sec) - if got != tc.Out { - t.Errorf("Wrong username: %s != %s", got, tc.Out) - } - } -} diff --git a/cmd/gopass-jsonapi/jsonapi.go b/cmd/gopass-jsonapi/jsonapi.go deleted file mode 100644 index 96d26f47f9..0000000000 --- a/cmd/gopass-jsonapi/jsonapi.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "context" - "os" - - "runtime" - "strings" - - "github.com/blang/semver" - "github.com/gopasspw/gopass/cmd/gopass-jsonapi/internal/jsonapi" - "github.com/gopasspw/gopass/cmd/gopass-jsonapi/internal/jsonapi/manifest" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass" - "github.com/gopasspw/gopass/pkg/termio" - - "github.com/fatih/color" - "github.com/pkg/errors" - "github.com/urfave/cli/v2" -) - -var ( - stdin = os.Stdin - stdout = os.Stdout -) - -type jsonapiCLI struct { - gp gopass.Store -} - -// listen reads a json message on stdin and responds on stdout -func (s *jsonapiCLI) listen(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - - version, err := semver.Parse(strings.TrimPrefix(c.App.Version, "v")) - if err != nil { - version = semver.Version{} - } - - api := jsonapi.API{Store: s.gp, Reader: stdin, Writer: stdout, Version: version} - if err := api.ReadAndRespond(ctx); err != nil { - return api.RespondError(err) - } - return nil -} - -func (s *jsonapiCLI) getBrowser(ctx context.Context, c *cli.Context) (string, error) { - browser := c.String("browser") - if browser != "" { - return browser, nil - } - - browser, err := termio.AskForString(ctx, color.BlueString("For which browser do you want to install gopass native messaging? [%s]", strings.Join(manifest.ValidBrowsers(), ",")), manifest.DefaultBrowser) - if err != nil { - return "", errors.Wrapf(err, "failed to ask for user input") - } - if !manifest.ValidBrowser(browser) { - return "", errors.Errorf("%s not one of %s", browser, strings.Join(manifest.ValidBrowsers(), ",")) - } - return browser, nil -} - -func (s *jsonapiCLI) getGlobalInstall(ctx context.Context, c *cli.Context) (bool, error) { - if !c.IsSet("global") { - return termio.AskForBool(ctx, color.BlueString("Install for all users? (might require sudo gopass)"), false) - } - return c.Bool("global"), nil -} - -func (s *jsonapiCLI) getLibPath(ctx context.Context, c *cli.Context, browser string, global bool) (string, error) { - if !c.IsSet("libpath") && runtime.GOOS == "linux" && browser == "firefox" && global { - return termio.AskForString(ctx, color.BlueString("What is your lib path?"), "/usr/lib") - } - return c.String("libpath"), nil -} - -func (s *jsonapiCLI) getWrapperPath(ctx context.Context, c *cli.Context, defaultWrapperPath string, wrapperName string) (string, error) { - path := c.String("path") - if path != "" { - return path, nil - } - path, err := termio.AskForString(ctx, color.BlueString("In which path should %s be installed?", wrapperName), defaultWrapperPath) - if err != nil { - return "", errors.Wrapf(err, "failed to ask for user input") - } - return path, nil -} diff --git a/cmd/gopass-jsonapi/jsonapi_others.go b/cmd/gopass-jsonapi/jsonapi_others.go deleted file mode 100644 index 82875d10ee..0000000000 --- a/cmd/gopass-jsonapi/jsonapi_others.go +++ /dev/null @@ -1,84 +0,0 @@ -// +build !windows - -package main - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/gopasspw/gopass/cmd/gopass-jsonapi/internal/jsonapi/manifest" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass/api" - "github.com/gopasspw/gopass/pkg/termio" - - "github.com/fatih/color" - "github.com/urfave/cli/v2" -) - -// setup sets up manifest for gopass as native messaging host -func (s *jsonapiCLI) setup(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - browser, err := s.getBrowser(ctx, c) - if err != nil { - return fmt.Errorf("failed to get browser: %s", err) - } - - globalInstall, err := s.getGlobalInstall(ctx, c) - if err != nil { - return fmt.Errorf("failed to get global flag: %s", err) - } - - libPath, err := s.getLibPath(ctx, c, browser, globalInstall) - if err != nil { - return fmt.Errorf("failed to get lib path: %s", err) - } - - wrapperPath, err := s.getWrapperPath(ctx, c, api.ConfigDir(), manifest.WrapperName) - if err != nil { - return fmt.Errorf("failed to get wrapper path: %s", err) - } - wrapperPath = filepath.Join(wrapperPath, manifest.WrapperName) - - manifestPath := c.String("manifest-path") - if manifestPath == "" { - p, err := manifest.Path(browser, libPath, globalInstall) - if err != nil { - return fmt.Errorf("failed to get manifest path: %s", err) - } - manifestPath = p - } - - wrap, mf, err := manifest.Render(browser, wrapperPath, c.String("gopass-path"), globalInstall) - if err != nil { - return fmt.Errorf("failed to render manifest: %s", err) - } - - if c.Bool("print") { - fmt.Printf("Native Messaging Setup Preview:\nWrapper Script (%s):\n%s\n\nManifest File (%s):\n%s\n", wrapperPath, string(wrap), manifestPath, string(mf)) - } - - if install, err := termio.AskForBool(ctx, color.BlueString("Install manifest and wrapper?"), true); err != nil || !install { - return err - } - - if os.Getenv("GNUPGHOME") != "" { - fmt.Printf("You seem to have GNUPGHOME set. If you intend to use the path in GNUPGHOME, you need to manually add:\n" + - "\n export GNUPGHOME=/path/to/gpg-home\n\n to the wrapper script") - } - - if err := os.MkdirAll(filepath.Dir(wrapperPath), 0755); err != nil { - return fmt.Errorf("failed to create wrapper path: %s", err) - } - if err := ioutil.WriteFile(wrapperPath, wrap, 0755); err != nil { - return fmt.Errorf("failed to write wrapper script: %s", err) - } - if err := os.MkdirAll(filepath.Dir(manifestPath), 0755); err != nil { - return fmt.Errorf("failed to create manifest path: %s", err) - } - if err := ioutil.WriteFile(manifestPath, mf, 0644); err != nil { - return fmt.Errorf("failed to write manifest file: %s", err) - } - return nil -} diff --git a/cmd/gopass-jsonapi/jsonapi_test.go b/cmd/gopass-jsonapi/jsonapi_test.go deleted file mode 100644 index 3c3d8d7f55..0000000000 --- a/cmd/gopass-jsonapi/jsonapi_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "context" - "testing" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/tests/gptest" - - "github.com/stretchr/testify/assert" -) - -func TestJSONAPI(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - ctx = ctxutil.WithAlwaysYes(ctx, true) - - act := &jsonapiCLI{} - - assert.NoError(t, act.listen(gptest.CliCtx(ctx, t))) - - b, err := act.getBrowser(ctx, gptest.CliCtx(ctx, t)) - assert.NoError(t, err) - assert.Equal(t, b, "chrome") -} diff --git a/cmd/gopass-jsonapi/jsonapi_windows.go b/cmd/gopass-jsonapi/jsonapi_windows.go deleted file mode 100644 index c81e5ee561..0000000000 --- a/cmd/gopass-jsonapi/jsonapi_windows.go +++ /dev/null @@ -1,135 +0,0 @@ -// +build windows - -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/gopasspw/gopass/cmd/gopass-jsonapi/internal/jsonapi/manifest" - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/termio" - - "github.com/fatih/color" - "github.com/urfave/cli/v2" - "golang.org/x/sys/windows/registry" -) - -// setup sets up manifest for gopass as native messaging host -func (s *jsonapiCLI) setup(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - browser, err := s.getBrowser(ctx, c) - if err != nil { - return fmt.Errorf("failed to get browser: %s", err) - } - - globalInstall, err := s.getGlobalInstall(ctx, c) - if err != nil { - return fmt.Errorf("failed to get global flag: %s", err) - } - - // Use windows specific folder to store wrapper and manifests - defaultWrapperPath := filepath.Join(os.Getenv("LOCALAPPDATA"), "gopass") - if globalInstall { - defaultWrapperPath = filepath.Join(os.Getenv("PROGRAMDATA"), "gopass") - } - - wrapperPath, err := s.getWrapperPath(ctx, c, defaultWrapperPath, manifest.NativeHostExeName) - if err != nil { - return fmt.Errorf("failed to get wrapper path: %s", err) - } - wrapperFileName := filepath.Join(wrapperPath, manifest.NativeHostExeName) - - manifestPath := c.String("manifest-path") - if manifestPath == "" { - manifestPath = filepath.Join(wrapperPath, browser, manifest.Name+".json") - } - - regPath, err := manifest.GetRegistryPath(browser) - if err != nil { - return fmt.Errorf("failed to get registry path: %s", err) - } - - _, mf, err := manifest.Render(browser, wrapperFileName, c.String("gopass-path"), globalInstall) - if err != nil { - return fmt.Errorf("failed to render manifest: %s", err) - } - - if c.Bool("print") { - fmt.Printf("Native Messaging Setup Preview:\nWrapper Script (%s)\n\nManifest File (%s = %s):\n%s\n", wrapperFileName, regPath, manifestPath, string(mf)) - } - - if install, err := termio.AskForBool(ctx, color.BlueString("Install manifest and wrapper?"), true); err != nil || !install { - return err - } - - if err := os.MkdirAll(filepath.Dir(wrapperFileName), 0755); err != nil { - return fmt.Errorf("failed to create wrapper path: %s", err) - } - - if err := s.setRegistryValue(regPath, manifestPath, globalInstall); err != nil { - return fmt.Errorf("failed to set registry value: %s", err) - } - - // If the calling binary has native_host.exe as suffix listener will be started. - if err := s.copyExecutionBinary(wrapperFileName); err != nil { - return fmt.Errorf("failed to copy gopass binary to wrapper path: %s", err) - } - - if err := os.MkdirAll(filepath.Dir(manifestPath), 0755); err != nil { - return fmt.Errorf("failed to create manifest path: %s", err) - } - if err := ioutil.WriteFile(manifestPath, mf, 0644); err != nil { - return fmt.Errorf("failed to write manifest file: %s", err) - } - - return nil -} - -func (s *jsonapiCLI) setRegistryValue(path string, value string, globalInstall bool) error { - key := registry.CURRENT_USER - if globalInstall { - key = registry.LOCAL_MACHINE - } - - k, err := registry.OpenKey(key, path, registry.WRITE) - if err != nil { - if err != registry.ErrNotExist { - return err - } - k, _, err = registry.CreateKey(key, path, registry.ALL_ACCESS) - if err != nil { - return err - } - } - defer k.Close() - return k.SetStringValue("", value) -} - -func (s *jsonapiCLI) copyExecutionBinary(destFileName string) error { - srcFileName, err := os.Executable() - if err != nil { - return err - } - - srcFile, err := os.Open(srcFileName) - if err != nil { - return err - } - defer srcFile.Close() - - destFile, err := os.Create(destFileName) // creates if file doesn't exist - if err != nil { - return err - } - defer destFile.Close() - - _, err = io.Copy(destFile, srcFile) - if err != nil { - return err - } - return destFile.Sync() -} diff --git a/cmd/gopass-jsonapi/main.go b/cmd/gopass-jsonapi/main.go deleted file mode 100644 index 73234a0ac0..0000000000 --- a/cmd/gopass-jsonapi/main.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "strings" - - "github.com/gopasspw/gopass/pkg/gopass/api" - "github.com/urfave/cli/v2" -) - -const ( - name = "gopass-jsonapi" -) - -var ( - // Version is the released version of gopass - version string -) - -func main() { - ctx := context.Background() - - // trap Ctrl+C and call cancel on the context - ctx, cancel := context.WithCancel(ctx) - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) - defer func() { - signal.Stop(sigChan) - cancel() - }() - go func() { - select { - case <-sigChan: - cancel() - case <-ctx.Done(): - } - }() - - gp, err := api.New(ctx) - if err != nil { - fmt.Printf("Failed to initialize gopass API: %s\n", err) - os.Exit(1) - } - - ja := &jsonapiCLI{ - gp: gp, - } - - app := cli.NewApp() - app.Name = name - app.Version = version - app.Usage = "Setup and run gopass-jsonapi as native messaging hosts, e.g. for browser plugins" - app.EnableBashCompletion = true - app.Action = func(c *cli.Context) error { - if strings.HasSuffix(os.Args[0], "native_host") || strings.HasSuffix(os.Args[0], "native_host.exe") { - return ja.listen(c) - } - return cli.ShowAppHelp(c) - } - app.Commands = []*cli.Command{ - { - Name: "listen", - Usage: "Listen and respond to messages via stdin/stdout", - Description: "Gopass-jsonapi is started in listen mode from browser plugins using a wrapper specified in native messaging host manifests", - Hidden: true, - Action: func(c *cli.Context) error { - return ja.listen(c) - }, - }, - { - Name: "configure", - Usage: "Setup gopass-jsonapi native messaging manifest for selected browser", - Description: "To access gopass from browser plugins, a native app manifest must be installed at the correct location", - Action: func(c *cli.Context) error { - return ja.setup(c) - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "browser", - Usage: "One of 'chrome' and 'firefox'", - }, - &cli.StringFlag{ - Name: "path", - Usage: "Path to install 'gopass_wrapper.sh' to", - }, - &cli.StringFlag{ - Name: "manifest-path", - Usage: "Path to install 'com.justwatch.gopass.json' to", - }, - &cli.BoolFlag{ - Name: "global", - Usage: "Install for all users, requires superuser rights", - }, - &cli.StringFlag{ - Name: "libpath", - Usage: "Library path for global installation on linux. Default is /usr/lib", - }, - &cli.StringFlag{ - Name: "gopass-path", - Usage: "Path to gopass binary. Default is auto detected", - }, - &cli.BoolFlag{ - Name: "print", - Usage: "Print installation summary before creating any files", - Value: true, - }, - }, - }, - } - - if err := app.RunContext(ctx, os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/gopass-summon-provider/.gitignore b/cmd/gopass-summon-provider/.gitignore deleted file mode 100644 index 252569b129..0000000000 --- a/cmd/gopass-summon-provider/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gopass-summon-provider diff --git a/cmd/gopass-summon-provider/main.go b/cmd/gopass-summon-provider/main.go deleted file mode 100644 index af4e39c340..0000000000 --- a/cmd/gopass-summon-provider/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - - "github.com/gopasspw/gopass/pkg/gopass/api" - "github.com/urfave/cli/v2" -) - -const ( - name = "summon-gopass" -) - -var ( - // Version is the released version of gopass - version string -) - -func main() { - ctx := context.Background() - - // trap Ctrl+C and call cancel on the context - ctx, cancel := context.WithCancel(ctx) - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt) - defer func() { - signal.Stop(sigChan) - cancel() - }() - go func() { - select { - case <-sigChan: - cancel() - case <-ctx.Done(): - } - }() - - gp, err := api.New(ctx) - if err != nil { - fmt.Printf("Failed to initialize gopass API: %s\n", err) - os.Exit(1) - } - - gc := &gc{ - gp: gp, - } - - app := cli.NewApp() - app.Name = name - app.Version = version - app.Usage = `Use "gopass-summon-provider" as provider for "summon"` - app.Description = "" + - "This command allows to use gopass as a secret provider for summon." + - "To use it set the 'SUMMON_PROVIDER' variable to this executable or" + - "copy or link it (as `gopass`) into the summon provider directory" + - "'/usr/local/lib/summon/'. See 'summon' documentation for more details." - app.Action = gc.Get - - if err := app.RunContext(ctx, os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/gopass-summon-provider/summon-provider.go b/cmd/gopass-summon-provider/summon-provider.go deleted file mode 100644 index efe628d7a0..0000000000 --- a/cmd/gopass-summon-provider/summon-provider.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - - "github.com/gopasspw/gopass/pkg/ctxutil" - "github.com/gopasspw/gopass/pkg/gopass" - "github.com/urfave/cli/v2" -) - -var ( - // Stdout is exported for tests - Stdout io.Writer = os.Stdout -) - -type gc struct { - gp gopass.Store -} - -// Get outputs the password for given path on stdout -func (s *gc) Get(c *cli.Context) error { - ctx := ctxutil.WithGlobalFlags(c) - ctx = ctxutil.WithNoNetwork(ctx, true) - path := c.Args().Get(0) - secret, err := s.gp.Get(ctx, path, "latest") - if err != nil { - return err - } - fmt.Fprintln(Stdout, secret.Password()) - return nil -} diff --git a/cmd/gopass-summon-provider/summon-provider_test.go b/cmd/gopass-summon-provider/summon-provider_test.go deleted file mode 100644 index 75887d523c..0000000000 --- a/cmd/gopass-summon-provider/summon-provider_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "bytes" - "context" - "os" - "testing" - - "github.com/fatih/color" - "github.com/gopasspw/gopass/pkg/gopass/apimock" - "github.com/gopasspw/gopass/pkg/termio" - "github.com/gopasspw/gopass/tests/gptest" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSummonProviderOutputsOnlySecret(t *testing.T) { - ctx := context.Background() - act := &gc{ - gp: apimock.New(), - } - require.NoError(t, act.gp.Set(ctx, "foo", &apimock.Secret{Buf: []byte("bar\nbaz: zab")})) - - buf := &bytes.Buffer{} - Stdout = buf - color.NoColor = true - defer func() { - termio.Stdin = os.Stdin - Stdout = os.Stdout - }() - - assert.NoError(t, act.Get(gptest.CliCtx(ctx, t, "foo"))) - assert.Equal(t, "bar\n", buf.String()) -}