lockbox

password manager
Log | Files | Refs | README | LICENSE

commit e13f01f8e083cac0fdd31d547e3233e5712e3133
parent fcd10fd994414c3523fca96c05b566ee03ecc221
Author: Sean Enck <sean@ttypty.com>
Date:   Sun,  5 Feb 2023 19:23:42 -0500

support rekey command

Diffstat:
Mcmd/main.go | 4++++
Dcontrib/rekey.sh | 24------------------------
Minternal/backend/actions.go | 25+++++++++++++++++++++++++
Minternal/cli/core.go | 4+++-
Minternal/cli/core_test.go | 2+-
Minternal/inputs/env.go | 10++++++++++
Mscripts/check.go | 11+++++++++++
Mscripts/tests.expected.log | 1+
8 files changed, 55 insertions(+), 26 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -115,6 +115,10 @@ func run() error { return wrapped("unable to build transaction model", err) } switch command { + case cli.ReKeyCommand: + if err := t.ReKey(); err != nil { + return wrapped("unable to rekey", err) + } case cli.ListCommand, cli.FindCommand: opts := backend.QueryOptions{} opts.Mode = backend.ListMode diff --git a/contrib/rekey.sh b/contrib/rekey.sh @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -if [ -z "$LOCKBOX_REKEY" ] && [ -z "$LOCKBOX_REKEYFILE" ]; then - echo "LOCKBOX_REKEY/LOCKBOX_REKEYFILE are not set properly for rekeying" - exit 1 -fi - -NEW_STORE="$(date +%Y%m%d%H%M%S).lb.kdbx" -_rekey() { - local entry modtime tmp - tmp=$(mktemp).kdbx - for entry in $(lb ls); do - modtime=$(lb stats "$entry" | grep '^modtime:' | cut -d ":" -f 2- | sed 's/^\s*//g') - echo "migrating: $entry" - if ! lb show "$entry" | LOCKBOX_HOOKDIR="" LOCKBOX_SET_MODTIME="$modtime" LOCKBOX_STORE="$tmp" LOCKBOX_KEY="$LOCKBOX_REKEY" LOCKBOX_KEYFILE="$LOCKBOX_REKEYFILE" lb insert "$entry" > /dev/null; then - echo "failed" - rm -f "$tmp" - exit 1 - fi - done - mv "$tmp" "$NEW_STORE" -} - -_rekey -echo "completed, '$NEW_STORE' created" diff --git a/internal/backend/actions.go b/internal/backend/actions.go @@ -217,6 +217,31 @@ func splitComponents(path string) ([]string, string, error) { return parts, title, nil } +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/cli/core.go b/internal/cli/core.go @@ -60,6 +60,7 @@ const ( BashCommand = "bash" // BashDefaultsCommand will generate environment agnostic completions BashDefaultsCommand = "-defaults" + ReKeyCommand = "key" ) //go:embed "completions.bash" @@ -164,7 +165,7 @@ func BashCompletions(defaults bool) ([]string, error) { c.CanClip = isClip c.ReadOnly = isReadOnly c.CanTOTP = isTOTP - options := []string{EnvCommand, FindCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, StatsCommand} + options := []string{EnvCommand, FindCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, StatsCommand, ReKeyCommand} if c.CanClip { options = append(options, ClipCommand) } @@ -205,6 +206,7 @@ func Usage() ([]string, error) { results = append(results, command(ListCommand, "", "list entries")) results = append(results, command(MoveCommand, "src dst", "move an entry from one location to another with the store")) results = append(results, command(RemoveCommand, "entry", "remove an entry from the store")) + results = append(results, command(ReKeyCommand, "", "rekey the database")) results = append(results, command(ShowCommand, "entry", "show the entry's value")) results = append(results, command(StatsCommand, "entry", "display entry detail information")) results = append(results, command(TOTPCommand, "entry", "display an updating totp generated code")) diff --git a/internal/cli/core_test.go b/internal/cli/core_test.go @@ -9,7 +9,7 @@ import ( func TestUsage(t *testing.T) { u, _ := cli.Usage() - if len(u) != 21 { + if len(u) != 22 { t.Errorf("invalid usage, out of date? %d", len(u)) } } diff --git a/internal/inputs/env.go b/internal/inputs/env.go @@ -14,6 +14,16 @@ import ( "github.com/google/shlex" ) +func SetReKey() error { + for _, k := range []string{keyModeEnv, keyEnv, KeyFileEnv, StoreEnv} { + val := os.Getenv(fmt.Sprintf("%s_NEW", k)) + if err := os.Setenv(k, val); err != nil { + return err + } + } + return nil +} + const ( otpAuth = "otpauth" otpIssuer = "lbissuer" diff --git a/scripts/check.go b/scripts/check.go @@ -124,4 +124,15 @@ func main() { rm("keys/k2/*") fmt.Println() ls() + reKeyStore := fmt.Sprintf("%s.rekey.kdbx", store) + reKey := "rekey" + os.Setenv("LOCKBOX_STORE_NEW", reKeyStore) + os.Setenv("LOCKBOX_KEY_NEW", reKey) + os.Setenv("LOCKBOX_KEYMODE_NEW", "plaintext") + os.Setenv("LOCKBOX_KEYFILE_NEW", "") + runCommand([]string{"key"}, nil) + os.Setenv("LOCKBOX_STORE", reKeyStore) + os.Setenv("LOCKBOX_KEYFILE", "") + os.Setenv("LOCKBOX_KEY", reKey) + ls() } diff --git a/scripts/tests.expected.log b/scripts/tests.expected.log @@ -116,3 +116,4 @@ post rm keys/k2/t2/one2 CALLED keys/k/one2 +keys/k/one2