Log message for revision 128158: LP #1071067: Use a stronger random number generator and a constant time comparison function.
Changed: U Zope/branches/2.12/doc/CHANGES.rst U Zope/branches/2.12/setup.py U Zope/branches/2.12/src/AccessControl/AuthEncoding.py U Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py U Zope/branches/2.12/versions.cfg -=- Modified: Zope/branches/2.12/doc/CHANGES.rst =================================================================== --- Zope/branches/2.12/doc/CHANGES.rst 2012-10-31 14:10:50 UTC (rev 128157) +++ Zope/branches/2.12/doc/CHANGES.rst 2012-10-31 14:13:07 UTC (rev 128158) @@ -5,9 +5,12 @@ Change information for previous versions of Zope can be found at http://docs.zope.org/zope2/releases/. -2.12.26 (unreleased) +2.12.26 (2012-10-31) -------------------- +- LP #1071067: Use a stronger random number generator and a constant time + comparison function. + - LP #930812: Scrub headers a bit more. 2.12.25 (2012-09-18) Modified: Zope/branches/2.12/setup.py =================================================================== --- Zope/branches/2.12/setup.py 2012-10-31 14:10:50 UTC (rev 128157) +++ Zope/branches/2.12/setup.py 2012-10-31 14:13:07 UTC (rev 128158) @@ -16,7 +16,7 @@ from setuptools import setup, find_packages, Extension setup(name='Zope2', - version='2.12.26dev', + version='2.12.26', url='http://www.zope.org', license='ZPL 2.1', description='Zope2 application server / web framework', Modified: Zope/branches/2.12/src/AccessControl/AuthEncoding.py =================================================================== --- Zope/branches/2.12/src/AccessControl/AuthEncoding.py 2012-10-31 14:10:50 UTC (rev 128157) +++ Zope/branches/2.12/src/AccessControl/AuthEncoding.py 2012-10-31 14:13:07 UTC (rev 128158) @@ -11,18 +11,59 @@ # ############################################################################## -__version__='$Revision: 1.9 $'[11:-2] +import binascii +from binascii import b2a_base64, a2b_base64 +from hashlib import sha1 as sha +from hashlib import sha256 +from os import getpid +import time +# Use the system PRNG if possible +import random try: - from hashlib import sha1 as sha -except: - from sha import new as sha + random = random.SystemRandom() + using_sysrandom = True +except NotImplementedError: + using_sysrandom = False -import binascii -from binascii import b2a_base64, a2b_base64 -from random import choice, randrange +def _reseed(): + if not using_sysrandom: + # This is ugly, and a hack, but it makes things better than + # the alternative of predictability. This re-seeds the PRNG + # using a value that is hard for an attacker to predict, every + # time a random string is required. This may change the + # properties of the chosen random sequence slightly, but this + # is better than absolute predictability. + random.seed(sha256( + "%s%s%s" % (random.getstate(), time.time(), getpid()) + ).digest()) + +def _choice(c): + _reseed() + return random.choice(c) + + +def _randrange(r): + _reseed() + return random.randrange(r) + + +def constant_time_compare(val1, val2): + """ + Returns True if the two strings are equal, False otherwise. + + The time taken is independent of the number of characters that match. + """ + if len(val1) != len(val2): + return False + result = 0 + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) + return result == 0 + + class PasswordEncryptionScheme: # An Interface def encrypt(pw): @@ -40,12 +81,14 @@ _schemes = [] + def registerScheme(id, s): ''' Registers an LDAP password encoding scheme. ''' _schemes.append((id, '{%s}' % id, s)) + def listSchemes(): r = [] for id, prefix, scheme in _schemes: @@ -67,7 +110,7 @@ # All 256 characters are available. salt = '' for n in range(7): - salt += chr(randrange(256)) + salt += chr(_randrange(256)) return salt def encrypt(self, pw): @@ -83,7 +126,7 @@ return 0 salt = ref[20:] compare = b2a_base64(sha(attempt + salt).digest() + salt)[:-1] - return (compare == reference) + return constant_time_compare(compare, reference) registerScheme('SSHA', SSHADigestScheme()) @@ -95,7 +138,7 @@ def validate(self, reference, attempt): compare = b2a_base64(sha(attempt).digest())[:-1] - return (compare == reference) + return constant_time_compare(compare, reference) registerScheme('SHA', SHADigestScheme()) @@ -114,14 +157,14 @@ choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789./") - return choice(choices) + choice(choices) + return _choice(choices) + _choice(choices) def encrypt(self, pw): return crypt(pw, self.generate_salt()) def validate(self, reference, attempt): a = crypt(attempt, reference[:2]) - return (a == reference) + return constant_time_compare(a, reference) registerScheme('CRYPT', CryptDigestScheme()) @@ -144,7 +187,7 @@ def validate(self, reference, attempt): a = self.encrypt(attempt) - return (a == reference) + return constant_time_compare(a, reference) registerScheme('MYSQL', MySQLDigestScheme()) @@ -158,8 +201,9 @@ if reference[:lp] == prefix: return scheme.validate(reference[lp:], attempt) # Assume cleartext. - return (reference == attempt) + return constant_time_compare(reference, attempt) + def is_encrypted(pw): for id, prefix, scheme in _schemes: lp = len(prefix) @@ -167,12 +211,13 @@ return 1 return 0 + def pw_encrypt(pw, encoding='SSHA'): """Encrypt the provided plain text password using the encoding if provided and return it in an LDAP-style representation.""" for id, prefix, scheme in _schemes: if encoding == id: return prefix + scheme.encrypt(pw) - raise ValueError, 'Not supported: %s' % encoding + raise ValueError('Not supported: %s' % encoding) pw_encode = pw_encrypt # backward compatibility Modified: Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py =================================================================== --- Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py 2012-10-31 14:10:50 UTC (rev 128157) +++ Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py 2012-10-31 14:13:07 UTC (rev 128158) @@ -1,5 +1,5 @@ ############################################################################ -# +# # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, @@ -13,8 +13,9 @@ import binascii from cgi import escape +from hashlib import sha256 import logging -import random +import os import re import string import sys @@ -63,6 +64,29 @@ LOG = logging.getLogger('Zope.BrowserIdManager') +# Use the system PRNG if possible +import random +try: + random = random.SystemRandom() + using_sysrandom = True +except NotImplementedError: + using_sysrandom = False + + +def _randint(start, end): + if not using_sysrandom: + # This is ugly, and a hack, but it makes things better than + # the alternative of predictability. This re-seeds the PRNG + # using a value that is hard for an attacker to predict, every + # time a random string is required. This may change the + # properties of the chosen random sequence slightly, but this + # is better than absolute predictability. + random.seed(sha256( + "%s%s%s" % (random.getstate(), time.time(), os.getpid()) + ).digest()) + return random.randint(start, end) + + def constructBrowserIdManager( self, id=BROWSERID_MANAGER_NAME, title='', idname='_ZopeId', location=('cookies', 'form'), cookiepath='/', cookiedomain='', @@ -558,7 +582,7 @@ return None -def getNewBrowserId(randint=random.randint, maxint=99999999): +def getNewBrowserId(randint=_randint, maxint=99999999): """ Returns 19-character string browser id 'AAAAAAAABBBBBBBB' where: @@ -573,5 +597,4 @@ An example is: 89972317A0C3EHnUi90w """ - return '%08i%s' % (randint(0, maxint-1), getB64TStamp()) - + return '%08i%s' % (randint(0, maxint - 1), getB64TStamp()) Modified: Zope/branches/2.12/versions.cfg =================================================================== --- Zope/branches/2.12/versions.cfg 2012-10-31 14:10:50 UTC (rev 128157) +++ Zope/branches/2.12/versions.cfg 2012-10-31 14:13:07 UTC (rev 128158) @@ -2,7 +2,7 @@ versions = versions [versions] -Zope2 = +Zope2 = 2.12.26 Acquisition = 2.13.8 buildout.dumppickedversions = 0.4 ClientForm = 0.2.10 _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org https://mail.zope.org/mailman/listinfo/zope-checkins