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