Author: lquack
Date: Wed May 11 14:24:07 2016
New Revision: 1743379

URL: http://svn.apache.org/viewvc?rev=1743379&view=rev
Log:
QPID-7258: Python Client for AMQP 0-8...0-9-1] Perform
 hostname verification of tls connections

* hostname verification is performed by default.
* introduce connection_option "ssl_skip_hostname_check" to disable this feature
* hostname verification will throw an ImportError on Python <2.6

Modified:
    qpid/trunk/qpid/python/qpid/connection08.py
    qpid/trunk/qpid/python/qpid/messaging/transports.py

Modified: qpid/trunk/qpid/python/qpid/connection08.py
URL: 
http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/connection08.py?rev=1743379&r1=1743378&r2=1743379&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/connection08.py (original)
+++ qpid/trunk/qpid/python/qpid/connection08.py Wed May 11 14:24:07 2016
@@ -71,31 +71,76 @@ class SockIO:
     finally:
       self.sock.close()
 
+class _OldSSLSock:
+  """This is a wrapper around old (<=2.5) Python SSLObjects"""
+
+  def __init__(self, sock, keyFile, certFile):
+    self._sock = sock
+    self._sslObj = socket.ssl(self._sock, self._keyFile, self._certFile)
+    self._keyFile = keyFile
+    self._certFile = certFile
+
+  def sendall(self, buf):
+    while buf:
+      bytesWritten = self._sslObj.write(buf)
+      buf = buf[bytesWritten:]
+
+  def recv(self, n):
+    return self._sslObj.read(n)
+
+  def shutdown(self, how):
+    self._sock.shutdown(how)
+
+  def close(self):
+    self._sock.close()
+    self._sslObj = None
+
+  def getpeercert(self):
+    raise socket.error("This version of Python does not support SSL hostname 
verification. Please upgrade.")
+
+
 def connect(host, port, options = None):
   sock = socket.socket()
+  sock.connect((host, port))
+  sock.setblocking(1)
 
   if options and options.get("ssl", False):
     log.debug("Wrapping socket for SSL")
-    from ssl import wrap_socket, CERT_REQUIRED, CERT_NONE
-
     ssl_certfile = options.get("ssl_certfile", None)
     ssl_keyfile = options.get("ssl_keyfile", ssl_certfile)
     ssl_trustfile = options.get("ssl_trustfile", None)
     ssl_require_trust = options.get("ssl_require_trust", True)
+    ssl_verify_hostname = not options.get("ssl_skip_hostname_check", False)
 
-    if ssl_require_trust:
-      validate = CERT_REQUIRED
-    else:
-      validate = CERT_NONE
-
-    sock = wrap_socket(sock,
-                       keyfile = ssl_keyfile,
-                       certfile = ssl_certfile,
-                       ca_certs = ssl_trustfile,
-                       cert_reqs = validate)
+    try:
+      # Python 2.6 and 2.7
+      from ssl import wrap_socket, CERT_REQUIRED, CERT_OPTIONAL, CERT_NONE
+      try:
+        # Python 2.7.9 and newer
+        from ssl import match_hostname as verify_hostname
+      except ImportError:
+        # Before Python 2.7.9 we roll our own
+        from qpid.messaging.transports import verify_hostname
+
+      if ssl_require_trust or ssl_verify_hostname:
+        validate = CERT_REQUIRED
+      else:
+        validate = CERT_NONE
+      sock = wrap_socket(sock,
+                         keyfile=ssl_keyfile,
+                         certfile=ssl_certfile,
+                         ca_certs=ssl_trustfile,
+                         cert_reqs=validate)
+    except ImportError, e:
+      # Python 2.5 and older
+      if ssl_verify_hostname:
+        log.error("Your version of Python does not support ssl hostname 
verification. Please upgrade your version of Python.")
+        raise e
+      sock = _OldSSLSock(sock, ssl_keyfile, ssl_certfile)
+
+    if ssl_verify_hostname:
+      verify_hostname(sock.getpeercert(), host)
 
-  sock.connect((host, port))
-  sock.setblocking(1)
   return SockIO(sock)
 
 def listen(host, port, predicate = lambda: True):

Modified: qpid/trunk/qpid/python/qpid/messaging/transports.py
URL: 
http://svn.apache.org/viewvc/qpid/trunk/qpid/python/qpid/messaging/transports.py?rev=1743379&r1=1743378&r2=1743379&view=diff
==============================================================================
--- qpid/trunk/qpid/python/qpid/messaging/transports.py (original)
+++ qpid/trunk/qpid/python/qpid/messaging/transports.py Wed May 11 14:24:07 2016
@@ -120,27 +120,7 @@ else:
                              cert_reqs=validate)
 
       if validate == CERT_REQUIRED and not conn.ssl_skip_hostname_check:
-        match_found = False
-        peer_cert = self.tls.getpeercert()
-        if peer_cert:
-          peer_names = []
-          if 'subjectAltName' in peer_cert:
-            for san in peer_cert['subjectAltName']:
-              if san[0] == 'DNS':
-                peer_names.append(san[1].lower())
-          if 'subject' in peer_cert:
-            for sub in peer_cert['subject']:
-              while isinstance(sub, tuple) and isinstance(sub[0],tuple):
-                sub = sub[0]   # why the extra level of indirection???
-              if sub[0] == 'commonName':
-                peer_names.append(sub[1].lower())
-          for pattern in peer_names:
-            if _match_dns_pattern( host.lower(), pattern ):
-              #print "Match found %s" % pattern
-              match_found = True
-              break
-        if not match_found:
-          raise SSLError("Connection hostname '%s' does not match names from 
peer certificate: %s" % (host, peer_names))
+        verify_hostname(self.tls.getpeercert(), host)
 
       self.socket.setblocking(0)
       self.state = None
@@ -205,6 +185,27 @@ else:
       # this closes the underlying socket
       self.tls.close()
 
+  def verify_hostname(peer_certificate, hostname):
+    match_found = False
+    peer_names = []
+    if peer_certificate:
+      if 'subjectAltName' in peer_certificate:
+        for san in peer_certificate['subjectAltName']:
+          if san[0] == 'DNS':
+            peer_names.append(san[1].lower())
+      if 'subject' in peer_certificate:
+        for sub in peer_certificate['subject']:
+          while isinstance(sub, tuple) and isinstance(sub[0], tuple):
+            sub = sub[0]  # why the extra level of indirection???
+          if sub[0] == 'commonName':
+            peer_names.append(sub[1].lower())
+      for pattern in peer_names:
+        if _match_dns_pattern(hostname.lower(), pattern):
+          match_found = True
+          break
+    if not match_found:
+      raise SSLError("Connection hostname '%s' does not match names from peer 
certificate: %s" % (hostname, peer_names))
+
   def _match_dns_pattern( hostname, pattern ):
     """ For checking the hostnames provided by the peer's certificate
     """



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to