##// END OF EJS Templates
sslutil: make cert fingerprints messages more actionable...
Gregory Szorc -
r29292:bc5f5549 default
parent child Browse files
Show More
@@ -1,411 +1,411
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 os
13 13 import ssl
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from . import (
18 18 error,
19 19 util,
20 20 )
21 21
22 22 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
23 23 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
24 24 # all exposed via the "ssl" module.
25 25 #
26 26 # Depending on the version of Python being used, SSL/TLS support is either
27 27 # modern/secure or legacy/insecure. Many operations in this module have
28 28 # separate code paths depending on support in Python.
29 29
30 30 hassni = getattr(ssl, 'HAS_SNI', False)
31 31
32 32 try:
33 33 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
34 34 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
35 35 except AttributeError:
36 36 OP_NO_SSLv2 = 0x1000000
37 37 OP_NO_SSLv3 = 0x2000000
38 38
39 39 try:
40 40 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
41 41 # SSL/TLS features are available.
42 42 SSLContext = ssl.SSLContext
43 43 modernssl = True
44 44 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
45 45 except AttributeError:
46 46 modernssl = False
47 47 _canloaddefaultcerts = False
48 48
49 49 # We implement SSLContext using the interface from the standard library.
50 50 class SSLContext(object):
51 51 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
52 52 _supportsciphers = sys.version_info >= (2, 7)
53 53
54 54 def __init__(self, protocol):
55 55 # From the public interface of SSLContext
56 56 self.protocol = protocol
57 57 self.check_hostname = False
58 58 self.options = 0
59 59 self.verify_mode = ssl.CERT_NONE
60 60
61 61 # Used by our implementation.
62 62 self._certfile = None
63 63 self._keyfile = None
64 64 self._certpassword = None
65 65 self._cacerts = None
66 66 self._ciphers = None
67 67
68 68 def load_cert_chain(self, certfile, keyfile=None, password=None):
69 69 self._certfile = certfile
70 70 self._keyfile = keyfile
71 71 self._certpassword = password
72 72
73 73 def load_default_certs(self, purpose=None):
74 74 pass
75 75
76 76 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
77 77 if capath:
78 78 raise error.Abort('capath not supported')
79 79 if cadata:
80 80 raise error.Abort('cadata not supported')
81 81
82 82 self._cacerts = cafile
83 83
84 84 def set_ciphers(self, ciphers):
85 85 if not self._supportsciphers:
86 86 raise error.Abort('setting ciphers not supported')
87 87
88 88 self._ciphers = ciphers
89 89
90 90 def wrap_socket(self, socket, server_hostname=None, server_side=False):
91 91 # server_hostname is unique to SSLContext.wrap_socket and is used
92 92 # for SNI in that context. So there's nothing for us to do with it
93 93 # in this legacy code since we don't support SNI.
94 94
95 95 args = {
96 96 'keyfile': self._keyfile,
97 97 'certfile': self._certfile,
98 98 'server_side': server_side,
99 99 'cert_reqs': self.verify_mode,
100 100 'ssl_version': self.protocol,
101 101 'ca_certs': self._cacerts,
102 102 }
103 103
104 104 if self._supportsciphers:
105 105 args['ciphers'] = self._ciphers
106 106
107 107 return ssl.wrap_socket(socket, **args)
108 108
109 109 def _hostsettings(ui, hostname):
110 110 """Obtain security settings for a hostname.
111 111
112 112 Returns a dict of settings relevant to that hostname.
113 113 """
114 114 s = {
115 115 # Whether we should attempt to load default/available CA certs
116 116 # if an explicit ``cafile`` is not defined.
117 117 'allowloaddefaultcerts': True,
118 118 # List of 2-tuple of (hash algorithm, hash).
119 119 'certfingerprints': [],
120 120 # Path to file containing concatenated CA certs. Used by
121 121 # SSLContext.load_verify_locations().
122 122 'cafile': None,
123 123 # Whether certificate verification should be disabled.
124 124 'disablecertverification': False,
125 125 # Whether the legacy [hostfingerprints] section has data for this host.
126 126 'legacyfingerprint': False,
127 127 # ssl.CERT_* constant used by SSLContext.verify_mode.
128 128 'verifymode': None,
129 129 }
130 130
131 131 # Look for fingerprints in [hostsecurity] section. Value is a list
132 132 # of <alg>:<fingerprint> strings.
133 133 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
134 134 [])
135 135 for fingerprint in fingerprints:
136 136 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
137 137 raise error.Abort(_('invalid fingerprint for %s: %s') % (
138 138 hostname, fingerprint),
139 139 hint=_('must begin with "sha1:", "sha256:", '
140 140 'or "sha512:"'))
141 141
142 142 alg, fingerprint = fingerprint.split(':', 1)
143 143 fingerprint = fingerprint.replace(':', '').lower()
144 144 s['certfingerprints'].append((alg, fingerprint))
145 145
146 146 # Fingerprints from [hostfingerprints] are always SHA-1.
147 147 for fingerprint in ui.configlist('hostfingerprints', hostname, []):
148 148 fingerprint = fingerprint.replace(':', '').lower()
149 149 s['certfingerprints'].append(('sha1', fingerprint))
150 150 s['legacyfingerprint'] = True
151 151
152 152 # If a host cert fingerprint is defined, it is the only thing that
153 153 # matters. No need to validate CA certs.
154 154 if s['certfingerprints']:
155 155 s['verifymode'] = ssl.CERT_NONE
156 156
157 157 # If --insecure is used, don't take CAs into consideration.
158 158 elif ui.insecureconnections:
159 159 s['disablecertverification'] = True
160 160 s['verifymode'] = ssl.CERT_NONE
161 161
162 162 if ui.configbool('devel', 'disableloaddefaultcerts'):
163 163 s['allowloaddefaultcerts'] = False
164 164
165 165 # Try to hook up CA certificate validation unless something above
166 166 # makes it not necessary.
167 167 if s['verifymode'] is None:
168 168 # Find global certificates file in config.
169 169 cafile = ui.config('web', 'cacerts')
170 170
171 171 if cafile:
172 172 cafile = util.expandpath(cafile)
173 173 if not os.path.exists(cafile):
174 174 raise error.Abort(_('could not find web.cacerts: %s') % cafile)
175 175 else:
176 176 # No global CA certs. See if we can load defaults.
177 177 cafile = _defaultcacerts()
178 178 if cafile:
179 179 ui.debug('using %s to enable OS X system CA\n' % cafile)
180 180
181 181 s['cafile'] = cafile
182 182
183 183 # Require certificate validation if CA certs are being loaded and
184 184 # verification hasn't been disabled above.
185 185 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
186 186 s['verifymode'] = ssl.CERT_REQUIRED
187 187 else:
188 188 # At this point we don't have a fingerprint, aren't being
189 189 # explicitly insecure, and can't load CA certs. Connecting
190 190 # at this point is insecure. But we do it for BC reasons.
191 191 # TODO abort here to make secure by default.
192 192 s['verifymode'] = ssl.CERT_NONE
193 193
194 194 assert s['verifymode'] is not None
195 195
196 196 return s
197 197
198 198 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
199 199 """Add SSL/TLS to a socket.
200 200
201 201 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
202 202 choices based on what security options are available.
203 203
204 204 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
205 205 the following additional arguments:
206 206
207 207 * serverhostname - The expected hostname of the remote server. If the
208 208 server (and client) support SNI, this tells the server which certificate
209 209 to use.
210 210 """
211 211 if not serverhostname:
212 212 raise error.Abort('serverhostname argument is required')
213 213
214 214 settings = _hostsettings(ui, serverhostname)
215 215
216 216 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
217 217 # that both ends support, including TLS protocols. On legacy stacks,
218 218 # the highest it likely goes in TLS 1.0. On modern stacks, it can
219 219 # support TLS 1.2.
220 220 #
221 221 # The PROTOCOL_TLSv* constants select a specific TLS version
222 222 # only (as opposed to multiple versions). So the method for
223 223 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
224 224 # disable protocols via SSLContext.options and OP_NO_* constants.
225 225 # However, SSLContext.options doesn't work unless we have the
226 226 # full/real SSLContext available to us.
227 227 #
228 228 # SSLv2 and SSLv3 are broken. We ban them outright.
229 229 if modernssl:
230 230 protocol = ssl.PROTOCOL_SSLv23
231 231 else:
232 232 protocol = ssl.PROTOCOL_TLSv1
233 233
234 234 # TODO use ssl.create_default_context() on modernssl.
235 235 sslcontext = SSLContext(protocol)
236 236
237 237 # This is a no-op on old Python.
238 238 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
239 239
240 240 # This still works on our fake SSLContext.
241 241 sslcontext.verify_mode = settings['verifymode']
242 242
243 243 if certfile is not None:
244 244 def password():
245 245 f = keyfile or certfile
246 246 return ui.getpass(_('passphrase for %s: ') % f, '')
247 247 sslcontext.load_cert_chain(certfile, keyfile, password)
248 248
249 249 if settings['cafile'] is not None:
250 250 sslcontext.load_verify_locations(cafile=settings['cafile'])
251 251 caloaded = True
252 252 elif settings['allowloaddefaultcerts']:
253 253 # This is a no-op on old Python.
254 254 sslcontext.load_default_certs()
255 255 caloaded = True
256 256 else:
257 257 caloaded = False
258 258
259 259 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
260 260 # check if wrap_socket failed silently because socket had been
261 261 # closed
262 262 # - see http://bugs.python.org/issue13721
263 263 if not sslsocket.cipher():
264 264 raise error.Abort(_('ssl connection failed'))
265 265
266 266 sslsocket._hgstate = {
267 267 'caloaded': caloaded,
268 268 'hostname': serverhostname,
269 269 'settings': settings,
270 270 'ui': ui,
271 271 }
272 272
273 273 return sslsocket
274 274
275 275 def _verifycert(cert, hostname):
276 276 '''Verify that cert (in socket.getpeercert() format) matches hostname.
277 277 CRLs is not handled.
278 278
279 279 Returns error message if any problems are found and None on success.
280 280 '''
281 281 if not cert:
282 282 return _('no certificate received')
283 283 dnsname = hostname.lower()
284 284 def matchdnsname(certname):
285 285 return (certname == dnsname or
286 286 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
287 287
288 288 san = cert.get('subjectAltName', [])
289 289 if san:
290 290 certnames = [value.lower() for key, value in san if key == 'DNS']
291 291 for name in certnames:
292 292 if matchdnsname(name):
293 293 return None
294 294 if certnames:
295 295 return _('certificate is for %s') % ', '.join(certnames)
296 296
297 297 # subject is only checked when subjectAltName is empty
298 298 for s in cert.get('subject', []):
299 299 key, value = s[0]
300 300 if key == 'commonName':
301 301 try:
302 302 # 'subject' entries are unicode
303 303 certname = value.lower().encode('ascii')
304 304 except UnicodeEncodeError:
305 305 return _('IDN in certificate not supported')
306 306 if matchdnsname(certname):
307 307 return None
308 308 return _('certificate is for %s') % certname
309 309 return _('no commonName or subjectAltName found in certificate')
310 310
311 311
312 312 # CERT_REQUIRED means fetch the cert from the server all the time AND
313 313 # validate it against the CA store provided in web.cacerts.
314 314
315 315 def _plainapplepython():
316 316 """return true if this seems to be a pure Apple Python that
317 317 * is unfrozen and presumably has the whole mercurial module in the file
318 318 system
319 319 * presumably is an Apple Python that uses Apple OpenSSL which has patches
320 320 for using system certificate store CAs in addition to the provided
321 321 cacerts file
322 322 """
323 323 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
324 324 return False
325 325 exe = os.path.realpath(sys.executable).lower()
326 326 return (exe.startswith('/usr/bin/python') or
327 327 exe.startswith('/system/library/frameworks/python.framework/'))
328 328
329 329 def _defaultcacerts():
330 330 """return path to default CA certificates or None."""
331 331 if _plainapplepython():
332 332 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
333 333 if os.path.exists(dummycert):
334 334 return dummycert
335 335
336 336 return None
337 337
338 338 def validatesocket(sock):
339 339 """Validate a socket meets security requiremnets.
340 340
341 341 The passed socket must have been created with ``wrapsocket()``.
342 342 """
343 343 host = sock._hgstate['hostname']
344 344 ui = sock._hgstate['ui']
345 345 settings = sock._hgstate['settings']
346 346
347 347 try:
348 348 peercert = sock.getpeercert(True)
349 349 peercert2 = sock.getpeercert()
350 350 except AttributeError:
351 351 raise error.Abort(_('%s ssl connection error') % host)
352 352
353 353 if not peercert:
354 354 raise error.Abort(_('%s certificate error: '
355 355 'no certificate received') % host)
356 356
357 357 if settings['disablecertverification']:
358 358 # We don't print the certificate fingerprint because it shouldn't
359 359 # be necessary: if the user requested certificate verification be
360 360 # disabled, they presumably already saw a message about the inability
361 361 # to verify the certificate and this message would have printed the
362 362 # fingerprint. So printing the fingerprint here adds little to no
363 363 # value.
364 364 ui.warn(_('warning: connection security to %s is disabled per current '
365 365 'settings; communication is susceptible to eavesdropping '
366 366 'and tampering\n') % host)
367 367 return
368 368
369 369 # If a certificate fingerprint is pinned, use it and only it to
370 370 # validate the remote cert.
371 371 peerfingerprints = {
372 372 'sha1': util.sha1(peercert).hexdigest(),
373 373 'sha256': util.sha256(peercert).hexdigest(),
374 374 'sha512': util.sha512(peercert).hexdigest(),
375 375 }
376 376
377 377 def fmtfingerprint(s):
378 378 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
379 379
380 380 legacyfingerprint = fmtfingerprint(peerfingerprints['sha1'])
381 381 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
382 382
383 383 if settings['legacyfingerprint']:
384 384 section = 'hostfingerprint'
385 385 else:
386 386 section = 'hostsecurity'
387 387
388 388 if settings['certfingerprints']:
389 389 for hash, fingerprint in settings['certfingerprints']:
390 390 if peerfingerprints[hash].lower() == fingerprint:
391 391 ui.debug('%s certificate matched fingerprint %s:%s\n' %
392 392 (host, hash, fmtfingerprint(fingerprint)))
393 393 return
394 394
395 395 raise error.Abort(_('certificate for %s has unexpected '
396 396 'fingerprint %s') % (host, legacyfingerprint),
397 397 hint=_('check %s configuration') % section)
398 398
399 399 if not sock._hgstate['caloaded']:
400 ui.warn(_('warning: %s certificate with fingerprint %s '
401 'not verified (check %s or web.cacerts config '
402 'setting)\n') %
403 (host, nicefingerprint, section))
400 ui.warn(_('warning: certificate for %s not verified '
401 '(set hostsecurity.%s:certfingerprints=%s or web.cacerts '
402 'config settings)\n') % (host, host, nicefingerprint))
404 403 return
405 404
406 405 msg = _verifycert(peercert2, host)
407 406 if msg:
408 407 raise error.Abort(_('%s certificate error: %s') % (host, msg),
409 hint=_('configure %s %s or use '
410 '--insecure to connect insecurely') %
411 (section, nicefingerprint))
408 hint=_('set hostsecurity.%s:certfingerprints=%s '
409 'config setting or use --insecure to connect '
410 'insecurely') %
411 (host, nicefingerprint))
@@ -1,431 +1,431
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 Certificates created with:
6 6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 8 Can be dumped with:
9 9 openssl x509 -in pub.pem -text
10 10
11 11 $ cat << EOT > priv.pem
12 12 > -----BEGIN PRIVATE KEY-----
13 13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 20 > HY8gUVkVRVs=
21 21 > -----END PRIVATE KEY-----
22 22 > EOT
23 23
24 24 $ cat << EOT > pub.pem
25 25 > -----BEGIN CERTIFICATE-----
26 26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 35 > -----END CERTIFICATE-----
36 36 > EOT
37 37 $ cat priv.pem pub.pem >> server.pem
38 38 $ PRIV=`pwd`/server.pem
39 39
40 40 $ cat << EOT > pub-other.pem
41 41 > -----BEGIN CERTIFICATE-----
42 42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 51 > -----END CERTIFICATE-----
52 52 > EOT
53 53
54 54 pub.pem patched with other notBefore / notAfter:
55 55
56 56 $ cat << EOT > pub-not-yet.pem
57 57 > -----BEGIN CERTIFICATE-----
58 58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 66 > -----END CERTIFICATE-----
67 67 > EOT
68 68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69 69
70 70 $ cat << EOT > pub-expired.pem
71 71 > -----BEGIN CERTIFICATE-----
72 72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 80 > -----END CERTIFICATE-----
81 81 > EOT
82 82 $ cat priv.pem pub-expired.pem > server-expired.pem
83 83
84 84 Client certificates created with:
85 85 openssl genrsa -aes128 -passout pass:1234 -out client-key.pem 512
86 86 openssl rsa -in client-key.pem -passin pass:1234 -out client-key-decrypted.pem
87 87 printf '.\n.\n.\n.\n.\n.\nhg-client@localhost\n.\n.\n' | \
88 88 openssl req -new -key client-key.pem -passin pass:1234 -out client-csr.pem
89 89 openssl x509 -req -days 9000 -in client-csr.pem -CA pub.pem -CAkey priv.pem \
90 90 -set_serial 01 -out client-cert.pem
91 91
92 92 $ cat << EOT > client-key.pem
93 93 > -----BEGIN RSA PRIVATE KEY-----
94 94 > Proc-Type: 4,ENCRYPTED
95 95 > DEK-Info: AES-128-CBC,C8B8F103A61A336FB0716D1C0F8BB2E8
96 96 >
97 97 > JolMlCFjEW3q3JJjO9z99NJWeJbFgF5DpUOkfSCxH56hxxtZb9x++rBvBZkxX1bF
98 98 > BAIe+iI90+jdCLwxbILWuFcrJUaLC5WmO14XDKYVmr2eW9e4MiCYOlO0Q6a9rDFS
99 99 > jctRCfvubOXFHbBGLH8uKEMpXEkP7Lc60FiIukqjuQEivJjrQirVtZCGwyk3qUi7
100 100 > Eyh4Lo63IKGu8T1Bkmn2kaMvFhu7nC/CQLBjSq0YYI1tmCOkVb/3tPrz8oqgDJp2
101 101 > u7bLS3q0xDNZ52nVrKIoZC/UlRXGlPyzPpa70/jPIdfCbkwDaBpRVXc+62Pj2n5/
102 102 > CnO2xaKwfOG6pDvanBhFD72vuBOkAYlFZPiEku4sc2WlNggsSWCPCIFwzmiHjKIl
103 103 > bWmdoTq3nb7sNfnBbV0OCa7fS1dFwCm4R1NC7ELENu0=
104 104 > -----END RSA PRIVATE KEY-----
105 105 > EOT
106 106
107 107 $ cat << EOT > client-key-decrypted.pem
108 108 > -----BEGIN RSA PRIVATE KEY-----
109 109 > MIIBOgIBAAJBAJs4LS3glAYU92bg5kPgRPNW84ewB0fWJfAKccCp1ACHAdZPeaKb
110 110 > FCinVMYKAVbVqBkyrZ/Tyr8aSfMz4xO4+KsCAwEAAQJAeKDr25+Q6jkZHEbkLRP6
111 111 > AfMtR+Ixhk6TJT24sbZKIC2V8KuJTDEvUhLU0CAr1nH79bDqiSsecOiVCr2HHyfT
112 112 > AQIhAM2C5rHbTs9R3PkywFEqq1gU3ztCnpiWglO7/cIkuGBhAiEAwVpMSAf77kop
113 113 > 4h/1kWsgMALQTJNsXd4CEUK4BOxvJIsCIQCbarVAKBQvoT81jfX27AfscsxnKnh5
114 114 > +MjSvkanvdFZwQIgbbcTefwt1LV4trtz2SR0i0nNcOZmo40Kl0jIquKO3qkCIH01
115 115 > mJHzZr3+jQqeIFtr5P+Xqi30DJxgrnEobbJ0KFjY
116 116 > -----END RSA PRIVATE KEY-----
117 117 > EOT
118 118
119 119 $ cat << EOT > client-cert.pem
120 120 > -----BEGIN CERTIFICATE-----
121 121 > MIIBPjCB6QIBATANBgkqhkiG9w0BAQsFADAxMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
122 122 > GzAZBgkqhkiG9w0BCQEWDGhnQGxvY2FsaG9zdDAeFw0xNTA1MDcwNjI5NDVaFw0z
123 123 > OTEyMjcwNjI5NDVaMCQxIjAgBgkqhkiG9w0BCQEWE2hnLWNsaWVudEBsb2NhbGhv
124 124 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAmzgtLeCUBhT3ZuDmQ+BE81bzh7AH
125 125 > R9Yl8ApxwKnUAIcB1k95opsUKKdUxgoBVtWoGTKtn9PKvxpJ8zPjE7j4qwIDAQAB
126 126 > MA0GCSqGSIb3DQEBCwUAA0EAfBTqBG5pYhuGk+ZnyUufgS+d7Nk/sZAZjNdCAEj/
127 127 > NFPo5fR1jM6jlEWoWbeg298+SkjV7tfO+2nt0otUFkdM6A==
128 128 > -----END CERTIFICATE-----
129 129 > EOT
130 130
131 131 $ hg init test
132 132 $ cd test
133 133 $ echo foo>foo
134 134 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
135 135 $ echo foo>foo.d/foo
136 136 $ echo bar>foo.d/bAr.hg.d/BaR
137 137 $ echo bar>foo.d/baR.d.hg/bAR
138 138 $ hg commit -A -m 1
139 139 adding foo
140 140 adding foo.d/bAr.hg.d/BaR
141 141 adding foo.d/baR.d.hg/bAR
142 142 adding foo.d/foo
143 143 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
144 144 $ cat ../hg0.pid >> $DAEMON_PIDS
145 145
146 146 cacert not found
147 147
148 148 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
149 149 abort: could not find web.cacerts: no-such.pem
150 150 [255]
151 151
152 152 Test server address cannot be reused
153 153
154 154 #if windows
155 155 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
156 156 abort: cannot start server at ':$HGPORT':
157 157 [255]
158 158 #else
159 159 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
160 160 abort: cannot start server at ':$HGPORT': Address already in use
161 161 [255]
162 162 #endif
163 163 $ cd ..
164 164
165 165 Our test cert is not signed by a trusted CA. It should fail to verify if
166 166 we are able to load CA certs.
167 167
168 168 #if defaultcacerts
169 169 $ hg clone https://localhost:$HGPORT/ copy-pull
170 170 abort: error: *certificate verify failed* (glob)
171 171 [255]
172 172 #endif
173 173
174 174 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
175 175
176 176 clone via pull
177 177
178 178 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
179 warning: localhost certificate with fingerprint 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 not verified (check hostsecurity or web.cacerts config setting)
179 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)
180 180 requesting all changes
181 181 adding changesets
182 182 adding manifests
183 183 adding file changes
184 184 added 1 changesets with 4 changes to 4 files
185 185 updating to branch default
186 186 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 $ hg verify -R copy-pull
188 188 checking changesets
189 189 checking manifests
190 190 crosschecking files in changesets and manifests
191 191 checking files
192 192 4 files, 1 changesets, 4 total revisions
193 193 $ cd test
194 194 $ echo bar > bar
195 195 $ hg commit -A -d '1 0' -m 2
196 196 adding bar
197 197 $ cd ..
198 198
199 199 pull without cacert
200 200
201 201 $ cd copy-pull
202 202 $ echo '[hooks]' >> .hg/hgrc
203 203 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
204 204 $ hg pull $DISABLECACERTS
205 205 pulling from https://localhost:$HGPORT/
206 warning: localhost certificate with fingerprint 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 not verified (check hostsecurity or web.cacerts config setting)
206 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)
207 207 searching for changes
208 208 adding changesets
209 209 adding manifests
210 210 adding file changes
211 211 added 1 changesets with 1 changes to 1 files
212 212 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
213 213 (run 'hg update' to get a working copy)
214 214 $ cd ..
215 215
216 216 cacert configured in local repo
217 217
218 218 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
219 219 $ echo "[web]" >> copy-pull/.hg/hgrc
220 220 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
221 221 $ hg -R copy-pull pull --traceback
222 222 pulling from https://localhost:$HGPORT/
223 223 searching for changes
224 224 no changes found
225 225 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
226 226
227 227 cacert configured globally, also testing expansion of environment
228 228 variables in the filename
229 229
230 230 $ echo "[web]" >> $HGRCPATH
231 231 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
232 232 $ P=`pwd` hg -R copy-pull pull
233 233 pulling from https://localhost:$HGPORT/
234 234 searching for changes
235 235 no changes found
236 236 $ P=`pwd` hg -R copy-pull pull --insecure
237 237 pulling from https://localhost:$HGPORT/
238 238 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
239 239 searching for changes
240 240 no changes found
241 241
242 242 cacert mismatch
243 243
244 244 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
245 245 pulling from https://127.0.0.1:$HGPORT/
246 246 abort: 127.0.0.1 certificate error: certificate is for localhost
247 (configure hostsecurity 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 use --insecure to connect insecurely)
247 (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)
248 248 [255]
249 249 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
250 250 pulling from https://127.0.0.1:$HGPORT/
251 251 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering
252 252 searching for changes
253 253 no changes found
254 254 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
255 255 pulling from https://localhost:$HGPORT/
256 256 abort: error: *certificate verify failed* (glob)
257 257 [255]
258 258 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
259 259 pulling from https://localhost:$HGPORT/
260 260 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
261 261 searching for changes
262 262 no changes found
263 263
264 264 Test server cert which isn't valid yet
265 265
266 266 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
267 267 $ cat hg1.pid >> $DAEMON_PIDS
268 268 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
269 269 pulling from https://localhost:$HGPORT1/
270 270 abort: error: *certificate verify failed* (glob)
271 271 [255]
272 272
273 273 Test server cert which no longer is valid
274 274
275 275 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
276 276 $ cat hg2.pid >> $DAEMON_PIDS
277 277 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
278 278 pulling from https://localhost:$HGPORT2/
279 279 abort: error: *certificate verify failed* (glob)
280 280 [255]
281 281
282 282 Fingerprints
283 283
284 284 - works without cacerts (hostkeyfingerprints)
285 285 $ 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
286 286 5fed3813f7f5
287 287
288 288 - works without cacerts (hostsecurity)
289 289 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca
290 290 5fed3813f7f5
291 291
292 292 $ 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
293 293 5fed3813f7f5
294 294
295 295 - multiple fingerprints specified and first matches
296 296 $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
297 297 5fed3813f7f5
298 298
299 299 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
300 300 5fed3813f7f5
301 301
302 302 - multiple fingerprints specified and last matches
303 303 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure
304 304 5fed3813f7f5
305 305
306 306 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/
307 307 5fed3813f7f5
308 308
309 309 - multiple fingerprints specified and none match
310 310
311 311 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
312 312 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
313 313 (check hostfingerprint configuration)
314 314 [255]
315 315
316 316 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
317 317 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
318 318 (check hostsecurity configuration)
319 319 [255]
320 320
321 321 - fails when cert doesn't match hostname (port is ignored)
322 322 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca
323 323 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
324 324 (check hostfingerprint configuration)
325 325 [255]
326 326
327 327
328 328 - ignores that certificate doesn't match hostname
329 329 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
330 330 5fed3813f7f5
331 331
332 332 HGPORT1 is reused below for tinyproxy tests. Kill that server.
333 333 $ killdaemons.py hg1.pid
334 334
335 335 Prepare for connecting through proxy
336 336
337 337 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
338 338 $ while [ ! -f proxy.pid ]; do sleep 0; done
339 339 $ cat proxy.pid >> $DAEMON_PIDS
340 340
341 341 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
342 342 $ echo "always=True" >> copy-pull/.hg/hgrc
343 343 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
344 344 $ echo "localhost =" >> copy-pull/.hg/hgrc
345 345
346 346 Test unvalidated https through proxy
347 347
348 348 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
349 349 pulling from https://localhost:$HGPORT/
350 350 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
351 351 searching for changes
352 352 no changes found
353 353
354 354 Test https with cacert and fingerprint through proxy
355 355
356 356 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
357 357 pulling from https://localhost:$HGPORT/
358 358 searching for changes
359 359 no changes found
360 360 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=914f1aff87249c09b6859b88b1906d30756491ca
361 361 pulling from https://127.0.0.1:$HGPORT/
362 362 searching for changes
363 363 no changes found
364 364
365 365 Test https with cert problems through proxy
366 366
367 367 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
368 368 pulling from https://localhost:$HGPORT/
369 369 abort: error: *certificate verify failed* (glob)
370 370 [255]
371 371 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
372 372 pulling from https://localhost:$HGPORT2/
373 373 abort: error: *certificate verify failed* (glob)
374 374 [255]
375 375
376 376
377 377 $ killdaemons.py hg0.pid
378 378
379 379 #if sslcontext
380 380
381 381 Start patched hgweb that requires client certificates:
382 382
383 383 $ cat << EOT > reqclientcert.py
384 384 > import ssl
385 385 > from mercurial.hgweb import server
386 386 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
387 387 > @staticmethod
388 388 > def preparehttpserver(httpserver, ssl_cert):
389 389 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
390 390 > sslcontext.verify_mode = ssl.CERT_REQUIRED
391 391 > sslcontext.load_cert_chain(ssl_cert)
392 392 > # verify clients by server certificate
393 393 > sslcontext.load_verify_locations(ssl_cert)
394 394 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
395 395 > server_side=True)
396 396 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
397 397 > EOT
398 398 $ cd test
399 399 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
400 400 > --config extensions.reqclientcert=../reqclientcert.py
401 401 $ cat ../hg0.pid >> $DAEMON_PIDS
402 402 $ cd ..
403 403
404 404 without client certificate:
405 405
406 406 $ P=`pwd` hg id https://localhost:$HGPORT/
407 407 abort: error: *handshake failure* (glob)
408 408 [255]
409 409
410 410 with client certificate:
411 411
412 412 $ cat << EOT >> $HGRCPATH
413 413 > [auth]
414 414 > l.prefix = localhost
415 415 > l.cert = client-cert.pem
416 416 > l.key = client-key.pem
417 417 > EOT
418 418
419 419 $ P=`pwd` hg id https://localhost:$HGPORT/ \
420 420 > --config auth.l.key=client-key-decrypted.pem
421 421 5fed3813f7f5
422 422
423 423 $ printf '1234\n' | env P=`pwd` hg id https://localhost:$HGPORT/ \
424 424 > --config ui.interactive=True --config ui.nontty=True
425 425 passphrase for client-key.pem: 5fed3813f7f5
426 426
427 427 $ env P=`pwd` hg id https://localhost:$HGPORT/
428 428 abort: error: * (glob)
429 429 [255]
430 430
431 431 #endif
General Comments 0
You need to be logged in to leave comments. Login now