aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2021-10-24 22:46:16 -0400
committerMitchell Riedstra <mitch@riedstra.dev>2021-10-24 22:46:16 -0400
commit6366a4b1f1dea523b666944cb82fcd039fa81823 (patch)
tree540f64b3d1cb0381e4e2211cdb3cc1636114bc14
downloaddpw-6366a4b1f1dea523b666944cb82fcd039fa81823.tar.gz
dpw-6366a4b1f1dea523b666944cb82fcd039fa81823.tar.xz
initial
-rw-r--r--Makefile13
-rwxr-xr-xdpw236
-rwxr-xr-xdpw-gpg141
-rw-r--r--readme.md40
4 files changed, 430 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..091d068
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+PREFIX ?= /usr/local
+
+all:
+ @echo "targets are:"
+ @echo " install"
+ @echo " uninstall"
+
+install:
+ install -m 755 dpw $(PREFIX)/bin/dpw
+ install -m 755 dpw-gpg $(PREFIX)/bin/dpw-gpg
+
+uninstall:
+ rm "$(PREFIX)/bin/dpw" "$(PREFIX)/bin/dpw-gpg"
diff --git a/dpw b/dpw
new file mode 100755
index 0000000..f8d0a77
--- /dev/null
+++ b/dpw
@@ -0,0 +1,236 @@
+#!/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.
+set -e
+PASSWORD_STORE_X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}"
+PASSWORD_STORE_CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}"
+DPW_BACKEND="${DPW_BACKEND:-dpw-gpg}"
+
+OTP="${OATH:-oathtool} -b --totp"
+
+_printH() {
+printf "%-25s # %s\n" "$@"
+}
+
+help() {
+cat <<EOF
+Usage: $0 [command] key/to/operate/on
+
+dpw is the dynamic password manager inspired by 'pass'
+
+Your current backend is: $DPW_BACKEND
+
+Where command may be one of:
+EOF
+
+_printH "show <key>" "dumps secret to stdout"
+_printH "copy <key>" "copies the first line of the secret to the clipboard"
+_printH "type <key>" "types the first line of the secret with xdotool"
+_printH "list [<key>]" "dumps a listing of all subkeys if key is supplied"
+_printH "ls [<key>]" "Same as above"
+_printH "mv <a> <b>" "Moves key a to b, conflict handling depends on backend"
+_printH "cp <a> <b>" "Copies key a to b, conflict handling depends on backend"
+_printH "rm <key>" "Removes an entry, additional flags are passed to the backend"
+_printH "edit <key>" "Edits with your \$EDITOR($EDITOR). Saves to a tmpfile removes when complete"
+_printH "otp <key>" "Generate otp from otpauth:// URL in a secret ( requires: oathtool ) "
+_printH "otp show <key>" "Same as above"
+_printH "otp copy <key>" "Same as above, but copies to the clipboard"
+_printH "otp type <key>" "Same as above, but types it out"
+_printH "fnd <key>" "runs 'grep' with the <key> arg over all the keys from 'list' "
+_printH "find <key>" "same as above"
+_printH "insert [-i] [-m] <key>" "-i for interactive, -m for multiline/stdin mode"
+
+cat <<EOF
+
+If you're using oksh / OpenBSD KSH, completion can be installled with:
+
+#shellcheck disable=SC2046
+[ -x "\$(command -v dpw)" ] && set -A complete_dpw_1 -- \\
+ show copy type list ls mv cp rm edit otp fnd find insert \\
+ && set -A complete_dpw_2 -- \$(dpw list)
+
+EOF
+
+exit 0
+}
+
+insert_interactive() {
+pth="$1"; shift
+printf "Enter password: "
+stty -echo; read -r passwd; stty echo
+
+printf "\nEnter password again: "
+stty -echo; read -r passwd2; stty echo
+
+echo ""
+
+if [ "$passwd" != "$passwd2" ] ; then
+ echo "Passwords do not match"
+ exit 1
+fi
+
+echo "$passwd" | "${DPW_BACKEND}" insert "$pth"
+}
+
+insert() {
+pth="$1"; shift
+"${DPW_BACKEND}" insert "$pth"
+}
+
+show() {
+pth="$1"; shift
+"${DPW_BACKEND}" show "$pth"
+}
+
+_find() {
+pth="$1"; shift
+"${DPW_BACKEND}" list | grep -iE "$pth"
+}
+
+copy() {
+pth="$1"; shift
+"${DPW_BACKEND}" show "$pth" \
+ | sed 1q \
+ | xclip -selection "$PASSWORD_STORE_X_SELECTION"
+
+sh -c "sleep ${PASSWORD_STORE_CLIP_TIME};
+ echo '' | xclip -selection '$PASSWORD_STORE_X_SELECTION'" &
+}
+
+_type() {
+pth="$1"; shift
+"${DPW_BACKEND}" show "$pth" \
+ | sed 1q \
+ | xdotool type --delay 2ms --clearmodifiers --file -
+}
+
+list() {
+"${DPW_BACKEND}" list "$1"
+}
+
+_cp() {
+a="$1"; shift
+b="$1"; shift
+"${DPW_BACKEND}" show "$a" | "${DPW_BACKEND}" insert "$b"
+}
+
+move() {
+a="$1"; shift
+b="$1"; shift
+_cp "$a" "$b"
+remove "$a"
+}
+
+remove() {
+"${DPW_BACKEND}" rm "$@"
+}
+
+edit() {
+_pth="$1"; shift
+tmpdir=/dev/shm
+if ! [ -d "$tmpdir" ] ; then
+ printf "Your system does not have /dev/shm, continue? [Yy] "
+ read -r resp
+ ok=0
+ case $resp in
+ Y*|y*) ok=1
+ esac
+ echo ""
+ [ $ok -eq 0 ] && return
+ tmpdir=/tmp
+fi
+_f="$(mktemp "${tmpdir}/dpw.XXXXXXXXXX")"
+#shellcheck disable=SC2064
+trap "rm -f \"$_f\"; exit 0" EXIT INT
+show "$_pth" > "$_f"
+${EDITOR:-vi} "$_f"
+insert "$_pth" < "$_f"
+rm -f "$_f"
+}
+
+otp() {
+a=show
+_pth="$1"
+if [ $# -gt 1 ] ; then
+ #shellcheck disable=SC2209
+ case $1 in
+ show) a=show ; shift ;;
+ copy) a=copy ; shift ;;
+ type) a=type ; shift ;;
+ *) echo "unknown option: $1" ; exit 1 ;;
+ esac
+ _pth="$1"
+fi
+
+if [ -z "$_pth" ] ; then
+ echo "Invalid path"
+ exit 1
+fi
+
+
+authstring="$(show "$_pth" \
+ | grep "otpauth" \
+ | sed 1q \
+ | grep -oE 'secret=[^&]*' \
+ | sed -e's/secret=//')"
+
+if [ -z "$authstring" ] ; then
+ echo "No OTP secret found"
+ exit 1
+fi
+
+case $a in
+ show) $OTP "$authstring" ;;
+ copy)
+ $OTP "$authstring" \
+ | xclip -selection "$PASSWORD_STORE_X_SELECTION"
+
+ sh -c "sleep ${PASSWORD_STORE_CLIP_TIME};
+ echo '' | xclip -selection '$PASSWORD_STORE_X_SELECTION'" &
+ ;;
+ type) $OTP "$authstring" | xdotool type --delay 2ms \
+ --clearmodifiers --file -
+ ;;
+esac
+}
+
+if [ -z "$1" ] ; then
+ list "$@"
+ exit 0
+fi
+
+action=show
+case $1 in
+ insert)
+ action=insert; shift;
+ if [ "$1" = "-m" ] ; then shift; fi
+ if [ "$1" = "-i" ] ; then
+ action=insert_interactive
+ shift
+ fi
+ ;;
+ show) shift ;;
+ copy) action=copy; shift ;;
+ type) action=_type; shift ;;
+ list|ls) action=list; shift ;;
+ rm) action=remove; shift ;;
+ mv) action=move; shift ;;
+ cp) action=_cp; shift ;;
+ edit) action=edit; shift ;;
+ find|fnd) action=_find; shift ;;
+ otp) action=otp; shift ;;
+ -h) help ;;
+esac
+
+"$action" "$@"
diff --git a/dpw-gpg b/dpw-gpg
new file mode 100755
index 0000000..538520b
--- /dev/null
+++ b/dpw-gpg
@@ -0,0 +1,141 @@
+#!/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's compatible with the original password
+# store
+set -e
+
+PASSWORD_STORE_DIR="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
+PASSWORD_STORE_KEY="${PASSWORD_STORE_KEY:-}"
+
+USE_GIT=0
+[ -e "${PASSWORD_STORE_DIR}/.git" ] && USE_GIT=1
+
+umask "${PASSWORD_STORE_UMASK:-077}"
+
+__gpg_opts="-q -z0"
+
+# Helper functions
+
+_git_commit() {
+ [ $USE_GIT -eq 0 ] && return
+ cd "${PASSWORD_STORE_DIR}"
+ git add --all
+ git commit -am "DPW Auto-commit: $1"
+}
+
+_set_gpg_id() {
+if [ -n "$PASSWORD_STORE_KEY" ] ; then
+ PASSWORD_STORE_KEY="$(echo "$PASSWORD_STORE_KEY" | tr ' ' '\n' \
+ | sed -e's/^/ -r/' | tr '\n' ' ')"
+ return
+fi
+_pth="$1"; shift
+
+_pth="${PASSWORD_STORE_DIR}/$_pth"
+
+
+id_file="$(dirname "${_pth}")/.gpg-id"
+while true ; do
+
+ # Break if id_file is above our password store directory
+ case $id_file in
+ ${PASSWORD_STORE_DIR}*) ;;
+ *) break ;;
+ esac
+
+ if [ -e "$id_file" ] ; then
+ keys=""
+ while read -r key ; do
+ keys=" -r $key"
+ done < "$id_file"
+
+ export PASSWORD_STORE_KEY="$keys"
+ return
+ fi
+
+ # Pop this up a directory level for the next time 'round
+ id_file="$(dirname "$id_file")"
+ id_file="$(dirname "$id_file")/.gpg-id"
+done
+
+echo "No '.gpg-id' files found, is '$PASSWORD_STORE_DIR' initialized?"
+exit 1
+}
+
+
+# Interface
+
+show() {
+pth="$1"; shift
+#shellcheck disable=SC2086
+exec gpg $__gpg_opts -d < "${PASSWORD_STORE_DIR}/${pth}.gpg"
+}
+
+
+insert() {
+pth="$1"; shift
+_set_gpg_id "$pth"
+mkdir -p "$(dirname "$pth")"
+#shellcheck disable=SC2086
+gpg $__gpg_opts -e ${PASSWORD_STORE_KEY} \
+ > "${PASSWORD_STORE_DIR}/${pth}.gpg"
+_git_commit "Insert: $pth"
+}
+
+list() {
+cd "$PASSWORD_STORE_DIR"
+find ./"$1" -type f \
+ | sed -e's@^\./@@g' \
+ | sed -e'/^\./d' \
+ | sed -e'/\.gpg-id$/d' \
+ | sed -e's/\.gpg$//g'
+}
+
+remove() {
+cd "$PASSWORD_STORE_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}.gpg" ] ; then
+ files="${fn}.gpg
+"
+ else
+ files="$fn
+"
+ fi
+done
+
+#shellcheck disable=SC2086
+rm $recursive $force $files
+_git_commit "Remove: $*"
+}
+
+act="$1"; shift
+case $act in
+ show) show "$@" ;;
+ list) list "$@" ;;
+ insert) insert "$@" ;;
+ rm) remove "$@" ;;
+ *) echo "Bad command $act"; exit 1; ;;
+esac
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..f0e3930
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,40 @@
+# dpw the dynamic password manager
+
+Inspired by [`pass`](https://www.passwordstore.org/) and designed to be
+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! )
+
+For instance, one could write a plugin that talks to Lastpass, Bitwarden
+Hasicorp's Vault, or similar.
+
+There are some mild changes in the interface from `pass`, a full list
+of commands and options are available with the `-h` command line flag.
+
+The environment variable `DPW_BACKEND` defaults to `dpw-gpg` which provides
+the `pass` compatible backend. Since it's just a call to another executable
+the backends can be written in any language.
+
+
+## Backends
+
+The `dpw-gpg` shell script should be short enough to read to give you an
+idea for implementing your own. That being said the interface is blindingly
+simple, accept the following four commands:
+
+ * list
+ * dump a list of the available keys, supporting arguments for sub keys
+ may be preferred by users but isn't necessary, `find` will still work
+ for them.
+ * insert `<key>`
+ * Read from stdin
+ * show `<key>`
+ * Dump to stdout
+ * rm `<key>`
+
+`dpw` will take care of all the additional commands by wrapping the four
+above as needed.