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