comparison mercurial/sslutil.py @ 28651:4827d07073e6

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.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 27 Mar 2016 14:18:32 -0700
parents 737863b01d9f
children c617614aefd2
comparison
equal deleted inserted replaced
28650:737863b01d9f 28651:4827d07073e6
105 args['ciphers'] = self._ciphers 105 args['ciphers'] = self._ciphers
106 106
107 return ssl.wrap_socket(socket, **args) 107 return ssl.wrap_socket(socket, **args)
108 108
109 try: 109 try:
110 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
111 # SSL/TLS features are available.
112 ssl_context = ssl.SSLContext
113
114 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE, 110 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
115 ca_certs=None, serverhostname=None): 111 ca_certs=None, serverhostname=None):
116 # Allow any version of SSL starting with TLSv1 and 112 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
117 # up. Note that specifying TLSv1 here prohibits use of 113 # that both ends support, including TLS protocols. On legacy stacks,
118 # newer standards (like TLSv1_2), so this is the right way 114 # the highest it likely goes in TLS 1.0. On modern stacks, it can
119 # to do this. Note that in the future it'd be better to 115 # support TLS 1.2.
120 # support using ssl.create_default_context(), which sets 116 #
121 # up a bunch of things in smart ways (strong ciphers, 117 # The PROTOCOL_TLSv* constants select a specific TLS version
122 # protocol versions, etc) and is upgraded by Python 118 # only (as opposed to multiple versions). So the method for
123 # maintainers for us, but that breaks too many things to 119 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
124 # do it in a hurry. 120 # disable protocols via SSLContext.options and OP_NO_* constants.
125 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 121 # However, SSLContext.options doesn't work unless we have the
122 # full/real SSLContext available to us.
123 #
124 # SSLv2 and SSLv3 are broken. We ban them outright.
125 if modernssl:
126 protocol = ssl.PROTOCOL_SSLv23
127 else:
128 protocol = ssl.PROTOCOL_TLSv1
129
130 # TODO use ssl.create_default_context() on modernssl.
131 sslcontext = SSLContext(protocol)
132
133 # This is a no-op on old Python.
126 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3 134 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
135
127 if certfile is not None: 136 if certfile is not None:
128 def password(): 137 def password():
129 f = keyfile or certfile 138 f = keyfile or certfile
130 return ui.getpass(_('passphrase for %s: ') % f, '') 139 return ui.getpass(_('passphrase for %s: ') % f, '')
131 sslcontext.load_cert_chain(certfile, keyfile, password) 140 sslcontext.load_cert_chain(certfile, keyfile, password)
132 sslcontext.verify_mode = cert_reqs 141 sslcontext.verify_mode = cert_reqs
133 if ca_certs is not None: 142 if ca_certs is not None:
134 sslcontext.load_verify_locations(cafile=ca_certs) 143 sslcontext.load_verify_locations(cafile=ca_certs)
135 elif _canloaddefaultcerts: 144 else:
145 # This is a no-op on old Python.
136 sslcontext.load_default_certs() 146 sslcontext.load_default_certs()
137 147
138 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname) 148 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
139 # check if wrap_socket failed silently because socket had been 149 # check if wrap_socket failed silently because socket had been
140 # closed 150 # closed
141 # - see http://bugs.python.org/issue13721 151 # - see http://bugs.python.org/issue13721
142 if not sslsocket.cipher(): 152 if not sslsocket.cipher():
143 raise error.Abort(_('ssl connection failed')) 153 raise error.Abort(_('ssl connection failed'))
144 return sslsocket 154 return sslsocket
145 except AttributeError: 155 except AttributeError:
146 # We don't have a modern version of the "ssl" module and are running 156 raise util.Abort('this should not happen')
147 # Python <2.7.9.
148 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
149 ca_certs=None, serverhostname=None):
150 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
151 cert_reqs=cert_reqs, ca_certs=ca_certs,
152 ssl_version=ssl.PROTOCOL_TLSv1)
153 # check if wrap_socket failed silently because socket had been
154 # closed
155 # - see http://bugs.python.org/issue13721
156 if not sslsocket.cipher():
157 raise error.Abort(_('ssl connection failed'))
158 return sslsocket
159 157
160 def _verifycert(cert, hostname): 158 def _verifycert(cert, hostname):
161 '''Verify that cert (in socket.getpeercert() format) matches hostname. 159 '''Verify that cert (in socket.getpeercert() format) matches hostname.
162 CRLs is not handled. 160 CRLs is not handled.
163 161