##// END OF EJS Templates
sslutil: use preferred formatting for import syntax
Gregory Szorc -
r28577:7efff6ce default
parent child Browse files
Show More
@@ -1,211 +1,214 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
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import os
12 import os
13 import ssl
13 import ssl
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import error, util
17 from . import (
18 error,
19 util,
20 )
18
21
19 hassni = getattr(ssl, 'HAS_SNI', False)
22 hassni = getattr(ssl, 'HAS_SNI', False)
20
23
21 _canloaddefaultcerts = False
24 _canloaddefaultcerts = False
22 try:
25 try:
23 ssl_context = ssl.SSLContext
26 ssl_context = ssl.SSLContext
24 _canloaddefaultcerts = util.safehasattr(ssl_context, 'load_default_certs')
27 _canloaddefaultcerts = util.safehasattr(ssl_context, 'load_default_certs')
25
28
26 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
29 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
27 ca_certs=None, serverhostname=None):
30 ca_certs=None, serverhostname=None):
28 # Allow any version of SSL starting with TLSv1 and
31 # Allow any version of SSL starting with TLSv1 and
29 # up. Note that specifying TLSv1 here prohibits use of
32 # up. Note that specifying TLSv1 here prohibits use of
30 # newer standards (like TLSv1_2), so this is the right way
33 # newer standards (like TLSv1_2), so this is the right way
31 # to do this. Note that in the future it'd be better to
34 # to do this. Note that in the future it'd be better to
32 # support using ssl.create_default_context(), which sets
35 # support using ssl.create_default_context(), which sets
33 # up a bunch of things in smart ways (strong ciphers,
36 # up a bunch of things in smart ways (strong ciphers,
34 # protocol versions, etc) and is upgraded by Python
37 # protocol versions, etc) and is upgraded by Python
35 # maintainers for us, but that breaks too many things to
38 # maintainers for us, but that breaks too many things to
36 # do it in a hurry.
39 # do it in a hurry.
37 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
40 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
38 sslcontext.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
41 sslcontext.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
39 if certfile is not None:
42 if certfile is not None:
40 def password():
43 def password():
41 f = keyfile or certfile
44 f = keyfile or certfile
42 return ui.getpass(_('passphrase for %s: ') % f, '')
45 return ui.getpass(_('passphrase for %s: ') % f, '')
43 sslcontext.load_cert_chain(certfile, keyfile, password)
46 sslcontext.load_cert_chain(certfile, keyfile, password)
44 sslcontext.verify_mode = cert_reqs
47 sslcontext.verify_mode = cert_reqs
45 if ca_certs is not None:
48 if ca_certs is not None:
46 sslcontext.load_verify_locations(cafile=ca_certs)
49 sslcontext.load_verify_locations(cafile=ca_certs)
47 elif _canloaddefaultcerts:
50 elif _canloaddefaultcerts:
48 sslcontext.load_default_certs()
51 sslcontext.load_default_certs()
49
52
50 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
53 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
51 # check if wrap_socket failed silently because socket had been
54 # check if wrap_socket failed silently because socket had been
52 # closed
55 # closed
53 # - see http://bugs.python.org/issue13721
56 # - see http://bugs.python.org/issue13721
54 if not sslsocket.cipher():
57 if not sslsocket.cipher():
55 raise error.Abort(_('ssl connection failed'))
58 raise error.Abort(_('ssl connection failed'))
56 return sslsocket
59 return sslsocket
57 except AttributeError:
60 except AttributeError:
58 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
61 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
59 ca_certs=None, serverhostname=None):
62 ca_certs=None, serverhostname=None):
60 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
63 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
61 cert_reqs=cert_reqs, ca_certs=ca_certs,
64 cert_reqs=cert_reqs, ca_certs=ca_certs,
62 ssl_version=ssl.PROTOCOL_TLSv1)
65 ssl_version=ssl.PROTOCOL_TLSv1)
63 # check if wrap_socket failed silently because socket had been
66 # check if wrap_socket failed silently because socket had been
64 # closed
67 # closed
65 # - see http://bugs.python.org/issue13721
68 # - see http://bugs.python.org/issue13721
66 if not sslsocket.cipher():
69 if not sslsocket.cipher():
67 raise error.Abort(_('ssl connection failed'))
70 raise error.Abort(_('ssl connection failed'))
68 return sslsocket
71 return sslsocket
69
72
70 def _verifycert(cert, hostname):
73 def _verifycert(cert, hostname):
71 '''Verify that cert (in socket.getpeercert() format) matches hostname.
74 '''Verify that cert (in socket.getpeercert() format) matches hostname.
72 CRLs is not handled.
75 CRLs is not handled.
73
76
74 Returns error message if any problems are found and None on success.
77 Returns error message if any problems are found and None on success.
75 '''
78 '''
76 if not cert:
79 if not cert:
77 return _('no certificate received')
80 return _('no certificate received')
78 dnsname = hostname.lower()
81 dnsname = hostname.lower()
79 def matchdnsname(certname):
82 def matchdnsname(certname):
80 return (certname == dnsname or
83 return (certname == dnsname or
81 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
84 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
82
85
83 san = cert.get('subjectAltName', [])
86 san = cert.get('subjectAltName', [])
84 if san:
87 if san:
85 certnames = [value.lower() for key, value in san if key == 'DNS']
88 certnames = [value.lower() for key, value in san if key == 'DNS']
86 for name in certnames:
89 for name in certnames:
87 if matchdnsname(name):
90 if matchdnsname(name):
88 return None
91 return None
89 if certnames:
92 if certnames:
90 return _('certificate is for %s') % ', '.join(certnames)
93 return _('certificate is for %s') % ', '.join(certnames)
91
94
92 # subject is only checked when subjectAltName is empty
95 # subject is only checked when subjectAltName is empty
93 for s in cert.get('subject', []):
96 for s in cert.get('subject', []):
94 key, value = s[0]
97 key, value = s[0]
95 if key == 'commonName':
98 if key == 'commonName':
96 try:
99 try:
97 # 'subject' entries are unicode
100 # 'subject' entries are unicode
98 certname = value.lower().encode('ascii')
101 certname = value.lower().encode('ascii')
99 except UnicodeEncodeError:
102 except UnicodeEncodeError:
100 return _('IDN in certificate not supported')
103 return _('IDN in certificate not supported')
101 if matchdnsname(certname):
104 if matchdnsname(certname):
102 return None
105 return None
103 return _('certificate is for %s') % certname
106 return _('certificate is for %s') % certname
104 return _('no commonName or subjectAltName found in certificate')
107 return _('no commonName or subjectAltName found in certificate')
105
108
106
109
107 # CERT_REQUIRED means fetch the cert from the server all the time AND
110 # CERT_REQUIRED means fetch the cert from the server all the time AND
108 # validate it against the CA store provided in web.cacerts.
111 # validate it against the CA store provided in web.cacerts.
109
112
110 def _plainapplepython():
113 def _plainapplepython():
111 """return true if this seems to be a pure Apple Python that
114 """return true if this seems to be a pure Apple Python that
112 * is unfrozen and presumably has the whole mercurial module in the file
115 * is unfrozen and presumably has the whole mercurial module in the file
113 system
116 system
114 * presumably is an Apple Python that uses Apple OpenSSL which has patches
117 * presumably is an Apple Python that uses Apple OpenSSL which has patches
115 for using system certificate store CAs in addition to the provided
118 for using system certificate store CAs in addition to the provided
116 cacerts file
119 cacerts file
117 """
120 """
118 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
121 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
119 return False
122 return False
120 exe = os.path.realpath(sys.executable).lower()
123 exe = os.path.realpath(sys.executable).lower()
121 return (exe.startswith('/usr/bin/python') or
124 return (exe.startswith('/usr/bin/python') or
122 exe.startswith('/system/library/frameworks/python.framework/'))
125 exe.startswith('/system/library/frameworks/python.framework/'))
123
126
124 def _defaultcacerts():
127 def _defaultcacerts():
125 """return path to CA certificates; None for system's store; ! to disable"""
128 """return path to CA certificates; None for system's store; ! to disable"""
126 if _plainapplepython():
129 if _plainapplepython():
127 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
130 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
128 if os.path.exists(dummycert):
131 if os.path.exists(dummycert):
129 return dummycert
132 return dummycert
130 if _canloaddefaultcerts:
133 if _canloaddefaultcerts:
131 return None
134 return None
132 return '!'
135 return '!'
133
136
134 def sslkwargs(ui, host):
137 def sslkwargs(ui, host):
135 kws = {'ui': ui}
138 kws = {'ui': ui}
136 hostfingerprint = ui.config('hostfingerprints', host)
139 hostfingerprint = ui.config('hostfingerprints', host)
137 if hostfingerprint:
140 if hostfingerprint:
138 return kws
141 return kws
139 cacerts = ui.config('web', 'cacerts')
142 cacerts = ui.config('web', 'cacerts')
140 if cacerts == '!':
143 if cacerts == '!':
141 pass
144 pass
142 elif cacerts:
145 elif cacerts:
143 cacerts = util.expandpath(cacerts)
146 cacerts = util.expandpath(cacerts)
144 if not os.path.exists(cacerts):
147 if not os.path.exists(cacerts):
145 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
148 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
146 else:
149 else:
147 cacerts = _defaultcacerts()
150 cacerts = _defaultcacerts()
148 if cacerts and cacerts != '!':
151 if cacerts and cacerts != '!':
149 ui.debug('using %s to enable OS X system CA\n' % cacerts)
152 ui.debug('using %s to enable OS X system CA\n' % cacerts)
150 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
153 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
151 if cacerts != '!':
154 if cacerts != '!':
152 kws.update({'ca_certs': cacerts,
155 kws.update({'ca_certs': cacerts,
153 'cert_reqs': ssl.CERT_REQUIRED,
156 'cert_reqs': ssl.CERT_REQUIRED,
154 })
157 })
155 return kws
158 return kws
156
159
157 class validator(object):
160 class validator(object):
158 def __init__(self, ui, host):
161 def __init__(self, ui, host):
159 self.ui = ui
162 self.ui = ui
160 self.host = host
163 self.host = host
161
164
162 def __call__(self, sock, strict=False):
165 def __call__(self, sock, strict=False):
163 host = self.host
166 host = self.host
164 cacerts = self.ui.config('web', 'cacerts')
167 cacerts = self.ui.config('web', 'cacerts')
165 hostfingerprints = self.ui.configlist('hostfingerprints', host)
168 hostfingerprints = self.ui.configlist('hostfingerprints', host)
166
169
167 if not sock.cipher(): # work around http://bugs.python.org/issue13721
170 if not sock.cipher(): # work around http://bugs.python.org/issue13721
168 raise error.Abort(_('%s ssl connection error') % host)
171 raise error.Abort(_('%s ssl connection error') % host)
169 try:
172 try:
170 peercert = sock.getpeercert(True)
173 peercert = sock.getpeercert(True)
171 peercert2 = sock.getpeercert()
174 peercert2 = sock.getpeercert()
172 except AttributeError:
175 except AttributeError:
173 raise error.Abort(_('%s ssl connection error') % host)
176 raise error.Abort(_('%s ssl connection error') % host)
174
177
175 if not peercert:
178 if not peercert:
176 raise error.Abort(_('%s certificate error: '
179 raise error.Abort(_('%s certificate error: '
177 'no certificate received') % host)
180 'no certificate received') % host)
178 peerfingerprint = util.sha1(peercert).hexdigest()
181 peerfingerprint = util.sha1(peercert).hexdigest()
179 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
182 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
180 for x in xrange(0, len(peerfingerprint), 2)])
183 for x in xrange(0, len(peerfingerprint), 2)])
181 if hostfingerprints:
184 if hostfingerprints:
182 fingerprintmatch = False
185 fingerprintmatch = False
183 for hostfingerprint in hostfingerprints:
186 for hostfingerprint in hostfingerprints:
184 if peerfingerprint.lower() == \
187 if peerfingerprint.lower() == \
185 hostfingerprint.replace(':', '').lower():
188 hostfingerprint.replace(':', '').lower():
186 fingerprintmatch = True
189 fingerprintmatch = True
187 break
190 break
188 if not fingerprintmatch:
191 if not fingerprintmatch:
189 raise error.Abort(_('certificate for %s has unexpected '
192 raise error.Abort(_('certificate for %s has unexpected '
190 'fingerprint %s') % (host, nicefingerprint),
193 'fingerprint %s') % (host, nicefingerprint),
191 hint=_('check hostfingerprint configuration'))
194 hint=_('check hostfingerprint configuration'))
192 self.ui.debug('%s certificate matched fingerprint %s\n' %
195 self.ui.debug('%s certificate matched fingerprint %s\n' %
193 (host, nicefingerprint))
196 (host, nicefingerprint))
194 elif cacerts != '!':
197 elif cacerts != '!':
195 msg = _verifycert(peercert2, host)
198 msg = _verifycert(peercert2, host)
196 if msg:
199 if msg:
197 raise error.Abort(_('%s certificate error: %s') % (host, msg),
200 raise error.Abort(_('%s certificate error: %s') % (host, msg),
198 hint=_('configure hostfingerprint %s or use '
201 hint=_('configure hostfingerprint %s or use '
199 '--insecure to connect insecurely') %
202 '--insecure to connect insecurely') %
200 nicefingerprint)
203 nicefingerprint)
201 self.ui.debug('%s certificate successfully verified\n' % host)
204 self.ui.debug('%s certificate successfully verified\n' % host)
202 elif strict:
205 elif strict:
203 raise error.Abort(_('%s certificate with fingerprint %s not '
206 raise error.Abort(_('%s certificate with fingerprint %s not '
204 'verified') % (host, nicefingerprint),
207 'verified') % (host, nicefingerprint),
205 hint=_('check hostfingerprints or web.cacerts '
208 hint=_('check hostfingerprints or web.cacerts '
206 'config setting'))
209 'config setting'))
207 else:
210 else:
208 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
211 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
209 'verified (check hostfingerprints or web.cacerts '
212 'verified (check hostfingerprints or web.cacerts '
210 'config setting)\n') %
213 'config setting)\n') %
211 (host, nicefingerprint))
214 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now