summaryrefslogtreecommitdiff
path: root/pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch
diff options
context:
space:
mode:
Diffstat (limited to 'pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch')
-rw-r--r--pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch258
1 files changed, 258 insertions, 0 deletions
diff --git a/pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch b/pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch
new file mode 100644
index 0000000..e118e22
--- /dev/null
+++ b/pipermail/pycrypto/attachments/20131017/05f461ef/attachment-0001.patch
@@ -0,0 +1,258 @@
+Description: Fix CVE-2013-1445
+ In PyCrypto before v2.6.1, the Crypto.Random pseudo-random number generator
+ (PRNG) exhibits a race condition that may cause it to generate the same
+ 'random' output in multiple processes that are forked from each other.
+ Depending on the application, this could reveal sensitive information or
+ cryptographic keys to remote attackers.
+ .
+ An application may be affected if, within 100 milliseconds, it performs the
+ following steps:
+ .
+ 1. Read from the Crypto.Random PRNG, causing an internal reseed;
+ 2. Fork the process and invoke Crypto.Random.atfork() in the child;
+ 3. Read from the Crypto.Random PRNG again, in at least two different
+ processes (parent and child, or multiple children).
+ .
+ Only applications that invoke Crypto.Random.atfork() and perform the above
+ steps are affected by this issue. Other applications are unaffected.
+ .
+ Note: Some PyCrypto functions, such as key generation and PKCS#1-related
+ functions, implicitly read from the Crypto.Random PRNG.
+Origin: upstream,
+ https://github.com/dlitz/pycrypto/commit/19dcf7b15d61b7dc1a125a367151de40df6ef175
+Last-Update: 2013-10-17
+
+Index: python-crypto-2.1.0/lib/Crypto/Random/Fortuna/FortunaAccumulator.py
+===================================================================
+--- python-crypto-2.1.0.orig/lib/Crypto/Random/Fortuna/FortunaAccumulator.py 2013-10-15 08:37:01.000000000 -0700
++++ python-crypto-2.1.0/lib/Crypto/Random/Fortuna/FortunaAccumulator.py 2013-10-15 08:37:29.000000000 -0700
+@@ -103,6 +103,15 @@
+ self.pools = [FortunaPool() for i in range(32)] # 32 pools
+ assert(self.pools[0] is not self.pools[1])
+
++ def _forget_last_reseed(self):
++ # This is not part of the standard Fortuna definition, and using this
++ # function frequently can weaken Fortuna's ability to resist a state
++ # compromise extension attack, but we need this in order to properly
++ # implement Crypto.Random.atfork(). Otherwise, forked child processes
++ # might continue to use their parent's PRNG state for up to 100ms in
++ # some cases. (e.g. CVE-2013-1445)
++ self.last_reseed = None
++
+ def random_data(self, bytes):
+ current_time = time.time()
+ if self.last_reseed > current_time:
+Index: python-crypto-2.1.0/lib/Crypto/Random/_UserFriendlyRNG.py
+===================================================================
+--- python-crypto-2.1.0.orig/lib/Crypto/Random/_UserFriendlyRNG.py 2013-10-15 08:37:01.000000000 -0700
++++ python-crypto-2.1.0/lib/Crypto/Random/_UserFriendlyRNG.py 2013-10-15 08:37:29.000000000 -0700
+@@ -88,9 +88,24 @@
+ """Initialize the random number generator and seed it with entropy from
+ the operating system.
+ """
++
++ # Save the pid (helps ensure that Crypto.Random.atfork() gets called)
+ self._pid = os.getpid()
++
++ # Collect entropy from the operating system and feed it to
++ # FortunaAccumulator
+ self._ec.reinit()
+
++ # Override FortunaAccumulator's 100ms minimum re-seed interval. This
++ # is necessary to avoid a race condition between this function and
++ # self.read(), which that can otherwise cause forked child processes to
++ # produce identical output. (e.g. CVE-2013-1445)
++ #
++ # Note that if this function can be called frequently by an attacker,
++ # (and if the bits from OSRNG are insufficiently random) it will weaken
++ # Fortuna's ability to resist a state compromise extension attack.
++ self._fa._forget_last_reseed()
++
+ def close(self):
+ self.closed = True
+ self._osrng = None
+Index: python-crypto-2.1.0/lib/Crypto/SelfTest/Random/__init__.py
+===================================================================
+--- python-crypto-2.1.0.orig/lib/Crypto/SelfTest/Random/__init__.py 2013-10-15 08:37:01.000000000 -0700
++++ python-crypto-2.1.0/lib/Crypto/SelfTest/Random/__init__.py 2013-10-15 08:37:29.000000000 -0700
+@@ -32,6 +32,7 @@
+ import OSRNG; tests += OSRNG.get_tests(config=config)
+ import test_random; tests += test_random.get_tests(config=config)
+ import test_rpoolcompat; tests += test_rpoolcompat.get_tests(config=config)
++ import test__UserFriendlyRNG; tests += test__UserFriendlyRNG.get_tests(config=config)
+ return tests
+
+ if __name__ == '__main__':
+Index: python-crypto-2.1.0/lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ python-crypto-2.1.0/lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py 2013-10-15 08:38:29.000000000 -0700
+@@ -0,0 +1,168 @@
++# -*- coding: utf-8 -*-
++# Self-tests for the user-friendly Crypto.Random interface
++#
++# Written in 2013 by Dwayne C. Litzenberger <dlitz@dlitz.net>
++#
++# ===================================================================
++# The contents of this file are dedicated to the public domain. To
++# the extent that dedication to the public domain is not available,
++# everyone is granted a worldwide, perpetual, royalty-free,
++# non-exclusive license to exercise all rights associated with the
++# contents of this file for any purpose whatsoever.
++# No rights are reserved.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++# ===================================================================
++
++"""Self-test suite for generic Crypto.Random stuff """
++
++from __future__ import nested_scopes
++
++__revision__ = "$Id$"
++
++import binascii
++import pprint
++import unittest
++import os
++import time
++import sys
++
++try:
++ import multiprocessing
++except ImportError:
++ multiprocessing = None
++
++import Crypto.Random._UserFriendlyRNG
++import Crypto.Random.random
++
++class RNGForkTest(unittest.TestCase):
++
++ def _get_reseed_count(self):
++ """
++ Get `FortunaAccumulator.reseed_count`, the global count of the
++ number of times that the PRNG has been reseeded.
++ """
++ rng_singleton = Crypto.Random._UserFriendlyRNG._get_singleton()
++ rng_singleton._lock.acquire()
++ try:
++ return rng_singleton._fa.reseed_count
++ finally:
++ rng_singleton._lock.release()
++
++ def runTest(self):
++ # Regression test for CVE-2013-1445. We had a bug where, under the
++ # right conditions, two processes might see the same random sequence.
++
++ if sys.platform.startswith('win'): # windows can't fork
++ assert not hasattr(os, 'fork') # ... right?
++ return
++
++ # Wait 150 ms so that we don't trigger the rate-limit prematurely.
++ time.sleep(0.15)
++
++ reseed_count_before = self._get_reseed_count()
++
++ # One or both of these calls together should trigger a reseed right here.
++ Crypto.Random._UserFriendlyRNG._get_singleton().reinit()
++ Crypto.Random.get_random_bytes(1)
++
++ reseed_count_after = self._get_reseed_count()
++ self.assertNotEqual(reseed_count_before, reseed_count_after) # sanity check: test should reseed parent before forking
++
++ rfiles = []
++ for i in range(10):
++ rfd, wfd = os.pipe()
++ if os.fork() == 0:
++ # child
++ os.close(rfd)
++ f = os.fdopen(wfd, "wb")
++
++ Crypto.Random.atfork()
++
++ data = Crypto.Random.get_random_bytes(16)
++
++ f.write(data)
++ f.close()
++ os._exit(0)
++ # parent
++ os.close(wfd)
++ rfiles.append(os.fdopen(rfd, "rb"))
++
++ results = []
++ results_dict = {}
++ for f in rfiles:
++ data = binascii.hexlify(f.read())
++ results.append(data)
++ results_dict[data] = 1
++ f.close()
++
++ if len(results) != len(results_dict.keys()):
++ raise AssertionError("RNG output duplicated across fork():\n%s" %
++ (pprint.pformat(results)))
++
++
++# For RNGMultiprocessingForkTest
++def _task_main(q):
++ a = Crypto.Random.get_random_bytes(16)
++ time.sleep(0.1) # wait 100 ms
++ b = Crypto.Random.get_random_bytes(16)
++ q.put(binascii.b2a_hex(a))
++ q.put(binascii.b2a_hex(b))
++ q.put(None) # Wait for acknowledgment
++
++
++class RNGMultiprocessingForkTest(unittest.TestCase):
++
++ def runTest(self):
++ # Another regression test for CVE-2013-1445. This is basically the
++ # same as RNGForkTest, but less compatible with old versions of Python,
++ # and a little easier to read.
++
++ n_procs = 5
++ manager = multiprocessing.Manager()
++ queues = [manager.Queue(1) for i in range(n_procs)]
++
++ # Reseed the pool
++ time.sleep(0.15)
++ Crypto.Random._UserFriendlyRNG._get_singleton().reinit()
++ Crypto.Random.get_random_bytes(1)
++
++ # Start the child processes
++ pool = multiprocessing.Pool(processes=n_procs, initializer=Crypto.Random.atfork)
++ map_result = pool.map_async(_task_main, queues)
++
++ # Get the results, ensuring that no pool processes are reused.
++ aa = [queues[i].get(30) for i in range(n_procs)]
++ bb = [queues[i].get(30) for i in range(n_procs)]
++ res = list(zip(aa, bb))
++
++ # Shut down the pool
++ map_result.get(30)
++ pool.close()
++ pool.join()
++
++ # Check that the results are unique
++ if len(set(aa)) != len(aa) or len(set(res)) != len(res):
++ raise AssertionError("RNG output duplicated across fork():\n%s" %
++ (pprint.pformat(res),))
++
++
++def get_tests(config={}):
++ tests = []
++ tests += [RNGForkTest()]
++ if multiprocessing is not None:
++ tests += [RNGMultiprocessingForkTest()]
++ return tests
++
++if __name__ == '__main__':
++ suite = lambda: unittest.TestSuite(get_tests())
++ unittest.main(defaultTest='suite')
++
++# vim:set ts=4 sw=4 sts=4 expandtab: