[PATCH] Add oathtool TOTP 2FA integration for pass show

binarysauce.com.au reply at binarysauce.com.au
Tue Oct 30 05:37:11 CET 2018


Hello, nice to meet you. This patch will integrate oathtool functionality, if oathtool is available in the user's environment. The intention is to init a subdirectory with it's own .git and .gpg-id, so the user can have a 2FA directory that is separated from ppasswords.

The introduced functionality is an --otp argument for cmd_show which when given the -o or --otp argument (additionally a token length) can show the generated token, clip will copy the generated token but qrcode will provides the qrcode of the original key so that it can be added to an authenticator app.

Having 2FA keys stored in pass suits my workflow so I imagine it might be helpful to others? If there is anything I can help with let me know. Thank you for your time.


---
 man/pass.1                |  8 +++++++-
 src/password-store.sh     | 32 ++++++++++++++++++++++++++------
 tests/t0020-show-tests.sh | 14 ++++++++++++++
 3 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/man/pass.1 b/man/pass.1
index 01a3fbe..73ce926 100644
--- a/man/pass.1
+++ b/man/pass.1
@@ -244,6 +244,11 @@ Copy existing password to clipboard
 .br
 Copied Email/jason at zx2c4.com to clipboard. Will clear in 45 seconds.
 .TP
+Show existing password as OTP 2FA code
+.B zx2c4 at laptop ~ $ pass -o 2FA/zx2c4.com
+.br
+398652
+.TP
 Add password to store
 .B zx2c4 at laptop ~ $ pass insert Business/cheese-whiz-factory
 .br
@@ -466,7 +471,8 @@ The location of the text editor used by \fBedit\fP.
 .BR tr (1),
 .BR git (1),
 .BR xclip (1),
-.BR qrencode (1).
+.BR qrencode (1),
+.BR oathtool (1).

 .SH AUTHOR
 .B pass
diff --git a/src/password-store.sh b/src/password-store.sh
index d89d455..ffa80a1 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -11,6 +11,7 @@ GPG="gpg"
 export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
 which gpg2 &>/dev/null && GPG="gpg2"
 [[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
+which oathtool &>/dev/null && OATHTOOL="oathtool"

 PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
 EXTENSIONS="${PASSWORD_STORE_EXTENSIONS_DIR:-$PREFIX/.extensions}"
@@ -348,33 +349,52 @@ cmd_init() {

 cmd_show() {
 	local opts selected_line clip=0 qrcode=0
-	opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")"
+	opts="$($GETOPT -o q::c::o:: -l qrcode::,clip::,otp:: -n "$PROGRAM" -- "$@")"
 	local err=$?
 	eval set -- "$opts"
 	while true; do case $1 in
 		-q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;;
+		-o|--otp) otp=1; otp_length="${2:-6}"; shift 2 ;;
 		-c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;;
 		--) shift; break ;;
 	esac done

-	[[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]"
+	[[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 && $otp -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [--otp[=length]],-o[length]] [pass-name]"

+	if [[ $otp -eq 1 ]] ; then
+		[[ -z $OATHTOOL ]] && die "this feature requires oathtool"
+		[[ $otp_length =~ ^[6-8]$ ]] || die "OTP length must be 6, 7 or 8 digits."
+	fi
+
 	local pass
 	local path="$1"
 	local passfile="$PREFIX/$path.gpg"
 	check_sneaky_paths "$path"
 	if [[ -f $passfile ]]; then
 		if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then
-			pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $?
-			echo "$pass" | $BASE64 -d
+			if [[ $otp -eq 1 ]]; then
+				pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile")" || exit $?
+				echo $($OATHTOOL --base32 --totp "$pass" -d $otp_length)
+			else
+				pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $?
+				echo "$pass" | $BASE64 -d
+			fi
 		else
 			[[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number."
 			pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${selected_line} | head -n 1)" || exit $?
 			[[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}."
 			if [[ $clip -eq 1 ]]; then
-				clip "$pass" "$path"
+				if [[ $otp -eq 1 ]]; then
+					clip "$($OATHTOOL --base32 --totp "$pass" -d $otp_length)" "$path"
+				else
+					clip "$pass" "$path"
+				fi
 			elif [[ $qrcode -eq 1 ]]; then
-				qrcode "$pass" "$path"
+				if [[ $otp -eq 1 ]]; then
+					qrcode "otpauth://totp/?secret=$pass&issuer=$path" "$path"
+				else
+					qrcode "$pass" "$path"
+				fi
 			fi
 		fi
 	elif [[ -d $PREFIX/$path ]]; then
diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh
index a4b782f..4139a10 100755
--- a/tests/t0020-show-tests.sh
+++ b/tests/t0020-show-tests.sh
@@ -19,4 +19,18 @@ test_expect_success 'Test "show" of nonexistant password' '
 	test_must_fail "$PASS" show cred2
 '

+which oathtool &>/dev/null && OATHTOOL="oathtool"
+if [[ ! -z $OATHTOOL ]] ; then
+
+	test_expect_success 'Test "show" command with 6 digit otp' '
+		"$PASS" insert -e "2FA/Token6"<<<"QXXFF4RRTTIIT4MMZZXZ555WWWWQQQFF" &&
+		[[ $("$PASS" show "2FA/Token6" -o6) =~ ^[0-9]{6}$ ]]
+	'
+
+	test_expect_success 'Test "show" command with 8 digit otp' '
+		"$PASS" insert -e "2FA/Token8"<<<"QXXFF4RRTTIIT4MMZZXZ555WWWWQQQFF" &&
+		[[ $("$PASS" show "2FA/Token8" -o8) =~ ^[0-9]{8}$ ]]
+	'
+fi
+
 test_done
--
2.17.2




More information about the Password-Store mailing list