crypto.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more 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. import base64
  17. import string
  18. import errno
  19. import itsdangerous
  20. import logging
  21. import os.path
  22. import random
  23. import tempfile
  24. from mediagoblin import mg_globals
  25. _log = logging.getLogger(__name__)
  26. # produces base64 alphabet
  27. ALPHABET = string.ascii_letters + "-_"
  28. # Use the system (hardware-based) random number generator if it exists.
  29. # -- this optimization is lifted from Django
  30. try:
  31. getrandbits = random.SystemRandom().getrandbits
  32. except AttributeError:
  33. getrandbits = random.getrandbits
  34. # TODO: This should be attached to the MediaGoblinApp
  35. __itsda_secret = None
  36. def load_key(filename):
  37. global __itsda_secret
  38. key_file = open(filename)
  39. try:
  40. __itsda_secret = key_file.read()
  41. finally:
  42. key_file.close()
  43. def create_key(key_dir, key_filepath):
  44. global __itsda_secret
  45. old_umask = os.umask(0o77)
  46. key_file = None
  47. try:
  48. if not os.path.isdir(key_dir):
  49. os.makedirs(key_dir)
  50. _log.info("Created %s", key_dir)
  51. key = str(getrandbits(192))
  52. key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin',
  53. delete=False)
  54. key_file.write(key.encode('ascii'))
  55. key_file.flush()
  56. os.rename(key_file.name, key_filepath)
  57. key_file.close()
  58. finally:
  59. os.umask(old_umask)
  60. if (key_file is not None) and (not key_file.closed):
  61. key_file.close()
  62. os.unlink(key_file.name)
  63. __itsda_secret = key
  64. _log.info("Saved new key for It's Dangerous")
  65. def setup_crypto(app_config):
  66. global __itsda_secret
  67. key_dir = app_config["crypto_path"]
  68. key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin')
  69. try:
  70. load_key(key_filepath)
  71. except IOError as error:
  72. if error.errno != errno.ENOENT:
  73. raise
  74. create_key(key_dir, key_filepath)
  75. def get_timed_signer_url(namespace):
  76. """
  77. This gives a basic signing/verifying object.
  78. The namespace makes sure signed tokens can't be used in
  79. a different area. Like using a forgot-password-token as
  80. a session cookie.
  81. Basic usage:
  82. .. code-block:: python
  83. _signer = None
  84. TOKEN_VALID_DAYS = 10
  85. def setup():
  86. global _signer
  87. _signer = get_timed_signer_url("session cookie")
  88. def create_token(obj):
  89. return _signer.dumps(obj)
  90. def parse_token(token):
  91. # This might raise an exception in case
  92. # of an invalid token, or an expired token.
  93. return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600)
  94. For more details see
  95. http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer
  96. """
  97. assert __itsda_secret is not None
  98. return itsdangerous.URLSafeTimedSerializer(__itsda_secret,
  99. salt=namespace)
  100. def random_string(length, alphabet=ALPHABET):
  101. """ Returns a URL safe base64 encoded crypographically strong string """
  102. base = len(alphabet)
  103. rstring = ""
  104. for i in range(length):
  105. n = getrandbits(6) # 6 bytes = 2^6 = 64
  106. n = divmod(n, base)[1]
  107. rstring += alphabet[n]
  108. return rstring