aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTad Fisher <tad@simple.com>2017-02-14 16:13:32 -0800
committerTad Fisher <tad@simple.com>2017-02-14 16:23:58 -0800
commita4e02eabb56faa60b1c29fc4008193135a7cfd47 (patch)
treedefb0a39fd89e08baef2e50dff8c02cb192e1616
Initial commit
-rw-r--r--Makefile37
-rw-r--r--README.md87
-rwxr-xr-xotp.bash275
-rw-r--r--pass-otp.1130
4 files changed, 529 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..da01f52
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+PROG ?= otp
+PREFIX ?= /usr
+DESTDIR ?=
+LIBDIR ?= $(PREFIX)/lib
+SYSTEM_EXTENSION_DIR ?= $(LIBDIR)/password-store/extensions
+MANDIR ?= $(PREFIX)/share/man
+
+all:
+ @echo "pass-$(PROG) is a shell script and does not need compilation, it can be simply executed."
+ @echo ""
+ @echo "To install it try \"make install\" instead."
+ @echo
+ @echo "To run pass $(PROG) one needs to have some tools installed on the system:"
+ @echo " password store"
+
+install:
+ @install -v -d "$(DESTDIR)$(MANDIR)/man1" && install -m 0644 -v pass-$(PROG).1 "$(DESTDIR)$(MANDIR)/man1/pass-$(PROG).1"
+ @install -v -d "$(DESTDIR)$(SYSTEM_EXTENSION_DIR)/"
+ @install -Dm0755 $(PROG).bash "$(DESTDIR)$(SYSTEM_EXTENSION_DIR)/$(PROG).bash"
+ @echo
+ @echo "pass-$(PROG) is installed succesfully"
+ @echo
+
+uninstall:
+ @rm -vrf \
+ "$(DESTDIR)$(SYSTEM_EXTENSION_DIR)/$(PROG).bash" \
+ "$(DESTDIR)$(IMPORTERS_DIR)/" \
+ "$(DESTDIR)$(MANDIR)/man1/pass-$(PROG).1" \
+
+test:
+ make -C tests
+
+lint:
+ shellcheck -s bash $(PROG).bash
+
+
+.PHONY: install uninstall test lint
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..72494da
--- /dev/null
+++ b/README.md
@@ -0,0 +1,87 @@
+# pass-otp
+
+A [pass](https://www.passwordstore.org/) extension for managing
+one-time-password (OTP) tokens.
+
+## Usage
+
+```
+Usage:
+ pass otp [show] [--clip,-c] pass-name
+ Generate an OTP code and optionally put it on the clipboard.
+ If put on the clipboard, it will be cleared in 45 seconds.
+ pass otp insert totp [--secret=key,-s key] [--algorithm alg,-a alg]
+ [--period=seconds,-p seconds]
+ [--digits=digits,-d digits] [--force,-f] pass-name
+ Insert new TOTP secret. Prompt before overwriting existing password
+ unless forced.
+ pass otp insert hotp [--secret=secret,-s secret]
+ [--digits=digits,-d digits] [--force,-f]
+ pass-name counter
+ Insert new HOTP secret with initial counter. Prompt before overwriting
+ existing password unless forced.
+ pass otp uri [--clip,-c] [--qrcode,-q] pass-name
+ Create a secret key URI suitable for importing into other TOTP clients.
+ Optionally, put it on the clipboard, or display a QR code.
+
+More information may be found in the pass-otp(1) man page.
+```
+
+## Example
+
+Insert a TOTP token:
+
+```
+$ pass otp insert totp -s AAAAAAAAAAAAAAAAAAAAA totp-secret
+[master 4f9b989] Add given OTP secret for totp-secret to store.
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ create mode 100644 totp-secret.gpg
+
+
+$ pass show totp-secret
+otp_secret: AAAAAAAAAAAAAAAAAAAAA
+otp_type: totp
+otp_algorithm: sha1
+otp_period: 30
+otp_digits: 6
+```
+
+Generate a 2FA code using this token:
+
+```
+$ pass otp show totp-secret
+698816
+```
+
+## Installation
+
+````
+git clone https://github.com/tadfisher/pass-otp
+cd pass-otp
+sudo make install
+```
+
+## Requirements
+
+- `pass` 1.7.0 or later for extenstion support
+- `oathtool` for generating 2FA codes
+- `qrencode` for generating QR code images
+
+## License
+
+```
+Copyright (C) 2017 Tad Fisher
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+```
diff --git a/otp.bash b/otp.bash
new file mode 100755
index 0000000..00f57e1
--- /dev/null
+++ b/otp.bash
@@ -0,0 +1,275 @@
+#!/usr/bin/env bash
+# pass otp - Password Store Extension (https://www.passwordstore.org/)
+# Copyright (C) 2017 Tad Fisher
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# []
+
+OATH=$(which oathtool)
+
+otp_increment_counter() {
+ local ret=$1
+ local counter=$2 contents="$3" path="$4" passfile="$5"
+
+ local inc=$((counter+1))
+
+ contents=${contents//otp_counter: $counter/otp_counter: $inc}
+
+ set_gpg_recipients "$(dirname "$path")"
+
+ $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$contents" || die "OTP secret encryption aborted."
+
+ git_add_file "$passfile" "Update HOTP counter value for $path."
+
+ eval $ret="'$inc'"
+}
+
+otp_insert() {
+ local path="${1%/}"
+ local passfile="$PREFIX/$path.gpg"
+ local force=$2
+ local contents="$3"
+
+ check_sneaky_paths "$path"
+
+ [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
+
+ mkdir -p -v "$PREFIX/$(dirname "$path")"
+ set_gpg_recipients "$(dirname "$path")"
+
+ $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$contents" || die "OTP secret encryption aborted."
+
+ git_add_file "$passfile" "Add given OTP secret for $path to store."
+}
+
+otp_insert_totp() {
+ local opts secret="" algorithm="sha1" period=30 digits=6 force=0
+ opts="$($GETOPT -o s:a:p:d:f -l secret:,algorithm:,period:,digits:,force -n "$PROGRAM" -- "$@")"
+ local err=$?
+ eval set -- "$opts"
+ while true; do case $1 in
+ -s|--secret) secret="$2"; shift 2 ;;
+ -a|--algorithm) algorithm="$2"; shift 2 ;;
+ -p|--period) period="$2"; shift 2 ;;
+ -d|--digits) digits="$2"; shift 2 ;;
+ -f|--force) force=1; shift ;;
+ --) shift; break ;;
+ esac done
+
+ [[ $err -ne 0 && $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND insert totp [--secret=key,s key] [--algorithm=algorithm,-a algorithm] [--period=seconds,-p seconds] [--digits=digits,-d digits] [--force,-f] pass-name"
+
+ case $algorithm in
+ sha1|sha256|sha512) ;;
+ *) die "Invalid algorithm '$algorithm'. May be one of 'sha1', 'sha256', or 'sha512'" ;;
+ esac
+
+ case $digits in
+ 6|8) ;;
+ *) die "Invalid digits '$digits'. May be one of '6' or '8'" ;;
+ esac
+
+ if [[ -z $secret ]]; then
+ read -r -p "Enter secret (base32-encoded): " -s secret || exit 1
+ fi
+
+ local contents=$(cat <<-_EOF
+ otp_secret: $secret
+ otp_type: totp
+ otp_algorithm: $algorithm
+ otp_period: $period
+ otp_digits: $digits
+ _EOF
+ )
+
+ otp_insert "$1" $force "$contents"
+}
+
+otp_insert_hotp() {
+ local opts secret="" digits=6 force=0
+ opts="$($GETOPT -o s:d:f -l secret:,digits:,force -n "$PROGRAM" -- "$@")"
+ local err=$?
+ eval set -- "$opts"
+ while true; do case $1 in
+ -s|--secret) secret="$2"; shift 2 ;;
+ -a|--algorithm) algorithm="$2"; shift 2 ;;
+ -d|--digits) digits="$2"; shift 2 ;;
+ -f|--force) force=1; shift ;;
+ --) shift; break ;;
+ esac done
+
+ [[ $err -ne 0 || $# -ne 2 ]] && die "Usage: $PROGRAM $COMMAND insert hotp [--secret=key,s key] [--digits=digits,-d digits] [--force,-f] pass-name counter"
+
+ case $digits in
+ 6|8) ;;
+ *) die "Invalid digits '$digits'. May be one of '6' or '8'" ;;
+ esac
+
+ if [[ -z $secret ]]; then
+ read -r -p "Enter secret (base32-encoded): " -s secret || exit 1
+ fi
+
+ local counter="$2"
+ [[ $counter =~ ^[0-9]+$ ]] || die "Invalid counter '$counter'. Must be a positive number"
+
+ local contents=$(cat <<-_EOF
+ otp_secret: $secret
+ otp_type: hotp
+ otp_counter: $counter
+ otp_digits: $digits
+ _EOF
+ )
+
+ otp_insert "$1" $force "$contents"
+}
+
+cmd_otp_usage() {
+ cat <<-_EOF
+ Usage:
+ $PROGRAM otp [show] [--clip,-c] pass-name
+ 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 totp [--secret=key,-s key] [--algorithm alg,-a alg]
+ [--period=seconds,-p seconds]
+ [--digits=digits,-d digits] [--force,-f] pass-name
+ Insert new TOTP secret. Prompt before overwriting existing password
+ unless forced.
+ $PROGRAM otp insert hotp [--secret=secret,-s secret]
+ [--digits=digits,-d digits] [--force,-f]
+ pass-name counter
+ Insert new HOTP secret with initial counter. Prompt before overwriting
+ existing password unless forced.
+ $PROGRAM otp uri [--clip,-c] [--qrcode,-q] pass-name
+ Create a secret key URI suitable for importing into other TOTP clients.
+ Optionally, put it on the clipboard, or display a QR code.
+
+ More information may be found in the pass-otp(1) man page.
+ _EOF
+ exit 0
+}
+
+cmd_otp_insert() {
+ case "$1" in
+ totp) shift; otp_insert_totp "$@" ;;
+ hotp) shift; otp_insert_hotp "$@" ;;
+ *) die "Invalid OTP type '$1'. May be one of 'totp' or 'hotp'" ;;
+ esac
+}
+
+cmd_otp_show() {
+ local clip=0
+ opts="$($GETOPT -o c -l clip -n "$PROGRAM" -- "$@")"
+ local err=$?
+ eval set -- "$opts"
+ while true; do case $1 in
+ -c|--clip) clip=1; shift ;;
+ --) shift; break ;;
+ esac done
+
+ [[ $err -ne 0 || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND show [--clip,-c] pass-name"
+
+ local path="$1"
+ local passfile="$PREFIX/$path.gpg"
+ check_sneaky_paths "$path"
+ [[ ! -f $passfile ]] && die "Passfile not found"
+
+ local secret="" type="" algorithm="" counter="" period=30 digits=6
+
+ local contents=$($GPG -d "${GPG_OPTS[@]}" "$passfile")
+ while read -r -a line; do case ${line[0]} in
+ otp_secret:) secret=${line[1]} ;;
+ otp_type:) type=${line[1]} ;;
+ otp_algorithm:) algorithm=${line[1]} ;;
+ otp_period:) period=${line[1]} ;;
+ otp_counter:) counter=${line[1]} ;;
+ otp_digits:) digits=${line[1]} ;;
+ *) true ;;
+ esac done <<< "$contents"
+
+ [[ -z $secret ]] && die "Missing otp_secret: line in $passfile"
+ [[ -z $type ]] && die "Missing otp_type: line in $passfile"
+ [[ $type = "totp" && -z $algorithm ]] && die "Missing otp_algorithm: line in $passfile"
+ [[ $type = "hotp" && -z $counter ]] && die "Missing otp_counter: line in $passfile"
+
+ local out
+ case $type in
+ totp) out="$($OATH -b --totp=$algorithm --time-step-size="$period"s --digits=$digits $secret)" ;;
+ hotp) otp_increment_counter counter $counter "$contents" "$path" "$passfile" > /dev/null
+ [[ $? -ne 0 ]] && die "Failed to increment HOTP counter for $passfile"
+ out="$($OATH -b --hotp --counter=$counter --digits=$digits $secret)"
+ ;;
+ *) die "Invalid OTP type '$type'. May be one of 'totp' or 'hotp'" ;;
+ esac
+
+ if [[ $clip -ne 0 ]]; then
+ clip "$out" "OTP code for $path"
+ else
+ echo "$out"
+ fi
+}
+
+cmd_otp_uri() {
+ local qrcode=0 clip=0
+ opts="$($GETOPT -o q -l qrcode -n "$PROGRAM" -- "$@")"
+ local err=$?
+ eval set -- "$opts"
+ while true; do case $1 in
+ -q|--qrcode) qrcode=1; shift ;;
+ -c|--clip) clip=1; shift ;;
+ --) shift; break ;;
+ esac done
+
+ [[ $err -ne 0 || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND uri [--clip,-c | --qrcode,-q] pass-name"
+
+ local path="$1"
+ local passfile="$PREFIX/$path.gpg"
+ check_sneaky_paths "$path"
+ [[ ! -f $passfile ]] && die "Passfile not found"
+
+ local secret="" type="" algorithm="" counter="" period=30 digits=6
+
+ local contents=$($GPG -d "${GPG_OPTS[@]}" "$passfile")
+ while read -r -a line; do case ${line[0]} in
+ otp_secret:) secret=${line[1]} ;;
+ otp_type:) type=${line[1]} ;;
+ otp_algorithm:) algorithm=${line[1]} ;;
+ otp_period:) period=${line[1]} ;;
+ otp_counter:) counter=${line[1]} ;;
+ otp_digits:) digits=${line[1]} ;;
+ *) true ;;
+ esac done <<< "$contents"
+
+ local uri
+ case $type in
+ totp) uri="otpauth://totp/$path?secret=$secret&algorithm=$algorithm&digits=$digits&period=$period" ;;
+ hotp) uri="otpauth://hotp/$path?secret=$secret&digits=$digits&counter=$counter" ;;
+ *) die "Invalid OTP type '$type'. Must be one of 'totp' or 'hotp'" ;;
+ esac
+
+ if [[ clip -eq 1 ]]; then
+ clip "$uri" "OTP key URI for $path"
+ elif [[ qrcode -eq 1 ]]; then
+ qrcode "$uri" "OTP key URI for $path"
+ else
+ echo "$uri"
+ fi
+}
+
+case "$1" in
+ help|--help|-h) shift; cmd_otp_usage "$@" ;;
+ show) shift; cmd_otp_show "$@" ;;
+ insert|add) shift; cmd_otp_insert "$@" ;;
+ uri) shift; cmd_otp_uri "$@" ;;
+ *) cmd_otp_show "$@" ;;
+esac
+exit 0
diff --git a/pass-otp.1 b/pass-otp.1
new file mode 100644
index 0000000..3b69fd6
--- /dev/null
+++ b/pass-otp.1
@@ -0,0 +1,130 @@
+.TH PASS-OTP 1 "2017 February 14" "Password store OTP extension"
+
+.SH NAME
+pass-otp - A \fBpass\fP(1) extension for managing one-time-password (OTP) tokens.
+
+.SH SYNOPSIS
+.B pass otp
+[
+.I COMMAND
+] [
+.I OPTIONS
+]... [
+.I ARGS
+]...
+
+.SH DESCRIPTION
+
+.B pass-otp
+extends the
+.BR pass (1)
+utility with the
+.B otp
+command for adding OTP secrets, generating OTP codes, and displaying secret key
+URIs using the standard \fIotpauth://\fP scheme.
+
+If no COMMAND is specified, COMMAND defaults to \fBshow\fP.
+
+.SH COMMANDS
+
+.TP
+\fBotp show\fP [ \fI--clip\fP, \fI-c\fP ] \fIpass-name\fP
+
+Generate and print an OTP code from the secret key stored in \fIpass-name\fP. If
+\fI--clip\fP or \fI-c\fP is specified, do not print the code but instead copy it to the clipboard using
+.BR xclip (1)
+and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP)
+seconds.
+
+.TP
+\fBotp insert totp\fP [ \fI--secret\fP=\fIkey\fP, \fI-s\fP \fIkey\fP ] [ \fI--algorithm\fP=\fIalgorithm\fP, \fI-a\fP \fIalgorithm\fP ] [ \fI--period\fP=\fIperiod\fP, \fI-p\fP \fIperiod\fP ] [ \fI--digits\fP=\fIdigits\fP, \fI-d\fP \fIdigits\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP
+
+Insert a new TOTP secret into the password store called \fIpass-name\fP. If
+\fI--secret\fP or \fI-s\fP are not specified, this will read \fIKEY\fP from
+standard in. Prompt before overwriting an existing password, unless
+\fI--force\fP or \fI-f\fP is specified. This command is alternatively named
+\fBadd totp\fP.
+
+.TP
+\fBotp insert hotp\fP [ \fI--secret\fP=\fIkey\fP, \fI-s\fP \fIkey\fP ] [ \fI--digits\fP=\fIdigits\fP, \fI-d\fP \fIdigits\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP \fIcounter\fP
+
+Insert a new HOTP secret into the password store called \fIpass-name\fP. A
+\fIcounter\fP argument is required, which is an integer specifying the initial
+HOTP counter stored alongside the secret. If
+\fI--secret\fP or \fI-s\fP are not specified, this will read \fIKEY\fP from
+standard in. Prompt before overwriting an existing password, unless
+\fI--force\fP or \fI-f\fP is specified. This command is alternatively named
+\fBadd hotp\fP.
+
+.TP
+\fBotp uri\fP [ \fI--clip\fP, \fI-c\fP | \fI--qrcode\fP, \fI-q\fP ] pass-name
+
+Create and print a URI encoding the secret key and OTP parameters using the
+standard \fIotpauth://\fP scheme. If \fI--clip\fP or \fI-c\fP is specified, do
+not print the URI but instead copy it to the clipboard using
+.BR xclip (1)
+and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP)
+seconds. If \fI--qrcode\fP or \fI-q\fP is specified, do not print the URI but
+instead display a QR code using
+.BR qrencode (1)
+either to the terminal or graphically if supported.
+
+.SH OPTIONS
+
+.TP
+\fB\-c\fP, \fB--clip\fP
+Put the OTP code in the clipboard.
+
+.TP
+\fB\-f\fP, \fB--force\fP
+Force to update and do not wait for user instruction.
+
+.TP
+\fB-s\fP \fIkey\fP, \fB--secret\fR=\fIkey\fP
+Provide a secret \fIkey\fP. This key must be base32-encoded.
+
+.TP
+\fB-a\fP \fIalgorithm\fP, \fB--algorithm\fP=\fIalgorithm\fP
+Specify the \fIalgorithm\fP for a TOTP secret. Accepted values are \fIsha1\fP,
+\fIsha256\fP, and \fIsha512\fP. This option defaults to \fIsha1\fP.
+
+.TP
+\fB-p\fP \fIperiod\fP, \fB--period\fP=\fIperiod\fP
+Specify the \fIperiod\fP for a TOTP secret, in seconds. This option defaults to
+\fI30\fP.
+
+.TP
+\fB-d\fP \fIdigits\fP, \fB--digits\fP=\fIdigits\fP
+Specify the number of \fIdigits\fP this secret should generate when used with
+\fBshow\fP. Accepted values are \fI6\fP and \fI8\fP. This option defaults to
+\fI6\fP.
+
+.TP
+\fB\-h\fB, \-\-help\fR
+Show usage message.
+
+.SH SEE ALSO
+.BR pass(1),
+
+
+.SH AUTHORS
+.B pass-otp
+was written by
+.MT tadfisher@gmail.com
+Tad Fisher
+.ME .
+
+
+.SH COPYING
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.