]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
Add OpenSSL version check and tests.
authorDaira Hopwood <daira@jacaranda.org>
Fri, 11 Apr 2014 16:17:06 +0000 (17:17 +0100)
committerDaira Hopwood <daira@jacaranda.org>
Fri, 11 Apr 2014 16:17:06 +0000 (17:17 +0100)
Signed-off-by: Daira Hopwood <daira@jacaranda.org>
src/allmydata/__init__.py
src/allmydata/_auto_deps.py
src/allmydata/test/test_version.py

index 4bafed474fb19e56e85efc44292cb937eb394295..94ad355c9728f2aac2237c21f2bae93e929aed60 100644 (file)
@@ -386,9 +386,71 @@ def check_all_requirements():
         except (ImportError, PackagingError), e:
             errors.append("%s: %s" % (e.__class__.__name__, e))
 
+    try:
+        from OpenSSL import SSL
+        check_openssl_version(SSL)
+    except PackagingError, e:
+        errors.append("%s: %s" % (e.__class__.__name__, e))
+    except Exception, e:
+        errors.append("Unable to check OpenSSL version due to %s: %s" % (e.__class__.__name__, e))
+
     if errors:
         raise PackagingError(get_error_string(errors, debug=True))
 
+MONTHS = {'Jan': 1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12}
+
+def parse_build_date(build_date):
+    day = int(build_date[0])
+    month = MONTHS[build_date[1]]
+    year = int(build_date[2])
+    return (year, month, day)
+
+def check_openssl_version(SSL):
+    openssl_version = SSL.SSLeay_version(SSL.SSLEAY_VERSION)
+    split_version = openssl_version.split(' ')
+
+    if len(split_version) < 2 or split_version[0] != 'OpenSSL':
+        raise PackagingError("could not understand OpenSSL version string %s" % (openssl_version,))
+
+    try:
+        components = split_version[1].split('.')
+        numeric_components = map(int, components[:2])
+        if len(components) > 2:
+            m = re.match(r'[0-9]*', components[2])
+            numeric_components += [int(m.group(0))]
+
+        if ((numeric_components == [0, 9, 8] and components[2] >= '8y') or
+            (numeric_components == [1, 0, 0] and components[2] >= '0l') or
+            (numeric_components == [1, 0, 1] and components[2] >= '1g') or
+            (numeric_components >= [1, 0, 2])):
+            return
+
+        if numeric_components == [1, 0, 1] and components[2] >= '1d':
+            # Unfortunately, Debian and Ubuntu patched the Heartbleed bug without bumping
+            # the version number or providing any other way to detect the patch status.
+            # (BAD! STOP DOING THIS!) So we allow versions 1.0.1d through 1.0.1f provided
+            # they were compiled on or after 6 April 2014.
+            if len(split_version) >= 5 and parse_build_date(split_version[2:5]) >= (2014, 4, 6):
+                return
+
+            # We also allow those versions if compiled with -DOPENSSL_NO_HEARTBEATS.
+            try:
+                openssl_cflags = SSL.SSLeay_version(SSL.SSLEAY_CFLAGS)
+            except Exception, e:
+                raise PackagingError("refusing to use %s which may be vulnerable to security bugs.\n"
+                                     "Unable to check compilation flags due to %s: %s\n"
+                                     "Please upgrade to OpenSSL 1.0.1g or later."
+                                     % (openssl_version, e.__class__.__name__, e))
+            else:
+                if '-DOPENSSL_NO_HEARTBEATS' in openssl_cflags.split(' '):
+                    return
+    except Exception, e:
+        pass
+
+    raise PackagingError("refusing to use %s which may be vulnerable to security bugs.\n"
+                         "Please upgrade to OpenSSL 1.0.1g or later." % (openssl_version,))
+
+
 check_all_requirements()
 
 
index b7be32128e6f4f44e85231df769ded65fc838d30..5c0794ff9220620e9c37727d7a29c992149a8210 100644 (file)
@@ -38,17 +38,18 @@ install_requires = [
     # * foolscap < 0.6.3 is incompatible with Twisted-11.1.0 and newer. Since
     #   current Twisted is 12.0, any build which needs twisted will grab a
     #   version that requires foolscap>=0.6.3
-    # * pyOpenSSL is required by foolscap for it (foolscap) to provide secure
+    #
+    "foolscap >= 0.6.3",
+
+    # * pyOpenSSL is required by foolscap for foolscap to provide secure
     #   connections. Foolscap doesn't reliably declare this dependency in a
     #   machine-readable way, so we need to declare a dependency on pyOpenSSL
-    #   ourselves. Tahoe-LAFS doesn't *really* depend directly on pyOpenSSL,
-    #   so if something changes in the relationship between foolscap and
-    #   pyOpenSSL, such as foolscap requiring a specific version of
-    #   pyOpenSSL, or foolscap switching from pyOpenSSL to a different crypto
-    #   library, we need to update this declaration here.
+    #   ourselves.
+    # * To check for OpenSSL security vulnerabilities such as Heartbleed,
+    #   we need to know the version of OpenSSL that pyOpenSSL is using. The
+    #   necessary API to do this requires pyOpenSSL >= 0.13.
     #
-    "foolscap >= 0.6.3",
-    "pyOpenSSL",
+    "pyOpenSSL >= 0.13",
 
     "Nevow >= 0.6.0",
 
index 296db0624c2f34f9adc666c716bad66747f68088..5e30d2a0f105a6d17a358d462054dcf1900a75b2 100644 (file)
@@ -1,12 +1,27 @@
 
 from twisted.trial import unittest
 
-from allmydata import check_requirement, cross_check, PackagingError
+from allmydata import check_requirement, cross_check, check_openssl_version, parse_build_date, PackagingError
 from allmydata.util.verlib import NormalizedVersion as V, \
                                   IrrationalVersionError, \
                                   suggest_normalized_version as suggest
 
 
+class MockSSL(object):
+    SSLEAY_VERSION = 0
+    SSLEAY_CFLAGS = 2
+
+    def __init__(self, version, compiled_without_heartbeats=False):
+        self.opts = {
+            self.SSLEAY_VERSION: version,
+            self.SSLEAY_CFLAGS: compiled_without_heartbeats and 'compiler: gcc -DOPENSSL_NO_HEARTBEATS'
+                                                             or 'compiler: gcc',
+        }
+
+    def SSLeay_version(self, which):
+        return self.opts[which]
+
+
 class CheckRequirement(unittest.TestCase):
     def test_check_requirement(self):
         check_requirement("setuptools >= 0.6c6", {"setuptools": ("0.6", "", None)})
@@ -106,6 +121,59 @@ class CheckRequirement(unittest.TestCase):
         self.failUnlessEqual(len(res), 1)
         self.failUnlessIn("but version '2.0'", res[0])
 
+    def test_check_openssl_version(self):
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL(""))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("NotOpenSSL"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL a.b.c"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.1.x"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.9"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.9.0"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.9.8"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.9.8", True))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.9.8x"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.0"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.0", True))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.0k"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1e 11 Feb 2013"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1e 5 Apr 2014"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1e 7 Abc 2014"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1e invalid_date"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.1e 7 Apr"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.10"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 0.10.0"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.0"))
+        self.failUnlessRaises(PackagingError, check_openssl_version, MockSSL("OpenSSL 1.0.0 1 Jan 2000"))
+
+        check_openssl_version(MockSSL("OpenSSL 0.9.8y"))
+        check_openssl_version(MockSSL("OpenSSL 0.9.8z"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.0l"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.0m"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1", True))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1e 11 Feb 2013", True))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1 7 Apr 2014"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1e 7 Apr 2014"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1g 1 Mar 2014"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1h 1 Jan 2015"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.1zzz"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.2"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.2a"))
+        check_openssl_version(MockSSL("OpenSSL 1.0.10a"))
+        check_openssl_version(MockSSL("OpenSSL 1.1"))
+        check_openssl_version(MockSSL("OpenSSL 1.1.0"))
+        check_openssl_version(MockSSL("OpenSSL 1.1.0a"))
+        check_openssl_version(MockSSL("OpenSSL 1.10"))
+        check_openssl_version(MockSSL("OpenSSL 1.10.10a"))
+        check_openssl_version(MockSSL("OpenSSL 2"))
+        check_openssl_version(MockSSL("OpenSSL 2.0.0 31 Dec 2020"))
+        check_openssl_version(MockSSL("OpenSSL 10.0.0 31 Dec 2099"))
+
+        self.failUnlessEqual(parse_build_date(['1', 'Jan', '2000']), (2000, 1, 1))
+        self.failUnlessEqual(parse_build_date(['5', 'Apr', '2014']), (2014, 4, 5))
+        self.failUnlessEqual(parse_build_date(['7', 'Apr', '2014']), (2014, 4, 7))
+        self.failUnlessRaises(Exception, parse_build_date, [])
+        self.failUnlessRaises(Exception, parse_build_date, ['1', 'Abc' '2000'])
+
 
 # based on https://bitbucket.org/tarek/distutilsversion/src/17df9a7d96ef/test_verlib.py