commit 95388e5f01e4708a35bbb7c890aa02594b99703f
Author: St John Karp <contact@stjo.hn>
Date: Sat, 4 Sep 2021 09:50:09 -0400
Initial commit
Initial commit of Balrog, an implementation of the `pass` password
manager using only a POSIX shell and utilities. This is designed
to support a subset of the features of `pass` while remaining
100% compatible with its API so that it can be used as a drop-in
replacement.
Implemented so far are:
- commands:
- edit
- generate
- show
- flags:
- -c/--clip
- -i/--in-place
- -n/--no-symbols
Also included is a *very* skinny "otp" command that covers some
of the functionality of pass-otp. This is by no means complete
and doesn't support much outside of my specific use case. Not sure
if it's eventually worth reimplementing pass-otp myself as an
extension or what.
Diffstat:
A | balrog | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 122 insertions(+), 0 deletions(-)
diff --git a/balrog b/balrog
@@ -0,0 +1,122 @@
+#!/usr/bin/env sh
+
+STORE="$HOME/.password-store"
+
+ACTION='show'
+COPY=0
+OTP=0
+LENGTH=32
+INPLACE=0
+CHARACTERS='[:alnum:][:punct:]'
+TMP_FILE="$HOME/.balrogtmp"
+
+# Loop through all the arguments and set flags/options.
+while [ "$#" -gt 0 ] ; do
+ case "$1" in
+ -c|--clip)
+ COPY=1
+ shift
+ ;;
+ -i|--in-place)
+ INPLACE=1
+ shift
+ ;;
+ -n|--no-symbols)
+ CHARACTERS='[:alnum:]'
+ shift
+ ;;
+ edit)
+ ACTION='edit'
+ shift
+ ;;
+ generate)
+ ACTION='generate'
+ shift
+ ;;
+ otp)
+ OTP=1
+ shift
+ ;;
+ show)
+ ACTION='show'
+ shift
+ ;;
+ *)
+ # Treat anything unidentified as the name of the password.
+ KEY="$1"
+ shift
+
+ # When generating a key, it may immediately be followed by
+ # an integer to set the length. The third line here
+ # verifies it's an integer.
+ [ "$ACTION" = 'generate' ] &&
+ [ -n "$1" ] &&
+ printf %d "$1" > /dev/null 2>&1 &&
+ LENGTH="$1" &&
+ shift
+ ;;
+ esac
+done
+
+KEY_FILE="$STORE/$KEY.gpg"
+
+# Dynamically changing the value of ACTION allow us to simulate fall-throughs.
+while [ -n "$ACTION" ] ; do
+ case "$ACTION" in
+ edit)
+ # Decrypt to a temporary file, allow the user to edit it,
+ # then re-encrypt and delete the temp file.
+ gpg2 --decrypt --quiet "$KEY_FILE" --output "$TMP_FILE"
+ "${EDITOR:-vi}" "$TMP_FILE"
+ gpg2 --quiet --encrypt --default-recipient-self --output "$KEY_FILE" 2> /dev/null
+ rm "$TMP_FILE"
+
+ ACTION=''
+ ;;
+ generate)
+ # Create the path.
+ mkdir -p "${KEY_FILE%/*}"
+
+ # If generating in-place, get all but the first line of the existing file.
+ (
+ [ "$INPLACE" -eq 1 ] &&
+ gpg2 --quiet --decrypt "$KEY_FILE" |
+ sed '1d'
+ ) |
+ # Get the value of the new password from /dev/urandom.
+ (
+ tr -d -c "$CHARACTERS" < /dev/urandom |
+ dd bs="$LENGTH" count=1 2> /dev/null &&
+ # Terminate the password with a line ending.
+ echo &&
+ # Append the rest of the existing file, if any.
+ cat
+ ) |
+ gpg2 --quiet --encrypt --default-recipient-self --output "$KEY_FILE" 2> /dev/null
+
+ # Fall through to the "show" logic.
+ ACTION='show'
+ ;;
+ show)
+ # Decrypt, extract the secret from the otpauth line, and pass it to oathtool.
+ if [ "$OTP" -eq 1 ] ; then
+ gpg2 --decrypt --quiet "$KEY_FILE" |
+ grep 'otpauth' |
+ sed 's/.*secret=\([[:alnum:]]*\).*/\1/' |
+ oathtool --base32 --totp - |
+ ([ "$COPY" -eq 1 ] && xclip -selection clipboard || cat)
+ # Decrypt and get the first line.
+ else
+ gpg2 --decrypt --quiet "$KEY_FILE" |
+ ([ "$COPY" -eq 1 ] && head -n 1 | xclip -selection clipboard || cat)
+ fi
+
+ # Launch a background job to clear the clipboard in 30 seconds.
+ [ "$COPY" -eq 1 ] &&
+ sleep 30 &&
+ xclip -selection clipboard < /dev/null &
+
+ ACTION=''
+ ;;
+ esac
+done