diff options
| author | Simeon Simeonov | 2020-03-26 15:24:34 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2020-03-26 15:24:34 +0100 |
| commit | 0a226b690b31ef31a92aea043ee8fe082b316633 (patch) | |
| tree | f3bec3676ddfd19b68b11cadd47c7fb79a336851 | |
| parent | 75bebab025aa03a2f1790d7d15b9c8264ab28fed (diff) | |
Add support for accepting a challenge string in the generator
| -rw-r--r-- | otp2289/__init__.py | 2 | ||||
| -rw-r--r-- | otp2289/generator.py | 192 | ||||
| -rw-r--r-- | otp2289/server.py | 4 | ||||
| -rw-r--r-- | tests/test_generator.py | 71 |
4 files changed, 217 insertions, 52 deletions
diff --git a/otp2289/__init__.py b/otp2289/__init__.py index d2f6386..82c1413 100644 --- a/otp2289/__init__.py +++ b/otp2289/__init__.py | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | from .generator import (OTP_ALGO_MD5, | 4 | from .generator import (OTP_ALGO_MD5, |
| 5 | OTP_ALGO_SHA1, | 5 | OTP_ALGO_SHA1, |
| 6 | OTPChallengeException, | ||
| 6 | OTPGenerator, | 7 | OTPGenerator, |
| 7 | OTPGeneratorException) | 8 | OTPGeneratorException) |
| 8 | 9 | ||
| @@ -24,5 +25,6 @@ VERSION = tuple(map(int_or_str, __version__.split('.'))) | |||
| 24 | 25 | ||
| 25 | __all__ = ['OTP_ALGO_MD5', | 26 | __all__ = ['OTP_ALGO_MD5', |
| 26 | 'OTP_ALGO_SHA1', | 27 | 'OTP_ALGO_SHA1', |
| 28 | 'OTPChallengeException', | ||
| 27 | 'OTPGenerator', | 29 | 'OTPGenerator', |
| 28 | 'OTPGeneratorException'] | 30 | 'OTPGeneratorException'] |
diff --git a/otp2289/generator.py b/otp2289/generator.py index b007d87..020d154 100644 --- a/otp2289/generator.py +++ b/otp2289/generator.py | |||
| @@ -1,5 +1,4 @@ | |||
| 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
| 2 | |||
| 3 | """ | 2 | """ |
| 4 | A pure Python implementation of the RFC-2289 OTP generator | 3 | A pure Python implementation of the RFC-2289 OTP generator |
| 5 | """ | 4 | """ |
| @@ -279,23 +278,28 @@ class OTPGeneratorException(Exception): | |||
| 279 | """OTPGeneratorException class""" | 278 | """OTPGeneratorException class""" |
| 280 | 279 | ||
| 281 | 280 | ||
| 281 | class OTPChallengeException(Exception): | ||
| 282 | """OTPChallengeException class""" | ||
| 283 | |||
| 284 | |||
| 282 | class OTPGenerator: | 285 | class OTPGenerator: |
| 283 | """OTPGenerator class""" | 286 | """OTPGenerator class""" |
| 284 | 287 | ||
| 285 | def __init__(self, password, seed, hash_algo=OTP_ALGO_MD5): | 288 | def __init__(self, password, seed='', hash_algo=OTP_ALGO_MD5): |
| 286 | """ | 289 | """ |
| 287 | Constructs an OTPGenerator object with a given password and seed | 290 | Constructs an OTPGenerator object with a given password and seed. |
| 288 | 291 | ||
| 289 | Keyword Arguments: | 292 | Keyword Arguments: |
| 290 | :param password: the password string | 293 | :param password: The password string |
| 291 | :type password: bytes | 294 | :type password: bytes |
| 292 | 295 | ||
| 293 | :param seed: the seed received from the server-challenge | 296 | :param seed: The seed received from the challenge, defaults to '' |
| 294 | :type seed: str | 297 | :type seed: str |
| 295 | 298 | ||
| 296 | :param hash_algo: the hash algo. | 299 | :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 |
| 297 | :type hash_algo: int or str | 300 | :type hash_algo: int or str |
| 298 | (default OTP_ALGO_MD5) | 301 | |
| 302 | :raises OTPGeneratorException: In case input does not validate | ||
| 299 | """ | 303 | """ |
| 300 | # enforce the rfc2289 constraints | 304 | # enforce the rfc2289 constraints |
| 301 | if not isinstance(password, bytes): | 305 | if not isinstance(password, bytes): |
| @@ -304,37 +308,20 @@ class OTPGenerator: | |||
| 304 | raise OTPGeneratorException( | 308 | raise OTPGeneratorException( |
| 305 | 'Password must be longer than 10 bytes') | 309 | 'Password must be longer than 10 bytes') |
| 306 | self._password = password | 310 | self._password = password |
| 307 | if not isinstance(seed, str): | ||
| 308 | raise OTPGeneratorException('Seed must be a string') | ||
| 309 | if not seed or len(seed) > 16: | ||
| 310 | raise OTPGeneratorException( | ||
| 311 | 'The seed MUST be of 1 to 16 characters in length') | ||
| 312 | for char in seed: | ||
| 313 | if char not in string.ascii_letters + string.digits: | ||
| 314 | raise OTPGeneratorException( | ||
| 315 | 'The seed MUST consist of purely alphanumeric characters') | ||
| 316 | self._seed = seed | 311 | self._seed = seed |
| 317 | if isinstance(hash_algo, int): | 312 | if self._seed: # the seed was set here. Validate it |
| 318 | self._hash_algo = _ALGO_DICT.get(hash_algo, 'md5') | 313 | self._seed = self.validate_seed(self._seed) |
| 319 | elif isinstance(hash_algo, str): | 314 | self._hash_algo = self.validate_hash_algo(hash_algo) |
| 320 | self._hash_algo = hash_algo | ||
| 321 | else: | ||
| 322 | raise OTPGeneratorException( | ||
| 323 | 'hash_algo must be an int or a str') | ||
| 324 | if self._hash_algo not in hashlib.algorithms_available: | ||
| 325 | raise OTPGeneratorException( | ||
| 326 | '{hash_algo} is not supported by this version of the ' | ||
| 327 | 'hashlib module'.format(hash_algo=self._hash_algo)) | ||
| 328 | 315 | ||
| 329 | @staticmethod | 316 | @staticmethod |
| 330 | def bit_pair_sum(bit_stream): | 317 | def bit_pair_sum(bit_stream): |
| 331 | """ | 318 | """ |
| 332 | Split bit_stream in bit-pairs and sum them all together | 319 | Split bit_stream in bit-pairs and sum them all together. |
| 333 | 320 | ||
| 334 | :param bit_stream: The bit-stream object | 321 | :param bit_stream: The bit-stream object |
| 335 | :type bit_stream: str | 322 | :type bit_stream: str |
| 336 | 323 | ||
| 337 | :return: the sum of all bit-pairs in bit_stream | 324 | :return: The sum of all bit-pairs in bit_stream |
| 338 | :rtype: int | 325 | :rtype: int |
| 339 | """ | 326 | """ |
| 340 | if not isinstance(bit_stream, str): | 327 | if not isinstance(bit_stream, str): |
| @@ -347,15 +334,41 @@ class OTPGenerator: | |||
| 347 | return value | 334 | return value |
| 348 | 335 | ||
| 349 | @staticmethod | 336 | @staticmethod |
| 337 | def get_tokens_from_challenge(challenge): | ||
| 338 | """ | ||
| 339 | Returns tokens (seed, hash_algo and step) from a challenge string. | ||
| 340 | |||
| 341 | N.B. The tokens are not validated here. | ||
| 342 | |||
| 343 | :param challenge: The challenge string described in RFC-2289 | ||
| 344 | :type challenge: str | ||
| 345 | |||
| 346 | :raises OTPChallengeException: When the challenge string is invalid | ||
| 347 | |||
| 348 | :return: (seed, hash_algo, step) tuple. | ||
| 349 | :rtype: tuple | ||
| 350 | """ | ||
| 351 | if not isinstance(challenge, str): | ||
| 352 | raise OTPChallengeException('Challenge must be str') | ||
| 353 | challenge = challenge.strip() | ||
| 354 | if not challenge.startswith('otp-'): | ||
| 355 | raise OTPChallengeException('Invalid challenge') | ||
| 356 | try: | ||
| 357 | hash_algo, step, seed = challenge[4:].split() | ||
| 358 | return (seed, hash_algo, int(step)) | ||
| 359 | except ValueError: | ||
| 360 | raise OTPChallengeException('Invalid challenge') | ||
| 361 | |||
| 362 | @staticmethod | ||
| 350 | def sha1_digest_folding(sha1_digest): | 363 | def sha1_digest_folding(sha1_digest): |
| 351 | """ | 364 | """ |
| 352 | Implementation of the 160bit -> 64bit folding algorithm | 365 | Implementation of the 160bit -> 64bit folding algorithm |
| 353 | for sha1 digest | 366 | for sha1 digest. |
| 354 | 367 | ||
| 355 | :param sha1_digest: The SHA1 digest | 368 | :param sha1_digest: The SHA1 digest |
| 356 | :type sha1_digest: bytes | 369 | :type sha1_digest: bytes |
| 357 | 370 | ||
| 358 | :return: the byte-string representing the folded sha1-digest | 371 | :return: The byte-string representing the folded sha1-digest |
| 359 | :rtype: bytes | 372 | :rtype: bytes |
| 360 | """ | 373 | """ |
| 361 | if not (isinstance(sha1_digest, bytes)): | 374 | if not (isinstance(sha1_digest, bytes)): |
| @@ -396,7 +409,7 @@ class OTPGenerator: | |||
| 396 | @staticmethod | 409 | @staticmethod |
| 397 | def strxor(byte_str1, byte_str2): | 410 | def strxor(byte_str1, byte_str2): |
| 398 | """ | 411 | """ |
| 399 | Implementation of strxor similar to the one provided by pycrypto | 412 | Implementation of strxor similar to the one provided by pycrypto. |
| 400 | 413 | ||
| 401 | :param byte_str1: Byte-string 1 | 414 | :param byte_str1: Byte-string 1 |
| 402 | :type byte_str1: bytes | 415 | :type byte_str1: bytes |
| @@ -404,7 +417,7 @@ class OTPGenerator: | |||
| 404 | :param byte_str2: Byte-string 2 | 417 | :param byte_str2: Byte-string 2 |
| 405 | :type byte_str2: bytes | 418 | :type byte_str2: bytes |
| 406 | 419 | ||
| 407 | :return: the byte-string representing the result of byte_str1^byte_str2 | 420 | :return: The byte-string representing the result of byte_str1^byte_str2 |
| 408 | :rtype: bytes | 421 | :rtype: bytes |
| 409 | """ | 422 | """ |
| 410 | if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): | 423 | if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): |
| @@ -418,28 +431,101 @@ class OTPGenerator: | |||
| 418 | [byte_str1[i] ^ byte_str2[i] for i in range(length)] | 431 | [byte_str1[i] ^ byte_str2[i] for i in range(length)] |
| 419 | ) | 432 | ) |
| 420 | 433 | ||
| 434 | @staticmethod | ||
| 435 | def validate_hash_algo(hash_algo): | ||
| 436 | """ | ||
| 437 | Validates the provided hash-algorithm. | ||
| 438 | |||
| 439 | :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 | ||
| 440 | :type hash_algo: int or str | ||
| 441 | |||
| 442 | :raises OTPGeneratorException: In case hash_algo does not validate | ||
| 443 | |||
| 444 | :return: The validated hash_algo in str-form | ||
| 445 | :rtype: str | ||
| 446 | """ | ||
| 447 | if isinstance(hash_algo, int): | ||
| 448 | if hash_algo not in _ALGO_DICT: | ||
| 449 | raise OTPGeneratorException( | ||
| 450 | 'hash_algo is not among the known algorithms') | ||
| 451 | hash_algo = _ALGO_DICT.get(hash_algo) | ||
| 452 | if not isinstance(hash_algo, str): | ||
| 453 | raise OTPGeneratorException( | ||
| 454 | 'hash_algo must be an int or a str') | ||
| 455 | if hash_algo not in hashlib.algorithms_available: | ||
| 456 | raise OTPGeneratorException( | ||
| 457 | f'{hash_algo} is not supported by this version of the ' | ||
| 458 | 'hashlib module') | ||
| 459 | return hash_algo | ||
| 460 | |||
| 461 | @staticmethod | ||
| 462 | def validate_seed(seed): | ||
| 463 | """ | ||
| 464 | Validates the provided seed as defined by RFC-2289. | ||
| 465 | |||
| 466 | :param seed: The seed received from the challenge, defaults to '' | ||
| 467 | :type seed: str | ||
| 468 | |||
| 469 | :raises OTPGeneratorException: In case seed does not validate | ||
| 470 | |||
| 471 | :return: The validated (and very same) seed | ||
| 472 | :rtype: str | ||
| 473 | """ | ||
| 474 | if not isinstance(seed, str): | ||
| 475 | raise OTPGeneratorException('Seed must be a string') | ||
| 476 | if not seed or len(seed) > 16: | ||
| 477 | raise OTPGeneratorException( | ||
| 478 | 'The seed MUST be of 1 to 16 characters in length') | ||
| 479 | for char in seed: | ||
| 480 | if char not in string.ascii_letters + string.digits: | ||
| 481 | raise OTPGeneratorException( | ||
| 482 | 'The seed MUST consist of purely alphanumeric characters') | ||
| 483 | return seed | ||
| 484 | |||
| 421 | def generate_otp_hexdigest(self, step): | 485 | def generate_otp_hexdigest(self, step): |
| 422 | """ | 486 | """ |
| 423 | Generates the OTP hexdigest for the given step | 487 | Generates the OTP hexdigest for the given step. |
| 424 | 488 | ||
| 425 | Keyword Arguments: | 489 | Keyword Arguments: |
| 426 | :param step: the step to generate OTP for | 490 | :param step: The step to generate OTP for |
| 427 | :type step: int | 491 | :type step: int |
| 428 | 492 | ||
| 429 | :return: hexdigest for the given step | 493 | :return: Hexdigest for the given step |
| 430 | :rtype: str | 494 | :rtype: str |
| 431 | """ | 495 | """ |
| 432 | return '0x' + binascii.hexlify(self._generate_otp_bytes(step)).decode() | 496 | return '0x' + binascii.hexlify(self._generate_otp_bytes(step)).decode() |
| 433 | 497 | ||
| 498 | def generate_otp_hexdigest_from_challenge(self, challenge): | ||
| 499 | """ | ||
| 500 | Same as generate_otp_hexdigest, but it generates hex. from a challenge. | ||
| 501 | |||
| 502 | RFC-2289 states: | ||
| 503 | The challenge MUST be in a standard syntax so | ||
| 504 | that automated generators can recognize the challenge in context and | ||
| 505 | extract these parameters. The syntax of the challenge is: | ||
| 506 | otp-<algorithm identifier> <sequence integer> <seed> | ||
| 507 | |||
| 508 | Keyword Arguments: | ||
| 509 | :param challenge: The challenge string | ||
| 510 | :type challenge: str | ||
| 511 | |||
| 512 | :return: Hexdigest for the given challenge | ||
| 513 | :rtype: str | ||
| 514 | """ | ||
| 515 | seed, hash_algo, step = self.get_tokens_from_challenge(challenge) | ||
| 516 | self._seed = self.validate_seed(seed) | ||
| 517 | self._hash_algo = self.validate_hash_algo(hash_algo) | ||
| 518 | return self.generate_otp_hexdigest(step) | ||
| 519 | |||
| 434 | def generate_otp_words(self, step): | 520 | def generate_otp_words(self, step): |
| 435 | """ | 521 | """ |
| 436 | Generates the OTP six words token for the given step | 522 | Generates the OTP six words token for the given step. |
| 437 | 523 | ||
| 438 | Keyword Arguments: | 524 | Keyword Arguments: |
| 439 | :param step: the step to generate OTP for | 525 | :param step: The step to generate OTP for |
| 440 | :type step: int | 526 | :type step: int |
| 441 | 527 | ||
| 442 | :return: six words (separated by single space) token for the given step | 528 | :return: Six words (separated by single space) token for the given step |
| 443 | :rtype: str | 529 | :rtype: str |
| 444 | """ | 530 | """ |
| 445 | digest = self._generate_otp_bytes(step) | 531 | digest = self._generate_otp_bytes(step) |
| @@ -457,6 +543,28 @@ class OTPGenerator: | |||
| 457 | bit_stream[55:64] + '{0:0>8b}'.format(bit_pair_sum)[-2:], 2)]) | 543 | bit_stream[55:64] + '{0:0>8b}'.format(bit_pair_sum)[-2:], 2)]) |
| 458 | return ' '.join(tokens) | 544 | return ' '.join(tokens) |
| 459 | 545 | ||
| 546 | def generate_otp_words_from_challenge(self, challenge): | ||
| 547 | """ | ||
| 548 | Same as generate_otp_words, but it generates words from a challenge. | ||
| 549 | |||
| 550 | RFC-2289 states: | ||
| 551 | The challenge MUST be in a standard syntax so | ||
| 552 | that automated generators can recognize the challenge in context and | ||
| 553 | extract these parameters. The syntax of the challenge is: | ||
| 554 | otp-<algorithm identifier> <sequence integer> <seed> | ||
| 555 | |||
| 556 | Keyword Arguments: | ||
| 557 | :param challenge: The challenge string | ||
| 558 | :type challenge: str | ||
| 559 | |||
| 560 | :return: Six words token for the given challenge | ||
| 561 | :rtype: str | ||
| 562 | """ | ||
| 563 | seed, hash_algo, step = self.get_tokens_from_challenge(challenge) | ||
| 564 | self._seed = self.validate_seed(seed) | ||
| 565 | self._hash_algo = self.validate_hash_algo(hash_algo) | ||
| 566 | return self.generate_otp_words(step) | ||
| 567 | |||
| 460 | def hexdigest_range(self, start=499, stop=0): | 568 | def hexdigest_range(self, start=499, stop=0): |
| 461 | """ | 569 | """ |
| 462 | Returns an iterator that providing hexdigests corresponding to steps | 570 | Returns an iterator that providing hexdigests corresponding to steps |
| @@ -503,13 +611,13 @@ class OTPGenerator: | |||
| 503 | 611 | ||
| 504 | def _generate_otp_bytes(self, step): | 612 | def _generate_otp_bytes(self, step): |
| 505 | """ | 613 | """ |
| 506 | Generates the OTP bytes for the given step | 614 | Generates the OTP bytes for the given step. |
| 507 | 615 | ||
| 508 | Keyword Arguments: | 616 | Keyword Arguments: |
| 509 | :param step: the step to generate OTP for | 617 | :param step: The step to generate OTP for |
| 510 | :type step: int | 618 | :type step: int |
| 511 | 619 | ||
| 512 | :return: the digest bytes for the given step | 620 | :return: The digest bytes for the given step |
| 513 | :rtype: bytes | 621 | :rtype: bytes |
| 514 | """ | 622 | """ |
| 515 | if not isinstance(step, int): | 623 | if not isinstance(step, int): |
diff --git a/otp2289/server.py b/otp2289/server.py new file mode 100644 index 0000000..86c4fa5 --- /dev/null +++ b/otp2289/server.py | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | # -*- coding: utf-8 -*- | ||
| 2 | """ | ||
| 3 | A pure Python implementation of the RFC-2289 OTP server | ||
| 4 | """ | ||
diff --git a/tests/test_generator.py b/tests/test_generator.py index 14258da..a4af3ba 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py | |||
| @@ -5,9 +5,38 @@ import pytest | |||
| 5 | import otp2289 | 5 | import otp2289 |
| 6 | 6 | ||
| 7 | 7 | ||
| 8 | def test_exceptions_and_constraints(): | 8 | def test_caller_exceptions(): |
| 9 | """Tests general exceptions and constraints""" | 9 | """Tests the exceptions when calling an initialized object""" |
| 10 | # test the otp2289.OTPGenerator __init__ | 10 | gen = otp2289.OTPGenerator('This is a test.'.encode(), |
| 11 | 'TeSt', | ||
| 12 | otp2289.OTP_ALGO_MD5) | ||
| 13 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: | ||
| 14 | gen.generate_otp_words('3') | ||
| 15 | assert exc_info.type is otp2289.OTPGeneratorException | ||
| 16 | assert exc_info.value.args[0] == 'Step value MUST be an int' | ||
| 17 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: | ||
| 18 | gen.generate_otp_hexdigest(-1) | ||
| 19 | assert exc_info.type is otp2289.OTPGeneratorException | ||
| 20 | assert exc_info.value.args[0] == 'Step value MUST be >= 0' | ||
| 21 | with pytest.raises(otp2289.OTPChallengeException) as exc_info: | ||
| 22 | gen.generate_otp_hexdigest_from_challenge(b'md5 fbd TeSt') | ||
| 23 | assert exc_info.type is otp2289.OTPChallengeException | ||
| 24 | assert exc_info.value.args[0] == 'Challenge must be str' | ||
| 25 | with pytest.raises(otp2289.OTPChallengeException) as exc_info: | ||
| 26 | gen.generate_otp_hexdigest_from_challenge('md5 fbd TeSt') | ||
| 27 | assert exc_info.type is otp2289.OTPChallengeException | ||
| 28 | assert exc_info.value.args[0] == 'Invalid challenge' | ||
| 29 | with pytest.raises(otp2289.generator.OTPChallengeException) as exc_info: | ||
| 30 | gen.generate_otp_hexdigest_from_challenge('otp-md5 fbd TeSt') | ||
| 31 | assert exc_info.type is otp2289.generator.OTPChallengeException | ||
| 32 | assert exc_info.value.args[0] == 'Invalid challenge' | ||
| 33 | |||
| 34 | |||
| 35 | def test_constructor_exceptions(): | ||
| 36 | """ | ||
| 37 | Tests the exceptions when initializing a new object (in the constructor) | ||
| 38 | """ | ||
| 39 | # test the otp2289.OTPGenerator __init__ and validators | ||
| 11 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: | 40 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: |
| 12 | otp2289.OTPGenerator('1234567', 'TeStø'.encode(), otp2289.OTP_ALGO_MD5) | 41 | otp2289.OTPGenerator('1234567', 'TeStø'.encode(), otp2289.OTP_ALGO_MD5) |
| 13 | assert exc_info.type is otp2289.OTPGeneratorException | 42 | assert exc_info.type is otp2289.OTPGeneratorException |
| @@ -38,17 +67,23 @@ def test_exceptions_and_constraints(): | |||
| 38 | assert exc_info.type is otp2289.OTPGeneratorException | 67 | assert exc_info.type is otp2289.OTPGeneratorException |
| 39 | assert exc_info.value.args[0] == ('The seed MUST consist of purely ' | 68 | assert exc_info.value.args[0] == ('The seed MUST consist of purely ' |
| 40 | 'alphanumeric characters') | 69 | 'alphanumeric characters') |
| 41 | gen = otp2289.OTPGenerator('This is a test.'.encode(), | ||
| 42 | 'TeSt', | ||
| 43 | otp2289.OTP_ALGO_MD5) | ||
| 44 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: | 70 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: |
| 45 | gen.generate_otp_words('3') | 71 | otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', 9) |
| 46 | assert exc_info.type is otp2289.OTPGeneratorException | 72 | assert exc_info.type is otp2289.OTPGeneratorException |
| 47 | assert exc_info.value.args[0] == 'Step value MUST be an int' | 73 | assert exc_info.value.args[0] == ( |
| 74 | 'hash_algo is not among the known algorithms') | ||
| 48 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: | 75 | with pytest.raises(otp2289.OTPGeneratorException) as exc_info: |
| 49 | gen.generate_otp_hexdigest(-1) | 76 | otp2289.OTPGenerator('This is a test.'.encode(), 'TeSt', b'md5') |
| 50 | assert exc_info.type is otp2289.OTPGeneratorException | 77 | assert exc_info.type is otp2289.OTPGeneratorException |
| 51 | assert exc_info.value.args[0] == 'Step value MUST be >= 0' | 78 | assert exc_info.value.args[0] == 'hash_algo must be an int or a str' |
| 79 | # test the package structure as well | ||
| 80 | with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info: | ||
| 81 | otp2289.generator.OTPGenerator('This is a test.'.encode(), | ||
| 82 | 'TeSt', | ||
| 83 | 'foo') | ||
| 84 | assert exc_info.type is otp2289.generator.OTPGeneratorException | ||
| 85 | assert exc_info.value.args[0] == ('foo is not supported by this version ' | ||
| 86 | 'of the hashlib module') | ||
| 52 | 87 | ||
| 53 | 88 | ||
| 54 | def test_md5(): | 89 | def test_md5(): |
| @@ -71,9 +106,17 @@ def test_md5(): | |||
| 71 | # step 1 | 106 | # step 1 |
| 72 | assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f' | 107 | assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f' |
| 73 | assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS' | 108 | assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS' |
| 109 | assert gen.generate_otp_hexdigest_from_challenge('otp-md5 1 TeSt') == ( | ||
| 110 | '0x7965e05436f5029f') | ||
| 111 | assert gen.generate_otp_words_from_challenge('otp-md5 1 TeSt') == ( | ||
| 112 | 'EASE OIL FUM CURE AWRY AVIS') | ||
| 74 | # step 99 | 113 | # step 99 |
| 75 | assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880' | 114 | assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880' |
| 76 | assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY' | 115 | assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY' |
| 116 | assert gen.generate_otp_hexdigest_from_challenge('otp-md5 99 TeSt') == ( | ||
| 117 | '0x50fe1962c4965880') | ||
| 118 | assert gen.generate_otp_words_from_challenge('otp-md5 99 TeSt') == ( | ||
| 119 | 'BAIL TUFT BITS GANG CHEF THY') | ||
| 77 | # iterator test | 120 | # iterator test |
| 78 | hexdigests = list(gen.hexdigest_range(105)) # testing the range itself | 121 | hexdigests = list(gen.hexdigest_range(105)) # testing the range itself |
| 79 | words = list(gen.words_range(99)) | 122 | words = list(gen.words_range(99)) |
| @@ -126,8 +169,16 @@ def test_sha1(): | |||
| 126 | assert res_words == 'MILT VARY MAST OK SEES WENT' | 169 | assert res_words == 'MILT VARY MAST OK SEES WENT' |
| 127 | assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b' | 170 | assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b' |
| 128 | assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT' | 171 | assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT' |
| 172 | assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 1 TeSt') == ( | ||
| 173 | '0x63d936639734385b') | ||
| 174 | assert gen.generate_otp_words_from_challenge('otp-sha1 1 TeSt') == ( | ||
| 175 | 'CART OTTO HIVE ODE VAT NUT') | ||
| 129 | assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9' | 176 | assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9' |
| 130 | assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED' | 177 | assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED' |
| 178 | assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 99 TeSt') == ( | ||
| 179 | '0x87fec7768b73ccf9') | ||
| 180 | assert gen.generate_otp_words_from_challenge('otp-sha1 99 TeSt') == ( | ||
| 181 | 'GAFF WAIT SKID GIG SKY EYED') | ||
| 131 | # iterator test | 182 | # iterator test |
| 132 | hexdigests = list(gen.hexdigest_range(105)) | 183 | hexdigests = list(gen.hexdigest_range(105)) |
| 133 | words = list(gen.words_range(99)) | 184 | words = list(gen.words_range(99)) |
