ssl_socket.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. """PyOpenSSL utilities including HTTPSSocket class which wraps PyOpenSSL
  2. SSL connection into a httplib-like interface suitable for use with urllib2
  3. """
  4. __author__ = "P J Kershaw"
  5. __date__ = "21/12/10"
  6. __copyright__ = "(C) 2012 Science and Technology Facilities Council"
  7. __license__ = "BSD - see LICENSE file in top-level directory"
  8. __contact__ = "Philip.Kershaw@stfc.ac.uk"
  9. __revision__ = '$Id$'
  10. from datetime import datetime
  11. import logging
  12. import socket
  13. from cStringIO import StringIO
  14. from OpenSSL import SSL
  15. log = logging.getLogger(__name__)
  16. class SSLSocket(object):
  17. """SSL Socket class wraps pyOpenSSL's SSL.Connection class implementing
  18. the makefile method so that it is compatible with the standard socket
  19. interface and usable with httplib.
  20. @cvar default_buf_size: default buffer size for recv operations in the
  21. makefile method
  22. @type default_buf_size: int
  23. """
  24. default_buf_size = 8192
  25. def __init__(self, ctx, sock=None):
  26. """Create SSL socket object
  27. @param ctx: SSL context
  28. @type ctx: OpenSSL.SSL.Context
  29. @param sock: underlying socket object
  30. @type sock: socket.socket
  31. """
  32. if sock is not None:
  33. self.socket = sock
  34. else:
  35. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  36. self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  37. self.__ssl_conn = SSL.Connection(ctx, self.socket)
  38. self.buf_size = self.__class__.default_buf_size
  39. self._makefile_refs = 0
  40. def __del__(self):
  41. """Close underlying socket when this object goes out of scope
  42. """
  43. self.close()
  44. @property
  45. def buf_size(self):
  46. """Buffer size for makefile method recv() operations"""
  47. return self.__buf_size
  48. @buf_size.setter
  49. def buf_size(self, value):
  50. """Buffer size for makefile method recv() operations"""
  51. if not isinstance(value, (int, long)):
  52. raise TypeError('Expecting int or long type for "buf_size"; '
  53. 'got %r instead' % type(value))
  54. self.__buf_size = value
  55. def close(self):
  56. """Shutdown the SSL connection and call the close method of the
  57. underlying socket"""
  58. # try:
  59. # self.__ssl_conn.shutdown()
  60. # except SSL.Error:
  61. # # Make errors on shutdown non-fatal
  62. # pass
  63. if self._makefile_refs < 1:
  64. self.__ssl_conn.shutdown()
  65. else:
  66. self._makefile_refs -= 1
  67. def set_shutdown(self, mode):
  68. """Set the shutdown state of the Connection.
  69. @param mode: bit vector of either or both of SENT_SHUTDOWN and
  70. RECEIVED_SHUTDOWN
  71. """
  72. self.__ssl_conn.set_shutdown(mode)
  73. def get_shutdown(self):
  74. """Get the shutdown state of the Connection.
  75. @return: bit vector of either or both of SENT_SHUTDOWN and
  76. RECEIVED_SHUTDOWN
  77. """
  78. return self.__ssl_conn.get_shutdown()
  79. def bind(self, addr):
  80. """bind to the given address - calls method of the underlying socket
  81. @param addr: address/port number tuple
  82. @type addr: tuple"""
  83. self.__ssl_conn.bind(addr)
  84. def listen(self, backlog):
  85. """Listen for connections made to the socket.
  86. @param backlog: specifies the maximum number of queued connections and
  87. should be at least 1; the maximum value is system-dependent (usually 5).
  88. @param backlog: int
  89. """
  90. self.__ssl_conn.listen(backlog)
  91. def set_accept_state(self):
  92. """Set the connection to work in server mode. The handshake will be
  93. handled automatically by read/write"""
  94. self.__ssl_conn.set_accept_state()
  95. def accept(self):
  96. """Accept an SSL connection.
  97. @return: pair (ssl, addr) where ssl is a new SSL connection object and
  98. addr is the address bound to the other end of the SSL connection.
  99. @rtype: tuple
  100. """
  101. return self.__ssl_conn.accept()
  102. def set_connect_state(self):
  103. """Set the connection to work in client mode. The handshake will be
  104. handled automatically by read/write"""
  105. self.__ssl_conn.set_connect_state()
  106. def connect(self, addr):
  107. """Call the connect method of the underlying socket and set up SSL on
  108. the socket, using the Context object supplied to this Connection object
  109. at creation.
  110. @param addr: address/port number pair
  111. @type addr: tuple
  112. """
  113. self.__ssl_conn.connect(addr)
  114. def shutdown(self, how):
  115. """Send the shutdown message to the Connection.
  116. @param how: for socket.socket this flag determines whether read, write
  117. or both type operations are supported. OpenSSL.SSL.Connection doesn't
  118. support this so this parameter is IGNORED
  119. @return: true if the shutdown message exchange is completed and false
  120. otherwise (in which case you call recv() or send() when the connection
  121. becomes readable/writeable.
  122. @rtype: bool
  123. """
  124. return self.__ssl_conn.shutdown()
  125. def renegotiate(self):
  126. """Renegotiate this connection's SSL parameters."""
  127. return self.__ssl_conn.renegotiate()
  128. def pending(self):
  129. """@return: numbers of bytes that can be safely read from the SSL
  130. buffer.
  131. @rtype: int
  132. """
  133. return self.__ssl_conn.pending()
  134. def send(self, data, *flags_arg):
  135. """Send data to the socket. Nb. The optional flags argument is ignored.
  136. - retained for compatibility with socket.socket interface
  137. @param data: data to send down the socket
  138. @type data: string
  139. """
  140. return self.__ssl_conn.send(data)
  141. def sendall(self, data):
  142. self.__ssl_conn.sendall(data)
  143. def recv(self, size=default_buf_size):
  144. """Receive data from the Connection.
  145. @param size: The maximum amount of data to be received at once
  146. @type size: int
  147. @return: data received.
  148. @rtype: string
  149. """
  150. return self.__ssl_conn.recv(size)
  151. def setblocking(self, mode):
  152. """Set this connection's underlying socket blocking _mode_.
  153. @param mode: blocking mode
  154. @type mode: int
  155. """
  156. self.__ssl_conn.setblocking(mode)
  157. def fileno(self):
  158. """
  159. @return: file descriptor number for the underlying socket
  160. @rtype: int
  161. """
  162. return self.__ssl_conn.fileno()
  163. def getsockopt(self, *args):
  164. """See socket.socket.getsockopt
  165. """
  166. return self.__ssl_conn.getsockopt(*args)
  167. def setsockopt(self, *args):
  168. """See socket.socket.setsockopt
  169. @return: value of the given socket option
  170. @rtype: int/string
  171. """
  172. return self.__ssl_conn.setsockopt(*args)
  173. def state_string(self):
  174. """Return the SSL state of this connection."""
  175. return self.__ssl_conn.state_string()
  176. def makefile(self, *args):
  177. """Specific to Python socket API and required by httplib: convert
  178. response into a file-like object. This implementation reads using recv
  179. and copies the output into a StringIO buffer to simulate a file object
  180. for consumption by httplib
  181. Nb. Ignoring optional file open mode (StringIO is generic and will
  182. open for read and write unless a string is passed to the constructor)
  183. and buffer size - httplib set a zero buffer size which results in recv
  184. reading nothing
  185. @return: file object for data returned from socket
  186. @rtype: cStringIO.StringO
  187. """
  188. self._makefile_refs += 1
  189. # Optimisation
  190. _buf_size = self.buf_size
  191. i=0
  192. stream = StringIO()
  193. startTime = datetime.utcnow()
  194. try:
  195. dat = self.__ssl_conn.recv(_buf_size)
  196. while dat:
  197. i+=1
  198. stream.write(dat)
  199. dat = self.__ssl_conn.recv(_buf_size)
  200. except (SSL.ZeroReturnError, SSL.SysCallError):
  201. # Connection is closed - assuming here that all is well and full
  202. # response has been received. httplib will catch an error in
  203. # incomplete content since it checks the content-length header
  204. # against the actual length of data received
  205. pass
  206. if log.getEffectiveLevel() <= logging.DEBUG:
  207. log.debug("Socket.makefile %d recv calls completed in %s", i,
  208. datetime.utcnow() - startTime)
  209. # Make sure to rewind the buffer otherwise consumers of the content will
  210. # read from the end of the buffer
  211. stream.seek(0)
  212. return stream
  213. # def makefile(self, mode='r', bufsize=-1):
  214. #
  215. # """Make and return a file-like object that
  216. # works with the SSL connection. Just use the code
  217. # from the socket module."""
  218. #
  219. # self._makefile_refs += 1
  220. # # close=True so as to decrement the reference count when done with
  221. # # the file-like object.
  222. # return socket._fileobject(self.socket, mode, bufsize, close=True)
  223. def getsockname(self):
  224. """
  225. @return: the socket's own address
  226. @rtype:
  227. """
  228. return self.__ssl_conn.getsockname()
  229. def getpeername(self):
  230. """
  231. @return: remote address to which the socket is connected
  232. """
  233. return self.__ssl_conn.getpeername()
  234. def get_context(self):
  235. '''Retrieve the Context object associated with this Connection. '''
  236. return self.__ssl_conn.get_context()
  237. def get_peer_certificate(self):
  238. '''Retrieve the other side's certificate (if any) '''
  239. return self.__ssl_conn.get_peer_certificate()