##// END OF EJS Templates
sslutil: extracted ssl methods from httpsconnection in url.py...
Augie Fackler -
r14204:5fa21960 default
parent child Browse files
Show More
@@ -0,0 +1,126 b''
1 # sslutil.py - SSL handling for mercurial
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
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.
9 import os
10
11 from mercurial import util
12 from mercurial.i18n import _
13 try:
14 # avoid using deprecated/broken FakeSocket in python 2.6
15 import ssl
16 ssl_wrap_socket = ssl.wrap_socket
17 CERT_REQUIRED = ssl.CERT_REQUIRED
18 except ImportError:
19 CERT_REQUIRED = 2
20
21 def ssl_wrap_socket(sock, key_file, cert_file,
22 cert_reqs=CERT_REQUIRED, ca_certs=None):
23 if ca_certs:
24 raise util.Abort(_(
25 'certificate checking requires Python 2.6'))
26
27 ssl = socket.ssl(sock, key_file, cert_file)
28 return httplib.FakeSocket(sock, ssl)
29
30 def _verifycert(cert, hostname):
31 '''Verify that cert (in socket.getpeercert() format) matches hostname.
32 CRLs is not handled.
33
34 Returns error message if any problems are found and None on success.
35 '''
36 if not cert:
37 return _('no certificate received')
38 dnsname = hostname.lower()
39 def matchdnsname(certname):
40 return (certname == dnsname or
41 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
42
43 san = cert.get('subjectAltName', [])
44 if san:
45 certnames = [value.lower() for key, value in san if key == 'DNS']
46 for name in certnames:
47 if matchdnsname(name):
48 return None
49 return _('certificate is for %s') % ', '.join(certnames)
50
51 # subject is only checked when subjectAltName is empty
52 for s in cert.get('subject', []):
53 key, value = s[0]
54 if key == 'commonName':
55 try:
56 # 'subject' entries are unicode
57 certname = value.lower().encode('ascii')
58 except UnicodeEncodeError:
59 return _('IDN in certificate not supported')
60 if matchdnsname(certname):
61 return None
62 return _('certificate is for %s') % certname
63 return _('no commonName or subjectAltName found in certificate')
64
65
66 # CERT_REQUIRED means fetch the cert from the server all the time AND
67 # validate it against the CA store provided in web.cacerts.
68 #
69 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
70 # busted on those versions.
71
72 def sslkwargs(ui, host):
73 cacerts = ui.config('web', 'cacerts')
74 hostfingerprint = ui.config('hostfingerprints', host)
75 if cacerts and not hostfingerprint:
76 cacerts = util.expandpath(cacerts)
77 if not os.path.exists(cacerts):
78 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
79 return {'ca_certs': cacerts,
80 'cert_reqs': CERT_REQUIRED,
81 }
82 return {}
83
84 class validator(object):
85 def __init__(self, ui, host):
86 self.ui = ui
87 self.host = host
88
89 def __call__(self, sock):
90 host = self.host
91 cacerts = self.ui.config('web', 'cacerts')
92 hostfingerprint = self.ui.config('hostfingerprints', host)
93 if cacerts and not hostfingerprint:
94 msg = _verifycert(sock.getpeercert(), host)
95 if msg:
96 raise util.Abort(_('%s certificate error: %s '
97 '(use --insecure to connect '
98 'insecurely)') % (host, msg))
99 self.ui.debug('%s certificate successfully verified\n' % host)
100 else:
101 if getattr(sock, 'getpeercert', False):
102 peercert = sock.getpeercert(True)
103 peerfingerprint = util.sha1(peercert).hexdigest()
104 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
105 for x in xrange(0, len(peerfingerprint), 2)])
106 if hostfingerprint:
107 if peerfingerprint.lower() != \
108 hostfingerprint.replace(':', '').lower():
109 raise util.Abort(_('invalid certificate for %s '
110 'with fingerprint %s') %
111 (host, nicefingerprint))
112 self.ui.debug('%s certificate matched fingerprint %s\n' %
113 (host, nicefingerprint))
114 else:
115 self.ui.warn(_('warning: %s certificate '
116 'with fingerprint %s not verified '
117 '(check hostfingerprints or web.cacerts '
118 'config setting)\n') %
119 (host, nicefingerprint))
120 else: # python 2.5 ?
121 if hostfingerprint:
122 raise util.Abort(_('no certificate for %s with '
123 'configured hostfingerprint') % host)
124 self.ui.warn(_('warning: %s certificate not verified '
125 '(check web.cacerts config setting)\n') %
126 host)
@@ -1,625 +1,530 b''
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import urllib, urllib2, httplib, os, socket, cStringIO
11 11 import __builtin__
12 12 from i18n import _
13 import keepalive, util
13 import keepalive, util, sslutil
14 14
15 15 def readauthforuri(ui, uri):
16 16 # Read configuration
17 17 config = dict()
18 18 for key, val in ui.configitems('auth'):
19 19 if '.' not in key:
20 20 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
21 21 continue
22 22 group, setting = key.rsplit('.', 1)
23 23 gdict = config.setdefault(group, dict())
24 24 if setting in ('username', 'cert', 'key'):
25 25 val = util.expandpath(val)
26 26 gdict[setting] = val
27 27
28 28 # Find the best match
29 29 scheme, hostpath = uri.split('://', 1)
30 30 bestlen = 0
31 31 bestauth = None
32 32 for group, auth in config.iteritems():
33 33 prefix = auth.get('prefix')
34 34 if not prefix:
35 35 continue
36 36 p = prefix.split('://', 1)
37 37 if len(p) > 1:
38 38 schemes, prefix = [p[0]], p[1]
39 39 else:
40 40 schemes = (auth.get('schemes') or 'https').split()
41 41 if (prefix == '*' or hostpath.startswith(prefix)) and \
42 42 len(prefix) > bestlen and scheme in schemes:
43 43 bestlen = len(prefix)
44 44 bestauth = group, auth
45 45 return bestauth
46 46
47 47 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
48 48 def __init__(self, ui):
49 49 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
50 50 self.ui = ui
51 51
52 52 def find_user_password(self, realm, authuri):
53 53 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
54 54 self, realm, authuri)
55 55 user, passwd = authinfo
56 56 if user and passwd:
57 57 self._writedebug(user, passwd)
58 58 return (user, passwd)
59 59
60 60 if not user:
61 61 res = readauthforuri(self.ui, authuri)
62 62 if res:
63 63 group, auth = res
64 64 user, passwd = auth.get('username'), auth.get('password')
65 65 self.ui.debug("using auth.%s.* for authentication\n" % group)
66 66 if not user or not passwd:
67 67 if not self.ui.interactive():
68 68 raise util.Abort(_('http authorization required'))
69 69
70 70 self.ui.write(_("http authorization required\n"))
71 71 self.ui.write(_("realm: %s\n") % realm)
72 72 if user:
73 73 self.ui.write(_("user: %s\n") % user)
74 74 else:
75 75 user = self.ui.prompt(_("user:"), default=None)
76 76
77 77 if not passwd:
78 78 passwd = self.ui.getpass()
79 79
80 80 self.add_password(realm, authuri, user, passwd)
81 81 self._writedebug(user, passwd)
82 82 return (user, passwd)
83 83
84 84 def _writedebug(self, user, passwd):
85 85 msg = _('http auth: user %s, password %s\n')
86 86 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
87 87
88 88 class proxyhandler(urllib2.ProxyHandler):
89 89 def __init__(self, ui):
90 90 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
91 91 # XXX proxyauthinfo = None
92 92
93 93 if proxyurl:
94 94 # proxy can be proper url or host[:port]
95 95 if not (proxyurl.startswith('http:') or
96 96 proxyurl.startswith('https:')):
97 97 proxyurl = 'http://' + proxyurl + '/'
98 98 proxy = util.url(proxyurl)
99 99 if not proxy.user:
100 100 proxy.user = ui.config("http_proxy", "user")
101 101 proxy.passwd = ui.config("http_proxy", "passwd")
102 102
103 103 # see if we should use a proxy for this url
104 104 no_list = ["localhost", "127.0.0.1"]
105 105 no_list.extend([p.lower() for
106 106 p in ui.configlist("http_proxy", "no")])
107 107 no_list.extend([p.strip().lower() for
108 108 p in os.getenv("no_proxy", '').split(',')
109 109 if p.strip()])
110 110 # "http_proxy.always" config is for running tests on localhost
111 111 if ui.configbool("http_proxy", "always"):
112 112 self.no_list = []
113 113 else:
114 114 self.no_list = no_list
115 115
116 116 proxyurl = str(proxy)
117 117 proxies = {'http': proxyurl, 'https': proxyurl}
118 118 ui.debug('proxying through http://%s:%s\n' %
119 119 (proxy.host, proxy.port))
120 120 else:
121 121 proxies = {}
122 122
123 123 # urllib2 takes proxy values from the environment and those
124 124 # will take precedence if found, so drop them
125 125 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
126 126 try:
127 127 if env in os.environ:
128 128 del os.environ[env]
129 129 except OSError:
130 130 pass
131 131
132 132 urllib2.ProxyHandler.__init__(self, proxies)
133 133 self.ui = ui
134 134
135 135 def proxy_open(self, req, proxy, type_):
136 136 host = req.get_host().split(':')[0]
137 137 if host in self.no_list:
138 138 return None
139 139
140 140 # work around a bug in Python < 2.4.2
141 141 # (it leaves a "\n" at the end of Proxy-authorization headers)
142 142 baseclass = req.__class__
143 143 class _request(baseclass):
144 144 def add_header(self, key, val):
145 145 if key.lower() == 'proxy-authorization':
146 146 val = val.strip()
147 147 return baseclass.add_header(self, key, val)
148 148 req.__class__ = _request
149 149
150 150 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
151 151
152 152 class httpsendfile(object):
153 153 """This is a wrapper around the objects returned by python's "open".
154 154
155 155 Its purpose is to send file-like objects via HTTP and, to do so, it
156 156 defines a __len__ attribute to feed the Content-Length header.
157 157 """
158 158
159 159 def __init__(self, ui, *args, **kwargs):
160 160 # We can't just "self._data = open(*args, **kwargs)" here because there
161 161 # is an "open" function defined in this module that shadows the global
162 162 # one
163 163 self.ui = ui
164 164 self._data = __builtin__.open(*args, **kwargs)
165 165 self.seek = self._data.seek
166 166 self.close = self._data.close
167 167 self.write = self._data.write
168 168 self._len = os.fstat(self._data.fileno()).st_size
169 169 self._pos = 0
170 170 self._total = len(self) / 1024 * 2
171 171
172 172 def read(self, *args, **kwargs):
173 173 try:
174 174 ret = self._data.read(*args, **kwargs)
175 175 except EOFError:
176 176 self.ui.progress(_('sending'), None)
177 177 self._pos += len(ret)
178 178 # We pass double the max for total because we currently have
179 179 # to send the bundle twice in the case of a server that
180 180 # requires authentication. Since we can't know until we try
181 181 # once whether authentication will be required, just lie to
182 182 # the user and maybe the push succeeds suddenly at 50%.
183 183 self.ui.progress(_('sending'), self._pos / 1024,
184 184 unit=_('kb'), total=self._total)
185 185 return ret
186 186
187 187 def __len__(self):
188 188 return self._len
189 189
190 190 def _gen_sendfile(orgsend):
191 191 def _sendfile(self, data):
192 192 # send a file
193 193 if isinstance(data, httpsendfile):
194 194 # if auth required, some data sent twice, so rewind here
195 195 data.seek(0)
196 196 for chunk in util.filechunkiter(data):
197 197 orgsend(self, chunk)
198 198 else:
199 199 orgsend(self, data)
200 200 return _sendfile
201 201
202 202 has_https = hasattr(urllib2, 'HTTPSHandler')
203 203 if has_https:
204 204 try:
205 # avoid using deprecated/broken FakeSocket in python 2.6
206 import ssl
207 _ssl_wrap_socket = ssl.wrap_socket
208 CERT_REQUIRED = ssl.CERT_REQUIRED
209 except ImportError:
210 CERT_REQUIRED = 2
211
212 def _ssl_wrap_socket(sock, key_file, cert_file,
213 cert_reqs=CERT_REQUIRED, ca_certs=None):
214 if ca_certs:
215 raise util.Abort(_(
216 'certificate checking requires Python 2.6'))
217
218 ssl = socket.ssl(sock, key_file, cert_file)
219 return httplib.FakeSocket(sock, ssl)
220
221 try:
222 205 _create_connection = socket.create_connection
223 206 except AttributeError:
224 207 _GLOBAL_DEFAULT_TIMEOUT = object()
225 208
226 209 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
227 210 source_address=None):
228 211 # lifted from Python 2.6
229 212
230 213 msg = "getaddrinfo returns an empty list"
231 214 host, port = address
232 215 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
233 216 af, socktype, proto, canonname, sa = res
234 217 sock = None
235 218 try:
236 219 sock = socket.socket(af, socktype, proto)
237 220 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
238 221 sock.settimeout(timeout)
239 222 if source_address:
240 223 sock.bind(source_address)
241 224 sock.connect(sa)
242 225 return sock
243 226
244 227 except socket.error, msg:
245 228 if sock is not None:
246 229 sock.close()
247 230
248 231 raise socket.error, msg
249 232
250 233 class httpconnection(keepalive.HTTPConnection):
251 234 # must be able to send big bundle as stream.
252 235 send = _gen_sendfile(keepalive.HTTPConnection.send)
253 236
254 237 def connect(self):
255 238 if has_https and self.realhostport: # use CONNECT proxy
256 239 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
257 240 self.sock.connect((self.host, self.port))
258 241 if _generic_proxytunnel(self):
259 242 # we do not support client x509 certificates
260 self.sock = _ssl_wrap_socket(self.sock, None, None)
243 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
261 244 else:
262 245 keepalive.HTTPConnection.connect(self)
263 246
264 247 def getresponse(self):
265 248 proxyres = getattr(self, 'proxyres', None)
266 249 if proxyres:
267 250 if proxyres.will_close:
268 251 self.close()
269 252 self.proxyres = None
270 253 return proxyres
271 254 return keepalive.HTTPConnection.getresponse(self)
272 255
273 256 # general transaction handler to support different ways to handle
274 257 # HTTPS proxying before and after Python 2.6.3.
275 258 def _generic_start_transaction(handler, h, req):
276 259 if hasattr(req, '_tunnel_host') and req._tunnel_host:
277 260 tunnel_host = req._tunnel_host
278 261 if tunnel_host[:7] not in ['http://', 'https:/']:
279 262 tunnel_host = 'https://' + tunnel_host
280 263 new_tunnel = True
281 264 else:
282 265 tunnel_host = req.get_selector()
283 266 new_tunnel = False
284 267
285 268 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
286 269 u = util.url(tunnel_host)
287 270 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
288 271 h.realhostport = ':'.join([u.host, (u.port or '443')])
289 272 h.headers = req.headers.copy()
290 273 h.headers.update(handler.parent.addheaders)
291 274 return
292 275
293 276 h.realhostport = None
294 277 h.headers = None
295 278
296 279 def _generic_proxytunnel(self):
297 280 proxyheaders = dict(
298 281 [(x, self.headers[x]) for x in self.headers
299 282 if x.lower().startswith('proxy-')])
300 283 self._set_hostport(self.host, self.port)
301 284 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
302 285 for header in proxyheaders.iteritems():
303 286 self.send('%s: %s\r\n' % header)
304 287 self.send('\r\n')
305 288
306 289 # majority of the following code is duplicated from
307 290 # httplib.HTTPConnection as there are no adequate places to
308 291 # override functions to provide the needed functionality
309 292 res = self.response_class(self.sock,
310 293 strict=self.strict,
311 294 method=self._method)
312 295
313 296 while True:
314 297 version, status, reason = res._read_status()
315 298 if status != httplib.CONTINUE:
316 299 break
317 300 while True:
318 301 skip = res.fp.readline().strip()
319 302 if not skip:
320 303 break
321 304 res.status = status
322 305 res.reason = reason.strip()
323 306
324 307 if res.status == 200:
325 308 while True:
326 309 line = res.fp.readline()
327 310 if line == '\r\n':
328 311 break
329 312 return True
330 313
331 314 if version == 'HTTP/1.0':
332 315 res.version = 10
333 316 elif version.startswith('HTTP/1.'):
334 317 res.version = 11
335 318 elif version == 'HTTP/0.9':
336 319 res.version = 9
337 320 else:
338 321 raise httplib.UnknownProtocol(version)
339 322
340 323 if res.version == 9:
341 324 res.length = None
342 325 res.chunked = 0
343 326 res.will_close = 1
344 327 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
345 328 return False
346 329
347 330 res.msg = httplib.HTTPMessage(res.fp)
348 331 res.msg.fp = None
349 332
350 333 # are we using the chunked-style of transfer encoding?
351 334 trenc = res.msg.getheader('transfer-encoding')
352 335 if trenc and trenc.lower() == "chunked":
353 336 res.chunked = 1
354 337 res.chunk_left = None
355 338 else:
356 339 res.chunked = 0
357 340
358 341 # will the connection close at the end of the response?
359 342 res.will_close = res._check_close()
360 343
361 344 # do we have a Content-Length?
362 345 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
363 346 length = res.msg.getheader('content-length')
364 347 if length and not res.chunked:
365 348 try:
366 349 res.length = int(length)
367 350 except ValueError:
368 351 res.length = None
369 352 else:
370 353 if res.length < 0: # ignore nonsensical negative lengths
371 354 res.length = None
372 355 else:
373 356 res.length = None
374 357
375 358 # does the body have a fixed length? (of zero)
376 359 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
377 360 100 <= status < 200 or # 1xx codes
378 361 res._method == 'HEAD'):
379 362 res.length = 0
380 363
381 364 # if the connection remains open, and we aren't using chunked, and
382 365 # a content-length was not provided, then assume that the connection
383 366 # WILL close.
384 367 if (not res.will_close and
385 368 not res.chunked and
386 369 res.length is None):
387 370 res.will_close = 1
388 371
389 372 self.proxyres = res
390 373
391 374 return False
392 375
393 376 class httphandler(keepalive.HTTPHandler):
394 377 def http_open(self, req):
395 378 return self.do_open(httpconnection, req)
396 379
397 380 def _start_transaction(self, h, req):
398 381 _generic_start_transaction(self, h, req)
399 382 return keepalive.HTTPHandler._start_transaction(self, h, req)
400 383
401 def _verifycert(cert, hostname):
402 '''Verify that cert (in socket.getpeercert() format) matches hostname.
403 CRLs is not handled.
404
405 Returns error message if any problems are found and None on success.
406 '''
407 if not cert:
408 return _('no certificate received')
409 dnsname = hostname.lower()
410 def matchdnsname(certname):
411 return (certname == dnsname or
412 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
413
414 san = cert.get('subjectAltName', [])
415 if san:
416 certnames = [value.lower() for key, value in san if key == 'DNS']
417 for name in certnames:
418 if matchdnsname(name):
419 return None
420 return _('certificate is for %s') % ', '.join(certnames)
421
422 # subject is only checked when subjectAltName is empty
423 for s in cert.get('subject', []):
424 key, value = s[0]
425 if key == 'commonName':
426 try:
427 # 'subject' entries are unicode
428 certname = value.lower().encode('ascii')
429 except UnicodeEncodeError:
430 return _('IDN in certificate not supported')
431 if matchdnsname(certname):
432 return None
433 return _('certificate is for %s') % certname
434 return _('no commonName or subjectAltName found in certificate')
435
436 384 if has_https:
437 385 class httpsconnection(httplib.HTTPSConnection):
438 386 response_class = keepalive.HTTPResponse
439 387 # must be able to send big bundle as stream.
440 388 send = _gen_sendfile(keepalive.safesend)
441 389 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
442 390
443 391 def connect(self):
444 392 self.sock = _create_connection((self.host, self.port))
445 393
446 394 host = self.host
447 395 if self.realhostport: # use CONNECT proxy
448 396 _generic_proxytunnel(self)
449 397 host = self.realhostport.rsplit(':', 1)[0]
450
451 cacerts = self.ui.config('web', 'cacerts')
452 hostfingerprint = self.ui.config('hostfingerprints', host)
453
454 if cacerts and not hostfingerprint:
455 cacerts = util.expandpath(cacerts)
456 if not os.path.exists(cacerts):
457 raise util.Abort(_('could not find '
458 'web.cacerts: %s') % cacerts)
459 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
460 self.cert_file, cert_reqs=CERT_REQUIRED,
461 ca_certs=cacerts)
462 msg = _verifycert(self.sock.getpeercert(), host)
463 if msg:
464 raise util.Abort(_('%s certificate error: %s '
465 '(use --insecure to connect '
466 'insecurely)') % (host, msg))
467 self.ui.debug('%s certificate successfully verified\n' % host)
468 else:
469 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
470 self.cert_file)
471 if hasattr(self.sock, 'getpeercert'):
472 peercert = self.sock.getpeercert(True)
473 peerfingerprint = util.sha1(peercert).hexdigest()
474 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
475 for x in xrange(0, len(peerfingerprint), 2)])
476 if hostfingerprint:
477 if peerfingerprint.lower() != \
478 hostfingerprint.replace(':', '').lower():
479 raise util.Abort(_('invalid certificate for %s '
480 'with fingerprint %s') %
481 (host, nicefingerprint))
482 self.ui.debug('%s certificate matched fingerprint %s\n' %
483 (host, nicefingerprint))
484 else:
485 self.ui.warn(_('warning: %s certificate '
486 'with fingerprint %s not verified '
487 '(check hostfingerprints or web.cacerts '
488 'config setting)\n') %
489 (host, nicefingerprint))
490 else: # python 2.5 ?
491 if hostfingerprint:
492 raise util.Abort(_('no certificate for %s with '
493 'configured hostfingerprint') % host)
494 self.ui.warn(_('warning: %s certificate not verified '
495 '(check web.cacerts config setting)\n') %
496 host)
398 self.sock = sslutil.ssl_wrap_socket(
399 self.sock, self.key_file, self.cert_file,
400 **sslutil.sslkwargs(self.ui, host))
401 sslutil.validator(self.ui, host)(self.sock)
497 402
498 403 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
499 404 def __init__(self, ui):
500 405 keepalive.KeepAliveHandler.__init__(self)
501 406 urllib2.HTTPSHandler.__init__(self)
502 407 self.ui = ui
503 408 self.pwmgr = passwordmgr(self.ui)
504 409
505 410 def _start_transaction(self, h, req):
506 411 _generic_start_transaction(self, h, req)
507 412 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
508 413
509 414 def https_open(self, req):
510 415 res = readauthforuri(self.ui, req.get_full_url())
511 416 if res:
512 417 group, auth = res
513 418 self.auth = auth
514 419 self.ui.debug("using auth.%s.* for authentication\n" % group)
515 420 else:
516 421 self.auth = None
517 422 return self.do_open(self._makeconnection, req)
518 423
519 424 def _makeconnection(self, host, port=None, *args, **kwargs):
520 425 keyfile = None
521 426 certfile = None
522 427
523 428 if len(args) >= 1: # key_file
524 429 keyfile = args[0]
525 430 if len(args) >= 2: # cert_file
526 431 certfile = args[1]
527 432 args = args[2:]
528 433
529 434 # if the user has specified different key/cert files in
530 435 # hgrc, we prefer these
531 436 if self.auth and 'key' in self.auth and 'cert' in self.auth:
532 437 keyfile = self.auth['key']
533 438 certfile = self.auth['cert']
534 439
535 440 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
536 441 conn.ui = self.ui
537 442 return conn
538 443
539 444 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
540 445 def __init__(self, *args, **kwargs):
541 446 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
542 447 self.retried_req = None
543 448
544 449 def reset_retry_count(self):
545 450 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
546 451 # forever. We disable reset_retry_count completely and reset in
547 452 # http_error_auth_reqed instead.
548 453 pass
549 454
550 455 def http_error_auth_reqed(self, auth_header, host, req, headers):
551 456 # Reset the retry counter once for each request.
552 457 if req is not self.retried_req:
553 458 self.retried_req = req
554 459 self.retried = 0
555 460 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
556 461 # it doesn't know about the auth type requested. This can happen if
557 462 # somebody is using BasicAuth and types a bad password.
558 463 try:
559 464 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
560 465 self, auth_header, host, req, headers)
561 466 except ValueError, inst:
562 467 arg = inst.args[0]
563 468 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
564 469 return
565 470 raise
566 471
567 472 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
568 473 def __init__(self, *args, **kwargs):
569 474 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
570 475 self.retried_req = None
571 476
572 477 def reset_retry_count(self):
573 478 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
574 479 # forever. We disable reset_retry_count completely and reset in
575 480 # http_error_auth_reqed instead.
576 481 pass
577 482
578 483 def http_error_auth_reqed(self, auth_header, host, req, headers):
579 484 # Reset the retry counter once for each request.
580 485 if req is not self.retried_req:
581 486 self.retried_req = req
582 487 self.retried = 0
583 488 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
584 489 self, auth_header, host, req, headers)
585 490
586 491 handlerfuncs = []
587 492
588 493 def opener(ui, authinfo=None):
589 494 '''
590 495 construct an opener suitable for urllib2
591 496 authinfo will be added to the password manager
592 497 '''
593 498 handlers = [httphandler()]
594 499 if has_https:
595 500 handlers.append(httpshandler(ui))
596 501
597 502 handlers.append(proxyhandler(ui))
598 503
599 504 passmgr = passwordmgr(ui)
600 505 if authinfo is not None:
601 506 passmgr.add_password(*authinfo)
602 507 user, passwd = authinfo[2:4]
603 508 ui.debug('http auth: user %s, password %s\n' %
604 509 (user, passwd and '*' * len(passwd) or 'not set'))
605 510
606 511 handlers.extend((httpbasicauthhandler(passmgr),
607 512 httpdigestauthhandler(passmgr)))
608 513 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
609 514 opener = urllib2.build_opener(*handlers)
610 515
611 516 # 1.0 here is the _protocol_ version
612 517 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
613 518 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
614 519 return opener
615 520
616 521 def open(ui, url_, data=None):
617 522 u = util.url(url_)
618 523 if u.scheme:
619 524 u.scheme = u.scheme.lower()
620 525 url_, authinfo = u.authinfo()
621 526 else:
622 527 path = util.normpath(os.path.abspath(url_))
623 528 url_ = 'file://' + urllib.pathname2url(path)
624 529 authinfo = None
625 530 return opener(ui, authinfo).open(url_, data)
@@ -1,205 +1,205 b''
1 1 import sys
2 2
3 3 def check(a, b):
4 4 if a != b:
5 5 print (a, b)
6 6
7 7 def cert(cn):
8 8 return dict(subject=((('commonName', cn),),))
9 9
10 from mercurial.url import _verifycert
10 from mercurial.sslutil import _verifycert
11 11
12 12 # Test non-wildcard certificates
13 13 check(_verifycert(cert('example.com'), 'example.com'),
14 14 None)
15 15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 16 'certificate is for example.com')
17 17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 18 'certificate is for www.example.com')
19 19
20 20 # Test wildcard certificates
21 21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 22 None)
23 23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 24 'certificate is for *.example.com')
25 25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 26 'certificate is for *.example.com')
27 27
28 28 # Test subjectAltName
29 29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 30 'subjectAltName': (('DNS', '*.example.net'),
31 31 ('DNS', 'example.net'))}
32 32 check(_verifycert(san_cert, 'example.net'),
33 33 None)
34 34 check(_verifycert(san_cert, 'foo.example.net'),
35 35 None)
36 36 # subject is only checked when subjectAltName is empty
37 37 check(_verifycert(san_cert, 'example.com'),
38 38 'certificate is for *.example.net, example.net')
39 39
40 40 # Avoid some pitfalls
41 41 check(_verifycert(cert('*.foo'), 'foo'),
42 42 'certificate is for *.foo')
43 43 check(_verifycert(cert('*o'), 'foo'),
44 44 'certificate is for *o')
45 45
46 46 check(_verifycert({'subject': ()},
47 47 'example.com'),
48 48 'no commonName or subjectAltName found in certificate')
49 49 check(_verifycert(None, 'example.com'),
50 50 'no certificate received')
51 51
52 52 import doctest
53 53
54 54 def test_url():
55 55 """
56 56 >>> from mercurial.util import url
57 57
58 58 This tests for edge cases in url.URL's parsing algorithm. Most of
59 59 these aren't useful for documentation purposes, so they aren't
60 60 part of the class's doc tests.
61 61
62 62 Query strings and fragments:
63 63
64 64 >>> url('http://host/a?b#c')
65 65 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
66 66 >>> url('http://host/a?')
67 67 <url scheme: 'http', host: 'host', path: 'a'>
68 68 >>> url('http://host/a#b#c')
69 69 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
70 70 >>> url('http://host/a#b?c')
71 71 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
72 72 >>> url('http://host/?a#b')
73 73 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
74 74 >>> url('http://host/?a#b', parsequery=False)
75 75 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
76 76 >>> url('http://host/?a#b', parsefragment=False)
77 77 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
78 78 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
79 79 <url scheme: 'http', host: 'host', path: '?a#b'>
80 80
81 81 IPv6 addresses:
82 82
83 83 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
84 84 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
85 85 query: 'objectClass?one'>
86 86 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
87 87 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
88 88 port: '80', path: 'c=GB', query: 'objectClass?one'>
89 89
90 90 Missing scheme, host, etc.:
91 91
92 92 >>> url('://192.0.2.16:80/')
93 93 <url path: '://192.0.2.16:80/'>
94 94 >>> url('http://mercurial.selenic.com')
95 95 <url scheme: 'http', host: 'mercurial.selenic.com'>
96 96 >>> url('/foo')
97 97 <url path: '/foo'>
98 98 >>> url('bundle:/foo')
99 99 <url scheme: 'bundle', path: '/foo'>
100 100 >>> url('a?b#c')
101 101 <url path: 'a?b', fragment: 'c'>
102 102 >>> url('http://x.com?arg=/foo')
103 103 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
104 104 >>> url('http://joe:xxx@/foo')
105 105 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
106 106
107 107 Just a scheme and a path:
108 108
109 109 >>> url('mailto:John.Doe@example.com')
110 110 <url scheme: 'mailto', path: 'John.Doe@example.com'>
111 111 >>> url('a:b:c:d')
112 112 <url path: 'a:b:c:d'>
113 113 >>> url('aa:bb:cc:dd')
114 114 <url scheme: 'aa', path: 'bb:cc:dd'>
115 115
116 116 SSH examples:
117 117
118 118 >>> url('ssh://joe@host//home/joe')
119 119 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
120 120 >>> url('ssh://joe:xxx@host/src')
121 121 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
122 122 >>> url('ssh://joe:xxx@host')
123 123 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
124 124 >>> url('ssh://joe@host')
125 125 <url scheme: 'ssh', user: 'joe', host: 'host'>
126 126 >>> url('ssh://host')
127 127 <url scheme: 'ssh', host: 'host'>
128 128 >>> url('ssh://')
129 129 <url scheme: 'ssh'>
130 130 >>> url('ssh:')
131 131 <url scheme: 'ssh'>
132 132
133 133 Non-numeric port:
134 134
135 135 >>> url('http://example.com:dd')
136 136 <url scheme: 'http', host: 'example.com', port: 'dd'>
137 137 >>> url('ssh://joe:xxx@host:ssh/foo')
138 138 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
139 139 path: 'foo'>
140 140
141 141 Bad authentication credentials:
142 142
143 143 >>> url('http://joe@joeville:123@4:@host/a?b#c')
144 144 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
145 145 host: 'host', path: 'a', query: 'b', fragment: 'c'>
146 146 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
147 147 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
148 148 >>> url('http://!*#?@!*#?:@host/a?b#c')
149 149 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
150 150 >>> url('http://!*@:!*@@host/a?b#c')
151 151 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
152 152 path: 'a', query: 'b', fragment: 'c'>
153 153
154 154 File paths:
155 155
156 156 >>> url('a/b/c/d.g.f')
157 157 <url path: 'a/b/c/d.g.f'>
158 158 >>> url('/x///z/y/')
159 159 <url path: '/x///z/y/'>
160 160 >>> url('/foo:bar')
161 161 <url path: '/foo:bar'>
162 162 >>> url('\\\\foo:bar')
163 163 <url path: '\\\\foo:bar'>
164 164 >>> url('./foo:bar')
165 165 <url path: './foo:bar'>
166 166
167 167 Non-localhost file URL:
168 168
169 169 >>> u = url('file://mercurial.selenic.com/foo')
170 170 Traceback (most recent call last):
171 171 File "<stdin>", line 1, in ?
172 172 Abort: file:// URLs can only refer to localhost
173 173
174 174 Empty URL:
175 175
176 176 >>> u = url('')
177 177 >>> u
178 178 <url path: ''>
179 179 >>> str(u)
180 180 ''
181 181
182 182 Empty path with query string:
183 183
184 184 >>> str(url('http://foo/?bar'))
185 185 'http://foo/?bar'
186 186
187 187 Invalid path:
188 188
189 189 >>> u = url('http://foo/bar')
190 190 >>> u.path = 'bar'
191 191 >>> str(u)
192 192 'http://foo/bar'
193 193
194 194 >>> u = url('file:///foo/bar/baz')
195 195 >>> u
196 196 <url scheme: 'file', path: '/foo/bar/baz'>
197 197 >>> str(u)
198 198 'file:/foo/bar/baz'
199 199 """
200 200
201 201 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
202 202
203 203 # Unicode (IDN) certname isn't supported
204 204 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
205 205 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now