##// END OF EJS Templates
url: raise error if CONNECT request to proxy was unsuccessful...
Manuel Jacob -
r50172:51b07ac1 stable
parent child Browse files
Show More
@@ -1,739 +1,667
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Olivia Mackall <olivia@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 base64
12 import base64
13 import socket
13 import socket
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .pycompat import getattr
18 from . import (
17 from . import (
19 encoding,
18 encoding,
20 error,
19 error,
21 httpconnection as httpconnectionmod,
20 httpconnection as httpconnectionmod,
22 keepalive,
21 keepalive,
23 pycompat,
22 pycompat,
24 sslutil,
23 sslutil,
25 urllibcompat,
24 urllibcompat,
26 util,
25 util,
27 )
26 )
28 from .utils import (
27 from .utils import (
29 stringutil,
28 stringutil,
30 urlutil,
29 urlutil,
31 )
30 )
32
31
33 httplib = util.httplib
32 httplib = util.httplib
34 stringio = util.stringio
33 stringio = util.stringio
35 urlerr = util.urlerr
34 urlerr = util.urlerr
36 urlreq = util.urlreq
35 urlreq = util.urlreq
37
36
38
37
39 def escape(s, quote=None):
38 def escape(s, quote=None):
40 """Replace special characters "&", "<" and ">" to HTML-safe sequences.
39 """Replace special characters "&", "<" and ">" to HTML-safe sequences.
41 If the optional flag quote is true, the quotation mark character (")
40 If the optional flag quote is true, the quotation mark character (")
42 is also translated.
41 is also translated.
43
42
44 This is the same as cgi.escape in Python, but always operates on
43 This is the same as cgi.escape in Python, but always operates on
45 bytes, whereas cgi.escape in Python 3 only works on unicodes.
44 bytes, whereas cgi.escape in Python 3 only works on unicodes.
46 """
45 """
47 s = s.replace(b"&", b"&amp;")
46 s = s.replace(b"&", b"&amp;")
48 s = s.replace(b"<", b"&lt;")
47 s = s.replace(b"<", b"&lt;")
49 s = s.replace(b">", b"&gt;")
48 s = s.replace(b">", b"&gt;")
50 if quote:
49 if quote:
51 s = s.replace(b'"', b"&quot;")
50 s = s.replace(b'"', b"&quot;")
52 return s
51 return s
53
52
54
53
55 class passwordmgr(object):
54 class passwordmgr(object):
56 def __init__(self, ui, passwddb):
55 def __init__(self, ui, passwddb):
57 self.ui = ui
56 self.ui = ui
58 self.passwddb = passwddb
57 self.passwddb = passwddb
59
58
60 def add_password(self, realm, uri, user, passwd):
59 def add_password(self, realm, uri, user, passwd):
61 return self.passwddb.add_password(realm, uri, user, passwd)
60 return self.passwddb.add_password(realm, uri, user, passwd)
62
61
63 def find_user_password(self, realm, authuri):
62 def find_user_password(self, realm, authuri):
64 assert isinstance(realm, (type(None), str))
63 assert isinstance(realm, (type(None), str))
65 assert isinstance(authuri, str)
64 assert isinstance(authuri, str)
66 authinfo = self.passwddb.find_user_password(realm, authuri)
65 authinfo = self.passwddb.find_user_password(realm, authuri)
67 user, passwd = authinfo
66 user, passwd = authinfo
68 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
67 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
69 if user and passwd:
68 if user and passwd:
70 self._writedebug(user, passwd)
69 self._writedebug(user, passwd)
71 return (pycompat.strurl(user), pycompat.strurl(passwd))
70 return (pycompat.strurl(user), pycompat.strurl(passwd))
72
71
73 if not user or not passwd:
72 if not user or not passwd:
74 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
73 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
75 if res:
74 if res:
76 group, auth = res
75 group, auth = res
77 user, passwd = auth.get(b'username'), auth.get(b'password')
76 user, passwd = auth.get(b'username'), auth.get(b'password')
78 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
77 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
79 if not user or not passwd:
78 if not user or not passwd:
80 u = urlutil.url(pycompat.bytesurl(authuri))
79 u = urlutil.url(pycompat.bytesurl(authuri))
81 u.query = None
80 u.query = None
82 if not self.ui.interactive():
81 if not self.ui.interactive():
83 raise error.Abort(
82 raise error.Abort(
84 _(b'http authorization required for %s')
83 _(b'http authorization required for %s')
85 % urlutil.hidepassword(bytes(u))
84 % urlutil.hidepassword(bytes(u))
86 )
85 )
87
86
88 self.ui.write(
87 self.ui.write(
89 _(b"http authorization required for %s\n")
88 _(b"http authorization required for %s\n")
90 % urlutil.hidepassword(bytes(u))
89 % urlutil.hidepassword(bytes(u))
91 )
90 )
92 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
91 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
93 if user:
92 if user:
94 self.ui.write(_(b"user: %s\n") % user)
93 self.ui.write(_(b"user: %s\n") % user)
95 else:
94 else:
96 user = self.ui.prompt(_(b"user:"), default=None)
95 user = self.ui.prompt(_(b"user:"), default=None)
97
96
98 if not passwd:
97 if not passwd:
99 passwd = self.ui.getpass()
98 passwd = self.ui.getpass()
100
99
101 # As of Python 3.8, the default implementation of
100 # As of Python 3.8, the default implementation of
102 # AbstractBasicAuthHandler.retry_http_basic_auth() assumes the user
101 # AbstractBasicAuthHandler.retry_http_basic_auth() assumes the user
103 # is set if pw is not None. This means (None, str) is not a valid
102 # is set if pw is not None. This means (None, str) is not a valid
104 # return type of find_user_password().
103 # return type of find_user_password().
105 if user is None:
104 if user is None:
106 return None, None
105 return None, None
107
106
108 self.passwddb.add_password(realm, authuri, user, passwd)
107 self.passwddb.add_password(realm, authuri, user, passwd)
109 self._writedebug(user, passwd)
108 self._writedebug(user, passwd)
110 return (pycompat.strurl(user), pycompat.strurl(passwd))
109 return (pycompat.strurl(user), pycompat.strurl(passwd))
111
110
112 def _writedebug(self, user, passwd):
111 def _writedebug(self, user, passwd):
113 msg = _(b'http auth: user %s, password %s\n')
112 msg = _(b'http auth: user %s, password %s\n')
114 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
113 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
115
114
116 def find_stored_password(self, authuri):
115 def find_stored_password(self, authuri):
117 return self.passwddb.find_user_password(None, authuri)
116 return self.passwddb.find_user_password(None, authuri)
118
117
119
118
120 class proxyhandler(urlreq.proxyhandler):
119 class proxyhandler(urlreq.proxyhandler):
121 def __init__(self, ui):
120 def __init__(self, ui):
122 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
121 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
123 b'http_proxy'
122 b'http_proxy'
124 )
123 )
125 # XXX proxyauthinfo = None
124 # XXX proxyauthinfo = None
126
125
127 if proxyurl:
126 if proxyurl:
128 # proxy can be proper url or host[:port]
127 # proxy can be proper url or host[:port]
129 if not (
128 if not (
130 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
129 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
131 ):
130 ):
132 proxyurl = b'http://' + proxyurl + b'/'
131 proxyurl = b'http://' + proxyurl + b'/'
133 proxy = urlutil.url(proxyurl)
132 proxy = urlutil.url(proxyurl)
134 if not proxy.user:
133 if not proxy.user:
135 proxy.user = ui.config(b"http_proxy", b"user")
134 proxy.user = ui.config(b"http_proxy", b"user")
136 proxy.passwd = ui.config(b"http_proxy", b"passwd")
135 proxy.passwd = ui.config(b"http_proxy", b"passwd")
137
136
138 # see if we should use a proxy for this url
137 # see if we should use a proxy for this url
139 no_list = [b"localhost", b"127.0.0.1"]
138 no_list = [b"localhost", b"127.0.0.1"]
140 no_list.extend(
139 no_list.extend(
141 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
140 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
142 )
141 )
143 no_list.extend(
142 no_list.extend(
144 [
143 [
145 p.strip().lower()
144 p.strip().lower()
146 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
145 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
147 if p.strip()
146 if p.strip()
148 ]
147 ]
149 )
148 )
150 # "http_proxy.always" config is for running tests on localhost
149 # "http_proxy.always" config is for running tests on localhost
151 if ui.configbool(b"http_proxy", b"always"):
150 if ui.configbool(b"http_proxy", b"always"):
152 self.no_list = []
151 self.no_list = []
153 else:
152 else:
154 self.no_list = no_list
153 self.no_list = no_list
155
154
156 # Keys and values need to be str because the standard library
155 # Keys and values need to be str because the standard library
157 # expects them to be.
156 # expects them to be.
158 proxyurl = str(proxy)
157 proxyurl = str(proxy)
159 proxies = {'http': proxyurl, 'https': proxyurl}
158 proxies = {'http': proxyurl, 'https': proxyurl}
160 ui.debug(
159 ui.debug(
161 b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy))
160 b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy))
162 )
161 )
163 else:
162 else:
164 proxies = {}
163 proxies = {}
165
164
166 urlreq.proxyhandler.__init__(self, proxies)
165 urlreq.proxyhandler.__init__(self, proxies)
167 self.ui = ui
166 self.ui = ui
168
167
169 def proxy_open(self, req, proxy, type_):
168 def proxy_open(self, req, proxy, type_):
170 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
169 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
171 for e in self.no_list:
170 for e in self.no_list:
172 if host == e:
171 if host == e:
173 return None
172 return None
174 if e.startswith(b'*.') and host.endswith(e[2:]):
173 if e.startswith(b'*.') and host.endswith(e[2:]):
175 return None
174 return None
176 if e.startswith(b'.') and host.endswith(e[1:]):
175 if e.startswith(b'.') and host.endswith(e[1:]):
177 return None
176 return None
178
177
179 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
178 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
180
179
181
180
182 def _gen_sendfile(orgsend):
181 def _gen_sendfile(orgsend):
183 def _sendfile(self, data):
182 def _sendfile(self, data):
184 # send a file
183 # send a file
185 if isinstance(data, httpconnectionmod.httpsendfile):
184 if isinstance(data, httpconnectionmod.httpsendfile):
186 # if auth required, some data sent twice, so rewind here
185 # if auth required, some data sent twice, so rewind here
187 data.seek(0)
186 data.seek(0)
188 for chunk in util.filechunkiter(data):
187 for chunk in util.filechunkiter(data):
189 orgsend(self, chunk)
188 orgsend(self, chunk)
190 else:
189 else:
191 orgsend(self, data)
190 orgsend(self, data)
192
191
193 return _sendfile
192 return _sendfile
194
193
195
194
196 has_https = util.safehasattr(urlreq, b'httpshandler')
195 has_https = util.safehasattr(urlreq, b'httpshandler')
197
196
198
197
199 class httpconnection(keepalive.HTTPConnection):
198 class httpconnection(keepalive.HTTPConnection):
200 # must be able to send big bundle as stream.
199 # must be able to send big bundle as stream.
201 send = _gen_sendfile(keepalive.HTTPConnection.send)
200 send = _gen_sendfile(keepalive.HTTPConnection.send)
202
201
203 def getresponse(self):
204 proxyres = getattr(self, 'proxyres', None)
205 if proxyres:
206 if proxyres.will_close:
207 self.close()
208 self.proxyres = None
209 return proxyres
210 return keepalive.HTTPConnection.getresponse(self)
211
212
202
213 # Large parts of this function have their origin from before Python 2.6
203 # Large parts of this function have their origin from before Python 2.6
214 # and could potentially be removed.
204 # and could potentially be removed.
215 def _generic_start_transaction(handler, h, req):
205 def _generic_start_transaction(handler, h, req):
216 tunnel_host = req._tunnel_host
206 tunnel_host = req._tunnel_host
217 if tunnel_host:
207 if tunnel_host:
218 if tunnel_host[:7] not in ['http://', 'https:/']:
208 if tunnel_host[:7] not in ['http://', 'https:/']:
219 tunnel_host = 'https://' + tunnel_host
209 tunnel_host = 'https://' + tunnel_host
220 new_tunnel = True
210 new_tunnel = True
221 else:
211 else:
222 tunnel_host = urllibcompat.getselector(req)
212 tunnel_host = urllibcompat.getselector(req)
223 new_tunnel = False
213 new_tunnel = False
224
214
225 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
215 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
226 u = urlutil.url(pycompat.bytesurl(tunnel_host))
216 u = urlutil.url(pycompat.bytesurl(tunnel_host))
227 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
217 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
228 h.realhostport = b':'.join([u.host, (u.port or b'443')])
218 h.realhostport = b':'.join([u.host, (u.port or b'443')])
229 h.headers = req.headers.copy()
219 h.headers = req.headers.copy()
230 h.headers.update(handler.parent.addheaders)
220 h.headers.update(handler.parent.addheaders)
231 return
221 return
232
222
233 h.realhostport = None
223 h.realhostport = None
234 h.headers = None
224 h.headers = None
235
225
236
226
237 def _generic_proxytunnel(self):
227 def _generic_proxytunnel(self):
238 proxyheaders = {
228 proxyheaders = {
239 pycompat.bytestr(x): pycompat.bytestr(self.headers[x])
229 pycompat.bytestr(x): pycompat.bytestr(self.headers[x])
240 for x in self.headers
230 for x in self.headers
241 if x.lower().startswith('proxy-')
231 if x.lower().startswith('proxy-')
242 }
232 }
243 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
233 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
244 for header in pycompat.iteritems(proxyheaders):
234 for header in pycompat.iteritems(proxyheaders):
245 self.send(b'%s: %s\r\n' % header)
235 self.send(b'%s: %s\r\n' % header)
246 self.send(b'\r\n')
236 self.send(b'\r\n')
247
237
248 # majority of the following code is duplicated from
238 # majority of the following code is duplicated from
249 # httplib.HTTPConnection as there are no adequate places to
239 # httplib.HTTPConnection as there are no adequate places to
250 # override functions to provide the needed functionality
240 # override functions to provide the needed functionality
251 # strict was removed in Python 3.4.
241 # strict was removed in Python 3.4.
252 kwargs = {}
242 kwargs = {}
253 if not pycompat.ispy3:
243 if not pycompat.ispy3:
254 kwargs[b'strict'] = self.strict
244 kwargs[b'strict'] = self.strict
255
245
256 res = self.response_class(self.sock, method=self._method, **kwargs)
246 res = self.response_class(self.sock, method=self._method, **kwargs)
257
247
258 while True:
248 while True:
259 version, status, reason = res._read_status()
249 version, status, reason = res._read_status()
260 if status != httplib.CONTINUE:
250 if status != httplib.CONTINUE:
261 break
251 break
262 # skip lines that are all whitespace
252 # skip lines that are all whitespace
263 list(iter(lambda: res.fp.readline().strip(), b''))
253 list(iter(lambda: res.fp.readline().strip(), b''))
264 res.status = status
265 res.reason = reason.strip()
266
254
267 if res.status == 200:
255 if status == 200:
268 # skip lines until we find a blank line
256 # skip lines until we find a blank line
269 list(iter(res.fp.readline, b'\r\n'))
257 list(iter(res.fp.readline, b'\r\n'))
270 return True
271
272 if version == b'HTTP/1.0':
273 res.version = 10
274 elif version.startswith(b'HTTP/1.'):
275 res.version = 11
276 elif version == b'HTTP/0.9':
277 res.version = 9
278 else:
258 else:
279 raise httplib.UnknownProtocol(version)
259 self.close()
280
260 raise socket.error(
281 if res.version == 9:
261 "Tunnel connection failed: %d %s" % (status, reason.strip())
282 res.length = None
262 )
283 res.chunked = 0
284 res.will_close = 1
285 res.msg = httplib.HTTPMessage(stringio())
286 return False
287
288 res.msg = httplib.HTTPMessage(res.fp)
289 res.msg.fp = None
290
291 # are we using the chunked-style of transfer encoding?
292 trenc = res.msg.getheader(b'transfer-encoding')
293 if trenc and trenc.lower() == b"chunked":
294 res.chunked = 1
295 res.chunk_left = None
296 else:
297 res.chunked = 0
298
299 # will the connection close at the end of the response?
300 res.will_close = res._check_close()
301
302 # do we have a Content-Length?
303 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
304 # transfer-encoding is "chunked"
305 length = res.msg.getheader(b'content-length')
306 if length and not res.chunked:
307 try:
308 res.length = int(length)
309 except ValueError:
310 res.length = None
311 else:
312 if res.length < 0: # ignore nonsensical negative lengths
313 res.length = None
314 else:
315 res.length = None
316
317 # does the body have a fixed length? (of zero)
318 if (
319 status == httplib.NO_CONTENT
320 or status == httplib.NOT_MODIFIED
321 or 100 <= status < 200
322 or res._method == b'HEAD' # 1xx codes
323 ):
324 res.length = 0
325
326 # if the connection remains open, and we aren't using chunked, and
327 # a content-length was not provided, then assume that the connection
328 # WILL close.
329 if not res.will_close and not res.chunked and res.length is None:
330 res.will_close = 1
331
332 self.proxyres = res
333
334 return False
335
263
336
264
337 class httphandler(keepalive.HTTPHandler):
265 class httphandler(keepalive.HTTPHandler):
338 def http_open(self, req):
266 def http_open(self, req):
339 return self.do_open(httpconnection, req)
267 return self.do_open(httpconnection, req)
340
268
341 def _start_transaction(self, h, req):
269 def _start_transaction(self, h, req):
342 _generic_start_transaction(self, h, req)
270 _generic_start_transaction(self, h, req)
343 return keepalive.HTTPHandler._start_transaction(self, h, req)
271 return keepalive.HTTPHandler._start_transaction(self, h, req)
344
272
345
273
346 class logginghttpconnection(keepalive.HTTPConnection):
274 class logginghttpconnection(keepalive.HTTPConnection):
347 def __init__(self, createconn, *args, **kwargs):
275 def __init__(self, createconn, *args, **kwargs):
348 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
276 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
349 self._create_connection = createconn
277 self._create_connection = createconn
350
278
351 if sys.version_info < (2, 7, 7):
279 if sys.version_info < (2, 7, 7):
352 # copied from 2.7.14, since old implementations directly call
280 # copied from 2.7.14, since old implementations directly call
353 # socket.create_connection()
281 # socket.create_connection()
354 def connect(self):
282 def connect(self):
355 self.sock = self._create_connection(
283 self.sock = self._create_connection(
356 (self.host, self.port), self.timeout, self.source_address
284 (self.host, self.port), self.timeout, self.source_address
357 )
285 )
358 if self._tunnel_host:
286 if self._tunnel_host:
359 self._tunnel()
287 self._tunnel()
360
288
361
289
362 class logginghttphandler(httphandler):
290 class logginghttphandler(httphandler):
363 """HTTP handler that logs socket I/O."""
291 """HTTP handler that logs socket I/O."""
364
292
365 def __init__(self, logfh, name, observeropts, timeout=None):
293 def __init__(self, logfh, name, observeropts, timeout=None):
366 super(logginghttphandler, self).__init__(timeout=timeout)
294 super(logginghttphandler, self).__init__(timeout=timeout)
367
295
368 self._logfh = logfh
296 self._logfh = logfh
369 self._logname = name
297 self._logname = name
370 self._observeropts = observeropts
298 self._observeropts = observeropts
371
299
372 # do_open() calls the passed class to instantiate an HTTPConnection. We
300 # do_open() calls the passed class to instantiate an HTTPConnection. We
373 # pass in a callable method that creates a custom HTTPConnection instance
301 # pass in a callable method that creates a custom HTTPConnection instance
374 # whose callback to create the socket knows how to proxy the socket.
302 # whose callback to create the socket knows how to proxy the socket.
375 def http_open(self, req):
303 def http_open(self, req):
376 return self.do_open(self._makeconnection, req)
304 return self.do_open(self._makeconnection, req)
377
305
378 def _makeconnection(self, *args, **kwargs):
306 def _makeconnection(self, *args, **kwargs):
379 def createconnection(*args, **kwargs):
307 def createconnection(*args, **kwargs):
380 sock = socket.create_connection(*args, **kwargs)
308 sock = socket.create_connection(*args, **kwargs)
381 return util.makeloggingsocket(
309 return util.makeloggingsocket(
382 self._logfh, sock, self._logname, **self._observeropts
310 self._logfh, sock, self._logname, **self._observeropts
383 )
311 )
384
312
385 return logginghttpconnection(createconnection, *args, **kwargs)
313 return logginghttpconnection(createconnection, *args, **kwargs)
386
314
387
315
388 if has_https:
316 if has_https:
389
317
390 class httpsconnection(keepalive.HTTPConnection):
318 class httpsconnection(keepalive.HTTPConnection):
391 response_class = keepalive.HTTPResponse
319 response_class = keepalive.HTTPResponse
392 default_port = httplib.HTTPS_PORT
320 default_port = httplib.HTTPS_PORT
393 # must be able to send big bundle as stream.
321 # must be able to send big bundle as stream.
394 send = _gen_sendfile(keepalive.safesend)
322 send = _gen_sendfile(keepalive.safesend)
395 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
323 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
396
324
397 def __init__(
325 def __init__(
398 self,
326 self,
399 host,
327 host,
400 port=None,
328 port=None,
401 key_file=None,
329 key_file=None,
402 cert_file=None,
330 cert_file=None,
403 *args,
331 *args,
404 **kwargs
332 **kwargs
405 ):
333 ):
406 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
334 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
407 self.key_file = key_file
335 self.key_file = key_file
408 self.cert_file = cert_file
336 self.cert_file = cert_file
409
337
410 def connect(self):
338 def connect(self):
411 self.sock = socket.create_connection((self.host, self.port))
339 self.sock = socket.create_connection((self.host, self.port))
412
340
413 host = self.host
341 host = self.host
414 if self.realhostport: # use CONNECT proxy
342 if self.realhostport: # use CONNECT proxy
415 _generic_proxytunnel(self)
343 _generic_proxytunnel(self)
416 host = self.realhostport.rsplit(b':', 1)[0]
344 host = self.realhostport.rsplit(b':', 1)[0]
417 self.sock = sslutil.wrapsocket(
345 self.sock = sslutil.wrapsocket(
418 self.sock,
346 self.sock,
419 self.key_file,
347 self.key_file,
420 self.cert_file,
348 self.cert_file,
421 ui=self.ui,
349 ui=self.ui,
422 serverhostname=host,
350 serverhostname=host,
423 )
351 )
424 sslutil.validatesocket(self.sock)
352 sslutil.validatesocket(self.sock)
425
353
426 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
354 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
427 def __init__(self, ui, timeout=None):
355 def __init__(self, ui, timeout=None):
428 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
356 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
429 urlreq.httpshandler.__init__(self)
357 urlreq.httpshandler.__init__(self)
430 self.ui = ui
358 self.ui = ui
431 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
359 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
432
360
433 def _start_transaction(self, h, req):
361 def _start_transaction(self, h, req):
434 _generic_start_transaction(self, h, req)
362 _generic_start_transaction(self, h, req)
435 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
363 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
436
364
437 def https_open(self, req):
365 def https_open(self, req):
438 # urllibcompat.getfullurl() does not contain credentials
366 # urllibcompat.getfullurl() does not contain credentials
439 # and we may need them to match the certificates.
367 # and we may need them to match the certificates.
440 url = urllibcompat.getfullurl(req)
368 url = urllibcompat.getfullurl(req)
441 user, password = self.pwmgr.find_stored_password(url)
369 user, password = self.pwmgr.find_stored_password(url)
442 res = httpconnectionmod.readauthforuri(self.ui, url, user)
370 res = httpconnectionmod.readauthforuri(self.ui, url, user)
443 if res:
371 if res:
444 group, auth = res
372 group, auth = res
445 self.auth = auth
373 self.auth = auth
446 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
374 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
447 else:
375 else:
448 self.auth = None
376 self.auth = None
449 return self.do_open(self._makeconnection, req)
377 return self.do_open(self._makeconnection, req)
450
378
451 def _makeconnection(self, host, port=None, *args, **kwargs):
379 def _makeconnection(self, host, port=None, *args, **kwargs):
452 keyfile = None
380 keyfile = None
453 certfile = None
381 certfile = None
454
382
455 if len(args) >= 1: # key_file
383 if len(args) >= 1: # key_file
456 keyfile = args[0]
384 keyfile = args[0]
457 if len(args) >= 2: # cert_file
385 if len(args) >= 2: # cert_file
458 certfile = args[1]
386 certfile = args[1]
459 args = args[2:]
387 args = args[2:]
460
388
461 # if the user has specified different key/cert files in
389 # if the user has specified different key/cert files in
462 # hgrc, we prefer these
390 # hgrc, we prefer these
463 if self.auth and b'key' in self.auth and b'cert' in self.auth:
391 if self.auth and b'key' in self.auth and b'cert' in self.auth:
464 keyfile = self.auth[b'key']
392 keyfile = self.auth[b'key']
465 certfile = self.auth[b'cert']
393 certfile = self.auth[b'cert']
466
394
467 conn = httpsconnection(
395 conn = httpsconnection(
468 host, port, keyfile, certfile, *args, **kwargs
396 host, port, keyfile, certfile, *args, **kwargs
469 )
397 )
470 conn.ui = self.ui
398 conn.ui = self.ui
471 return conn
399 return conn
472
400
473
401
474 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
402 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
475 def __init__(self, *args, **kwargs):
403 def __init__(self, *args, **kwargs):
476 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
404 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
477 self.retried_req = None
405 self.retried_req = None
478
406
479 def reset_retry_count(self):
407 def reset_retry_count(self):
480 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
408 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
481 # forever. We disable reset_retry_count completely and reset in
409 # forever. We disable reset_retry_count completely and reset in
482 # http_error_auth_reqed instead.
410 # http_error_auth_reqed instead.
483 pass
411 pass
484
412
485 def http_error_auth_reqed(self, auth_header, host, req, headers):
413 def http_error_auth_reqed(self, auth_header, host, req, headers):
486 # Reset the retry counter once for each request.
414 # Reset the retry counter once for each request.
487 if req is not self.retried_req:
415 if req is not self.retried_req:
488 self.retried_req = req
416 self.retried_req = req
489 self.retried = 0
417 self.retried = 0
490 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
418 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
491 self, auth_header, host, req, headers
419 self, auth_header, host, req, headers
492 )
420 )
493
421
494
422
495 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
423 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
496 def __init__(self, *args, **kwargs):
424 def __init__(self, *args, **kwargs):
497 self.auth = None
425 self.auth = None
498 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
426 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
499 self.retried_req = None
427 self.retried_req = None
500
428
501 def http_request(self, request):
429 def http_request(self, request):
502 if self.auth:
430 if self.auth:
503 request.add_unredirected_header(self.auth_header, self.auth)
431 request.add_unredirected_header(self.auth_header, self.auth)
504
432
505 return request
433 return request
506
434
507 def https_request(self, request):
435 def https_request(self, request):
508 if self.auth:
436 if self.auth:
509 request.add_unredirected_header(self.auth_header, self.auth)
437 request.add_unredirected_header(self.auth_header, self.auth)
510
438
511 return request
439 return request
512
440
513 def reset_retry_count(self):
441 def reset_retry_count(self):
514 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
442 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
515 # forever. We disable reset_retry_count completely and reset in
443 # forever. We disable reset_retry_count completely and reset in
516 # http_error_auth_reqed instead.
444 # http_error_auth_reqed instead.
517 pass
445 pass
518
446
519 def http_error_auth_reqed(self, auth_header, host, req, headers):
447 def http_error_auth_reqed(self, auth_header, host, req, headers):
520 # Reset the retry counter once for each request.
448 # Reset the retry counter once for each request.
521 if req is not self.retried_req:
449 if req is not self.retried_req:
522 self.retried_req = req
450 self.retried_req = req
523 self.retried = 0
451 self.retried = 0
524 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
452 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
525 self, auth_header, host, req, headers
453 self, auth_header, host, req, headers
526 )
454 )
527
455
528 def retry_http_basic_auth(self, host, req, realm):
456 def retry_http_basic_auth(self, host, req, realm):
529 user, pw = self.passwd.find_user_password(
457 user, pw = self.passwd.find_user_password(
530 realm, urllibcompat.getfullurl(req)
458 realm, urllibcompat.getfullurl(req)
531 )
459 )
532 if pw is not None:
460 if pw is not None:
533 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
461 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
534 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
462 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
535 if req.get_header(self.auth_header, None) == auth:
463 if req.get_header(self.auth_header, None) == auth:
536 return None
464 return None
537 self.auth = auth
465 self.auth = auth
538 req.add_unredirected_header(self.auth_header, auth)
466 req.add_unredirected_header(self.auth_header, auth)
539 return self.parent.open(req)
467 return self.parent.open(req)
540 else:
468 else:
541 return None
469 return None
542
470
543
471
544 class cookiehandler(urlreq.basehandler):
472 class cookiehandler(urlreq.basehandler):
545 def __init__(self, ui):
473 def __init__(self, ui):
546 self.cookiejar = None
474 self.cookiejar = None
547
475
548 cookiefile = ui.config(b'auth', b'cookiefile')
476 cookiefile = ui.config(b'auth', b'cookiefile')
549 if not cookiefile:
477 if not cookiefile:
550 return
478 return
551
479
552 cookiefile = util.expandpath(cookiefile)
480 cookiefile = util.expandpath(cookiefile)
553 try:
481 try:
554 cookiejar = util.cookielib.MozillaCookieJar(
482 cookiejar = util.cookielib.MozillaCookieJar(
555 pycompat.fsdecode(cookiefile)
483 pycompat.fsdecode(cookiefile)
556 )
484 )
557 cookiejar.load()
485 cookiejar.load()
558 self.cookiejar = cookiejar
486 self.cookiejar = cookiejar
559 except util.cookielib.LoadError as e:
487 except util.cookielib.LoadError as e:
560 ui.warn(
488 ui.warn(
561 _(
489 _(
562 b'(error loading cookie file %s: %s; continuing without '
490 b'(error loading cookie file %s: %s; continuing without '
563 b'cookies)\n'
491 b'cookies)\n'
564 )
492 )
565 % (cookiefile, stringutil.forcebytestr(e))
493 % (cookiefile, stringutil.forcebytestr(e))
566 )
494 )
567
495
568 def http_request(self, request):
496 def http_request(self, request):
569 if self.cookiejar:
497 if self.cookiejar:
570 self.cookiejar.add_cookie_header(request)
498 self.cookiejar.add_cookie_header(request)
571
499
572 return request
500 return request
573
501
574 def https_request(self, request):
502 def https_request(self, request):
575 if self.cookiejar:
503 if self.cookiejar:
576 self.cookiejar.add_cookie_header(request)
504 self.cookiejar.add_cookie_header(request)
577
505
578 return request
506 return request
579
507
580
508
581 handlerfuncs = []
509 handlerfuncs = []
582
510
583
511
584 def opener(
512 def opener(
585 ui,
513 ui,
586 authinfo=None,
514 authinfo=None,
587 useragent=None,
515 useragent=None,
588 loggingfh=None,
516 loggingfh=None,
589 loggingname=b's',
517 loggingname=b's',
590 loggingopts=None,
518 loggingopts=None,
591 sendaccept=True,
519 sendaccept=True,
592 ):
520 ):
593 """
521 """
594 construct an opener suitable for urllib2
522 construct an opener suitable for urllib2
595 authinfo will be added to the password manager
523 authinfo will be added to the password manager
596
524
597 The opener can be configured to log socket events if the various
525 The opener can be configured to log socket events if the various
598 ``logging*`` arguments are specified.
526 ``logging*`` arguments are specified.
599
527
600 ``loggingfh`` denotes a file object to log events to.
528 ``loggingfh`` denotes a file object to log events to.
601 ``loggingname`` denotes the name of the to print when logging.
529 ``loggingname`` denotes the name of the to print when logging.
602 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
530 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
603 ``util.socketobserver`` instance.
531 ``util.socketobserver`` instance.
604
532
605 ``sendaccept`` allows controlling whether the ``Accept`` request header
533 ``sendaccept`` allows controlling whether the ``Accept`` request header
606 is sent. The header is sent by default.
534 is sent. The header is sent by default.
607 """
535 """
608 timeout = ui.configwith(float, b'http', b'timeout')
536 timeout = ui.configwith(float, b'http', b'timeout')
609 handlers = []
537 handlers = []
610
538
611 if loggingfh:
539 if loggingfh:
612 handlers.append(
540 handlers.append(
613 logginghttphandler(
541 logginghttphandler(
614 loggingfh, loggingname, loggingopts or {}, timeout=timeout
542 loggingfh, loggingname, loggingopts or {}, timeout=timeout
615 )
543 )
616 )
544 )
617 # We don't yet support HTTPS when logging I/O. If we attempt to open
545 # We don't yet support HTTPS when logging I/O. If we attempt to open
618 # an HTTPS URL, we'll likely fail due to unknown protocol.
546 # an HTTPS URL, we'll likely fail due to unknown protocol.
619
547
620 else:
548 else:
621 handlers.append(httphandler(timeout=timeout))
549 handlers.append(httphandler(timeout=timeout))
622 if has_https:
550 if has_https:
623 handlers.append(httpshandler(ui, timeout=timeout))
551 handlers.append(httpshandler(ui, timeout=timeout))
624
552
625 handlers.append(proxyhandler(ui))
553 handlers.append(proxyhandler(ui))
626
554
627 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
555 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
628 if authinfo is not None:
556 if authinfo is not None:
629 realm, uris, user, passwd = authinfo
557 realm, uris, user, passwd = authinfo
630 saveduser, savedpass = passmgr.find_stored_password(uris[0])
558 saveduser, savedpass = passmgr.find_stored_password(uris[0])
631 if user != saveduser or passwd:
559 if user != saveduser or passwd:
632 passmgr.add_password(realm, uris, user, passwd)
560 passmgr.add_password(realm, uris, user, passwd)
633 ui.debug(
561 ui.debug(
634 b'http auth: user %s, password %s\n'
562 b'http auth: user %s, password %s\n'
635 % (user, passwd and b'*' * len(passwd) or b'not set')
563 % (user, passwd and b'*' * len(passwd) or b'not set')
636 )
564 )
637
565
638 handlers.extend(
566 handlers.extend(
639 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
567 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
640 )
568 )
641 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
569 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
642 handlers.append(cookiehandler(ui))
570 handlers.append(cookiehandler(ui))
643 opener = urlreq.buildopener(*handlers)
571 opener = urlreq.buildopener(*handlers)
644
572
645 # keepalive.py's handlers will populate these attributes if they exist.
573 # keepalive.py's handlers will populate these attributes if they exist.
646 opener.requestscount = 0
574 opener.requestscount = 0
647 opener.sentbytescount = 0
575 opener.sentbytescount = 0
648 opener.receivedbytescount = 0
576 opener.receivedbytescount = 0
649
577
650 # The user agent should should *NOT* be used by servers for e.g.
578 # The user agent should should *NOT* be used by servers for e.g.
651 # protocol detection or feature negotiation: there are other
579 # protocol detection or feature negotiation: there are other
652 # facilities for that.
580 # facilities for that.
653 #
581 #
654 # "mercurial/proto-1.0" was the original user agent string and
582 # "mercurial/proto-1.0" was the original user agent string and
655 # exists for backwards compatibility reasons.
583 # exists for backwards compatibility reasons.
656 #
584 #
657 # The "(Mercurial %s)" string contains the distribution
585 # The "(Mercurial %s)" string contains the distribution
658 # name and version. Other client implementations should choose their
586 # name and version. Other client implementations should choose their
659 # own distribution name. Since servers should not be using the user
587 # own distribution name. Since servers should not be using the user
660 # agent string for anything, clients should be able to define whatever
588 # agent string for anything, clients should be able to define whatever
661 # user agent they deem appropriate.
589 # user agent they deem appropriate.
662 #
590 #
663 # The custom user agent is for lfs, because unfortunately some servers
591 # The custom user agent is for lfs, because unfortunately some servers
664 # do look at this value.
592 # do look at this value.
665 if not useragent:
593 if not useragent:
666 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
594 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
667 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
595 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
668 else:
596 else:
669 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
597 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
670
598
671 # This header should only be needed by wire protocol requests. But it has
599 # This header should only be needed by wire protocol requests. But it has
672 # been sent on all requests since forever. We keep sending it for backwards
600 # been sent on all requests since forever. We keep sending it for backwards
673 # compatibility reasons. Modern versions of the wire protocol use
601 # compatibility reasons. Modern versions of the wire protocol use
674 # X-HgProto-<N> for advertising client support.
602 # X-HgProto-<N> for advertising client support.
675 if sendaccept:
603 if sendaccept:
676 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
604 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
677
605
678 return opener
606 return opener
679
607
680
608
681 def open(ui, url_, data=None, sendaccept=True):
609 def open(ui, url_, data=None, sendaccept=True):
682 u = urlutil.url(url_)
610 u = urlutil.url(url_)
683 if u.scheme:
611 if u.scheme:
684 u.scheme = u.scheme.lower()
612 u.scheme = u.scheme.lower()
685 url_, authinfo = u.authinfo()
613 url_, authinfo = u.authinfo()
686 else:
614 else:
687 path = util.normpath(util.abspath(url_))
615 path = util.normpath(util.abspath(url_))
688 url_ = b'file://' + pycompat.bytesurl(
616 url_ = b'file://' + pycompat.bytesurl(
689 urlreq.pathname2url(pycompat.fsdecode(path))
617 urlreq.pathname2url(pycompat.fsdecode(path))
690 )
618 )
691 authinfo = None
619 authinfo = None
692 return opener(ui, authinfo, sendaccept=sendaccept).open(
620 return opener(ui, authinfo, sendaccept=sendaccept).open(
693 pycompat.strurl(url_), data
621 pycompat.strurl(url_), data
694 )
622 )
695
623
696
624
697 def wrapresponse(resp):
625 def wrapresponse(resp):
698 """Wrap a response object with common error handlers.
626 """Wrap a response object with common error handlers.
699
627
700 This ensures that any I/O from any consumer raises the appropriate
628 This ensures that any I/O from any consumer raises the appropriate
701 error and messaging.
629 error and messaging.
702 """
630 """
703 origread = resp.read
631 origread = resp.read
704
632
705 class readerproxy(resp.__class__):
633 class readerproxy(resp.__class__):
706 def read(self, size=None):
634 def read(self, size=None):
707 try:
635 try:
708 return origread(size)
636 return origread(size)
709 except httplib.IncompleteRead as e:
637 except httplib.IncompleteRead as e:
710 # e.expected is an integer if length known or None otherwise.
638 # e.expected is an integer if length known or None otherwise.
711 if e.expected:
639 if e.expected:
712 got = len(e.partial)
640 got = len(e.partial)
713 total = e.expected + got
641 total = e.expected + got
714 msg = _(
642 msg = _(
715 b'HTTP request error (incomplete response; '
643 b'HTTP request error (incomplete response; '
716 b'expected %d bytes got %d)'
644 b'expected %d bytes got %d)'
717 ) % (total, got)
645 ) % (total, got)
718 else:
646 else:
719 msg = _(b'HTTP request error (incomplete response)')
647 msg = _(b'HTTP request error (incomplete response)')
720
648
721 raise error.PeerTransportError(
649 raise error.PeerTransportError(
722 msg,
650 msg,
723 hint=_(
651 hint=_(
724 b'this may be an intermittent network failure; '
652 b'this may be an intermittent network failure; '
725 b'if the error persists, consider contacting the '
653 b'if the error persists, consider contacting the '
726 b'network or server operator'
654 b'network or server operator'
727 ),
655 ),
728 )
656 )
729 except httplib.HTTPException as e:
657 except httplib.HTTPException as e:
730 raise error.PeerTransportError(
658 raise error.PeerTransportError(
731 _(b'HTTP request error (%s)') % e,
659 _(b'HTTP request error (%s)') % e,
732 hint=_(
660 hint=_(
733 b'this may be an intermittent network failure; '
661 b'this may be an intermittent network failure; '
734 b'if the error persists, consider contacting the '
662 b'if the error persists, consider contacting the '
735 b'network or server operator'
663 b'network or server operator'
736 ),
664 ),
737 )
665 )
738
666
739 resp.__class__ = readerproxy
667 resp.__class__ = readerproxy
@@ -1,554 +1,561
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 Disable the system configuration which may set stricter TLS requirements.
5 Disable the system configuration which may set stricter TLS requirements.
6 This test expects that legacy TLS versions are supported.
6 This test expects that legacy TLS versions are supported.
7
7
8 $ OPENSSL_CONF=
8 $ OPENSSL_CONF=
9 $ export OPENSSL_CONF
9 $ export OPENSSL_CONF
10
10
11 Make server certificates:
11 Make server certificates:
12
12
13 $ CERTSDIR="$TESTDIR/sslcerts"
13 $ CERTSDIR="$TESTDIR/sslcerts"
14 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
14 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
15 $ PRIV=`pwd`/server.pem
15 $ PRIV=`pwd`/server.pem
16 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
16 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
17 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
17 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
18
18
19 $ hg init test
19 $ hg init test
20 $ cd test
20 $ cd test
21 $ echo foo>foo
21 $ echo foo>foo
22 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
22 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
23 $ echo foo>foo.d/foo
23 $ echo foo>foo.d/foo
24 $ echo bar>foo.d/bAr.hg.d/BaR
24 $ echo bar>foo.d/bAr.hg.d/BaR
25 $ echo bar>foo.d/baR.d.hg/bAR
25 $ echo bar>foo.d/baR.d.hg/bAR
26 $ hg commit -A -m 1
26 $ hg commit -A -m 1
27 adding foo
27 adding foo
28 adding foo.d/bAr.hg.d/BaR
28 adding foo.d/bAr.hg.d/BaR
29 adding foo.d/baR.d.hg/bAR
29 adding foo.d/baR.d.hg/bAR
30 adding foo.d/foo
30 adding foo.d/foo
31 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
31 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
32 $ cat ../hg0.pid >> $DAEMON_PIDS
32 $ cat ../hg0.pid >> $DAEMON_PIDS
33
33
34 cacert not found
34 cacert not found
35
35
36 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
36 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
37 abort: could not find web.cacerts: no-such.pem
37 abort: could not find web.cacerts: no-such.pem
38 [255]
38 [255]
39
39
40 Test server address cannot be reused
40 Test server address cannot be reused
41
41
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
43 abort: cannot start server at 'localhost:$HGPORT': $EADDRINUSE$
43 abort: cannot start server at 'localhost:$HGPORT': $EADDRINUSE$
44 [255]
44 [255]
45
45
46 $ cd ..
46 $ cd ..
47
47
48 Our test cert is not signed by a trusted CA. It should fail to verify if
48 Our test cert is not signed by a trusted CA. It should fail to verify if
49 we are able to load CA certs.
49 we are able to load CA certs.
50
50
51 #if no-defaultcacertsloaded
51 #if no-defaultcacertsloaded
52 $ hg clone https://localhost:$HGPORT/ copy-pull
52 $ hg clone https://localhost:$HGPORT/ copy-pull
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
54 abort: error: *certificate verify failed* (glob)
54 abort: error: *certificate verify failed* (glob)
55 [100]
55 [100]
56 #endif
56 #endif
57
57
58 #if defaultcacertsloaded
58 #if defaultcacertsloaded
59 $ hg clone https://localhost:$HGPORT/ copy-pull
59 $ hg clone https://localhost:$HGPORT/ copy-pull
60 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
60 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
61 abort: error: *certificate verify failed* (glob)
61 abort: error: *certificate verify failed* (glob)
62 [100]
62 [100]
63 #endif
63 #endif
64
64
65 Specifying a per-host certificate file that doesn't exist will abort. The full
65 Specifying a per-host certificate file that doesn't exist will abort. The full
66 C:/path/to/msysroot will print on Windows.
66 C:/path/to/msysroot will print on Windows.
67
67
68 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
68 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
69 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: */does/not/exist (glob)
69 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: */does/not/exist (glob)
70 [255]
70 [255]
71
71
72 A malformed per-host certificate file will raise an error
72 A malformed per-host certificate file will raise an error
73
73
74 $ echo baddata > badca.pem
74 $ echo baddata > badca.pem
75 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
75 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
76 abort: error loading CA file badca.pem: * (glob)
76 abort: error loading CA file badca.pem: * (glob)
77 (file is empty or malformed?)
77 (file is empty or malformed?)
78 [255]
78 [255]
79
79
80 A per-host certificate mismatching the server will fail verification
80 A per-host certificate mismatching the server will fail verification
81
81
82 (modern ssl is able to discern whether the loaded cert is a CA cert)
82 (modern ssl is able to discern whether the loaded cert is a CA cert)
83 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
83 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
84 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
84 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
85 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
85 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
86 abort: error: *certificate verify failed* (glob)
86 abort: error: *certificate verify failed* (glob)
87 [100]
87 [100]
88
88
89 A per-host certificate matching the server's cert will be accepted
89 A per-host certificate matching the server's cert will be accepted
90
90
91 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
91 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
92 requesting all changes
92 requesting all changes
93 adding changesets
93 adding changesets
94 adding manifests
94 adding manifests
95 adding file changes
95 adding file changes
96 added 1 changesets with 4 changes to 4 files
96 added 1 changesets with 4 changes to 4 files
97 new changesets 8b6053c928fe
97 new changesets 8b6053c928fe
98
98
99 A per-host certificate with multiple certs and one matching will be accepted
99 A per-host certificate with multiple certs and one matching will be accepted
100
100
101 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
101 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
102 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
102 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
103 requesting all changes
103 requesting all changes
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 1 changesets with 4 changes to 4 files
107 added 1 changesets with 4 changes to 4 files
108 new changesets 8b6053c928fe
108 new changesets 8b6053c928fe
109
109
110 Defining both per-host certificate and a fingerprint will print a warning
110 Defining both per-host certificate and a fingerprint will print a warning
111
111
112 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
112 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
113 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
113 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
114 requesting all changes
114 requesting all changes
115 adding changesets
115 adding changesets
116 adding manifests
116 adding manifests
117 adding file changes
117 adding file changes
118 added 1 changesets with 4 changes to 4 files
118 added 1 changesets with 4 changes to 4 files
119 new changesets 8b6053c928fe
119 new changesets 8b6053c928fe
120
120
121 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
121 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
122
122
123 Inability to verify peer certificate will result in abort
123 Inability to verify peer certificate will result in abort
124
124
125 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
125 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
126 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
126 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
127 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
127 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
128 [150]
128 [150]
129
129
130 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
130 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
131 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
131 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
132 requesting all changes
132 requesting all changes
133 adding changesets
133 adding changesets
134 adding manifests
134 adding manifests
135 adding file changes
135 adding file changes
136 added 1 changesets with 4 changes to 4 files
136 added 1 changesets with 4 changes to 4 files
137 new changesets 8b6053c928fe
137 new changesets 8b6053c928fe
138 updating to branch default
138 updating to branch default
139 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 $ hg verify -R copy-pull
140 $ hg verify -R copy-pull
141 checking changesets
141 checking changesets
142 checking manifests
142 checking manifests
143 crosschecking files in changesets and manifests
143 crosschecking files in changesets and manifests
144 checking files
144 checking files
145 checked 1 changesets with 4 changes to 4 files
145 checked 1 changesets with 4 changes to 4 files
146 $ cd test
146 $ cd test
147 $ echo bar > bar
147 $ echo bar > bar
148 $ hg commit -A -d '1 0' -m 2
148 $ hg commit -A -d '1 0' -m 2
149 adding bar
149 adding bar
150 $ cd ..
150 $ cd ..
151
151
152 pull without cacert
152 pull without cacert
153
153
154 $ cd copy-pull
154 $ cd copy-pull
155 $ cat >> .hg/hgrc <<EOF
155 $ cat >> .hg/hgrc <<EOF
156 > [hooks]
156 > [hooks]
157 > changegroup = sh -c "printenv.py --line changegroup"
157 > changegroup = sh -c "printenv.py --line changegroup"
158 > EOF
158 > EOF
159 $ hg pull $DISABLECACERTS
159 $ hg pull $DISABLECACERTS
160 pulling from https://localhost:$HGPORT/
160 pulling from https://localhost:$HGPORT/
161 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
161 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
162 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
162 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
163 [150]
163 [150]
164
164
165 $ hg pull --insecure
165 $ hg pull --insecure
166 pulling from https://localhost:$HGPORT/
166 pulling from https://localhost:$HGPORT/
167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
168 searching for changes
168 searching for changes
169 adding changesets
169 adding changesets
170 adding manifests
170 adding manifests
171 adding file changes
171 adding file changes
172 added 1 changesets with 1 changes to 1 files
172 added 1 changesets with 1 changes to 1 files
173 new changesets 5fed3813f7f5
173 new changesets 5fed3813f7f5
174 changegroup hook: HG_HOOKNAME=changegroup
174 changegroup hook: HG_HOOKNAME=changegroup
175 HG_HOOKTYPE=changegroup
175 HG_HOOKTYPE=changegroup
176 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
176 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
177 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
177 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
178 HG_SOURCE=pull
178 HG_SOURCE=pull
179 HG_TXNID=TXN:$ID$
179 HG_TXNID=TXN:$ID$
180 HG_TXNNAME=pull
180 HG_TXNNAME=pull
181 https://localhost:$HGPORT/
181 https://localhost:$HGPORT/
182 HG_URL=https://localhost:$HGPORT/
182 HG_URL=https://localhost:$HGPORT/
183
183
184 (run 'hg update' to get a working copy)
184 (run 'hg update' to get a working copy)
185 $ cd ..
185 $ cd ..
186
186
187 cacert configured in local repo
187 cacert configured in local repo
188
188
189 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
189 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
190 $ echo "[web]" >> copy-pull/.hg/hgrc
190 $ echo "[web]" >> copy-pull/.hg/hgrc
191 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
191 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
192 $ hg -R copy-pull pull
192 $ hg -R copy-pull pull
193 pulling from https://localhost:$HGPORT/
193 pulling from https://localhost:$HGPORT/
194 searching for changes
194 searching for changes
195 no changes found
195 no changes found
196 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
196 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
197
197
198 cacert configured globally, also testing expansion of environment
198 cacert configured globally, also testing expansion of environment
199 variables in the filename
199 variables in the filename
200
200
201 $ echo "[web]" >> $HGRCPATH
201 $ echo "[web]" >> $HGRCPATH
202 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
202 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
203 $ P="$CERTSDIR" hg -R copy-pull pull
203 $ P="$CERTSDIR" hg -R copy-pull pull
204 pulling from https://localhost:$HGPORT/
204 pulling from https://localhost:$HGPORT/
205 searching for changes
205 searching for changes
206 no changes found
206 no changes found
207 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
207 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
208 pulling from https://localhost:$HGPORT/
208 pulling from https://localhost:$HGPORT/
209 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
209 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
210 searching for changes
210 searching for changes
211 no changes found
211 no changes found
212
212
213 empty cacert file
213 empty cacert file
214
214
215 $ touch emptycafile
215 $ touch emptycafile
216
216
217 $ hg --config web.cacerts=emptycafile -R copy-pull pull
217 $ hg --config web.cacerts=emptycafile -R copy-pull pull
218 pulling from https://localhost:$HGPORT/
218 pulling from https://localhost:$HGPORT/
219 abort: error loading CA file emptycafile: * (glob)
219 abort: error loading CA file emptycafile: * (glob)
220 (file is empty or malformed?)
220 (file is empty or malformed?)
221 [255]
221 [255]
222
222
223 cacert mismatch
223 cacert mismatch
224
224
225 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
225 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
226 > https://$LOCALIP:$HGPORT/
226 > https://$LOCALIP:$HGPORT/
227 pulling from https://*:$HGPORT/ (glob)
227 pulling from https://*:$HGPORT/ (glob)
228 abort: $LOCALIP certificate error: certificate is for localhost (glob)
228 abort: $LOCALIP certificate error: certificate is for localhost (glob)
229 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
229 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
230 [150]
230 [150]
231 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
231 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
232 > https://$LOCALIP:$HGPORT/ --insecure
232 > https://$LOCALIP:$HGPORT/ --insecure
233 pulling from https://*:$HGPORT/ (glob)
233 pulling from https://*:$HGPORT/ (glob)
234 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
234 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
235 searching for changes
235 searching for changes
236 no changes found
236 no changes found
237 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
237 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
238 pulling from https://localhost:$HGPORT/
238 pulling from https://localhost:$HGPORT/
239 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
239 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
240 abort: error: *certificate verify failed* (glob)
240 abort: error: *certificate verify failed* (glob)
241 [100]
241 [100]
242 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
242 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
243 > --insecure
243 > --insecure
244 pulling from https://localhost:$HGPORT/
244 pulling from https://localhost:$HGPORT/
245 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
245 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
246 searching for changes
246 searching for changes
247 no changes found
247 no changes found
248
248
249 Test server cert which isn't valid yet
249 Test server cert which isn't valid yet
250
250
251 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
251 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
252 $ cat hg1.pid >> $DAEMON_PIDS
252 $ cat hg1.pid >> $DAEMON_PIDS
253 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
253 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
254 > https://localhost:$HGPORT1/
254 > https://localhost:$HGPORT1/
255 pulling from https://localhost:$HGPORT1/
255 pulling from https://localhost:$HGPORT1/
256 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
256 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
257 abort: error: *certificate verify failed* (glob)
257 abort: error: *certificate verify failed* (glob)
258 [100]
258 [100]
259
259
260 Test server cert which no longer is valid
260 Test server cert which no longer is valid
261
261
262 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
262 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
263 $ cat hg2.pid >> $DAEMON_PIDS
263 $ cat hg2.pid >> $DAEMON_PIDS
264 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
264 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
265 > https://localhost:$HGPORT2/
265 > https://localhost:$HGPORT2/
266 pulling from https://localhost:$HGPORT2/
266 pulling from https://localhost:$HGPORT2/
267 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
267 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
268 abort: error: *certificate verify failed* (glob)
268 abort: error: *certificate verify failed* (glob)
269 [100]
269 [100]
270
270
271 Setting ciphers to an invalid value aborts
271 Setting ciphers to an invalid value aborts
272 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
272 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
273 abort: could not set ciphers: No cipher can be selected.
273 abort: could not set ciphers: No cipher can be selected.
274 (change cipher string (invalid) in config)
274 (change cipher string (invalid) in config)
275 [255]
275 [255]
276
276
277 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
277 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
278 abort: could not set ciphers: No cipher can be selected.
278 abort: could not set ciphers: No cipher can be selected.
279 (change cipher string (invalid) in config)
279 (change cipher string (invalid) in config)
280 [255]
280 [255]
281
281
282 Changing the cipher string works
282 Changing the cipher string works
283
283
284 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
284 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
285 5fed3813f7f5
285 5fed3813f7f5
286
286
287 Fingerprints
287 Fingerprints
288
288
289 - works without cacerts (hostfingerprints)
289 - works without cacerts (hostfingerprints)
290 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
290 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
291 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
291 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
292 5fed3813f7f5
292 5fed3813f7f5
293
293
294 - works without cacerts (hostsecurity)
294 - works without cacerts (hostsecurity)
295 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
295 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
296 5fed3813f7f5
296 5fed3813f7f5
297
297
298 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
298 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
299 5fed3813f7f5
299 5fed3813f7f5
300
300
301 - multiple fingerprints specified and first matches
301 - multiple fingerprints specified and first matches
302 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
302 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
303 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
303 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
304 5fed3813f7f5
304 5fed3813f7f5
305
305
306 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
306 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
307 5fed3813f7f5
307 5fed3813f7f5
308
308
309 - multiple fingerprints specified and last matches
309 - multiple fingerprints specified and last matches
310 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
310 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
311 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
311 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
312 5fed3813f7f5
312 5fed3813f7f5
313
313
314 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
314 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
315 5fed3813f7f5
315 5fed3813f7f5
316
316
317 - multiple fingerprints specified and none match
317 - multiple fingerprints specified and none match
318
318
319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
320 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
320 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
321 (check hostfingerprint configuration)
321 (check hostfingerprint configuration)
322 [150]
322 [150]
323
323
324 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
324 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
325 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
325 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
326 (check hostsecurity configuration)
326 (check hostsecurity configuration)
327 [150]
327 [150]
328
328
329 - fails when cert doesn't match hostname (port is ignored)
329 - fails when cert doesn't match hostname (port is ignored)
330 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
330 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
331 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
331 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
332 (check hostfingerprint configuration)
332 (check hostfingerprint configuration)
333 [150]
333 [150]
334
334
335
335
336 - ignores that certificate doesn't match hostname
336 - ignores that certificate doesn't match hostname
337 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
337 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
338 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: $LOCALIP:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
338 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: $LOCALIP:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
339 5fed3813f7f5
339 5fed3813f7f5
340
340
341 Ports used by next test. Kill servers.
341 Ports used by next test. Kill servers.
342
342
343 $ killdaemons.py hg0.pid
343 $ killdaemons.py hg0.pid
344 $ killdaemons.py hg1.pid
344 $ killdaemons.py hg1.pid
345 $ killdaemons.py hg2.pid
345 $ killdaemons.py hg2.pid
346
346
347 #if tls1.2
347 #if tls1.2
348 Start servers running supported TLS versions
348 Start servers running supported TLS versions
349
349
350 $ cd test
350 $ cd test
351 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
351 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
352 > --config devel.serverexactprotocol=tls1.0
352 > --config devel.serverexactprotocol=tls1.0
353 $ cat ../hg0.pid >> $DAEMON_PIDS
353 $ cat ../hg0.pid >> $DAEMON_PIDS
354 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
354 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
355 > --config devel.serverexactprotocol=tls1.1
355 > --config devel.serverexactprotocol=tls1.1
356 $ cat ../hg1.pid >> $DAEMON_PIDS
356 $ cat ../hg1.pid >> $DAEMON_PIDS
357 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
357 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
358 > --config devel.serverexactprotocol=tls1.2
358 > --config devel.serverexactprotocol=tls1.2
359 $ cat ../hg2.pid >> $DAEMON_PIDS
359 $ cat ../hg2.pid >> $DAEMON_PIDS
360 $ cd ..
360 $ cd ..
361
361
362 Clients talking same TLS versions work
362 Clients talking same TLS versions work
363
363
364 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
364 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
365 5fed3813f7f5
365 5fed3813f7f5
366 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
366 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
367 5fed3813f7f5
367 5fed3813f7f5
368 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
368 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
369 5fed3813f7f5
369 5fed3813f7f5
370
370
371 Clients requiring newer TLS version than what server supports fail
371 Clients requiring newer TLS version than what server supports fail
372
372
373 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
373 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
374 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
374 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
375 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
375 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
376 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
376 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
377 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
377 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
378 [100]
378 [100]
379
379
380 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
380 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
381 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
381 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
382 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
382 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
383 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
383 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
384 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
384 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
385 [100]
385 [100]
386 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
386 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
387 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
387 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
388 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
388 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
389 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
389 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
390 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
390 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
391 [100]
391 [100]
392 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
392 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
393 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
393 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
394 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
394 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
395 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
395 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
396 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
396 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
397 [100]
397 [100]
398
398
399 --insecure will allow TLS 1.0 connections and override configs
399 --insecure will allow TLS 1.0 connections and override configs
400
400
401 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
401 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
402 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
402 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
403 5fed3813f7f5
403 5fed3813f7f5
404
404
405 The per-host config option overrides the default
405 The per-host config option overrides the default
406
406
407 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
407 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
408 > --config hostsecurity.minimumprotocol=tls1.2 \
408 > --config hostsecurity.minimumprotocol=tls1.2 \
409 > --config hostsecurity.localhost:minimumprotocol=tls1.0
409 > --config hostsecurity.localhost:minimumprotocol=tls1.0
410 5fed3813f7f5
410 5fed3813f7f5
411
411
412 The per-host config option by itself works
412 The per-host config option by itself works
413
413
414 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
414 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
415 > --config hostsecurity.localhost:minimumprotocol=tls1.2
415 > --config hostsecurity.localhost:minimumprotocol=tls1.2
416 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
416 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
417 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
417 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
418 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
418 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
419 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
419 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
420 [100]
420 [100]
421
421
422 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
422 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
423
423
424 $ cat >> copy-pull/.hg/hgrc << EOF
424 $ cat >> copy-pull/.hg/hgrc << EOF
425 > [hostsecurity]
425 > [hostsecurity]
426 > localhost:minimumprotocol=tls1.2
426 > localhost:minimumprotocol=tls1.2
427 > EOF
427 > EOF
428 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
428 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
429 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
429 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
430 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
430 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
431 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
431 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
432 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
432 abort: error: .*(unsupported protocol|wrong ssl version).* (re)
433 [100]
433 [100]
434
434
435 $ killdaemons.py hg0.pid
435 $ killdaemons.py hg0.pid
436 $ killdaemons.py hg1.pid
436 $ killdaemons.py hg1.pid
437 $ killdaemons.py hg2.pid
437 $ killdaemons.py hg2.pid
438 #endif
438 #endif
439
439
440 Prepare for connecting through proxy
440 Prepare for connecting through proxy
441
441
442 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
442 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
443 $ cat hg0.pid >> $DAEMON_PIDS
443 $ cat hg0.pid >> $DAEMON_PIDS
444 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
444 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
445 $ cat hg2.pid >> $DAEMON_PIDS
445 $ cat hg2.pid >> $DAEMON_PIDS
446 tinyproxy.py doesn't fully detach, so killing it may result in extra output
446 tinyproxy.py doesn't fully detach, so killing it may result in extra output
447 from the shell. So don't kill it.
447 from the shell. So don't kill it.
448 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
448 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
449 $ while [ ! -f proxy.pid ]; do sleep 0; done
449 $ while [ ! -f proxy.pid ]; do sleep 0; done
450 $ cat proxy.pid >> $DAEMON_PIDS
450 $ cat proxy.pid >> $DAEMON_PIDS
451
451
452 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
452 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
453 $ echo "always=True" >> copy-pull/.hg/hgrc
453 $ echo "always=True" >> copy-pull/.hg/hgrc
454 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
454 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
455 $ echo "localhost =" >> copy-pull/.hg/hgrc
455 $ echo "localhost =" >> copy-pull/.hg/hgrc
456
456
457 Test unvalidated https through proxy
457 Test unvalidated https through proxy
458
458
459 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
459 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
460 pulling from https://localhost:$HGPORT/
460 pulling from https://localhost:$HGPORT/
461 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
461 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
462 searching for changes
462 searching for changes
463 no changes found
463 no changes found
464
464
465 Test https with cacert and fingerprint through proxy
465 Test https with cacert and fingerprint through proxy
466
466
467 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
467 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
468 > --config web.cacerts="$CERTSDIR/pub.pem"
468 > --config web.cacerts="$CERTSDIR/pub.pem"
469 pulling from https://localhost:$HGPORT/
469 pulling from https://localhost:$HGPORT/
470 searching for changes
470 searching for changes
471 no changes found
471 no changes found
472 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
472 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
473 pulling from https://*:$HGPORT/ (glob)
473 pulling from https://*:$HGPORT/ (glob)
474 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
474 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
475 searching for changes
475 searching for changes
476 no changes found
476 no changes found
477
477
478 Test https with cert problems through proxy
478 Test https with cert problems through proxy
479
479
480 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
480 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
481 > --config web.cacerts="$CERTSDIR/pub-other.pem"
481 > --config web.cacerts="$CERTSDIR/pub-other.pem"
482 pulling from https://localhost:$HGPORT/
482 pulling from https://localhost:$HGPORT/
483 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
483 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
484 abort: error: *certificate verify failed* (glob)
484 abort: error: *certificate verify failed* (glob)
485 [100]
485 [100]
486 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
486 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
487 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
487 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
488 pulling from https://localhost:$HGPORT2/
488 pulling from https://localhost:$HGPORT2/
489 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
489 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
490 abort: error: *certificate verify failed* (glob)
490 abort: error: *certificate verify failed* (glob)
491 [100]
491 [100]
492
492
493 Test when proxy can't connect to server
494
495 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure https://localhost:0/
496 pulling from https://localhost:0/
497 abort: error: Tunnel connection failed: 404 Connection refused
498 [100]
499
493
500
494 $ killdaemons.py hg0.pid
501 $ killdaemons.py hg0.pid
495
502
496 $ cd test
503 $ cd test
497
504
498 Missing certificate file(s) are detected
505 Missing certificate file(s) are detected
499
506
500 $ hg serve -p $HGPORT --certificate=/missing/certificate \
507 $ hg serve -p $HGPORT --certificate=/missing/certificate \
501 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
508 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
502 abort: referenced certificate file (*/missing/certificate) does not exist (glob)
509 abort: referenced certificate file (*/missing/certificate) does not exist (glob)
503 [255]
510 [255]
504
511
505 $ hg serve -p $HGPORT --certificate=$PRIV \
512 $ hg serve -p $HGPORT --certificate=$PRIV \
506 > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
513 > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
507 abort: referenced certificate file (*/missing/cafile) does not exist (glob)
514 abort: referenced certificate file (*/missing/cafile) does not exist (glob)
508 [255]
515 [255]
509
516
510 Start hgweb that requires client certificates:
517 Start hgweb that requires client certificates:
511
518
512 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
519 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
513 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
520 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
514 $ cat ../hg0.pid >> $DAEMON_PIDS
521 $ cat ../hg0.pid >> $DAEMON_PIDS
515 $ cd ..
522 $ cd ..
516
523
517 without client certificate:
524 without client certificate:
518
525
519 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
526 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
520 abort: error: .*(\$ECONNRESET\$|certificate required|handshake failure).* (re)
527 abort: error: .*(\$ECONNRESET\$|certificate required|handshake failure).* (re)
521 [100]
528 [100]
522
529
523 with client certificate:
530 with client certificate:
524
531
525 $ cat << EOT >> $HGRCPATH
532 $ cat << EOT >> $HGRCPATH
526 > [auth]
533 > [auth]
527 > l.prefix = localhost
534 > l.prefix = localhost
528 > l.cert = $CERTSDIR/client-cert.pem
535 > l.cert = $CERTSDIR/client-cert.pem
529 > l.key = $CERTSDIR/client-key.pem
536 > l.key = $CERTSDIR/client-key.pem
530 > EOT
537 > EOT
531
538
532 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
539 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
533 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
540 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
534 5fed3813f7f5
541 5fed3813f7f5
535
542
536 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
543 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
537 > --config ui.interactive=True --config ui.nontty=True
544 > --config ui.interactive=True --config ui.nontty=True
538 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
545 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
539
546
540 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
547 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
541 abort: error: * (glob)
548 abort: error: * (glob)
542 [100]
549 [100]
543
550
544 Missing certficate and key files result in error
551 Missing certficate and key files result in error
545
552
546 $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
553 $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
547 abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
554 abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
548 (restore missing file or fix references in Mercurial config)
555 (restore missing file or fix references in Mercurial config)
549 [255]
556 [255]
550
557
551 $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
558 $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
552 abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
559 abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
553 (restore missing file or fix references in Mercurial config)
560 (restore missing file or fix references in Mercurial config)
554 [255]
561 [255]
General Comments 0
You need to be logged in to leave comments. Login now