Skip to content

Commit

Permalink
GPG related fixes (gopasspw#419)
Browse files Browse the repository at this point in the history
This commit fixes a number of issue around GPG, improves
the GPG binary detection and covers some windows cases.

Fixes #5
Fixes gopasspw#334
Fixes gopasspw#418
  • Loading branch information
dominikschulz authored Oct 24, 2017
1 parent a67f26f commit f98eef3
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 24 deletions.
1 change: 1 addition & 0 deletions action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type gpger interface {
CreatePrivateKeyBatch(context.Context, string, string, string) error
CreatePrivateKey(context.Context) error
ExportPublicKey(context.Context, string, string) error
Version(context.Context) semver.Version
}

// Action knows everything to run gopass CLI actions
Expand Down
4 changes: 4 additions & 0 deletions action/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/blang/semver"
"github.com/fatih/color"
"github.com/justwatchcom/gopass/config"
"github.com/justwatchcom/gopass/utils/ctxutil"
Expand All @@ -17,6 +18,9 @@ import (
// Initialized returns an error if the store is not properly
// prepared.
func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
if s.gpg.Version(ctx).LT(semver.Version{Major: 2, Minor: 0, Patch: 0}) {
out.Red(ctx, "Warning: Using GPG 1.x. Using GPG 2.0 or later is highly recommended")
}
if !s.Store.Initialized() {
if ctxutil.IsInteractive(ctx) {
if ok, err := s.askForBool(ctx, "It seems you are new to gopass. Do you want to run the onboarding wizard?", true); err == nil && ok {
Expand Down
53 changes: 46 additions & 7 deletions action/recipients.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,42 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
store := c.String("store")
added := 0

// select store
if store == "" {
stores := []string{"<root>"}
stores = append(stores, s.Store.MountPoints()...)
act, sel := termwiz.GetSelection(ctx, "Store for secret", "<↑/↓> to change the selection, <→> to select, <ESC> to quit", stores)
switch act {
case "show":
store = stores[sel]
if store == "<root>" {
store = ""
}
default:
store = "" // root store
}
}

// select recipient
recipients := []string(c.Args())
if len(recipients) < 1 {
choices := []string{}
kl, _ := s.gpg.FindPublicKeys(ctx)
for _, key := range kl.UseableKeys() {
kl = kl.UseableKeys()
for _, key := range kl {
choices = append(choices, key.OneLine())
}
if len(choices) > 0 {
act, sel := termwiz.GetSelection(ctx, "Add Recipient -", "<↑/↓> to change the selection, <→> to add this recipient, <ESC> to quit", choices)
switch act {
case "show":
recipients = []string{choices[sel]}
recipients = []string{kl[sel].Fingerprint}
default:
return s.exitError(ctx, ExitAborted, nil, "user aborted")
}
}
}

for _, r := range recipients {
keys, err := s.gpg.FindPublicKeys(ctx, r)
if err != nil {
Expand All @@ -95,11 +114,12 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
if len(keys) < 1 {
out.Cyan(ctx, "Warning: No matching valid key found. If the key is in your keyring you may need to validate it.")
out.Cyan(ctx, "If this is your key: gpg --edit-key %s; trust (set to ultimate); quit", r)
out.Cyan(ctx, "If this is not your key: gpg --edit-key %s; lsign; save; quit", r)
out.Cyan(ctx, "If this is not your key: gpg --edit-key %s; lsign; trust; save; quit", r)
out.Cyan(ctx, "You may need to run 'gpg --update-trustdb' afterwards")
continue
}

if !s.AskForConfirmation(ctx, fmt.Sprintf("Do you want to add '%s' as an recipient?", keys[0].OneLine())) {
if !s.AskForConfirmation(ctx, fmt.Sprintf("Do you want to add '%s' as an recipient to the store '%s'?", keys[0].OneLine(), store)) {
continue
}

Expand All @@ -112,14 +132,32 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
return s.exitError(ctx, ExitUnknown, nil, "no key added")
}

out.Green(ctx, "Added %d recipients\n", added)
out.Green(ctx, "\nAdded %d recipients", added)
out.Cyan(ctx, "You need to run 'gopass sync' to push these changes")
return nil
}

// RecipientsRemove removes recipients
func (s *Action) RecipientsRemove(ctx context.Context, c *cli.Context) error {
store := c.String("store")

// select store
if store == "" {
stores := []string{"<root>"}
stores = append(stores, s.Store.MountPoints()...)
act, sel := termwiz.GetSelection(ctx, "Store for secret", "<↑/↓> to change the selection, <→> to select, <ESC> to quit", stores)
switch act {
case "show":
store = stores[sel]
if store == "<root>" {
store = ""
}
default:
store = "" // root store
}
}

// select recipient
recipients := []string(c.Args())
if len(recipients) < 1 {
ids := s.Store.ListRecipients(ctx, store)
Expand All @@ -138,7 +176,7 @@ func (s *Action) RecipientsRemove(ctx context.Context, c *cli.Context) error {
act, sel := termwiz.GetSelection(ctx, "Remove recipient -", "<↑/↓> to change the selection, <→> to remove this recipient, <ESC> to quit", choices)
switch act {
case "show":
recipients = []string{choices[sel]}
recipients = []string{ids[sel]}
default:
return s.exitError(ctx, ExitAborted, nil, "user aborted")
}
Expand All @@ -162,6 +200,7 @@ func (s *Action) RecipientsRemove(ctx context.Context, c *cli.Context) error {
removed++
}

fmt.Printf("Removed %d recipients\n", removed)
out.Green(ctx, "\nRemoved %d recipients", removed)
out.Cyan(ctx, "You need to run 'gopass sync' to push these changes")
return nil
}
17 changes: 8 additions & 9 deletions backend/gpg/cli/gpg.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gpg
package cli

import (
"bufio"
Expand Down Expand Up @@ -58,25 +58,24 @@ func New(cfg Config) *GPG {
binary: "gpg",
args: cfg.Args,
}

for _, b := range []string{cfg.Binary, "gpg2", "gpg1", "gpg"} {
if p, err := exec.LookPath(b); err == nil {
g.binary = p
break
}
}
_ = g.detectBinary(cfg.Binary)

return g
}

// Binary returns the GPG binary location
func (g *GPG) Binary() string {
return g.binary
}

// listKey lists all keys of the given type and matching the search strings
func (g *GPG) listKeys(ctx context.Context, typ string, search ...string) (gpg.KeyList, error) {
args := []string{"--with-colons", "--with-fingerprint", "--fixed-list-mode", "--list-" + typ + "-keys"}
args = append(args, search...)
cmd := exec.CommandContext(ctx, g.binary, args...)
cmd.Stderr = nil

out.Debug(ctx, "[DEBUG] gpg.listKeys: %s %+v\n", cmd.Path, cmd.Args)
out.Debug(ctx, "gpg.listKeys: %s %+v\n", cmd.Path, cmd.Args)
cmdout, err := cmd.Output()
if err != nil {
if bytes.Contains(cmdout, []byte("secret key not available")) {
Expand Down
24 changes: 24 additions & 0 deletions backend/gpg/cli/gpg_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// +build !windows

package cli

import (
"context"
"os/exec"

"github.com/blang/semver"
)

func (g *GPG) detectBinary(bin string) error {
for _, b := range []string{bin, "gpg", "gpg2", "gpg1", "gpg"} {
if p, err := exec.LookPath(b); err == nil {
g.binary = p
// if we found a GPG 2.x binary we're good, otherwise we try the
// others as well
if g.Version(context.Background()).GTE(semver.Version{Major: 2}) {
break
}
}
}
return nil
}
2 changes: 1 addition & 1 deletion backend/gpg/cli/gpg_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gpg
package cli

import "testing"

Expand Down
37 changes: 37 additions & 0 deletions backend/gpg/cli/gpg_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build windows
package cli

import (
"errors"
"path/filepath"

"github.com/justwatchcom/gopass/utils/fsutil"

"golang.org/x/sys/windows/registry"
)

func (g *GPG) detectBinary(bin string) error {
// set default
g.binary = "gpg.exe"

// try to detect location
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\GnuPG`, registry.QUERY_VALUE|registry.WOW64_32KEY)
if err != nil {
return err
}

v, _, err := k.GetStringValue("Install Directory")
if err != nil {
return err
}

// gpg.exe for GPG4Win 3.0.0; would be gpg2.exe for 2.x
for _, b := range []string{bin, "gpg.exe", "gpg2.exe"} {
gpgPath := filepath.Join(v, "bin", b)
if fsutil.IsFile(gpgPath) {
g.binary = gpgPath
return nil
}
}
return errors.New("gpg.exe not found")
}
2 changes: 1 addition & 1 deletion backend/gpg/cli/parse_colons.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gpg
package cli

import (
"bufio"
Expand Down
2 changes: 1 addition & 1 deletion backend/gpg/cli/umask_others.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build !windows

package gpg
package cli

import "syscall"

Expand Down
2 changes: 1 addition & 1 deletion backend/gpg/cli/umask_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build windows

package gpg
package cli

func umask(mask int) int {
return -1
Expand Down
2 changes: 1 addition & 1 deletion backend/gpg/cli/utils.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gpg
package cli

import (
"strconv"
Expand Down
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,11 @@ func main() {
Description: "" +
"This command clones an existing password store from a git remote to " +
"a local password store. Can be either used to initialize a new root store " +
"or to add a new mounted sub store.",
"or to add a new mounted sub store." +
"" +
"Needs at least one argument (git URL) to clone from. " +
"Accepts as second argument (mount location) to clone and mount a sub store, e.g. " +
"gopass clone git@example.com/store.git foo/bar",
Action: func(c *cli.Context) error {
return action.Clone(withGlobalFlags(ctx, c), c)
},
Expand Down
2 changes: 1 addition & 1 deletion store/sub/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (s *Store) gitFixConfig(ctx context.Context) error {
out.Yellow(ctx, "Error while initializing git: %s", err)
}

return nil
return s.gitFixConfigOSDep(ctx)
}

// GitInit initializes this store's git repo and
Expand Down
10 changes: 10 additions & 0 deletions store/sub/git_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build !windows

package sub

import "context"

func (s *Store) gitFixConfigOSDep(ctx context.Context) error {
// nothing to do
return nil
}
16 changes: 16 additions & 0 deletions store/sub/git_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// +build windows

package sub

import (
"context"

"github.com/pkg/errors"
)

func (s *Store) gitFixConfigOSDep(ctx context.Context) error {
if err := s.gitCmd(ctx, "gitFixConfigOSDep", "config", "--local", "gpg.program", "TODO"); err != nil {
return errors.Wrapf(err, "failed to set git config gpg.program")
}
return nil
}
3 changes: 2 additions & 1 deletion store/sub/recipients.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (s *Store) AddRecipient(ctx context.Context, id string) error {
return errors.Wrapf(err, "failed to save recipients")
}

out.Cyan(ctx, "Reencrypting existing secrets. This may take some time ...")
return s.reencrypt(WithReason(ctx, "Added Recipient "+id))
}

Expand All @@ -69,7 +70,7 @@ func (s *Store) RemoveRecipient(ctx context.Context, id string) error {
// just try to remove it literally
keys, err := s.gpg.FindPublicKeys(ctx, id)
if err != nil {
fmt.Printf("Failed to get GPG Key Info for %s: %s\n", id, err)
out.Cyan(ctx, "Warning: Failed to get GPG Key Info for %s: %s", id, err)
}

rs, err := s.GetRecipients(ctx, "")
Expand Down
17 changes: 17 additions & 0 deletions store/sub/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/justwatchcom/gopass/utils/ctxutil"
"github.com/justwatchcom/gopass/utils/fsutil"
"github.com/justwatchcom/gopass/utils/out"
"github.com/muesli/goprogressbar"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -156,13 +157,29 @@ func (s *Store) reencrypt(ctx context.Context) error {
// shadow ctx in this block only
ctx := WithAutoSync(ctx, false)
ctx = ctxutil.WithGitCommit(ctx, false)

// progress bar
bar := &goprogressbar.ProgressBar{
Total: int64(len(entries)),
Width: 120,
}
if !ctxutil.IsTerminal(ctx) {
bar = nil
}
for _, e := range entries {
// check for context cancelation
select {
case <-ctx.Done():
return errors.New("context canceled")
default:
}

if bar != nil {
bar.Current++
bar.Text = fmt.Sprintf("%d of %d secrets reencrypted", bar.Current, bar.Total)
bar.LazyPrint()
}

content, err := s.Get(ctx, e)
if err != nil {
fmt.Printf("Failed to get current value for %s: %s\n", e, err)
Expand Down

0 comments on commit f98eef3

Please sign in to comment.