summaryrefslogtreecommitdiff
path: root/otp2289/__main__.py
diff options
context:
space:
mode:
Diffstat (limited to 'otp2289/__main__.py')
-rw-r--r--otp2289/__main__.py155
1 files changed, 103 insertions, 52 deletions
diff --git a/otp2289/__main__.py b/otp2289/__main__.py
index 7bb0673..471ae24 100644
--- a/otp2289/__main__.py
+++ b/otp2289/__main__.py
@@ -1,7 +1,7 @@
1# -*- coding: utf-8 -*- 1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 3#
4# Copyright (c) 2020, Simeon Simeonov 4# Copyright (c) 2020-2022 Simeon Simeonov
5# All rights reserved. 5# All rights reserved.
6# 6#
7# Redistribution and use in source and binary forms, with or without 7# Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,7 @@ python -m otp2289 --generate-otp-response -s TesT -i 499 -f token
35import argparse 35import argparse
36import errno 36import errno
37import getpass 37import getpass
38import io
38import os 39import os
39import secrets 40import secrets
40import string 41import string
@@ -48,7 +49,7 @@ def eprint(*arg, **kwargs):
48 print(*arg, file=sys.stderr, flush=True, **kwargs) 49 print(*arg, file=sys.stderr, flush=True, **kwargs)
49 50
50 51
51def generate_otp_response(args): 52def generate_otp_response(args: argparse.Namespace) -> str:
52 """ 53 """
53 Generates a response based on the parameters sent from the parser 54 Generates a response based on the parameters sent from the parser
54 55
@@ -65,24 +66,29 @@ def generate_otp_response(args):
65 generator = otp2289.generator.OTPGenerator( 66 generator = otp2289.generator.OTPGenerator(
66 args.password.encode(), 67 args.password.encode(),
67 args.seed, 68 args.seed,
68 args.hash_algo) 69 args.hash_algo,
70 )
69 if args.challenge_string: 71 if args.challenge_string:
70 if args.output_format == 'token': 72 if args.output_format == 'token':
71 return generator.generate_otp_words_from_challenge( 73 return generator.generate_otp_words_from_challenge(
72 args.challenge_string) 74 args.challenge_string
75 )
73 return generator.generate_otp_hexdigest_from_challenge( 76 return generator.generate_otp_hexdigest_from_challenge(
74 args.challenge_string) 77 args.challenge_string
78 )
75 # regular parameters 79 # regular parameters
76 header = '' 80 header = ''
77 if not args.quiet: 81 if not args.quiet:
78 header = (f'Seed: {args.seed}, Step: {args.step}, ' 82 header = (
79 f'Hash: {args.hash_algo}{os.linesep}') 83 f'Seed: {args.seed}, Step: {args.step}, '
84 f'Hash: {args.hash_algo}{os.linesep}'
85 )
80 if args.output_format == 'token': 86 if args.output_format == 'token':
81 return header + generator.generate_otp_words(args.step) 87 return header + generator.generate_otp_words(args.step)
82 return header + generator.generate_otp_hexdigest(args.step) 88 return header + generator.generate_otp_hexdigest(args.step)
83 89
84 90
85def generate_otp_range(args): 91def generate_otp_range(args: argparse.Namespace) -> str:
86 """ 92 """
87 Generates range of responses based on the parameters sent from the parser 93 Generates range of responses based on the parameters sent from the parser
88 94
@@ -99,7 +105,8 @@ def generate_otp_range(args):
99 generator = otp2289.generator.OTPGenerator( 105 generator = otp2289.generator.OTPGenerator(
100 args.password.encode(), 106 args.password.encode(),
101 args.seed, 107 args.seed,
102 args.hash_algo) 108 args.hash_algo,
109 )
103 if args.output_format == 'token': 110 if args.output_format == 'token':
104 method = generator.generate_otp_words 111 method = generator.generate_otp_words
105 else: 112 else:
@@ -112,25 +119,35 @@ def generate_otp_range(args):
112 # any need for quiet? 119 # any need for quiet?
113 header = '' 120 header = ''
114 if not args.quiet: 121 if not args.quiet:
115 header = (f'Seed: {args.seed}, Step: {args.step}, ' 122 header = (
116 f'Hash: {args.hash_algo}, Range: {args.range}{os.linesep}') 123 f'Seed: {args.seed}, Step: {args.step}, '
124 f'Hash: {args.hash_algo}, Range: {args.range}{os.linesep}'
125 )
117 return header + os.linesep.join( 126 return header + os.linesep.join(
118 [f'{step}: ' + method(step) for step in range( 127 [
119 args.step, args.step - args.range, -1)]) 128 f'{step}: ' + method(step)
129 for step in range(
130 args.step,
131 args.step - args.range,
132 -1,
133 )
134 ]
135 )
120 136
121 137
122def get_rnd_seed(): 138def get_rnd_seed() -> str:
123 """ 139 """
124 Returns a random seed in the format: 140 Returns a random seed in the format:
125 141
126 2 random letters (capitalize()) + 5 random digits 142 2 random letters (capitalize()) + 5 random digits
127 """ 143 """
128 rnd = secrets.SystemRandom() 144 rnd = secrets.SystemRandom()
129 return (''.join(rnd.choices(string.ascii_lowercase, k=2)).capitalize() + 145 return ''.join(
130 ''.join(rnd.choices(string.digits, k=5))) 146 rnd.choices(string.ascii_lowercase, k=2)
147 ).capitalize() + ''.join(rnd.choices(string.digits, k=5))
131 148
132 149
133def initiate_new_sequence(args): 150def initiate_new_sequence(args: argparse.Namespace) -> str:
134 """ 151 """
135 Generates a new sequence based on the parameters sent from the parser. 152 Generates a new sequence based on the parameters sent from the parser.
136 153
@@ -148,15 +165,19 @@ def initiate_new_sequence(args):
148 args.seed = get_rnd_seed() 165 args.seed = get_rnd_seed()
149 header = '' 166 header = ''
150 if not args.quiet: 167 if not args.quiet:
151 header = (f'Seed: {args.seed}, Step: {args.step}, ' 168 header = (
152 f'Hash: {args.hash_algo}{os.linesep}') 169 f'Seed: {args.seed}, Step: {args.step}, '
170 f'Hash: {args.hash_algo}{os.linesep}'
171 )
153 generator = otp2289.generator.OTPGenerator( 172 generator = otp2289.generator.OTPGenerator(
154 args.password.encode(), 173 args.password.encode(),
155 args.seed, 174 args.seed,
156 args.hash_algo) 175 args.hash_algo,
176 )
157 if args.challenge_string: 177 if args.challenge_string:
158 return header + generator.generate_otp_hexdigest_from_challenge( 178 return header + generator.generate_otp_hexdigest_from_challenge(
159 args.challenge_string) 179 args.challenge_string
180 )
160 return header + generator.generate_otp_hexdigest(args.step) 181 return header + generator.generate_otp_hexdigest(args.step)
161 182
162 183
@@ -164,92 +185,122 @@ def main(args=None):
164 """the main entry point""" 185 """the main entry point"""
165 parser = argparse.ArgumentParser( 186 parser = argparse.ArgumentParser(
166 prog=__package__, 187 prog=__package__,
167 epilog=(f'%(prog)s {otp2289.__version__} by Simeon Simeonov ' 188 epilog=(
168 '(sgs @ LiberaChat)'), 189 f'%(prog)s {otp2289.__version__} by Simeon Simeonov '
169 description='The following options are available') 190 '(sgs @ LiberaChat)'
191 ),
192 description='The following options are available',
193 )
170 group = parser.add_mutually_exclusive_group(required=True) 194 group = parser.add_mutually_exclusive_group(required=True)
171 group.add_argument( 195 group.add_argument(
172 '--generate-otp-range', 196 '--generate-otp-range',
173 action='store_true', 197 action='store_true',
174 dest='generate_otp_range', 198 dest='generate_otp_range',
175 default=False, 199 default=False,
176 help='Generates a range of OTP responses') 200 help='Generates a range of OTP responses',
201 )
177 group.add_argument( 202 group.add_argument(
178 '--generate-otp-response', 203 '--generate-otp-response',
179 action='store_true', 204 action='store_true',
180 dest='generate_otp_response', 205 dest='generate_otp_response',
181 default=False, 206 default=False,
182 help='Generates a new OTP response') 207 help='Generates a new OTP response',
208 )
183 group.add_argument( 209 group.add_argument(
184 '--initiate-new-sequence', 210 '--initiate-new-sequence',
185 action='store_true', 211 action='store_true',
186 dest='initiate_new_sequence', 212 dest='initiate_new_sequence',
187 default=False, 213 default=False,
188 help=('Initiates a new OTP sequence. Essentially the same as ' 214 help=(
189 '--generate-otp-response only it prompts twice for password ' 215 'Initiates a new OTP sequence. Essentially the same as '
190 'and always outputs hex (ignores -f).')) 216 '--generate-otp-response only it prompts twice for password '
217 'and always outputs hex (ignores -f).'
218 ),
219 )
191 parser.add_argument( 220 parser.add_argument(
192 '-a', '--hash-algorithm', 221 '-a',
222 '--hash-algorithm',
193 metavar='<md5 | sha1>', 223 metavar='<md5 | sha1>',
194 type=str, 224 type=str,
195 dest='hash_algo', 225 dest='hash_algo',
196 default='md5', 226 default='md5',
197 help='The hash algorithm to use. Possible values: md5 (default), sha1') 227 help='The hash algorithm to use. Possible values: md5 (default), sha1',
228 )
198 parser.add_argument( 229 parser.add_argument(
199 '-c', '--challenge-string', 230 '-c',
231 '--challenge-string',
200 metavar='<challenge string>', 232 metavar='<challenge string>',
201 type=str, 233 type=str,
202 dest='challenge_string', 234 dest='challenge_string',
203 default='', 235 default='',
204 help='Use challenge string when generating response') 236 help='Use challenge string when generating response',
237 )
205 parser.add_argument( 238 parser.add_argument(
206 '-f', '--output-format', 239 '-f',
240 '--output-format',
207 metavar='<hex | token>', 241 metavar='<hex | token>',
208 type=str, 242 type=str,
209 dest='output_format', 243 dest='output_format',
210 default='hex', 244 default='hex',
211 help='The output format to use. Possible values: hex (default), token') 245 help='The output format to use. Possible values: hex (default), token',
246 )
212 parser.add_argument( 247 parser.add_argument(
213 '-i', '--step', 248 '-i',
249 '--step',
214 metavar='<step>', 250 metavar='<step>',
215 type=int, 251 type=int,
216 dest='step', 252 dest='step',
217 default=500, 253 default=500,
218 help='The step. Default for initiating a new sequence is: 500') 254 help='The step. Default for initiating a new sequence is: 500',
255 )
219 parser.add_argument( 256 parser.add_argument(
220 '-p', '--password', 257 '-p',
258 '--password',
221 metavar='<PASSWORD[FILE]>', 259 metavar='<PASSWORD[FILE]>',
222 type=str, 260 type=str,
223 dest='password', 261 dest='password',
224 default='', 262 default='',
225 help=('The password or path to password file ' 263 help=(
226 '(default & recommended: prompt for passwd)')) 264 'The password or path to password file '
265 '(default & recommended: prompt for passwd)'
266 ),
267 )
227 parser.add_argument( 268 parser.add_argument(
228 '-q', '--quiet', 269 '-q',
270 '--quiet',
229 action='store_true', 271 action='store_true',
230 dest='quiet', 272 dest='quiet',
231 default=False, 273 default=False,
232 help='Dot not show headers. Only hex / tokens') 274 help='Dot not show headers. Only hex / tokens',
275 )
233 parser.add_argument( 276 parser.add_argument(
234 '-r', '--range', 277 '-r',
278 '--range',
235 metavar='<range>', 279 metavar='<range>',
236 type=int, 280 type=int,
237 dest='range', 281 dest='range',
238 default=1, 282 default=1,
239 help='Amount of consecutive OTP hex/tokens to generate. default: 1') 283 help='Amount of consecutive OTP hex/tokens to generate. default: 1',
284 )
240 parser.add_argument( 285 parser.add_argument(
241 '-s', '--seed', 286 '-s',
287 '--seed',
242 metavar='[seed]', 288 metavar='[seed]',
243 type=str, 289 type=str,
244 dest='seed', 290 dest='seed',
245 default='', 291 default='',
246 help=('The seed to use (1 to 16 alphanumeric characters) ' 292 help=(
247 '(default & recommended: random seed)')) 293 'The seed to use (1 to 16 alphanumeric characters) '
294 '(default & recommended: random seed)'
295 ),
296 )
248 parser.add_argument( 297 parser.add_argument(
249 '-v', '--version', 298 '-v',
299 '--version',
250 action='version', 300 action='version',
251 version=f'%(prog)s {otp2289.__version__}', 301 version=f'%(prog)s {otp2289.__version__}',
252 help='display program-version and exit') 302 help='display program-version and exit',
303 )
253 args = parser.parse_args(args) 304 args = parser.parse_args(args)
254 # handle the password before everything else 305 # handle the password before everything else
255 if not args.password: 306 if not args.password:
@@ -257,8 +308,8 @@ def main(args=None):
257 while True: 308 while True:
258 args.password = getpass.getpass() 309 args.password = getpass.getpass()
259 if ( 310 if (
260 not args.initiate_new_sequence or 311 not args.initiate_new_sequence
261 args.password == getpass.getpass('Repeat password: ') 312 or args.password == getpass.getpass('Repeat password: ')
262 ): 313 ):
263 break 314 break
264 eprint('The passwords do not match') 315 eprint('The passwords do not match')
@@ -267,7 +318,7 @@ def main(args=None):
267 sys.exit(errno.EACCES) 318 sys.exit(errno.EACCES)
268 elif os.path.isfile(args.password): 319 elif os.path.isfile(args.password):
269 try: 320 try:
270 with open(args.password, 'r') as fp: 321 with io.open(args.password, 'r', encoding='utf-8') as fp:
271 args.password = fp.readline().strip() 322 args.password = fp.readline().strip()
272 except Exception as exp: 323 except Exception as exp:
273 eprint(f'Unable to open password file: {exp}') 324 eprint(f'Unable to open password file: {exp}')