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