commit e0ec888a9a9e52f284d80e3ac2a470adfb4fc4ec
parent a67d62dde8daa86f3ea90059910a6198d8b49036
Author: Sean Enck <sean@ttypty.com>
Date: Tue, 12 Jul 2022 18:55:34 -0400
updating lockbox to be internally maintained without stock and adding hooks
Diffstat:
11 files changed, 160 insertions(+), 89 deletions(-)
diff --git a/cmd/lb-diff/main.go b/cmd/lb-diff/main.go
@@ -5,18 +5,17 @@ import (
"os"
"voidedtech.com/lockbox/internal"
- "voidedtech.com/stock"
)
func main() {
args := os.Args
l, err := internal.NewLockbox("", "", args[len(args)-1])
if err != nil {
- stock.Die("unable to make lockbox model instance", err)
+ internal.Die("unable to make lockbox model instance", err)
}
result, err := l.Decrypt()
if err != nil {
- stock.Die("unable to read file", err)
+ internal.Die("unable to read file", err)
}
if result != nil {
fmt.Println(string(result))
diff --git a/cmd/lb-pwgen/main.go b/cmd/lb-pwgen/main.go
@@ -11,7 +11,7 @@ import (
"strings"
"time"
- "voidedtech.com/stock"
+ "voidedtech.com/lockbox/internal"
)
const (
@@ -41,15 +41,15 @@ func main() {
var paths []string
parts := strings.Split(src, ":")
for _, p := range parts {
- if stock.PathExists(p) {
+ if internal.PathExists(p) {
info, err := os.Stat(p)
if err != nil {
- stock.Die("unable to stat", err)
+ internal.Die("unable to stat", err)
}
if info.IsDir() {
files, err := os.ReadDir(p)
if err != nil {
- stock.Die("failed to read directory", err)
+ internal.Die("failed to read directory", err)
}
var results []string
for _, f := range files {
@@ -62,7 +62,7 @@ func main() {
}
}
if len(paths) == 0 {
- stock.Die("no paths found for generation", stock.NewBasicError("unable to read paths"))
+ internal.Die("no paths found for generation", internal.NewLockboxError("unable to read paths"))
}
result := ""
l := *length
@@ -92,37 +92,37 @@ func main() {
name = newValue
case transformModeSed:
if len(sedPattern) == 0 {
- stock.Die("unable to use sed transform without pattern", stock.NewBasicError("set PWGEN_SED"))
+ internal.Die("unable to use sed transform without pattern", internal.NewLockboxError("set PWGEN_SED"))
}
cmd := exec.Command("sed", "-e", sedPattern)
stdin, err := cmd.StdinPipe()
if err != nil {
- stock.Die("unable to attach stdin to sed", err)
+ internal.Die("unable to attach stdin to sed", err)
}
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
- stock.Die("failed to run sed", err)
+ internal.Die("failed to run sed", err)
}
if _, err := io.WriteString(stdin, name); err != nil {
stdin.Close()
- stock.Die("write to stdin failed for sed", err)
+ internal.Die("write to stdin failed for sed", err)
}
stdin.Close()
if err := cmd.Wait(); err != nil {
- stock.Die("sed failed", err)
+ internal.Die("sed failed", err)
}
errors := strings.TrimSpace(stderr.String())
if len(errors) > 0 {
- stock.Die("sed stderr failure", stock.NewBasicError(errors))
+ internal.Die("sed stderr failure", internal.NewLockboxError(errors))
}
name = strings.TrimSpace(stdout.String())
case transformModeNone:
break
default:
- stock.Die("unknown transform mode", stock.NewBasicError(transform))
+ internal.Die("unknown transform mode", internal.NewLockboxError(transform))
}
result += name
}
diff --git a/cmd/lb-rekey/main.go b/cmd/lb-rekey/main.go
@@ -6,7 +6,6 @@ import (
"strings"
"voidedtech.com/lockbox/internal"
- "voidedtech.com/stock"
)
func main() {
@@ -17,24 +16,24 @@ func main() {
flag.Parse()
found, err := internal.Find(internal.GetStore(), false)
if err != nil {
- stock.Die("failed finding entries", err)
+ internal.Die("failed finding entries", err)
}
for _, file := range found {
fmt.Printf("rekeying: %s\n", file)
in, err := internal.NewLockbox(*inKey, *inMode, file)
if err != nil {
- stock.Die("unable to make input lockbox", err)
+ internal.Die("unable to make input lockbox", err)
}
decrypt, err := in.Decrypt()
if err != nil {
- stock.Die("failed to process file decryption", err)
+ internal.Die("failed to process file decryption", err)
}
out, err := internal.NewLockbox(*outKey, *outMode, file)
if err != nil {
- stock.Die("unable to make output lockbox", err)
+ internal.Die("unable to make output lockbox", err)
}
if err := out.Encrypt([]byte(strings.TrimSpace(string(decrypt)))); err != nil {
- stock.Die("failed to encrypt file", err)
+ internal.Die("failed to encrypt file", err)
}
}
}
diff --git a/cmd/lb-rw/main.go b/cmd/lb-rw/main.go
@@ -5,7 +5,6 @@ import (
"fmt"
"voidedtech.com/lockbox/internal"
- "voidedtech.com/stock"
)
func main() {
@@ -16,20 +15,20 @@ func main() {
flag.Parse()
l, err := internal.NewLockbox(*key, *keyMode, *file)
if err != nil {
- stock.Die("unable to make lockbox model instance", err)
+ internal.Die("unable to make lockbox model instance", err)
}
switch *mode {
case "encrypt":
if err := l.Encrypt(nil); err != nil {
- stock.Die("failed to encrypt", err)
+ internal.Die("failed to encrypt", err)
}
case "decrypt":
results, err := l.Decrypt()
if err != nil {
- stock.Die("failed to decrypt", err)
+ internal.Die("failed to decrypt", err)
}
fmt.Println(string(results))
default:
- stock.Die("invalid mode", stock.NewBasicError("bad mode"))
+ internal.Die("invalid mode", internal.NewLockboxError("bad mode"))
}
}
diff --git a/cmd/lb-totp/main.go b/cmd/lb-totp/main.go
@@ -11,7 +11,6 @@ import (
otp "github.com/pquerna/otp/totp"
"voidedtech.com/lockbox/internal"
- "voidedtech.com/stock"
)
func getEnv() string {
@@ -32,7 +31,7 @@ func list() ([]string, error) {
}
}
if len(results) == 0 {
- return nil, stock.NewBasicError("no objects found")
+ return nil, internal.NewLockboxError("no objects found")
}
return results, nil
}
@@ -54,7 +53,7 @@ func display(token string, clip, once, short bool) error {
interactive = false
}
if !interactive && clip {
- return stock.NewBasicError("clipboard not available in non-interactive mode")
+ return internal.NewLockboxError("clipboard not available in non-interactive mode")
}
redStart, redEnd, err := internal.GetColor(internal.ColorRed)
if err != nil {
@@ -62,8 +61,8 @@ func display(token string, clip, once, short bool) error {
}
tok := strings.TrimSpace(token)
store := filepath.Join(getEnv(), tok+internal.Extension)
- if !stock.PathExists(store) {
- return stock.NewBasicError("object does not exist")
+ if !internal.PathExists(store) {
+ return internal.NewLockboxError("object does not exist")
}
l, err := internal.NewLockbox("", "", store)
if err != nil {
@@ -146,13 +145,13 @@ func display(token string, clip, once, short bool) error {
func main() {
args := os.Args
if len(args) > 3 || len(args) < 2 {
- stock.Die("subkey required", stock.NewBasicError("invalid arguments"))
+ internal.Die("subkey required", internal.NewLockboxError("invalid arguments"))
}
cmd := args[1]
if cmd == "-list" || cmd == "-ls" {
result, err := list()
if err != nil {
- stock.Die("invalid list response", err)
+ internal.Die("invalid list response", err)
}
sort.Strings(result)
for _, entry := range result {
@@ -165,7 +164,7 @@ func main() {
short := false
if len(args) == 3 {
if cmd != "-c" && cmd != "clip" && cmd != "-once" && cmd != "-short" {
- stock.Die("subcommand not supported", stock.NewBasicError("invalid sub command"))
+ internal.Die("subcommand not supported", internal.NewLockboxError("invalid sub command"))
}
clip = cmd == "-clip" || cmd == "-c"
once = cmd == "-once"
@@ -173,6 +172,6 @@ func main() {
cmd = args[2]
}
if err := display(cmd, clip, once, short); err != nil {
- stock.Die("failed to show totp token", err)
+ internal.Die("failed to show totp token", err)
}
}
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -10,7 +10,6 @@ import (
"time"
"voidedtech.com/lockbox/internal"
- "voidedtech.com/stock"
)
var (
@@ -19,15 +18,41 @@ var (
func getEntry(store string, args []string, idx int) string {
if len(args) != idx+1 {
- stock.Die("invalid entry given", stock.NewBasicError("specific entry required"))
+ internal.Die("invalid entry given", internal.NewLockboxError("specific entry required"))
}
return filepath.Join(store, args[idx]) + internal.Extension
}
+func hooks() {
+ hookDir := os.Getenv("LOCKBOX_HOOKDIR")
+ if !internal.PathExists(hookDir) {
+ return
+ }
+ dirs, err := os.ReadDir(hookDir)
+ if err != nil {
+ internal.Die("unable to read hookdir", err)
+ }
+ for _, d := range dirs {
+ if !d.IsDir() {
+ if d.Type() & 0111 == 011 {
+ name := d.Name()
+ cmd := exec.Command(filepath.Join(hookDir, name))
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ internal.Die(fmt.Sprintf("hook failed: %s", name), err)
+ }
+ continue
+ }
+ }
+ internal.Die("invalid hook", internal.NewLockboxError("hook is not file and/or has wrong mode"))
+ }
+}
+
func main() {
args := os.Args
if len(args) < 2 {
- stock.Die("missing arguments", stock.NewBasicError("requires subcommand"))
+ internal.Die("missing arguments", internal.NewLockboxError("requires subcommand"))
}
command := args[1]
store := internal.GetStore()
@@ -37,13 +62,13 @@ func main() {
searchTerm := ""
if isFind {
if len(args) < 3 {
- stock.Die("find requires an argument to search for", stock.NewBasicError("search term required"))
+ internal.Die("find requires an argument to search for", internal.NewLockboxError("search term required"))
}
searchTerm = args[2]
}
files, err := internal.Find(store, true)
if err != nil {
- stock.Die("unable to list files", err)
+ internal.Die("unable to list files", err)
}
for _, f := range files {
if isFind {
@@ -60,20 +85,20 @@ func main() {
idx := 2
switch len(args) {
case 2:
- stock.Die("insert missing required arguments", stock.NewBasicError("entry required"))
+ internal.Die("insert missing required arguments", internal.NewLockboxError("entry required"))
case 3:
case 4:
multi = args[2] == "-m"
if !multi {
- stock.Die("multi-line insert must be after 'insert'", stock.NewBasicError("invalid command"))
+ internal.Die("multi-line insert must be after 'insert'", internal.NewLockboxError("invalid command"))
}
idx = 3
default:
- stock.Die("too many arguments", stock.NewBasicError("insert can only perform one operation"))
+ internal.Die("too many arguments", internal.NewLockboxError("insert can only perform one operation"))
}
isPipe := internal.IsInputFromPipe()
entry := getEntry(store, args, idx)
- if stock.PathExists(entry) {
+ if internal.PathExists(entry) {
if !isPipe {
if !confirm("overwrite existing") {
return
@@ -81,9 +106,9 @@ func main() {
}
} else {
dir := filepath.Dir(entry)
- if !stock.PathExists(dir) {
+ if !internal.PathExists(dir) {
if err := os.MkdirAll(dir, 0755); err != nil {
- stock.Die("failed to create directory structure", err)
+ internal.Die("failed to create directory structure", err)
}
}
}
@@ -91,34 +116,36 @@ func main() {
if !multi && !isPipe {
input, err := internal.ConfirmInput()
if err != nil {
- stock.Die("password input failed", err)
+ internal.Die("password input failed", err)
}
password = input
} else {
input, err := internal.Stdin(false)
if err != nil {
- stock.Die("failed to read stdin", err)
+ internal.Die("failed to read stdin", err)
}
password = input
}
if password == "" {
- stock.Die("empty password provided", stock.NewBasicError("password can NOT be empty"))
+ internal.Die("empty password provided", internal.NewLockboxError("password can NOT be empty"))
}
l, err := internal.NewLockbox("", "", entry)
if err != nil {
- stock.Die("unable to make lockbox model instance", err)
+ internal.Die("unable to make lockbox model instance", err)
}
if err := l.Encrypt([]byte(password)); err != nil {
- stock.Die("failed to save password", err)
+ internal.Die("failed to save password", err)
}
fmt.Println("")
+ hooks()
case "rm":
entry := getEntry(store, args, 2)
- if !stock.PathExists(entry) {
- stock.Die("does not exists", stock.NewBasicError("can not delete unknown entry"))
+ if !internal.PathExists(entry) {
+ internal.Die("does not exists", internal.NewLockboxError("can not delete unknown entry"))
}
if confirm("remove entry") {
os.Remove(entry)
+ hooks()
}
case "show", "-c", "clip":
inEntry := getEntry(store, args, 2)
@@ -127,32 +154,32 @@ func main() {
if strings.Contains(inEntry, "*") {
matches, err := filepath.Glob(inEntry)
if err != nil {
- stock.Die("bad glob", err)
+ internal.Die("bad glob", err)
}
entries = matches
}
isGlob := len(entries) > 1
if isGlob {
if !isShow {
- stock.Die("cannot glob to clipboard", stock.NewBasicError("bad glob request"))
+ internal.Die("cannot glob to clipboard", internal.NewLockboxError("bad glob request"))
}
sort.Strings(entries)
}
startColor, endColor, err := internal.GetColor(internal.ColorRed)
if err != nil {
- stock.Die("unable to get color for terminal", err)
+ internal.Die("unable to get color for terminal", err)
}
for _, entry := range entries {
- if !stock.PathExists(entry) {
- stock.Die("invalid entry", stock.NewBasicError("entry not found"))
+ if !internal.PathExists(entry) {
+ internal.Die("invalid entry", internal.NewLockboxError("entry not found"))
}
l, err := internal.NewLockbox("", "", entry)
if err != nil {
- stock.Die("unable to make lockbox model instance", err)
+ internal.Die("unable to make lockbox model instance", err)
}
decrypt, err := l.Decrypt()
if err != nil {
- stock.Die("unable to decrypt", err)
+ internal.Die("unable to decrypt", err)
}
value := strings.TrimSpace(string(decrypt))
if isShow {
@@ -173,11 +200,11 @@ func main() {
idx := 0
val, err := internal.Stdin(false)
if err != nil {
- stock.Die("unable to read value to clear", err)
+ internal.Die("unable to read value to clear", err)
}
_, paste, err := internal.GetClipboardCommand()
if err != nil {
- stock.Die("unable to get paste command", err)
+ internal.Die("unable to get paste command", err)
}
var args []string
if len(paste) > 1 {
@@ -204,7 +231,7 @@ func main() {
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
- stock.Die("bad command", err)
+ internal.Die("bad command", err)
}
}
}
@@ -213,7 +240,7 @@ func confirm(prompt string) bool {
fmt.Printf("%s? (y/N) ", prompt)
resp, err := internal.Stdin(true)
if err != nil {
- stock.Die("failed to get response", err)
+ internal.Die("failed to get response", err)
}
return resp == "Y" || resp == "y"
}
diff --git a/go.mod b/go.mod
@@ -6,7 +6,6 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/pquerna/otp v1.3.0
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
- voidedtech.com/stock v0.0.0-20211014234009-93c0ed43354e
)
require (
diff --git a/go.sum b/go.sum
@@ -23,5 +23,3 @@ golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-voidedtech.com/stock v0.0.0-20211014234009-93c0ed43354e h1:feU8+uf5lbdKC9Z4+5+x7KObXW6mjTZK+ZqnY/d6oZA=
-voidedtech.com/stock v0.0.0-20211014234009-93c0ed43354e/go.mod h1:fDeTx9Bymp++UZEUI+pxljhMDzibXQvRKTcg1h+5tw4=
diff --git a/internal/clip.go b/internal/clip.go
@@ -5,8 +5,6 @@ import (
"os"
"os/exec"
"strings"
-
- "voidedtech.com/stock"
)
const (
@@ -32,14 +30,14 @@ func GetClipboardCommand() ([]string, []string, error) {
case "Linux":
if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
- return nil, nil, stock.NewBasicError("unable to detect linux clipboard mode")
+ return nil, nil, NewLockboxError("unable to detect linux clipboard mode")
}
env = xClipMode
} else {
env = waylandClipMode
}
default:
- return nil, nil, stock.NewBasicError("unable to detect clipboard mode")
+ return nil, nil, NewLockboxError("unable to detect clipboard mode")
}
}
switch env {
@@ -50,9 +48,9 @@ func GetClipboardCommand() ([]string, []string, error) {
case waylandClipMode:
return []string{"wl-copy"}, []string{"wl-paste"}, nil
case "off":
- return nil, nil, stock.NewBasicError("clipboard is turned off")
+ return nil, nil, NewLockboxError("clipboard is turned off")
}
- return nil, nil, stock.NewBasicError("unable to get clipboard command(s)")
+ return nil, nil, NewLockboxError("unable to get clipboard command(s)")
}
// CopyToClipboard will copy to clipboard, if non-empty will clear later.
@@ -77,7 +75,7 @@ func pipeTo(command, value string, wait bool, args ...string) {
cmd := exec.Command(command, args...)
stdin, err := cmd.StdinPipe()
if err != nil {
- stock.Die("unable to get stdin pipe", err)
+ Die("unable to get stdin pipe", err)
}
go func() {
@@ -93,6 +91,6 @@ func pipeTo(command, value string, wait bool, args ...string) {
ran = cmd.Start()
}
if ran != nil {
- stock.Die("failed to run command", ran)
+ Die("failed to run command", ran)
}
}
diff --git a/internal/encdec.go b/internal/encdec.go
@@ -11,7 +11,6 @@ import (
"github.com/google/shlex"
"golang.org/x/crypto/nacl/secretbox"
- "voidedtech.com/stock"
)
const (
@@ -48,11 +47,11 @@ func NewLockbox(key, keyMode, file string) (Lockbox, error) {
}
if len(b) == 0 {
- return Lockbox{}, stock.NewBasicError("key is empty")
+ return Lockbox{}, NewLockboxError("key is empty")
}
if len(b) > keyLength {
- return Lockbox{}, stock.NewBasicError("key is too large for use")
+ return Lockbox{}, NewLockboxError("key is too large for use")
}
for len(b) < keyLength {
@@ -80,7 +79,7 @@ func getKey(keyMode, name string) ([]byte, error) {
case PlainKeyMode:
data = []byte(name)
default:
- return nil, stock.NewBasicError("unknown keymode")
+ return nil, NewLockboxError("unknown keymode")
}
return []byte(strings.TrimSpace(string(data))), nil
}
@@ -98,7 +97,7 @@ func (l Lockbox) Encrypt(datum []byte) error {
}
data := datum
if data == nil {
- b, err := stock.Stdin(false)
+ b, err := getStdin(false)
if err != nil {
return err
}
@@ -126,7 +125,7 @@ func (l Lockbox) Decrypt() ([]byte, error) {
copy(nonce[:], encrypted[:nonceLength])
decrypted, ok := secretbox.Open(nil, encrypted[nonceLength:], &nonce, &l.secret)
if !ok {
- return nil, stock.NewBasicError("decrypt not ok")
+ return nil, NewLockboxError("decrypt not ok")
}
padding := int(decrypted[0])
diff --git a/internal/utils.go b/internal/utils.go
@@ -1,6 +1,8 @@
package internal
import (
+ "bufio"
+ "bytes"
"fmt"
"io/fs"
"os"
@@ -8,13 +10,14 @@ import (
"sort"
"strings"
"syscall"
-
- "voidedtech.com/stock"
)
type (
// Color are terminal colors for dumb terminal coloring.
Color int
+ LockboxError struct {
+ message string
+ }
)
const (
@@ -37,7 +40,7 @@ func isYesNoEnv(defaultValue bool, env string) (bool, error) {
case "yes":
return true, nil
}
- return false, stock.NewBasicError(fmt.Sprintf("invalid yes/no env value for %s", env))
+ return false, NewLockboxError(fmt.Sprintf("invalid yes/no env value for %s", env))
}
// IsInteractive indicates if running as a user UI experience.
@@ -48,7 +51,7 @@ func IsInteractive() (bool, error) {
// GetColor will retrieve start/end terminal coloration indicators.
func GetColor(color Color) (string, string, error) {
if color != ColorRed {
- return "", "", stock.NewBasicError("bad color")
+ return "", "", NewLockboxError("bad color")
}
interactive, err := IsInteractive()
if err != nil {
@@ -76,8 +79,8 @@ func GetStore() string {
// Find will find all lockbox files in a directory store.
func Find(store string, display bool) ([]string, error) {
var results []string
- if !stock.PathExists(store) {
- return nil, stock.NewBasicError("store does not exists")
+ if !PathExists(store) {
+ return nil, NewLockboxError("store does not exists")
}
err := filepath.Walk(store, func(path string, info fs.FileInfo, err error) error {
if err != nil {
@@ -150,14 +153,14 @@ func ConfirmInput() (string, error) {
return "", err
}
if first != second {
- return "", stock.NewBasicError("passwords do NOT match")
+ return "", NewLockboxError("passwords do NOT match")
}
return first, nil
}
// Stdin will retrieve stdin data.
func Stdin(one bool) (string, error) {
- b, err := stock.Stdin(one)
+ b, err := getStdin(one)
if err != nil {
return "", err
}
@@ -169,3 +172,54 @@ func IsInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}
+
+// NewLockboxError creates a non-category error.
+func NewLockboxError(message string) error {
+ return &LockboxError{message}
+}
+
+// Error gets the error message for a basic error.
+func (err *LockboxError) Error() string {
+ return err.message
+}
+
+// LogError will log an error to stderr.
+func LogError(message string, err error) {
+ msg := message
+ if err != nil {
+ msg = fmt.Sprintf("%s (%v)", msg, err)
+ }
+ fmt.Fprintln(os.Stderr, msg)
+}
+
+// Die will print messages and exit.
+func Die(message string, err error) {
+ LogError(message, err)
+ os.Exit(1)
+}
+
+// PathExists indicates if a path exists.
+func PathExists(path string) bool {
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ return false
+ }
+ }
+ return true
+}
+
+func getStdin(one bool) ([]byte, error) {
+ scanner := bufio.NewScanner(os.Stdin)
+ var b bytes.Buffer
+ for scanner.Scan() {
+ b.WriteString(scanner.Text())
+ b.WriteString("\n")
+ if one {
+ break
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+ return b.Bytes(), nil
+}