commit fc0c6c317501548ecb60f69796f626693d96c981
parent 98bbda36808e6abb99bdf309b7e5cd23b29d42a2
Author: Sean Enck <sean@ttypty.com>
Date: Wed, 28 Dec 2022 18:46:27 -0500
support keyfile
Diffstat:
7 files changed, 64 insertions(+), 7 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,6 +7,7 @@ MAN := $(BUILD)lb.man
DOCTEXT := scripts/doc.sections
ACTUAL := $(BUILD)actual.log
DATE := $(shell date +%Y-%m-%d)
+RUNS := -keyfile=true -keyfile=false
.PHONY: $(TESTDIR)
@@ -21,9 +22,11 @@ $(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions*
$(TESTDIR):
cd $@ && go test
-check: $(TARGET) $(TESTDIR)
+check: $(TARGET) $(TESTDIR) $(RUNS)
+
+$(RUNS):
rm -f $(BUILD)*.kdbx
- LB_BUILD=$(TARGET) TEST_DATA=$(BUILD) SCRIPTS=$(PWD)/scripts/ go run scripts/check.go 2>&1 | sed "s#$(PWD)/$(DATA)##g" | sed 's/^[0-9][0-9][0-9][0-9][0-9][0-9]$$/XXXXXX/g' | sed 's/modtime: $(DATE).*/modtime: XXXX-XX-XX/g' > $(ACTUAL)
+ LB_BUILD=$(TARGET) TEST_DATA=$(BUILD) SCRIPTS=$(PWD)/scripts/ go run scripts/check.go $@ 2>&1 | sed "s#$(PWD)/$(DATA)##g" | sed 's/^[0-9][0-9][0-9][0-9][0-9][0-9]$$/XXXXXX/g' | sed 's/modtime: $(DATE).*/modtime: XXXX-XX-XX/g' > $(ACTUAL)
diff -u $(ACTUAL) scripts/tests.expected.log
clean:
diff --git a/internal/backend/actions.go b/internal/backend/actions.go
@@ -65,8 +65,9 @@ func (t *Transaction) act(cb action) error {
return err
}
k := string(key)
+ file := inputs.EnvOrDefault(inputs.KeyFileEnv, "")
if !t.exists {
- if err := create(t.file, k); err != nil {
+ if err := create(t.file, k, file); err != nil {
return err
}
}
@@ -76,7 +77,11 @@ func (t *Transaction) act(cb action) error {
}
defer f.Close()
db := gokeepasslib.NewDatabase()
- db.Credentials = gokeepasslib.NewPasswordCredentials(k)
+ creds, err := getCredentials(k, file)
+ if err != nil {
+ return err
+ }
+ db.Credentials = creds
if err := gokeepasslib.NewDecoder(f).Decode(db); err != nil {
return err
}
diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go
@@ -17,6 +17,7 @@ func fullSetup(t *testing.T, keep bool) *backend.Transaction {
os.Setenv("LOCKBOX_READONLY", "no")
os.Setenv("LOCKBOX_STORE", "test.kdbx")
os.Setenv("LOCKBOX_KEY", "test")
+ os.Setenv("LOCKBOX_KEYFILE", "")
os.Setenv("LOCKBOX_KEYMODE", "plaintext")
os.Setenv("LOCKBOX_TOTP", "totp")
os.Setenv("LOCKBOX_HOOKDIR", "")
@@ -27,6 +28,26 @@ func fullSetup(t *testing.T, keep bool) *backend.Transaction {
return tr
}
+func TestKeyFile(t *testing.T) {
+ os.Remove("file.key")
+ os.Remove("keyfile_test.kdbx")
+ os.Setenv("LOCKBOX_READONLY", "no")
+ os.Setenv("LOCKBOX_STORE", "keyfile_test.kdbx")
+ os.Setenv("LOCKBOX_KEY", "test")
+ os.Setenv("LOCKBOX_KEYFILE", "file.key.kdbx")
+ os.Setenv("LOCKBOX_KEYMODE", "plaintext")
+ os.Setenv("LOCKBOX_TOTP", "totp")
+ os.Setenv("LOCKBOX_HOOKDIR", "")
+ os.WriteFile("file.key.kdbx", []byte("test"), 0644)
+ tr, err := backend.NewTransaction()
+ if err != nil {
+ t.Errorf("failed: %v", err)
+ }
+ if err := tr.Insert(backend.NewPath("a", "b"), "t"); err != nil {
+ t.Errorf("no error: %v", err)
+ }
+}
+
func setup(t *testing.T) *backend.Transaction {
return fullSetup(t, false)
}
diff --git a/internal/backend/core.go b/internal/backend/core.go
@@ -37,11 +37,25 @@ func NewTransaction() (*Transaction, error) {
return loadFile(os.Getenv(inputs.StoreEnv), false)
}
-func create(file, key string) error {
+func getCredentials(key, keyFile string) (*gokeepasslib.DBCredentials, error) {
+ if len(keyFile) > 0 {
+ if !pathExists(keyFile) {
+ return nil, errors.New("no keyfile found on disk")
+ }
+ return gokeepasslib.NewPasswordAndKeyCredentials(key, keyFile)
+ }
+ return gokeepasslib.NewPasswordCredentials(key), nil
+}
+
+func create(file, key, keyFile string) error {
root := gokeepasslib.NewGroup()
root.Name = "root"
db := gokeepasslib.NewDatabase(gokeepasslib.WithDatabaseKDBXVersion4())
- db.Credentials = gokeepasslib.NewPasswordCredentials(key)
+ creds, err := getCredentials(key, keyFile)
+ if err != nil {
+ return err
+ }
+ db.Credentials = creds
db.Content.Root =
&gokeepasslib.RootData{
Groups: []gokeepasslib.Group{root},
diff --git a/internal/inputs/env.go b/internal/inputs/env.go
@@ -26,6 +26,8 @@ const (
formatTOTPEnv = fieldTOTPEnv + "_FORMAT"
keyModeEnv = prefixKey + "KEYMODE"
keyEnv = prefixKey + "KEY"
+ // KeyFileEnv is an OPTIONAL keyfile for the database
+ KeyFileEnv = prefixKey + "KEYFILE"
plainKeyMode = "plaintext"
commandKeyMode = "command"
// PlatformEnv is the platform lb is running on.
@@ -292,5 +294,6 @@ func ListEnvironmentVariables(showValues bool) []string {
results = append(results, e.formatEnvironmentVariable(false, noTOTPEnv, isNo, "disable TOTP integrations", isYesNoArgs))
results = append(results, e.formatEnvironmentVariable(false, HookDirEnv, "", "the path to hooks to execute on actions against the database", []string{"directory"}))
results = append(results, e.formatEnvironmentVariable(false, clipOSC52Env, isNo, "enable OSC52 clipboard mode", isYesNoArgs))
+ results = append(results, e.formatEnvironmentVariable(false, KeyFileEnv, "", "additional keyfile to access/protect the database", []string{"keyfile"}))
return results
}
diff --git a/internal/inputs/env_test.go b/internal/inputs/env_test.go
@@ -166,7 +166,7 @@ func TestListVariables(t *testing.T) {
known[trim] = struct{}{}
}
l := len(known)
- if l != 17 {
+ if l != 18 {
t.Errorf("invalid env count, outdated? %d", l)
}
}
diff --git a/scripts/check.go b/scripts/check.go
@@ -3,6 +3,7 @@ package main
import (
"bytes"
+ "flag"
"fmt"
"os"
"os/exec"
@@ -56,7 +57,16 @@ func totpList() {
}
func main() {
+ keyFile := flag.Bool("keyfile", false, "enable keyfile")
+ flag.Parse()
path := os.Getenv("TEST_DATA")
+ useKeyFile := ""
+ if *keyFile {
+ useKeyFile = filepath.Join(path, "test.key")
+ if err := os.WriteFile(useKeyFile, []byte("thisisatest"), 0644); err != nil {
+ die("unable to write keyfile", err)
+ }
+ }
store := filepath.Join(path, fmt.Sprintf("%s.kdbx", time.Now().Format("20060102150405")))
os.Setenv("LOCKBOX_HOOKDIR", "")
os.Setenv("LOCKBOX_STORE", store)
@@ -65,6 +75,7 @@ func main() {
os.Setenv("LOCKBOX_INTERACTIVE", "no")
os.Setenv("LOCKBOX_READONLY", "no")
os.Setenv("LOCKBOX_KEYMODE", "plaintext")
+ os.Setenv("LOCKBOX_KEYFILE", useKeyFile)
insert("keys/k/one2", []string{"test2"})
for _, k := range []string{"keys/k/one", "key/a/one", "keys/k/one", "keys/k/one/", "/keys/k/one", "keys/aa/b//s///e"} {
insert(k, []string{"test"})