From patchwork Mon Mar 28 04:28:34 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [5,of,7] sslutil: always use SSLContext From: Gregory Szorc X-Patchwork-Id: 14096 Message-Id: To: mercurial-devel@mercurial-scm.org Date: Sun, 27 Mar 2016 21:28:34 -0700 # HG changeset patch # User Gregory Szorc # Date 1459113512 25200 # Sun Mar 27 14:18:32 2016 -0700 # Node ID cb4f105cc25744f725bb721ba53fde08a709f788 # Parent bc7d81803a7558f7f744d2a26fab593466b6d5e9 sslutil: always use SSLContext Now that we have a fake SSLContext instance, we can unify the code paths for wrapping sockets to always use the SSLContext APIs. Because this is security code, I've retained the try..except to make the diff easier to read. It will be removed in the next patch. I took the liberty of updating the inline docs about supported protocols and how the constants work because this stuff is important and needs to be explicitly documented. diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py --- a/mercurial/sslutil.py +++ b/mercurial/sslutil.py @@ -102,65 +102,63 @@ except AttributeError: } if self._supportsciphers: args['ciphers'] = self._ciphers return ssl.wrap_socket(socket, **args) try: - # ssl.SSLContext was added in 2.7.9 and presence indicates modern - # SSL/TLS features are available. - ssl_context = ssl.SSLContext - def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE, ca_certs=None, serverhostname=None): - # Allow any version of SSL starting with TLSv1 and - # up. Note that specifying TLSv1 here prohibits use of - # newer standards (like TLSv1_2), so this is the right way - # to do this. Note that in the future it'd be better to - # support using ssl.create_default_context(), which sets - # up a bunch of things in smart ways (strong ciphers, - # protocol versions, etc) and is upgraded by Python - # maintainers for us, but that breaks too many things to - # do it in a hurry. - sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + # Despite its name, PROTOCOL_SSLv23 selects the highest protocol + # that both ends support, including TLS protocols. On legacy stacks, + # the highest it likely goes in TLS 1.0. On modern stacks, it can + # support TLS 1.2. + # + # The PROTOCOL_TLSv* constants select a specific TLS version + # only (as opposed to multiple versions). So the method for + # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and + # disable protocols via SSLContext.options and OP_NO_* constants. + # However, SSLContext.options doesn't work unless we have the + # full/real SSLContext available to us. + # + # SSLv2 and SSLv3 are broken. We ban them outright. + if modernssl: + protocol = ssl.PROTOCOL_SSLv23 + else: + protocol = ssl.PROTOCOL_TLSv1 + + # TODO use ssl.create_default_context() on modernssl. + sslcontext = SSLContext(protocol) + + # This is a no-op on old Python. sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3 + if certfile is not None: def password(): f = keyfile or certfile return ui.getpass(_('passphrase for %s: ') % f, '') sslcontext.load_cert_chain(certfile, keyfile, password) sslcontext.verify_mode = cert_reqs if ca_certs is not None: sslcontext.load_verify_locations(cafile=ca_certs) - elif _canloaddefaultcerts: + else: + # This is a no-op on old Python. sslcontext.load_default_certs() sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) # check if wrap_socket failed silently because socket had been # closed # - see http://bugs.python.org/issue13721 if not sslsocket.cipher(): raise error.Abort(_('ssl connection failed')) return sslsocket except AttributeError: - # We don't have a modern version of the "ssl" module and are running - # Python <2.7.9. - def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE, - ca_certs=None, serverhostname=None): - sslsocket = ssl.wrap_socket(sock, keyfile, certfile, - cert_reqs=cert_reqs, ca_certs=ca_certs, - ssl_version=ssl.PROTOCOL_TLSv1) - # check if wrap_socket failed silently because socket had been - # closed - # - see http://bugs.python.org/issue13721 - if not sslsocket.cipher(): - raise error.Abort(_('ssl connection failed')) - return sslsocket + raise util.Abort('this should not happen') def _verifycert(cert, hostname): '''Verify that cert (in socket.getpeercert() format) matches hostname. CRLs is not handled. Returns error message if any problems are found and None on success. ''' if not cert: