Statistics
| Revision:

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)