diff options
| -rw-r--r-- | Makefile | 13 | ||||
| -rwxr-xr-x | dpw | 236 | ||||
| -rwxr-xr-x | dpw-gpg | 141 | ||||
| -rw-r--r-- | readme.md | 40 |
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" @@ -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" "$@" @@ -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. |
