aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTad Fisher <tad@simple.com>2017-03-18 15:37:07 -0700
committerTad Fisher <tad@simple.com>2017-03-18 15:44:12 -0700
commit01a95a45932f9a54c2c2fc3423824bdbac3918d8 (patch)
tree828ff13a10d9864c4568c2ebe4d3589818926c0b
parentdb2baf62a27d5a657d0477d928f21a739981dc46 (diff)
Parse key URIs
-rwxr-xr-xotp.bash44
-rwxr-xr-xtest/uri.t27
2 files changed, 71 insertions, 0 deletions
diff --git a/otp.bash b/otp.bash
index 4a21322..93f815e 100755
--- a/otp.bash
+++ b/otp.bash
@@ -18,6 +18,45 @@
OATH=$(which oathtool)
+# Parse a Key URI per: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+# Vars are consumed by caller
+# shellcheck disable=SC2034
+otp_parse_uri() {
+ local uri="$*"
+
+ uri="${uri//\`/%60}"
+ uri="${uri//\"/%22}"
+
+ local pattern='^otpauth:\/\/(totp|hotp)(\/(([^:?]+)?(:([^:?]*))?))?(\?([^#&?]+))(&([^#&?]+))*$'
+ [[ "$uri" =~ $pattern ]] || die "Cannot parse OTP key URI: $uri"
+
+ otp_uri=${BASH_REMATCH[0]}
+ 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]}
+
+ local parameters=(${BASH_REMATCH[@]:7})
+ pattern='^([^?&=]+)(=(.+))$'
+ for param in "${parameters[@]}"; do
+ if [[ "$param" =~ $pattern ]]; then
+ case ${BASH_REMATCH[1]} in
+ secret) otp_secret=${BASH_REMATCH[3]} ;;
+ digits) otp_digits=${BASH_REMATCH[3]} ;;
+ algorithm) otp_algorithm=${BASH_REMATCH[3]} ;;
+ period) otp_period=${BASH_REMATCH[3]} ;;
+ counter) otp_counter=${BASH_REMATCH[3]} ;;
+ issuer) otp_issuer=${BASH_REMATCH[3]} ;;
+ *) ;;
+ esac
+ fi
+ done
+
+ [[ -z "$otp_secret" ]] && die "Invalid key URI (missing secret): $otp_uri"
+ [[ "$otp_type" == 'hotp' && -z "$otp_counter" ]] && die "Invalid key URI (missing counter): $otp_uri"
+}
+
otp_increment_counter() {
local ret=$1
local counter=$2 contents="$3" path="$4" passfile="$5"
@@ -265,11 +304,16 @@ cmd_otp_uri() {
fi
}
+cmd_otp_validate() {
+ otp_parse_uri "$1"
+}
+
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 "$@" ;;
+ validate) shift; cmd_otp_validate "$@" ;;
*) cmd_otp_show "$@" ;;
esac
exit 0
diff --git a/test/uri.t b/test/uri.t
new file mode 100755
index 0000000..9fbe66c
--- /dev/null
+++ b/test/uri.t
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+export test_description='Tests pass otp URI parsing'
+
+. ./setup.sh
+
+test_expect_success 'Parses a basic TOTP URI' '
+ "$PASS" otp validate "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
+'
+
+test_expect_success 'Parses a complex TOTP URI' '
+ "$PASS" otp validate otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
+'
+
+test_expect_success 'Fails for bogus URL' '
+ test_must_fail "$PASS" otp validate https://www.google.com/
+'
+
+test_expect_success 'Fails for missing secret' '
+ test_must_fail "$PASS" otp validate otpauth://totp/ACME%20Co:john.doe@email.com?issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
+'
+
+test_expect_success 'Fails for missing counter' '
+ test_must_fail "$PASS" otp validate otpauth://hotp?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ
+'
+
+test_done