##// END OF EJS Templates
sslutil: abort when unable to verify peer connection (BC)...
Gregory Szorc -
r29411:e1778b9c default
parent child Browse files
Show More
@@ -1,428 +1,434
1 1 # sslutil.py - SSL handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import hashlib
13 13 import os
14 14 import ssl
15 15 import sys
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 error,
20 20 util,
21 21 )
22 22
23 23 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 24 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 25 # all exposed via the "ssl" module.
26 26 #
27 27 # Depending on the version of Python being used, SSL/TLS support is either
28 28 # modern/secure or legacy/insecure. Many operations in this module have
29 29 # separate code paths depending on support in Python.
30 30
31 31 hassni = getattr(ssl, 'HAS_SNI', False)
32 32
33 33 try:
34 34 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
35 35 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
36 36 except AttributeError:
37 37 OP_NO_SSLv2 = 0x1000000
38 38 OP_NO_SSLv3 = 0x2000000
39 39
40 40 try:
41 41 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
42 42 # SSL/TLS features are available.
43 43 SSLContext = ssl.SSLContext
44 44 modernssl = True
45 45 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
46 46 except AttributeError:
47 47 modernssl = False
48 48 _canloaddefaultcerts = False
49 49
50 50 # We implement SSLContext using the interface from the standard library.
51 51 class SSLContext(object):
52 52 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
53 53 _supportsciphers = sys.version_info >= (2, 7)
54 54
55 55 def __init__(self, protocol):
56 56 # From the public interface of SSLContext
57 57 self.protocol = protocol
58 58 self.check_hostname = False
59 59 self.options = 0
60 60 self.verify_mode = ssl.CERT_NONE
61 61
62 62 # Used by our implementation.
63 63 self._certfile = None
64 64 self._keyfile = None
65 65 self._certpassword = None
66 66 self._cacerts = None
67 67 self._ciphers = None
68 68
69 69 def load_cert_chain(self, certfile, keyfile=None, password=None):
70 70 self._certfile = certfile
71 71 self._keyfile = keyfile
72 72 self._certpassword = password
73 73
74 74 def load_default_certs(self, purpose=None):
75 75 pass
76 76
77 77 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
78 78 if capath:
79 79 raise error.Abort(_('capath not supported'))
80 80 if cadata:
81 81 raise error.Abort(_('cadata not supported'))
82 82
83 83 self._cacerts = cafile
84 84
85 85 def set_ciphers(self, ciphers):
86 86 if not self._supportsciphers:
87 87 raise error.Abort(_('setting ciphers not supported'))
88 88
89 89 self._ciphers = ciphers
90 90
91 91 def wrap_socket(self, socket, server_hostname=None, server_side=False):
92 92 # server_hostname is unique to SSLContext.wrap_socket and is used
93 93 # for SNI in that context. So there's nothing for us to do with it
94 94 # in this legacy code since we don't support SNI.
95 95
96 96 args = {
97 97 'keyfile': self._keyfile,
98 98 'certfile': self._certfile,
99 99 'server_side': server_side,
100 100 'cert_reqs': self.verify_mode,
101 101 'ssl_version': self.protocol,
102 102 'ca_certs': self._cacerts,
103 103 }
104 104
105 105 if self._supportsciphers:
106 106 args['ciphers'] = self._ciphers
107 107
108 108 return ssl.wrap_socket(socket, **args)
109 109
110 110 def _hostsettings(ui, hostname):
111 111 """Obtain security settings for a hostname.
112 112
113 113 Returns a dict of settings relevant to that hostname.
114 114 """
115 115 s = {
116 116 # Whether we should attempt to load default/available CA certs
117 117 # if an explicit ``cafile`` is not defined.
118 118 'allowloaddefaultcerts': True,
119 119 # List of 2-tuple of (hash algorithm, hash).
120 120 'certfingerprints': [],
121 121 # Path to file containing concatenated CA certs. Used by
122 122 # SSLContext.load_verify_locations().
123 123 'cafile': None,
124 124 # Whether certificate verification should be disabled.
125 125 'disablecertverification': False,
126 126 # Whether the legacy [hostfingerprints] section has data for this host.
127 127 'legacyfingerprint': False,
128 128 # ssl.CERT_* constant used by SSLContext.verify_mode.
129 129 'verifymode': None,
130 130 }
131 131
132 132 # Look for fingerprints in [hostsecurity] section. Value is a list
133 133 # of <alg>:<fingerprint> strings.
134 134 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
135 135 [])
136 136 for fingerprint in fingerprints:
137 137 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
138 138 raise error.Abort(_('invalid fingerprint for %s: %s') % (
139 139 hostname, fingerprint),
140 140 hint=_('must begin with "sha1:", "sha256:", '
141 141 'or "sha512:"'))
142 142
143 143 alg, fingerprint = fingerprint.split(':', 1)
144 144 fingerprint = fingerprint.replace(':', '').lower()
145 145 s['certfingerprints'].append((alg, fingerprint))
146 146
147 147 # Fingerprints from [hostfingerprints] are always SHA-1.
148 148 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
149 149 fingerprint = fingerprint.replace(':', '').lower()
150 150 s['certfingerprints'].append(('sha1', fingerprint))
151 151 s['legacyfingerprint'] = True
152 152
153 153 # If a host cert fingerprint is defined, it is the only thing that
154 154 # matters. No need to validate CA certs.
155 155 if s['certfingerprints']:
156 156 s['verifymode'] = ssl.CERT_NONE
157 157
158 158 # If --insecure is used, don't take CAs into consideration.
159 159 elif ui.insecureconnections:
160 160 s['disablecertverification'] = True
161 161 s['verifymode'] = ssl.CERT_NONE
162 162
163 163 if ui.configbool('devel', 'disableloaddefaultcerts'):
164 164 s['allowloaddefaultcerts'] = False
165 165
166 166 # If both fingerprints and a per-host ca file are specified, issue a warning
167 167 # because users should not be surprised about what security is or isn't
168 168 # being performed.
169 169 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
170 170 if s['certfingerprints'] and cafile:
171 171 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
172 172 'fingerprints defined; using host fingerprints for '
173 173 'verification)\n') % hostname)
174 174
175 175 # Try to hook up CA certificate validation unless something above
176 176 # makes it not necessary.
177 177 if s['verifymode'] is None:
178 178 # Look at per-host ca file first.
179 179 if cafile:
180 180 cafile = util.expandpath(cafile)
181 181 if not os.path.exists(cafile):
182 182 raise error.Abort(_('path specified by %s does not exist: %s') %
183 183 ('hostsecurity.%s:verifycertsfile' % hostname,
184 184 cafile))
185 185 s['cafile'] = cafile
186 186 else:
187 187 # Find global certificates file in config.
188 188 cafile = ui.config('web', 'cacerts')
189 189
190 190 if cafile:
191 191 cafile = util.expandpath(cafile)
192 192 if not os.path.exists(cafile):
193 193 raise error.Abort(_('could not find web.cacerts: %s') %
194 194 cafile)
195 195 else:
196 196 # No global CA certs. See if we can load defaults.
197 197 cafile = _defaultcacerts()
198 198 if cafile:
199 199 ui.debug('using %s to enable OS X system CA\n' % cafile)
200 200
201 201 s['cafile'] = cafile
202 202
203 203 # Require certificate validation if CA certs are being loaded and
204 204 # verification hasn't been disabled above.
205 205 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
206 206 s['verifymode'] = ssl.CERT_REQUIRED
207 207 else:
208 208 # At this point we don't have a fingerprint, aren't being
209 209 # explicitly insecure, and can't load CA certs. Connecting
210 # at this point is insecure. But we do it for BC reasons.
211 # TODO abort here to make secure by default.
210 # is insecure. We allow the connection and abort during
211 # validation (once we have the fingerprint to print to the
212 # user).
212 213 s['verifymode'] = ssl.CERT_NONE
213 214
214 215 assert s['verifymode'] is not None
215 216
216 217 return s
217 218
218 219 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
219 220 """Add SSL/TLS to a socket.
220 221
221 222 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
222 223 choices based on what security options are available.
223 224
224 225 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
225 226 the following additional arguments:
226 227
227 228 * serverhostname - The expected hostname of the remote server. If the
228 229 server (and client) support SNI, this tells the server which certificate
229 230 to use.
230 231 """
231 232 if not serverhostname:
232 233 raise error.Abort(_('serverhostname argument is required'))
233 234
234 235 settings = _hostsettings(ui, serverhostname)
235 236
236 237 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
237 238 # that both ends support, including TLS protocols. On legacy stacks,
238 239 # the highest it likely goes in TLS 1.0. On modern stacks, it can
239 240 # support TLS 1.2.
240 241 #
241 242 # The PROTOCOL_TLSv* constants select a specific TLS version
242 243 # only (as opposed to multiple versions). So the method for
243 244 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
244 245 # disable protocols via SSLContext.options and OP_NO_* constants.
245 246 # However, SSLContext.options doesn't work unless we have the
246 247 # full/real SSLContext available to us.
247 248 #
248 249 # SSLv2 and SSLv3 are broken. We ban them outright.
249 250 if modernssl:
250 251 protocol = ssl.PROTOCOL_SSLv23
251 252 else:
252 253 protocol = ssl.PROTOCOL_TLSv1
253 254
254 255 # TODO use ssl.create_default_context() on modernssl.
255 256 sslcontext = SSLContext(protocol)
256 257
257 258 # This is a no-op on old Python.
258 259 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
259 260
260 261 # This still works on our fake SSLContext.
261 262 sslcontext.verify_mode = settings['verifymode']
262 263
263 264 if certfile is not None:
264 265 def password():
265 266 f = keyfile or certfile
266 267 return ui.getpass(_('passphrase for %s: ') % f, '')
267 268 sslcontext.load_cert_chain(certfile, keyfile, password)
268 269
269 270 if settings['cafile'] is not None:
270 271 sslcontext.load_verify_locations(cafile=settings['cafile'])
271 272 caloaded = True
272 273 elif settings['allowloaddefaultcerts']:
273 274 # This is a no-op on old Python.
274 275 sslcontext.load_default_certs()
275 276 caloaded = True
276 277 else:
277 278 caloaded = False
278 279
279 280 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
280 281 # check if wrap_socket failed silently because socket had been
281 282 # closed
282 283 # - see http://bugs.python.org/issue13721
283 284 if not sslsocket.cipher():
284 285 raise error.Abort(_('ssl connection failed'))
285 286
286 287 sslsocket._hgstate = {
287 288 'caloaded': caloaded,
288 289 'hostname': serverhostname,
289 290 'settings': settings,
290 291 'ui': ui,
291 292 }
292 293
293 294 return sslsocket
294 295
295 296 def _verifycert(cert, hostname):
296 297 '''Verify that cert (in socket.getpeercert() format) matches hostname.
297 298 CRLs is not handled.
298 299
299 300 Returns error message if any problems are found and None on success.
300 301 '''
301 302 if not cert:
302 303 return _('no certificate received')
303 304 dnsname = hostname.lower()
304 305 def matchdnsname(certname):
305 306 return (certname == dnsname or
306 307 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
307 308
308 309 san = cert.get('subjectAltName', [])
309 310 if san:
310 311 certnames = [value.lower() for key, value in san if key == 'DNS']
311 312 for name in certnames:
312 313 if matchdnsname(name):
313 314 return None
314 315 if certnames:
315 316 return _('certificate is for %s') % ', '.join(certnames)
316 317
317 318 # subject is only checked when subjectAltName is empty
318 319 for s in cert.get('subject', []):
319 320 key, value = s[0]
320 321 if key == 'commonName':
321 322 try:
322 323 # 'subject' entries are unicode
323 324 certname = value.lower().encode('ascii')
324 325 except UnicodeEncodeError:
325 326 return _('IDN in certificate not supported')
326 327 if matchdnsname(certname):
327 328 return None
328 329 return _('certificate is for %s') % certname
329 330 return _('no commonName or subjectAltName found in certificate')
330 331
331 332 def _plainapplepython():
332 333 """return true if this seems to be a pure Apple Python that
333 334 * is unfrozen and presumably has the whole mercurial module in the file
334 335 system
335 336 * presumably is an Apple Python that uses Apple OpenSSL which has patches
336 337 for using system certificate store CAs in addition to the provided
337 338 cacerts file
338 339 """
339 340 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
340 341 return False
341 342 exe = os.path.realpath(sys.executable).lower()
342 343 return (exe.startswith('/usr/bin/python') or
343 344 exe.startswith('/system/library/frameworks/python.framework/'))
344 345
345 346 def _defaultcacerts():
346 347 """return path to default CA certificates or None."""
347 348 if _plainapplepython():
348 349 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
349 350 if os.path.exists(dummycert):
350 351 return dummycert
351 352
352 353 return None
353 354
354 355 def validatesocket(sock):
355 356 """Validate a socket meets security requiremnets.
356 357
357 358 The passed socket must have been created with ``wrapsocket()``.
358 359 """
359 360 host = sock._hgstate['hostname']
360 361 ui = sock._hgstate['ui']
361 362 settings = sock._hgstate['settings']
362 363
363 364 try:
364 365 peercert = sock.getpeercert(True)
365 366 peercert2 = sock.getpeercert()
366 367 except AttributeError:
367 368 raise error.Abort(_('%s ssl connection error') % host)
368 369
369 370 if not peercert:
370 371 raise error.Abort(_('%s certificate error: '
371 372 'no certificate received') % host)
372 373
373 374 if settings['disablecertverification']:
374 375 # We don't print the certificate fingerprint because it shouldn't
375 376 # be necessary: if the user requested certificate verification be
376 377 # disabled, they presumably already saw a message about the inability
377 378 # to verify the certificate and this message would have printed the
378 379 # fingerprint. So printing the fingerprint here adds little to no
379 380 # value.
380 381 ui.warn(_('warning: connection security to %s is disabled per current '
381 382 'settings; communication is susceptible to eavesdropping '
382 383 'and tampering\n') % host)
383 384 return
384 385
385 386 # If a certificate fingerprint is pinned, use it and only it to
386 387 # validate the remote cert.
387 388 peerfingerprints = {
388 389 'sha1': hashlib.sha1(peercert).hexdigest(),
389 390 'sha256': hashlib.sha256(peercert).hexdigest(),
390 391 'sha512': hashlib.sha512(peercert).hexdigest(),
391 392 }
392 393
393 394 def fmtfingerprint(s):
394 395 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
395 396
396 397 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
397 398
398 399 if settings['certfingerprints']:
399 400 for hash, fingerprint in settings['certfingerprints']:
400 401 if peerfingerprints[hash].lower() == fingerprint:
401 402 ui.debug('%s certificate matched fingerprint %s:%s\n' %
402 403 (host, hash, fmtfingerprint(fingerprint)))
403 404 return
404 405
405 406 # Pinned fingerprint didn't match. This is a fatal error.
406 407 if settings['legacyfingerprint']:
407 408 section = 'hostfingerprint'
408 409 nice = fmtfingerprint(peerfingerprints['sha1'])
409 410 else:
410 411 section = 'hostsecurity'
411 412 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
412 413 raise error.Abort(_('certificate for %s has unexpected '
413 414 'fingerprint %s') % (host, nice),
414 415 hint=_('check %s configuration') % section)
415 416
417 # Security is enabled but no CAs are loaded. We can't establish trust
418 # for the cert so abort.
416 419 if not sock._hgstate['caloaded']:
417 ui.warn(_('warning: certificate for %s not verified '
418 '(set hostsecurity.%s:certfingerprints=%s or web.cacerts '
419 'config settings)\n') % (host, host, nicefingerprint))
420 return
420 raise error.Abort(
421 _('unable to verify security of %s (no loaded CA certificates); '
422 'refusing to connect') % host,
423 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
424 'how to configure Mercurial to avoid this error or set '
425 'hostsecurity.%s:fingerprints=%s to trust this server') %
426 (host, nicefingerprint))
421 427
422 428 msg = _verifycert(peercert2, host)
423 429 if msg:
424 430 raise error.Abort(_('%s certificate error: %s') % (host, msg),
425 431 hint=_('set hostsecurity.%s:certfingerprints=%s '
426 432 'config setting or use --insecure to connect '
427 433 'insecurely') %
428 434 (host, nicefingerprint))
@@ -1,369 +1,380
1 1 #require serve ssl
2 2
3 3 Proper https client requires the built-in ssl from Python 2.6.
4 4
5 5 Make server certificates:
6 6
7 7 $ CERTSDIR="$TESTDIR/sslcerts"
8 8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
9 9 $ PRIV=`pwd`/server.pem
10 10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
11 11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
12 12
13 13 $ hg init test
14 14 $ cd test
15 15 $ echo foo>foo
16 16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
17 17 $ echo foo>foo.d/foo
18 18 $ echo bar>foo.d/bAr.hg.d/BaR
19 19 $ echo bar>foo.d/baR.d.hg/bAR
20 20 $ hg commit -A -m 1
21 21 adding foo
22 22 adding foo.d/bAr.hg.d/BaR
23 23 adding foo.d/baR.d.hg/bAR
24 24 adding foo.d/foo
25 25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
26 26 $ cat ../hg0.pid >> $DAEMON_PIDS
27 27
28 28 cacert not found
29 29
30 30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
31 31 abort: could not find web.cacerts: no-such.pem
32 32 [255]
33 33
34 34 Test server address cannot be reused
35 35
36 36 #if windows
37 37 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 38 abort: cannot start server at ':$HGPORT':
39 39 [255]
40 40 #else
41 41 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 42 abort: cannot start server at ':$HGPORT': Address already in use
43 43 [255]
44 44 #endif
45 45 $ cd ..
46 46
47 47 Our test cert is not signed by a trusted CA. It should fail to verify if
48 48 we are able to load CA certs.
49 49
50 50 #if defaultcacerts
51 51 $ hg clone https://localhost:$HGPORT/ copy-pull
52 52 abort: error: *certificate verify failed* (glob)
53 53 [255]
54 54 #endif
55 55
56 56 Specifying a per-host certificate file that doesn't exist will abort
57 57
58 58 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
59 59 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
60 60 [255]
61 61
62 62 A malformed per-host certificate file will raise an error
63 63
64 64 $ echo baddata > badca.pem
65 65 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
66 66 abort: error: * (glob)
67 67 [255]
68 68
69 69 A per-host certificate mismatching the server will fail verification
70 70
71 71 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
72 72 abort: error: *certificate verify failed* (glob)
73 73 [255]
74 74
75 75 A per-host certificate matching the server's cert will be accepted
76 76
77 77 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
78 78 requesting all changes
79 79 adding changesets
80 80 adding manifests
81 81 adding file changes
82 82 added 1 changesets with 4 changes to 4 files
83 83
84 84 A per-host certificate with multiple certs and one matching will be accepted
85 85
86 86 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
87 87 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
88 88 requesting all changes
89 89 adding changesets
90 90 adding manifests
91 91 adding file changes
92 92 added 1 changesets with 4 changes to 4 files
93 93
94 94 Defining both per-host certificate and a fingerprint will print a warning
95 95
96 96 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
97 97 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
98 98 requesting all changes
99 99 adding changesets
100 100 adding manifests
101 101 adding file changes
102 102 added 1 changesets with 4 changes to 4 files
103 103
104 104 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
105 105
106 clone via pull
106 Inability to verify peer certificate will result in abort
107 107
108 108 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
109 warning: certificate for localhost not verified (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 or web.cacerts config settings)
109 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
110 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
111 [255]
112
113 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
114 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
110 115 requesting all changes
111 116 adding changesets
112 117 adding manifests
113 118 adding file changes
114 119 added 1 changesets with 4 changes to 4 files
115 120 updating to branch default
116 121 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 122 $ hg verify -R copy-pull
118 123 checking changesets
119 124 checking manifests
120 125 crosschecking files in changesets and manifests
121 126 checking files
122 127 4 files, 1 changesets, 4 total revisions
123 128 $ cd test
124 129 $ echo bar > bar
125 130 $ hg commit -A -d '1 0' -m 2
126 131 adding bar
127 132 $ cd ..
128 133
129 134 pull without cacert
130 135
131 136 $ cd copy-pull
132 137 $ echo '[hooks]' >> .hg/hgrc
133 138 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
134 139 $ hg pull $DISABLECACERTS
135 140 pulling from https://localhost:$HGPORT/
136 warning: certificate for localhost not verified (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 or web.cacerts config settings)
141 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
142 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
143 [255]
144
145 $ hg pull --insecure
146 pulling from https://localhost:$HGPORT/
147 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
137 148 searching for changes
138 149 adding changesets
139 150 adding manifests
140 151 adding file changes
141 152 added 1 changesets with 1 changes to 1 files
142 153 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
143 154 (run 'hg update' to get a working copy)
144 155 $ cd ..
145 156
146 157 cacert configured in local repo
147 158
148 159 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
149 160 $ echo "[web]" >> copy-pull/.hg/hgrc
150 161 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
151 162 $ hg -R copy-pull pull --traceback
152 163 pulling from https://localhost:$HGPORT/
153 164 searching for changes
154 165 no changes found
155 166 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
156 167
157 168 cacert configured globally, also testing expansion of environment
158 169 variables in the filename
159 170
160 171 $ echo "[web]" >> $HGRCPATH
161 172 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
162 173 $ P="$CERTSDIR" hg -R copy-pull pull
163 174 pulling from https://localhost:$HGPORT/
164 175 searching for changes
165 176 no changes found
166 177 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
167 178 pulling from https://localhost:$HGPORT/
168 179 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
169 180 searching for changes
170 181 no changes found
171 182
172 183 cacert mismatch
173 184
174 185 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
175 186 > https://127.0.0.1:$HGPORT/
176 187 pulling from https://127.0.0.1:$HGPORT/
177 188 abort: 127.0.0.1 certificate error: certificate is for localhost
178 189 (set hostsecurity.127.0.0.1:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
179 190 [255]
180 191 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
181 192 > https://127.0.0.1:$HGPORT/ --insecure
182 193 pulling from https://127.0.0.1:$HGPORT/
183 194 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
184 195 searching for changes
185 196 no changes found
186 197 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
187 198 pulling from https://localhost:$HGPORT/
188 199 abort: error: *certificate verify failed* (glob)
189 200 [255]
190 201 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
191 202 > --insecure
192 203 pulling from https://localhost:$HGPORT/
193 204 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
194 205 searching for changes
195 206 no changes found
196 207
197 208 Test server cert which isn't valid yet
198 209
199 210 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
200 211 $ cat hg1.pid >> $DAEMON_PIDS
201 212 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
202 213 > https://localhost:$HGPORT1/
203 214 pulling from https://localhost:$HGPORT1/
204 215 abort: error: *certificate verify failed* (glob)
205 216 [255]
206 217
207 218 Test server cert which no longer is valid
208 219
209 220 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
210 221 $ cat hg2.pid >> $DAEMON_PIDS
211 222 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
212 223 > https://localhost:$HGPORT2/
213 224 pulling from https://localhost:$HGPORT2/
214 225 abort: error: *certificate verify failed* (glob)
215 226 [255]
216 227
217 228 Fingerprints
218 229
219 230 - works without cacerts (hostkeyfingerprints)
220 231 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
221 232 5fed3813f7f5
222 233
223 234 - works without cacerts (hostsecurity)
224 235 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
225 236 5fed3813f7f5
226 237
227 238 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30
228 239 5fed3813f7f5
229 240
230 241 - multiple fingerprints specified and first matches
231 242 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
232 243 5fed3813f7f5
233 244
234 245 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
235 246 5fed3813f7f5
236 247
237 248 - multiple fingerprints specified and last matches
238 249 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
239 250 5fed3813f7f5
240 251
241 252 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
242 253 5fed3813f7f5
243 254
244 255 - multiple fingerprints specified and none match
245 256
246 257 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
247 258 abort: certificate for localhost has unexpected fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
248 259 (check hostfingerprint configuration)
249 260 [255]
250 261
251 262 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
252 263 abort: certificate for localhost has unexpected fingerprint sha1:91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca
253 264 (check hostsecurity configuration)
254 265 [255]
255 266
256 267 - fails when cert doesn't match hostname (port is ignored)
257 268 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
258 269 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
259 270 (check hostfingerprint configuration)
260 271 [255]
261 272
262 273
263 274 - ignores that certificate doesn't match hostname
264 275 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
265 276 5fed3813f7f5
266 277
267 278 HGPORT1 is reused below for tinyproxy tests. Kill that server.
268 279 $ killdaemons.py hg1.pid
269 280
270 281 Prepare for connecting through proxy
271 282
272 283 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
273 284 $ while [ ! -f proxy.pid ]; do sleep 0; done
274 285 $ cat proxy.pid >> $DAEMON_PIDS
275 286
276 287 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
277 288 $ echo "always=True" >> copy-pull/.hg/hgrc
278 289 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
279 290 $ echo "localhost =" >> copy-pull/.hg/hgrc
280 291
281 292 Test unvalidated https through proxy
282 293
283 294 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
284 295 pulling from https://localhost:$HGPORT/
285 296 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
286 297 searching for changes
287 298 no changes found
288 299
289 300 Test https with cacert and fingerprint through proxy
290 301
291 302 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
292 303 > --config web.cacerts="$CERTSDIR/pub.pem"
293 304 pulling from https://localhost:$HGPORT/
294 305 searching for changes
295 306 no changes found
296 307 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
297 308 pulling from https://127.0.0.1:$HGPORT/
298 309 searching for changes
299 310 no changes found
300 311
301 312 Test https with cert problems through proxy
302 313
303 314 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
304 315 > --config web.cacerts="$CERTSDIR/pub-other.pem"
305 316 pulling from https://localhost:$HGPORT/
306 317 abort: error: *certificate verify failed* (glob)
307 318 [255]
308 319 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
309 320 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
310 321 pulling from https://localhost:$HGPORT2/
311 322 abort: error: *certificate verify failed* (glob)
312 323 [255]
313 324
314 325
315 326 $ killdaemons.py hg0.pid
316 327
317 328 #if sslcontext
318 329
319 330 Start patched hgweb that requires client certificates:
320 331
321 332 $ cat << EOT > reqclientcert.py
322 333 > import ssl
323 334 > from mercurial.hgweb import server
324 335 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
325 336 > @staticmethod
326 337 > def preparehttpserver(httpserver, ssl_cert):
327 338 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
328 339 > sslcontext.verify_mode = ssl.CERT_REQUIRED
329 340 > sslcontext.load_cert_chain(ssl_cert)
330 341 > # verify clients by server certificate
331 342 > sslcontext.load_verify_locations(ssl_cert)
332 343 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
333 344 > server_side=True)
334 345 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
335 346 > EOT
336 347 $ cd test
337 348 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
338 349 > --config extensions.reqclientcert=../reqclientcert.py
339 350 $ cat ../hg0.pid >> $DAEMON_PIDS
340 351 $ cd ..
341 352
342 353 without client certificate:
343 354
344 355 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
345 356 abort: error: *handshake failure* (glob)
346 357 [255]
347 358
348 359 with client certificate:
349 360
350 361 $ cat << EOT >> $HGRCPATH
351 362 > [auth]
352 363 > l.prefix = localhost
353 364 > l.cert = $CERTSDIR/client-cert.pem
354 365 > l.key = $CERTSDIR/client-key.pem
355 366 > EOT
356 367
357 368 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
358 369 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
359 370 5fed3813f7f5
360 371
361 372 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
362 373 > --config ui.interactive=True --config ui.nontty=True
363 374 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
364 375
365 376 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
366 377 abort: error: * (glob)
367 378 [255]
368 379
369 380 #endif
@@ -1,89 +1,90
1 1 #require serve ssl
2 2
3 3 Set up SMTP server:
4 4
5 5 $ CERTSDIR="$TESTDIR/sslcerts"
6 6 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
7 7
8 8 $ python "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
9 9 > --tls smtps --certificate `pwd`/server.pem
10 10 listening at localhost:$HGPORT
11 11 $ cat a.pid >> $DAEMON_PIDS
12 12
13 13 Ensure hg email output is sent to stdout:
14 14
15 15 $ unset PAGER
16 16
17 17 Set up repository:
18 18
19 19 $ hg init t
20 20 $ cd t
21 21 $ cat <<EOF >> .hg/hgrc
22 22 > [extensions]
23 23 > patchbomb =
24 24 > [email]
25 25 > method = smtp
26 26 > [smtp]
27 27 > host = localhost
28 28 > port = $HGPORT
29 29 > tls = smtps
30 30 > EOF
31 31
32 32 $ echo a > a
33 33 $ hg commit -Ama -d '1 0'
34 34 adding a
35 35
36 36 Utility functions:
37 37
38 38 $ DISABLECACERTS=
39 39 $ try () {
40 40 > hg email $DISABLECACERTS -f quux -t foo -c bar -r tip "$@"
41 41 > }
42 42
43 43 Our test cert is not signed by a trusted CA. It should fail to verify if
44 44 we are able to load CA certs:
45 45
46 46 #if defaultcacerts
47 47 $ try
48 48 this patch series consists of 1 patches.
49 49
50 50
51 51 (?i)abort: .*?certificate.verify.failed.* (re)
52 52 [255]
53 53 #endif
54 54
55 55 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
56 56
57 57 Without certificates:
58 58
59 59 $ try --debug
60 60 this patch series consists of 1 patches.
61 61
62 62
63 63 (using smtps)
64 64 sending mail: smtp host localhost, port * (glob)
65 65 (verifying remote certificate)
66 warning: certificate for localhost not verified (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 or web.cacerts config settings)
67 sending [PATCH] a ...
66 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
67 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 to trust this server)
68 [255]
68 69
69 70 With global certificates:
70 71
71 72 $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
72 73 this patch series consists of 1 patches.
73 74
74 75
75 76 (using smtps)
76 77 sending mail: smtp host localhost, port * (glob)
77 78 (verifying remote certificate)
78 79 sending [PATCH] a ...
79 80
80 81 With invalid certificates:
81 82
82 83 $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
83 84 this patch series consists of 1 patches.
84 85
85 86
86 87 (?i)abort: .*?certificate.verify.failed.* (re)
87 88 [255]
88 89
89 90 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now