lockbox

password manager
Log | Files | Refs | README | LICENSE

commit aee4561166ec184eb93e87188ea5354d22bea6d8
parent 03474c5cdfe847ff9540afc64b335a53cf90e2ed
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  1 Oct 2022 11:38:56 -0400

working through remaining bugs

Diffstat:
Mcmd/main.go | 2+-
Minternal/backend/query.go | 14++++++--------
Minternal/backend/transact.go | 56+++++++++++++++++++++++++++++++++++---------------------
Mtests/expected.log | 11++++++++---
Mtests/run.sh | 2++
5 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -128,7 +128,7 @@ func run() *programError { return newError("invalid input", err) } p := strings.TrimSpace(string(password)) - if err := t.Insert(entry, p, len(strings.Split(p, "\n")) > 1); err != nil { + if err := t.Insert(entry, p, existing, len(strings.Split(p, "\n")) > 1); err != nil { return newError("failed to insert", err) } fmt.Println("") diff --git a/internal/backend/query.go b/internal/backend/query.go @@ -76,7 +76,8 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { var keys []string entities := make(map[string]QueryEntity) isSort := args.Mode == ListMode || args.Mode == FindMode || args.Mode == SuffixMode - err := t.Act(func(ctx Context) error { + decrypt := args.Values != BlankValue + err := t.act(func(ctx Context) error { for idx, entry := range ctx.db.Content.Root.Groups[0].Entries { path := getPathName(entry) if isSort { @@ -101,6 +102,9 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { keys = append(keys, path) entities[path] = QueryEntity{backing: entry, Index: idx} } + if decrypt { + return ctx.db.UnlockProtectedEntries() + } return nil }) if err != nil { @@ -110,12 +114,6 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { sort.Strings(keys) } var results []QueryEntity - decrypt := args.Values != BlankValue - if decrypt { - if err := t.db.UnlockProtectedEntries(); err != nil { - return nil, err - } - } for _, k := range keys { entity := QueryEntity{Path: k} if args.Values != BlankValue { @@ -129,7 +127,7 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { case SecretValue: entity.Value = val case HashedValue: - entity.Value = fmt.Sprintf("%x", sha512.New().Sum([]byte(val))) + entity.Value = fmt.Sprintf("%x", sha512.Sum512([]byte(val))) } } results = append(results, entity) diff --git a/internal/backend/transact.go b/internal/backend/transact.go @@ -20,19 +20,22 @@ const ( ) type ( - Action func(t Context) error + // action are transcation operations that more or less CRUD the kdbx file + action func(t Context) error + // Transaction handles the overall operation of the transaction Transaction struct { valid bool file string exists bool - db *gokeepasslib.Database write bool } + // Context handles operating on the underlying database Context struct { db *gokeepasslib.Database } ) +// Load will load a kdbx file for transactions func Load(file string) (*Transaction, error) { return loadFile(file, true) } @@ -50,6 +53,7 @@ func loadFile(file string, must bool) (*Transaction, error) { return &Transaction{valid: true, file: file, exists: exists}, nil } +// NewTransaction will use the underlying environment data store location func NewTransaction() (*Transaction, error) { return loadFile(os.Getenv(inputs.StoreEnv), false) } @@ -75,7 +79,7 @@ func encode(f *os.File, db *gokeepasslib.Database) error { return gokeepasslib.NewEncoder(f).Encode(db) } -func (t *Transaction) Act(cb Action) error { +func (t *Transaction) act(cb action) error { if !t.valid { return errors.New("invalid transaction") } @@ -102,10 +106,9 @@ func (t *Transaction) Act(cb Action) error { if len(db.Content.Root.Groups) != 1 { return errors.New("kdbx must only have ONE root group") } - t.db = db - cErr := cb(Context{db: t.db}) + cErr := cb(Context{db: db}) if t.write { - if err := t.db.LockProtectedEntries(); err != nil { + if err := db.LockProtectedEntries(); err != nil { return err } if err := f.Close(); err != nil { @@ -116,13 +119,13 @@ func (t *Transaction) Act(cb Action) error { return err } defer f.Close() - return encode(f, t.db) + return encode(f, db) } return cErr } -func (t *Transaction) change(cb Action) error { - return t.Act(func(c Context) error { +func (t *Transaction) change(cb action) error { + return t.act(func(c Context) error { if err := c.db.UnlockProtectedEntries(); err != nil { return err } @@ -131,8 +134,14 @@ func (t *Transaction) change(cb Action) error { }) } -func (t *Transaction) Insert(path, val string, multi bool) error { +// Insert handles inserting a new element +func (t *Transaction) Insert(path, val string, entity *QueryEntity, multi bool) error { return t.change(func(c Context) error { + if entity != nil { + if err := remove(entity, c); err != nil { + return err + } + } e := gokeepasslib.NewEntry() e.Values = append(e.Values, value(titleKey, filepath.Dir(path))) e.Values = append(e.Values, value(userNameKey, filepath.Base(path))) @@ -147,22 +156,27 @@ func (t *Transaction) Insert(path, val string, multi bool) error { }) } +func remove(entity *QueryEntity, c Context) error { + entries := c.db.Content.Root.Groups[0].Entries + if entity.Index >= len(entries) { + return errors.New("index out of bounds") + } + e := entries[entity.Index] + n := getPathName(e) + if n != entity.Path { + return errors.New("validation failed, index/name mismatch") + } + c.db.Content.Root.Groups[0].Entries = append(entries[:entity.Index], entries[entity.Index+1:]...) + return nil +} + +// Remove handles remove an element func (t *Transaction) Remove(entity *QueryEntity) error { if entity == nil { return errors.New("entity is empty/invalid") } return t.change(func(c Context) error { - entries := c.db.Content.Root.Groups[0].Entries - if entity.Index >= len(entries) { - return errors.New("index out of bounds") - } - e := entries[entity.Index] - n := getPathName(e) - if n != entity.Path { - return errors.New("validation failed, index/name mismatch") - } - c.db.Content.Root.Groups[0].Entries = append(entries[:entity.Index], entries[entity.Index+1:]...) - return nil + return remove(entity, c) }) } diff --git a/tests/expected.log b/tests/expected.log @@ -1,12 +1,15 @@ + keys/one keys/one2 keys2/three delete entry? (y/N) +keys/one keys/one2 keys2/three +keys/one keys/one2 keys2/three test2 @@ -15,11 +18,13 @@ test4 test/totp XXXXXX +keys/one: +hash:ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff keys/one2: -hash:7465737432cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +hash:6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9 keys2/three: -hash:74657374330a7465737434cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +hash:132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f test/totp: -hash:35616534373261627164656b6a71796b6f79786b37687663326c656b6c71356ecf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +hash:7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1 delete entry? (y/N) delete entry? (y/N) \ No newline at end of file diff --git a/tests/run.sh b/tests/run.sh @@ -14,6 +14,7 @@ mkdir -p $TESTS _run() { echo "test" | "$BIN/lb" insert keys/one echo "test2" | "$BIN/lb" insert keys/one2 + echo "test" | "$BIN/lb" insert keys/one echo -e "test3\ntest4" | "$BIN/lb" insert keys2/three "$BIN/lb" ls yes 2>/dev/null | "$BIN/lb" rm keys/one @@ -29,6 +30,7 @@ _run() { yes 2>/dev/null | "$BIN/lb" rm keys2/three echo yes 2>/dev/null | "$BIN/lb" rm test/totp + yes 2>/dev/null | "$BIN/lb" rm test/one } _run 2>&1 | sed "s#$LOCKBOX_STORE##g" > $TESTS/actual.log