Mercurial > public > mercurial-scm > hg-stable
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 ) |