Add secret option (#43)

* Support insert/append secret directly

* Validate issuer and accout name option in secret directly mode

* Add test for secret option

* Fix up docs
This commit is contained in:
Osamu Matsumoto 2018-01-22 06:23:32 +09:00 committed by Tad Fisher
parent 33f390ba22
commit b8009511b3
4 changed files with 150 additions and 29 deletions

112
otp.bash
View File

@ -18,6 +18,26 @@
OATH=$(which oathtool)
## source: https://gist.github.com/cdown/1163649
urlencode() {
local l=${#1}
for (( i = 0 ; i < l ; i++ )); do
local c=${1:i:1}
case "$c" in
[a-zA-Z0-9.~_-]) printf "%c" "$c";;
' ') printf + ;;
*) printf '%%%.2X' "'$c"
esac
done
}
urldecode() {
# urldecode <string>
local url_encoded="${1//+/ }"
printf '%b' "${url_encoded//%/\\x}"
}
# Parse a Key URI per: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
# Vars are consumed by caller
# shellcheck disable=SC2034
@ -34,8 +54,8 @@ otp_parse_uri() {
otp_type=${BASH_REMATCH[1]}
otp_label=${BASH_REMATCH[3]}
otp_accountname=${BASH_REMATCH[6]}
[[ -z $otp_accountname ]] && otp_accountname=${BASH_REMATCH[4]} || otp_issuer=${BASH_REMATCH[4]}
otp_accountname=$(urldecode "${BASH_REMATCH[6]}")
[[ -z $otp_accountname ]] && otp_accountname=$(urldecode "${BASH_REMATCH[4]}") || otp_issuer=$(urldecode "${BASH_REMATCH[4]}")
[[ -z $otp_accountname ]] && die "Invalid key URI (missing accountname): $otp_uri"
local p=${BASH_REMATCH[7]}
@ -50,7 +70,7 @@ otp_parse_uri() {
algorithm) otp_algorithm=${BASH_REMATCH[2]} ;;
period) otp_period=${BASH_REMATCH[2]} ;;
counter) otp_counter=${BASH_REMATCH[2]} ;;
issuer) otp_issuer=${BASH_REMATCH[2]} ;;
issuer) otp_issuer=$(urldecode "${BASH_REMATCH[2]}") ;;
*) ;;
esac
fi
@ -82,6 +102,29 @@ otp_read_uri() {
otp_parse_uri "$uri"
}
otp_read_secret() {
local uri prompt="$1" echo="$2" issuer accountname
issuer="$(urlencode "$3")"
accountname="$(urlencode "$4")"
if [[ -t 0 ]]; then
if [[ $echo -eq 0 ]]; then
read -r -p "Enter secret for $prompt: " -s secret || exit 1
echo
read -r -p "Retype secret for $prompt: " -s secret_again || exit 1
echo
[[ "$secret" == "$secret_again" ]] || die "Error: the entered secrets do not match."
else
read -r -p "Enter secret for $prompt: " -e secret
fi
else
read -r secret
fi
uri="otpauth://totp/$issuer:$accountname?secret=$secret&issuer=$issuer"
otp_parse_uri "$uri"
}
otp_insert() {
local path="$1" passfile="$2" contents="$3" message="$4"
@ -104,15 +147,30 @@ Usage:
Generate an OTP code and optionally put it on the clipboard.
If put on the clipboard, it will be cleared in $CLIP_TIME seconds.
$PROGRAM otp insert [--force,-f] [--echo,-e] [pass-name]
Prompt for and insert a new OTP key URI. If pass-name is not supplied,
use the URI label. Optionally, echo the input. Prompt before overwriting
existing password unless forced. This command accepts input from stdin.
$PROGRAM otp insert [--force,-f] [--echo,-e]
[[--secret, -s] [--issuer,-i issuer] [--account,-a account]]
[pass-name]
Prompt for and insert a new OTP key.
$PROGRAM otp append [--force,-f] [--echo,-e] pass-name
Appends an OTP key URI to an existing password file. Optionally, echo
the input. Prompt before overwriting an existing URI unless forced. This
command accepts input from stdin.
If 'secret' is specified, prompt for the OTP secret, assuming SHA1
algorithm, 30-second period, and 6 OTP digits; one of 'issuer' or
'account' is also required. Otherwise, prompt for a key URI; if
'pass-name' is not supplied, use the URI label.
Optionally, echo the input. Prompt before overwriting existing URI
unless forced. This command accepts input from stdin.
$PROGRAM otp append [--force,-f] [--echo,-e]
[[--secret, -s] [--issuer,-i issuer] [--account,-a account]]
pass-name
Appends an OTP key URI to an existing password file.
If 'secret' is specified, prompt for the OTP secret, assuming SHA1
algorithm, 30-second period, and 6 OTP digits; one of 'issuer' or
'account' is also required. Otherwise, prompt for a key URI.
Optionally, echo the input. Prompt before overwriting an existing URI
unless forced. This command accepts input from stdin.
$PROGRAM otp uri [--clip,-c] [--qrcode,-q] pass-name
Display the key URI stored in pass-name. Optionally, put it on the
@ -127,17 +185,20 @@ _EOF
}
cmd_otp_insert() {
local opts force=0 echo=0
opts="$($GETOPT -o fe -l force,echo -n "$PROGRAM" -- "$@")"
local opts force=0 echo=0 from_secret=0
opts="$($GETOPT -o fesi:a: -l force,echo,secret,issuer:,account: -n "$PROGRAM" -- "$@")"
local err=$?
eval set -- "$opts"
while true; do case $1 in
-f|--force) force=1; shift ;;
-e|--echo) echo=1; shift ;;
-s|--secret) from_secret=1; shift;;
-i|--issuer) issuer=$2; shift; shift;;
-a|--account) account=$2; shift; shift;;
--) shift; break ;;
esac done
[[ $err -ne 0 ]] && die "Usage: $PROGRAM $COMMAND insert [--force,-f] [--echo,-e] [pass-name]"
[[ $err -ne 0 ]] && die "Usage: $PROGRAM $COMMAND insert [--force,-f] [--echo,-e] [--secret, -s] [--issuer,-i issuer] [--account,-a account] [pass-name]"
local prompt path uri
if [[ $# -eq 1 ]]; then
@ -147,7 +208,12 @@ cmd_otp_insert() {
prompt="this token"
fi
otp_read_uri "$prompt" $echo
if [[ $from_secret -eq 1 ]]; then
([[ -z "$issuer" ]] || [[ -z "$account" ]]) && die "Missing issuer or account"
otp_read_secret "$prompt" $echo "$issuer" "$account"
else
otp_read_uri "$prompt" $echo
fi
if [[ -z "$path" ]]; then
[[ -n "$otp_issuer" ]] && path+="$otp_issuer/"
@ -162,17 +228,20 @@ cmd_otp_insert() {
}
cmd_otp_append() {
local opts force=0 echo=0
opts="$($GETOPT -o fe -l force,echo -n "$PROGRAM" -- "$@")"
local opts force=0 echo=0 from_secret=0
opts="$($GETOPT -o fesi:a: -l force,echo,secret,issuer:account: -n "$PROGRAM" -- "$@")"
local err=$?
eval set -- "$opts"
while true; do case $1 in
-f|--force) force=1; shift ;;
-e|--echo) echo=1; shift ;;
-s|--secret) from_secret=1; shift;;
-i|--issuer) issuer=$2; shift; shift;;
-a|--account) account=$2; shift; shift;;
--) shift; break ;;
esac done
[[ $err -ne 0 || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND append [--force,-f] [--echo,-e] pass-name"
[[ $err -ne 0 || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND append [--force,-f] [--echo,-e] [--secret, -s] [--issuer,-i issuer] [--account,-a account] pass-name"
local prompt uri
local path="${1%/}"
@ -189,7 +258,12 @@ cmd_otp_append() {
[[ -n "$existing" ]] && yesno "An OTP secret already exists for $path. Overwrite it?"
otp_read_uri "$path" $echo
if [[ $from_secret -eq 1 ]]; then
([[ -z "$issuer" ]] || [[ -z "$account" ]]) && die "Missing issuer or account"
otp_read_secret "$prompt" $echo "$issuer" "$account"
else
otp_read_uri "$prompt" $echo
fi
local replaced
if [[ -n "$existing" ]]; then

View File

@ -37,28 +37,46 @@ and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP)
seconds. This command is alternatively named \fBshow\fP.
.TP
\fBotp insert\fP [ \fI--force\fP, \fI-f\fP ] [ \fI--echo\fP, \fI-e\fP ] [ \fIpass-name\fP ]
\fBotp insert\fP [ \fI--force\fP, \fI-f\fP ] [ \fI--echo\fP, \fI-e\fP ] \
[ [ \fI--secret\fP, \fI-s\fP ] [ \fI--issuer\fP, \fI-i\fP \fIissuer\fP ] \
[ \fI--account\fP, \fI-a\fP \fIaccount\fP ] ] [ \fIpass-name\fP ]
Prompt for and insert a new OTP secret into the password store at
\fIpass-name\fP. The secret must be formatted according to the Key Uri Format;
see the documentation at
\fIpass-name\fP.
If \fI--secret\fP is specified, prompt for the \fIsecret\fP value, assuming SHA1
algorithm, 30-second period, and 6 OTP digits. One or both of \fIissuer\fP and
\fIaccount\fP must also be specified.
If \fI--secret\fP is not specified, prompt for a key URI; see the documentation at
.UR https://\:github.\:com/\:google/\:google-authenticator/\:wiki/\:Key-Uri-Format
.UE .
The URI is consumed from stdin; specify \fI--echo\fP or \fI-e\fP to echo input
.UE
for the key URI specification.
The secret is consumed from stdin; specify \fI--echo\fP or \fI-e\fP to echo input
when running this command interactively. If \fIpass-name\fP is not specified,
convert the \fIissuer:accountname\fP URI label to a path in the form of
\fIisser/accountname\fP. Prompt before overwriting an existing password, unless
\fIisser/accountname\fP. Prompt before overwriting an existing secret, unless
\fI--force\fP or \fI-f\fP is specified. This command is alternatively named
\fBadd\fP.
.TP
\fBotp append\fP [ \fI--force\fP, \fI-f\fP ] [ \fI--echo\fP, \fI-e\fP ] \fIpass-name\fP
\fBotp append\fP [ \fI--force\fP, \fI-f\fP ] [ \fI--echo\fP, \fI-e\fP ] \
[ [ \fI--secret\fP, \fI-s\fP ] [ \fI--issuer\fP, \fI-i\fP \fIissuer\fP ] \
[ \fI--account\fP, \fI-a\fP \fIaccount\fP ] ] \fIpass-name\fP
Append an OTP secret to the password stored in \fIpass-name\fP, preserving any
existing lines. The secret must be formatted according to the Key Uri Format;
see the documentation at
existing lines.
If \fI--secret\fP is specified, prompt for the \fIsecret\fP value, assuming SHA1
algorithm, 30-second period, and 6 OTP digits. One or both of \fIissuer\fP and
\fIaccount\fP must also be specified.
If \fI--secret\fP is not specified, prompt for a key URI; see the documentation at
.UR https://\:github.\:com/\:google/\:google-authenticator/\:wiki/\:Key-Uri-Format
.UE .
.UE
for the key URI specification.
The URI is consumed from stdin; specify \fI--echo\fP or \fI-e\fP to echo input
when running this command interactively. Prompt before overwriting an existing
secret, unless \fI--force\fP or \fI-f\fP is specified.

View File

@ -14,6 +14,17 @@ test_expect_success 'Reads non-terminal input' '
[[ $("$PASS" otp uri passfile) == "$uri" ]]
'
test_expect_success 'Read secret non-terminal input' '
existing="foo bar baz"
secret=JBSWY3DPEHPK3PXP
uri="otpauth://totp/Example:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
test_pass_init &&
"$PASS" insert -e passfile <<< "$existing" &&
"$PASS" otp append -s -i Example -a alice@google.com -e passfile <<< "$secret" &&
[[ $("$PASS" otp uri passfile) == "$uri" ]]
'
test_expect_success 'Reads terminal input in noecho mode' '
existing="foo bar baz"
uri="otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"

View File

@ -112,4 +112,22 @@ test_expect_success 'Force overwrites key URI' '
[[ $("$PASS" show passfile) == "$uri2" ]]
'
test_expect_success 'Insert passfile from secret with options(issuer, accountname)' '
secret="JBSWY3DPEHPK3PXP"
uri="otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
test_pass_init &&
"$PASS" otp insert -s -i Example -a alice@google.com passfile <<< "$secret" &&
echo [[ $("$PASS" show passfile) == "$uri" ]]
'
test_expect_success 'Insert from secret without passfile' '
secret="JBSWY3DPEHPK3PXP"
uri="otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
test_pass_init &&
"$PASS" otp insert -s -i Example -a alice@google.com <<< "$secret" &&
echo [[ $("$PASS" show Example/alice@google.com) == "$uri" ]]
'
test_done