Merge branch 'merge-python3-port'
[mediagoblin.git] / mediagoblin / tools / crypto.py
CommitLineData
5907154a
E
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
4990b47c 17import base64
18import string
09102e07
BS
19import errno
20import itsdangerous
5907154a 21import logging
09102e07 22import os.path
5907154a 23import random
09102e07 24import tempfile
5907154a
E
25from mediagoblin import mg_globals
26
27_log = logging.getLogger(__name__)
28
4990b47c 29# produces base64 alphabet
c5eb24b8 30ALPHABET = string.ascii_letters + "-_"
5907154a
E
31
32# Use the system (hardware-based) random number generator if it exists.
33# -- this optimization is lifted from Django
09102e07 34try:
5907154a 35 getrandbits = random.SystemRandom().getrandbits
09102e07 36except AttributeError:
5907154a
E
37 getrandbits = random.getrandbits
38
39
40__itsda_secret = None
41
42
09102e07 43def load_key(filename):
5907154a 44 global __itsda_secret
09102e07
BS
45 key_file = open(filename)
46 try:
47 __itsda_secret = key_file.read()
48 finally:
49 key_file.close()
5907154a 50
11780855 51
09102e07
BS
52def create_key(key_dir, key_filepath):
53 global __itsda_secret
7f342c72 54 old_umask = os.umask(0o77)
09102e07
BS
55 key_file = None
56 try:
57 if not os.path.isdir(key_dir):
58 os.makedirs(key_dir)
11780855 59 _log.info("Created %s", key_dir)
09102e07
BS
60 key = str(getrandbits(192))
61 key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin',
62 delete=False)
cda3055b 63 key_file.write(key.encode('ascii'))
09102e07
BS
64 key_file.flush()
65 os.rename(key_file.name, key_filepath)
66 key_file.close()
67 finally:
68 os.umask(old_umask)
69 if (key_file is not None) and (not key_file.closed):
70 key_file.close()
71 os.unlink(key_file.name)
72 __itsda_secret = key
73 _log.info("Saved new key for It's Dangerous")
74
11780855 75
09102e07
BS
76def setup_crypto():
77 global __itsda_secret
78 key_dir = mg_globals.app_config["crypto_path"]
79 key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin')
80 try:
81 load_key(key_filepath)
7f342c72 82 except IOError as error:
09102e07
BS
83 if error.errno != errno.ENOENT:
84 raise
85 create_key(key_dir, key_filepath)
5907154a 86
11780855 87
5907154a 88def get_timed_signer_url(namespace):
5a8aae3a
E
89 """
90 This gives a basic signing/verifying object.
91
92 The namespace makes sure signed tokens can't be used in
93 a different area. Like using a forgot-password-token as
94 a session cookie.
95
96 Basic usage:
97
98 .. code-block:: python
99
100 _signer = None
101 TOKEN_VALID_DAYS = 10
102 def setup():
103 global _signer
104 _signer = get_timed_signer_url("session cookie")
105 def create_token(obj):
106 return _signer.dumps(obj)
107 def parse_token(token):
108 # This might raise an exception in case
109 # of an invalid token, or an expired token.
110 return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600)
111
112 For more details see
113 http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer
114 """
5907154a
E
115 assert __itsda_secret is not None
116 return itsdangerous.URLSafeTimedSerializer(__itsda_secret,
117 salt=namespace)
4990b47c 118
c5eb24b8 119def random_string(length, alphabet=ALPHABET):
4990b47c 120 """ Returns a URL safe base64 encoded crypographically strong string """
c5eb24b8 121 base = len(alphabet)
4990b47c 122 rstring = ""
123 for i in range(length):
124 n = getrandbits(6) # 6 bytes = 2^6 = 64
125 n = divmod(n, base)[1]
126 rstring += alphabet[n]
127
128 return rstring