Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(414)

Side by Side Diff: third_party/google-endpoints/urllib3/contrib/pyopenssl.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 """
2 SSL with SNI_-support for Python 2. Follow these instructions if you would
3 like to verify SSL certificates in Python 2. Note, the default libraries do
4 *not* do certificate checking; you need to do additional work to validate
5 certificates yourself.
6
7 This needs the following packages installed:
8
9 * pyOpenSSL (tested with 16.0.0)
10 * cryptography (minimum 1.3.4, from pyopenssl)
11 * idna (minimum 2.0, from cryptography)
12
13 However, pyopenssl depends on cryptography, which depends on idna, so while we
14 use all three directly here we end up having relatively few packages required.
15
16 You can install them with the following command:
17
18 pip install pyopenssl cryptography idna
19
20 To activate certificate checking, call
21 :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
22 before you begin making HTTP requests. This can be done in a ``sitecustomize``
23 module, or at any other time before your application begins using ``urllib3``,
24 like this::
25
26 try:
27 import urllib3.contrib.pyopenssl
28 urllib3.contrib.pyopenssl.inject_into_urllib3()
29 except ImportError:
30 pass
31
32 Now you can use :mod:`urllib3` as you normally would, and it will support SNI
33 when the required modules are installed.
34
35 Activating this module also has the positive side effect of disabling SSL/TLS
36 compression in Python 2 (see `CRIME attack`_).
37
38 If you want to configure the default list of supported cipher suites, you can
39 set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
40
41 .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
42 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
43 """
44 from __future__ import absolute_import
45
46 import OpenSSL.SSL
47 from cryptography import x509
48 from cryptography.hazmat.backends.openssl import backend as openssl_backend
49 from cryptography.hazmat.backends.openssl.x509 import _Certificate
50
51 from socket import timeout, error as SocketError
52 from io import BytesIO
53
54 try: # Platform-specific: Python 2
55 from socket import _fileobject
56 except ImportError: # Platform-specific: Python 3
57 _fileobject = None
58 from ..packages.backports.makefile import backport_makefile
59
60 import logging
61 import ssl
62 import six
63 import sys
64
65 from .. import util
66
67 __all__ = ['inject_into_urllib3', 'extract_from_urllib3']
68
69 # SNI always works.
70 HAS_SNI = True
71
72 # Map from urllib3 to PyOpenSSL compatible parameter-values.
73 _openssl_versions = {
74 ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
75 ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
76 }
77
78 if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
79 _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
80
81 if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
82 _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
83
84 try:
85 _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
86 except AttributeError:
87 pass
88
89 _stdlib_to_openssl_verify = {
90 ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
91 ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
92 ssl.CERT_REQUIRED:
93 OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
94 }
95 _openssl_to_stdlib_verify = dict(
96 (v, k) for k, v in _stdlib_to_openssl_verify.items()
97 )
98
99 # OpenSSL will only write 16K at a time
100 SSL_WRITE_BLOCKSIZE = 16384
101
102 orig_util_HAS_SNI = util.HAS_SNI
103 orig_util_SSLContext = util.ssl_.SSLContext
104
105
106 log = logging.getLogger(__name__)
107
108
109 def inject_into_urllib3():
110 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
111
112 _validate_dependencies_met()
113
114 util.ssl_.SSLContext = PyOpenSSLContext
115 util.HAS_SNI = HAS_SNI
116 util.ssl_.HAS_SNI = HAS_SNI
117 util.IS_PYOPENSSL = True
118 util.ssl_.IS_PYOPENSSL = True
119
120
121 def extract_from_urllib3():
122 'Undo monkey-patching by :func:`inject_into_urllib3`.'
123
124 util.ssl_.SSLContext = orig_util_SSLContext
125 util.HAS_SNI = orig_util_HAS_SNI
126 util.ssl_.HAS_SNI = orig_util_HAS_SNI
127 util.IS_PYOPENSSL = False
128 util.ssl_.IS_PYOPENSSL = False
129
130
131 def _validate_dependencies_met():
132 """
133 Verifies that PyOpenSSL's package-level dependencies have been met.
134 Throws `ImportError` if they are not met.
135 """
136 # Method added in `cryptography==1.1`; not available in older versions
137 from cryptography.x509.extensions import Extensions
138 if getattr(Extensions, "get_extension_for_class", None) is None:
139 raise ImportError("'cryptography' module missing required functionality. "
140 "Try upgrading to v1.3.4 or newer.")
141
142 # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509
143 # attribute is only present on those versions.
144 from OpenSSL.crypto import X509
145 x509 = X509()
146 if getattr(x509, "_x509", None) is None:
147 raise ImportError("'pyOpenSSL' module missing required functionality. "
148 "Try upgrading to v0.14 or newer.")
149
150
151 def _dnsname_to_stdlib(name):
152 """
153 Converts a dNSName SubjectAlternativeName field to the form used by the
154 standard library on the given Python version.
155
156 Cryptography produces a dNSName as a unicode string that was idna-decoded
157 from ASCII bytes. We need to idna-encode that string to get it back, and
158 then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib
159 uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).
160 """
161 def idna_encode(name):
162 """
163 Borrowed wholesale from the Python Cryptography Project. It turns out
164 that we can't just safely call `idna.encode`: it can explode for
165 wildcard names. This avoids that problem.
166 """
167 import idna
168
169 for prefix in [u'*.', u'.']:
170 if name.startswith(prefix):
171 name = name[len(prefix):]
172 return prefix.encode('ascii') + idna.encode(name)
173 return idna.encode(name)
174
175 name = idna_encode(name)
176 if sys.version_info >= (3, 0):
177 name = name.decode('utf-8')
178 return name
179
180
181 def get_subj_alt_name(peer_cert):
182 """
183 Given an PyOpenSSL certificate, provides all the subject alternative names.
184 """
185 # Pass the cert to cryptography, which has much better APIs for this.
186 # This is technically using private APIs, but should work across all
187 # relevant versions until PyOpenSSL gets something proper for this.
188 cert = _Certificate(openssl_backend, peer_cert._x509)
189
190 # We want to find the SAN extension. Ask Cryptography to locate it (it's
191 # faster than looping in Python)
192 try:
193 ext = cert.extensions.get_extension_for_class(
194 x509.SubjectAlternativeName
195 ).value
196 except x509.ExtensionNotFound:
197 # No such extension, return the empty list.
198 return []
199 except (x509.DuplicateExtension, x509.UnsupportedExtension,
200 x509.UnsupportedGeneralNameType, UnicodeError) as e:
201 # A problem has been found with the quality of the certificate. Assume
202 # no SAN field is present.
203 log.warning(
204 "A problem was encountered with the certificate that prevented "
205 "urllib3 from finding the SubjectAlternativeName field. This can "
206 "affect certificate validation. The error was %s",
207 e,
208 )
209 return []
210
211 # We want to return dNSName and iPAddress fields. We need to cast the IPs
212 # back to strings because the match_hostname function wants them as
213 # strings.
214 # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8
215 # decoded. This is pretty frustrating, but that's what the standard library
216 # does with certificates, and so we need to attempt to do the same.
217 names = [
218 ('DNS', _dnsname_to_stdlib(name))
219 for name in ext.get_values_for_type(x509.DNSName)
220 ]
221 names.extend(
222 ('IP Address', str(name))
223 for name in ext.get_values_for_type(x509.IPAddress)
224 )
225
226 return names
227
228
229 class WrappedSocket(object):
230 '''API-compatibility wrapper for Python OpenSSL's Connection-class.
231
232 Note: _makefile_refs, _drop() and _reuse() are needed for the garbage
233 collector of pypy.
234 '''
235
236 def __init__(self, connection, socket, suppress_ragged_eofs=True):
237 self.connection = connection
238 self.socket = socket
239 self.suppress_ragged_eofs = suppress_ragged_eofs
240 self._makefile_refs = 0
241 self._closed = False
242
243 def fileno(self):
244 return self.socket.fileno()
245
246 # Copy-pasted from Python 3.5 source code
247 def _decref_socketios(self):
248 if self._makefile_refs > 0:
249 self._makefile_refs -= 1
250 if self._closed:
251 self.close()
252
253 def recv(self, *args, **kwargs):
254 try:
255 data = self.connection.recv(*args, **kwargs)
256 except OpenSSL.SSL.SysCallError as e:
257 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
258 return b''
259 else:
260 raise SocketError(str(e))
261 except OpenSSL.SSL.ZeroReturnError as e:
262 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
263 return b''
264 else:
265 raise
266 except OpenSSL.SSL.WantReadError:
267 rd = util.wait_for_read(self.socket, self.socket.gettimeout())
268 if not rd:
269 raise timeout('The read operation timed out')
270 else:
271 return self.recv(*args, **kwargs)
272 else:
273 return data
274
275 def recv_into(self, *args, **kwargs):
276 try:
277 return self.connection.recv_into(*args, **kwargs)
278 except OpenSSL.SSL.SysCallError as e:
279 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
280 return 0
281 else:
282 raise SocketError(str(e))
283 except OpenSSL.SSL.ZeroReturnError as e:
284 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
285 return 0
286 else:
287 raise
288 except OpenSSL.SSL.WantReadError:
289 rd = util.wait_for_read(self.socket, self.socket.gettimeout())
290 if not rd:
291 raise timeout('The read operation timed out')
292 else:
293 return self.recv_into(*args, **kwargs)
294
295 def settimeout(self, timeout):
296 return self.socket.settimeout(timeout)
297
298 def _send_until_done(self, data):
299 while True:
300 try:
301 return self.connection.send(data)
302 except OpenSSL.SSL.WantWriteError:
303 wr = util.wait_for_write(self.socket, self.socket.gettimeout())
304 if not wr:
305 raise timeout()
306 continue
307
308 def sendall(self, data):
309 total_sent = 0
310 while total_sent < len(data):
311 sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_ BLOCKSIZE])
312 total_sent += sent
313
314 def shutdown(self):
315 # FIXME rethrow compatible exceptions should we ever use this
316 self.connection.shutdown()
317
318 def close(self):
319 if self._makefile_refs < 1:
320 try:
321 self._closed = True
322 return self.connection.close()
323 except OpenSSL.SSL.Error:
324 return
325 else:
326 self._makefile_refs -= 1
327
328 def getpeercert(self, binary_form=False):
329 x509 = self.connection.get_peer_certificate()
330
331 if not x509:
332 return x509
333
334 if binary_form:
335 return OpenSSL.crypto.dump_certificate(
336 OpenSSL.crypto.FILETYPE_ASN1,
337 x509)
338
339 return {
340 'subject': (
341 (('commonName', x509.get_subject().CN),),
342 ),
343 'subjectAltName': get_subj_alt_name(x509)
344 }
345
346 def _reuse(self):
347 self._makefile_refs += 1
348
349 def _drop(self):
350 if self._makefile_refs < 1:
351 self.close()
352 else:
353 self._makefile_refs -= 1
354
355
356 if _fileobject: # Platform-specific: Python 2
357 def makefile(self, mode, bufsize=-1):
358 self._makefile_refs += 1
359 return _fileobject(self, mode, bufsize, close=True)
360 else: # Platform-specific: Python 3
361 makefile = backport_makefile
362
363 WrappedSocket.makefile = makefile
364
365
366 class PyOpenSSLContext(object):
367 """
368 I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible
369 for translating the interface of the standard library ``SSLContext`` object
370 to calls into PyOpenSSL.
371 """
372 def __init__(self, protocol):
373 self.protocol = _openssl_versions[protocol]
374 self._ctx = OpenSSL.SSL.Context(self.protocol)
375 self._options = 0
376 self.check_hostname = False
377
378 @property
379 def options(self):
380 return self._options
381
382 @options.setter
383 def options(self, value):
384 self._options = value
385 self._ctx.set_options(value)
386
387 @property
388 def verify_mode(self):
389 return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]
390
391 @verify_mode.setter
392 def verify_mode(self, value):
393 self._ctx.set_verify(
394 _stdlib_to_openssl_verify[value],
395 _verify_callback
396 )
397
398 def set_default_verify_paths(self):
399 self._ctx.set_default_verify_paths()
400
401 def set_ciphers(self, ciphers):
402 if isinstance(ciphers, six.text_type):
403 ciphers = ciphers.encode('utf-8')
404 self._ctx.set_cipher_list(ciphers)
405
406 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
407 if cafile is not None:
408 cafile = cafile.encode('utf-8')
409 if capath is not None:
410 capath = capath.encode('utf-8')
411 self._ctx.load_verify_locations(cafile, capath)
412 if cadata is not None:
413 self._ctx.load_verify_locations(BytesIO(cadata))
414
415 def load_cert_chain(self, certfile, keyfile=None, password=None):
416 self._ctx.use_certificate_file(certfile)
417 if password is not None:
418 self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: p assword)
419 self._ctx.use_privatekey_file(keyfile or certfile)
420
421 def wrap_socket(self, sock, server_side=False,
422 do_handshake_on_connect=True, suppress_ragged_eofs=True,
423 server_hostname=None):
424 cnx = OpenSSL.SSL.Connection(self._ctx, sock)
425
426 if isinstance(server_hostname, six.text_type): # Platform-specific: Pyt hon 3
427 server_hostname = server_hostname.encode('utf-8')
428
429 if server_hostname is not None:
430 cnx.set_tlsext_host_name(server_hostname)
431
432 cnx.set_connect_state()
433
434 while True:
435 try:
436 cnx.do_handshake()
437 except OpenSSL.SSL.WantReadError:
438 rd = util.wait_for_read(sock, sock.gettimeout())
439 if not rd:
440 raise timeout('select timed out')
441 continue
442 except OpenSSL.SSL.Error as e:
443 raise ssl.SSLError('bad handshake: %r' % e)
444 break
445
446 return WrappedSocket(cnx, sock)
447
448
449 def _verify_callback(cnx, x509, err_no, err_depth, return_code):
450 return err_no == 0
OLDNEW
« no previous file with comments | « third_party/google-endpoints/urllib3/contrib/ntlmpool.py ('k') | third_party/google-endpoints/urllib3/contrib/socks.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698