summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2022-03-29 20:44:03 +0200
committerSimeon Simeonov2022-03-29 20:44:03 +0200
commitf19fde48c6b3f65c164c931286e72a0d254c1fb3 (patch)
tree5b6c1bf6cef51c852c49abff3a0e4626e8a33146
parent12c5e38691b24d12a8fc9605e3b93fac86d461ae (diff)
Add the -P param and improve tests
-rw-r--r--CHANGELOG.md22
-rw-r--r--COPYING2
-rw-r--r--LICENSE2
-rw-r--r--otp2289/__init__.py2
-rw-r--r--otp2289/__main__.py105
-rw-r--r--tests/test_generator.py152
-rw-r--r--tests/test_main.py207
-rw-r--r--tests/test_server.py77
-rw-r--r--tests/test_static.py57
9 files changed, 450 insertions, 176 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4288634
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
1# Changelog
2
3## [1.1.0](https://github.com/blackm0re/pyotp2289/tree/1.1.0) (2022-03-29)
4
5[Full Changelog](https://github.com/blackm0re/pyotp2289/compare/1.0.0...1.1.0)
6
7**Changes:**
8
9- (CLI) a password can now be set using the *OTP2289_PASSWORD* env. variable
10
11- (CLI) a new parameter *-P* / *--force-password-prompt* can be used in order to force password prompt
12
13- improved tests for CLI
14
15- use *setuptools* instead of the deprecated *distutils*
16
17
18# [1.0.0](https://github.com/blackm0re/pyotp2289/tree/1.0.0) (2020-04-07)
19
20**Changes:**
21
22- Initial release
diff --git a/COPYING b/COPYING
index 968627b..e7fb5f0 100644
--- a/COPYING
+++ b/COPYING
@@ -1,6 +1,6 @@
1SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1SPDX-License-Identifier: BSD-2-Clause-FreeBSD
2 2
3Copyright (c) 2020, Simeon Simeonov 3Copyright (c) 2020-2022, Simeon Simeonov
4All rights reserved. 4All rights reserved.
5 5
6Redistribution and use in source and binary forms, with or without 6Redistribution and use in source and binary forms, with or without
diff --git a/LICENSE b/LICENSE
index 968627b..e7fb5f0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
1SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1SPDX-License-Identifier: BSD-2-Clause-FreeBSD
2 2
3Copyright (c) 2020, Simeon Simeonov 3Copyright (c) 2020-2022, Simeon Simeonov
4All rights reserved. 4All rights reserved.
5 5
6Redistribution and use in source and binary forms, with or without 6Redistribution and use in source and binary forms, with or without
diff --git a/otp2289/__init__.py b/otp2289/__init__.py
index 608595b..59694a7 100644
--- a/otp2289/__init__.py
+++ b/otp2289/__init__.py
@@ -40,7 +40,7 @@ from .server import (
40) 40)
41 41
42__author__ = 'Simeon Simeonov' 42__author__ = 'Simeon Simeonov'
43__version__ = '1.1.0-beta1' 43__version__ = '1.1.0'
44__license__ = 'BSD 2-Clause' 44__license__ = 'BSD 2-Clause'
45 45
46 46
diff --git a/otp2289/__main__.py b/otp2289/__main__.py
index 471ae24..78e60e8 100644
--- a/otp2289/__main__.py
+++ b/otp2289/__main__.py
@@ -135,6 +135,48 @@ def generate_otp_range(args: argparse.Namespace) -> str:
135 ) 135 )
136 136
137 137
138def get_password(args: argparse.Namespace) -> str:
139 """
140 Extract the provided password using the defined argparse arguments
141
142 :param args: The arguments assigned from argparse
143 :type args: argparse.Namespace
144
145 :raises KeyboardInterrupt: If the password prompt is interrupted
146
147 :return: The extrated password string
148 :rtype: str
149 """
150 if args.force_password_prompt:
151 while True:
152 password = getpass.getpass()
153 if not args.initiate_new_sequence or password == getpass.getpass(
154 'Repeat password: '
155 ):
156 break
157 eprint('The passwords do not match')
158 return password
159
160 if not args.password:
161 password = os.environ.get('OTP2289_PASSWORD')
162 if password is not None:
163 return password
164 while True:
165 password = getpass.getpass()
166 if not args.initiate_new_sequence or password == getpass.getpass(
167 'Repeat password: '
168 ):
169 break
170 eprint('The passwords do not match')
171 return password
172
173 if os.path.isfile(args.password):
174 with io.open(args.password, 'r', encoding='utf-8') as fp:
175 return fp.readline().strip()
176
177 return args.password
178
179
138def get_rnd_seed() -> str: 180def get_rnd_seed() -> str:
139 """ 181 """
140 Returns a random seed in the format: 182 Returns a random seed in the format:
@@ -192,6 +234,7 @@ def main(args=None):
192 description='The following options are available', 234 description='The following options are available',
193 ) 235 )
194 group = parser.add_mutually_exclusive_group(required=True) 236 group = parser.add_mutually_exclusive_group(required=True)
237 password_group = parser.add_mutually_exclusive_group()
195 group.add_argument( 238 group.add_argument(
196 '--generate-otp-range', 239 '--generate-otp-range',
197 action='store_true', 240 action='store_true',
@@ -217,6 +260,28 @@ def main(args=None):
217 'and always outputs hex (ignores -f).' 260 'and always outputs hex (ignores -f).'
218 ), 261 ),
219 ) 262 )
263 password_group.add_argument(
264 '-P',
265 '--force-password-prompt',
266 dest='force_password_prompt',
267 action='store_true',
268 help=(
269 'Force password prompt even if the env. variable '
270 '"OTP2289_PASSWORD" is set'
271 ),
272 )
273 password_group.add_argument(
274 '-p',
275 '--password',
276 metavar='<PASSWORD[FILE]>',
277 type=str,
278 dest='password',
279 default='',
280 help=(
281 'The password or path to password file '
282 '(default & recommended: prompt for passwd)'
283 ),
284 )
220 parser.add_argument( 285 parser.add_argument(
221 '-a', 286 '-a',
222 '--hash-algorithm', 287 '--hash-algorithm',
@@ -254,18 +319,6 @@ def main(args=None):
254 help='The step. Default for initiating a new sequence is: 500', 319 help='The step. Default for initiating a new sequence is: 500',
255 ) 320 )
256 parser.add_argument( 321 parser.add_argument(
257 '-p',
258 '--password',
259 metavar='<PASSWORD[FILE]>',
260 type=str,
261 dest='password',
262 default='',
263 help=(
264 'The password or path to password file '
265 '(default & recommended: prompt for passwd)'
266 ),
267 )
268 parser.add_argument(
269 '-q', 322 '-q',
270 '--quiet', 323 '--quiet',
271 action='store_true', 324 action='store_true',
@@ -303,26 +356,14 @@ def main(args=None):
303 ) 356 )
304 args = parser.parse_args(args) 357 args = parser.parse_args(args)
305 # handle the password before everything else 358 # handle the password before everything else
306 if not args.password: 359 try:
307 try: 360 args.password = get_password(args)
308 while True: 361 except KeyboardInterrupt:
309 args.password = getpass.getpass() 362 eprint(os.linesep + 'Prompt terminated')
310 if ( 363 sys.exit(errno.EACCES)
311 not args.initiate_new_sequence 364 except Exception as exp:
312 or args.password == getpass.getpass('Repeat password: ') 365 eprint(f'Unable to fetch password: {exp}')
313 ): 366 sys.exit(1)
314 break
315 eprint('The passwords do not match')
316 except KeyboardInterrupt:
317 eprint(os.linesep + 'Prompt terminated')
318 sys.exit(errno.EACCES)
319 elif os.path.isfile(args.password):
320 try:
321 with io.open(args.password, 'r', encoding='utf-8') as fp:
322 args.password = fp.readline().strip()
323 except Exception as exp:
324 eprint(f'Unable to open password file: {exp}')
325 sys.exit(1)
326 try: 367 try:
327 if args.initiate_new_sequence: 368 if args.initiate_new_sequence:
328 print(initiate_new_sequence(args)) 369 print(initiate_new_sequence(args))
diff --git a/tests/test_generator.py b/tests/test_generator.py
index 50274e2..aa795a3 100644
--- a/tests/test_generator.py
+++ b/tests/test_generator.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
@@ -31,9 +31,11 @@ import otp2289
31 31
32def test_caller_exceptions(): 32def test_caller_exceptions():
33 """Tests the exceptions when calling an initialized object""" 33 """Tests the exceptions when calling an initialized object"""
34 gen = otp2289.OTPGenerator('This is a test.'.encode(), 34 gen = otp2289.OTPGenerator(
35 'TeSt', 35 'This is a test.'.encode(),
36 otp2289.OTP_ALGO_MD5) 36 'TeSt',
37 otp2289.OTP_ALGO_MD5,
38 )
37 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 39 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
38 gen.generate_otp_words('3') 40 gen.generate_otp_words('3')
39 assert exc_info.type is otp2289.OTPGeneratorException 41 assert exc_info.type is otp2289.OTPGeneratorException
@@ -62,50 +64,72 @@ def test_constructor_exceptions():
62 """ 64 """
63 # test the otp2289.OTPGenerator __init__ and validators 65 # test the otp2289.OTPGenerator __init__ and validators
64 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 66 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
65 otp2289.OTPGenerator('This is a test.'.encode(), 67 otp2289.OTPGenerator(
66 'TeStø'.encode(), 68 'This is a test.'.encode(),
67 otp2289.OTP_ALGO_MD5) 69 'TeStø'.encode(),
70 otp2289.OTP_ALGO_MD5,
71 )
68 assert exc_info.type is otp2289.OTPGeneratorException 72 assert exc_info.type is otp2289.OTPGeneratorException
69 assert exc_info.value.args[0] == 'Seed must be a string' 73 assert exc_info.value.args[0] == 'Seed must be a string'
70 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 74 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
71 otp2289.OTPGenerator('This is a test.'.encode(), 75 otp2289.OTPGenerator(
72 'TeStøtEsTteSTteStTest', 76 'This is a test.'.encode(),
73 otp2289.OTP_ALGO_SHA1) 77 'TeStøtEsTteSTteStTest',
78 otp2289.OTP_ALGO_SHA1,
79 )
74 assert exc_info.type is otp2289.OTPGeneratorException 80 assert exc_info.type is otp2289.OTPGeneratorException
75 assert exc_info.value.args[0] == ('The seed MUST be of 1 to 16 ' 81 assert exc_info.value.args[0] == (
76 'characters in length') 82 'The seed MUST be of 1 to 16 characters in length'
83 )
77 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 84 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
78 otp2289.OTPGenerator('This is a test.'.encode(), 85 otp2289.OTPGenerator(
79 'TeStø', 86 'This is a test.'.encode(),
80 otp2289.OTP_ALGO_SHA1) 87 'TeStø',
88 otp2289.OTP_ALGO_SHA1,
89 )
81 assert exc_info.type is otp2289.OTPGeneratorException 90 assert exc_info.type is otp2289.OTPGeneratorException
82 assert exc_info.value.args[0] == ('The seed MUST consist of purely ' 91 assert exc_info.value.args[0] == (
83 'alphanumeric characters') 92 'The seed MUST consist of purely alphanumeric characters'
93 )
84 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 94 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
85 otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', 9) 95 otp2289.OTPGenerator(
96 'This is a test.'.encode(),
97 'TeSt',
98 9,
99 )
86 assert exc_info.type is otp2289.OTPGeneratorException 100 assert exc_info.type is otp2289.OTPGeneratorException
87 assert exc_info.value.args[0] == ( 101 assert exc_info.value.args[0] == (
88 'hash_algo is not among the known algorithms') 102 'hash_algo is not among the known algorithms'
103 )
89 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 104 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
90 otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', b'md5') 105 otp2289.OTPGenerator(
106 'This is a test.'.encode(),
107 'TeSt',
108 b'md5',
109 )
91 assert exc_info.type is otp2289.OTPGeneratorException 110 assert exc_info.type is otp2289.OTPGeneratorException
92 assert exc_info.value.args[0] == 'hash_algo must be an int or a str' 111 assert exc_info.value.args[0] == 'hash_algo must be an int or a str'
93 # test the package structure as well 112 # test the package structure as well
94 with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info: 113 with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info:
95 otp2289.generator.OTPGenerator('This is a test.'.encode(), 114 otp2289.generator.OTPGenerator(
96 'TeSt', 115 'This is a test.'.encode(),
97 'foo') 116 'TeSt',
117 'foo',
118 )
98 assert exc_info.type is otp2289.generator.OTPGeneratorException 119 assert exc_info.type is otp2289.generator.OTPGeneratorException
99 assert exc_info.value.args[0] == ('foo is not supported by this version ' 120 assert exc_info.value.args[0] == (
100 'of the hashlib module') 121 'foo is not supported by this version of the hashlib module'
122 )
101 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 123 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
102 otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5) 124 otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5)
103 assert exc_info.type is otp2289.OTPGeneratorException 125 assert exc_info.type is otp2289.OTPGeneratorException
104 assert exc_info.value.args[0] == 'Password must be a byte-string' 126 assert exc_info.value.args[0] == 'Password must be a byte-string'
105 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 127 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
106 otp2289.OTPGenerator('1234567'.encode(), 128 otp2289.OTPGenerator(
107 'TeSt', 129 '1234567'.encode(),
108 otp2289.OTP_ALGO_MD5) 130 'TeSt',
131 otp2289.OTP_ALGO_MD5,
132 )
109 assert exc_info.type is otp2289.OTPGeneratorException 133 assert exc_info.type is otp2289.OTPGeneratorException
110 assert exc_info.value.args[0] == 'Password must be longer than 10 bytes' 134 assert exc_info.value.args[0] == 'Password must be longer than 10 bytes'
111 135
@@ -118,9 +142,11 @@ def test_md5():
118 """ 142 """
119 # We could run this in a loop, but I guess "Readability counts." 143 # We could run this in a loop, but I guess "Readability counts."
120 # pass='This is a test.', seed='TeSt' 144 # pass='This is a test.', seed='TeSt'
121 gen = otp2289.OTPGenerator('This is a test.'.encode(), 145 gen = otp2289.OTPGenerator(
122 'TeSt', 146 'This is a test.'.encode(),
123 otp2289.OTP_ALGO_MD5) 147 'TeSt',
148 otp2289.OTP_ALGO_MD5,
149 )
124 res_words = gen.generate_otp_words(0) 150 res_words = gen.generate_otp_words(0)
125 res_hex = gen.generate_otp_hexdigest(0) 151 res_hex = gen.generate_otp_hexdigest(0)
126 assert isinstance(res_words, str) 152 assert isinstance(res_words, str)
@@ -131,16 +157,20 @@ def test_md5():
131 assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f' 157 assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f'
132 assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS' 158 assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS'
133 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 1 TeSt') == ( 159 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 1 TeSt') == (
134 '0x7965e05436f5029f') 160 '0x7965e05436f5029f'
161 )
135 assert gen.generate_otp_words_from_challenge('otp-md5 1 TeSt') == ( 162 assert gen.generate_otp_words_from_challenge('otp-md5 1 TeSt') == (
136 'EASE OIL FUM CURE AWRY AVIS') 163 'EASE OIL FUM CURE AWRY AVIS'
164 )
137 # step 99 165 # step 99
138 assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880' 166 assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880'
139 assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY' 167 assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY'
140 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 99 TeSt') == ( 168 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 99 TeSt') == (
141 '0x50fe1962c4965880') 169 '0x50fe1962c4965880'
170 )
142 assert gen.generate_otp_words_from_challenge('otp-md5 99 TeSt') == ( 171 assert gen.generate_otp_words_from_challenge('otp-md5 99 TeSt') == (
143 'BAIL TUFT BITS GANG CHEF THY') 172 'BAIL TUFT BITS GANG CHEF THY'
173 )
144 # iterator test 174 # iterator test
145 hexdigests = list(gen.hexdigest_range(105)) # testing the range itself 175 hexdigests = list(gen.hexdigest_range(105)) # testing the range itself
146 words = list(gen.words_range(99)) 176 words = list(gen.words_range(99))
@@ -153,9 +183,11 @@ def test_md5():
153 assert words[1] == 'EASE OIL FUM CURE AWRY AVIS' 183 assert words[1] == 'EASE OIL FUM CURE AWRY AVIS'
154 assert words[99] == 'BAIL TUFT BITS GANG CHEF THY' 184 assert words[99] == 'BAIL TUFT BITS GANG CHEF THY'
155 # pass='AbCdEfGhIjK', seed='alpha1' 185 # pass='AbCdEfGhIjK', seed='alpha1'
156 gen = otp2289.OTPGenerator('AbCdEfGhIjK'.encode(), 186 gen = otp2289.OTPGenerator(
157 'alpha1', 187 'AbCdEfGhIjK'.encode(),
158 otp2289.OTP_ALGO_MD5) 188 'alpha1',
189 otp2289.OTP_ALGO_MD5,
190 )
159 assert gen.generate_otp_hexdigest(0) == '0x87066dd9644bf206' 191 assert gen.generate_otp_hexdigest(0) == '0x87066dd9644bf206'
160 assert gen.generate_otp_words(0) == 'FULL PEW DOWN ONCE MORT ARC' 192 assert gen.generate_otp_words(0) == 'FULL PEW DOWN ONCE MORT ARC'
161 assert gen.generate_otp_hexdigest(1) == '0x7cd34c1040add14b' 193 assert gen.generate_otp_hexdigest(1) == '0x7cd34c1040add14b'
@@ -163,9 +195,11 @@ def test_md5():
163 assert gen.generate_otp_hexdigest(99) == '0x5aa37a81f212146c' 195 assert gen.generate_otp_hexdigest(99) == '0x5aa37a81f212146c'
164 assert gen.generate_otp_words(99) == 'BODE HOP JAKE STOW JUT RAP' 196 assert gen.generate_otp_words(99) == 'BODE HOP JAKE STOW JUT RAP'
165 # pass="OTP's are good", seed='correct' 197 # pass="OTP's are good", seed='correct'
166 gen = otp2289.OTPGenerator("OTP's are good".encode(), 198 gen = otp2289.OTPGenerator(
167 'correct', 199 "OTP's are good".encode(),
168 otp2289.OTP_ALGO_MD5) 200 'correct',
201 otp2289.OTP_ALGO_MD5,
202 )
169 assert gen.generate_otp_hexdigest(0) == '0xf205753943de4cf9' 203 assert gen.generate_otp_hexdigest(0) == '0xf205753943de4cf9'
170 assert gen.generate_otp_words(0) == 'ULAN NEW ARMY FUSE SUIT EYED' 204 assert gen.generate_otp_words(0) == 'ULAN NEW ARMY FUSE SUIT EYED'
171 assert gen.generate_otp_hexdigest(1) == '0xddcdac956f234937' 205 assert gen.generate_otp_hexdigest(1) == '0xddcdac956f234937'
@@ -181,9 +215,11 @@ def test_sha1():
181 Those are the tests from 'RFC-2289 Appendix C - OTP Verification Examples' 215 Those are the tests from 'RFC-2289 Appendix C - OTP Verification Examples'
182 """ 216 """
183 # pass='This is a test.', seed='TeSt' 217 # pass='This is a test.', seed='TeSt'
184 gen = otp2289.OTPGenerator('This is a test.'.encode(), 218 gen = otp2289.OTPGenerator(
185 'TeSt', 219 'This is a test.'.encode(),
186 otp2289.OTP_ALGO_SHA1) 220 'TeSt',
221 otp2289.OTP_ALGO_SHA1,
222 )
187 # step=0 223 # step=0
188 res_hex = gen.generate_otp_hexdigest(0) 224 res_hex = gen.generate_otp_hexdigest(0)
189 res_words = gen.generate_otp_words(0) 225 res_words = gen.generate_otp_words(0)
@@ -194,15 +230,19 @@ def test_sha1():
194 assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b' 230 assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b'
195 assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT' 231 assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT'
196 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 1 TeSt') == ( 232 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 1 TeSt') == (
197 '0x63d936639734385b') 233 '0x63d936639734385b'
234 )
198 assert gen.generate_otp_words_from_challenge('otp-sha1 1 TeSt') == ( 235 assert gen.generate_otp_words_from_challenge('otp-sha1 1 TeSt') == (
199 'CART OTTO HIVE ODE VAT NUT') 236 'CART OTTO HIVE ODE VAT NUT'
237 )
200 assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9' 238 assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9'
201 assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED' 239 assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED'
202 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 99 TeSt') == ( 240 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 99 TeSt') == (
203 '0x87fec7768b73ccf9') 241 '0x87fec7768b73ccf9'
242 )
204 assert gen.generate_otp_words_from_challenge('otp-sha1 99 TeSt') == ( 243 assert gen.generate_otp_words_from_challenge('otp-sha1 99 TeSt') == (
205 'GAFF WAIT SKID GIG SKY EYED') 244 'GAFF WAIT SKID GIG SKY EYED'
245 )
206 # iterator test 246 # iterator test
207 hexdigests = list(gen.hexdigest_range(105)) 247 hexdigests = list(gen.hexdigest_range(105))
208 words = list(gen.words_range(99)) 248 words = list(gen.words_range(99))
@@ -215,9 +255,11 @@ def test_sha1():
215 assert words[1] == 'CART OTTO HIVE ODE VAT NUT' 255 assert words[1] == 'CART OTTO HIVE ODE VAT NUT'
216 assert words[99] == 'GAFF WAIT SKID GIG SKY EYED' 256 assert words[99] == 'GAFF WAIT SKID GIG SKY EYED'
217 # pass='AbCdEfGhIjK', seed='alpha1' 257 # pass='AbCdEfGhIjK', seed='alpha1'
218 gen = otp2289.OTPGenerator('AbCdEfGhIjK'.encode(), 258 gen = otp2289.OTPGenerator(
219 'alpha1', 259 'AbCdEfGhIjK'.encode(),
220 otp2289.OTP_ALGO_SHA1) 260 'alpha1',
261 otp2289.OTP_ALGO_SHA1,
262 )
221 assert gen.generate_otp_hexdigest(0) == '0xad85f658ebe383c9' 263 assert gen.generate_otp_hexdigest(0) == '0xad85f658ebe383c9'
222 assert gen.generate_otp_words(0) == 'LEST OR HEEL SCOT ROB SUIT' 264 assert gen.generate_otp_words(0) == 'LEST OR HEEL SCOT ROB SUIT'
223 assert gen.generate_otp_hexdigest(1) == '0xd07ce229b5cf119b' 265 assert gen.generate_otp_hexdigest(1) == '0xd07ce229b5cf119b'
@@ -225,9 +267,11 @@ def test_sha1():
225 assert gen.generate_otp_hexdigest(99) == '0x27bc71035aaf3dc6' 267 assert gen.generate_otp_hexdigest(99) == '0x27bc71035aaf3dc6'
226 assert gen.generate_otp_words(99) == 'MAY STAR TIN LYON VEDA STAN' 268 assert gen.generate_otp_words(99) == 'MAY STAR TIN LYON VEDA STAN'
227 # pass="OTP's are good", seed='correct' 269 # pass="OTP's are good", seed='correct'
228 gen = otp2289.OTPGenerator("OTP's are good".encode(), 270 gen = otp2289.OTPGenerator(
229 'correct', 271 "OTP's are good".encode(),
230 otp2289.OTP_ALGO_SHA1) 272 'correct',
273 otp2289.OTP_ALGO_SHA1,
274 )
231 assert gen.generate_otp_hexdigest(0) == '0xd51f3e99bf8e6f0b' 275 assert gen.generate_otp_hexdigest(0) == '0xd51f3e99bf8e6f0b'
232 assert gen.generate_otp_words(0) == 'RUST WELT KICK FELL TAIL FRAU' 276 assert gen.generate_otp_words(0) == 'RUST WELT KICK FELL TAIL FRAU'
233 assert gen.generate_otp_hexdigest(1) == '0x82aeb52d943774e4' 277 assert gen.generate_otp_hexdigest(1) == '0x82aeb52d943774e4'
diff --git a/tests/test_main.py b/tests/test_main.py
index b48f625..a331274 100644
--- a/tests/test_main.py
+++ b/tests/test_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
@@ -25,6 +25,7 @@
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""Tests for otp2289.__main__""" 26"""Tests for otp2289.__main__"""
27import os 27import os
28import unittest.mock
28 29
29import pytest 30import pytest
30 31
@@ -33,28 +34,34 @@ from otp2289.__main__ import main
33 34
34def test_main_generate_otp_response(capsys): 35def test_main_generate_otp_response(capsys):
35 """tests main""" 36 """tests main"""
36 args = ['--generate-otp-response', 37 args = [
37 '-a', 38 '--generate-otp-response',
38 'sha1', 39 '-a',
39 '-i', 40 'sha1',
40 '99', 41 '-i',
41 '-s', 42 '99',
42 'TesT', 43 '-s',
43 '-p', 44 'TesT',
44 'This is a test.'] 45 '-p',
46 'This is a test.',
47 ]
45 with pytest.raises(SystemExit) as exit_info: 48 with pytest.raises(SystemExit) as exit_info:
46 main(args) 49 main(args)
47 captured = capsys.readouterr() 50 captured = capsys.readouterr()
48 assert captured.out == (f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' 51 assert captured.out == (
49 f'0x87fec7768b73ccf9{os.linesep}') 52 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
53 f'0x87fec7768b73ccf9{os.linesep}'
54 )
50 assert exit_info.type == SystemExit 55 assert exit_info.type == SystemExit
51 assert exit_info.value.code == 0 56 assert exit_info.value.code == 0
52 args.extend(['-f', 'token']) 57 args.extend(['-f', 'token'])
53 with pytest.raises(SystemExit) as exit_info: 58 with pytest.raises(SystemExit) as exit_info:
54 main(args) 59 main(args)
55 captured = capsys.readouterr() 60 captured = capsys.readouterr()
56 assert captured.out == (f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}' 61 assert captured.out == (
57 f'GAFF WAIT SKID GIG SKY EYED{os.linesep}') 62 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
63 f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
64 )
58 assert exit_info.type == SystemExit 65 assert exit_info.type == SystemExit
59 assert exit_info.value.code == 0 66 assert exit_info.value.code == 0
60 args.append('-q') 67 args.append('-q')
@@ -66,61 +73,187 @@ def test_main_generate_otp_response(capsys):
66 assert exit_info.value.code == 0 73 assert exit_info.value.code == 0
67 74
68 75
76def test_main_generate_otp_response_env_passwd(capsys):
77 """tests main by fetching password from the env. var. 'OTP2289_PASSWORD'"""
78 args = [
79 '--generate-otp-response',
80 '-a',
81 'sha1',
82 '-i',
83 '99',
84 '-s',
85 'TesT',
86 ]
87 with unittest.mock.patch.dict(
88 os.environ, {'OTP2289_PASSWORD': 'This is a test.'}
89 ):
90 with pytest.raises(SystemExit) as exit_info:
91 main(args)
92 captured = capsys.readouterr()
93 assert captured.out == (
94 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
95 f'0x87fec7768b73ccf9{os.linesep}'
96 )
97 assert exit_info.type == SystemExit
98 assert exit_info.value.code == 0
99 args.extend(['-f', 'token'])
100 with pytest.raises(SystemExit) as exit_info:
101 main(args)
102 captured = capsys.readouterr()
103 assert captured.out == (
104 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
105 f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
106 )
107 assert exit_info.type == SystemExit
108 assert exit_info.value.code == 0
109 args.append('-q')
110 with pytest.raises(SystemExit) as exit_info:
111 main(args)
112 captured = capsys.readouterr()
113 assert captured.out == f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
114 assert exit_info.type == SystemExit
115 assert exit_info.value.code == 0
116
117
69def test_main_generate_otp_range(capsys): 118def test_main_generate_otp_range(capsys):
70 """tests main""" 119 """tests main"""
71 args = ['--generate-otp-range', 120 args = [
121 '--generate-otp-range',
122 '-i',
123 '2',
124 '-s',
125 'TesT',
126 '-r',
127 '5',
128 '-p',
129 'This is a test.',
130 ]
131 with pytest.raises(SystemExit) as exit_info:
132 main(args)
133 captured = capsys.readouterr()
134 assert captured.out == (
135 f'Seed: TesT, Step: 2, Hash: md5, Range: 3'
136 f'{os.linesep}'
137 f'2: 0x4049f8b161669b7b{os.linesep}'
138 f'1: 0x7965e05436f5029f{os.linesep}'
139 f'0: 0x9e876134d90499dd{os.linesep}'
140 )
141 assert exit_info.type == SystemExit
142 assert exit_info.value.code == 0
143 args.append('-q')
144 with pytest.raises(SystemExit) as exit_info:
145 main(args)
146 captured = capsys.readouterr()
147 assert captured.out == (
148 f'2: 0x4049f8b161669b7b{os.linesep}'
149 f'1: 0x7965e05436f5029f{os.linesep}'
150 f'0: 0x9e876134d90499dd{os.linesep}'
151 )
152 assert exit_info.type == SystemExit
153 assert exit_info.value.code == 0
154 args.extend(['-f', 'token'])
155 with pytest.raises(SystemExit) as exit_info:
156 main(args)
157 captured = capsys.readouterr()
158 assert captured.out == (
159 f'2: THY AVON NO NECK COKE MOLL{os.linesep}'
160 f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}'
161 f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}'
162 )
163 assert exit_info.type == SystemExit
164 assert exit_info.value.code == 0
165
166
167@pytest.mark.parametrize(
168 'args',
169 [
170 [
171 '--generate-otp-range',
172 '-i',
173 '2',
174 '-s',
175 'TesT',
176 '-r',
177 '5',
178 ],
179 [
180 '--generate-otp-range',
72 '-i', 181 '-i',
73 '2', 182 '2',
74 '-s', 183 '-s',
75 'TesT', 184 'TesT',
76 '-r', 185 '-r',
77 '5', 186 '5',
78 '-p', 187 '-P',
79 'This is a test.'] 188 ],
189 ],
190)
191@unittest.mock.patch('getpass.getpass', lambda *args: 'This is a test.')
192def test_main_generate_otp_range_passwd_prompt(capsys, args):
193 """tests main by prompting for password (with or without -P)"""
194 args = [
195 '--generate-otp-range',
196 '-i',
197 '2',
198 '-s',
199 'TesT',
200 '-r',
201 '5',
202 ]
80 with pytest.raises(SystemExit) as exit_info: 203 with pytest.raises(SystemExit) as exit_info:
81 main(args) 204 main(args)
82 captured = capsys.readouterr() 205 captured = capsys.readouterr()
83 assert captured.out == (f'Seed: TesT, Step: 2, Hash: md5, Range: 3' 206 assert captured.out == (
84 f'{os.linesep}' 207 f'Seed: TesT, Step: 2, Hash: md5, Range: 3'
85 f'2: 0x4049f8b161669b7b{os.linesep}' 208 f'{os.linesep}'
86 f'1: 0x7965e05436f5029f{os.linesep}' 209 f'2: 0x4049f8b161669b7b{os.linesep}'
87 f'0: 0x9e876134d90499dd{os.linesep}') 210 f'1: 0x7965e05436f5029f{os.linesep}'
211 f'0: 0x9e876134d90499dd{os.linesep}'
212 )
88 assert exit_info.type == SystemExit 213 assert exit_info.type == SystemExit
89 assert exit_info.value.code == 0 214 assert exit_info.value.code == 0
90 args.append('-q') 215 args.append('-q')
91 with pytest.raises(SystemExit) as exit_info: 216 with pytest.raises(SystemExit) as exit_info:
92 main(args) 217 main(args)
93 captured = capsys.readouterr() 218 captured = capsys.readouterr()
94 assert captured.out == (f'2: 0x4049f8b161669b7b{os.linesep}' 219 assert captured.out == (
95 f'1: 0x7965e05436f5029f{os.linesep}' 220 f'2: 0x4049f8b161669b7b{os.linesep}'
96 f'0: 0x9e876134d90499dd{os.linesep}') 221 f'1: 0x7965e05436f5029f{os.linesep}'
222 f'0: 0x9e876134d90499dd{os.linesep}'
223 )
97 assert exit_info.type == SystemExit 224 assert exit_info.type == SystemExit
98 assert exit_info.value.code == 0 225 assert exit_info.value.code == 0
99 args.extend(['-f', 'token']) 226 args.extend(['-f', 'token'])
100 with pytest.raises(SystemExit) as exit_info: 227 with pytest.raises(SystemExit) as exit_info:
101 main(args) 228 main(args)
102 captured = capsys.readouterr() 229 captured = capsys.readouterr()
103 assert captured.out == (f'2: THY AVON NO NECK COKE MOLL{os.linesep}' 230 assert captured.out == (
104 f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}' 231 f'2: THY AVON NO NECK COKE MOLL{os.linesep}'
105 f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}') 232 f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}'
233 f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}'
234 )
106 assert exit_info.type == SystemExit 235 assert exit_info.type == SystemExit
107 assert exit_info.value.code == 0 236 assert exit_info.value.code == 0
108 237
109 238
110def test_main_initiate(capsys): 239def test_main_initiate(capsys):
111 """tests main""" 240 """tests main"""
112 args = ['--initiate-new-sequence', 241 args = [
113 '-i', 242 '--initiate-new-sequence',
114 '500', 243 '-i',
115 '-s', 244 '500',
116 'TesT', 245 '-s',
117 '-p', 246 'TesT',
118 'This is a test.'] 247 '-p',
248 'This is a test.',
249 ]
119 with pytest.raises(SystemExit) as exit_info: 250 with pytest.raises(SystemExit) as exit_info:
120 main(args) 251 main(args)
121 captured = capsys.readouterr() 252 captured = capsys.readouterr()
122 assert captured.out == (f'Seed: TesT, Step: 500, Hash: md5{os.linesep}' 253 assert captured.out == (
123 f'0x2b8d82b6ac14346c{os.linesep}') 254 f'Seed: TesT, Step: 500, Hash: md5{os.linesep}'
255 f'0x2b8d82b6ac14346c{os.linesep}'
256 )
124 assert exit_info.type == SystemExit 257 assert exit_info.type == SystemExit
125 assert exit_info.value.code == 0 258 assert exit_info.value.code == 0
126 args.append('-q') 259 args.append('-q')
diff --git a/tests/test_server.py b/tests/test_server.py
index 8d24cb8..3d3b77d 100644
--- a/tests/test_server.py
+++ b/tests/test_server.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
@@ -33,41 +33,50 @@ import otp2289
33 33
34def test_state_caller_exceptions(): 34def test_state_caller_exceptions():
35 """Tests the exceptions when calling the OTPState objects""" 35 """Tests the exceptions when calling the OTPState objects"""
36 state = otp2289.OTPState('0x7965e05436f5029f', 36 state = otp2289.OTPState(
37 1, 37 '0x7965e05436f5029f',
38 'TeSt', 38 1,
39 otp2289.OTP_ALGO_MD5) 39 'TeSt',
40 otp2289.OTP_ALGO_MD5,
41 )
40 with pytest.raises(otp2289.OTPInvalidResponse) as exc_info: 42 with pytest.raises(otp2289.OTPInvalidResponse) as exc_info:
41 state.response_validates('bla') 43 state.response_validates('bla')
42 assert exc_info.type is otp2289.OTPInvalidResponse 44 assert exc_info.type is otp2289.OTPInvalidResponse
43 assert exc_info.value.args[0] == ( 45 assert exc_info.value.args[0] == (
44 'The response is neither a valid token or hex') 46 'The response is neither a valid token or hex'
47 )
45 48
46 49
47def test_state_constructor_exceptions(): 50def test_state_constructor_exceptions():
48 """Tests the exceptions when initializing new OTPState objects""" 51 """Tests the exceptions when initializing new OTPState objects"""
49 with pytest.raises(otp2289.OTPStateException) as exc_info: 52 with pytest.raises(otp2289.OTPStateException) as exc_info:
50 otp2289.OTPState('0x7965e05436f5029t', 53 otp2289.OTPState(
51 1, 54 '0x7965e05436f5029t',
52 'TeStø'.encode(), 55 1,
53 otp2289.OTP_ALGO_MD5) 56 'TeStø'.encode(),
57 otp2289.OTP_ALGO_MD5,
58 )
54 assert exc_info.type is otp2289.OTPStateException 59 assert exc_info.type is otp2289.OTPStateException
55 assert exc_info.value.args[0] == 'Seed must be a string' 60 assert exc_info.value.args[0] == 'Seed must be a string'
56 with pytest.raises(otp2289.OTPStateException) as exc_info: 61 with pytest.raises(otp2289.OTPStateException) as exc_info:
57 otp2289.OTPState('0x7965e05436f5029t', 62 otp2289.OTPState(
58 '1', 63 '0x7965e05436f5029t',
59 'TeSt', 64 '1',
60 otp2289.OTP_ALGO_MD5) 65 'TeSt',
66 otp2289.OTP_ALGO_MD5,
67 )
61 assert exc_info.type is otp2289.OTPStateException 68 assert exc_info.type is otp2289.OTPStateException
62 assert exc_info.value.args[0] == 'Step value MUST be an int' 69 assert exc_info.value.args[0] == 'Step value MUST be an int'
63 70
64 71
65def test_state_validation_md5(): 72def test_state_validation_md5():
66 """Tests the OTPState validation functionality for MD5""" 73 """Tests the OTPState validation functionality for MD5"""
67 state = otp2289.OTPState('0x7965e05436f5029f', 74 state = otp2289.OTPState(
68 1, 75 '0x7965e05436f5029f',
69 'TeSt', 76 1,
70 otp2289.OTP_ALGO_MD5) 77 'TeSt',
78 otp2289.OTP_ALGO_MD5,
79 )
71 assert state.validated is False 80 assert state.validated is False
72 assert state.response_validates('0x9e876134d90499dd') is True 81 assert state.response_validates('0x9e876134d90499dd') is True
73 assert state.response_validates('INCH SEA ANNE LONG AHEM TOUR') is True 82 assert state.response_validates('INCH SEA ANNE LONG AHEM TOUR') is True
@@ -76,10 +85,12 @@ def test_state_validation_md5():
76 85
77def test_state_validation_sha1(): 86def test_state_validation_sha1():
78 """Tests the OTPState validation functionality for SHA1""" 87 """Tests the OTPState validation functionality for SHA1"""
79 state = otp2289.OTPState('0x63d936639734385b', 88 state = otp2289.OTPState(
80 1, 89 '0x63d936639734385b',
81 'TeSt', 90 1,
82 otp2289.OTP_ALGO_SHA1) 91 'TeSt',
92 otp2289.OTP_ALGO_SHA1,
93 )
83 assert state.validated is False 94 assert state.validated is False
84 assert state.response_validates('0xbb9e6ae1979d8ff4') is True 95 assert state.response_validates('0xbb9e6ae1979d8ff4') is True
85 assert state.response_validates('MILT VARY MAST OK SEES WENT') is True 96 assert state.response_validates('MILT VARY MAST OK SEES WENT') is True
@@ -88,14 +99,20 @@ def test_state_validation_sha1():
88 99
89def test_store(): 100def test_store():
90 """Tests the OTPStore functionality""" 101 """Tests the OTPStore functionality"""
91 store_data = {'sgs': {'ot_hex': '0x7965e05436f5029f', 102 store_data = {
92 'current_step': 1, 103 'sgs': {
93 'seed': 'TeSt', 104 'ot_hex': '0x7965e05436f5029f',
94 'hash_algo': 'md5'}, 105 'current_step': 1,
95 'blackmore': {'ot_hex': '0x63d936639734385b', 106 'seed': 'TeSt',
96 'current_step': 1, 107 'hash_algo': 'md5',
97 'seed': 'TeSt', 108 },
98 'hash_algo': 'sha1'}} 109 'blackmore': {
110 'ot_hex': '0x63d936639734385b',
111 'current_step': 1,
112 'seed': 'TeSt',
113 'hash_algo': 'sha1',
114 },
115 }
99 store = otp2289.OTPStore(store_data) 116 store = otp2289.OTPStore(store_data)
100 assert len(store) == 2 117 assert len(store) == 2
101 assert isinstance(json.dumps(store.to_dict()), str) # serializable? 118 assert isinstance(json.dumps(store.to_dict()), str) # serializable?
diff --git a/tests/test_static.py b/tests/test_static.py
index dc6e2d7..a942374 100644
--- a/tests/test_static.py
+++ b/tests/test_static.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
@@ -33,42 +33,59 @@ import otp2289
33def test_bytes_and_tokens(): 33def test_bytes_and_tokens():
34 """Tests the official hex and tokens defined in RFC2289""" 34 """Tests the official hex and tokens defined in RFC2289"""
35 assert binascii.unhexlify('9e876134d90499dd') == ( 35 assert binascii.unhexlify('9e876134d90499dd') == (
36 otp2289.OTPGenerator.tokens_to_bytes('INCH SEA ANNE LONG AHEM TOUR')) 36 otp2289.OTPGenerator.tokens_to_bytes('INCH SEA ANNE LONG AHEM TOUR')
37 )
37 assert binascii.unhexlify('7965e05436f5029f') == ( 38 assert binascii.unhexlify('7965e05436f5029f') == (
38 otp2289.OTPGenerator.tokens_to_bytes('EASE OIL FUM CURE AWRY AVIS')) 39 otp2289.OTPGenerator.tokens_to_bytes('EASE OIL FUM CURE AWRY AVIS')
40 )
39 assert binascii.unhexlify('50fe1962c4965880') == ( 41 assert binascii.unhexlify('50fe1962c4965880') == (
40 otp2289.OTPGenerator.tokens_to_bytes('BAIL TUFT BITS GANG CHEF THY')) 42 otp2289.OTPGenerator.tokens_to_bytes('BAIL TUFT BITS GANG CHEF THY')
43 )
41 assert binascii.unhexlify('87066dd9644bf206') == ( 44 assert binascii.unhexlify('87066dd9644bf206') == (
42 otp2289.OTPGenerator.tokens_to_bytes('FULL PEW DOWN ONCE MORT ARC')) 45 otp2289.OTPGenerator.tokens_to_bytes('FULL PEW DOWN ONCE MORT ARC')
46 )
43 assert binascii.unhexlify('7cd34c1040add14b') == ( 47 assert binascii.unhexlify('7cd34c1040add14b') == (
44 otp2289.OTPGenerator.tokens_to_bytes('FACT HOOF AT FIST SITE KENT')) 48 otp2289.OTPGenerator.tokens_to_bytes('FACT HOOF AT FIST SITE KENT')
49 )
45 assert binascii.unhexlify('5aa37a81f212146c') == ( 50 assert binascii.unhexlify('5aa37a81f212146c') == (
46 otp2289.OTPGenerator.tokens_to_bytes('BODE HOP JAKE STOW JUT RAP')) 51 otp2289.OTPGenerator.tokens_to_bytes('BODE HOP JAKE STOW JUT RAP')
52 )
47 assert binascii.unhexlify('f205753943de4cf9') == ( 53 assert binascii.unhexlify('f205753943de4cf9') == (
48 otp2289.OTPGenerator.tokens_to_bytes('ULAN NEW ARMY FUSE SUIT EYED')) 54 otp2289.OTPGenerator.tokens_to_bytes('ULAN NEW ARMY FUSE SUIT EYED')
55 )
49 assert binascii.unhexlify('ddcdac956f234937') == ( 56 assert binascii.unhexlify('ddcdac956f234937') == (
50 otp2289.OTPGenerator.tokens_to_bytes('SKIM CULT LOB SLAM POE HOWL')) 57 otp2289.OTPGenerator.tokens_to_bytes('SKIM CULT LOB SLAM POE HOWL')
58 )
51 assert binascii.unhexlify('b203e28fa525be47') == ( 59 assert binascii.unhexlify('b203e28fa525be47') == (
52 otp2289.OTPGenerator.tokens_to_bytes('LONG IVY JULY AJAR BOND LEE')) 60 otp2289.OTPGenerator.tokens_to_bytes('LONG IVY JULY AJAR BOND LEE')
53 61 )
54 assert binascii.unhexlify('bb9e6ae1979d8ff4') == ( 62 assert binascii.unhexlify('bb9e6ae1979d8ff4') == (
55 otp2289.OTPGenerator.tokens_to_bytes('MILT VARY MAST OK SEES WENT')) 63 otp2289.OTPGenerator.tokens_to_bytes('MILT VARY MAST OK SEES WENT')
64 )
56 assert binascii.unhexlify('63d936639734385b') == ( 65 assert binascii.unhexlify('63d936639734385b') == (
57 otp2289.OTPGenerator.tokens_to_bytes('CART OTTO HIVE ODE VAT NUT')) 66 otp2289.OTPGenerator.tokens_to_bytes('CART OTTO HIVE ODE VAT NUT')
67 )
58 assert binascii.unhexlify('87fec7768b73ccf9') == ( 68 assert binascii.unhexlify('87fec7768b73ccf9') == (
59 otp2289.OTPGenerator.tokens_to_bytes('GAFF WAIT SKID GIG SKY EYED')) 69 otp2289.OTPGenerator.tokens_to_bytes('GAFF WAIT SKID GIG SKY EYED')
70 )
60 assert binascii.unhexlify('ad85f658ebe383c9') == ( 71 assert binascii.unhexlify('ad85f658ebe383c9') == (
61 otp2289.OTPGenerator.tokens_to_bytes('LEST OR HEEL SCOT ROB SUIT')) 72 otp2289.OTPGenerator.tokens_to_bytes('LEST OR HEEL SCOT ROB SUIT')
73 )
62 assert binascii.unhexlify('d07ce229b5cf119b') == ( 74 assert binascii.unhexlify('d07ce229b5cf119b') == (
63 otp2289.OTPGenerator.tokens_to_bytes('RITE TAKE GELD COST TUNE RECK')) 75 otp2289.OTPGenerator.tokens_to_bytes('RITE TAKE GELD COST TUNE RECK')
76 )
64 assert binascii.unhexlify('27bc71035aaf3dc6') == ( 77 assert binascii.unhexlify('27bc71035aaf3dc6') == (
65 otp2289.OTPGenerator.tokens_to_bytes('MAY STAR TIN LYON VEDA STAN')) 78 otp2289.OTPGenerator.tokens_to_bytes('MAY STAR TIN LYON VEDA STAN')
79 )
66 assert binascii.unhexlify('d51f3e99bf8e6f0b') == ( 80 assert binascii.unhexlify('d51f3e99bf8e6f0b') == (
67 otp2289.OTPGenerator.tokens_to_bytes('RUST WELT KICK FELL TAIL FRAU')) 81 otp2289.OTPGenerator.tokens_to_bytes('RUST WELT KICK FELL TAIL FRAU')
82 )
68 assert binascii.unhexlify('82aeb52d943774e4') == ( 83 assert binascii.unhexlify('82aeb52d943774e4') == (
69 otp2289.OTPGenerator.tokens_to_bytes('FLIT DOSE ALSO MEW DRUM DEFY')) 84 otp2289.OTPGenerator.tokens_to_bytes('FLIT DOSE ALSO MEW DRUM DEFY')
85 )
70 assert binascii.unhexlify('4f296a74fe1567ec') == ( 86 assert binascii.unhexlify('4f296a74fe1567ec') == (
71 otp2289.OTPGenerator.tokens_to_bytes('AURA ALOE HURL WING BERG WAIT')) 87 otp2289.OTPGenerator.tokens_to_bytes('AURA ALOE HURL WING BERG WAIT')
88 )
72 89
73 90
74def test_random_bytes(): 91def test_random_bytes():