bash/sources/18_kotp
raw ยท 4218 bytes
#!/bin/bash
declare -f kstore > /dev/null
if [ $? -ne 0 ]; then
echo "kotp depends on kstore" >&2
return 1
fi
which oathtool > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "oathtool is required" >&2
return 1
fi
_KOTP_PREFIX="${_KOTP_PREFIX:-otp}"
_KOTP_PROP="${_KOTP_PROP:-secret}"
function kotp {
case "$1" in
set) shift; _kotp-set "$@" ;;
gen|get) shift; _kotp-gen "$@" ;;
spread) shift; _kotp-spread "$@" ;;
list) shift; _kotp-list "$@" ;;
del|delete|rm) shift; _kotp-del "$@" ;;
*)
__func_head "set NAME [SECRET|-|file] set/update otp key"
__func_help "gen NAME [TIME] generate otp at TIME; defaults to now"
__func_help "spread NAME [COUNT] [TIME] generate surrounding otp codes; COUNT defaults to 1"
__func_help "list [TERM] list stored otp keys"
__func_help "del NAME delete otp key"
return 1
;;
esac
return $?
}
function _kotp-key {
echo "${_KOTP_PREFIX}:$1"
}
function _kotp-clean-secret {
local s
if [ $# -gt 0 ]; then
s="$1"
else
s="$(cat)"
fi
case "$s" in
otpauth://*)
s="$(printf '%s' "$s" \
| sed -n 's/.*[?&]secret=\([^&]*\).*/\1/p')"
;;
esac
printf '%s' "$s" \
| tr -d '[:space:]-' \
| tr '[:lower:]' '[:upper:]'
}
function _kotp-read-value {
if [ "$1" = "-" ]; then
cat
elif [ -n "$1" ] && [ -f "$1" ]; then
cat "$1"
else
printf '%s' "$1"
fi
}
function _kotp-set {
if [ -z "$1" ]; then
__func_head "NAME [SECRET|-|file]"
return 1
fi
local name src secret key
name="$1"
src="$2"
if [ -z "$src" ]; then
read -rsp "OTP secret: " src
echo
fi
secret="$(_kotp-read-value "$src" | _kotp-clean-secret)"
if [ -z "$secret" ]; then
echo "empty otp secret" >&2
return 1
fi
key="$(_kotp-key "$name")"
# Validate before storing. Most TOTP secrets are base32.
oathtool --totp --base32 "$secret" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "invalid otp secret, or oathtool cannot parse it as base32" >&2
return 1
fi
kstore add "$key" "$secret" "$_KOTP_PROP" > /dev/null 2>&1
if [ $? -ne 0 ]; then
kstore update "$key" "$secret" "$_KOTP_PROP" || return 1
fi
echo "stored: $name"
}
function _kotp-secret {
if [ -z "$1" ]; then
__func_head "NAME"
return 1
fi
kstore get "$(_kotp-key "$1")" "$_KOTP_PROP"
}
function _kotp-gen {
if [ -z "$1" ]; then
__func_head "NAME [TIME]"
return 1
fi
local name when secret
name="$1"
when="$2"
secret="$(_kotp-secret "$name")" || return 1
if [ -n "$when" ]; then
oathtool --totp --base32 --now "$when" "$secret"
else
oathtool --totp --base32 "$secret"
fi
}
function _kotp-spread {
if [ -z "$1" ]; then
__func_head "NAME [COUNT] [TIME]"
return 1
fi
local name count when secret i offset label
name="$1"
count="${2:-1}"
when="$3"
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
echo "COUNT must be a number" >&2
return 1
fi
secret="$(_kotp-secret "$name")" || return 1
for (( i=-count; i<=count; i++ )); do
offset=$(( i * 30 ))
if [ "$i" -eq 0 ]; then
label="now"
elif [ "$i" -lt 0 ]; then
label="${offset}s"
else
label="+${offset}s"
fi
if [ -n "$when" ]; then
printf '%8s ' "$label"
oathtool --totp --base32 --now "$when $offset seconds" "$secret"
else
printf '%8s ' "$label"
oathtool --totp --base32 --now "$offset seconds" "$secret"
fi
done
}
function _kotp-list {
local term="$1"
if [ -n "$term" ]; then
kstore search "${_KOTP_PREFIX}:$term" "$_KOTP_PROP"
else
kstore search "${_KOTP_PREFIX}:" "$_KOTP_PROP"
fi
}
function _kotp-del {
if [ -z "$1" ]; then
__func_head "NAME"
return 1
fi
kstore del "$(_kotp-key "$1")" "$_KOTP_PROP"
}
function _kotp {
local cur scope
cur="${COMP_WORDS[COMP_CWORD]}"
scope="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=()
case "$scope" in
kotp)
COMPREPLY=( $(compgen -W "set gen get spread list del" -- "$cur") )
return
;;
gen|get|spread|del|delete|rm)
COMPREPLY=( $(compgen -W "$(
kstore search "${_KOTP_PREFIX}:" "$_KOTP_PROP" 2>/dev/null \
| awk 'NR > 2 {print $1}' \
| sed "s/^${_KOTP_PREFIX}://"
)" -- "$cur") )
return
;;
set)
COMPREPLY=( $(compgen -f -- "$cur") )
return
;;
esac
}
complete -F _kotp kotp