From ea182896b0082211479ab93e2a0b717ae7ddb4e4 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 1 Jun 2015 14:52:03 +0100 Subject: [PATCH] Add OpenSSL version check and tests. Signed-off-by: Daira Hopwood --- src/allmydata/__init__.py | 62 ++++++++++++++++++++++++++ src/allmydata/_auto_deps.py | 3 +- src/allmydata/test/test_version.py | 70 +++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/allmydata/__init__.py b/src/allmydata/__init__.py index 44f971f8..9883931d 100644 --- a/src/allmydata/__init__.py +++ b/src/allmydata/__init__.py @@ -400,9 +400,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() diff --git a/src/allmydata/_auto_deps.py b/src/allmydata/_auto_deps.py index 69a60815..eb469f6f 100644 --- a/src/allmydata/_auto_deps.py +++ b/src/allmydata/_auto_deps.py @@ -169,7 +169,8 @@ else: # not *directly* depend on pyOpenSSL. # # * pyOpenSSL >= 0.13 is needed in order to avoid -# . +# , and also to check the +# version of OpenSSL that pyOpenSSL is using. # # * pyOpenSSL >= 0.14 is built on the 'cryptography' package which depends # on 'cffi' (and indirectly several other packages). Unfortunately cffi diff --git a/src/allmydata/test/test_version.py b/src/allmydata/test/test_version.py index fc642777..9471016e 100644 --- a/src/allmydata/test/test_version.py +++ b/src/allmydata/test/test_version.py @@ -3,12 +3,27 @@ from pkg_resources import Requirement 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): self._check_success("setuptools >= 0.6c6", {"setuptools": ("0.6", "", None)}) @@ -118,6 +133,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 -- 2.45.2