diff options
author | Lorenz Quack <lquack@apache.org> | 2016-05-11 14:24:07 +0000 |
---|---|---|
committer | Lorenz Quack <lquack@apache.org> | 2016-05-11 14:24:07 +0000 |
commit | 58ac9b9bec850fe58c3f23c17e185cda9614c8be (patch) | |
tree | 9dc2229b12632512675fede9e21759fb46e332ca | |
parent | 622bdec53040d563b6439f97dc72483822b25c5b (diff) | |
download | qpid-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.py | 73 | ||||
-rw-r--r-- | qpid/python/qpid/messaging/transports.py | 43 |
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 """ |