gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / requests / packages / urllib3 / contrib / pyopenssl.py @ 564
History | View | Annotate | Download (9.86 KB)
1 |
'''SSL with SNI_-support for Python 2. Follow these instructions if you would
|
---|---|
2 |
like to verify SSL certificates in Python 2. Note, the default libraries do
|
3 |
*not* do certificate checking; you need to do additional work to validate
|
4 |
certificates yourself.
|
5 |
|
6 |
This needs the following packages installed:
|
7 |
|
8 |
* pyOpenSSL (tested with 0.13)
|
9 |
* ndg-httpsclient (tested with 0.3.2)
|
10 |
* pyasn1 (tested with 0.1.6)
|
11 |
|
12 |
You can install them with the following command:
|
13 |
|
14 |
pip install pyopenssl ndg-httpsclient pyasn1
|
15 |
|
16 |
To activate certificate checking, call
|
17 |
:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
|
18 |
before you begin making HTTP requests. This can be done in a ``sitecustomize``
|
19 |
module, or at any other time before your application begins using ``urllib3``,
|
20 |
like this::
|
21 |
|
22 |
try:
|
23 |
import urllib3.contrib.pyopenssl
|
24 |
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
25 |
except ImportError:
|
26 |
pass
|
27 |
|
28 |
Now you can use :mod:`urllib3` as you normally would, and it will support SNI
|
29 |
when the required modules are installed.
|
30 |
|
31 |
Activating this module also has the positive side effect of disabling SSL/TLS
|
32 |
compression in Python 2 (see `CRIME attack`_).
|
33 |
|
34 |
If you want to configure the default list of supported cipher suites, you can
|
35 |
set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
|
36 |
|
37 |
Module Variables
|
38 |
----------------
|
39 |
|
40 |
:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
|
41 |
|
42 |
.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
|
43 |
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
|
44 |
|
45 |
'''
|
46 |
from __future__ import absolute_import |
47 |
|
48 |
try:
|
49 |
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT |
50 |
from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName |
51 |
except SyntaxError as e: |
52 |
raise ImportError(e) |
53 |
|
54 |
import OpenSSL.SSL |
55 |
from pyasn1.codec.der import decoder as der_decoder |
56 |
from pyasn1.type import univ, constraint |
57 |
from socket import _fileobject, timeout, error as SocketError |
58 |
import ssl |
59 |
import select |
60 |
|
61 |
from .. import connection
|
62 |
from .. import util
|
63 |
|
64 |
__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] |
65 |
|
66 |
# SNI only *really* works if we can read the subjectAltName of certificates.
|
67 |
HAS_SNI = SUBJ_ALT_NAME_SUPPORT |
68 |
|
69 |
# Map from urllib3 to PyOpenSSL compatible parameter-values.
|
70 |
_openssl_versions = { |
71 |
ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, |
72 |
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, |
73 |
} |
74 |
|
75 |
if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): |
76 |
_openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD |
77 |
|
78 |
if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): |
79 |
_openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD |
80 |
|
81 |
try:
|
82 |
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) |
83 |
except AttributeError: |
84 |
pass
|
85 |
|
86 |
_openssl_verify = { |
87 |
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, |
88 |
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, |
89 |
ssl.CERT_REQUIRED: |
90 |
OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, |
91 |
} |
92 |
|
93 |
DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS |
94 |
|
95 |
# OpenSSL will only write 16K at a time
|
96 |
SSL_WRITE_BLOCKSIZE = 16384
|
97 |
|
98 |
orig_util_HAS_SNI = util.HAS_SNI |
99 |
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket |
100 |
|
101 |
|
102 |
def inject_into_urllib3(): |
103 |
'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
|
104 |
|
105 |
connection.ssl_wrap_socket = ssl_wrap_socket |
106 |
util.HAS_SNI = HAS_SNI |
107 |
|
108 |
|
109 |
def extract_from_urllib3(): |
110 |
'Undo monkey-patching by :func:`inject_into_urllib3`.'
|
111 |
|
112 |
connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket |
113 |
util.HAS_SNI = orig_util_HAS_SNI |
114 |
|
115 |
|
116 |
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
117 |
class SubjectAltName(BaseSubjectAltName): |
118 |
'''ASN.1 implementation for subjectAltNames support'''
|
119 |
|
120 |
# There is no limit to how many SAN certificates a certificate may have,
|
121 |
# however this needs to have some limit so we'll set an arbitrarily high
|
122 |
# limit.
|
123 |
sizeSpec = univ.SequenceOf.sizeSpec + \ |
124 |
constraint.ValueSizeConstraint(1, 1024) |
125 |
|
126 |
|
127 |
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
128 |
def get_subj_alt_name(peer_cert): |
129 |
# Search through extensions
|
130 |
dns_name = [] |
131 |
if not SUBJ_ALT_NAME_SUPPORT: |
132 |
return dns_name
|
133 |
|
134 |
general_names = SubjectAltName() |
135 |
for i in range(peer_cert.get_extension_count()): |
136 |
ext = peer_cert.get_extension(i) |
137 |
ext_name = ext.get_short_name() |
138 |
if ext_name != 'subjectAltName': |
139 |
continue
|
140 |
|
141 |
# PyOpenSSL returns extension data in ASN.1 encoded form
|
142 |
ext_dat = ext.get_data() |
143 |
decoded_dat = der_decoder.decode(ext_dat, |
144 |
asn1Spec=general_names) |
145 |
|
146 |
for name in decoded_dat: |
147 |
if not isinstance(name, SubjectAltName): |
148 |
continue
|
149 |
for entry in range(len(name)): |
150 |
component = name.getComponentByPosition(entry) |
151 |
if component.getName() != 'dNSName': |
152 |
continue
|
153 |
dns_name.append(str(component.getComponent()))
|
154 |
|
155 |
return dns_name
|
156 |
|
157 |
|
158 |
class WrappedSocket(object): |
159 |
'''API-compatibility wrapper for Python OpenSSL's Connection-class.
|
160 |
|
161 |
Note: _makefile_refs, _drop() and _reuse() are needed for the garbage
|
162 |
collector of pypy.
|
163 |
'''
|
164 |
|
165 |
def __init__(self, connection, socket, suppress_ragged_eofs=True): |
166 |
self.connection = connection
|
167 |
self.socket = socket
|
168 |
self.suppress_ragged_eofs = suppress_ragged_eofs
|
169 |
self._makefile_refs = 0 |
170 |
|
171 |
def fileno(self): |
172 |
return self.socket.fileno() |
173 |
|
174 |
def makefile(self, mode, bufsize=-1): |
175 |
self._makefile_refs += 1 |
176 |
return _fileobject(self, mode, bufsize, close=True) |
177 |
|
178 |
def recv(self, *args, **kwargs): |
179 |
try:
|
180 |
data = self.connection.recv(*args, **kwargs)
|
181 |
except OpenSSL.SSL.SysCallError as e: |
182 |
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): |
183 |
return b'' |
184 |
else:
|
185 |
raise SocketError(e)
|
186 |
except OpenSSL.SSL.ZeroReturnError as e: |
187 |
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: |
188 |
return b'' |
189 |
else:
|
190 |
raise
|
191 |
except OpenSSL.SSL.WantReadError:
|
192 |
rd, wd, ed = select.select( |
193 |
[self.socket], [], [], self.socket.gettimeout()) |
194 |
if not rd: |
195 |
raise timeout('The read operation timed out') |
196 |
else:
|
197 |
return self.recv(*args, **kwargs) |
198 |
else:
|
199 |
return data
|
200 |
|
201 |
def settimeout(self, timeout): |
202 |
return self.socket.settimeout(timeout) |
203 |
|
204 |
def _send_until_done(self, data): |
205 |
while True: |
206 |
try:
|
207 |
return self.connection.send(data) |
208 |
except OpenSSL.SSL.WantWriteError:
|
209 |
_, wlist, _ = select.select([], [self.socket], [],
|
210 |
self.socket.gettimeout())
|
211 |
if not wlist: |
212 |
raise timeout()
|
213 |
continue
|
214 |
|
215 |
def sendall(self, data): |
216 |
total_sent = 0
|
217 |
while total_sent < len(data): |
218 |
sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
|
219 |
total_sent += sent |
220 |
|
221 |
def shutdown(self): |
222 |
# FIXME rethrow compatible exceptions should we ever use this
|
223 |
self.connection.shutdown()
|
224 |
|
225 |
def close(self): |
226 |
if self._makefile_refs < 1: |
227 |
try:
|
228 |
return self.connection.close() |
229 |
except OpenSSL.SSL.Error:
|
230 |
return
|
231 |
else:
|
232 |
self._makefile_refs -= 1 |
233 |
|
234 |
def getpeercert(self, binary_form=False): |
235 |
x509 = self.connection.get_peer_certificate()
|
236 |
|
237 |
if not x509: |
238 |
return x509
|
239 |
|
240 |
if binary_form:
|
241 |
return OpenSSL.crypto.dump_certificate(
|
242 |
OpenSSL.crypto.FILETYPE_ASN1, |
243 |
x509) |
244 |
|
245 |
return {
|
246 |
'subject': (
|
247 |
(('commonName', x509.get_subject().CN),),
|
248 |
), |
249 |
'subjectAltName': [
|
250 |
('DNS', value)
|
251 |
for value in get_subj_alt_name(x509) |
252 |
] |
253 |
} |
254 |
|
255 |
def _reuse(self): |
256 |
self._makefile_refs += 1 |
257 |
|
258 |
def _drop(self): |
259 |
if self._makefile_refs < 1: |
260 |
self.close()
|
261 |
else:
|
262 |
self._makefile_refs -= 1 |
263 |
|
264 |
|
265 |
def _verify_callback(cnx, x509, err_no, err_depth, return_code): |
266 |
return err_no == 0 |
267 |
|
268 |
|
269 |
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
270 |
ca_certs=None, server_hostname=None, |
271 |
ssl_version=None, ca_cert_dir=None): |
272 |
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version]) |
273 |
if certfile:
|
274 |
keyfile = keyfile or certfile # Match behaviour of the normal python ssl library |
275 |
ctx.use_certificate_file(certfile) |
276 |
if keyfile:
|
277 |
ctx.use_privatekey_file(keyfile) |
278 |
if cert_reqs != ssl.CERT_NONE:
|
279 |
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) |
280 |
if ca_certs or ca_cert_dir: |
281 |
try:
|
282 |
ctx.load_verify_locations(ca_certs, ca_cert_dir) |
283 |
except OpenSSL.SSL.Error as e: |
284 |
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) |
285 |
else:
|
286 |
ctx.set_default_verify_paths() |
287 |
|
288 |
# Disable TLS compression to migitate CRIME attack (issue #309)
|
289 |
OP_NO_COMPRESSION = 0x20000
|
290 |
ctx.set_options(OP_NO_COMPRESSION) |
291 |
|
292 |
# Set list of supported ciphersuites.
|
293 |
ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST) |
294 |
|
295 |
cnx = OpenSSL.SSL.Connection(ctx, sock) |
296 |
cnx.set_tlsext_host_name(server_hostname) |
297 |
cnx.set_connect_state() |
298 |
while True: |
299 |
try:
|
300 |
cnx.do_handshake() |
301 |
except OpenSSL.SSL.WantReadError:
|
302 |
rd, _, _ = select.select([sock], [], [], sock.gettimeout()) |
303 |
if not rd: |
304 |
raise timeout('select timed out') |
305 |
continue
|
306 |
except OpenSSL.SSL.Error as e: |
307 |
raise ssl.SSLError('bad handshake: %r' % e) |
308 |
break
|
309 |
|
310 |
return WrappedSocket(cnx, sock)
|