Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- this patch series consists of 1 patches.
- [?1034hCc:
- displaying [PATCH] https: support tls sni (server name indication) for https urls (issue3090) ...
- Content-Type: text/plain; charset="us-ascii"
- MIME-Version: 1.0
- Content-Transfer-Encoding: 7bit
- Subject: [PATCH] https: support tls sni (server name indication) for https
- urls (issue3090)
- X-Mercurial-Node: 1521e79acdcd4fbfea77dc4d95be2106508934d5
- Message-Id: <1521e79acdcd4fbfea77.1377772380@localhost>
- User-Agent: Mercurial-patchbomb/2.4.2
- Date: Thu, 29 Aug 2013 04:33:00 -0600
- From: Alex Orange <crazycasta@gmail.com>
- To: mercurial-devel@selenic.com
- # HG changeset patch
- # User Alex Orange <crazycasta@gmail.com>
- # Date 1377769698 21600
- # Node ID 1521e79acdcd4fbfea77dc4d95be2106508934d5
- # Parent d4a0055af149cdea20b3136b66cae8a24b2e2a98
- https: support tls sni (server name indication) for https urls (issue3090)
- SNI is a common way of sharing servers across multiple domains using separate
- SSL certificates. Python 2.x does not, and will not, support SNI according to:
- http://bugs.python.org/issue5639#msg192234. In order to support SNI pyOpenSSL
- and pyasn1 have been used to emulate Python's SSLSocket object (the one
- returned by wrap_socket). Additionally, changes are made to code in httpclient,
- sslutil, and url to pass the server hostname to the relavent wrap_socket
- wrapper function.
- The research I did led me to the conclusion that an OpenSSL based solution
- would best replicate that existing python ssl object. As opposed to a GNUTLS
- solution like PyGnuTLS (https://gitorious.org/pygnutls). The two packages I
- found that perform the basic functions of the python ssl object are pyOpenSSL
- (http://pythonhosted.org/pyOpenSSL/) and M2Crypto
- (http://www.heikkitoivonen.net/m2crypto/). M2Crypto does not support sending
- the host name as far as I can tell, which leaves pyOpenSSL. The shortcoming of
- both of these libraries is that they do not provide give the subjectAltName
- subobjects as separate objects. They give either the DER encoded raw data or
- an openssl command line style string "DNS:a.b.com, DNS:c.d.com, ...". I did not
- want to parse this string in case a certificate came up with a dNSName had the
- form 'a.b.com, DNS:c.d.com' which could conceivably lead to a security problem.
- In order to handle this problem I used pyasn1 to parse the subjectAltName data
- along with code taken from ndg-httpsclient. There appears to also be an
- way to do this directly through python's ssl module with an undocumented
- function called _test_decode_cert. However this would involve writing
- certificates to temporary files and then reading them back in. This seems like
- a bad idea both because the function is undocumented (and therefore vulnerable
- to going away without warning) and a possible security risk: writing to files
- and then reading them back in. Finally, to make the all of this as unobtrusive
- as possible I wrapped this functionality into a single class that emulates the
- python ssl class called SocketWrapper in opensslwrap.py.
- Finally, imports of pyOpenSSL and pyasn1 are tried before the import of ssl in
- httpclient/socketutil.py and sslutil.py. This prefers the use of this scheme
- over the built-in ssl module for those that have the supporting packages. The
- imports take the form of from a import b so as to force immediate import since
- pyasn1 in particular is not immediately used.
- diff -r d4a0055af149 -r 1521e79acdcd mercurial/sslutil.py
- --- a/mercurial/sslutil.py Fri Aug 23 16:16:22 2013 -0400
- +++ b/mercurial/sslutil.py Thu Aug 29 03:48:18 2013 -0600
- @@ -11,34 +11,51 @@
- from mercurial import util
- from mercurial.i18n import _
- try:
- - # avoid using deprecated/broken FakeSocket in python 2.6
- - import ssl
- - CERT_REQUIRED = ssl.CERT_REQUIRED
- + # Force the import
- + from ssl_sni import openssl
- +
- + CERT_REQUIRED = openssl.CERT_REQUIRED
- def ssl_wrap_socket(sock, keyfile, certfile,
- - cert_reqs=ssl.CERT_NONE, ca_certs=None):
- - sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
- - cert_reqs=cert_reqs, ca_certs=ca_certs,
- - ssl_version=ssl.PROTOCOL_SSLv3)
- - # check if wrap_socket failed silently because socket had been closed
- - # - see http://bugs.python.org/issue13721
- - if not sslsocket.cipher():
- - raise util.Abort(_('ssl connection failed'))
- + cert_reqs=openssl.CERT_NONE, ca_certs=None,
- + server_hostname=None):
- + sslsocket = openssl.wrap_socket(sock, keyfile, certfile,
- + cert_reqs=cert_reqs,
- + ca_certs=ca_certs,
- + server_hostname=server_hostname)
- return sslsocket
- except ImportError:
- - CERT_REQUIRED = 2
- + try:
- + # avoid using deprecated/broken FakeSocket in python 2.6
- + import ssl
- + CERT_REQUIRED = ssl.CERT_REQUIRED
- + def ssl_wrap_socket(sock, keyfile, certfile,
- + cert_reqs=ssl.CERT_NONE, ca_certs=None,
- + server_hostname=None):
- + sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
- + cert_reqs=cert_reqs, ca_certs=ca_certs,
- + ssl_version=ssl.PROTOCOL_SSLv3)
- + # check if wrap_socket failed silently because socket had been
- + # closed
- + # - see http://bugs.python.org/issue13721
- + if not sslsocket.cipher():
- + raise util.Abort(_('ssl connection failed'))
- + return sslsocket
- + except ImportError:
- + CERT_REQUIRED = 2
- - import socket, httplib
- + import socket, httplib
- - def ssl_wrap_socket(sock, key_file, cert_file,
- - cert_reqs=CERT_REQUIRED, ca_certs=None):
- - if not util.safehasattr(socket, 'ssl'):
- - raise util.Abort(_('Python SSL support not found'))
- - if ca_certs:
- - raise util.Abort(_(
- - 'certificate checking requires Python 2.6'))
- + def ssl_wrap_socket(sock, key_file, cert_file,
- + cert_reqs=CERT_REQUIRED, ca_certs=None,
- + server_hostname=None):
- + if not util.safehasattr(socket, 'ssl'):
- + raise util.Abort(_('Python SSL support not found'))
- + if ca_certs:
- + raise util.Abort(_(
- + 'certificate checking requires Python 2.6'))
- - ssl = socket.ssl(sock, key_file, cert_file)
- - return httplib.FakeSocket(sock, ssl)
- + ssl = socket.ssl(sock, key_file, cert_file)
- + return httplib.FakeSocket(sock, ssl)
- def _verifycert(cert, hostname):
- '''Verify that cert (in socket.getpeercert() format) matches hostname.
- @@ -115,9 +132,14 @@
- self.ui.warn(_("warning: certificate for %s can't be verified "
- "(Python too old)\n") % host)
- return
- + try:
- + # work around http://bugs.python.org/issue13721
- + if not sock.cipher():
- + raise util.Abort(_('%s ssl connection error') % host)
- + except AttributeError:
- + # This is not the ssl object you are looking for
- + pass
- - if not sock.cipher(): # work around http://bugs.python.org/issue13721
- - raise util.Abort(_('%s ssl connection error') % host)
- try:
- peercert = sock.getpeercert(True)
- peercert2 = sock.getpeercert()
- diff -r d4a0055af149 -r 1521e79acdcd mercurial/url.py
- --- a/mercurial/url.py Fri Aug 23 16:16:22 2013 -0400
- +++ b/mercurial/url.py Thu Aug 29 03:48:18 2013 -0600
- @@ -181,7 +181,8 @@
- self.sock.connect((self.host, self.port))
- if _generic_proxytunnel(self):
- # we do not support client X.509 certificates
- - self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
- + self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
- + server_hostname=self.host)
- else:
- keepalive.HTTPConnection.connect(self)
- @@ -338,7 +339,7 @@
- _generic_proxytunnel(self)
- host = self.realhostport.rsplit(':', 1)[0]
- self.sock = sslutil.ssl_wrap_socket(
- - self.sock, self.key_file, self.cert_file,
- + self.sock, self.key_file, self.cert_file, server_hostname=host,
- **sslutil.sslkwargs(self.ui, host))
- sslutil.validator(self.ui, host)(self.sock)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement