-
Notifications
You must be signed in to change notification settings - Fork 0
/
get-totp.py
executable file
·100 lines (77 loc) · 2.75 KB
/
get-totp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/python3
import re
import sys
import subprocess
from collections import namedtuple
from urllib.parse import parse_qs, unquote, urlparse
from colorama import init as colorama_init, Fore
TotpCode = namedtuple("OtpInfo", ("name", "account", "secret", "digits", "period"))
def first_or_none(params, key):
try:
return params[key][0]
except KeyError:
return None
def get_totp_codes():
totp_raw = subprocess.check_output(["pass", "show", "misc/totp-codes"]).decode("utf-8")
totp_codes = []
for line in totp_raw.splitlines():
parts = urlparse(line)
assert parts.netloc == "totp", "OTP kind not TOTP"
assert parts.path.startswith("/")
name = unquote(parts.path[1:])
if ":" in name:
name, account = name.split(":")
else:
account = None
params = parse_qs(parts.query)
secret = first_or_none(params, "secret")
digits = first_or_none(params, "digits")
period = first_or_none(params, "period")
totp_codes.append(TotpCode(name=name, account=account, secret=secret, digits=digits, period=period))
return totp_codes
def get_totp(entry):
command = ["oathtool", "--base32", "--totp"]
if entry.digits:
command.append(f"--digits={entry.digits}")
if entry.period:
command.append(f"--time-step-size={entry.period}")
command.append(entry.secret)
raw_output = subprocess.check_output(command)
return raw_output.decode("utf-8").strip()
def find_matching(totp_codes, app_regex):
matching = []
for entry in totp_codes:
if app_regex.search(entry.name):
matching.append(entry)
return matching
def format_name(entry):
base = f"{Fore.MAGENTA}{entry.name}{Fore.RESET}"
if entry.account is None:
return base
else:
return f"{base} ({Fore.GREEN}{entry.account}{Fore.RESET})"
if __name__ == "__main__":
exit_code = 0
colorama_init()
totp_codes = get_totp_codes()
# No arguments, list all app names
if len(sys.argv) < 2:
print("List of all TOTP applications:")
if not totp_codes:
print("* (no entries found)")
for entry in totp_codes:
totp = get_totp(entry)
print(f"* {format_name(entry)}: {totp}")
sys.exit(0)
# Print TOTP codes for each app listed
for app_pattern in sys.argv[1:]:
app_regex = re.compile(app_pattern, re.IGNORECASE)
entries = find_matching(totp_codes, app_regex)
if not entries:
print(f"No matches for '{app_pattern}'", file=sys.stderr)
exit_code = 1
continue
for entry in entries:
totp = get_totp(entry)
print(f"{format_name(entry)}: {totp}")
sys.exit(exit_code)