]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
Work in progress.
authorDaira Hopwood <daira@jacaranda.org>
Mon, 21 Apr 2014 15:20:29 +0000 (16:20 +0100)
committerDaira Hopwood <daira@jacaranda.org>
Mon, 21 Apr 2014 15:20:29 +0000 (16:20 +0100)
Signed-off-by: Daira Hopwood <daira@jacaranda.org>
src/allmydata/__init__.py
src/allmydata/util/check_pyopenssl.py [new file with mode: 0644]
test.crt [new file with mode: 0644]
test.key [new file with mode: 0644]

index 4408156660eb6db3e4999da4759fa77074922ed7..2eeae91c97274e6c455c4bdc948234a93257053f 100644 (file)
@@ -386,10 +386,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))
@@ -397,60 +398,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 (file)
index 0000000..5829b9b
--- /dev/null
@@ -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 (file)
index 0000000..e7b626a
--- /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 (file)
index 0000000..13042cb
--- /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-----