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