##// END OF EJS Templates
sslutil: remove code checking for presence of ssl.SSLContext...
Manuel Jacob -
r45411:7c19eb37 default
parent child Browse files
Show More
@@ -1,1024 +1,964
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16
16
17 from .i18n import _
17 from .i18n import _
18 from .pycompat import getattr
18 from .pycompat import getattr
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 node,
22 node,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 hashutil,
27 hashutil,
28 resourceutil,
28 resourceutil,
29 stringutil,
29 stringutil,
30 )
30 )
31
31
32 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
32 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
33 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
33 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
34 # all exposed via the "ssl" module.
34 # all exposed via the "ssl" module.
35 #
35 #
36 # Depending on the version of Python being used, SSL/TLS support is either
36 # We require in setup.py the presence of ssl.SSLContext, which indicates modern
37 # modern/secure or legacy/insecure. Many operations in this module have
37 # SSL/TLS support.
38 # separate code paths depending on support in Python.
39
38
40 configprotocols = {
39 configprotocols = {
41 b'tls1.0',
40 b'tls1.0',
42 b'tls1.1',
41 b'tls1.1',
43 b'tls1.2',
42 b'tls1.2',
44 }
43 }
45
44
46 hassni = getattr(ssl, 'HAS_SNI', False)
45 hassni = getattr(ssl, 'HAS_SNI', False)
47
46
48 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
47 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
49 # against doesn't support them.
48 # against doesn't support them.
50 supportedprotocols = {b'tls1.0'}
49 supportedprotocols = {b'tls1.0'}
51 if util.safehasattr(ssl, b'PROTOCOL_TLSv1_1'):
50 if util.safehasattr(ssl, b'PROTOCOL_TLSv1_1'):
52 supportedprotocols.add(b'tls1.1')
51 supportedprotocols.add(b'tls1.1')
53 if util.safehasattr(ssl, b'PROTOCOL_TLSv1_2'):
52 if util.safehasattr(ssl, b'PROTOCOL_TLSv1_2'):
54 supportedprotocols.add(b'tls1.2')
53 supportedprotocols.add(b'tls1.2')
55
54
56 try:
55 modernssl = True
57 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
56 _canloaddefaultcerts = True
58 # SSL/TLS features are available.
59 SSLContext = ssl.SSLContext
60 modernssl = True
61 _canloaddefaultcerts = True
62 except AttributeError:
63 modernssl = False
64 _canloaddefaultcerts = False
65
66 # We implement SSLContext using the interface from the standard library.
67 class SSLContext(object):
68 def __init__(self, protocol):
69 # From the public interface of SSLContext
70 self.protocol = protocol
71 self.check_hostname = False
72 self.options = 0
73 self.verify_mode = ssl.CERT_NONE
74
75 # Used by our implementation.
76 self._certfile = None
77 self._keyfile = None
78 self._certpassword = None
79 self._cacerts = None
80 self._ciphers = None
81
82 def load_cert_chain(self, certfile, keyfile=None, password=None):
83 self._certfile = certfile
84 self._keyfile = keyfile
85 self._certpassword = password
86
87 def load_default_certs(self, purpose=None):
88 pass
89
90 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
91 if capath:
92 raise error.Abort(_(b'capath not supported'))
93 if cadata:
94 raise error.Abort(_(b'cadata not supported'))
95
96 self._cacerts = cafile
97
98 def set_ciphers(self, ciphers):
99 self._ciphers = ciphers
100
101 def wrap_socket(self, socket, server_hostname=None, server_side=False):
102 # server_hostname is unique to SSLContext.wrap_socket and is used
103 # for SNI in that context. So there's nothing for us to do with it
104 # in this legacy code since we don't support SNI.
105
106 args = {
107 'keyfile': self._keyfile,
108 'certfile': self._certfile,
109 'server_side': server_side,
110 'cert_reqs': self.verify_mode,
111 'ssl_version': self.protocol,
112 'ca_certs': self._cacerts,
113 'ciphers': self._ciphers,
114 }
115
116 return ssl.wrap_socket(socket, **args)
117
57
118
58
119 def _hostsettings(ui, hostname):
59 def _hostsettings(ui, hostname):
120 """Obtain security settings for a hostname.
60 """Obtain security settings for a hostname.
121
61
122 Returns a dict of settings relevant to that hostname.
62 Returns a dict of settings relevant to that hostname.
123 """
63 """
124 bhostname = pycompat.bytesurl(hostname)
64 bhostname = pycompat.bytesurl(hostname)
125 s = {
65 s = {
126 # Whether we should attempt to load default/available CA certs
66 # Whether we should attempt to load default/available CA certs
127 # if an explicit ``cafile`` is not defined.
67 # if an explicit ``cafile`` is not defined.
128 b'allowloaddefaultcerts': True,
68 b'allowloaddefaultcerts': True,
129 # List of 2-tuple of (hash algorithm, hash).
69 # List of 2-tuple of (hash algorithm, hash).
130 b'certfingerprints': [],
70 b'certfingerprints': [],
131 # Path to file containing concatenated CA certs. Used by
71 # Path to file containing concatenated CA certs. Used by
132 # SSLContext.load_verify_locations().
72 # SSLContext.load_verify_locations().
133 b'cafile': None,
73 b'cafile': None,
134 # Whether certificate verification should be disabled.
74 # Whether certificate verification should be disabled.
135 b'disablecertverification': False,
75 b'disablecertverification': False,
136 # Whether the legacy [hostfingerprints] section has data for this host.
76 # Whether the legacy [hostfingerprints] section has data for this host.
137 b'legacyfingerprint': False,
77 b'legacyfingerprint': False,
138 # PROTOCOL_* constant to use for SSLContext.__init__.
78 # PROTOCOL_* constant to use for SSLContext.__init__.
139 b'protocol': None,
79 b'protocol': None,
140 # String representation of minimum protocol to be used for UI
80 # String representation of minimum protocol to be used for UI
141 # presentation.
81 # presentation.
142 b'protocolui': None,
82 b'protocolui': None,
143 # ssl.CERT_* constant used by SSLContext.verify_mode.
83 # ssl.CERT_* constant used by SSLContext.verify_mode.
144 b'verifymode': None,
84 b'verifymode': None,
145 # Defines extra ssl.OP* bitwise options to set.
85 # Defines extra ssl.OP* bitwise options to set.
146 b'ctxoptions': None,
86 b'ctxoptions': None,
147 # OpenSSL Cipher List to use (instead of default).
87 # OpenSSL Cipher List to use (instead of default).
148 b'ciphers': None,
88 b'ciphers': None,
149 }
89 }
150
90
151 # Allow minimum TLS protocol to be specified in the config.
91 # Allow minimum TLS protocol to be specified in the config.
152 def validateprotocol(protocol, key):
92 def validateprotocol(protocol, key):
153 if protocol not in configprotocols:
93 if protocol not in configprotocols:
154 raise error.Abort(
94 raise error.Abort(
155 _(b'unsupported protocol from hostsecurity.%s: %s')
95 _(b'unsupported protocol from hostsecurity.%s: %s')
156 % (key, protocol),
96 % (key, protocol),
157 hint=_(b'valid protocols: %s')
97 hint=_(b'valid protocols: %s')
158 % b' '.join(sorted(configprotocols)),
98 % b' '.join(sorted(configprotocols)),
159 )
99 )
160
100
161 # We default to TLS 1.1+ where we can because TLS 1.0 has known
101 # We default to TLS 1.1+ where we can because TLS 1.0 has known
162 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
102 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
163 # TLS 1.0+ via config options in case a legacy server is encountered.
103 # TLS 1.0+ via config options in case a legacy server is encountered.
164 if b'tls1.1' in supportedprotocols:
104 if b'tls1.1' in supportedprotocols:
165 defaultprotocol = b'tls1.1'
105 defaultprotocol = b'tls1.1'
166 else:
106 else:
167 # Let people know they are borderline secure.
107 # Let people know they are borderline secure.
168 # We don't document this config option because we want people to see
108 # We don't document this config option because we want people to see
169 # the bold warnings on the web site.
109 # the bold warnings on the web site.
170 # internal config: hostsecurity.disabletls10warning
110 # internal config: hostsecurity.disabletls10warning
171 if not ui.configbool(b'hostsecurity', b'disabletls10warning'):
111 if not ui.configbool(b'hostsecurity', b'disabletls10warning'):
172 ui.warn(
112 ui.warn(
173 _(
113 _(
174 b'warning: connecting to %s using legacy security '
114 b'warning: connecting to %s using legacy security '
175 b'technology (TLS 1.0); see '
115 b'technology (TLS 1.0); see '
176 b'https://mercurial-scm.org/wiki/SecureConnections for '
116 b'https://mercurial-scm.org/wiki/SecureConnections for '
177 b'more info\n'
117 b'more info\n'
178 )
118 )
179 % bhostname
119 % bhostname
180 )
120 )
181 defaultprotocol = b'tls1.0'
121 defaultprotocol = b'tls1.0'
182
122
183 key = b'minimumprotocol'
123 key = b'minimumprotocol'
184 protocol = ui.config(b'hostsecurity', key, defaultprotocol)
124 protocol = ui.config(b'hostsecurity', key, defaultprotocol)
185 validateprotocol(protocol, key)
125 validateprotocol(protocol, key)
186
126
187 key = b'%s:minimumprotocol' % bhostname
127 key = b'%s:minimumprotocol' % bhostname
188 protocol = ui.config(b'hostsecurity', key, protocol)
128 protocol = ui.config(b'hostsecurity', key, protocol)
189 validateprotocol(protocol, key)
129 validateprotocol(protocol, key)
190
130
191 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
131 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
192 # We always print a "connection security to %s is disabled..." message when
132 # We always print a "connection security to %s is disabled..." message when
193 # --insecure is used. So no need to print anything more here.
133 # --insecure is used. So no need to print anything more here.
194 if ui.insecureconnections:
134 if ui.insecureconnections:
195 protocol = b'tls1.0'
135 protocol = b'tls1.0'
196
136
197 s[b'protocol'], s[b'ctxoptions'], s[b'protocolui'] = protocolsettings(
137 s[b'protocol'], s[b'ctxoptions'], s[b'protocolui'] = protocolsettings(
198 protocol
138 protocol
199 )
139 )
200
140
201 ciphers = ui.config(b'hostsecurity', b'ciphers')
141 ciphers = ui.config(b'hostsecurity', b'ciphers')
202 ciphers = ui.config(b'hostsecurity', b'%s:ciphers' % bhostname, ciphers)
142 ciphers = ui.config(b'hostsecurity', b'%s:ciphers' % bhostname, ciphers)
203 s[b'ciphers'] = ciphers
143 s[b'ciphers'] = ciphers
204
144
205 # Look for fingerprints in [hostsecurity] section. Value is a list
145 # Look for fingerprints in [hostsecurity] section. Value is a list
206 # of <alg>:<fingerprint> strings.
146 # of <alg>:<fingerprint> strings.
207 fingerprints = ui.configlist(
147 fingerprints = ui.configlist(
208 b'hostsecurity', b'%s:fingerprints' % bhostname
148 b'hostsecurity', b'%s:fingerprints' % bhostname
209 )
149 )
210 for fingerprint in fingerprints:
150 for fingerprint in fingerprints:
211 if not (fingerprint.startswith((b'sha1:', b'sha256:', b'sha512:'))):
151 if not (fingerprint.startswith((b'sha1:', b'sha256:', b'sha512:'))):
212 raise error.Abort(
152 raise error.Abort(
213 _(b'invalid fingerprint for %s: %s') % (bhostname, fingerprint),
153 _(b'invalid fingerprint for %s: %s') % (bhostname, fingerprint),
214 hint=_(b'must begin with "sha1:", "sha256:", or "sha512:"'),
154 hint=_(b'must begin with "sha1:", "sha256:", or "sha512:"'),
215 )
155 )
216
156
217 alg, fingerprint = fingerprint.split(b':', 1)
157 alg, fingerprint = fingerprint.split(b':', 1)
218 fingerprint = fingerprint.replace(b':', b'').lower()
158 fingerprint = fingerprint.replace(b':', b'').lower()
219 s[b'certfingerprints'].append((alg, fingerprint))
159 s[b'certfingerprints'].append((alg, fingerprint))
220
160
221 # Fingerprints from [hostfingerprints] are always SHA-1.
161 # Fingerprints from [hostfingerprints] are always SHA-1.
222 for fingerprint in ui.configlist(b'hostfingerprints', bhostname):
162 for fingerprint in ui.configlist(b'hostfingerprints', bhostname):
223 fingerprint = fingerprint.replace(b':', b'').lower()
163 fingerprint = fingerprint.replace(b':', b'').lower()
224 s[b'certfingerprints'].append((b'sha1', fingerprint))
164 s[b'certfingerprints'].append((b'sha1', fingerprint))
225 s[b'legacyfingerprint'] = True
165 s[b'legacyfingerprint'] = True
226
166
227 # If a host cert fingerprint is defined, it is the only thing that
167 # If a host cert fingerprint is defined, it is the only thing that
228 # matters. No need to validate CA certs.
168 # matters. No need to validate CA certs.
229 if s[b'certfingerprints']:
169 if s[b'certfingerprints']:
230 s[b'verifymode'] = ssl.CERT_NONE
170 s[b'verifymode'] = ssl.CERT_NONE
231 s[b'allowloaddefaultcerts'] = False
171 s[b'allowloaddefaultcerts'] = False
232
172
233 # If --insecure is used, don't take CAs into consideration.
173 # If --insecure is used, don't take CAs into consideration.
234 elif ui.insecureconnections:
174 elif ui.insecureconnections:
235 s[b'disablecertverification'] = True
175 s[b'disablecertverification'] = True
236 s[b'verifymode'] = ssl.CERT_NONE
176 s[b'verifymode'] = ssl.CERT_NONE
237 s[b'allowloaddefaultcerts'] = False
177 s[b'allowloaddefaultcerts'] = False
238
178
239 if ui.configbool(b'devel', b'disableloaddefaultcerts'):
179 if ui.configbool(b'devel', b'disableloaddefaultcerts'):
240 s[b'allowloaddefaultcerts'] = False
180 s[b'allowloaddefaultcerts'] = False
241
181
242 # If both fingerprints and a per-host ca file are specified, issue a warning
182 # If both fingerprints and a per-host ca file are specified, issue a warning
243 # because users should not be surprised about what security is or isn't
183 # because users should not be surprised about what security is or isn't
244 # being performed.
184 # being performed.
245 cafile = ui.config(b'hostsecurity', b'%s:verifycertsfile' % bhostname)
185 cafile = ui.config(b'hostsecurity', b'%s:verifycertsfile' % bhostname)
246 if s[b'certfingerprints'] and cafile:
186 if s[b'certfingerprints'] and cafile:
247 ui.warn(
187 ui.warn(
248 _(
188 _(
249 b'(hostsecurity.%s:verifycertsfile ignored when host '
189 b'(hostsecurity.%s:verifycertsfile ignored when host '
250 b'fingerprints defined; using host fingerprints for '
190 b'fingerprints defined; using host fingerprints for '
251 b'verification)\n'
191 b'verification)\n'
252 )
192 )
253 % bhostname
193 % bhostname
254 )
194 )
255
195
256 # Try to hook up CA certificate validation unless something above
196 # Try to hook up CA certificate validation unless something above
257 # makes it not necessary.
197 # makes it not necessary.
258 if s[b'verifymode'] is None:
198 if s[b'verifymode'] is None:
259 # Look at per-host ca file first.
199 # Look at per-host ca file first.
260 if cafile:
200 if cafile:
261 cafile = util.expandpath(cafile)
201 cafile = util.expandpath(cafile)
262 if not os.path.exists(cafile):
202 if not os.path.exists(cafile):
263 raise error.Abort(
203 raise error.Abort(
264 _(b'path specified by %s does not exist: %s')
204 _(b'path specified by %s does not exist: %s')
265 % (
205 % (
266 b'hostsecurity.%s:verifycertsfile' % (bhostname,),
206 b'hostsecurity.%s:verifycertsfile' % (bhostname,),
267 cafile,
207 cafile,
268 )
208 )
269 )
209 )
270 s[b'cafile'] = cafile
210 s[b'cafile'] = cafile
271 else:
211 else:
272 # Find global certificates file in config.
212 # Find global certificates file in config.
273 cafile = ui.config(b'web', b'cacerts')
213 cafile = ui.config(b'web', b'cacerts')
274
214
275 if cafile:
215 if cafile:
276 cafile = util.expandpath(cafile)
216 cafile = util.expandpath(cafile)
277 if not os.path.exists(cafile):
217 if not os.path.exists(cafile):
278 raise error.Abort(
218 raise error.Abort(
279 _(b'could not find web.cacerts: %s') % cafile
219 _(b'could not find web.cacerts: %s') % cafile
280 )
220 )
281 elif s[b'allowloaddefaultcerts']:
221 elif s[b'allowloaddefaultcerts']:
282 # CAs not defined in config. Try to find system bundles.
222 # CAs not defined in config. Try to find system bundles.
283 cafile = _defaultcacerts(ui)
223 cafile = _defaultcacerts(ui)
284 if cafile:
224 if cafile:
285 ui.debug(b'using %s for CA file\n' % cafile)
225 ui.debug(b'using %s for CA file\n' % cafile)
286
226
287 s[b'cafile'] = cafile
227 s[b'cafile'] = cafile
288
228
289 # Require certificate validation if CA certs are being loaded and
229 # Require certificate validation if CA certs are being loaded and
290 # verification hasn't been disabled above.
230 # verification hasn't been disabled above.
291 if cafile or (_canloaddefaultcerts and s[b'allowloaddefaultcerts']):
231 if cafile or (_canloaddefaultcerts and s[b'allowloaddefaultcerts']):
292 s[b'verifymode'] = ssl.CERT_REQUIRED
232 s[b'verifymode'] = ssl.CERT_REQUIRED
293 else:
233 else:
294 # At this point we don't have a fingerprint, aren't being
234 # At this point we don't have a fingerprint, aren't being
295 # explicitly insecure, and can't load CA certs. Connecting
235 # explicitly insecure, and can't load CA certs. Connecting
296 # is insecure. We allow the connection and abort during
236 # is insecure. We allow the connection and abort during
297 # validation (once we have the fingerprint to print to the
237 # validation (once we have the fingerprint to print to the
298 # user).
238 # user).
299 s[b'verifymode'] = ssl.CERT_NONE
239 s[b'verifymode'] = ssl.CERT_NONE
300
240
301 assert s[b'protocol'] is not None
241 assert s[b'protocol'] is not None
302 assert s[b'ctxoptions'] is not None
242 assert s[b'ctxoptions'] is not None
303 assert s[b'verifymode'] is not None
243 assert s[b'verifymode'] is not None
304
244
305 return s
245 return s
306
246
307
247
308 def protocolsettings(protocol):
248 def protocolsettings(protocol):
309 """Resolve the protocol for a config value.
249 """Resolve the protocol for a config value.
310
250
311 Returns a 3-tuple of (protocol, options, ui value) where the first
251 Returns a 3-tuple of (protocol, options, ui value) where the first
312 2 items are values used by SSLContext and the last is a string value
252 2 items are values used by SSLContext and the last is a string value
313 of the ``minimumprotocol`` config option equivalent.
253 of the ``minimumprotocol`` config option equivalent.
314 """
254 """
315 if protocol not in configprotocols:
255 if protocol not in configprotocols:
316 raise ValueError(b'protocol value not supported: %s' % protocol)
256 raise ValueError(b'protocol value not supported: %s' % protocol)
317
257
318 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
258 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
319 # that both ends support, including TLS protocols. On legacy stacks,
259 # that both ends support, including TLS protocols. On legacy stacks,
320 # the highest it likely goes is TLS 1.0. On modern stacks, it can
260 # the highest it likely goes is TLS 1.0. On modern stacks, it can
321 # support TLS 1.2.
261 # support TLS 1.2.
322 #
262 #
323 # The PROTOCOL_TLSv* constants select a specific TLS version
263 # The PROTOCOL_TLSv* constants select a specific TLS version
324 # only (as opposed to multiple versions). So the method for
264 # only (as opposed to multiple versions). So the method for
325 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
265 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
326 # disable protocols via SSLContext.options and OP_NO_* constants.
266 # disable protocols via SSLContext.options and OP_NO_* constants.
327 # However, SSLContext.options doesn't work unless we have the
267 # However, SSLContext.options doesn't work unless we have the
328 # full/real SSLContext available to us.
268 # full/real SSLContext available to us.
329 if supportedprotocols == {b'tls1.0'}:
269 if supportedprotocols == {b'tls1.0'}:
330 if protocol != b'tls1.0':
270 if protocol != b'tls1.0':
331 raise error.Abort(
271 raise error.Abort(
332 _(b'current Python does not support protocol setting %s')
272 _(b'current Python does not support protocol setting %s')
333 % protocol,
273 % protocol,
334 hint=_(
274 hint=_(
335 b'upgrade Python or disable setting since '
275 b'upgrade Python or disable setting since '
336 b'only TLS 1.0 is supported'
276 b'only TLS 1.0 is supported'
337 ),
277 ),
338 )
278 )
339
279
340 return ssl.PROTOCOL_TLSv1, 0, b'tls1.0'
280 return ssl.PROTOCOL_TLSv1, 0, b'tls1.0'
341
281
342 # WARNING: returned options don't work unless the modern ssl module
282 # WARNING: returned options don't work unless the modern ssl module
343 # is available. Be careful when adding options here.
283 # is available. Be careful when adding options here.
344
284
345 # SSLv2 and SSLv3 are broken. We ban them outright.
285 # SSLv2 and SSLv3 are broken. We ban them outright.
346 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
286 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
347
287
348 if protocol == b'tls1.0':
288 if protocol == b'tls1.0':
349 # Defaults above are to use TLS 1.0+
289 # Defaults above are to use TLS 1.0+
350 pass
290 pass
351 elif protocol == b'tls1.1':
291 elif protocol == b'tls1.1':
352 options |= ssl.OP_NO_TLSv1
292 options |= ssl.OP_NO_TLSv1
353 elif protocol == b'tls1.2':
293 elif protocol == b'tls1.2':
354 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
294 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
355 else:
295 else:
356 raise error.Abort(_(b'this should not happen'))
296 raise error.Abort(_(b'this should not happen'))
357
297
358 # Prevent CRIME.
298 # Prevent CRIME.
359 # There is no guarantee this attribute is defined on the module.
299 # There is no guarantee this attribute is defined on the module.
360 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
300 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
361
301
362 return ssl.PROTOCOL_SSLv23, options, protocol
302 return ssl.PROTOCOL_SSLv23, options, protocol
363
303
364
304
365 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
305 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
366 """Add SSL/TLS to a socket.
306 """Add SSL/TLS to a socket.
367
307
368 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
308 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
369 choices based on what security options are available.
309 choices based on what security options are available.
370
310
371 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
311 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
372 the following additional arguments:
312 the following additional arguments:
373
313
374 * serverhostname - The expected hostname of the remote server. If the
314 * serverhostname - The expected hostname of the remote server. If the
375 server (and client) support SNI, this tells the server which certificate
315 server (and client) support SNI, this tells the server which certificate
376 to use.
316 to use.
377 """
317 """
378 if not serverhostname:
318 if not serverhostname:
379 raise error.Abort(_(b'serverhostname argument is required'))
319 raise error.Abort(_(b'serverhostname argument is required'))
380
320
381 if b'SSLKEYLOGFILE' in encoding.environ:
321 if b'SSLKEYLOGFILE' in encoding.environ:
382 try:
322 try:
383 import sslkeylog
323 import sslkeylog
384
324
385 sslkeylog.set_keylog(
325 sslkeylog.set_keylog(
386 pycompat.fsdecode(encoding.environ[b'SSLKEYLOGFILE'])
326 pycompat.fsdecode(encoding.environ[b'SSLKEYLOGFILE'])
387 )
327 )
388 ui.warnnoi18n(
328 ui.warnnoi18n(
389 b'sslkeylog enabled by SSLKEYLOGFILE environment variable\n'
329 b'sslkeylog enabled by SSLKEYLOGFILE environment variable\n'
390 )
330 )
391 except ImportError:
331 except ImportError:
392 ui.warnnoi18n(
332 ui.warnnoi18n(
393 b'sslkeylog module missing, '
333 b'sslkeylog module missing, '
394 b'but SSLKEYLOGFILE set in environment\n'
334 b'but SSLKEYLOGFILE set in environment\n'
395 )
335 )
396
336
397 for f in (keyfile, certfile):
337 for f in (keyfile, certfile):
398 if f and not os.path.exists(f):
338 if f and not os.path.exists(f):
399 raise error.Abort(
339 raise error.Abort(
400 _(b'certificate file (%s) does not exist; cannot connect to %s')
340 _(b'certificate file (%s) does not exist; cannot connect to %s')
401 % (f, pycompat.bytesurl(serverhostname)),
341 % (f, pycompat.bytesurl(serverhostname)),
402 hint=_(
342 hint=_(
403 b'restore missing file or fix references '
343 b'restore missing file or fix references '
404 b'in Mercurial config'
344 b'in Mercurial config'
405 ),
345 ),
406 )
346 )
407
347
408 settings = _hostsettings(ui, serverhostname)
348 settings = _hostsettings(ui, serverhostname)
409
349
410 # We can't use ssl.create_default_context() because it calls
350 # We can't use ssl.create_default_context() because it calls
411 # load_default_certs() unless CA arguments are passed to it. We want to
351 # load_default_certs() unless CA arguments are passed to it. We want to
412 # have explicit control over CA loading because implicitly loading
352 # have explicit control over CA loading because implicitly loading
413 # CAs may undermine the user's intent. For example, a user may define a CA
353 # CAs may undermine the user's intent. For example, a user may define a CA
414 # bundle with a specific CA cert removed. If the system/default CA bundle
354 # bundle with a specific CA cert removed. If the system/default CA bundle
415 # is loaded and contains that removed CA, you've just undone the user's
355 # is loaded and contains that removed CA, you've just undone the user's
416 # choice.
356 # choice.
417 sslcontext = SSLContext(settings[b'protocol'])
357 sslcontext = ssl.SSLContext(settings[b'protocol'])
418
358
419 # This is a no-op unless using modern ssl.
359 # This is a no-op unless using modern ssl.
420 sslcontext.options |= settings[b'ctxoptions']
360 sslcontext.options |= settings[b'ctxoptions']
421
361
422 # This still works on our fake SSLContext.
362 # This still works on our fake SSLContext.
423 sslcontext.verify_mode = settings[b'verifymode']
363 sslcontext.verify_mode = settings[b'verifymode']
424
364
425 if settings[b'ciphers']:
365 if settings[b'ciphers']:
426 try:
366 try:
427 sslcontext.set_ciphers(pycompat.sysstr(settings[b'ciphers']))
367 sslcontext.set_ciphers(pycompat.sysstr(settings[b'ciphers']))
428 except ssl.SSLError as e:
368 except ssl.SSLError as e:
429 raise error.Abort(
369 raise error.Abort(
430 _(b'could not set ciphers: %s')
370 _(b'could not set ciphers: %s')
431 % stringutil.forcebytestr(e.args[0]),
371 % stringutil.forcebytestr(e.args[0]),
432 hint=_(b'change cipher string (%s) in config')
372 hint=_(b'change cipher string (%s) in config')
433 % settings[b'ciphers'],
373 % settings[b'ciphers'],
434 )
374 )
435
375
436 if certfile is not None:
376 if certfile is not None:
437
377
438 def password():
378 def password():
439 f = keyfile or certfile
379 f = keyfile or certfile
440 return ui.getpass(_(b'passphrase for %s: ') % f, b'')
380 return ui.getpass(_(b'passphrase for %s: ') % f, b'')
441
381
442 sslcontext.load_cert_chain(certfile, keyfile, password)
382 sslcontext.load_cert_chain(certfile, keyfile, password)
443
383
444 if settings[b'cafile'] is not None:
384 if settings[b'cafile'] is not None:
445 try:
385 try:
446 sslcontext.load_verify_locations(cafile=settings[b'cafile'])
386 sslcontext.load_verify_locations(cafile=settings[b'cafile'])
447 except ssl.SSLError as e:
387 except ssl.SSLError as e:
448 if len(e.args) == 1: # pypy has different SSLError args
388 if len(e.args) == 1: # pypy has different SSLError args
449 msg = e.args[0]
389 msg = e.args[0]
450 else:
390 else:
451 msg = e.args[1]
391 msg = e.args[1]
452 raise error.Abort(
392 raise error.Abort(
453 _(b'error loading CA file %s: %s')
393 _(b'error loading CA file %s: %s')
454 % (settings[b'cafile'], stringutil.forcebytestr(msg)),
394 % (settings[b'cafile'], stringutil.forcebytestr(msg)),
455 hint=_(b'file is empty or malformed?'),
395 hint=_(b'file is empty or malformed?'),
456 )
396 )
457 caloaded = True
397 caloaded = True
458 elif settings[b'allowloaddefaultcerts']:
398 elif settings[b'allowloaddefaultcerts']:
459 # This is a no-op on old Python.
399 # This is a no-op on old Python.
460 sslcontext.load_default_certs()
400 sslcontext.load_default_certs()
461 caloaded = True
401 caloaded = True
462 else:
402 else:
463 caloaded = False
403 caloaded = False
464
404
465 try:
405 try:
466 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
406 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
467 except ssl.SSLError as e:
407 except ssl.SSLError as e:
468 # If we're doing certificate verification and no CA certs are loaded,
408 # If we're doing certificate verification and no CA certs are loaded,
469 # that is almost certainly the reason why verification failed. Provide
409 # that is almost certainly the reason why verification failed. Provide
470 # a hint to the user.
410 # a hint to the user.
471 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
411 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
472 # only show this warning if modern ssl is available.
412 # only show this warning if modern ssl is available.
473 # The exception handler is here to handle bugs around cert attributes:
413 # The exception handler is here to handle bugs around cert attributes:
474 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
414 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
475 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
415 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
476 # non-empty list, but the following conditional is otherwise True.
416 # non-empty list, but the following conditional is otherwise True.
477 try:
417 try:
478 if (
418 if (
479 caloaded
419 caloaded
480 and settings[b'verifymode'] == ssl.CERT_REQUIRED
420 and settings[b'verifymode'] == ssl.CERT_REQUIRED
481 and modernssl
421 and modernssl
482 and not sslcontext.get_ca_certs()
422 and not sslcontext.get_ca_certs()
483 ):
423 ):
484 ui.warn(
424 ui.warn(
485 _(
425 _(
486 b'(an attempt was made to load CA certificates but '
426 b'(an attempt was made to load CA certificates but '
487 b'none were loaded; see '
427 b'none were loaded; see '
488 b'https://mercurial-scm.org/wiki/SecureConnections '
428 b'https://mercurial-scm.org/wiki/SecureConnections '
489 b'for how to configure Mercurial to avoid this '
429 b'for how to configure Mercurial to avoid this '
490 b'error)\n'
430 b'error)\n'
491 )
431 )
492 )
432 )
493 except ssl.SSLError:
433 except ssl.SSLError:
494 pass
434 pass
495
435
496 # Try to print more helpful error messages for known failures.
436 # Try to print more helpful error messages for known failures.
497 if util.safehasattr(e, b'reason'):
437 if util.safehasattr(e, b'reason'):
498 # This error occurs when the client and server don't share a
438 # This error occurs when the client and server don't share a
499 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
439 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
500 # outright. Hopefully the reason for this error is that we require
440 # outright. Hopefully the reason for this error is that we require
501 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
441 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
502 # reason, try to emit an actionable warning.
442 # reason, try to emit an actionable warning.
503 if e.reason == 'UNSUPPORTED_PROTOCOL':
443 if e.reason == 'UNSUPPORTED_PROTOCOL':
504 # We attempted TLS 1.0+.
444 # We attempted TLS 1.0+.
505 if settings[b'protocolui'] == b'tls1.0':
445 if settings[b'protocolui'] == b'tls1.0':
506 # We support more than just TLS 1.0+. If this happens,
446 # We support more than just TLS 1.0+. If this happens,
507 # the likely scenario is either the client or the server
447 # the likely scenario is either the client or the server
508 # is really old. (e.g. server doesn't support TLS 1.0+ or
448 # is really old. (e.g. server doesn't support TLS 1.0+ or
509 # client doesn't support modern TLS versions introduced
449 # client doesn't support modern TLS versions introduced
510 # several years from when this comment was written).
450 # several years from when this comment was written).
511 if supportedprotocols != {b'tls1.0'}:
451 if supportedprotocols != {b'tls1.0'}:
512 ui.warn(
452 ui.warn(
513 _(
453 _(
514 b'(could not communicate with %s using security '
454 b'(could not communicate with %s using security '
515 b'protocols %s; if you are using a modern Mercurial '
455 b'protocols %s; if you are using a modern Mercurial '
516 b'version, consider contacting the operator of this '
456 b'version, consider contacting the operator of this '
517 b'server; see '
457 b'server; see '
518 b'https://mercurial-scm.org/wiki/SecureConnections '
458 b'https://mercurial-scm.org/wiki/SecureConnections '
519 b'for more info)\n'
459 b'for more info)\n'
520 )
460 )
521 % (
461 % (
522 pycompat.bytesurl(serverhostname),
462 pycompat.bytesurl(serverhostname),
523 b', '.join(sorted(supportedprotocols)),
463 b', '.join(sorted(supportedprotocols)),
524 )
464 )
525 )
465 )
526 else:
466 else:
527 ui.warn(
467 ui.warn(
528 _(
468 _(
529 b'(could not communicate with %s using TLS 1.0; the '
469 b'(could not communicate with %s using TLS 1.0; the '
530 b'likely cause of this is the server no longer '
470 b'likely cause of this is the server no longer '
531 b'supports TLS 1.0 because it has known security '
471 b'supports TLS 1.0 because it has known security '
532 b'vulnerabilities; see '
472 b'vulnerabilities; see '
533 b'https://mercurial-scm.org/wiki/SecureConnections '
473 b'https://mercurial-scm.org/wiki/SecureConnections '
534 b'for more info)\n'
474 b'for more info)\n'
535 )
475 )
536 % pycompat.bytesurl(serverhostname)
476 % pycompat.bytesurl(serverhostname)
537 )
477 )
538 else:
478 else:
539 # We attempted TLS 1.1+. We can only get here if the client
479 # We attempted TLS 1.1+. We can only get here if the client
540 # supports the configured protocol. So the likely reason is
480 # supports the configured protocol. So the likely reason is
541 # the client wants better security than the server can
481 # the client wants better security than the server can
542 # offer.
482 # offer.
543 ui.warn(
483 ui.warn(
544 _(
484 _(
545 b'(could not negotiate a common security protocol (%s+) '
485 b'(could not negotiate a common security protocol (%s+) '
546 b'with %s; the likely cause is Mercurial is configured '
486 b'with %s; the likely cause is Mercurial is configured '
547 b'to be more secure than the server can support)\n'
487 b'to be more secure than the server can support)\n'
548 )
488 )
549 % (
489 % (
550 settings[b'protocolui'],
490 settings[b'protocolui'],
551 pycompat.bytesurl(serverhostname),
491 pycompat.bytesurl(serverhostname),
552 )
492 )
553 )
493 )
554 ui.warn(
494 ui.warn(
555 _(
495 _(
556 b'(consider contacting the operator of this '
496 b'(consider contacting the operator of this '
557 b'server and ask them to support modern TLS '
497 b'server and ask them to support modern TLS '
558 b'protocol versions; or, set '
498 b'protocol versions; or, set '
559 b'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
499 b'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
560 b'use of legacy, less secure protocols when '
500 b'use of legacy, less secure protocols when '
561 b'communicating with this server)\n'
501 b'communicating with this server)\n'
562 )
502 )
563 % pycompat.bytesurl(serverhostname)
503 % pycompat.bytesurl(serverhostname)
564 )
504 )
565 ui.warn(
505 ui.warn(
566 _(
506 _(
567 b'(see https://mercurial-scm.org/wiki/SecureConnections '
507 b'(see https://mercurial-scm.org/wiki/SecureConnections '
568 b'for more info)\n'
508 b'for more info)\n'
569 )
509 )
570 )
510 )
571
511
572 elif e.reason == 'CERTIFICATE_VERIFY_FAILED' and pycompat.iswindows:
512 elif e.reason == 'CERTIFICATE_VERIFY_FAILED' and pycompat.iswindows:
573
513
574 ui.warn(
514 ui.warn(
575 _(
515 _(
576 b'(the full certificate chain may not be available '
516 b'(the full certificate chain may not be available '
577 b'locally; see "hg help debugssl")\n'
517 b'locally; see "hg help debugssl")\n'
578 )
518 )
579 )
519 )
580 raise
520 raise
581
521
582 # check if wrap_socket failed silently because socket had been
522 # check if wrap_socket failed silently because socket had been
583 # closed
523 # closed
584 # - see http://bugs.python.org/issue13721
524 # - see http://bugs.python.org/issue13721
585 if not sslsocket.cipher():
525 if not sslsocket.cipher():
586 raise error.Abort(_(b'ssl connection failed'))
526 raise error.Abort(_(b'ssl connection failed'))
587
527
588 sslsocket._hgstate = {
528 sslsocket._hgstate = {
589 b'caloaded': caloaded,
529 b'caloaded': caloaded,
590 b'hostname': serverhostname,
530 b'hostname': serverhostname,
591 b'settings': settings,
531 b'settings': settings,
592 b'ui': ui,
532 b'ui': ui,
593 }
533 }
594
534
595 return sslsocket
535 return sslsocket
596
536
597
537
598 def wrapserversocket(
538 def wrapserversocket(
599 sock, ui, certfile=None, keyfile=None, cafile=None, requireclientcert=False
539 sock, ui, certfile=None, keyfile=None, cafile=None, requireclientcert=False
600 ):
540 ):
601 """Wrap a socket for use by servers.
541 """Wrap a socket for use by servers.
602
542
603 ``certfile`` and ``keyfile`` specify the files containing the certificate's
543 ``certfile`` and ``keyfile`` specify the files containing the certificate's
604 public and private keys, respectively. Both keys can be defined in the same
544 public and private keys, respectively. Both keys can be defined in the same
605 file via ``certfile`` (the private key must come first in the file).
545 file via ``certfile`` (the private key must come first in the file).
606
546
607 ``cafile`` defines the path to certificate authorities.
547 ``cafile`` defines the path to certificate authorities.
608
548
609 ``requireclientcert`` specifies whether to require client certificates.
549 ``requireclientcert`` specifies whether to require client certificates.
610
550
611 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
551 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
612 """
552 """
613 # This function is not used much by core Mercurial, so the error messaging
553 # This function is not used much by core Mercurial, so the error messaging
614 # doesn't have to be as detailed as for wrapsocket().
554 # doesn't have to be as detailed as for wrapsocket().
615 for f in (certfile, keyfile, cafile):
555 for f in (certfile, keyfile, cafile):
616 if f and not os.path.exists(f):
556 if f and not os.path.exists(f):
617 raise error.Abort(
557 raise error.Abort(
618 _(b'referenced certificate file (%s) does not exist') % f
558 _(b'referenced certificate file (%s) does not exist') % f
619 )
559 )
620
560
621 protocol, options, _protocolui = protocolsettings(b'tls1.0')
561 protocol, options, _protocolui = protocolsettings(b'tls1.0')
622
562
623 # This config option is intended for use in tests only. It is a giant
563 # This config option is intended for use in tests only. It is a giant
624 # footgun to kill security. Don't define it.
564 # footgun to kill security. Don't define it.
625 exactprotocol = ui.config(b'devel', b'serverexactprotocol')
565 exactprotocol = ui.config(b'devel', b'serverexactprotocol')
626 if exactprotocol == b'tls1.0':
566 if exactprotocol == b'tls1.0':
627 protocol = ssl.PROTOCOL_TLSv1
567 protocol = ssl.PROTOCOL_TLSv1
628 elif exactprotocol == b'tls1.1':
568 elif exactprotocol == b'tls1.1':
629 if b'tls1.1' not in supportedprotocols:
569 if b'tls1.1' not in supportedprotocols:
630 raise error.Abort(_(b'TLS 1.1 not supported by this Python'))
570 raise error.Abort(_(b'TLS 1.1 not supported by this Python'))
631 protocol = ssl.PROTOCOL_TLSv1_1
571 protocol = ssl.PROTOCOL_TLSv1_1
632 elif exactprotocol == b'tls1.2':
572 elif exactprotocol == b'tls1.2':
633 if b'tls1.2' not in supportedprotocols:
573 if b'tls1.2' not in supportedprotocols:
634 raise error.Abort(_(b'TLS 1.2 not supported by this Python'))
574 raise error.Abort(_(b'TLS 1.2 not supported by this Python'))
635 protocol = ssl.PROTOCOL_TLSv1_2
575 protocol = ssl.PROTOCOL_TLSv1_2
636 elif exactprotocol:
576 elif exactprotocol:
637 raise error.Abort(
577 raise error.Abort(
638 _(b'invalid value for serverexactprotocol: %s') % exactprotocol
578 _(b'invalid value for serverexactprotocol: %s') % exactprotocol
639 )
579 )
640
580
641 if modernssl:
581 if modernssl:
642 # We /could/ use create_default_context() here since it doesn't load
582 # We /could/ use create_default_context() here since it doesn't load
643 # CAs when configured for client auth. However, it is hard-coded to
583 # CAs when configured for client auth. However, it is hard-coded to
644 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
584 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
645 sslcontext = SSLContext(protocol)
585 sslcontext = ssl.SSLContext(protocol)
646 sslcontext.options |= options
586 sslcontext.options |= options
647
587
648 # Improve forward secrecy.
588 # Improve forward secrecy.
649 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
589 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
650 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
590 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
651
591
652 # Use the list of more secure ciphers if found in the ssl module.
592 # Use the list of more secure ciphers if found in the ssl module.
653 if util.safehasattr(ssl, b'_RESTRICTED_SERVER_CIPHERS'):
593 if util.safehasattr(ssl, b'_RESTRICTED_SERVER_CIPHERS'):
654 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
594 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
655 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
595 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
656 else:
596 else:
657 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
597 sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
658
598
659 if requireclientcert:
599 if requireclientcert:
660 sslcontext.verify_mode = ssl.CERT_REQUIRED
600 sslcontext.verify_mode = ssl.CERT_REQUIRED
661 else:
601 else:
662 sslcontext.verify_mode = ssl.CERT_NONE
602 sslcontext.verify_mode = ssl.CERT_NONE
663
603
664 if certfile or keyfile:
604 if certfile or keyfile:
665 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
605 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
666
606
667 if cafile:
607 if cafile:
668 sslcontext.load_verify_locations(cafile=cafile)
608 sslcontext.load_verify_locations(cafile=cafile)
669
609
670 return sslcontext.wrap_socket(sock, server_side=True)
610 return sslcontext.wrap_socket(sock, server_side=True)
671
611
672
612
673 class wildcarderror(Exception):
613 class wildcarderror(Exception):
674 """Represents an error parsing wildcards in DNS name."""
614 """Represents an error parsing wildcards in DNS name."""
675
615
676
616
677 def _dnsnamematch(dn, hostname, maxwildcards=1):
617 def _dnsnamematch(dn, hostname, maxwildcards=1):
678 """Match DNS names according RFC 6125 section 6.4.3.
618 """Match DNS names according RFC 6125 section 6.4.3.
679
619
680 This code is effectively copied from CPython's ssl._dnsname_match.
620 This code is effectively copied from CPython's ssl._dnsname_match.
681
621
682 Returns a bool indicating whether the expected hostname matches
622 Returns a bool indicating whether the expected hostname matches
683 the value in ``dn``.
623 the value in ``dn``.
684 """
624 """
685 pats = []
625 pats = []
686 if not dn:
626 if not dn:
687 return False
627 return False
688 dn = pycompat.bytesurl(dn)
628 dn = pycompat.bytesurl(dn)
689 hostname = pycompat.bytesurl(hostname)
629 hostname = pycompat.bytesurl(hostname)
690
630
691 pieces = dn.split(b'.')
631 pieces = dn.split(b'.')
692 leftmost = pieces[0]
632 leftmost = pieces[0]
693 remainder = pieces[1:]
633 remainder = pieces[1:]
694 wildcards = leftmost.count(b'*')
634 wildcards = leftmost.count(b'*')
695 if wildcards > maxwildcards:
635 if wildcards > maxwildcards:
696 raise wildcarderror(
636 raise wildcarderror(
697 _(b'too many wildcards in certificate DNS name: %s') % dn
637 _(b'too many wildcards in certificate DNS name: %s') % dn
698 )
638 )
699
639
700 # speed up common case w/o wildcards
640 # speed up common case w/o wildcards
701 if not wildcards:
641 if not wildcards:
702 return dn.lower() == hostname.lower()
642 return dn.lower() == hostname.lower()
703
643
704 # RFC 6125, section 6.4.3, subitem 1.
644 # RFC 6125, section 6.4.3, subitem 1.
705 # The client SHOULD NOT attempt to match a presented identifier in which
645 # The client SHOULD NOT attempt to match a presented identifier in which
706 # the wildcard character comprises a label other than the left-most label.
646 # the wildcard character comprises a label other than the left-most label.
707 if leftmost == b'*':
647 if leftmost == b'*':
708 # When '*' is a fragment by itself, it matches a non-empty dotless
648 # When '*' is a fragment by itself, it matches a non-empty dotless
709 # fragment.
649 # fragment.
710 pats.append(b'[^.]+')
650 pats.append(b'[^.]+')
711 elif leftmost.startswith(b'xn--') or hostname.startswith(b'xn--'):
651 elif leftmost.startswith(b'xn--') or hostname.startswith(b'xn--'):
712 # RFC 6125, section 6.4.3, subitem 3.
652 # RFC 6125, section 6.4.3, subitem 3.
713 # The client SHOULD NOT attempt to match a presented identifier
653 # The client SHOULD NOT attempt to match a presented identifier
714 # where the wildcard character is embedded within an A-label or
654 # where the wildcard character is embedded within an A-label or
715 # U-label of an internationalized domain name.
655 # U-label of an internationalized domain name.
716 pats.append(stringutil.reescape(leftmost))
656 pats.append(stringutil.reescape(leftmost))
717 else:
657 else:
718 # Otherwise, '*' matches any dotless string, e.g. www*
658 # Otherwise, '*' matches any dotless string, e.g. www*
719 pats.append(stringutil.reescape(leftmost).replace(br'\*', b'[^.]*'))
659 pats.append(stringutil.reescape(leftmost).replace(br'\*', b'[^.]*'))
720
660
721 # add the remaining fragments, ignore any wildcards
661 # add the remaining fragments, ignore any wildcards
722 for frag in remainder:
662 for frag in remainder:
723 pats.append(stringutil.reescape(frag))
663 pats.append(stringutil.reescape(frag))
724
664
725 pat = re.compile(br'\A' + br'\.'.join(pats) + br'\Z', re.IGNORECASE)
665 pat = re.compile(br'\A' + br'\.'.join(pats) + br'\Z', re.IGNORECASE)
726 return pat.match(hostname) is not None
666 return pat.match(hostname) is not None
727
667
728
668
729 def _verifycert(cert, hostname):
669 def _verifycert(cert, hostname):
730 '''Verify that cert (in socket.getpeercert() format) matches hostname.
670 '''Verify that cert (in socket.getpeercert() format) matches hostname.
731 CRLs is not handled.
671 CRLs is not handled.
732
672
733 Returns error message if any problems are found and None on success.
673 Returns error message if any problems are found and None on success.
734 '''
674 '''
735 if not cert:
675 if not cert:
736 return _(b'no certificate received')
676 return _(b'no certificate received')
737
677
738 dnsnames = []
678 dnsnames = []
739 san = cert.get('subjectAltName', [])
679 san = cert.get('subjectAltName', [])
740 for key, value in san:
680 for key, value in san:
741 if key == 'DNS':
681 if key == 'DNS':
742 try:
682 try:
743 if _dnsnamematch(value, hostname):
683 if _dnsnamematch(value, hostname):
744 return
684 return
745 except wildcarderror as e:
685 except wildcarderror as e:
746 return stringutil.forcebytestr(e.args[0])
686 return stringutil.forcebytestr(e.args[0])
747
687
748 dnsnames.append(value)
688 dnsnames.append(value)
749
689
750 if not dnsnames:
690 if not dnsnames:
751 # The subject is only checked when there is no DNS in subjectAltName.
691 # The subject is only checked when there is no DNS in subjectAltName.
752 for sub in cert.get('subject', []):
692 for sub in cert.get('subject', []):
753 for key, value in sub:
693 for key, value in sub:
754 # According to RFC 2818 the most specific Common Name must
694 # According to RFC 2818 the most specific Common Name must
755 # be used.
695 # be used.
756 if key == 'commonName':
696 if key == 'commonName':
757 # 'subject' entries are unicode.
697 # 'subject' entries are unicode.
758 try:
698 try:
759 value = value.encode('ascii')
699 value = value.encode('ascii')
760 except UnicodeEncodeError:
700 except UnicodeEncodeError:
761 return _(b'IDN in certificate not supported')
701 return _(b'IDN in certificate not supported')
762
702
763 try:
703 try:
764 if _dnsnamematch(value, hostname):
704 if _dnsnamematch(value, hostname):
765 return
705 return
766 except wildcarderror as e:
706 except wildcarderror as e:
767 return stringutil.forcebytestr(e.args[0])
707 return stringutil.forcebytestr(e.args[0])
768
708
769 dnsnames.append(value)
709 dnsnames.append(value)
770
710
771 dnsnames = [pycompat.bytesurl(d) for d in dnsnames]
711 dnsnames = [pycompat.bytesurl(d) for d in dnsnames]
772 if len(dnsnames) > 1:
712 if len(dnsnames) > 1:
773 return _(b'certificate is for %s') % b', '.join(dnsnames)
713 return _(b'certificate is for %s') % b', '.join(dnsnames)
774 elif len(dnsnames) == 1:
714 elif len(dnsnames) == 1:
775 return _(b'certificate is for %s') % dnsnames[0]
715 return _(b'certificate is for %s') % dnsnames[0]
776 else:
716 else:
777 return _(b'no commonName or subjectAltName found in certificate')
717 return _(b'no commonName or subjectAltName found in certificate')
778
718
779
719
780 def _plainapplepython():
720 def _plainapplepython():
781 """return true if this seems to be a pure Apple Python that
721 """return true if this seems to be a pure Apple Python that
782 * is unfrozen and presumably has the whole mercurial module in the file
722 * is unfrozen and presumably has the whole mercurial module in the file
783 system
723 system
784 * presumably is an Apple Python that uses Apple OpenSSL which has patches
724 * presumably is an Apple Python that uses Apple OpenSSL which has patches
785 for using system certificate store CAs in addition to the provided
725 for using system certificate store CAs in addition to the provided
786 cacerts file
726 cacerts file
787 """
727 """
788 if (
728 if (
789 not pycompat.isdarwin
729 not pycompat.isdarwin
790 or resourceutil.mainfrozen()
730 or resourceutil.mainfrozen()
791 or not pycompat.sysexecutable
731 or not pycompat.sysexecutable
792 ):
732 ):
793 return False
733 return False
794 exe = os.path.realpath(pycompat.sysexecutable).lower()
734 exe = os.path.realpath(pycompat.sysexecutable).lower()
795 return exe.startswith(b'/usr/bin/python') or exe.startswith(
735 return exe.startswith(b'/usr/bin/python') or exe.startswith(
796 b'/system/library/frameworks/python.framework/'
736 b'/system/library/frameworks/python.framework/'
797 )
737 )
798
738
799
739
800 _systemcacertpaths = [
740 _systemcacertpaths = [
801 # RHEL, CentOS, and Fedora
741 # RHEL, CentOS, and Fedora
802 b'/etc/pki/tls/certs/ca-bundle.trust.crt',
742 b'/etc/pki/tls/certs/ca-bundle.trust.crt',
803 # Debian, Ubuntu, Gentoo
743 # Debian, Ubuntu, Gentoo
804 b'/etc/ssl/certs/ca-certificates.crt',
744 b'/etc/ssl/certs/ca-certificates.crt',
805 ]
745 ]
806
746
807
747
808 def _defaultcacerts(ui):
748 def _defaultcacerts(ui):
809 """return path to default CA certificates or None.
749 """return path to default CA certificates or None.
810
750
811 It is assumed this function is called when the returned certificates
751 It is assumed this function is called when the returned certificates
812 file will actually be used to validate connections. Therefore this
752 file will actually be used to validate connections. Therefore this
813 function may print warnings or debug messages assuming this usage.
753 function may print warnings or debug messages assuming this usage.
814
754
815 We don't print a message when the Python is able to load default
755 We don't print a message when the Python is able to load default
816 CA certs because this scenario is detected at socket connect time.
756 CA certs because this scenario is detected at socket connect time.
817 """
757 """
818 # The "certifi" Python package provides certificates. If it is installed
758 # The "certifi" Python package provides certificates. If it is installed
819 # and usable, assume the user intends it to be used and use it.
759 # and usable, assume the user intends it to be used and use it.
820 try:
760 try:
821 import certifi
761 import certifi
822
762
823 certs = certifi.where()
763 certs = certifi.where()
824 if os.path.exists(certs):
764 if os.path.exists(certs):
825 ui.debug(b'using ca certificates from certifi\n')
765 ui.debug(b'using ca certificates from certifi\n')
826 return pycompat.fsencode(certs)
766 return pycompat.fsencode(certs)
827 except (ImportError, AttributeError):
767 except (ImportError, AttributeError):
828 pass
768 pass
829
769
830 # On Windows, only the modern ssl module is capable of loading the system
770 # On Windows, only the modern ssl module is capable of loading the system
831 # CA certificates. If we're not capable of doing that, emit a warning
771 # CA certificates. If we're not capable of doing that, emit a warning
832 # because we'll get a certificate verification error later and the lack
772 # because we'll get a certificate verification error later and the lack
833 # of loaded CA certificates will be the reason why.
773 # of loaded CA certificates will be the reason why.
834 # Assertion: this code is only called if certificates are being verified.
774 # Assertion: this code is only called if certificates are being verified.
835 if pycompat.iswindows:
775 if pycompat.iswindows:
836 if not _canloaddefaultcerts:
776 if not _canloaddefaultcerts:
837 ui.warn(
777 ui.warn(
838 _(
778 _(
839 b'(unable to load Windows CA certificates; see '
779 b'(unable to load Windows CA certificates; see '
840 b'https://mercurial-scm.org/wiki/SecureConnections for '
780 b'https://mercurial-scm.org/wiki/SecureConnections for '
841 b'how to configure Mercurial to avoid this message)\n'
781 b'how to configure Mercurial to avoid this message)\n'
842 )
782 )
843 )
783 )
844
784
845 return None
785 return None
846
786
847 # Apple's OpenSSL has patches that allow a specially constructed certificate
787 # Apple's OpenSSL has patches that allow a specially constructed certificate
848 # to load the system CA store. If we're running on Apple Python, use this
788 # to load the system CA store. If we're running on Apple Python, use this
849 # trick.
789 # trick.
850 if _plainapplepython():
790 if _plainapplepython():
851 dummycert = os.path.join(
791 dummycert = os.path.join(
852 os.path.dirname(pycompat.fsencode(__file__)), b'dummycert.pem'
792 os.path.dirname(pycompat.fsencode(__file__)), b'dummycert.pem'
853 )
793 )
854 if os.path.exists(dummycert):
794 if os.path.exists(dummycert):
855 return dummycert
795 return dummycert
856
796
857 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
797 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
858 # load system certs, we're out of luck.
798 # load system certs, we're out of luck.
859 if pycompat.isdarwin:
799 if pycompat.isdarwin:
860 # FUTURE Consider looking for Homebrew or MacPorts installed certs
800 # FUTURE Consider looking for Homebrew or MacPorts installed certs
861 # files. Also consider exporting the keychain certs to a file during
801 # files. Also consider exporting the keychain certs to a file during
862 # Mercurial install.
802 # Mercurial install.
863 if not _canloaddefaultcerts:
803 if not _canloaddefaultcerts:
864 ui.warn(
804 ui.warn(
865 _(
805 _(
866 b'(unable to load CA certificates; see '
806 b'(unable to load CA certificates; see '
867 b'https://mercurial-scm.org/wiki/SecureConnections for '
807 b'https://mercurial-scm.org/wiki/SecureConnections for '
868 b'how to configure Mercurial to avoid this message)\n'
808 b'how to configure Mercurial to avoid this message)\n'
869 )
809 )
870 )
810 )
871 return None
811 return None
872
812
873 # / is writable on Windows. Out of an abundance of caution make sure
813 # / is writable on Windows. Out of an abundance of caution make sure
874 # we're not on Windows because paths from _systemcacerts could be installed
814 # we're not on Windows because paths from _systemcacerts could be installed
875 # by non-admin users.
815 # by non-admin users.
876 assert not pycompat.iswindows
816 assert not pycompat.iswindows
877
817
878 # Try to find CA certificates in well-known locations. We print a warning
818 # Try to find CA certificates in well-known locations. We print a warning
879 # when using a found file because we don't want too much silent magic
819 # when using a found file because we don't want too much silent magic
880 # for security settings. The expectation is that proper Mercurial
820 # for security settings. The expectation is that proper Mercurial
881 # installs will have the CA certs path defined at install time and the
821 # installs will have the CA certs path defined at install time and the
882 # installer/packager will make an appropriate decision on the user's
822 # installer/packager will make an appropriate decision on the user's
883 # behalf. We only get here and perform this setting as a feature of
823 # behalf. We only get here and perform this setting as a feature of
884 # last resort.
824 # last resort.
885 if not _canloaddefaultcerts:
825 if not _canloaddefaultcerts:
886 for path in _systemcacertpaths:
826 for path in _systemcacertpaths:
887 if os.path.isfile(path):
827 if os.path.isfile(path):
888 ui.warn(
828 ui.warn(
889 _(
829 _(
890 b'(using CA certificates from %s; if you see this '
830 b'(using CA certificates from %s; if you see this '
891 b'message, your Mercurial install is not properly '
831 b'message, your Mercurial install is not properly '
892 b'configured; see '
832 b'configured; see '
893 b'https://mercurial-scm.org/wiki/SecureConnections '
833 b'https://mercurial-scm.org/wiki/SecureConnections '
894 b'for how to configure Mercurial to avoid this '
834 b'for how to configure Mercurial to avoid this '
895 b'message)\n'
835 b'message)\n'
896 )
836 )
897 % path
837 % path
898 )
838 )
899 return path
839 return path
900
840
901 ui.warn(
841 ui.warn(
902 _(
842 _(
903 b'(unable to load CA certificates; see '
843 b'(unable to load CA certificates; see '
904 b'https://mercurial-scm.org/wiki/SecureConnections for '
844 b'https://mercurial-scm.org/wiki/SecureConnections for '
905 b'how to configure Mercurial to avoid this message)\n'
845 b'how to configure Mercurial to avoid this message)\n'
906 )
846 )
907 )
847 )
908
848
909 return None
849 return None
910
850
911
851
912 def validatesocket(sock):
852 def validatesocket(sock):
913 """Validate a socket meets security requirements.
853 """Validate a socket meets security requirements.
914
854
915 The passed socket must have been created with ``wrapsocket()``.
855 The passed socket must have been created with ``wrapsocket()``.
916 """
856 """
917 shost = sock._hgstate[b'hostname']
857 shost = sock._hgstate[b'hostname']
918 host = pycompat.bytesurl(shost)
858 host = pycompat.bytesurl(shost)
919 ui = sock._hgstate[b'ui']
859 ui = sock._hgstate[b'ui']
920 settings = sock._hgstate[b'settings']
860 settings = sock._hgstate[b'settings']
921
861
922 try:
862 try:
923 peercert = sock.getpeercert(True)
863 peercert = sock.getpeercert(True)
924 peercert2 = sock.getpeercert()
864 peercert2 = sock.getpeercert()
925 except AttributeError:
865 except AttributeError:
926 raise error.Abort(_(b'%s ssl connection error') % host)
866 raise error.Abort(_(b'%s ssl connection error') % host)
927
867
928 if not peercert:
868 if not peercert:
929 raise error.Abort(
869 raise error.Abort(
930 _(b'%s certificate error: no certificate received') % host
870 _(b'%s certificate error: no certificate received') % host
931 )
871 )
932
872
933 if settings[b'disablecertverification']:
873 if settings[b'disablecertverification']:
934 # We don't print the certificate fingerprint because it shouldn't
874 # We don't print the certificate fingerprint because it shouldn't
935 # be necessary: if the user requested certificate verification be
875 # be necessary: if the user requested certificate verification be
936 # disabled, they presumably already saw a message about the inability
876 # disabled, they presumably already saw a message about the inability
937 # to verify the certificate and this message would have printed the
877 # to verify the certificate and this message would have printed the
938 # fingerprint. So printing the fingerprint here adds little to no
878 # fingerprint. So printing the fingerprint here adds little to no
939 # value.
879 # value.
940 ui.warn(
880 ui.warn(
941 _(
881 _(
942 b'warning: connection security to %s is disabled per current '
882 b'warning: connection security to %s is disabled per current '
943 b'settings; communication is susceptible to eavesdropping '
883 b'settings; communication is susceptible to eavesdropping '
944 b'and tampering\n'
884 b'and tampering\n'
945 )
885 )
946 % host
886 % host
947 )
887 )
948 return
888 return
949
889
950 # If a certificate fingerprint is pinned, use it and only it to
890 # If a certificate fingerprint is pinned, use it and only it to
951 # validate the remote cert.
891 # validate the remote cert.
952 peerfingerprints = {
892 peerfingerprints = {
953 b'sha1': node.hex(hashutil.sha1(peercert).digest()),
893 b'sha1': node.hex(hashutil.sha1(peercert).digest()),
954 b'sha256': node.hex(hashlib.sha256(peercert).digest()),
894 b'sha256': node.hex(hashlib.sha256(peercert).digest()),
955 b'sha512': node.hex(hashlib.sha512(peercert).digest()),
895 b'sha512': node.hex(hashlib.sha512(peercert).digest()),
956 }
896 }
957
897
958 def fmtfingerprint(s):
898 def fmtfingerprint(s):
959 return b':'.join([s[x : x + 2] for x in range(0, len(s), 2)])
899 return b':'.join([s[x : x + 2] for x in range(0, len(s), 2)])
960
900
961 nicefingerprint = b'sha256:%s' % fmtfingerprint(peerfingerprints[b'sha256'])
901 nicefingerprint = b'sha256:%s' % fmtfingerprint(peerfingerprints[b'sha256'])
962
902
963 if settings[b'certfingerprints']:
903 if settings[b'certfingerprints']:
964 for hash, fingerprint in settings[b'certfingerprints']:
904 for hash, fingerprint in settings[b'certfingerprints']:
965 if peerfingerprints[hash].lower() == fingerprint:
905 if peerfingerprints[hash].lower() == fingerprint:
966 ui.debug(
906 ui.debug(
967 b'%s certificate matched fingerprint %s:%s\n'
907 b'%s certificate matched fingerprint %s:%s\n'
968 % (host, hash, fmtfingerprint(fingerprint))
908 % (host, hash, fmtfingerprint(fingerprint))
969 )
909 )
970 if settings[b'legacyfingerprint']:
910 if settings[b'legacyfingerprint']:
971 ui.warn(
911 ui.warn(
972 _(
912 _(
973 b'(SHA-1 fingerprint for %s found in legacy '
913 b'(SHA-1 fingerprint for %s found in legacy '
974 b'[hostfingerprints] section; '
914 b'[hostfingerprints] section; '
975 b'if you trust this fingerprint, remove the old '
915 b'if you trust this fingerprint, remove the old '
976 b'SHA-1 fingerprint from [hostfingerprints] and '
916 b'SHA-1 fingerprint from [hostfingerprints] and '
977 b'add the following entry to the new '
917 b'add the following entry to the new '
978 b'[hostsecurity] section: %s:fingerprints=%s)\n'
918 b'[hostsecurity] section: %s:fingerprints=%s)\n'
979 )
919 )
980 % (host, host, nicefingerprint)
920 % (host, host, nicefingerprint)
981 )
921 )
982 return
922 return
983
923
984 # Pinned fingerprint didn't match. This is a fatal error.
924 # Pinned fingerprint didn't match. This is a fatal error.
985 if settings[b'legacyfingerprint']:
925 if settings[b'legacyfingerprint']:
986 section = b'hostfingerprint'
926 section = b'hostfingerprint'
987 nice = fmtfingerprint(peerfingerprints[b'sha1'])
927 nice = fmtfingerprint(peerfingerprints[b'sha1'])
988 else:
928 else:
989 section = b'hostsecurity'
929 section = b'hostsecurity'
990 nice = b'%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
930 nice = b'%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
991 raise error.Abort(
931 raise error.Abort(
992 _(b'certificate for %s has unexpected fingerprint %s')
932 _(b'certificate for %s has unexpected fingerprint %s')
993 % (host, nice),
933 % (host, nice),
994 hint=_(b'check %s configuration') % section,
934 hint=_(b'check %s configuration') % section,
995 )
935 )
996
936
997 # Security is enabled but no CAs are loaded. We can't establish trust
937 # Security is enabled but no CAs are loaded. We can't establish trust
998 # for the cert so abort.
938 # for the cert so abort.
999 if not sock._hgstate[b'caloaded']:
939 if not sock._hgstate[b'caloaded']:
1000 raise error.Abort(
940 raise error.Abort(
1001 _(
941 _(
1002 b'unable to verify security of %s (no loaded CA certificates); '
942 b'unable to verify security of %s (no loaded CA certificates); '
1003 b'refusing to connect'
943 b'refusing to connect'
1004 )
944 )
1005 % host,
945 % host,
1006 hint=_(
946 hint=_(
1007 b'see https://mercurial-scm.org/wiki/SecureConnections for '
947 b'see https://mercurial-scm.org/wiki/SecureConnections for '
1008 b'how to configure Mercurial to avoid this error or set '
948 b'how to configure Mercurial to avoid this error or set '
1009 b'hostsecurity.%s:fingerprints=%s to trust this server'
949 b'hostsecurity.%s:fingerprints=%s to trust this server'
1010 )
950 )
1011 % (host, nicefingerprint),
951 % (host, nicefingerprint),
1012 )
952 )
1013
953
1014 msg = _verifycert(peercert2, shost)
954 msg = _verifycert(peercert2, shost)
1015 if msg:
955 if msg:
1016 raise error.Abort(
956 raise error.Abort(
1017 _(b'%s certificate error: %s') % (host, msg),
957 _(b'%s certificate error: %s') % (host, msg),
1018 hint=_(
958 hint=_(
1019 b'set hostsecurity.%s:certfingerprints=%s '
959 b'set hostsecurity.%s:certfingerprints=%s '
1020 b'config setting or use --insecure to connect '
960 b'config setting or use --insecure to connect '
1021 b'insecurely'
961 b'insecurely'
1022 )
962 )
1023 % (host, nicefingerprint),
963 % (host, nicefingerprint),
1024 )
964 )
General Comments 0
You need to be logged in to leave comments. Login now