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