Mercurial > hg
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 |