From 40474f44f2a7c8916d894a49dfd88e5323e3e9b4 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 21 Apr 2014 16:20:29 +0100 Subject: [PATCH] Work in progress. Signed-off-by: Daira Hopwood --- src/allmydata/__init__.py | 57 +--------- src/allmydata/util/check_pyopenssl.py | 153 ++++++++++++++++++++++++++ test.crt | 24 ++++ test.key | 28 +++++ 4 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 src/allmydata/util/check_pyopenssl.py create mode 100644 test.crt create mode 100644 test.key diff --git a/src/allmydata/__init__.py b/src/allmydata/__init__.py index de2ec26d..a41967e5 100644 --- a/src/allmydata/__init__.py +++ b/src/allmydata/__init__.py @@ -400,10 +400,11 @@ def check_all_requirements(): except (ImportError, PackagingError), e: errors.append("%s: %s" % (e.__class__.__name__, e)) + from allmydata.util.check_pyopenssl import check_openssl_version, OpenSSLVersionError try: from OpenSSL import SSL check_openssl_version(SSL) - except PackagingError, e: + except OpenSSLVersionError, 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)) @@ -411,60 +412,6 @@ def check_all_requirements(): 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] and not components[2].startswith('2-beta')) or - (numeric_components >= [1, 0, 3])): - 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/util/check_pyopenssl.py b/src/allmydata/util/check_pyopenssl.py new file mode 100644 index 00000000..5829b9ba --- /dev/null +++ b/src/allmydata/util/check_pyopenssl.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +import re + + +class OpenSSLVersionError(EnvironmentError): + pass + + +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 OpenSSLVersionError("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))] + + #print numeric_components + 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] and not components[2].startswith('2-beta')) or + (numeric_components >= [1, 0, 3])): + return + except Exception, e: + #import traceback + #traceback.print_exc() + pass + else: + 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!) + + # Allow versions 1.0.1d through 1.0.1f if compiled with -DOPENSSL_NO_HEARTBEATS: + try: + openssl_cflags = SSL.SSLeay_version(SSL.SSLEAY_CFLAGS) + except Exception, e: + raise OpenSSLVersionError("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 + + # Also allow these versions if a vulnerability test passes (we do this only if + # the version and compiler flag checks are inconclusive, to minimize the chance + # for the test to break or give the wrong result somehow). + if not is_vulnerable(SSL): + return + + raise OpenSSLVersionError("refusing to use %s which may be vulnerable to security bugs.\n" + "Please upgrade to OpenSSL 1.0.1g or later." % (openssl_version,)) + + +# As simple as possible, but no simpler. +_CLIENT_HELLO = ( + '\x16' # Handshake + '\x03\x02' # TLS version 1.1 + '\x00\x34' # length of ClientHello + '\x01' # Handshake type (ClientHello) + '\x00\x00\x30' # length + '\x03\x02' # TLS version 1.1 + '\x53\x43\x5b\x90' # timestamp + '\x9d\x9b\x72\x0b\xbc\x0c\xbc\x2b\x92\xa8\x48\x97\xcf\xbd' + '\x39\x04\xcc\x16\x0a\x85\x03\x90\x9f\x77\x04\x33\xd4\xde' # client random + '\x00' # length of session ID (not resuming session) + '\x00\x02' # length of ciphersuites + '\x00\x0a' # TLS_RSA_WITH_3DES_EDE_CBC_SHA + '\x01' # length of compression methods + '\x00' # null compression + '\x00\x05' # length of extensions + '\x00\x0f\x00\x01\x01' # heartbeat extension +) + +_HEARTBEAT = ( + '\x18' # Heartbeat + '\x03\x02' # TLS version 1.1 + '\x00\x03' # length + '\x01' # heartbeat request + '\x10\x00' # payload length (4096 bytes) +) +_HEARTBEAT2 = ( + '\x18' # Heartbeat + '\x03\x02' # TLS version 1.1 + '\x00\x23' # length + '\x01' # heartbeat request + '\x00\x01' # payload length (0 bytes) +) + '\x00'*33 + +def is_vulnerable(SSL): + def verify_callback(connection, x509, errnum, errdepth, ok): + return ok + + ctx = SSL.Context(SSL.TLSv1_1_METHOD) + ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3) + ctx.use_certificate_file('test.crt') + ctx.use_privatekey_file('test.key') + ctx.set_cipher_list('DES-CBC3-SHA') # TLS_RSA_WITH_3DES_EDE_CBC_SHA + ctx.set_verify(SSL.VERIFY_NONE, verify_callback) + + server = SSL.Connection(ctx, None) + server.set_accept_state() + server.bio_write(_CLIENT_HELLO + _HEARTBEAT + _HEARTBEAT2) + + server_response = bytearray() + try: + server.do_handshake() + except SSL.WantReadError: + pass # this is expected + + while True: + try: + server_response += server.bio_read(32768) + except SSL.WantReadError: + break + print repr(server_response) + print len(server_response) + + # Fortunately we don't need to parse anything complicated, just the outer layer. + i = 0 + while i+5 <= len(server_response): + record_type = server_response[i] + # we don't care about the record version + record_length = (server_response[i+3]<<8) + server_response[i+4] + print record_type, record_length + if record_length == 0: + # avoid infinite loop + return True + if record_type == 0x18 and record_length > 3: + # longer than expected heartbeat response => vulnerable + return True + i += 5 + record_length + + if i < len(server_response): + print "hmm" + + return False + + +if __name__ == '__main__': + from OpenSSL import SSL + #check_openssl_version(SSL) + #print "Not vulnerable." + print is_vulnerable(SSL) + diff --git a/test.crt b/test.crt new file mode 100644 index 00000000..e7b626a5 --- /dev/null +++ b/test.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIJAKZLXxbqxONXMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYD +VQQGEwJVSzEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRMwEQYDVQQHDApN +YW5jaGVzdGVyMRswGQYDVQQKDBJKYWNhcmFuZGEgU29mdHdhcmUxFjAUBgNVBAMM +DWphY2FyYW5kYS5vcmcxIjAgBgkqhkiG9w0BCQEWE2RhaXJhQGphY2FyYW5kYS5v +cmcwHhcNMTQwNDE5MTkzNTUyWhcNMTQwNTE5MTkzNTUyWjCBmDELMAkGA1UEBhMC +VUsxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjETMBEGA1UEBwwKTWFuY2hl +c3RlcjEbMBkGA1UECgwSSmFjYXJhbmRhIFNvZnR3YXJlMRYwFAYDVQQDDA1qYWNh +cmFuZGEub3JnMSIwIAYJKoZIhvcNAQkBFhNkYWlyYUBqYWNhcmFuZGEub3JnMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqUUgLyuUie+lmdrLfeUmkbPW +282f7j6WRx0I0YdMQh6Qih4WvA+AY/l1rNI2o4h+vqobs8rBVxjE9jVSZYZDUbs6 +cNSfhm9VDwX5KfZeFM9XPpL6Gc2DX9Ay6ucdE17d10JYRdnyE5iaHow137SdykBM +QAccoL4yTB+f4kKx3qE6AEXEkATc9PUloPdZ7bNkClzqjyTAFeS2Vci5ubdkrIXZ +YNUrdrTtakJVZiu1CL2xnPnzyCxrF2/VuX//e77CBwk8qo/nFudkajoXN66bMeW0 +1YreOuu/OBvqjwb3aW+45h7/BNo87kh6brhZpswOqRJDvlnh49lpHBnj8HgJlQID +AQABo1AwTjAdBgNVHQ4EFgQUHbuzLZ2vYHmjjntSfHw/PstZgFwwHwYDVR0jBBgw +FoAUHbuzLZ2vYHmjjntSfHw/PstZgFwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B +AQUFAAOCAQEAUxtcB0kfWQBJDw189F/+KE43zQbD2vbGkoZBCQHk200nMPXb3IIc +entP25YyX9xJp54ZlnMho6rJKxKKWOOL+LmQr8WwwF74x8+VpsmIa09WDfFGQGMs +NWCJaLbKi+lv/++6zvp1b8/n8G4/JgOczHh6dptKr5o/NRBEClwwgBnlpth6Mtxh +5TehHnZKyBUIvhY5THaNgZYQ6tZ4MPawXsdlsq0umQwn9GlZKEE0XdoRKShOxZtg +v04vmHAZHdVB53GGBnXaRVqp005I3bsdnCvgCrCKjp/VXp2SSscYuvfCjQUlMTiv +9VDNo9Tkie2pm154E+bLADwePQhvbAqjRA== +-----END CERTIFICATE----- diff --git a/test.key b/test.key new file mode 100644 index 00000000..13042cbb --- /dev/null +++ b/test.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpRSAvK5SJ76WZ +2st95SaRs9bbzZ/uPpZHHQjRh0xCHpCKHha8D4Bj+XWs0jajiH6+qhuzysFXGMT2 +NVJlhkNRuzpw1J+Gb1UPBfkp9l4Uz1c+kvoZzYNf0DLq5x0TXt3XQlhF2fITmJoe +jDXftJ3KQExABxygvjJMH5/iQrHeoToARcSQBNz09SWg91nts2QKXOqPJMAV5LZV +yLm5t2Sshdlg1St2tO1qQlVmK7UIvbGc+fPILGsXb9W5f/97vsIHCTyqj+cW52Rq +Ohc3rpsx5bTVit466784G+qPBvdpb7jmHv8E2jzuSHpuuFmmzA6pEkO+WeHj2Wkc +GePweAmVAgMBAAECggEBAJdy1tn9hxhnR+bBoWJR/dFNdAcUEm0qqHCWJMa75Fog +WLiLNVIsGHbawxPOvb5RqP1U3BZgad5QtiGIEeHuYBsnhqFfTCAPSq53bLjDyueZ +n/jHrmgZvdqjQHt2L0iej6RCoxJUb0hjODnuv2EZjDhikRaUhmO36O69OONLQzPp +3mMHhSZCh8k8CpD4uEELohAPG7CohLY5dt0jizEQw/GbhqWAlSVpzc0AlOtv0Oy7 +JzEMIfpJmEvRCLzPhGlY0SxGiDQmQ6o4GBTfuxKWpb9ZA77YyXMQ2mM3lW9/SJ5s +FSY7DWJp1z4PR71Ph0LjT/EiAmbIZ/0K+koA/OxCQp0CgYEA2C6E+Vpg/jZtyW7/ +G0YYA4Usj21bPgfN38JHgNncLbEq+LMLMmmGbE3HweEjnREBoErdyQBsUzmrFGq+ +VjxS9GTjJ/630UrVlHXl02hhgpaBRqjB5GatcuGHHzItgEFWvKC853BYPntsIMyy +XPu2cckqRibXl/qNhzOf2QyYXccCgYEAyHKb0ZSL1f8CN0KVuWAQeQ2kgYjzfQW4 +GDkGdkBNZ0Q26kMDM64wmVjN66leo/pakwdyxJQRwha4NamVF/dUz3myNYsIYGYJ +V12WC5gBLU9SqBfD/ARah7agJIoAlBuall7woCDqrf2wHKXxV8BHMSrVQkAANHx0 +wQiMCNUujcMCgYB/d/aaXQtB+fAETTmI6Gyybq0WqSGa0tk2rgShWlR0cLnoyRG6 +GPTVGYvGqyznqSZvUJWztlcpP6C7ujEfAhTb3D0A0TWr59dF4bqxTCPq7zms40bo +mQ5+5bJZ16lyrZQqRxD72od92CKquGgXYahzMW2GzdJj363h4bPINKAG8wKBgAFi +sWsIcNMA2T4SisBmRpbfnkR68tvpXzVjp4THuwE8unhyECKaUKGuuHWpjQrrHxcn +FQcA0wKZb5qHWo142zA38iBmnp8z9VqSPghEe7WZd8PLrkIesZWAkKjP/2MGsdHa +RCWHNLgRKdd42is2HAfd+O1lTdsPBSuUaItjFdhLAoGBANQwRy2HIxo6tTlxPeJQ +DelNCaLWmKfMKQhG2SFLc08Au4XwxG8/wCddFK7Ohmb1HCwRr5rq4ItO5yhfomZU +kTtRsVb4LpiI8Fb+anG6pLQPaZK/CJiEb7Dwb/6DDMKAVcdZQnBEiIBQ5ELudq0n +t79zujWHlJ9FXZKmNkOQpyAm +-----END PRIVATE KEY----- -- 2.45.2