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