##// END OF EJS Templates
sslutil: try harder to avoid getpeercert problems...
Matt Mackall -
r18879:93b03a22 default
parent child Browse files
Show More
@@ -1,143 +1,149 b''
1 1 # sslutil.py - SSL 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 import os
10 10
11 11 from mercurial import util
12 12 from mercurial.i18n import _
13 13 try:
14 14 # avoid using deprecated/broken FakeSocket in python 2.6
15 15 import ssl
16 16 CERT_REQUIRED = ssl.CERT_REQUIRED
17 17 def ssl_wrap_socket(sock, keyfile, certfile,
18 18 cert_reqs=ssl.CERT_NONE, ca_certs=None):
19 19 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
20 20 cert_reqs=cert_reqs, ca_certs=ca_certs)
21 21 # check if wrap_socket failed silently because socket had been closed
22 22 # - see http://bugs.python.org/issue13721
23 23 if not sslsocket.cipher():
24 24 raise util.Abort(_('ssl connection failed'))
25 25 return sslsocket
26 26 except ImportError:
27 27 CERT_REQUIRED = 2
28 28
29 29 import socket, httplib
30 30
31 31 def ssl_wrap_socket(sock, key_file, cert_file,
32 32 cert_reqs=CERT_REQUIRED, ca_certs=None):
33 33 if not util.safehasattr(socket, 'ssl'):
34 34 raise util.Abort(_('Python SSL support not found'))
35 35 if ca_certs:
36 36 raise util.Abort(_(
37 37 'certificate checking requires Python 2.6'))
38 38
39 39 ssl = socket.ssl(sock, key_file, cert_file)
40 40 return httplib.FakeSocket(sock, ssl)
41 41
42 42 def _verifycert(cert, hostname):
43 43 '''Verify that cert (in socket.getpeercert() format) matches hostname.
44 44 CRLs is not handled.
45 45
46 46 Returns error message if any problems are found and None on success.
47 47 '''
48 48 if not cert:
49 49 return _('no certificate received')
50 50 dnsname = hostname.lower()
51 51 def matchdnsname(certname):
52 52 return (certname == dnsname or
53 53 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
54 54
55 55 san = cert.get('subjectAltName', [])
56 56 if san:
57 57 certnames = [value.lower() for key, value in san if key == 'DNS']
58 58 for name in certnames:
59 59 if matchdnsname(name):
60 60 return None
61 61 if certnames:
62 62 return _('certificate is for %s') % ', '.join(certnames)
63 63
64 64 # subject is only checked when subjectAltName is empty
65 65 for s in cert.get('subject', []):
66 66 key, value = s[0]
67 67 if key == 'commonName':
68 68 try:
69 69 # 'subject' entries are unicode
70 70 certname = value.lower().encode('ascii')
71 71 except UnicodeEncodeError:
72 72 return _('IDN in certificate not supported')
73 73 if matchdnsname(certname):
74 74 return None
75 75 return _('certificate is for %s') % certname
76 76 return _('no commonName or subjectAltName found in certificate')
77 77
78 78
79 79 # CERT_REQUIRED means fetch the cert from the server all the time AND
80 80 # validate it against the CA store provided in web.cacerts.
81 81 #
82 82 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
83 83 # busted on those versions.
84 84
85 85 def sslkwargs(ui, host):
86 86 cacerts = ui.config('web', 'cacerts')
87 87 hostfingerprint = ui.config('hostfingerprints', host)
88 88 if cacerts and not hostfingerprint:
89 89 cacerts = util.expandpath(cacerts)
90 90 if not os.path.exists(cacerts):
91 91 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
92 92 return {'ca_certs': cacerts,
93 93 'cert_reqs': CERT_REQUIRED,
94 94 }
95 95 return {}
96 96
97 97 class validator(object):
98 98 def __init__(self, ui, host):
99 99 self.ui = ui
100 100 self.host = host
101 101
102 102 def __call__(self, sock):
103 103 host = self.host
104 104 cacerts = self.ui.config('web', 'cacerts')
105 105 hostfingerprint = self.ui.config('hostfingerprints', host)
106 106 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
107 107 if hostfingerprint:
108 108 raise util.Abort(_("host fingerprint for %s can't be "
109 109 "verified (Python too old)") % host)
110 110 if self.ui.configbool('ui', 'reportoldssl', True):
111 111 self.ui.warn(_("warning: certificate for %s can't be verified "
112 112 "(Python too old)\n") % host)
113 113 return
114
114 115 if not sock.cipher(): # work around http://bugs.python.org/issue13721
115 116 raise util.Abort(_('%s ssl connection error') % host)
116 peercert = sock.getpeercert(True)
117 try:
118 peercert = sock.getpeercert(True)
119 peercert2 = sock.getpeercert()
120 except AttributeError:
121 raise util.Abort(_('%s ssl connection error') % host)
122
117 123 if not peercert:
118 124 raise util.Abort(_('%s certificate error: '
119 125 'no certificate received') % host)
120 126 peerfingerprint = util.sha1(peercert).hexdigest()
121 127 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
122 128 for x in xrange(0, len(peerfingerprint), 2)])
123 129 if hostfingerprint:
124 130 if peerfingerprint.lower() != \
125 131 hostfingerprint.replace(':', '').lower():
126 132 raise util.Abort(_('certificate for %s has unexpected '
127 133 'fingerprint %s') % (host, nicefingerprint),
128 134 hint=_('check hostfingerprint configuration'))
129 135 self.ui.debug('%s certificate matched fingerprint %s\n' %
130 136 (host, nicefingerprint))
131 137 elif cacerts:
132 msg = _verifycert(sock.getpeercert(), host)
138 msg = _verifycert(peercert2, host)
133 139 if msg:
134 140 raise util.Abort(_('%s certificate error: %s') % (host, msg),
135 141 hint=_('configure hostfingerprint %s or use '
136 142 '--insecure to connect insecurely') %
137 143 nicefingerprint)
138 144 self.ui.debug('%s certificate successfully verified\n' % host)
139 145 else:
140 146 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
141 147 'verified (check hostfingerprints or web.cacerts '
142 148 'config setting)\n') %
143 149 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now