summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz Quack <lquack@apache.org>2016-05-11 14:24:07 +0000
committerLorenz Quack <lquack@apache.org>2016-05-11 14:24:07 +0000
commit58ac9b9bec850fe58c3f23c17e185cda9614c8be (patch)
tree9dc2229b12632512675fede9e21759fb46e332ca
parent622bdec53040d563b6439f97dc72483822b25c5b (diff)
downloadqpid-python-58ac9b9bec850fe58c3f23c17e185cda9614c8be.tar.gz
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 git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1743379 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--qpid/python/qpid/connection08.py73
-rw-r--r--qpid/python/qpid/messaging/transports.py43
2 files changed, 81 insertions, 35 deletions
diff --git a/qpid/python/qpid/connection08.py b/qpid/python/qpid/connection08.py
index 9565937a6e..0b91ca6312 100644
--- a/qpid/python/qpid/connection08.py
+++ b/qpid/python/qpid/connection08.py
@@ -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):
diff --git a/qpid/python/qpid/messaging/transports.py b/qpid/python/qpid/messaging/transports.py
index f39c256d02..c4e7c6834e 100644
--- a/qpid/python/qpid/messaging/transports.py
+++ b/qpid/python/qpid/messaging/transports.py
@@ -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
"""