##// END OF EJS Templates
hgweb: use sslutil.wrapserversocket()...
Gregory Szorc -
r29555:121d1181 default
parent child Browse files
Show More
@@ -1,326 +1,334
1 1 # hgweb/server.py - The standalone hg web server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import BaseHTTPServer
12 12 import errno
13 13 import os
14 14 import socket
15 15 import sys
16 16 import traceback
17 17
18 18 from ..i18n import _
19 19
20 20 from .. import (
21 21 error,
22 22 util,
23 23 )
24 24
25 25 socketserver = util.socketserver
26 26 urlerr = util.urlerr
27 27 urlreq = util.urlreq
28 28
29 29 from . import (
30 30 common,
31 31 )
32 32
33 33 def _splitURI(uri):
34 34 """Return path and query that has been split from uri
35 35
36 36 Just like CGI environment, the path is unquoted, the query is
37 37 not.
38 38 """
39 39 if '?' in uri:
40 40 path, query = uri.split('?', 1)
41 41 else:
42 42 path, query = uri, ''
43 43 return urlreq.unquote(path), query
44 44
45 45 class _error_logger(object):
46 46 def __init__(self, handler):
47 47 self.handler = handler
48 48 def flush(self):
49 49 pass
50 50 def write(self, str):
51 51 self.writelines(str.split('\n'))
52 52 def writelines(self, seq):
53 53 for msg in seq:
54 54 self.handler.log_error("HG error: %s", msg)
55 55
56 56 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
57 57
58 58 url_scheme = 'http'
59 59
60 60 @staticmethod
61 61 def preparehttpserver(httpserver, ui):
62 62 """Prepare .socket of new HTTPServer instance"""
63 63 pass
64 64
65 65 def __init__(self, *args, **kargs):
66 66 self.protocol_version = 'HTTP/1.1'
67 67 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
68 68
69 69 def _log_any(self, fp, format, *args):
70 70 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
71 71 self.log_date_time_string(),
72 72 format % args))
73 73 fp.flush()
74 74
75 75 def log_error(self, format, *args):
76 76 self._log_any(self.server.errorlog, format, *args)
77 77
78 78 def log_message(self, format, *args):
79 79 self._log_any(self.server.accesslog, format, *args)
80 80
81 81 def log_request(self, code='-', size='-'):
82 82 xheaders = []
83 83 if util.safehasattr(self, 'headers'):
84 84 xheaders = [h for h in self.headers.items()
85 85 if h[0].startswith('x-')]
86 86 self.log_message('"%s" %s %s%s',
87 87 self.requestline, str(code), str(size),
88 88 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
89 89
90 90 def do_write(self):
91 91 try:
92 92 self.do_hgweb()
93 93 except socket.error as inst:
94 94 if inst[0] != errno.EPIPE:
95 95 raise
96 96
97 97 def do_POST(self):
98 98 try:
99 99 self.do_write()
100 100 except Exception:
101 101 self._start_response("500 Internal Server Error", [])
102 102 self._write("Internal Server Error")
103 103 self._done()
104 104 tb = "".join(traceback.format_exception(*sys.exc_info()))
105 105 self.log_error("Exception happened during processing "
106 106 "request '%s':\n%s", self.path, tb)
107 107
108 108 def do_GET(self):
109 109 self.do_POST()
110 110
111 111 def do_hgweb(self):
112 112 path, query = _splitURI(self.path)
113 113
114 114 env = {}
115 115 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
116 116 env['REQUEST_METHOD'] = self.command
117 117 env['SERVER_NAME'] = self.server.server_name
118 118 env['SERVER_PORT'] = str(self.server.server_port)
119 119 env['REQUEST_URI'] = self.path
120 120 env['SCRIPT_NAME'] = self.server.prefix
121 121 env['PATH_INFO'] = path[len(self.server.prefix):]
122 122 env['REMOTE_HOST'] = self.client_address[0]
123 123 env['REMOTE_ADDR'] = self.client_address[0]
124 124 if query:
125 125 env['QUERY_STRING'] = query
126 126
127 127 if self.headers.typeheader is None:
128 128 env['CONTENT_TYPE'] = self.headers.type
129 129 else:
130 130 env['CONTENT_TYPE'] = self.headers.typeheader
131 131 length = self.headers.getheader('content-length')
132 132 if length:
133 133 env['CONTENT_LENGTH'] = length
134 134 for header in [h for h in self.headers.keys()
135 135 if h not in ('content-type', 'content-length')]:
136 136 hkey = 'HTTP_' + header.replace('-', '_').upper()
137 137 hval = self.headers.getheader(header)
138 138 hval = hval.replace('\n', '').strip()
139 139 if hval:
140 140 env[hkey] = hval
141 141 env['SERVER_PROTOCOL'] = self.request_version
142 142 env['wsgi.version'] = (1, 0)
143 143 env['wsgi.url_scheme'] = self.url_scheme
144 144 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
145 145 self.rfile = common.continuereader(self.rfile, self.wfile.write)
146 146
147 147 env['wsgi.input'] = self.rfile
148 148 env['wsgi.errors'] = _error_logger(self)
149 149 env['wsgi.multithread'] = isinstance(self.server,
150 150 socketserver.ThreadingMixIn)
151 151 env['wsgi.multiprocess'] = isinstance(self.server,
152 152 socketserver.ForkingMixIn)
153 153 env['wsgi.run_once'] = 0
154 154
155 155 self.saved_status = None
156 156 self.saved_headers = []
157 157 self.sent_headers = False
158 158 self.length = None
159 159 self._chunked = None
160 160 for chunk in self.server.application(env, self._start_response):
161 161 self._write(chunk)
162 162 if not self.sent_headers:
163 163 self.send_headers()
164 164 self._done()
165 165
166 166 def send_headers(self):
167 167 if not self.saved_status:
168 168 raise AssertionError("Sending headers before "
169 169 "start_response() called")
170 170 saved_status = self.saved_status.split(None, 1)
171 171 saved_status[0] = int(saved_status[0])
172 172 self.send_response(*saved_status)
173 173 self.length = None
174 174 self._chunked = False
175 175 for h in self.saved_headers:
176 176 self.send_header(*h)
177 177 if h[0].lower() == 'content-length':
178 178 self.length = int(h[1])
179 179 if (self.length is None and
180 180 saved_status[0] != common.HTTP_NOT_MODIFIED):
181 181 self._chunked = (not self.close_connection and
182 182 self.request_version == "HTTP/1.1")
183 183 if self._chunked:
184 184 self.send_header('Transfer-Encoding', 'chunked')
185 185 else:
186 186 self.send_header('Connection', 'close')
187 187 self.end_headers()
188 188 self.sent_headers = True
189 189
190 190 def _start_response(self, http_status, headers, exc_info=None):
191 191 code, msg = http_status.split(None, 1)
192 192 code = int(code)
193 193 self.saved_status = http_status
194 194 bad_headers = ('connection', 'transfer-encoding')
195 195 self.saved_headers = [h for h in headers
196 196 if h[0].lower() not in bad_headers]
197 197 return self._write
198 198
199 199 def _write(self, data):
200 200 if not self.saved_status:
201 201 raise AssertionError("data written before start_response() called")
202 202 elif not self.sent_headers:
203 203 self.send_headers()
204 204 if self.length is not None:
205 205 if len(data) > self.length:
206 206 raise AssertionError("Content-length header sent, but more "
207 207 "bytes than specified are being written.")
208 208 self.length = self.length - len(data)
209 209 elif self._chunked and data:
210 210 data = '%x\r\n%s\r\n' % (len(data), data)
211 211 self.wfile.write(data)
212 212 self.wfile.flush()
213 213
214 214 def _done(self):
215 215 if self._chunked:
216 216 self.wfile.write('0\r\n\r\n')
217 217 self.wfile.flush()
218 218
219 219 class _httprequesthandlerssl(_httprequesthandler):
220 220 """HTTPS handler based on Python's ssl module"""
221 221
222 222 url_scheme = 'https'
223 223
224 224 @staticmethod
225 225 def preparehttpserver(httpserver, ui):
226 226 try:
227 import ssl
228 ssl.wrap_socket
227 from .. import sslutil
228 sslutil.modernssl
229 229 except ImportError:
230 230 raise error.Abort(_("SSL support is unavailable"))
231 231
232 232 certfile = ui.config('web', 'certificate')
233 httpserver.socket = ssl.wrap_socket(
234 httpserver.socket, server_side=True,
235 certfile=certfile, ssl_version=ssl.PROTOCOL_TLSv1)
233
234 # These config options are currently only meant for testing. Use
235 # at your own risk.
236 cafile = ui.config('devel', 'servercafile')
237 reqcert = ui.configbool('devel', 'serverrequirecert')
238
239 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
240 ui,
241 certfile=certfile,
242 cafile=cafile,
243 requireclientcert=reqcert)
236 244
237 245 def setup(self):
238 246 self.connection = self.request
239 247 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
240 248 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
241 249
242 250 try:
243 251 import threading
244 252 threading.activeCount() # silence pyflakes and bypass demandimport
245 253 _mixin = socketserver.ThreadingMixIn
246 254 except ImportError:
247 255 if util.safehasattr(os, "fork"):
248 256 _mixin = socketserver.ForkingMixIn
249 257 else:
250 258 class _mixin(object):
251 259 pass
252 260
253 261 def openlog(opt, default):
254 262 if opt and opt != '-':
255 263 return open(opt, 'a')
256 264 return default
257 265
258 266 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
259 267
260 268 # SO_REUSEADDR has broken semantics on windows
261 269 if os.name == 'nt':
262 270 allow_reuse_address = 0
263 271
264 272 def __init__(self, ui, app, addr, handler, **kwargs):
265 273 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
266 274 self.daemon_threads = True
267 275 self.application = app
268 276
269 277 handler.preparehttpserver(self, ui)
270 278
271 279 prefix = ui.config('web', 'prefix', '')
272 280 if prefix:
273 281 prefix = '/' + prefix.strip('/')
274 282 self.prefix = prefix
275 283
276 284 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
277 285 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
278 286 self.accesslog = alog
279 287 self.errorlog = elog
280 288
281 289 self.addr, self.port = self.socket.getsockname()[0:2]
282 290 self.fqaddr = socket.getfqdn(addr[0])
283 291
284 292 class IPv6HTTPServer(MercurialHTTPServer):
285 293 address_family = getattr(socket, 'AF_INET6', None)
286 294 def __init__(self, *args, **kwargs):
287 295 if self.address_family is None:
288 296 raise error.RepoError(_('IPv6 is not available on this system'))
289 297 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
290 298
291 299 def create_server(ui, app):
292 300
293 301 if ui.config('web', 'certificate'):
294 302 handler = _httprequesthandlerssl
295 303 else:
296 304 handler = _httprequesthandler
297 305
298 306 if ui.configbool('web', 'ipv6'):
299 307 cls = IPv6HTTPServer
300 308 else:
301 309 cls = MercurialHTTPServer
302 310
303 311 # ugly hack due to python issue5853 (for threaded use)
304 312 try:
305 313 import mimetypes
306 314 mimetypes.init()
307 315 except UnicodeDecodeError:
308 316 # Python 2.x's mimetypes module attempts to decode strings
309 317 # from Windows' ANSI APIs as ascii (fail), then re-encode them
310 318 # as ascii (clown fail), because the default Python Unicode
311 319 # codec is hardcoded as ascii.
312 320
313 321 sys.argv # unwrap demand-loader so that reload() works
314 322 reload(sys) # resurrect sys.setdefaultencoding()
315 323 oldenc = sys.getdefaultencoding()
316 324 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
317 325 mimetypes.init()
318 326 sys.setdefaultencoding(oldenc)
319 327
320 328 address = ui.config('web', 'address', '')
321 329 port = util.getport(ui.config('web', 'port', 8000))
322 330 try:
323 331 return cls(ui, app, (address, port), handler)
324 332 except socket.error as inst:
325 333 raise error.Abort(_("cannot start server at '%s:%d': %s")
326 334 % (address, port, inst.args[1]))
@@ -1,451 +1,435
1 1 #require serve ssl
2 2
3 3 Proper https client requires the built-in ssl from Python 2.6.
4 4
5 5 Make server certificates:
6 6
7 7 $ CERTSDIR="$TESTDIR/sslcerts"
8 8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
9 9 $ PRIV=`pwd`/server.pem
10 10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
11 11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
12 12
13 13 $ hg init test
14 14 $ cd test
15 15 $ echo foo>foo
16 16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
17 17 $ echo foo>foo.d/foo
18 18 $ echo bar>foo.d/bAr.hg.d/BaR
19 19 $ echo bar>foo.d/baR.d.hg/bAR
20 20 $ hg commit -A -m 1
21 21 adding foo
22 22 adding foo.d/bAr.hg.d/BaR
23 23 adding foo.d/baR.d.hg/bAR
24 24 adding foo.d/foo
25 25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
26 26 $ cat ../hg0.pid >> $DAEMON_PIDS
27 27
28 28 cacert not found
29 29
30 30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
31 31 abort: could not find web.cacerts: no-such.pem
32 32 [255]
33 33
34 34 Test server address cannot be reused
35 35
36 36 #if windows
37 37 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 38 abort: cannot start server at ':$HGPORT':
39 39 [255]
40 40 #else
41 41 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 42 abort: cannot start server at ':$HGPORT': Address already in use
43 43 [255]
44 44 #endif
45 45 $ cd ..
46 46
47 47 Our test cert is not signed by a trusted CA. It should fail to verify if
48 48 we are able to load CA certs.
49 49
50 50 #if sslcontext defaultcacerts no-defaultcacertsloaded
51 51 $ hg clone https://localhost:$HGPORT/ copy-pull
52 52 (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 53 abort: error: *certificate verify failed* (glob)
54 54 [255]
55 55 #endif
56 56
57 57 #if no-sslcontext defaultcacerts
58 58 $ hg clone https://localhost:$HGPORT/ copy-pull
59 59 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
60 60 abort: error: *certificate verify failed* (glob)
61 61 [255]
62 62 #endif
63 63
64 64 #if no-sslcontext windows
65 65 $ hg clone https://localhost:$HGPORT/ copy-pull
66 66 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
67 67 abort: error: *certificate verify failed* (glob)
68 68 [255]
69 69 #endif
70 70
71 71 #if no-sslcontext osx
72 72 $ hg clone https://localhost:$HGPORT/ copy-pull
73 73 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
74 74 abort: localhost certificate error: no certificate received
75 75 (set hostsecurity.localhost: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)
76 76 [255]
77 77 #endif
78 78
79 79 #if defaultcacertsloaded
80 80 $ hg clone https://localhost:$HGPORT/ copy-pull
81 81 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
82 82 abort: error: *certificate verify failed* (glob)
83 83 [255]
84 84 #endif
85 85
86 86 #if no-defaultcacerts
87 87 $ hg clone https://localhost:$HGPORT/ copy-pull
88 88 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
89 89 abort: localhost certificate error: no certificate received
90 90 (set hostsecurity.localhost: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)
91 91 [255]
92 92 #endif
93 93
94 94 Specifying a per-host certificate file that doesn't exist will abort
95 95
96 96 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
97 97 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
98 98 [255]
99 99
100 100 A malformed per-host certificate file will raise an error
101 101
102 102 $ echo baddata > badca.pem
103 103 #if sslcontext
104 104 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
105 105 abort: error loading CA file badca.pem: * (glob)
106 106 (file is empty or malformed?)
107 107 [255]
108 108 #else
109 109 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
110 110 abort: error: * (glob)
111 111 [255]
112 112 #endif
113 113
114 114 A per-host certificate mismatching the server will fail verification
115 115
116 116 (modern ssl is able to discern whether the loaded cert is a CA cert)
117 117 #if sslcontext
118 118 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
119 119 (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)
120 120 abort: error: *certificate verify failed* (glob)
121 121 [255]
122 122 #else
123 123 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
124 124 abort: error: *certificate verify failed* (glob)
125 125 [255]
126 126 #endif
127 127
128 128 A per-host certificate matching the server's cert will be accepted
129 129
130 130 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
131 131 requesting all changes
132 132 adding changesets
133 133 adding manifests
134 134 adding file changes
135 135 added 1 changesets with 4 changes to 4 files
136 136
137 137 A per-host certificate with multiple certs and one matching will be accepted
138 138
139 139 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
140 140 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
141 141 requesting all changes
142 142 adding changesets
143 143 adding manifests
144 144 adding file changes
145 145 added 1 changesets with 4 changes to 4 files
146 146
147 147 Defining both per-host certificate and a fingerprint will print a warning
148 148
149 149 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
150 150 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
151 151 requesting all changes
152 152 adding changesets
153 153 adding manifests
154 154 adding file changes
155 155 added 1 changesets with 4 changes to 4 files
156 156
157 157 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
158 158
159 159 Inability to verify peer certificate will result in abort
160 160
161 161 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
162 162 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
163 163 (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)
164 164 [255]
165 165
166 166 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
167 167 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
168 168 requesting all changes
169 169 adding changesets
170 170 adding manifests
171 171 adding file changes
172 172 added 1 changesets with 4 changes to 4 files
173 173 updating to branch default
174 174 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 175 $ hg verify -R copy-pull
176 176 checking changesets
177 177 checking manifests
178 178 crosschecking files in changesets and manifests
179 179 checking files
180 180 4 files, 1 changesets, 4 total revisions
181 181 $ cd test
182 182 $ echo bar > bar
183 183 $ hg commit -A -d '1 0' -m 2
184 184 adding bar
185 185 $ cd ..
186 186
187 187 pull without cacert
188 188
189 189 $ cd copy-pull
190 190 $ echo '[hooks]' >> .hg/hgrc
191 191 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
192 192 $ hg pull $DISABLECACERTS
193 193 pulling from https://localhost:$HGPORT/
194 194 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
195 195 (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)
196 196 [255]
197 197
198 198 $ hg pull --insecure
199 199 pulling from https://localhost:$HGPORT/
200 200 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
201 201 searching for changes
202 202 adding changesets
203 203 adding manifests
204 204 adding file changes
205 205 added 1 changesets with 1 changes to 1 files
206 206 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=https://localhost:$HGPORT/ (glob)
207 207 (run 'hg update' to get a working copy)
208 208 $ cd ..
209 209
210 210 cacert configured in local repo
211 211
212 212 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
213 213 $ echo "[web]" >> copy-pull/.hg/hgrc
214 214 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
215 215 $ hg -R copy-pull pull --traceback
216 216 pulling from https://localhost:$HGPORT/
217 217 searching for changes
218 218 no changes found
219 219 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
220 220
221 221 cacert configured globally, also testing expansion of environment
222 222 variables in the filename
223 223
224 224 $ echo "[web]" >> $HGRCPATH
225 225 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
226 226 $ P="$CERTSDIR" hg -R copy-pull pull
227 227 pulling from https://localhost:$HGPORT/
228 228 searching for changes
229 229 no changes found
230 230 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
231 231 pulling from https://localhost:$HGPORT/
232 232 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
233 233 searching for changes
234 234 no changes found
235 235
236 236 empty cacert file
237 237
238 238 $ touch emptycafile
239 239
240 240 #if sslcontext
241 241 $ hg --config web.cacerts=emptycafile -R copy-pull pull
242 242 pulling from https://localhost:$HGPORT/
243 243 abort: error loading CA file emptycafile: * (glob)
244 244 (file is empty or malformed?)
245 245 [255]
246 246 #else
247 247 $ hg --config web.cacerts=emptycafile -R copy-pull pull
248 248 pulling from https://localhost:$HGPORT/
249 249 abort: error: * (glob)
250 250 [255]
251 251 #endif
252 252
253 253 cacert mismatch
254 254
255 255 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
256 256 > https://127.0.0.1:$HGPORT/
257 257 pulling from https://127.0.0.1:$HGPORT/ (glob)
258 258 abort: 127.0.0.1 certificate error: certificate is for localhost (glob)
259 259 (set hostsecurity.127.0.0.1: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) (glob)
260 260 [255]
261 261 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
262 262 > https://127.0.0.1:$HGPORT/ --insecure
263 263 pulling from https://127.0.0.1:$HGPORT/ (glob)
264 264 warning: connection security to 127.0.0.1 is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
265 265 searching for changes
266 266 no changes found
267 267 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
268 268 pulling from https://localhost:$HGPORT/
269 269 abort: error: *certificate verify failed* (glob)
270 270 [255]
271 271 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
272 272 > --insecure
273 273 pulling from https://localhost:$HGPORT/
274 274 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
275 275 searching for changes
276 276 no changes found
277 277
278 278 Test server cert which isn't valid yet
279 279
280 280 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
281 281 $ cat hg1.pid >> $DAEMON_PIDS
282 282 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
283 283 > https://localhost:$HGPORT1/
284 284 pulling from https://localhost:$HGPORT1/
285 285 abort: error: *certificate verify failed* (glob)
286 286 [255]
287 287
288 288 Test server cert which no longer is valid
289 289
290 290 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
291 291 $ cat hg2.pid >> $DAEMON_PIDS
292 292 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
293 293 > https://localhost:$HGPORT2/
294 294 pulling from https://localhost:$HGPORT2/
295 295 abort: error: *certificate verify failed* (glob)
296 296 [255]
297 297
298 298 Fingerprints
299 299
300 300 - works without cacerts (hostkeyfingerprints)
301 301 $ 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
302 302 5fed3813f7f5
303 303
304 304 - works without cacerts (hostsecurity)
305 305 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
306 306 5fed3813f7f5
307 307
308 308 $ 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
309 309 5fed3813f7f5
310 310
311 311 - multiple fingerprints specified and first matches
312 312 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
313 313 5fed3813f7f5
314 314
315 315 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
316 316 5fed3813f7f5
317 317
318 318 - multiple fingerprints specified and last matches
319 319 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
320 320 5fed3813f7f5
321 321
322 322 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
323 323 5fed3813f7f5
324 324
325 325 - multiple fingerprints specified and none match
326 326
327 327 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
328 328 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
329 329 (check hostfingerprint configuration)
330 330 [255]
331 331
332 332 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
333 333 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
334 334 (check hostsecurity configuration)
335 335 [255]
336 336
337 337 - fails when cert doesn't match hostname (port is ignored)
338 338 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
339 339 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
340 340 (check hostfingerprint configuration)
341 341 [255]
342 342
343 343
344 344 - ignores that certificate doesn't match hostname
345 345 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
346 346 5fed3813f7f5
347 347
348 348 HGPORT1 is reused below for tinyproxy tests. Kill that server.
349 349 $ killdaemons.py hg1.pid
350 350
351 351 Prepare for connecting through proxy
352 352
353 353 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
354 354 $ while [ ! -f proxy.pid ]; do sleep 0; done
355 355 $ cat proxy.pid >> $DAEMON_PIDS
356 356
357 357 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
358 358 $ echo "always=True" >> copy-pull/.hg/hgrc
359 359 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
360 360 $ echo "localhost =" >> copy-pull/.hg/hgrc
361 361
362 362 Test unvalidated https through proxy
363 363
364 364 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
365 365 pulling from https://localhost:$HGPORT/
366 366 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
367 367 searching for changes
368 368 no changes found
369 369
370 370 Test https with cacert and fingerprint through proxy
371 371
372 372 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
373 373 > --config web.cacerts="$CERTSDIR/pub.pem"
374 374 pulling from https://localhost:$HGPORT/
375 375 searching for changes
376 376 no changes found
377 377 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/ --config hostfingerprints.127.0.0.1=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
378 378 pulling from https://127.0.0.1:$HGPORT/ (glob)
379 379 searching for changes
380 380 no changes found
381 381
382 382 Test https with cert problems through proxy
383 383
384 384 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
385 385 > --config web.cacerts="$CERTSDIR/pub-other.pem"
386 386 pulling from https://localhost:$HGPORT/
387 387 abort: error: *certificate verify failed* (glob)
388 388 [255]
389 389 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
390 390 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
391 391 pulling from https://localhost:$HGPORT2/
392 392 abort: error: *certificate verify failed* (glob)
393 393 [255]
394 394
395 395
396 396 $ killdaemons.py hg0.pid
397 397
398 398 #if sslcontext
399 399
400 Start patched hgweb that requires client certificates:
400 Start hgweb that requires client certificates:
401 401
402 $ cat << EOT > reqclientcert.py
403 > import ssl
404 > from mercurial.hgweb import server
405 > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
406 > @staticmethod
407 > def preparehttpserver(httpserver, ui):
408 > certfile = ui.config('web', 'certificate')
409 > sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
410 > sslcontext.verify_mode = ssl.CERT_REQUIRED
411 > sslcontext.load_cert_chain(certfile)
412 > # verify clients by server certificate
413 > sslcontext.load_verify_locations(certfile)
414 > httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
415 > server_side=True)
416 > server._httprequesthandlerssl = _httprequesthandlersslclientcert
417 > EOT
418 402 $ cd test
419 403 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
420 > --config extensions.reqclientcert=../reqclientcert.py
404 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
421 405 $ cat ../hg0.pid >> $DAEMON_PIDS
422 406 $ cd ..
423 407
424 408 without client certificate:
425 409
426 410 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
427 411 abort: error: *handshake failure* (glob)
428 412 [255]
429 413
430 414 with client certificate:
431 415
432 416 $ cat << EOT >> $HGRCPATH
433 417 > [auth]
434 418 > l.prefix = localhost
435 419 > l.cert = $CERTSDIR/client-cert.pem
436 420 > l.key = $CERTSDIR/client-key.pem
437 421 > EOT
438 422
439 423 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
440 424 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
441 425 5fed3813f7f5
442 426
443 427 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
444 428 > --config ui.interactive=True --config ui.nontty=True
445 429 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
446 430
447 431 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
448 432 abort: error: * (glob)
449 433 [255]
450 434
451 435 #endif
General Comments 0
You need to be logged in to leave comments. Login now