https.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """ndg_httpsclient HTTPS module containing PyOpenSSL implementation of
  2. httplib.HTTPSConnection
  3. PyOpenSSL utility to make a httplib-like interface suitable for use with
  4. urllib2
  5. """
  6. __author__ = "P J Kershaw (STFC)"
  7. __date__ = "09/12/11"
  8. __copyright__ = "(C) 2012 Science and Technology Facilities Council"
  9. __license__ = "BSD - see LICENSE file in top-level directory"
  10. __contact__ = "Philip.Kershaw@stfc.ac.uk"
  11. __revision__ = '$Id$'
  12. import logging
  13. import socket
  14. from httplib import HTTPS_PORT
  15. from httplib import HTTPConnection
  16. from urllib2 import AbstractHTTPHandler
  17. from OpenSSL import SSL
  18. from ndg.httpsclient.ssl_socket import SSLSocket
  19. log = logging.getLogger(__name__)
  20. class HTTPSConnection(HTTPConnection):
  21. """This class allows communication via SSL using PyOpenSSL.
  22. It is based on httplib.HTTPSConnection, modified to use PyOpenSSL.
  23. Note: This uses the constructor inherited from HTTPConnection to allow it to
  24. be used with httplib and HTTPSContextHandler. To use the class directly with
  25. an SSL context set ssl_context after construction.
  26. @cvar default_port: default port for this class (443)
  27. @type default_port: int
  28. @cvar default_ssl_method: default SSL method used if no SSL context is
  29. explicitly set - defaults to version 2/3.
  30. @type default_ssl_method: int
  31. """
  32. default_port = HTTPS_PORT
  33. default_ssl_method = SSL.SSLv23_METHOD
  34. def __init__(self, host, port=None, strict=None,
  35. timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None):
  36. HTTPConnection.__init__(self, host, port, strict, timeout)
  37. if not hasattr(self, 'ssl_context'):
  38. self.ssl_context = None
  39. if ssl_context is not None:
  40. if not isinstance(ssl_context, SSL.Context):
  41. raise TypeError('Expecting OpenSSL.SSL.Context type for "'
  42. 'ssl_context" keyword; got %r instead' %
  43. ssl_context)
  44. self.ssl_context = ssl_context
  45. def connect(self):
  46. """Create SSL socket and connect to peer
  47. """
  48. if getattr(self, 'ssl_context', None):
  49. if not isinstance(self.ssl_context, SSL.Context):
  50. raise TypeError('Expecting OpenSSL.SSL.Context type for "'
  51. 'ssl_context" attribute; got %r instead' %
  52. self.ssl_context)
  53. ssl_context = self.ssl_context
  54. else:
  55. ssl_context = SSL.Context(self.__class__.default_ssl_method)
  56. sock = socket.create_connection((self.host, self.port), self.timeout)
  57. # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above
  58. if getattr(self, '_tunnel_host', None):
  59. self.sock = sock
  60. self._tunnel()
  61. self.sock = SSLSocket(ssl_context, sock)
  62. # Go to client mode.
  63. self.sock.set_connect_state()
  64. def close(self):
  65. """Close socket and shut down SSL connection"""
  66. self.sock.close()
  67. class HTTPSContextHandler(AbstractHTTPHandler):
  68. '''HTTPS handler that allows a SSL context to be set for the SSL
  69. connections.
  70. '''
  71. https_request = AbstractHTTPHandler.do_request_
  72. def __init__(self, ssl_context, debuglevel=0):
  73. """
  74. @param ssl_context:SSL context
  75. @type ssl_context: OpenSSL.SSL.Context
  76. @param debuglevel: debug level for HTTPSHandler
  77. @type debuglevel: int
  78. """
  79. AbstractHTTPHandler.__init__(self, debuglevel)
  80. if ssl_context is not None:
  81. if not isinstance(ssl_context, SSL.Context):
  82. raise TypeError('Expecting OpenSSL.SSL.Context type for "'
  83. 'ssl_context" keyword; got %r instead' %
  84. ssl_context)
  85. self.ssl_context = ssl_context
  86. else:
  87. self.ssl_context = SSL.Context(SSL.SSLv23_METHOD)
  88. def https_open(self, req):
  89. """Opens HTTPS request
  90. @param req: HTTP request
  91. @return: HTTP Response object
  92. """
  93. # Make a custom class extending HTTPSConnection, with the SSL context
  94. # set as a class variable so that it is available to the connect method.
  95. customHTTPSContextConnection = type('CustomHTTPSContextConnection',
  96. (HTTPSConnection, object),
  97. {'ssl_context': self.ssl_context})
  98. return self.do_open(customHTTPSContextConnection, req)