Mercurial > public > mercurial-scm > hg
comparison mercurial/url.py @ 14204:5fa21960b2f4
sslutil: extracted ssl methods from httpsconnection in url.py
This makes it easier to share ssl cert validation with other http
implementations.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Wed, 04 May 2011 22:08:55 -0500 |
parents | 924c82157d46 |
children | e7525a555a64 |
comparison
equal
deleted
inserted
replaced
14203:b230922eb0c3 | 14204:5fa21960b2f4 |
---|---|
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 import urllib, urllib2, httplib, os, socket, cStringIO | 10 import urllib, urllib2, httplib, os, socket, cStringIO |
11 import __builtin__ | 11 import __builtin__ |
12 from i18n import _ | 12 from i18n import _ |
13 import keepalive, util | 13 import keepalive, util, sslutil |
14 | 14 |
15 def readauthforuri(ui, uri): | 15 def readauthforuri(ui, uri): |
16 # Read configuration | 16 # Read configuration |
17 config = dict() | 17 config = dict() |
18 for key, val in ui.configitems('auth'): | 18 for key, val in ui.configitems('auth'): |
200 return _sendfile | 200 return _sendfile |
201 | 201 |
202 has_https = hasattr(urllib2, 'HTTPSHandler') | 202 has_https = hasattr(urllib2, 'HTTPSHandler') |
203 if has_https: | 203 if has_https: |
204 try: | 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 _create_connection = socket.create_connection | 205 _create_connection = socket.create_connection |
223 except AttributeError: | 206 except AttributeError: |
224 _GLOBAL_DEFAULT_TIMEOUT = object() | 207 _GLOBAL_DEFAULT_TIMEOUT = object() |
225 | 208 |
226 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, | 209 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, |
255 if has_https and self.realhostport: # use CONNECT proxy | 238 if has_https and self.realhostport: # use CONNECT proxy |
256 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 239 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
257 self.sock.connect((self.host, self.port)) | 240 self.sock.connect((self.host, self.port)) |
258 if _generic_proxytunnel(self): | 241 if _generic_proxytunnel(self): |
259 # we do not support client x509 certificates | 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 else: | 244 else: |
262 keepalive.HTTPConnection.connect(self) | 245 keepalive.HTTPConnection.connect(self) |
263 | 246 |
264 def getresponse(self): | 247 def getresponse(self): |
265 proxyres = getattr(self, 'proxyres', None) | 248 proxyres = getattr(self, 'proxyres', None) |
396 | 379 |
397 def _start_transaction(self, h, req): | 380 def _start_transaction(self, h, req): |
398 _generic_start_transaction(self, h, req) | 381 _generic_start_transaction(self, h, req) |
399 return keepalive.HTTPHandler._start_transaction(self, h, req) | 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 if has_https: | 384 if has_https: |
437 class httpsconnection(httplib.HTTPSConnection): | 385 class httpsconnection(httplib.HTTPSConnection): |
438 response_class = keepalive.HTTPResponse | 386 response_class = keepalive.HTTPResponse |
439 # must be able to send big bundle as stream. | 387 # must be able to send big bundle as stream. |
440 send = _gen_sendfile(keepalive.safesend) | 388 send = _gen_sendfile(keepalive.safesend) |
445 | 393 |
446 host = self.host | 394 host = self.host |
447 if self.realhostport: # use CONNECT proxy | 395 if self.realhostport: # use CONNECT proxy |
448 _generic_proxytunnel(self) | 396 _generic_proxytunnel(self) |
449 host = self.realhostport.rsplit(':', 1)[0] | 397 host = self.realhostport.rsplit(':', 1)[0] |
450 | 398 self.sock = sslutil.ssl_wrap_socket( |
451 cacerts = self.ui.config('web', 'cacerts') | 399 self.sock, self.key_file, self.cert_file, |
452 hostfingerprint = self.ui.config('hostfingerprints', host) | 400 **sslutil.sslkwargs(self.ui, host)) |
453 | 401 sslutil.validator(self.ui, host)(self.sock) |
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) | |
497 | 402 |
498 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): | 403 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): |
499 def __init__(self, ui): | 404 def __init__(self, ui): |
500 keepalive.KeepAliveHandler.__init__(self) | 405 keepalive.KeepAliveHandler.__init__(self) |
501 urllib2.HTTPSHandler.__init__(self) | 406 urllib2.HTTPSHandler.__init__(self) |