Skip to content

Commit

Permalink
Merge pull request #56 from tadfisher/develop
Browse files Browse the repository at this point in the history
Merge branch 'develop' into master
  • Loading branch information
tadfisher authored Mar 4, 2018
2 parents 0737f1f + 948c2f0 commit f88e6a3
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 46 deletions.
32 changes: 26 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
dist: trusty
sudo: false

language: bash

env:
- PASS_VERSION=1.7.1
- PASS_VERSION=master

addons:
apt:
sources:
- debian-sid
packages:
- cabal-install
- expect
- ghc
- oathtool
- shellcheck

before_script:
- wget https://git.zx2c4.com/password-store/snapshot/password-store-1.7.tar.xz
- tar -xvf password-store-1.7.tar.xz
- ln -s password-store-1.7/src/password-store.sh pass
- wget https://git.zx2c4.com/password-store/snapshot/password-store-$PASS_VERSION.tar.xz
- tar -xvf password-store-$PASS_VERSION.tar.xz
- ln -s password-store-$PASS_VERSION/src/password-store.sh pass
- if [ ! -f "${HOME}/.cabal/bin/shellcheck" ]; then cabal update; cabal install ShellCheck; fi
- ln -s "${HOME}/.cabal/bin/shellcheck" shellcheck
- export PATH=$PATH:.
- export TEST_OPTS="-v"

install: true

before_cache:
- rm $HOME/.cabal/logs/build.log

cache:
directories:
- $HOME/.cabal

script:
- make lint
- cd test && make all

notifications:
email:
recipients:
- [email protected]
on_success: never
on_failure: always
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ uninstall:
lint:
shellcheck -s bash $(PROG).bash

.PHONY: install uninstall lint
test:
$(MAKE) -C test

.PHONY: install uninstall lint test
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,20 @@ sudo make install

### Arch Linux

`pass-otp` is available in the
[Arch User Repository](https://aur.archlinux.org/packages/pass-otp/).
`pass-otp` is available in the `[community]` repository:

```
pacman -S pass-otp
```

### macOS

```
brew install oath-toolkit
git clone https://github.com/tadfisher/pass-otp
cd pass-otp
make install PREFIX=/usr/local
```

## Requirements

Expand Down
129 changes: 104 additions & 25 deletions otp.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,12 +54,13 @@ 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]}
local IFS=\&; local params=(${p[@]}); unset IFS
local params
local IFS=\&; read -r -a params <<< "$p"; unset IFS

pattern='^(.+)=(.+)$'
for param in "${params[@]}"; do
Expand All @@ -50,7 +71,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
Expand Down Expand Up @@ -82,6 +103,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"

Expand All @@ -104,15 +148,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.
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.
$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.
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
Expand All @@ -127,17 +186,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
Expand All @@ -147,7 +209,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/"
Expand All @@ -162,17 +229,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%/}"
Expand All @@ -189,7 +259,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
Expand Down Expand Up @@ -229,7 +304,7 @@ cmd_otp_code() {
local path="${1%/}"
local passfile="$PREFIX/$path.gpg"
check_sneaky_paths "$path"
[[ ! -f $passfile ]] && die "Passfile not found"
[[ ! -f $passfile ]] && die "$path: passfile not found."

contents=$($GPG -d "${GPG_OPTS[@]}" "$passfile")
while read -r -a line; do
Expand All @@ -243,7 +318,7 @@ cmd_otp_code() {
case "$otp_type" in
totp)
cmd="$OATH -b --totp"
[[ -n "$otp_algorithm" ]] && cmd+="=$otp_algorithm"
[[ -n "$otp_algorithm" ]] && cmd+=$(echo "=${otp_algorithm}"|tr "[:upper:]" "[:lower:]")
[[ -n "$otp_period" ]] && cmd+=" --time-step-size=$otp_period"s
[[ -n "$otp_digits" ]] && cmd+=" --digits=$otp_digits"
cmd+=" $otp_secret"
Expand All @@ -255,9 +330,13 @@ cmd_otp_code() {
[[ -n "$otp_digits" ]] && cmd+=" --digits=$otp_digits"
cmd+=" $otp_secret"
;;

*)
die "$path: OTP secret not found."
;;
esac

local out; out=$($cmd) || die "Failed to generate OTP code for $path"
local out; out=$($cmd) || die "$path: failed to generate OTP code."

if [[ "$otp_type" == "hotp" ]]; then
# Increment HOTP counter in-place
Expand Down Expand Up @@ -304,9 +383,9 @@ cmd_otp_uri() {
fi
done <<< "$contents"

if [[ clip -eq 1 ]]; then
if [[ $clip -eq 1 ]]; then
clip "$otp_uri" "OTP key URI for $path"
elif [[ qrcode -eq 1 ]]; then
elif [[ $qrcode -eq 1 ]]; then
qrcode "$otp_uri" "OTP key URI for $path"
else
echo "$otp_uri"
Expand Down
38 changes: 28 additions & 10 deletions pass-otp.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ clean-except-prove-cache:

clean: clean-except-prove-cache
$(RM) .prove
$(RM) gnupg/random_seed

aggregate-results-and-cleanup: $(T)
$(MAKE) aggregate-results
Expand Down
Loading

0 comments on commit f88e6a3

Please sign in to comment.