aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rwxr-xr-xdpw9
-rwxr-xr-xdpw-age223
-rwxr-xr-xdpw-gpg52
-rw-r--r--readme.md49
5 files changed, 325 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index 08f7b42..baffd11 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,9 @@ all:
install:
install -m 755 dpw $(PREFIX)/bin/dpw
install -m 755 dpw-gpg $(PREFIX)/bin/dpw-gpg
+ install -m 755 dpw-age $(PREFIX)/bin/dpw-age
install -m 755 dpw-menu $(PREFIX)/bin/dpw-menu
uninstall:
- rm "$(PREFIX)/bin/dpw" "$(PREFIX)/bin/dpw-gpg" "$(PREFIX)/bin/dpw-menu"
+ rm "$(PREFIX)/bin/dpw" "$(PREFIX)/bin/dpw-gpg" "$(PREFIX)/bin/dpw-menu" \
+ "$(PREFIX)/bin/dpw-menu"
diff --git a/dpw b/dpw
index a9e6a75..a342366 100755
--- a/dpw
+++ b/dpw
@@ -53,7 +53,7 @@ Usage: $0 [command] key/to/operate/on
dpw is the dynamic password manager inspired by 'pass'
-Your current backend is: $DPW_BACKEND
+Your current backend is: $DPW_BACKEND ( Set by DPW_BACKEND )
Where command may be one of:
EOF
@@ -232,6 +232,10 @@ case $a in
esac
}
+init() {
+"${DPW_BACKEND}" init "$@"
+}
+
if [ -z "$1" ] ; then
list "$@"
exit 0
@@ -257,7 +261,8 @@ case $1 in
edit) action=edit; shift ;;
find|fnd) action=_find; shift ;;
otp) action=otp; shift ;;
- -h) help ;;
+ init) action=init; shift ;;
+ -h|--help|help) help ;;
esac
"$action" "$@"
diff --git a/dpw-age b/dpw-age
new file mode 100755
index 0000000..1d98303
--- /dev/null
+++ b/dpw-age
@@ -0,0 +1,223 @@
+#!/bin/sh
+# Copyright 2021 Mitchell Riedstra
+#
+# Permission to use, copy, modify, and/or distribute this software for any purpose
+# with or without fee is hereby granted, provided that the above copyright notice
+# and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+# THIS SOFTWARE.
+#
+# Example storage plugin that wraps 'age' into a password manager.
+# https://github.com/FiloSottile/age
+set -e
+
+UMASK="${PASSWORD_STORE_UMASK:-077}"
+umask "$UMASK"
+
+DPW_AGE_DIR="${DPW_AGE_DIR:-$HOME/.dpw-age}"
+DPW_AGE_KEY="${DPW_AGE_KEY:-$HOME/.dpw-age-key}"
+
+USE_GIT=0
+[ -e "${DPW_AGE_DIR}/.git" ] && USE_GIT=1
+
+# Helper functions
+
+_git_commit() {
+ [ $USE_GIT -eq 0 ] && return
+ cd "${DPW_AGE_DIR}"
+ git add --all
+ git commit -am "DPW Auto-commit: $1"
+}
+
+_set_age_recipients() {
+if [ -n "$DPW_AGE_RECIPIENTS" ] ; then
+ DPW_AGE_RECIPIENTS="$(echo "$DPW_AGE_RECIPIENTS" | tr ' ' '\n' \
+ | sed -e's/^/ -r/' | tr '\n' ' ')"
+ return
+fi
+_pth="$1"; shift
+
+_pth="${DPW_AGE_DIR}/$_pth"
+
+
+id_file="$(dirname "${_pth}")/.recipients"
+while true ; do
+
+ # Break if id_file is above our password store directory
+ case $id_file in
+ ${DPW_AGE_DIR}*) ;;
+ *) break ;;
+ esac
+
+ if [ -e "$id_file" ] ; then
+ keys=""
+ while read -r key ; do
+ if [ -z "$key" ] ; then continue ; fi
+ keys="$keys -r $key"
+ done < "$id_file"
+
+ export DPW_AGE_RECIPIENTS="$keys"
+ return
+ fi
+
+ # Pop this up a directory level for the next time 'round
+ id_file="$(dirname "$id_file")"
+ id_file="$(dirname "$id_file")/.recipients"
+done
+
+echo "No '.recipients' files found, is '$DPW_AGE_DIR' initialized?"
+exit 1
+}
+
+
+# Interface
+
+show() {
+pth="$1"; shift
+#shellcheck disable=SC2086
+exec age -i "${DPW_AGE_KEY}" -d < "${DPW_AGE_DIR}/${pth}.age"
+}
+
+
+insert() {
+pth="$1"; shift
+_set_age_recipients "$pth"
+mkdir -p "$DPW_AGE_DIR/$(dirname "$pth")"
+#shellcheck disable=SC2086
+age -e ${DPW_AGE_RECIPIENTS} \
+ > "${DPW_AGE_DIR}/${pth}.age"
+_git_commit "Insert: $pth"
+}
+
+list() {
+find "$DPW_AGE_DIR/$1" -type f -iname "*.age" \
+ | while read -r line ; do
+ line="${line##$DPW_AGE_DIR}"
+ line="${line#/}"
+ line="${line#/}"
+ line="${line%.age}"
+ echo "$line"
+ done
+}
+
+remove() {
+cd "$DPW_AGE_DIR"
+recursive=
+force=
+while [ $# -gt 0 ] ; do case $1 in
+ -r) recursive="-r" ; shift ;;
+ -f) force="-f" ; shift ;;
+ -rf|-fr) recursive="-r" ; force="-f" ; shift ;;
+ *) break ;;
+esac ; done
+
+files=
+for fn in "$@" ; do
+ if [ -e "${fn}.age" ] ; then
+ files="${fn}.age "
+ else
+ files="$fn "
+ fi
+done
+
+#shellcheck disable=SC2086
+rm $recursive $force $files
+_git_commit "Remove: $*"
+}
+
+_init_help() {
+cat <<EOF
+Usage: $0 [--no-git] [--gen-key] [--[no-]pass] <recipients>...
+
+Git is the default.
+
+Gen key will generate a passphrase protected private key for you and place it in
+\$DPW_AGE_KEY ($DPW_AGE_KEY) and the public side will be '.pub'
+
+No pass disables the default for a passphrase protected key file
+
+The key will automatically be added to the recipients list.
+
+Multiple recipients may be specified
+
+See: https://github.com/FiloSottile/age for more information on 'age' itself.
+EOF
+exit 1
+}
+
+_init() {
+USE_GIT=1
+if [ -d "${DPW_AGE_DIR}" ] ; then
+ echo "Cannot init new password store, one exists"
+ exit 1
+fi
+
+recipients=
+genkey=1
+passphrase=1
+while [ $# -gt 0 ] ; do case $1 in
+ --no-git) USE_GIT=0 ; shift ;;
+ --gen-key) genkey=1; shift ;;
+ --pass) passphrase=1; shift ;;
+ --no-pass) passphrase=0; shift ;;
+ -r|--recipient) recipients="$recipients:$2"; shift ; shift ;;
+ *) _init_help ;;
+esac ; done
+
+[ $genkey -eq 1 ] && [ -e "${DPW_AGE_KEY}" ] \
+ && echo "Cannot continue, \"${DPW_AGE_KEY}\" already exists" && exit
+
+echo "genkey: $genkey passphrase: $passphrase"
+
+if [ $genkey -eq 1 ] && [ $passphrase -eq 1 ] ; then
+ touch "${DPW_AGE_KEY}.pub" # Let the user's UMASK bet set
+ umask 077 # but override it for the key
+ age-keygen 2>"${DPW_AGE_KEY}.pub" | age -p > "${DPW_AGE_KEY}" 2>&2
+ sed -i.bak -e's/^Public key: //g' "${DPW_AGE_KEY}.pub"
+ rm "${DPW_AGE_KEY}.pub.bak"
+ umask "$UMASK"
+elif [ $genkey -eq 1 ] ; then
+ touch "${DPW_AGE_KEY}.pub" # Let the user's UMASK bet set
+ umask 077 # but override it for the key
+ age-keygen >"${DPW_AGE_KEY}"
+ sed -ne's/^# public key: //gp' < "${DPW_AGE_KEY}" > "${DPW_AGE_KEY}.pub"
+ umask "$UMASK"
+fi
+
+mkdir -p "${DPW_AGE_DIR}"
+cd "${DPW_AGE_DIR}"
+
+if [ $USE_GIT -eq 1 ] ; then
+ git init
+ cat >> .git/config <<EOF
+[diff "age"]
+ binary = true
+ textconv = age --decrypt -i "${DPW_AGE_KEY}"
+EOF
+
+ echo "*.age diff=age" >> .gitattributes
+fi
+
+cat < "${DPW_AGE_KEY}.pub" > "${DPW_AGE_DIR}/.recipients"
+echo "$recipients" | tr ':' '\n' >> "${DPW_AGE_DIR}/.recipients"
+
+_git_commit
+
+echo "Age Password store initialized in ${DPW_AGE_DIR}"
+}
+
+act="$1"; shift
+case $act in
+ show) show "$@" ;;
+ list) list "$@" ;;
+ insert) insert "$@" ;;
+ rm) remove "$@" ;;
+ init) _init "$@" ;;
+ *) echo "Bad command $act"; exit 1; ;;
+esac
diff --git a/dpw-gpg b/dpw-gpg
index d26b665..b0669dc 100755
--- a/dpw-gpg
+++ b/dpw-gpg
@@ -59,7 +59,7 @@ while true ; do
if [ -e "$id_file" ] ; then
keys=""
while read -r key ; do
- keys=" -r $key"
+ keys="$keys -r $key"
done < "$id_file"
export PASSWORD_STORE_KEY="$keys"
@@ -88,7 +88,7 @@ exec gpg $__gpg_opts -d < "${PASSWORD_STORE_DIR}/${pth}.gpg"
insert() {
pth="$1"; shift
_set_gpg_id "$pth"
-mkdir -p "$(dirname "$pth")"
+mkdir -p "$PASSWORD_STORE_DIR/$(dirname "$pth")"
#shellcheck disable=SC2086
gpg $__gpg_opts -e ${PASSWORD_STORE_KEY} \
> "${PASSWORD_STORE_DIR}/${pth}.gpg"
@@ -133,11 +133,59 @@ rm $recursive $force $files
_git_commit "Remove: $*"
}
+_init_help() {
+cat <<EOF
+usage: $0 [--no-git] <GPG IDs>...
+EOF
+exit 1
+}
+
+_init() {
+USE_GIT=1
+if [ -d "${PASSWORD_STORE_DIR}" ] ; then
+ echo "Cannot init new password store, one exists"
+ exit 1
+fi
+while [ $# -gt 0 ] ; do case $1 in
+ --no-git) USE_GIT=0 ; shift ;;
+ -h|--help) _init_help ;;
+ *) break ;;
+esac ; done
+
+if [ $# -eq 0 ] ; then
+ echo "No GPG id supplied, bailing"
+ exit 1
+fi
+
+mkdir -p "${PASSWORD_STORE_DIR}"
+cd "${PASSWORD_STORE_DIR}"
+
+if [ $USE_GIT -eq 1 ] ; then
+ git init
+ cat >> .git/config <<EOF
+[diff "gpg"]
+ binary = true
+ textconv = gpg --no-tty --decrypt
+EOF
+
+ echo "*.gpg diff=gpg" >> .gitattributes
+fi
+
+for id in "$@" ; do
+ echo "$id" >> .gpg-id
+done
+
+_git_commit
+
+echo "Password store initialized"
+}
+
act="$1"; shift
case $act in
show) show "$@" ;;
list) list "$@" ;;
insert) insert "$@" ;;
rm) remove "$@" ;;
+ init) _init "$@" ;;
*) echo "Bad command $act"; exit 1; ;;
esac
diff --git a/readme.md b/readme.md
index b90da50..53bfe86 100644
--- a/readme.md
+++ b/readme.md
@@ -1,13 +1,10 @@
# dpw the dynamic password manager
Inspired by [`pass`](https://www.passwordstore.org/) and designed to be
-compatible out of the box.
+partly compatible out of the box.
-The main point of this is to provide a somewhat familiar interface for
-pluggable backends. Secondary benefits that people may be interested in is the
-ISC license, so you can modify the `dpw` script without giving me your changes
-even if you use it in a commercial product. ( Though, it might be appreciated to
-get code back! )
+The main point of this is to provide a somewhat familiar command line interface
+for pluggable backends.
For instance, one could write a plugin that talks to Lastpass, Bitwarden
Hasicorp's Vault, or similar.
@@ -35,12 +32,50 @@ simple, accept the following four commands:
* show `<key>`
* Dump to stdout
* rm `<key>`
+ * init ( optional )
`dpw` will take care of all the additional commands by wrapping the four
above as needed.
-
## dmenu script
There's also a small dmenu script included that makes copying or typing
out passwords and OTP tokens very quick and easy
+
+
+# [age](https://github.com/FiloSottile/age) backend
+
+Pretty similar to the GPG backend, except it uses the new `age` tool as
+the backend. The usage should be straightforward, install the utility
+and then set `DPW_BACKEND=dpw-age` in your environment. It will not
+clobber any GPG based password store you have unless you explicitly
+override `DPW_AGE_DIR`
+
+It expects `age` and `age-keygen` to be in your path.
+
+Check over `dpw init --help` for more options when initalizing the store.
+
+Environment variables:
+
+ * `DPW_AGE_DIR=$HOME/.dpw-age`
+ * Default directory for storage
+ * `DPW_AGE_KEY=$HOME/.dpw-age-key`
+ * default key file
+ * `PASSWORD_STORE_UMASK=077`
+ * Umask for files created
+ * `DPW_AGE_RECIPIENTS`
+ * If set, no `.recipients` files are read and encryption will be to the keys
+ specified
+
+
+NOTE: Because there's no agent passphrase protected keys are going to be
+somewhat of a pain to use with this. Having a password manager with the
+key sitting on disk right next to the files sort of defeats the purpose.
+
+It's still perhaps useful in the sense that the requirements for setup
+are minimal and you don't have to deal with dragging GPG around.
+
+The Go library is available so in theory it wouldn't be hard to write
+a self-contained backend that kept the key in memory. If made setgid
+nobody it'll prevent your regular user from being able to extract the
+keys from memory.