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) |
@@ -10,7 +10,7 b'' | |||
|
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 |
@@ -202,23 +202,6 b' def _gen_sendfile(orgsend):' | |||
|
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() |
@@ -257,7 +240,7 b' class httpconnection(keepalive.HTTPConne' | |||
|
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 = |
|
|
243 | self.sock = sslutil.ssl_wrap_socket(self.sock, None, None) | |
|
261 | 244 | else: |
|
262 | 245 | keepalive.HTTPConnection.connect(self) |
|
263 | 246 | |
@@ -398,41 +381,6 b' class httphandler(keepalive.HTTPHandler)' | |||
|
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 |
@@ -447,53 +395,10 b' if has_https:' | |||
|
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): |
General Comments 0
You need to be logged in to leave comments.
Login now