lockbox

password manager
Log | Files | Refs | README | LICENSE

commit ceecd65a687989792b00142dcceac15d3f67c541
parent 8d33a511c1e08acddf0350bbe807818367b4b172
Author: Sean Enck <sean@ttypty.com>
Date:   Sat, 10 Aug 2024 20:19:54 -0400

rewrite rekey to use a database credential switch

Diffstat:
Mcmd/main.go | 6+-----
Minternal/app/core.go | 32++++++++++++++------------------
Minternal/app/core_test.go | 2+-
Minternal/app/doc/rekey.txt | 11+++--------
Minternal/app/rekey.go | 123+++++++++++++++++++++++++------------------------------------------------------
Minternal/app/rekey_test.go | 169++++++++++++++++++++++++-------------------------------------------------------
Minternal/backend/actions.go | 12++++++++++++
Minternal/backend/actions_test.go | 24++++++++++++++++++++++++
Minternal/config/core.go | 20++++----------------
Minternal/config/vars.go | 37+++++++------------------------------
Minternal/config/vars_test.go | 33+++++++--------------------------
Mtests/expected.log | 1-
Mtests/run.sh | 14+++++++++++---
13 files changed, 173 insertions(+), 311 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -97,11 +97,7 @@ func run() error { } switch command { case app.ReKeyCommand: - keyer, err := app.NewDefaultKeyer() - if err != nil { - return err - } - return app.ReKey(p, keyer) + return app.ReKey(p) case app.ListCommand: return app.List(p) case app.MoveCommand: diff --git a/internal/app/core.go b/internal/app/core.go @@ -106,16 +106,8 @@ type ( CompletionsCommand string CompletionsEnv string ReKey struct { - Store string - KeyFile string - Key string - KeyMode string - ModMode string - ModModes struct { - Skip string - Error string - None string - } + KeyFile string + NoKey string } } ) @@ -169,6 +161,16 @@ func (a *DefaultCommand) IsPipe() bool { return platform.IsInputFromPipe() } +// ReadLine handles a single stdin read +func (a DefaultCommand) ReadLine() (string, error) { + return platform.Stdin(true) +} + +// Password is how a keyer gets the user's password for rekey +func (a DefaultCommand) Password() (string, error) { + return platform.ReadInteractivePassword() +} + // Input will read user input func (a *DefaultCommand) Input(pipe, multi bool) ([]byte, error) { return platform.GetUserInputPassword(pipe, multi) @@ -229,14 +231,8 @@ func Usage(verbose bool, exe string) ([]string, error) { CompletionsCommand: CompletionsCommand, CompletionsEnv: config.EnvDefaultCompletionKey, } - document.ReKey.Store = setDocFlag(config.ReKeyStoreFlag) - document.ReKey.Key = setDocFlag(config.ReKeyKeyFlag) - document.ReKey.KeyMode = setDocFlag(config.ReKeyKeyModeFlag) - document.ReKey.KeyFile = setDocFlag(config.ReKeyKeyModeFlag) - document.ReKey.ModMode = setDocFlag(config.ReKeyModModeFlag) - document.ReKey.ModModes.None = config.ReKeyModModeNone - document.ReKey.ModModes.Skip = config.ReKeyModModeSkip - document.ReKey.ModModes.Error = config.ReKeyModModeError + document.ReKey.KeyFile = setDocFlag(config.ReKeyKeyFileFlag) + document.ReKey.NoKey = config.ReKeyNoKeyFlag files, err := docs.ReadDir(docDir) if err != nil { return nil, err diff --git a/internal/app/core_test.go b/internal/app/core_test.go @@ -13,7 +13,7 @@ func TestUsage(t *testing.T) { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = app.Usage(true, "lb") - if len(u) != 109 { + if len(u) != 106 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { diff --git a/internal/app/doc/rekey.txt b/internal/app/doc/rekey.txt @@ -1,11 +1,6 @@ The password store can have the key (and file) changed via the '{{ $.ReKeyCommand }}' -subcommand. This command requires that '{{ $.ReKey.Store }}' is set and -a combination of new key settings are configured via '{{ $.ReKey.Key }}', '{{ $.ReKey.KeyMode }}', and '{{ $.ReKey.KeyFile }}' depending on the new database -credential preferences. The settings correspond to the 'LOCKBOX_' -settings normally used when running `{{ $.Executable }}`. Additionally -'{{ $.ReKey.ModMode }}' can be set to `{{ $.ReKey.ModModes.Skip }}` to skip -modtime issues, `{{ $.ReKey.ModModes.None }}` to disable modtime imports, or -`{{ $.ReKey.ModModes.Error }}` to ignore missing modification times (the -default). +subcommand. This command requires that a combination of new key +settings are configured via user input (unless `{{ $.ReKey.NoKey }}` is set) and '{{ $.ReKey.KeyFile }}' +depending on the new database credential preferences. Note that is an advanced feature and should be used with caution/backups/etc. diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -1,118 +1,71 @@ package app import ( - "encoding/json" "errors" "fmt" - "os" - "os/exec" - "strings" - "github.com/seanenck/lockbox/internal/backend" "github.com/seanenck/lockbox/internal/config" ) type ( - // Keyer defines how rekeying happens - Keyer interface { - JSON() (map[string]backend.JSON, error) - Insert(ReKeyEntry) error - } - // ReKeyEntry is an entry that is being rekeyed - ReKeyEntry struct { - Path string - Env []string - Data []byte - } - // DefaultKeyer is the default keyer for the application - DefaultKeyer struct { - exe string + // KeyerOptions defines how rekeying happens + KeyerOptions interface { + CommandOptions + IsPipe() bool + Password() (string, error) + ReadLine() (string, error) } ) -// NewDefaultKeyer initializes the default keyer -func NewDefaultKeyer() (DefaultKeyer, error) { - exe, err := os.Executable() - if err != nil { - return DefaultKeyer{}, err +func getNewPassword(pipe bool, against string, r KeyerOptions) (string, error) { + if pipe { + val, err := r.ReadLine() + if err != nil { + return "", err + } + return val, nil } - return DefaultKeyer{exe: exe}, nil -} - -// JSON will get the JSON backing entries -func (r DefaultKeyer) JSON() (map[string]backend.JSON, error) { - out, err := exec.Command(r.exe, JSONCommand).Output() + fmt.Print("new ") + p, err := r.Password() if err != nil { - return nil, err - } - var j map[string]backend.JSON - if err := json.Unmarshal(out, &j); err != nil { - return nil, err + return "", err } - return j, nil -} - -// Insert will insert the rekeying entry -func (r DefaultKeyer) Insert(entry ReKeyEntry) error { - cmd := exec.Command(r.exe, InsertCommand, entry.Path) - cmd.Env = append(os.Environ(), entry.Env...) - in, err := cmd.StdinPipe() - if nil != err { - return err + if against != "" { + if p != against { + return "", errors.New("rekey passwords do not match") + } } - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - go func() { - defer in.Close() - in.Write(entry.Data) - }() - return cmd.Run() + return p, nil } // ReKey handles entry rekeying -func ReKey(cmd CommandOptions, r Keyer) error { +func ReKey(cmd KeyerOptions) error { args := cmd.Args() vars, err := config.GetReKey(args) if err != nil { return err } - if !cmd.Confirm("proceed with rekey") { - return nil - } - if err := config.EnvJSONDataOutput.Set(string(config.JSONDataOutputRaw)); err != nil { - return err - } - entries, err := r.JSON() - if err != nil { - return err + piping := cmd.IsPipe() + if !piping { + if !cmd.Confirm("proceed with rekey") { + return nil + } } - writer := cmd.Writer() - for path, entry := range entries { - if _, err := fmt.Fprintf(writer, "rekeying: %s\n", path); err != nil { + var pass string + if !vars.NoKey { + first, err := getNewPassword(piping, "", cmd) + if err != nil { return err } - var modTime string - if vars.ModMode != config.ReKeyModModeNone { - modTime = strings.TrimSpace(entry.ModTime) - if modTime == "" { - switch vars.ModMode { - case config.ReKeyModModeSkip: - case config.ReKeyModModeError: - return errors.New("did not read modtime") - default: - return errors.New("unknown modtime control") - } + if !piping { + if _, err := getNewPassword(piping, first, cmd); err != nil { + return err } } - - var insertEnv []string - insertEnv = append(insertEnv, vars.Env...) - if modTime != "" { - insertEnv = append(insertEnv, config.EnvModTime.KeyValue(modTime)) - } - if err := r.Insert(ReKeyEntry{Path: path, Env: insertEnv, Data: []byte(entry.Data)}); err != nil { - return err + pass = first + if pass == "" { + return errors.New("password required but not given") } } - return nil + return cmd.Transaction().ReKey(pass, vars.KeyFile) } diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go @@ -2,9 +2,7 @@ package app_test import ( "bytes" - "errors" - "fmt" - "strings" + "io" "testing" "github.com/seanenck/lockbox/internal/app" @@ -13,142 +11,77 @@ import ( type ( mockKeyer struct { - data map[string][]byte - err error - items map[string]backend.JSON - rekeys [][]string + pass string + secondPass string + confirm bool + args []string + buf bytes.Buffer + t *testing.T + pipe bool } ) -func (m *mockKeyer) JSON() (map[string]backend.JSON, error) { - if m.err != nil { - return nil, m.err - } - return m.items, nil +func (m *mockKeyer) Confirm(string) bool { + return m.confirm } -func (m *mockKeyer) Insert(entry app.ReKeyEntry) error { - m.rekeys = append(m.rekeys, entry.Env) - if entry.Path == "error" { - return errors.New("bad insert") - } - return nil +func (m *mockKeyer) Transaction() *backend.Transaction { + return fullSetup(m.t, true) } -func TestErrors(t *testing.T) { - cmd := &mockCommand{} - cmd.confirm = false - cmd.buf = bytes.Buffer{} - m := &mockKeyer{} - cmd.args = []string{"-store", "store", "-key", "abc"} - if err := app.ReKey(cmd, m); err != nil { - t.Errorf("invalid error: %v", err) - } - cmd.confirm = true - m.err = errors.New("invalid call") - if err := app.ReKey(cmd, m); err == nil || err.Error() != "invalid call" { - t.Errorf("invalid error: %v", err) - } - m.err = nil - m.items = map[string]backend.JSON{"test": {ModTime: ""}} - if err := app.ReKey(cmd, m); err == nil || err.Error() != "did not read modtime" { - t.Errorf("invalid error: %v", err) - } - m.data = make(map[string][]byte) - m.data["test1"] = []byte{1} - m.data["error"] = []byte{2} - m.items = map[string]backend.JSON{"error": {ModTime: "2"}} - if err := app.ReKey(cmd, m); err == nil || err.Error() != "bad insert" { - t.Errorf("invalid error: %v", err) - } +func (m *mockKeyer) Args() []string { + return m.args +} + +func (m *mockKeyer) ReadLine() (string, error) { + return m.Password() +} + +func (m *mockKeyer) Password() (string, error) { + p := m.pass + m.pass = m.secondPass + m.secondPass = "" + return p, nil +} + +func (m *mockKeyer) IsPipe() bool { + return m.pipe +} + +func (m *mockKeyer) Writer() io.Writer { + return &m.buf } func TestReKey(t *testing.T) { - cmd := &mockCommand{} - cmd.confirm = true - cmd.buf = bytes.Buffer{} - cmd.args = []string{"-store", "store", "-key", "abc"} - if err := app.ReKey(cmd, &mockKeyer{}); err != nil { + newMockCommand(t) + mock := &mockKeyer{} + if err := app.ReKey(mock); err != nil { t.Errorf("invalid error: %v", err) } - if cmd.buf.String() != "" { - t.Error("no data") - } - m := &mockKeyer{} - m.items = map[string]backend.JSON{ - "test1": {ModTime: "1"}, - "test2": {ModTime: "2"}, - } - m.data = make(map[string][]byte) - m.data["test1"] = []byte{1} - m.data["test2"] = []byte{2} - cmd.buf = bytes.Buffer{} - if err := app.ReKey(cmd, m); err != nil { + mock.confirm = true + if err := app.ReKey(mock); err == nil || err.Error() != "password required but not given" { t.Errorf("invalid error: %v", err) } - if cmd.buf.String() == "" { - t.Error("invalid data") - } - if len(m.rekeys) != 2 { - t.Errorf("invalid results") - } - obj := fmt.Sprintf("%v", m.rekeys) - for _, idx := range []int{1, 2} { - if !strings.Contains(obj, fmt.Sprintf("LOCKBOX_SET_MODTIME=%d", idx)) { - t.Errorf("missing converted modtime: %s", obj) - } - } -} - -func modTimeKey(t *testing.T, mode string, modSet int) { - cmd := &mockCommand{} - cmd.confirm = true - cmd.buf = bytes.Buffer{} - cmd.args = []string{"-store", "store", "-key", "abc"} - m := &mockKeyer{} - m.items = map[string]backend.JSON{ - "test1": {ModTime: "1"}, - "test2": {ModTime: ""}, - } - m.data = make(map[string][]byte) - m.data["test1"] = []byte{1} - m.data["test2"] = []byte{2} - cmd.buf = bytes.Buffer{} - cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", mode} - if err := app.ReKey(cmd, m); err != nil { + mock.pass = "abc" + if err := app.ReKey(mock); err == nil || err.Error() != "rekey passwords do not match" { t.Errorf("invalid error: %v", err) } - if len(m.rekeys) != 2 { - t.Errorf("invalid results") - } - if strings.Count(fmt.Sprintf("%v", m.rekeys), "LOCKBOX_SET_MODTIME=") != modSet { - t.Errorf("invalid object: %v", m.rekeys) + mock.pass = "xyz" + mock.secondPass = "xyz" + if err := app.ReKey(mock); err != nil { + t.Errorf("invalid error: %v", err) } } -func TestReKeyModTime(t *testing.T) { - cmd := &mockCommand{} - cmd.confirm = true - cmd.buf = bytes.Buffer{} - cmd.args = []string{"-store", "store", "-key", "abc"} - m := &mockKeyer{} - m.items = map[string]backend.JSON{ - "test1": {ModTime: "1"}, - "test3": {ModTime: "a"}, - "test2": {ModTime: ""}, - } - m.data = make(map[string][]byte) - m.data["test1"] = []byte{1} - m.data["test2"] = []byte{2} - m.data["test3"] = []byte{4} - cmd.buf = bytes.Buffer{} - if err := app.ReKey(cmd, m); err == nil || err.Error() != "did not read modtime" { +func TestReKeyPipe(t *testing.T) { + newMockCommand(t) + mock := &mockKeyer{} + mock.pipe = true + if err := app.ReKey(mock); err == nil || err.Error() != "password required but not given" { t.Errorf("invalid error: %v", err) } - cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", "xyz"} - if err := app.ReKey(cmd, m); err == nil || err.Error() != "unknown modtime setting for import: xyz" { + mock.pass = "abc" + if err := app.ReKey(mock); err != nil { t.Errorf("invalid error: %v", err) } - modTimeKey(t, "none", 0) - modTimeKey(t, "skip", 1) } diff --git a/internal/backend/actions.go b/internal/backend/actions.go @@ -83,6 +83,18 @@ func (t *Transaction) act(cb action) error { return err } +// ReKey will change the credentials on a database +func (t *Transaction) ReKey(pass, keyFile string) error { + creds, err := getCredentials(pass, keyFile) + if err != nil { + return err + } + return t.change(func(c Context) error { + c.db.Credentials = creds + return nil + }) +} + func (t *Transaction) change(cb action) error { if t.readonly { return errors.New("unable to alter database in readonly mode") diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go @@ -300,3 +300,27 @@ func keyAndOrKeyFile(t *testing.T, key, keyFile bool) { } } } + +func TestReKey(t *testing.T) { + os.Clearenv() + f := "rekey_test.kdbx" + file := testFile(f) + defer os.Remove(filepath.Join(testDir, f)) + os.Setenv("LOCKBOX_READONLY", "no") + os.Setenv("LOCKBOX_STORE", file) + os.Setenv("LOCKBOX_KEY", "test") + os.Setenv("LOCKBOX_KEYMODE", "plaintext") + os.Setenv("LOCKBOX_TOTP", "totp") + os.Setenv("LOCKBOX_HOOKDIR", "") + os.Setenv("LOCKBOX_SET_MODTIME", "") + tr, err := backend.NewTransaction() + if err != nil { + t.Errorf("failed: %v", err) + } + if err := tr.ReKey("", ""); err == nil || err.Error() != "key and/or keyfile must be set" { + t.Errorf("no error: %v", err) + } + if err := tr.ReKey("abc", ""); err != nil { + t.Errorf("no error: %v", err) + } +} diff --git a/internal/config/core.go b/internal/config/core.go @@ -33,27 +33,15 @@ const ( // WindowsLinuxPlatform for WSL subsystems WindowsLinuxPlatform = "wsl" unknownPlatform = "" - // ReKeyStoreFlag is the flag used for rekey to set the store - ReKeyStoreFlag = "store" // ReKeyKeyFileFlag is the flag used for rekey to set the keyfile ReKeyKeyFileFlag = "keyfile" - // ReKeyKeyFlag is the flag used for rekey to set the key - ReKeyKeyFlag = "key" - // ReKeyKeyModeFlag is the flag used for rekey to set the key mode - ReKeyKeyModeFlag = "keymode" - // ReKeyModModeFlag indicates how to control modtime inserts - ReKeyModModeFlag = "modtime" + // ReKeyNoKeyFlag indicates no key is used for rekeying (e.g. keyfile only) + ReKeyNoKeyFlag = "nokey" // sub categories clipCategory keyCategory = "CLIP_" totpCategory keyCategory = "TOTP_" // YesValue are yes (on) values YesValue = yes - // ReKeyModModeSkip will skip modtime import issues - ReKeyModModeSkip = "skip" - // ReKeyModModeNone will not attempt to import modtimes at all - ReKeyModModeNone = "none" - // ReKeyModModeError will error on modtime import issues - ReKeyModModeError = "error" ) var ( @@ -124,8 +112,8 @@ type ( } // ReKeyArgs are the arguments for rekeying ReKeyArgs struct { - Env []string - ModMode string + NoKey bool + KeyFile string } ) diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -2,11 +2,11 @@ package config import ( + "errors" "flag" "fmt" "net/url" "os" - "slices" "sort" "strings" "time" @@ -210,40 +210,17 @@ This value can NOT be an expansion itself.`, // GetReKey will get the rekey environment settings func GetReKey(args []string) (ReKeyArgs, error) { set := flag.NewFlagSet("rekey", flag.ExitOnError) - store := set.String(ReKeyStoreFlag, "", "new store") - key := set.String(ReKeyKeyFlag, "", "new key") keyFile := set.String(ReKeyKeyFileFlag, "", "new keyfile") - keyMode := set.String(ReKeyKeyModeFlag, "", "new keymode") - modSetting := set.String(ReKeyModModeFlag, ReKeyModModeError, "import setting for modtime") + noKey := set.Bool(ReKeyNoKeyFlag, false, "disable password/key credential") if err := set.Parse(args); err != nil { return ReKeyArgs{}, err } - mod := *modSetting - if !slices.Contains([]string{ReKeyModModeError, ReKeyModModeSkip, ReKeyModModeNone}, mod) { - return ReKeyArgs{}, fmt.Errorf("unknown modtime setting for import: %s", mod) + noPass := *noKey + file := *keyFile + if strings.TrimSpace(file) == "" && noPass { + return ReKeyArgs{}, errors.New("a key or keyfile must be passed for rekey") } - type keyer struct { - env EnvironmentString - has bool - in string - } - check := func(in string, e EnvironmentString) keyer { - val := strings.TrimSpace(in) - return keyer{has: val != "", env: e, in: in} - } - inStore := check(*store, EnvStore) - inKey := check(*key, envKey) - inKeyFile := check(*keyFile, EnvKeyFile) - inKeyMode := check(*keyMode, envKeyMode) - var out []string - for _, k := range []keyer{inStore, inKey, inKeyFile, inKeyMode} { - out = append(out, k.env.KeyValue(k.in)) - } - if !inStore.has || (!inKey.has && !inKeyFile.has) { - return ReKeyArgs{}, fmt.Errorf("missing required arguments for rekey:\n -help for information on the flags or the lockbox help documentation for detailed usage") - } - sort.Strings(out) - return ReKeyArgs{Env: out, ModMode: mod}, nil + return ReKeyArgs{KeyFile: file, NoKey: noPass}, nil } // ListEnvironmentVariables will print information about env variables diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go @@ -99,42 +99,23 @@ func TestListVariables(t *testing.T) { } func TestReKey(t *testing.T) { - _, err := config.GetReKey([]string{}) - if err == nil || !strings.HasPrefix(err.Error(), "missing required arguments for rekey") { + if _, err := config.GetReKey([]string{"-nokey"}); err == nil || err.Error() != "a key or keyfile must be passed for rekey" { t.Errorf("failed: %v", err) } - _, err = config.GetReKey([]string{"-store", "abc"}) - if err == nil || !strings.HasPrefix(err.Error(), "missing required arguments for rekey") { - t.Errorf("failed: %v", err) - } - _, err = config.GetReKey([]string{"-store", "abc", "-key", "aaa", "-modtime", "xyz"}) - if err == nil || !strings.HasPrefix(err.Error(), "unknown modtime setting for import: xyz") { - t.Errorf("failed: %v", err) - } - out, err := config.GetReKey([]string{"-store", "abc", "-key", "aaa"}) + out, err := config.GetReKey([]string{}) if err != nil { t.Errorf("failed: %v", err) } - if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { - t.Errorf("invalid env: %v", out) - } - if out.ModMode != "error" { - t.Errorf("invalid modtime setting: %v", out) + if out.NoKey || out.KeyFile != "" { + t.Errorf("invalid args: %v", out) } - out, err = config.GetReKey([]string{"-store", "abc", "-keyfile", "aaa", "-modtime", "none"}) + out, err = config.GetReKey([]string{"-keyfile", "vars.go", "-nokey"}) if err != nil { t.Errorf("failed: %v", err) } - if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY= LOCKBOX_KEYFILE=aaa LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { - t.Errorf("invalid env: %v", out) - } - if out.ModMode != "none" { - t.Errorf("invalid modtime setting: %v", out) + if !out.NoKey || out.KeyFile != "vars.go" { + t.Errorf("invalid args: %v", out) } - os.Setenv("LOCKBOX_KEY_NEW", "") - os.Setenv("LOCKBOX_STORE_NEW", "") - os.Setenv("LOCKBOX_KEY_NEW", "") - os.Setenv("LOCKBOX_KEYFILE_NEW", "") } func TestFormatTOTP(t *testing.T) { diff --git a/tests/expected.log b/tests/expected.log @@ -141,7 +141,6 @@ CALLED keys/k/one2 -proceed with rekey? (y/N) rekeying: keys/k/one2 keys/k/one2 test2 diff --git a/tests/run.sh b/tests/run.sh @@ -153,14 +153,22 @@ printf "%-10s ... " "$1" ${LB_BINARY} ls echo # rekeying - REKEY="$LOCKBOX_STORE.rekey.kdbx" - REKEYFILE="" + REKEY_ARGS="" + NEWKEY="newkey$1" export LOCKBOX_HOOKDIR="" if [ -n "$LOCKBOX_KEYFILE" ]; then REKEYFILE="$DATA/newkeyfile" + REKEY_ARGS="-keyfile $REKEYFILE" echo "thisisanewkey" > "$REKEYFILE" + if [ -z "$LOCKBOX_KEY" ]; then + REKEY_ARGS="$REKEY_ARGS -nokey" + NEWKEY="" + fi fi - echo y |${LB_BINARY} rekey -store="$REKEY" -key="newkey$1" -keymode="plaintext" -keyfile="$REKEYFILE" + # shellcheck disable=SC2086 + echo "$NEWKEY" | ${LB_BINARY} rekey $REKEY_ARGS + export LOCKBOX_KEY="$NEWKEY" + export LOCKBOX_KEYFILE="$REKEYFILE" echo ${LB_BINARY} ls ${LB_BINARY} show keys/k/one2