CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

146 lines
4.6 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Tests for CAT-SOOP's crypto
  18. """
  19. import os
  20. import time
  21. import base64
  22. import random
  23. import string
  24. import unittest
  25. from contextlib import contextmanager
  26. from catsoop.fernet import RawFernet, InvalidToken
  27. from cryptography.fernet import Fernet
  28. from ..test import CATSOOPTest
  29. # -----------------------------------------------------------------------------
  30. @contextmanager
  31. def spoof_time(current_time=None):
  32. if current_time == None:
  33. current_time = random.uniform(0, 4102444800)
  34. old_time_func = time.time
  35. time.time = lambda: current_time
  36. yield current_time
  37. time.time = old_time_func
  38. @contextmanager
  39. def spoof_urandom(pattern=None):
  40. if pattern == None:
  41. pattern = os.urandom(1000)
  42. def _spoofed(x):
  43. out = bytearray(pattern)
  44. while len(out) < x:
  45. out.extend(pattern)
  46. return bytes(out[:x])
  47. old_urandom = os.urandom
  48. os.urandom = _spoofed
  49. yield pattern
  50. os.urandom = old_urandom
  51. class Test_Fernet(CATSOOPTest):
  52. """
  53. Test for the RawFernet class
  54. """
  55. test_key = (
  56. b"s\x0f\xf4\xc7\xaf=F\x92>\x8e\xd4Q\xee\x81<\x87\xf7\x90\xb0"
  57. b"\xa2&\xbc\x96\xa9-\xe4\x9b^\x9c\x05\xe1\xee"
  58. )
  59. test_tok = (
  60. b"\x80\x00\x00\x00\x00\x1d\xc0\x9e\xb0\x00\x01\x02\x03\x04\x05"
  61. b"\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f-6\xd5\xcaFUb\x99\xfd\xe10"
  62. b"\x08c8\x04\xb2\xc5\xff\x90\x95\xf5\xd3\x8f\x9a\xb8nUC\xe0&"
  63. b"\x86\xf0;>\xc9q\xb9\xabG\xae#VjT\xe0\x8c*\x0c"
  64. )
  65. test_msg = b"hello"
  66. test_ctime = 499162800
  67. test_iv = bytes(range(16))
  68. test_ttl = 60
  69. def test_generate(self):
  70. """
  71. Test that the proper token is generated in a specific case
  72. """
  73. r = RawFernet(self.test_key)
  74. tok = r._encrypt_from_parts(self.test_msg, self.test_ctime, self.test_iv)
  75. self.assertEqual(tok, self.test_tok)
  76. with spoof_time(self.test_ctime):
  77. with spoof_urandom(self.test_iv):
  78. self.assertEqual(r.encrypt(self.test_msg), self.test_tok)
  79. def test_verify(self):
  80. """
  81. Test proper verification of a token
  82. """
  83. with spoof_time(self.test_ctime):
  84. r = RawFernet(self.test_key)
  85. out = r.decrypt(self.test_tok, ttl=self.test_ttl)
  86. self.assertEqual(out, self.test_msg)
  87. def test_ttl_handling(self):
  88. """
  89. Try a couple of verifications to
  90. """
  91. with spoof_time(self.test_ctime + 61):
  92. # try to decrypt with expire message
  93. r = RawFernet(self.test_key)
  94. with self.assertRaises(InvalidToken):
  95. out = r.decrypt(self.test_tok, ttl=self.test_ttl)
  96. # now decrypt with no ttl
  97. out = r.decrypt(self.test_tok)
  98. self.assertEqual(out, self.test_msg)
  99. def test_compare_fernet(self):
  100. """
  101. A test to compare our binary Fernet implementation against the base
  102. Fernet implementation from the cryptography library. 50 times, encrypt
  103. a random message with a random key and make sure our result matches
  104. that of the regular Fernet implementation.
  105. """
  106. chars = (
  107. string.ascii_lowercase + string.ascii_uppercase + string.digits
  108. ).encode("utf-8")
  109. for i in range(50):
  110. message = bytes(
  111. random.choice(chars) for i in range(random.randint(100, 10000))
  112. )
  113. with spoof_time():
  114. with spoof_urandom():
  115. key = Fernet.generate_key()
  116. raw_key = base64.urlsafe_b64decode(key)
  117. secret_base = Fernet(key).encrypt(message)
  118. secret_catsoop = RawFernet(raw_key).encrypt(message)
  119. self.assertEqual(
  120. base64.urlsafe_b64decode(secret_base), secret_catsoop
  121. )
  122. if __name__ == "__main__":
  123. unittest.main()