aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/password-store.sh242
1 files changed, 242 insertions, 0 deletions
diff --git a/src/password-store.sh b/src/password-store.sh
new file mode 100755
index 0000000..2c6bd12
--- /dev/null
+++ b/src/password-store.sh
@@ -0,0 +1,242 @@
+#!/bin/bash
+
+umask 077
+
+PREFIX="$HOME/.password-store"
+ID="$PREFIX/.gpg-id"
+GIT="$PREFIX/.git"
+
+export GIT_DIR="$GIT"
+export GIT_WORK_TREE="$PREFIX"
+
+usage() {
+ cat <<_EOF
+Password Store
+by Jason Donenfeld
+ Jason@zx2c4.com
+
+Usage:
+ $program init gpg-id
+ Initialize new password storage and use gpg-id for encryption.
+ $program [ls] [subfolder]
+ List passwords.
+ $program [show] [--clip,-c] pass-name
+ Show existing password and optionally put it on the clipboard.
+ If put on the clipboard, it will be cleared in 45 seconds.
+ $program insert [--multiline,-m] pass-name
+ Insert new optionally multiline password.
+ $program generate [--no-symbols,-n] [--clip,-c] pass-name pass-length
+ Generate a new password of pass-length with optionally no symbols.
+ Optionally put it on the clipboard and clear board after 45 seconds.
+ $program rm pass-name
+ Remove existing password.
+ $program push
+ If the password store is a git repository, push the latest changes.
+ $program pull
+ If the password store is a git repository, pull the latest changes.
+ $program git git-command-args...
+ If the password store is a git repository, execute a git command
+ specified by git-command-args.
+ $program help
+ Show this text.
+_EOF
+}
+isCommand() {
+ case "$1" in
+ init|ls|list|show|insert|generate|remove|rm|delete|push|pull|git|help) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+clip() {
+ # This base64 business is a disgusting hack to deal with newline inconsistancies
+ # in shell. There must be a better way to deal with this, but because I'm a dolt,
+ # we're going with this for now.
+
+ before="$(xclip -o -selection clipboard | base64)"
+ echo -n "$1" | xclip -selection clipboard
+ (
+ sleep 45s
+ now="$(xclip -o -selection clipboard | base64)"
+ if [[ $now != $(echo -n "$1" | base64) ]]; then
+ before="$now"
+ fi
+ # It might be nice to programatically check to see if klipper exists,
+ # as well as checking for other common clipboard managers. But for now,
+ # this works fine. Clipboard managers frequently write their history
+ # out in plaintext, so we axe it here.
+ qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory >/dev/null 2>&1
+ echo "$before" | base64 -d | xclip -selection clipboard
+ ) & disown
+ echo "Copied $2 to clipboard. Will clear in 45 seconds."
+}
+program="$(basename "$0")"
+command="$1"
+if isCommand "$command"; then
+ shift
+else
+ command="show"
+fi
+
+case "$command" in
+ init)
+ if [[ $# -ne 1 ]]; then
+ echo "Usage: $program $command gpg-id"
+ exit 1
+ fi
+ gpg_id="$1"
+ mkdir -v -p "$PREFIX"
+ echo "$gpg_id" > "$ID"
+ echo "Password store initialized for $gpg_id."
+ exit 0
+ ;;
+ help)
+ usage
+ exit 0
+ ;;
+esac
+
+if ! [[ -f $ID ]]; then
+ echo "You must run:"
+ echo " $0 init your-gpg-id"
+ echo "before you may use the password store."
+ echo
+ usage
+ exit 1
+else
+ ID="$(head -n 1 "$ID")"
+fi
+
+case "$command" in
+ show|ls|list)
+ clip=0
+ if [[ $1 == "--clip" || $1 == "-c" ]]; then
+ clip=1
+ shift
+ fi
+ path="$1"
+ if [[ -d $PREFIX/$path ]]; then
+ if [[ $path == "" ]]; then
+ echo "Password Store"
+ else
+ echo $path
+ fi
+ tree "$PREFIX/$path" | tail -n +2 | head -n -2 | sed 's/\(.*\)\.gpg$/\1/';
+ else
+ passfile="$PREFIX/$path.gpg"
+ if ! [[ -f $passfile ]]; then
+ echo "$path is not in the password store."
+ exit 1
+ fi
+ if [ $clip -eq 0 ]; then
+ exec gpg -q -d "$passfile"
+ else
+ clip $(gpg -q -d "$passfile") $path
+ fi
+ fi
+ ;;
+ insert)
+ ml=0
+ if [[ $1 == "--multiline" || $1 == "-m" ]]; then
+ ml=1
+ shift
+ fi
+ if [[ $# -ne 1 ]]; then
+ echo "Usage: $program $command [--multiline,-m] pass-name"
+ exit 1
+ fi
+ path="$1"
+ mkdir -p -v "$PREFIX/$(dirname "$path")"
+
+ passfile="$PREFIX/$path.gpg"
+ if [[ $ml -eq 0 ]]; then
+ echo -n "Enter password for $path: "
+ head -n 1 | gpg -e -r "$ID" > "$passfile"
+ else
+ echo "Enter contents of $path and press Ctrl+D when finished:"
+ echo
+ cat | gpg -e -r "$ID" > "$passfile"
+ fi
+ if [[ -d $GIT ]]; then
+ git add "$passfile"
+ git commit -m "Added given password for $path to store."
+ fi
+ ;;
+ generate)
+ clip=0
+ symbols="-y"
+ while true; do
+ if [[ $1 == "--no-symbols" || $1 == "-n" ]]; then
+ symbols=""
+ shift
+ elif [[ $1 == "--clip" || $1 == "-c" ]]; then
+ clip=1
+ shift
+ else
+ break
+ fi
+ done
+ if [[ $# -ne 2 ]]; then
+ echo "Usage: $program $command [--no-symbols,-n] [--clip,-c] pass-name pass-length"
+ exit 1
+ fi
+ path="$1"
+ length="$2"
+ if ! [[ $length =~ ^[0-9]+$ ]]; then
+ echo "pass-length \"$length\" must be a number."
+ exit 1
+ fi
+ mkdir -p -v "$PREFIX/$(dirname "$path")"
+ pass="$(pwgen -s $symbols $length 1)"
+ passfile="$PREFIX/$path.gpg"
+ echo $pass | gpg -e -r "$ID" > "$passfile"
+ if [[ -d $GIT ]]; then
+ git add "$passfile"
+ git commit -m "Added generated password for $path to store."
+ fi
+
+ if [ $clip -eq 0 ]; then
+ echo "The generated password to $path is:"
+ echo "$pass"
+ else
+ clip "$pass" "$path"
+ fi
+ ;;
+ delete|rm|remove)
+ if [[ $# -ne 1 ]]; then
+ echo "Usage: $program $command pass-name"
+ exit
+ fi
+ path="$1"
+ passfile="$PREFIX/$path.gpg"
+ if ! [[ -f $passfile ]]; then
+ echo "$path is not in the password store."
+ exit 1
+ fi
+ rm -i -v "$passfile"
+ if [[ -d $GIT ]] && ! [[ -f $passfile ]]; then
+ git rm -f "$passfile"
+ git commit -m "Removed $path from store."
+ fi
+ ;;
+ push|pull)
+ if [[ -d $GIT ]]; then
+ exec git $command $@
+ else
+ echo "Error: the password store is not a git repository."
+ exit 1
+ fi
+ ;;
+ git)
+ if [[ -d $GIT ]]; then
+ exec git $@
+ else
+ echo "Error: the password store is not a git repository."
+ exit 1
+ fi
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+esac
+exit 0