| 358 | | def test_multiResponse(self): |
| 359 | | """ |
| 360 | | L{DigestCredentialFactory.decode} handles multiple responses to a |
| 361 | | single challenge. |
| 362 | | """ |
| 363 | | challenge = self.credentialFactory.getChallenge(self.clientAddress.host) |
| 364 | | |
| 365 | | nc = "00000001" |
| 366 | | clientResponse = self.formatResponse( |
| 367 | | nonce=challenge['nonce'], |
| 368 | | response=self.getDigestResponse(challenge, nc), |
| 369 | | nc=nc, |
| 370 | | opaque=challenge['opaque']) |
| 371 | | |
| 372 | | creds = self.credentialFactory.decode(clientResponse, self.method, |
| 373 | | self.clientAddress.host) |
| 374 | | self.assertTrue(creds.checkPassword(self.password)) |
| 375 | | self.assertFalse(creds.checkPassword(self.password + 'wrong')) |
| 376 | | |
| 377 | | nc = "00000002" |
| 378 | | clientResponse = self.formatResponse( |
| 379 | | nonce=challenge['nonce'], |
| 380 | | response=self.getDigestResponse(challenge, nc), |
| 381 | | nc=nc, |
| 382 | | opaque=challenge['opaque']) |
| 383 | | |
| 384 | | creds = self.credentialFactory.decode(clientResponse, self.method, |
| 385 | | self.clientAddress.host) |
| 386 | | self.assertTrue(creds.checkPassword(self.password)) |
| 387 | | self.assertFalse(creds.checkPassword(self.password + 'wrong')) |
| 388 | | |
| 389 | | |
| | 633 | |
| | 634 | |
| | 635 | def test_reusedNonce(self): |
| | 636 | """ |
| | 637 | L{DigestCredentialFactory.decode} raises L{LoginFailed} when the given |
| | 638 | same nonce twice |
| | 639 | """ |
| | 640 | credentialFactory = FakeDigestCredentialFactory(self.algorithm, |
| | 641 | self.realm) |
| | 642 | challenge = credentialFactory.getChallenge(self.clientAddress.host) |
| | 643 | |
| | 644 | key = '%s,%s,%s' % (challenge['nonce'], |
| | 645 | self.clientAddress.host, |
| | 646 | '0') |
| | 647 | digest = md5(key + credentialFactory.privateKey).hexdigest() |
| | 648 | ekey = b64encode(key) |
| | 649 | |
| | 650 | nonceOpaque = '%s-%s' % (digest, ekey.strip('\n')) |
| | 651 | |
| | 652 | self.assertEqual(credentialFactory._verifyNonce( |
| | 653 | challenge['nonce']), |
| | 654 | True) |
| | 655 | |
| | 656 | self.assertRaises( |
| | 657 | LoginFailed, |
| | 658 | credentialFactory._verifyNonce, |
| | 659 | challenge['nonce']) |
| | 660 | |
| | 661 | |
| | 662 | def test_nonceTimeoutCleanup(self): |
| | 663 | """ |
| | 664 | L{DigestCredentialFactory._nonceCleanup} cleans up old nonces |
| | 665 | to prevent memory exhaustion. The nonces cannot facilitate replay |
| | 666 | attacks as the original request is timestamped. |
| | 667 | """ |
| | 668 | credentialFactory = FakeDigestCredentialFactory(self.algorithm, |
| | 669 | self.realm) |
| | 670 | |
| | 671 | challenge = credentialFactory.getChallenge(self.clientAddress.host) |
| | 672 | |
| | 673 | # First verify a nonce, this adds it to known nonces |
| | 674 | self.assertEqual(credentialFactory._verifyNonce( |
| | 675 | challenge['nonce']), |
| | 676 | True) |
| | 677 | |
| | 678 | # Turn the clock forward |
| | 679 | credentialFactory.fakeTime = 10000 |
| | 680 | |
| | 681 | # Cleanup old nonces |
| | 682 | credentialFactory._nonceCleanup() |
| | 683 | |
| | 684 | # The nonce tracking mechanisms must now be empty as the only item |
| | 685 | # tracked was cleaned up. |
| | 686 | self.assertEqual(len(credentialFactory._nonces), 0) |
| | 687 | self.assertEqual(len(credentialFactory._nonceCleanupTracker), 0) |
| | 688 | |
| | 689 | # Turn clock back again, the same nonce must now work again |
| | 690 | # as it is no longer known |
| | 691 | self.assertEqual(credentialFactory._verifyNonce( |
| | 692 | challenge['nonce']), |
| | 693 | True) |
| | 694 | |
| | 695 | def test_nonceCounter(self): |
| | 696 | """ |
| | 697 | L{DigestCredentialFactory._verifyNonce} keeps track of nonce counter |
| | 698 | and allows reuse if the nonces are in order |
| | 699 | """ |
| | 700 | credentialFactory = FakeDigestCredentialFactory(self.algorithm, |
| | 701 | self.realm) |
| | 702 | |
| | 703 | challenge = credentialFactory.getChallenge(self.clientAddress.host) |
| | 704 | |
| | 705 | self.assertEqual(credentialFactory._verifyNonce( |
| | 706 | challenge['nonce'], |
| | 707 | '00000001'), |
| | 708 | True) |
| | 709 | |
| | 710 | self.assertEqual(credentialFactory._verifyNonce( |
| | 711 | challenge['nonce'], |
| | 712 | '00000002'), |
| | 713 | True) |
| | 714 | |
| | 715 | def test_nonceCounterWrongInitValue(self): |
| | 716 | """ |
| | 717 | L{DigestCredentialFactory._verifyNonce} keeps track of nonce counter |
| | 718 | and allows reuse if the nonces are in order |
| | 719 | """ |
| | 720 | credentialFactory = FakeDigestCredentialFactory(self.algorithm, |
| | 721 | self.realm) |
| | 722 | |
| | 723 | challenge = credentialFactory.getChallenge(self.clientAddress.host) |
| | 724 | |
| | 725 | self.assertRaises( |
| | 726 | LoginFailed, |
| | 727 | credentialFactory._verifyNonce, |
| | 728 | challenge['nonce'], '00000002') |
| | 729 | |
| | 730 | def test_nonceCounterOutOfOrder(self): |
| | 731 | """ |
| | 732 | FIXME |
| | 733 | """ |
| | 734 | credentialFactory = FakeDigestCredentialFactory(self.algorithm, |
| | 735 | self.realm) |
| | 736 | |
| | 737 | challenge = credentialFactory.getChallenge(self.clientAddress.host) |
| | 738 | |
| | 739 | self.assertEqual(credentialFactory._verifyNonce( |
| | 740 | challenge['nonce'], |
| | 741 | '00000001'), |
| | 742 | True) |
| | 743 | |
| | 744 | self.assertRaises( |
| | 745 | LoginFailed, |
| | 746 | credentialFactory._verifyNonce, |
| | 747 | challenge['nonce'], '00000003') |
| | 748 | |
| | 749 | self.assertEqual(credentialFactory._verifyNonce( |
| | 750 | challenge['nonce'], |
| | 751 | '00000002'), |
| | 752 | True) |
| | 753 | |
| | 754 | def test_withQopWithoutCnonceOrNc(self): |
| | 755 | """ |
| | 756 | L{DigestCredentialFactory.decode} must fail when provided with qop but |
| | 757 | without cnonce or nc. |
| | 758 | """ |
| | 759 | # Test for nc |
| | 760 | challenge = self.credentialFactory.getChallenge(self.clientAddress.host) |
| | 761 | |
| | 762 | nc = None |
| | 763 | clientResponse = self.formatResponse( |
| | 764 | nonce=challenge['nonce'], |
| | 765 | response=self.getDigestResponse(challenge, nc), |
| | 766 | nc=nc, |
| | 767 | opaque=challenge['opaque']) |
| | 768 | self.assertRaises(LoginFailed, self.credentialFactory.decode, |
| | 769 | clientResponse, self.method, self.clientAddress.host) |
| | 770 | |
| | 771 | # Test for cnonce |
| | 772 | challenge = self.credentialFactory.getChallenge(self.clientAddress.host) |
| | 773 | |
| | 774 | nc = "00000001" |
| | 775 | clientResponse = self.formatResponse( |
| | 776 | nonce=challenge['nonce'], |
| | 777 | response=self.getDigestResponse(challenge, nc), |
| | 778 | cnonce=None, |
| | 779 | opaque=challenge['opaque']) |
| | 780 | self.assertRaises(LoginFailed, self.credentialFactory.decode, |
| | 781 | clientResponse, self.method, self.clientAddress.host) |
| | 782 | |
| | 783 | def test_withoutQopWithCnonceAndNc(self): |
| | 784 | """ |
| | 785 | L{DigestCredentialFactory.decode} must fail when not provided with qop but |
| | 786 | with cnonce or nc. |
| | 787 | """ |
| | 788 | challenge = self.credentialFactory.getChallenge(self.clientAddress.host) |
| | 789 | |
| | 790 | nc = "00000001" |
| | 791 | clientResponse = self.formatResponse( |
| | 792 | nonce=challenge['nonce'], |
| | 793 | response=self.getDigestResponse(challenge, nc), |
| | 794 | nc=nc, |
| | 795 | qop=None, |
| | 796 | opaque=challenge['opaque']) |
| | 797 | self.assertRaises(LoginFailed, self.credentialFactory.decode, |
| | 798 | clientResponse, self.method, self.clientAddress.host) |
| | 799 | |
| | 800 | def test_withoutQop(self): |
| | 801 | """ |
| | 802 | L{DigestCredentialFactory.decode} must work when no qop is provided to be backward |
| | 803 | compatible. |
| | 804 | """ |
| | 805 | challenge = self.credentialFactory.getChallenge(self.clientAddress.host) |
| | 806 | |
| | 807 | nc = None |
| | 808 | clientResponse = self.formatResponse( |
| | 809 | nonce=challenge['nonce'], |
| | 810 | response=self.getDigestResponse(challenge, nc), |
| | 811 | nc=nc, |
| | 812 | cnonce=None, |
| | 813 | qop=None, |
| | 814 | opaque=challenge['opaque']) |
| | 815 | creds = self.credentialFactory.decode( |
| | 816 | clientResponse, self.method, self.clientAddress.host) |
| | 817 | self.assertTrue(creds.checkPassword(self.password)) |
| | 818 | self.assertFalse(creds.checkPassword(self.password + 'wrong')) |
| | 819 | No newline at end of file |