aboutsummaryrefslogtreecommitdiff
path: root/extension/otp.bash
diff options
context:
space:
mode:
Diffstat (limited to 'extension/otp.bash')
-rwxr-xr-xextension/otp.bash95
1 files changed, 95 insertions, 0 deletions
diff --git a/extension/otp.bash b/extension/otp.bash
new file mode 100755
index 0000000..6eb8266
--- /dev/null
+++ b/extension/otp.bash
@@ -0,0 +1,95 @@
+_otp_parse() {
+ urldecode() {
+ local url_encoded="${1//+/ }"
+ printf '%b' "${url_encoded//%/\\x}"
+ }
+
+ local uri="$1"
+
+ uri="${uri//\`/%60}"
+ uri="${uri//\"/%22}"
+
+ local pattern='^otpauth:\/\/(totp|hotp)(\/(([^:?]+)?(:([^:?]*))?)(:([0-9]+))?)?\?(.+)$'
+ [[ "$uri" =~ $pattern ]] || error "Cannot parse OTP key URI: $uri"
+
+ otp_uri=${BASH_REMATCH[0]}
+ otp_type=${BASH_REMATCH[1]}
+ otp_label=${BASH_REMATCH[3]}
+
+ otp_accountname=$(urldecode "${BASH_REMATCH[6]}")
+ [[ -z $otp_accountname ]] && otp_accountname=$(urldecode "${BASH_REMATCH[4]}") || otp_issuer=$(urldecode "${BASH_REMATCH[4]}")
+ [[ -z $otp_accountname ]] && error "Invalid key URI (missing accountname): $otp_uri"
+
+ local p=${BASH_REMATCH[9]}
+ local params
+ local IFS=\&; read -r -a params < <(echo "$p") ; unset IFS
+
+ pattern='^([^=]+)=(.+)$'
+ for param in "${params[@]}"; do
+ if [[ "$param" =~ $pattern ]]; then
+ case ${BASH_REMATCH[1]} in
+ secret) otp_secret=${BASH_REMATCH[2]} ;;
+ digits) otp_digits=${BASH_REMATCH[2]} ;;
+ algorithm) otp_algorithm=${BASH_REMATCH[2]} ;;
+ period) otp_period=${BASH_REMATCH[2]} ;;
+ counter) otp_counter=${BASH_REMATCH[2]} ;;
+ issuer) otp_issuer=$(urldecode "${BASH_REMATCH[2]}") ;;
+ *) ;;
+ esac
+ fi
+ done
+
+ [[ -z "$otp_secret" ]] && error "Invalid key URI (missing secret): $otp_uri"
+
+ pattern='^[0-9]+$'
+ [[ "$otp_type" == 'hotp' ]] && [[ ! "$otp_counter" =~ $pattern ]] && \
+ error "Invalid key URI (missing counter): $otp_uri"
+}
+
+BASE64="base64"
+OATH=$(which oathtool)
+
+otp_code() {
+ [[ -z "$OATH" ]] && error "oathtool is required by otp_code action."
+
+ while read -r line; do
+ if [[ "$line" == otpauth://* ]]; then
+ uri="$line"
+ _otp_parse "$line"
+ break
+ fi
+ done < "$FILE"
+
+ local cmd
+ case "$otp_type" in
+ totp) cmd="$OATH -b --totp"
+ [[ -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"
+ ;;
+
+ hotp)
+ counter=$((otp_counter + 1))
+ cmd="$OATH -b --hotp --counter=$counter"
+ [[ -n "$otp_digits" ]] && cmd+=" --digits=$otp_digits"
+ cmd+=" $otp_secret"
+ ;;
+
+ *) error "OTP secret not found." ;;
+ esac
+
+ eval "$cmd" || error "Failed to generate OTP code."
+
+ if [[ "$otp_type" == "hotp" ]]; then
+ local line replaced uri=${otp_uri/&counter=$otp_counter/&counter=$counter}
+ while IFS= read -r line; do
+ [[ "$line" == otpauth://* ]] && line="$uri"
+ [[ -n "$replaced" ]] && replaced+=$'\n'
+ replaced+="$line"
+ done < "$FILE"
+
+ echo "$replaced" > "$FILE"
+ MESSAGE="Increment HOTP counter for $NAME."
+ fi
+}