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