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