comparison mercurial/sslutil.py @ 43077:687b865b95ad

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