lockbox

password manager
Log | Files | Refs | README | LICENSE

commit fcfdef653168425889c1b9388a442a5cb19107c3
parent bf3c7236c2700bb1f429582c5fb6c3fac615565d
Author: Sean Enck <sean@ttypty.com>
Date:   Sun,  3 Sep 2023 10:08:43 -0400

improved key read/handling

Diffstat:
Minternal/app/completions.go | 4++--
Minternal/app/completions_test.go | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Minternal/backend/actions.go | 5++---
Minternal/backend/actions_test.go | 2++
Minternal/config/core.go | 1+
Minternal/config/core_test.go | 7-------
Minternal/config/key.go | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Minternal/config/key_test.go | 196++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Minternal/config/vars.go | 8++++----
Minternal/platform/os.go | 26--------------------------
Minternal/platform/os_test.go | 34----------------------------------
Mtests/run.sh | 13+++++++++++--
12 files changed, 339 insertions(+), 167 deletions(-)

diff --git a/internal/app/completions.go b/internal/app/completions.go @@ -79,11 +79,11 @@ func GenerateCompletions(isBash, defaults bool, exe string) ([]string, error) { if noTOTP { isTOTP = false } - k, err := config.GetKey(true) + k, err := config.NewKey(config.IgnoreKeyMode) if err != nil { return nil, err } - if k != nil && k.Interactive() { + if k.Ask() { c.CanList = false } } diff --git a/internal/app/completions_test.go b/internal/app/completions_test.go @@ -1,7 +1,9 @@ package app_test import ( + "fmt" "os" + "strings" "testing" "github.com/enckse/lockbox/internal/app" @@ -12,42 +14,80 @@ func TestGenerateCompletions(t *testing.T) { testCompletions(t, false) } -func testCompletions(t *testing.T, bash bool) { - os.Setenv("LOCKBOX_NOTOTP", "yes") - os.Setenv("LOCKBOX_READONLY", "yes") - os.Setenv("LOCKBOX_NOCLIP", "yes") - defaults, _ := app.GenerateCompletions(bash, true, "lb") - roNoTOTPClip, _ := app.GenerateCompletions(bash, false, "lb") - if roNoTOTPClip[0] == defaults[0] { - t.Error("should not match defaults") - } +func generate(keys []string, bash bool, t *testing.T) (string, string) { os.Setenv("LOCKBOX_NOTOTP", "") - roNoClip, _ := app.GenerateCompletions(bash, false, "lb") - if roNoClip[0] == defaults[0] || roNoClip[0] == roNoTOTPClip[0] { - t.Error("should not equal defaults nor no totp/clip") - } os.Setenv("LOCKBOX_READONLY", "") - os.Setenv("LOCKBOX_NOCLIP", "yes") - noClip, _ := app.GenerateCompletions(bash, false, "lb") - if roNoClip[0] == noClip[0] || noClip[0] == defaults[0] || noClip[0] == roNoTOTPClip[0] { - t.Error("readonly/noclip != noclip (nor defaults, nor ro/no totp/clip)") - } - os.Setenv("LOCKBOX_READONLY", "yes") os.Setenv("LOCKBOX_NOCLIP", "") - ro, _ := app.GenerateCompletions(bash, false, "lb") - if roNoClip[0] == ro[0] || noClip[0] == ro[0] || ro[0] == defaults[0] || ro[0] == roNoTOTPClip[0] { - t.Error("readonly/noclip != ro (nor ro == noclip, nor ro == defaults)") + os.Setenv("LOCKBOX_KEYMODE", "") + key := "bash" + if !bash { + key = "zsh" + } + for _, k := range keys { + use := "yes" + if k == "KEYMODE" { + use = "ask" + } + os.Setenv(fmt.Sprintf("LOCKBOX_%s", k), use) + key = fmt.Sprintf("%s-%s", key, strings.ToLower(k)) + } + v, err := app.GenerateCompletions(bash, false, "lb") + if err != nil { + t.Errorf("invalid error: %v", err) + } + if len(v) != 1 { + t.Errorf("invalid result") + } + return key, v[0] +} + +func generateTest(keys []string, bash bool, t *testing.T) map[string]string { + r := make(map[string]string) + if len(keys) == 0 { + return r + } + k, v := generate(keys, bash, t) + r[k] = v + for _, cur := range keys { + var subset []string + for _, key := range keys { + if key == cur { + continue + } + subset = append(subset, key) + } + + for k, v := range generateTest(subset, bash, t) { + r[k] = v + } + } + return r +} + +func testCompletions(t *testing.T, bash bool) { + m := make(map[string]string) + defaults, _ := app.GenerateCompletions(bash, true, "lb") + m["defaults"] = defaults[0] + for k, v := range generateTest([]string{"NOTOTP", "READONLY", "NOCLIP", "KEYMODE"}, true, t) { + m[k] = v } + os.Setenv("LOCKBOX_KEYMODE", "") os.Setenv("LOCKBOX_READONLY", "") os.Setenv("LOCKBOX_NOCLIP", "") os.Setenv("LOCKBOX_NOTOTP", "") - isDefaultsToo, _ := app.GenerateCompletions(bash, false, "lb") - if isDefaultsToo[0] != defaults[0] { - t.Error("defaults should match env defaults") + defaultsToo, _ := app.GenerateCompletions(bash, false, "lb") + if defaultsToo[0] != defaults[0] || len(defaultsToo) != 1 || len(defaults) != 1 { + t.Error("defaults should match env defaults/invalid defaults detected") } - for _, confirm := range [][]string{defaults, roNoClip, noClip, ro, isDefaultsToo} { - if len(confirm) != 1 { - t.Error("completions returned an invalid array") + for k, v := range m { + fmt.Println(k) + for kOther, vOther := range m { + if kOther == k { + continue + } + if vOther == v { + t.Errorf("found overlapping completion: %s == %s", k, kOther) + } } } } diff --git a/internal/backend/actions.go b/internal/backend/actions.go @@ -31,15 +31,14 @@ func (t *Transaction) act(cb action) error { if !t.valid { return errors.New("invalid transaction") } - key, err := config.GetKey(false) + key, err := config.NewKey(config.DefaultKeyMode) if err != nil { return err } - useKey, err := platform.ReadKey(key, platform.ReadInteractivePassword) + k, err := key.Read(platform.ReadInteractivePassword) if err != nil { return err } - k := string(useKey) file := config.EnvKeyFile.Get() if !t.exists { if err := create(t.file, k, file); err != nil { diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go @@ -276,6 +276,8 @@ func keyAndOrKeyFile(t *testing.T, key, keyFile bool) { if key { os.Setenv("LOCKBOX_KEY", "test") os.Setenv("LOCKBOX_KEYMODE", "plaintext") + } else { + os.Setenv("LOCKBOX_KEYMODE", "none") } if keyFile { key := testFile("keyfileor.key") diff --git a/internal/config/core.go b/internal/config/core.go @@ -52,6 +52,7 @@ type ( key string desc string requirement string + whenUnset string } // EnvironmentInt are environment settings that are integers EnvironmentInt struct { diff --git a/internal/config/core_test.go b/internal/config/core_test.go @@ -227,10 +227,3 @@ func TestExpandParsed(t *testing.T) { t.Errorf("invalid expand: %v", r) } } - -func TestKey(t *testing.T) { - k := config.Key{} - if !k.Interactive() || k.Key() != nil { - t.Error("should be interactive without data") - } -} diff --git a/internal/config/key.go b/internal/config/key.go @@ -9,66 +9,110 @@ import ( ) type ( + // KeyModeType are valid ways to get the key + KeyModeType string + // AskPassword is a function to prompt for passwords (when required) + AskPassword func() (string, error) // Key is a wrapper to help manage the returned key Key struct { - key []byte + inputKey string + mode KeyModeType + valid bool } ) -// Interactive indicates if the key requires interactive input -func (e *Key) Interactive() bool { - return e.key == nil -} - -// Key returns the key data -func (e *Key) Key() []byte { - return e.key -} +const ( + plainKeyMode KeyModeType = "plaintext" + askKeyMode KeyModeType = "ask" + noKeyMode KeyModeType = "none" + // IgnoreKeyMode will ignore the value set in the key (acts like no key) + IgnoreKeyMode KeyModeType = "ignore" + commandKeyMode KeyModeType = "command" + // DefaultKeyMode is the default operating keymode if NOT set + DefaultKeyMode = commandKeyMode +) -// GetKey will get the encryption key setup for lb -func GetKey(dryrun bool) (*Key, error) { +// NewKey will create a new key +func NewKey(defaultKeyModeType KeyModeType) (Key, error) { useKey := envKey.Get() keyMode := envKeyMode.Get() - if keyMode == askKeyMode { + if keyMode == "" { + keyMode = string(defaultKeyModeType) + } + requireEmptyKey := false + switch keyMode { + case string(IgnoreKeyMode): + return Key{mode: IgnoreKeyMode, inputKey: "", valid: true}, nil + case string(noKeyMode): + requireEmptyKey = true + case string(commandKeyMode), string(plainKeyMode): + case string(askKeyMode): isInteractive, err := EnvInteractive.Get() if err != nil { - return nil, err + return Key{}, err } if !isInteractive { - return nil, errors.New("ask key mode requested in non-interactive mode") + return Key{}, errors.New("ask key mode requested in non-interactive mode") } - if useKey != "" { - return nil, errors.New("key can NOT be set in ask key mode") + requireEmptyKey = true + default: + return Key{}, fmt.Errorf("unknown key mode: %s", keyMode) + } + isEmpty := strings.TrimSpace(useKey) == "" + if requireEmptyKey { + if !isEmpty { + return Key{}, errors.New("key can NOT be set in this key mode") } - return &Key{}, nil + } else { + if isEmpty { + return Key{}, errors.New("key MUST be set in this key mode") + } + } + return Key{mode: KeyModeType(keyMode), inputKey: useKey, valid: true}, nil +} + +func (k Key) empty() bool { + return k.valid && len(k.inputKey) == 0 +} + +// Ask will indicate if prompting is required to get the key +func (k Key) Ask() bool { + return k.valid && k.mode == askKeyMode +} + +// Read will read the key as configured by the mode +func (k Key) Read(ask AskPassword) (string, error) { + if ask == nil { + return "", errors.New("invalid function given") } - if useKey == "" { - return nil, nil + if !k.valid { + return "", errors.New("invalid key given") } - if dryrun { - return &Key{key: []byte{0}}, nil + if k.empty() && !k.Ask() { + return "", nil } - var data []byte - switch keyMode { + useKey := k.inputKey + switch k.mode { + case askKeyMode: + read, err := ask() + if err != nil { + return "", err + } + useKey = read case commandKeyMode: parts, err := shlex(useKey) if err != nil { - return nil, err + return "", err } cmd := exec.Command(parts[0], parts[1:]...) b, err := cmd.Output() if err != nil { - return nil, fmt.Errorf("key command failed: %w", err) + return "", fmt.Errorf("key command failed: %w", err) } - data = b - case plainKeyMode: - data = []byte(useKey) - default: - return nil, errors.New("unknown keymode") + useKey = string(b) } - b := []byte(strings.TrimSpace(string(data))) - if len(b) == 0 { - return nil, errors.New("key is empty") + if strings.TrimSpace(useKey) == "" { + return "", errors.New("key is empty") } - return &Key{key: b}, nil + return useKey, nil } diff --git a/internal/config/key_test.go b/internal/config/key_test.go @@ -1,60 +1,204 @@ package config_test import ( + "errors" "os" + "strings" "testing" "github.com/enckse/lockbox/internal/config" ) -func TestGetKey(t *testing.T) { - os.Setenv("LOCKBOX_KEY", "aaa") - os.Setenv("LOCKBOX_KEYMODE", "lak;jfea") - if k, err := config.GetKey(false); err.Error() != "unknown keymode" || k != nil { +func TestDefaultKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "") + os.Setenv("LOCKBOX_KEY", "test") + if _, err := config.NewKey(config.IgnoreKeyMode); err != nil { t.Errorf("invalid error: %v", err) } - os.Setenv("LOCKBOX_KEYMODE", "plaintext") + os.Setenv("LOCKBOX_KEYMODE", "") os.Setenv("LOCKBOX_KEY", "") - if k, err := config.GetKey(false); err != nil || k != nil { + if _, err := config.NewKey(config.DefaultKeyMode); err == nil || err.Error() != "key MUST be set in this key mode" { + t.Errorf("invalid error: %v", err) + } +} + +func TestNewKeyErrors(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "invalid") + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "unknown key mode: invalid" { t.Errorf("invalid error: %v", err) } - os.Setenv("LOCKBOX_KEY", "key") - k, err := config.GetKey(false) - if err != nil || k == nil || string(k.Key()) != "key" || k.Interactive() { - t.Error("invalid key retrieval") + os.Setenv("LOCKBOX_KEYMODE", "none") + os.Setenv("LOCKBOX_KEY", " test") + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "key can NOT be set in this key mode" { + t.Errorf("invalid error: %v", err) } - os.Setenv("LOCKBOX_KEY", "key") - k, err = config.GetKey(true) - if err != nil || k == nil || len(k.Key()) != 1 || k.Key()[0] != 0 || k.Interactive() { - t.Error("invalid key retrieval") + os.Setenv("LOCKBOX_KEYMODE", "ask") + os.Setenv("LOCKBOX_KEY", "test") + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "key can NOT be set in this key mode" { + t.Errorf("invalid error: %v", err) } os.Setenv("LOCKBOX_KEYMODE", "command") - os.Setenv("LOCKBOX_KEY", "invalid command text is long and invalid via shlex") - if k, err := config.GetKey(false); err == nil || k != nil { - t.Error("should have failed") + os.Setenv("LOCKBOX_KEY", " ") + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "key MUST be set in this key mode" { + t.Errorf("invalid error: %v", err) } - os.Setenv("LOCKBOX_INTERACTIVE", "yes") - os.Setenv("LOCKBOX_KEYMODE", "ask") + os.Setenv("LOCKBOX_KEYMODE", "plaintext") os.Setenv("LOCKBOX_KEY", "") - if k, err := config.GetKey(false); err != nil || k == nil || !k.Interactive() { + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "key MUST be set in this key mode" { t.Errorf("invalid error: %v", err) } os.Setenv("LOCKBOX_INTERACTIVE", "yes") os.Setenv("LOCKBOX_KEYMODE", "ask") os.Setenv("LOCKBOX_KEY", "") - if k, err := config.GetKey(true); err != nil || k == nil || !k.Interactive() { + if _, err := config.NewKey(config.IgnoreKeyMode); err != nil { t.Errorf("invalid error: %v", err) } os.Setenv("LOCKBOX_INTERACTIVE", "no") + if _, err := config.NewKey(config.IgnoreKeyMode); err == nil || err.Error() != "ask key mode requested in non-interactive mode" { + t.Errorf("invalid error: %v", err) + } +} + +func TestAskKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "") + os.Setenv("LOCKBOX_KEY", "test") + k, _ := config.NewKey(config.IgnoreKeyMode) + if k.Ask() { + t.Error("invalid ask key") + } os.Setenv("LOCKBOX_KEYMODE", "ask") os.Setenv("LOCKBOX_KEY", "") - if k, err := config.GetKey(false); err == nil || err.Error() != "ask key mode requested in non-interactive mode" || k != nil { - t.Errorf("invalid error: %v", err) + os.Setenv("LOCKBOX_INTERACTIVE", "no") + k, _ = config.NewKey(config.IgnoreKeyMode) + if k.Ask() { + t.Error("invalid ask key") } - os.Setenv("LOCKBOX_INTERACTIVE", "yes") os.Setenv("LOCKBOX_KEYMODE", "ask") - os.Setenv("LOCKBOX_KEY", "aaa") - if k, err := config.GetKey(false); err == nil || err.Error() != "key can NOT be set in ask key mode" || k != nil { + os.Setenv("LOCKBOX_KEY", "") + os.Setenv("LOCKBOX_INTERACTIVE", "yes") + k, _ = config.NewKey(config.IgnoreKeyMode) + if !k.Ask() { + t.Error("invalid ask key") + } + fxn := func() (string, error) { + return "", errors.New("TEST") + } + _, err := k.Read(fxn) + if err == nil || err.Error() != "TEST" { + t.Errorf("invalid error: %v", err) + } + fxn = func() (string, error) { + return "", nil + } + _, err = k.Read(fxn) + if err == nil || err.Error() != "key is empty" { + t.Errorf("invalid error: %v", err) + } + fxn = func() (string, error) { + return "abc", nil + } + val, err := k.Read(fxn) + if err != nil || val != "abc" { + t.Errorf("invalid error: %v", err) + } +} + +func TestIgnoreKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "ignore") + os.Setenv("LOCKBOX_KEY", "test") + if _, err := config.NewKey(config.IgnoreKeyMode); err != nil { + t.Errorf("invalid error: %v", err) + } + os.Setenv("LOCKBOX_KEYMODE", "ignore") + os.Setenv("LOCKBOX_KEY", "") + if _, err := config.NewKey(config.IgnoreKeyMode); err != nil { + t.Errorf("invalid error: %v", err) + } +} + +func TestReadErrors(t *testing.T) { + k := config.Key{} + if _, err := k.Read(nil); err == nil || err.Error() != "invalid function given" { + t.Errorf("invalid error: %v", err) + } + fxn := func() (string, error) { + return "", nil + } + if _, err := k.Read(fxn); err == nil || err.Error() != "invalid key given" { + t.Errorf("invalid error: %v", err) + } +} + +func TestPlainKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "plaintext") + os.Setenv("LOCKBOX_KEY", " test ") + k, err := config.NewKey(config.IgnoreKeyMode) + if err != nil { + t.Errorf("invalid error: %v", err) + } + fxn := func() (string, error) { + return "", nil + } + val, err := k.Read(fxn) + if err != nil || val != " test " { + t.Errorf("invalid error: %v", err) + } +} + +func TestReadIgnoreOrNoKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "ignore") + os.Setenv("LOCKBOX_KEY", "test") + k, err := config.NewKey(config.IgnoreKeyMode) + if err != nil { + t.Errorf("invalid error: %v", err) + } + fxn := func() (string, error) { + return "", nil + } + val, err := k.Read(fxn) + if err != nil || val != "" { + t.Errorf("invalid error: %v", err) + } + os.Setenv("LOCKBOX_KEYMODE", "ignore") + os.Setenv("LOCKBOX_KEY", "") + k, err = config.NewKey(config.IgnoreKeyMode) + if err != nil { + t.Errorf("invalid error: %v", err) + } + val, err = k.Read(fxn) + if err != nil || val != "" { + t.Errorf("invalid error: %v", err) + } + os.Setenv("LOCKBOX_KEYMODE", "none") + k, err = config.NewKey(config.IgnoreKeyMode) + if err != nil { + t.Errorf("invalid error: %v", err) + } + val, err = k.Read(fxn) + if err != nil || val != "" { + t.Errorf("invalid error: %v", err) + } +} + +func TestCommandKey(t *testing.T) { + defer os.Clearenv() + os.Setenv("LOCKBOX_KEYMODE", "command") + os.Setenv("LOCKBOX_KEY", "thisisagarbagekey") + k, err := config.NewKey(config.IgnoreKeyMode) + if err != nil { + t.Errorf("invalid error: %v", err) + } + fxn := func() (string, error) { + return "", nil + } + _, err = k.Read(fxn) + if err == nil || !strings.HasPrefix(err.Error(), "key command failed:") { t.Errorf("invalid error: %v", err) } } diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -13,9 +13,6 @@ import ( const ( prefixKey = "LOCKBOX_" clipBaseEnv = prefixKey + "CLIP_" - plainKeyMode = "plaintext" - askKeyMode = "ask" - commandKeyMode = "command" commandArgsExample = "[cmd args...]" fileExample = "<file>" detectedValue = "<detected>" @@ -80,7 +77,7 @@ var ( EnvFormatTOTP = EnvironmentFormatter{environmentBase: environmentBase{key: EnvTOTPToken.key + "_FORMAT", desc: "Override the otpauth url used to store totp tokens. It must have ONE format\nstring ('%s') to insert the totp base code."}, fxn: formatterTOTP, allowed: "otpauth//url/%s/args..."} // EnvConfig is the location of the config file to read environment variables from EnvConfig = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "ENV", desc: fmt.Sprintf("Allows setting a specific file of environment variables for lockbox\nto read and use as configuration values (an '.env' file). The keyword\n'%s' will disable this functionality the keyword '%s' will search\nfor a file in the following paths in user's home directory matching\nthe first file found.\n\ndefault search paths:\n%v\n\nNote that this setting is not output as part of the environment.", noEnvironment, detectEnvironment, detectEnvironmentPaths)}, canDefault: true, defaultValue: detectEnvironment, allowed: []string{detectEnvironment, fileExample, noEnvironment}} - envKeyMode = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "KEYMODE", requirement: "must be set to a valid mode when using a key", desc: "How to retrieve the database store password."}, allowed: []string{askKeyMode, commandKeyMode, plainKeyMode}, canDefault: true, defaultValue: commandKeyMode} + envKeyMode = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "KEYMODE", requirement: "must be set to a valid mode when using a key", desc: fmt.Sprintf("How to retrieve the database store password.\nSet to '%s' when only using a key file\nSet to '%s' to ignore the set key value", noKeyMode, IgnoreKeyMode), whenUnset: string(DefaultKeyMode)}, allowed: []string{string(askKeyMode), string(commandKeyMode), string(IgnoreKeyMode), string(noKeyMode), string(plainKeyMode)}, canDefault: true, defaultValue: ""} envKey = EnvironmentString{environmentBase: environmentBase{requirement: requiredKeyOrKeyFile, key: prefixKey + "KEY", desc: fmt.Sprintf("The database key ('%s' mode) or command to run ('%s' mode)\nto retrieve the database password.", plainKeyMode, commandKeyMode)}, allowed: []string{commandArgsExample, "password"}, canDefault: false} envConfigExpands = EnvironmentInt{environmentBase: environmentBase{key: EnvConfig.key + "_EXPANDS", desc: "The maximum number of times to expand the input env to resolve variables,\nset to 0 to disable expansion. This value can NOT be an expansion itself\nif set in the env config file."}, shortDesc: "max expands", allowZero: true, defaultValue: 20} ) @@ -127,6 +124,9 @@ func ListEnvironmentVariables() []string { value, allow := item.values() if len(value) == 0 { value = "(unset)" + if env.whenUnset != "" { + value = env.whenUnset + } } description := strings.ReplaceAll(env.desc, "\n", "\n ") requirement := "optional/default" diff --git a/internal/platform/os.go b/internal/platform/os.go @@ -9,14 +9,10 @@ import ( "os" "strings" "syscall" - - "github.com/enckse/lockbox/internal/config" ) type ( stdinReaderFunc func(string) (bool, error) - // PasswordReader is for input password handling (stdin) as a db key - PasswordReader func() (string, error) ) func termEcho(on bool) { @@ -71,28 +67,6 @@ func GetUserInputPassword(piping, multiLine bool) ([]byte, error) { return []byte(password), nil } -// ReadKey will attempt to resolve a key (if interactive) for the platform -func ReadKey(key *config.Key, fxn PasswordReader) (string, error) { - if fxn == nil { - return "", errors.New("invalid function given") - } - if key == nil { - return "", nil - } - useKey := string(key.Key()) - if key.Interactive() { - read, err := fxn() - if err != nil { - return "", err - } - if len(read) == 0 { - return "", errors.New("interactive password can NOT be empty") - } - useKey = read - } - return useKey, nil -} - // ReadInteractivePassword will prompt for a single password for unlocking func ReadInteractivePassword() (string, error) { termEcho(false) diff --git a/internal/platform/os_test.go b/internal/platform/os_test.go @@ -1,12 +1,10 @@ package platform_test import ( - "errors" "os" "path/filepath" "testing" - "github.com/enckse/lockbox/internal/config" "github.com/enckse/lockbox/internal/platform" ) @@ -21,35 +19,3 @@ func TestPathExist(t *testing.T) { t.Error("test dir SHOULD exist") } } - -func TestReadKey(t *testing.T) { - o, err := platform.ReadKey(nil, nil) - if o != "" || err == nil || err.Error() != "invalid function given" { - t.Errorf("invalid error: %v", err) - } - fxn := func() (string, error) { - return "", nil - } - o, err = platform.ReadKey(nil, fxn) - if o != "" || err != nil { - t.Errorf("invalid error: %v", err) - } - o, err = platform.ReadKey(&config.Key{}, fxn) - if o != "" || err == nil || err.Error() != "interactive password can NOT be empty" { - t.Errorf("invalid error: %v", err) - } - fxn = func() (string, error) { - return "abc", errors.New("test error") - } - o, err = platform.ReadKey(&config.Key{}, fxn) - if o != "" || err == nil || err.Error() != "test error" { - t.Errorf("invalid error: %v", err) - } - fxn = func() (string, error) { - return "abc", nil - } - o, err = platform.ReadKey(&config.Key{}, fxn) - if o != "abc" || err != nil { - t.Errorf("invalid error: %v", err) - } -} diff --git a/tests/run.sh b/tests/run.sh @@ -14,7 +14,11 @@ _execute() { export LOCKBOX_TOTP=totp export LOCKBOX_INTERACTIVE=no export LOCKBOX_READONLY=no - export LOCKBOX_KEYMODE=plaintext + if [ "$LOCKBOX_KEY" == "" ]; then + export LOCKBOX_KEYMODE=none + else + export LOCKBOX_KEYMODE=plaintext + fi export LOCKBOX_JSON_DATA_HASH_LENGTH=0 echo test2 |${LB_BINARY} insert keys/k/one2 oldmode="$LOCKBOX_KEYMODE" @@ -125,8 +129,9 @@ _config() { } _invalid() { - local keyfile oldkey oldkeyfile + local keyfile oldkey oldkeyfile oldmode oldkey="$LOCKBOX_KEY" + oldmode="$LOCKBOX_KEYMODE" oldkeyfile="$LOCKBOX_KEYFILE" if [ -n "$LOCKBOX_KEYFILE" ]; then export LOCKBOX_KEYFILE="" @@ -138,9 +143,13 @@ _invalid() { echo "invalid" > "$keyfile" export LOCKBOX_KEYFILE="$keyfile" fi + if [ "$oldmode" == "none" ]; then + export LOCKBOX_KEYMODE="plaintext" + fi ${LB_BINARY} ls export LOCKBOX_KEYFILE="$oldkeyfile" export LOCKBOX_KEY="$oldkey" + export LOCKBOX_KEYMODE="$oldmode" } _rekey() {