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