Parcourir la source

* Added test case for SSL verification of peer failing
* ''Really'' making this the 0.1.0 release this time :)

git-svn-id: http://proj.badc.rl.ac.uk/svn/ndg-security/trunk/ndg_httpsclient@7993 051b1e3e-aa0c-0410-b6c2-bfbade6052be

pjkersha il y a 13 ans
Parent
commit
03488d241c

+ 0 - 1
MANIFEST.in

@@ -10,4 +10,3 @@
 # Licence: BSD - See LICENCE file for details
 recursive-include ndg/httpsclient/test *.crt *.key *.sh README
 recursive-include documentation Makefile
-include README

+ 10 - 2
ndg/httpsclient/https.py

@@ -42,18 +42,26 @@ class HTTPSConnection(HTTPConnection):
     default_ssl_method = SSL.SSLv23_METHOD
     
     def __init__(self, host, port=None, strict=None,
-                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
+                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None):
         HTTPConnection.__init__(self, host, port, strict, timeout)
         if not hasattr(self, 'ssl_context'):
             self.ssl_context = None
 
+        if ssl_context is not None:
+            if not isinstance(ssl_context, SSL.Context):
+                raise TypeError('Expecting OpenSSL.SSL.Context type for "'
+                                'ssl_context" keyword; got %r instead' %
+                                ssl_context)
+                
+            self.ssl_context = ssl_context
+            
     def connect(self):
         """Create SSL socket and connect to peer
         """
         if getattr(self, 'ssl_context', None):
             if not isinstance(self.ssl_context, SSL.Context):
                 raise TypeError('Expecting OpenSSL.SSL.Context type for "'
-                                'ssl_context" keyword; got %r instead' %
+                                'ssl_context" attribute; got %r instead' %
                                 self.ssl_context)
             ssl_context = self.ssl_context
         else:

+ 3 - 2
ndg/httpsclient/ssl_context_util.py

@@ -36,11 +36,12 @@ def make_ssl_context_from_config(ssl_config=False, url=None):
 
 
 def make_ssl_context(key_file=None, cert_file=None, pem_file=None, ca_dir=None,
-                     verify_peer=False, url=None):
+                     verify_peer=False, url=None, method=SSL.SSLv23_METHOD):
     """
     Creates SSL context containing certificate and key file locations.
     """
-    ssl_context = SSL.Context(SSL.SSLv23_METHOD)
+    ssl_context = SSL.Context(method)
+    
     # Key file defaults to certificate file if present.
     if cert_file:
         ssl_context.use_certificate_file(cert_file)

+ 3 - 3
ndg/httpsclient/ssl_socket.py

@@ -70,10 +70,10 @@ class SSLSocket(object):
         underlying socket"""
         try:
             self.__ssl_conn.shutdown()
-        except SSL.Error, e:
+        except SSL.Error:
             # Make errors on shutdown non-fatal
-            log.warning('Connection shutdown failed: %r', e)
-
+            pass
+        
         self.__ssl_conn.close()
 
     def set_shutdown(self, mode):

+ 16 - 2
ndg/httpsclient/test/README

@@ -5,8 +5,22 @@ The unit tests expect to connect to a simple HTTPS server listening on port
 
 $ ./scripts/openssl_https_server.sh
 
+Unit tests
+----------
+Run for example,
+
+$ python ./test_urllib2.py
+
 Troubleshooting
 ---------------
-Run it from *this* directory.  Also ensure it is has execute bits set. e.g.
+ * Run the openssl script from *this* directory.  
+ * Also ensure it is has execute bits set. e.g.
+
+ $ chmod 755 ./scripts/openssl_https_server.sh
+ 
+ * You may need to set the no_proxy environment variable if you have a HTTPS
+ proxy in place:
+ 
+ $ export no_proxy=localhost
+
 
-$ chmod 755 ./scripts/openssl_https_server.sh

+ 1 - 0
ndg/httpsclient/test/__init__.py

@@ -22,6 +22,7 @@ class Constants(object):
     TEST_URI2 = 'https://%s:%d' % (HOSTNAME, PORT2)
 
     UNITTEST_DIR = os.path.dirname(os.path.abspath(__file__))
+    CACERT_DIR = os.path.join(UNITTEST_DIR, 'pki', 'ca')
     SSL_CERT_FILENAME = 'localhost.crt'
     SSL_CERT_FILEPATH = os.path.join(UNITTEST_DIR, 'pki', SSL_CERT_FILENAME)
     SSL_PRIKEY_FILENAME = 'localhost.key'

+ 41 - 0
ndg/httpsclient/test/test_https.py

@@ -11,9 +11,12 @@ __contact__ = "Philip.Kershaw@stfc.ac.uk"
 __revision__ = '$Id$'
 import logging
 logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger(__name__)
 import unittest
 import socket
 
+from OpenSSL import SSL
+
 from ndg.httpsclient.test import Constants
 from ndg.httpsclient.https import HTTPSConnection
 
@@ -33,6 +36,44 @@ class TestHTTPSConnection(unittest.TestCase):
         conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT2)
         self.failUnlessRaises(socket.error, conn.connect)
 
+    def test03_ssl_verification_of_peer_fails(self):
+        ctx = SSL.Context(SSL.SSLv3_METHOD)
+        
+        def verify_callback(conn, x509, errnum, errdepth, preverify_ok): 
+            log.debug('SSL peer certificate verification failed for %r',
+                      x509.get_subject())
+            return preverify_ok 
+            
+        ctx.set_verify(SSL.VERIFY_PEER, verify_callback)
+        ctx.set_verify_depth(9)
+        
+        # Set bad location - unit test dir has no CA certs to verify with
+        ctx.load_verify_locations(None, Constants.UNITTEST_DIR)
+        
+        conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT,
+                               ssl_context=ctx)
+        conn.connect()        
+        self.failUnlessRaises(SSL.Error, conn.request, 'GET', '/')
+
+    def test03_ssl_verification_of_peer_succeeds(self):
+        ctx = SSL.Context(SSL.SSLv3_METHOD)
+        
+        verify_callback = lambda conn, x509, errnum, errdepth, preverify_ok: \
+            preverify_ok 
+            
+        ctx.set_verify(SSL.VERIFY_PEER, verify_callback)
+        ctx.set_verify_depth(9)
+        
+        # Set correct location for CA certs to verify with
+        ctx.load_verify_locations(None, Constants.CACERT_DIR)
+        
+        conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT,
+                               ssl_context=ctx)
+        conn.connect()
+        conn.request('GET', '/')
+        resp = conn.getresponse()
+        print('Response = %s' % resp.read())
 
+        
 if __name__ == "__main__":
     unittest.main()

+ 4 - 2
setup.py

@@ -13,8 +13,8 @@ enable it to be used with urllib2.
 
 Prerequisites
 =============
-This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL.  Note
-that proxy support is only available from Python 2.6.2 onwards.
+This has been developed and tested for Python 2.6 and 2.7 with pyOpenSSL 0.13.  
+Note that proxy support is only available from Python 2.6.2 onwards.
 
 Installation
 ============
@@ -48,6 +48,8 @@ setup(
     description='Provides enhanced HTTPS support for httplib and urllib2 using '
                 'PyOpenSSL',
     author='Richard Wilkinson and Philip Kershaw',
+    author_email='Philip.Kershaw@stfc.ac.uk',
+    url='http://ndg-security.ceda.ac.uk/wiki/ndg_httpsclient/',
     long_description=_long_description,
     license='BSD - See LICENCE file for details',
     namespace_packages=['ndg'],