OLD | NEW |
1 '''SSL with SNI-support for Python 2. | 1 '''SSL with SNI-support for Python 2. |
2 | 2 |
3 This needs the following packages installed: | 3 This needs the following packages installed: |
4 | 4 |
5 * pyOpenSSL (tested with 0.13) | 5 * pyOpenSSL (tested with 0.13) |
6 * ndg-httpsclient (tested with 0.3.2) | 6 * ndg-httpsclient (tested with 0.3.2) |
7 * pyasn1 (tested with 0.1.6) | 7 * pyasn1 (tested with 0.1.6) |
8 | 8 |
9 To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`. | 9 To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`. |
10 This can be done in a ``sitecustomize`` module, or at any other time before | 10 This can be done in a ``sitecustomize`` module, or at any other time before |
11 your application begins using ``urllib3``, like this:: | 11 your application begins using ``urllib3``, like this:: |
12 | 12 |
13 try: | 13 try: |
14 import urllib3.contrib.pyopenssl | 14 import urllib3.contrib.pyopenssl |
15 urllib3.contrib.pyopenssl.inject_into_urllib3() | 15 urllib3.contrib.pyopenssl.inject_into_urllib3() |
16 except ImportError: | 16 except ImportError: |
17 pass | 17 pass |
18 | 18 |
19 Now you can use :mod:`urllib3` as you normally would, and it will support SNI | 19 Now you can use :mod:`urllib3` as you normally would, and it will support SNI |
20 when the required modules are installed. | 20 when the required modules are installed. |
21 ''' | 21 ''' |
22 | 22 |
23 from ndg.httpsclient.ssl_peer_verification import (ServerSSLCertVerification, | 23 from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT |
24 SUBJ_ALT_NAME_SUPPORT) | |
25 from ndg.httpsclient.subj_alt_name import SubjectAltName | 24 from ndg.httpsclient.subj_alt_name import SubjectAltName |
26 import OpenSSL.SSL | 25 import OpenSSL.SSL |
27 from pyasn1.codec.der import decoder as der_decoder | 26 from pyasn1.codec.der import decoder as der_decoder |
28 from socket import _fileobject | 27 from socket import _fileobject |
29 import ssl | 28 import ssl |
| 29 from cStringIO import StringIO |
30 | 30 |
31 from .. import connectionpool | 31 from .. import connectionpool |
32 from .. import util | 32 from .. import util |
33 | 33 |
34 __all__ = ['inject_into_urllib3', 'extract_from_urllib3'] | 34 __all__ = ['inject_into_urllib3', 'extract_from_urllib3'] |
35 | 35 |
36 # SNI only *really* works if we can read the subjectAltName of certificates. | 36 # SNI only *really* works if we can read the subjectAltName of certificates. |
37 HAS_SNI = SUBJ_ALT_NAME_SUPPORT | 37 HAS_SNI = SUBJ_ALT_NAME_SUPPORT |
38 | 38 |
39 # Map from urllib3 to PyOpenSSL compatible parameter-values. | 39 # Map from urllib3 to PyOpenSSL compatible parameter-values. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 continue | 92 continue |
93 for entry in range(len(name)): | 93 for entry in range(len(name)): |
94 component = name.getComponentByPosition(entry) | 94 component = name.getComponentByPosition(entry) |
95 if component.getName() != 'dNSName': | 95 if component.getName() != 'dNSName': |
96 continue | 96 continue |
97 dns_name.append(str(component.getComponent())) | 97 dns_name.append(str(component.getComponent())) |
98 | 98 |
99 return dns_name | 99 return dns_name |
100 | 100 |
101 | 101 |
| 102 class fileobject(_fileobject): |
| 103 |
| 104 def read(self, size=-1): |
| 105 # Use max, disallow tiny reads in a loop as they are very inefficient. |
| 106 # We never leave read() with any leftover data from a new recv() call |
| 107 # in our internal buffer. |
| 108 rbufsize = max(self._rbufsize, self.default_bufsize) |
| 109 # Our use of StringIO rather than lists of string objects returned by |
| 110 # recv() minimizes memory usage and fragmentation that occurs when |
| 111 # rbufsize is large compared to the typical return value of recv(). |
| 112 buf = self._rbuf |
| 113 buf.seek(0, 2) # seek end |
| 114 if size < 0: |
| 115 # Read until EOF |
| 116 self._rbuf = StringIO() # reset _rbuf. we consume it via buf. |
| 117 while True: |
| 118 try: |
| 119 data = self._sock.recv(rbufsize) |
| 120 except OpenSSL.SSL.WantReadError: |
| 121 continue |
| 122 if not data: |
| 123 break |
| 124 buf.write(data) |
| 125 return buf.getvalue() |
| 126 else: |
| 127 # Read until size bytes or EOF seen, whichever comes first |
| 128 buf_len = buf.tell() |
| 129 if buf_len >= size: |
| 130 # Already have size bytes in our buffer? Extract and return. |
| 131 buf.seek(0) |
| 132 rv = buf.read(size) |
| 133 self._rbuf = StringIO() |
| 134 self._rbuf.write(buf.read()) |
| 135 return rv |
| 136 |
| 137 self._rbuf = StringIO() # reset _rbuf. we consume it via buf. |
| 138 while True: |
| 139 left = size - buf_len |
| 140 # recv() will malloc the amount of memory given as its |
| 141 # parameter even though it often returns much less data |
| 142 # than that. The returned data string is short lived |
| 143 # as we copy it into a StringIO and free it. This avoids |
| 144 # fragmentation issues on many platforms. |
| 145 try: |
| 146 data = self._sock.recv(left) |
| 147 except OpenSSL.SSL.WantReadError: |
| 148 continue |
| 149 if not data: |
| 150 break |
| 151 n = len(data) |
| 152 if n == size and not buf_len: |
| 153 # Shortcut. Avoid buffer data copies when: |
| 154 # - We have no data in our buffer. |
| 155 # AND |
| 156 # - Our call to recv returned exactly the |
| 157 # number of bytes we were asked to read. |
| 158 return data |
| 159 if n == left: |
| 160 buf.write(data) |
| 161 del data # explicit free |
| 162 break |
| 163 assert n <= left, "recv(%d) returned %d bytes" % (left, n) |
| 164 buf.write(data) |
| 165 buf_len += n |
| 166 del data # explicit free |
| 167 #assert buf_len == buf.tell() |
| 168 return buf.getvalue() |
| 169 |
| 170 def readline(self, size=-1): |
| 171 buf = self._rbuf |
| 172 buf.seek(0, 2) # seek end |
| 173 if buf.tell() > 0: |
| 174 # check if we already have it in our buffer |
| 175 buf.seek(0) |
| 176 bline = buf.readline(size) |
| 177 if bline.endswith('\n') or len(bline) == size: |
| 178 self._rbuf = StringIO() |
| 179 self._rbuf.write(buf.read()) |
| 180 return bline |
| 181 del bline |
| 182 if size < 0: |
| 183 # Read until \n or EOF, whichever comes first |
| 184 if self._rbufsize <= 1: |
| 185 # Speed up unbuffered case |
| 186 buf.seek(0) |
| 187 buffers = [buf.read()] |
| 188 self._rbuf = StringIO() # reset _rbuf. we consume it via buf. |
| 189 data = None |
| 190 recv = self._sock.recv |
| 191 while True: |
| 192 try: |
| 193 while data != "\n": |
| 194 data = recv(1) |
| 195 if not data: |
| 196 break |
| 197 buffers.append(data) |
| 198 except OpenSSL.SSL.WantReadError: |
| 199 continue |
| 200 break |
| 201 return "".join(buffers) |
| 202 |
| 203 buf.seek(0, 2) # seek end |
| 204 self._rbuf = StringIO() # reset _rbuf. we consume it via buf. |
| 205 while True: |
| 206 try: |
| 207 data = self._sock.recv(self._rbufsize) |
| 208 except OpenSSL.SSL.WantReadError: |
| 209 continue |
| 210 if not data: |
| 211 break |
| 212 nl = data.find('\n') |
| 213 if nl >= 0: |
| 214 nl += 1 |
| 215 buf.write(data[:nl]) |
| 216 self._rbuf.write(data[nl:]) |
| 217 del data |
| 218 break |
| 219 buf.write(data) |
| 220 return buf.getvalue() |
| 221 else: |
| 222 # Read until size bytes or \n or EOF seen, whichever comes first |
| 223 buf.seek(0, 2) # seek end |
| 224 buf_len = buf.tell() |
| 225 if buf_len >= size: |
| 226 buf.seek(0) |
| 227 rv = buf.read(size) |
| 228 self._rbuf = StringIO() |
| 229 self._rbuf.write(buf.read()) |
| 230 return rv |
| 231 self._rbuf = StringIO() # reset _rbuf. we consume it via buf. |
| 232 while True: |
| 233 try: |
| 234 data = self._sock.recv(self._rbufsize) |
| 235 except OpenSSL.SSL.WantReadError: |
| 236 continue |
| 237 if not data: |
| 238 break |
| 239 left = size - buf_len |
| 240 # did we just receive a newline? |
| 241 nl = data.find('\n', 0, left) |
| 242 if nl >= 0: |
| 243 nl += 1 |
| 244 # save the excess data to _rbuf |
| 245 self._rbuf.write(data[nl:]) |
| 246 if buf_len: |
| 247 buf.write(data[:nl]) |
| 248 break |
| 249 else: |
| 250 # Shortcut. Avoid data copy through buf when returning |
| 251 # a substring of our first recv(). |
| 252 return data[:nl] |
| 253 n = len(data) |
| 254 if n == size and not buf_len: |
| 255 # Shortcut. Avoid data copy through buf when |
| 256 # returning exactly all of our first recv(). |
| 257 return data |
| 258 if n >= left: |
| 259 buf.write(data[:left]) |
| 260 self._rbuf.write(data[left:]) |
| 261 break |
| 262 buf.write(data) |
| 263 buf_len += n |
| 264 #assert buf_len == buf.tell() |
| 265 return buf.getvalue() |
| 266 |
| 267 |
102 class WrappedSocket(object): | 268 class WrappedSocket(object): |
103 '''API-compatibility wrapper for Python OpenSSL's Connection-class.''' | 269 '''API-compatibility wrapper for Python OpenSSL's Connection-class.''' |
104 | 270 |
105 def __init__(self, connection, socket): | 271 def __init__(self, connection, socket): |
106 self.connection = connection | 272 self.connection = connection |
107 self.socket = socket | 273 self.socket = socket |
108 | 274 |
| 275 def fileno(self): |
| 276 return self.socket.fileno() |
| 277 |
109 def makefile(self, mode, bufsize=-1): | 278 def makefile(self, mode, bufsize=-1): |
110 return _fileobject(self.connection, mode, bufsize) | 279 return fileobject(self.connection, mode, bufsize) |
111 | 280 |
112 def settimeout(self, timeout): | 281 def settimeout(self, timeout): |
113 return self.socket.settimeout(timeout) | 282 return self.socket.settimeout(timeout) |
114 | 283 |
115 def sendall(self, data): | 284 def sendall(self, data): |
116 return self.connection.sendall(data) | 285 return self.connection.sendall(data) |
117 | 286 |
| 287 def close(self): |
| 288 return self.connection.shutdown() |
| 289 |
118 def getpeercert(self, binary_form=False): | 290 def getpeercert(self, binary_form=False): |
119 x509 = self.connection.get_peer_certificate() | 291 x509 = self.connection.get_peer_certificate() |
| 292 |
120 if not x509: | 293 if not x509: |
121 raise ssl.SSLError('') | 294 return x509 |
122 | 295 |
123 if binary_form: | 296 if binary_form: |
124 return OpenSSL.crypto.dump_certificate( | 297 return OpenSSL.crypto.dump_certificate( |
125 OpenSSL.crypto.FILETYPE_ASN1, | 298 OpenSSL.crypto.FILETYPE_ASN1, |
126 x509) | 299 x509) |
127 | 300 |
128 return { | 301 return { |
129 'subject': ( | 302 'subject': ( |
130 (('commonName', x509.get_subject().CN),), | 303 (('commonName', x509.get_subject().CN),), |
131 ), | 304 ), |
(...skipping 20 matching lines...) Expand all Loading... |
152 ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) | 325 ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) |
153 if ca_certs: | 326 if ca_certs: |
154 try: | 327 try: |
155 ctx.load_verify_locations(ca_certs, None) | 328 ctx.load_verify_locations(ca_certs, None) |
156 except OpenSSL.SSL.Error as e: | 329 except OpenSSL.SSL.Error as e: |
157 raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) | 330 raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) |
158 | 331 |
159 cnx = OpenSSL.SSL.Connection(ctx, sock) | 332 cnx = OpenSSL.SSL.Connection(ctx, sock) |
160 cnx.set_tlsext_host_name(server_hostname) | 333 cnx.set_tlsext_host_name(server_hostname) |
161 cnx.set_connect_state() | 334 cnx.set_connect_state() |
162 try: | 335 while True: |
163 cnx.do_handshake() | 336 try: |
164 except OpenSSL.SSL.Error as e: | 337 cnx.do_handshake() |
165 raise ssl.SSLError('bad handshake', e) | 338 except OpenSSL.SSL.WantReadError: |
| 339 continue |
| 340 except OpenSSL.SSL.Error as e: |
| 341 raise ssl.SSLError('bad handshake', e) |
| 342 break |
166 | 343 |
167 return WrappedSocket(cnx, sock) | 344 return WrappedSocket(cnx, sock) |
OLD | NEW |