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