Plex plugin to to play various online streams (mostly Latvian).

pyopenssl.py 12KB

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