lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 2a77cd856ae5f288560904ab60f40c514daba028
parent 7dd4bcf81387321aff3d87674ac5bc5e5939ba87
Author: Sean Enck <sean@ttypty.com>
Date:   Thu, 30 Mar 2023 19:44:48 -0400

reworking rekey

Diffstat:
Mcmd/main.go | 2+-
Ainternal/app/rekey.go | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/backend/actions.go | 26--------------------------
Minternal/backend/query.go | 2+-
Minternal/backend/types.go | 2++
Minternal/inputs/env.go | 17++++++-----------
Minternal/inputs/env_test.go | 23++++++++++-------------
Mtests/expected.log | 3++-
Mtests/run.sh | 1+
9 files changed, 104 insertions(+), 53 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -66,7 +66,7 @@ func run() error { switch command { case cli.ReKeyCommand: if p.Confirm("proceed with rekey") { - return p.Transaction().ReKey() + return app.ReKey(p) } case cli.ListCommand: return app.List(p) diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -0,0 +1,81 @@ +package app + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/enckse/lockbox/internal/backend" + "github.com/enckse/lockbox/internal/cli" + "github.com/enckse/lockbox/internal/inputs" +) + +func getCommandLines(exe string, args ...string) ([]string, error) { + out, err := exec.Command(exe, args...).Output() + if err != nil { + return nil, err + } + return strings.Split(strings.TrimSpace(string(out)), "\n"), nil +} + +// ReKey handles entry rekeying +func ReKey(cmd *DefaultCommand) error { + exe, err := os.Executable() + if err != nil { + return err + } + env, err := inputs.GetReKey() + if err != nil { + return err + } + entries, err := getCommandLines(exe, cli.ListCommand) + if err != nil { + return err + } + writer := cmd.Writer() + for _, entry := range entries { + if _, err := fmt.Fprintf(writer, "rekeying: %s\n", entry); err != nil { + return err + } + stats, err := getCommandLines(exe, cli.StatsCommand, entry) + if err != nil { + return fmt.Errorf("failed to get modtime, command failed: %w", err) + } + modTime := "" + for _, stat := range stats { + if strings.HasPrefix(stat, backend.ModTimeField) { + if modTime != "" { + return errors.New("unable to read modtime, too many values") + } + modTime = strings.TrimPrefix(stat, backend.ModTimeField) + } + } + modTime = strings.TrimSpace(modTime) + if modTime == "" { + return errors.New("did not read modtime") + } + data, err := exec.Command(exe, cli.ShowCommand, entry).Output() + if err != nil { + return err + } + cmd := exec.Command(exe, cli.InsertCommand, entry) + cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", inputs.ModTimeEnv, modTime)) + in, err := cmd.StdinPipe() + if nil != err { + return err + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + go func() { + defer in.Close() + in.Write(data) + }() + if err := cmd.Run(); err != nil { + return err + } + } + return nil +} diff --git a/internal/backend/actions.go b/internal/backend/actions.go @@ -217,32 +217,6 @@ func splitComponents(path string) ([]string, string, error) { return parts, title, nil } -// ReKey will rekey the database to a new store output -func (t *Transaction) ReKey() error { - return t.act(func(c Context) error { - t.write = false - if err := inputs.SetReKey(); err != nil { - return err - } - n, err := NewTransaction() - if err != nil { - return err - } - if err := c.db.UnlockProtectedEntries(); err != nil { - return err - } - err = n.act(func(nCtx Context) error { - n.write = true - nCtx.db.Content.Root = c.db.Content.Root - return nCtx.db.LockProtectedEntries() - }) - if err != nil { - return err - } - return c.db.LockProtectedEntries() - }) -} - // Move will move a src object to a dst location func (t *Transaction) Move(src QueryEntity, dst string) error { if strings.TrimSpace(src.Path) == "" { diff --git a/internal/backend/query.go b/internal/backend/query.go @@ -133,7 +133,7 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { entity.Value = val case HashedValue, StatsValue: t := getValue(e.backing, modTimeKey) - res := fmt.Sprintf("modtime: %s", t) + res := fmt.Sprintf("%s %s", ModTimeField, t) if args.Values == HashedValue { res = fmt.Sprintf("%s\nhash: %x", res, sha512.Sum512([]byte(val))) } diff --git a/internal/backend/types.go b/internal/backend/types.go @@ -100,6 +100,8 @@ const ( pathSep = "/" isGlob = pathSep + "*" modTimeKey = "ModTime" + // ModTimeField is the stats field for modification time + ModTimeField = "modtime:" ) var errPath = errors.New("input paths must contain at LEAST 2 components") diff --git a/internal/inputs/env.go b/internal/inputs/env.go @@ -94,16 +94,15 @@ type ( SystemPlatform string ) -// SetReKey will enable the rekeying mode for the environment -func SetReKey() error { +// GetReKey will get the rekey environment settings +func GetReKey() ([]string, error) { hasStore := false hasKey := false hasKeyFile := false - var env []string + var out []string for _, k := range []string{keyModeEnv, keyEnv, KeyFileEnv, StoreEnv} { newKey := fmt.Sprintf("%s%s", k, reKeySuffix) val := os.Getenv(newKey) - envVal := "unset" if val != "" { switch k { case StoreEnv: @@ -113,17 +112,13 @@ func SetReKey() error { case KeyFileEnv: hasKeyFile = true } - envVal = "set" } - if err := os.Setenv(k, val); err != nil { - return err - } - env = append(env, fmt.Sprintf("%s=[%s]", newKey, envVal)) + out = append(out, fmt.Sprintf("%s=%s", k, val)) } if !hasStore || (!hasKey && !hasKeyFile) { - return fmt.Errorf("missing required environment variables for rekey: %s", strings.Join(env, " ")) + return nil, fmt.Errorf("missing required environment variables for rekey: %s", strings.Join(out, " ")) } - return nil + return out, nil } func toString(windows []ColorWindow) string { diff --git a/internal/inputs/env_test.go b/internal/inputs/env_test.go @@ -175,34 +175,31 @@ func TestReKey(t *testing.T) { os.Setenv("LOCKBOX_STORE_NEW", "") os.Setenv("LOCKBOX_KEY_NEW", "") os.Setenv("LOCKBOX_KEYFILE_NEW", "") - err := inputs.SetReKey() - if err == nil || err.Error() != "missing required environment variables for rekey: LOCKBOX_KEYMODE_NEW=[unset] LOCKBOX_KEY_NEW=[unset] LOCKBOX_KEYFILE_NEW=[unset] LOCKBOX_STORE_NEW=[unset]" { + _, err := inputs.GetReKey() + if err == nil || err.Error() != "missing required environment variables for rekey: LOCKBOX_KEYMODE= LOCKBOX_KEY= LOCKBOX_KEYFILE= LOCKBOX_STORE=" { t.Errorf("failed: %v", err) } os.Setenv("LOCKBOX_STORE_NEW", "abc") - err = inputs.SetReKey() - if err == nil || err.Error() != "missing required environment variables for rekey: LOCKBOX_KEYMODE_NEW=[unset] LOCKBOX_KEY_NEW=[unset] LOCKBOX_KEYFILE_NEW=[unset] LOCKBOX_STORE_NEW=[set]" { + _, err = inputs.GetReKey() + if err == nil || err.Error() != "missing required environment variables for rekey: LOCKBOX_KEYMODE= LOCKBOX_KEY= LOCKBOX_KEYFILE= LOCKBOX_STORE=abc" { t.Errorf("failed: %v", err) } - if os.Getenv("LOCKBOX_STORE") != "abc" { - t.Error("not set") - } os.Setenv("LOCKBOX_KEY_NEW", "aaa") - err = inputs.SetReKey() + out, err := inputs.GetReKey() if err != nil { t.Errorf("failed: %v", err) } - if os.Getenv("LOCKBOX_KEY") != "aaa" && os.Getenv("LOCKBOX_KEYFILE") == "" { - t.Error("not set") + if fmt.Sprintf("%v", out) != "[LOCKBOX_KEYMODE= LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_STORE=abc]" { + t.Errorf("invalid env: %v", out) } os.Setenv("LOCKBOX_KEY_NEW", "") os.Setenv("LOCKBOX_KEYFILE_NEW", "xxx") - err = inputs.SetReKey() + out, err = inputs.GetReKey() if err != nil { t.Errorf("failed: %v", err) } - if os.Getenv("LOCKBOX_KEYFILE") != "xxx" && os.Getenv("LOCKBOX_KEY") == "" { - t.Error("not set") + if fmt.Sprintf("%v", out) != "[LOCKBOX_KEYMODE= LOCKBOX_KEY= LOCKBOX_KEYFILE=xxx LOCKBOX_STORE=abc]" { + t.Errorf("invalid env: %v", out) } os.Setenv("LOCKBOX_KEY_NEW", "") os.Setenv("LOCKBOX_STORE_NEW", "") diff --git a/tests/expected.log b/tests/expected.log @@ -100,7 +100,8 @@ CALLED keys/k/one2 -proceed with rekey? (y/N) +proceed with rekey? (y/N) rekeying: keys/k/one2 + keys/k/one2 test2 clipboard will clear in 5 seconds diff --git a/tests/run.sh b/tests/run.sh @@ -87,6 +87,7 @@ _rekey() { local rekey rekeyFile rekey="$LOCKBOX_STORE.rekey.kdbx" rekeyFile="" + export LOCKBOX_HOOKDIR="" export LOCKBOX_STORE_NEW="$rekey" export LOCKBOX_KEY_NEW="newkey" export LOCKBOX_KEYMODE_NEW=plaintext