From 5907154a593bf5fc02c1e0fbc8afe683ac7d3602 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 22 Mar 2013 18:46:47 +0100 Subject: Basic itsdangerous infrastructure. Implement the basic infrastructure for using itsdangerous in mediagoblin. Usage instructions will follow. --- mediagoblin/tools/crypto.py | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 mediagoblin/tools/crypto.py (limited to 'mediagoblin/tools/crypto.py') diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py new file mode 100644 index 00000000..46752b55 --- /dev/null +++ b/mediagoblin/tools/crypto.py @@ -0,0 +1,55 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os.path +import logging +import random +import itsdangerous +from mediagoblin import mg_globals + +_log = logging.getLogger(__name__) + + +# Use the system (hardware-based) random number generator if it exists. +# -- this optimization is lifted from Django +if hasattr(random, 'SystemRandom'): + getrandbits = random.SystemRandom().getrandbits +else: + getrandbits = random.getrandbits + + +__itsda_secret = None + + +def setup_crypto(): + global __itsda_secret + dir = mg_globals.app_config["crypto_path"] + if not os.path.isdir(dir): + _log.info("Creating %s", dir) + os.makedirs(dir) + name = os.path.join(dir, "itsdangeroussecret.bin") + if os.path.exists(name): + __itsda_secret = file(name, "r").read() + else: + __itsda_secret = str(getrandbits(192)) + file(name, "w").write(__itsda_secret) + _log.info("Created %s", name) + + +def get_timed_signer_url(namespace): + assert __itsda_secret is not None + return itsdangerous.URLSafeTimedSerializer(__itsda_secret, + salt=namespace) -- cgit v1.2.3 From 5a8aae3abac43fdebe6818330ad3c5d951de42b9 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 22 Mar 2013 19:09:19 +0100 Subject: Docs for get_timed_signer_url. --- mediagoblin/tools/crypto.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'mediagoblin/tools/crypto.py') diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 46752b55..3294f135 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -50,6 +50,32 @@ def setup_crypto(): def get_timed_signer_url(namespace): + """ + This gives a basic signing/verifying object. + + The namespace makes sure signed tokens can't be used in + a different area. Like using a forgot-password-token as + a session cookie. + + Basic usage: + + .. code-block:: python + + _signer = None + TOKEN_VALID_DAYS = 10 + def setup(): + global _signer + _signer = get_timed_signer_url("session cookie") + def create_token(obj): + return _signer.dumps(obj) + def parse_token(token): + # This might raise an exception in case + # of an invalid token, or an expired token. + return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600) + + For more details see + http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer + """ assert __itsda_secret is not None return itsdangerous.URLSafeTimedSerializer(__itsda_secret, salt=namespace) -- cgit v1.2.3 From bb530c44450b88c3584f4e50119857599e5a5f40 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 22 Mar 2013 19:12:55 +0100 Subject: Improve fs security for itsdangerous secret. Set mode 700 on the directory, mode 600 on the file. --- mediagoblin/tools/crypto.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'mediagoblin/tools/crypto.py') diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 3294f135..0fb2ba2e 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -38,14 +38,18 @@ def setup_crypto(): global __itsda_secret dir = mg_globals.app_config["crypto_path"] if not os.path.isdir(dir): - _log.info("Creating %s", dir) os.makedirs(dir) + os.chmod(dir, 0700) + _log.info("Created %s", dir) name = os.path.join(dir, "itsdangeroussecret.bin") if os.path.exists(name): __itsda_secret = file(name, "r").read() else: __itsda_secret = str(getrandbits(192)) - file(name, "w").write(__itsda_secret) + f = file(name, "w") + f.write(__itsda_secret) + f.close() + os.chmod(name, 0600) _log.info("Created %s", name) -- cgit v1.2.3 From 09102e0767d3c24e0be7988dc22113993cbd3d3d Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Sun, 24 Mar 2013 16:27:20 -0400 Subject: Harden It's Dangerous key management. The previous code was theoretically subject to timing attacks, where an attacker could read the key in between the time it was saved to the file and when the chmod happened. This version prevents that by using umasks to ensure the files always have the right permissions. This version also avoids using a key that cannot be saved due to some system setup bug. --- mediagoblin/tools/crypto.py | 65 +++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 20 deletions(-) (limited to 'mediagoblin/tools/crypto.py') diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 0fb2ba2e..55811aea 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -14,10 +14,12 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import os.path +import errno +import itsdangerous import logging +import os.path import random -import itsdangerous +import tempfile from mediagoblin import mg_globals _log = logging.getLogger(__name__) @@ -25,33 +27,56 @@ _log = logging.getLogger(__name__) # Use the system (hardware-based) random number generator if it exists. # -- this optimization is lifted from Django -if hasattr(random, 'SystemRandom'): +try: getrandbits = random.SystemRandom().getrandbits -else: +except AttributeError: getrandbits = random.getrandbits __itsda_secret = None -def setup_crypto(): +def load_key(filename): global __itsda_secret - dir = mg_globals.app_config["crypto_path"] - if not os.path.isdir(dir): - os.makedirs(dir) - os.chmod(dir, 0700) - _log.info("Created %s", dir) - name = os.path.join(dir, "itsdangeroussecret.bin") - if os.path.exists(name): - __itsda_secret = file(name, "r").read() - else: - __itsda_secret = str(getrandbits(192)) - f = file(name, "w") - f.write(__itsda_secret) - f.close() - os.chmod(name, 0600) - _log.info("Created %s", name) + key_file = open(filename) + try: + __itsda_secret = key_file.read() + finally: + key_file.close() +def create_key(key_dir, key_filepath): + global __itsda_secret + old_umask = os.umask(077) + key_file = None + try: + if not os.path.isdir(key_dir): + os.makedirs(key_dir) + _log.info("Created %s", dirname) + key = str(getrandbits(192)) + key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin', + delete=False) + key_file.write(key) + key_file.flush() + os.rename(key_file.name, key_filepath) + key_file.close() + finally: + os.umask(old_umask) + if (key_file is not None) and (not key_file.closed): + key_file.close() + os.unlink(key_file.name) + __itsda_secret = key + _log.info("Saved new key for It's Dangerous") + +def setup_crypto(): + global __itsda_secret + key_dir = mg_globals.app_config["crypto_path"] + key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin') + try: + load_key(key_filepath) + except IOError, error: + if error.errno != errno.ENOENT: + raise + create_key(key_dir, key_filepath) def get_timed_signer_url(namespace): """ -- cgit v1.2.3 From 11780855da05b1e1b3bb2db7655290bbe7430e24 Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 9 Apr 2013 22:37:10 +0200 Subject: Fix left over from variable renaming. --- mediagoblin/tools/crypto.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'mediagoblin/tools/crypto.py') diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 55811aea..1379d21b 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -44,6 +44,7 @@ def load_key(filename): finally: key_file.close() + def create_key(key_dir, key_filepath): global __itsda_secret old_umask = os.umask(077) @@ -51,7 +52,7 @@ def create_key(key_dir, key_filepath): try: if not os.path.isdir(key_dir): os.makedirs(key_dir) - _log.info("Created %s", dirname) + _log.info("Created %s", key_dir) key = str(getrandbits(192)) key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin', delete=False) @@ -67,6 +68,7 @@ def create_key(key_dir, key_filepath): __itsda_secret = key _log.info("Saved new key for It's Dangerous") + def setup_crypto(): global __itsda_secret key_dir = mg_globals.app_config["crypto_path"] @@ -78,6 +80,7 @@ def setup_crypto(): raise create_key(key_dir, key_filepath) + def get_timed_signer_url(namespace): """ This gives a basic signing/verifying object. -- cgit v1.2.3