123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- """PyOpenSSL utilities including HTTPSSocket class which wraps PyOpenSSL
- SSL connection into a httplib-like interface suitable for use with urllib2
- """
- __author__ = "P J Kershaw"
- __date__ = "21/12/10"
- __copyright__ = "(C) 2012 Science and Technology Facilities Council"
- __license__ = "BSD - see LICENSE file in top-level directory"
- __contact__ = "Philip.Kershaw@stfc.ac.uk"
- __revision__ = '$Id$'
- from datetime import datetime
- import logging
- import socket
- from cStringIO import StringIO
- from OpenSSL import SSL
- log = logging.getLogger(__name__)
- class SSLSocket(object):
- """SSL Socket class wraps pyOpenSSL's SSL.Connection class implementing
- the makefile method so that it is compatible with the standard socket
- interface and usable with httplib.
- @cvar default_buf_size: default buffer size for recv operations in the
- makefile method
- @type default_buf_size: int
- """
- default_buf_size = 8192
- def __init__(self, ctx, sock=None):
- """Create SSL socket object
- @param ctx: SSL context
- @type ctx: OpenSSL.SSL.Context
- @param sock: underlying socket object
- @type sock: socket.socket
- """
- if sock is not None:
- self.socket = sock
- else:
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.__ssl_conn = SSL.Connection(ctx, self.socket)
- self.buf_size = self.__class__.default_buf_size
- def __del__(self):
- """Close underlying socket when this object goes out of scope
- """
- self.close()
- @property
- def buf_size(self):
- """Buffer size for makefile method recv() operations"""
- return self.__buf_size
- @buf_size.setter
- def buf_size(self, value):
- """Buffer size for makefile method recv() operations"""
- if not isinstance(value, (int, long)):
- raise TypeError('Expecting int or long type for "buf_size"; '
- 'got %r instead' % type(value))
- self.__buf_size = value
- def close(self):
- """Shutdown the SSL connection and call the close method of the
- underlying socket"""
- try:
- self.__ssl_conn.shutdown()
- except SSL.Error, e:
- # Make errors on shutdown non-fatal
- log.warning('Connection shutdown failed: %r', e)
- self.__ssl_conn.close()
- def set_shutdown(self, mode):
- """Set the shutdown state of the Connection.
- @param mode: bit vector of either or both of SENT_SHUTDOWN and
- RECEIVED_SHUTDOWN
- """
- self.__ssl_conn.set_shutdown(mode)
- def get_shutdown(self):
- """Get the shutdown state of the Connection.
- @return: bit vector of either or both of SENT_SHUTDOWN and
- RECEIVED_SHUTDOWN
- """
- return self.__ssl_conn.get_shutdown()
- def bind(self, addr):
- """bind to the given address - calls method of the underlying socket
- @param addr: address/port number tuple
- @type addr: tuple"""
- self.__ssl_conn.bind(addr)
- def listen(self, backlog):
- """Listen for connections made to the socket.
- @param backlog: specifies the maximum number of queued connections and
- should be at least 1; the maximum value is system-dependent (usually 5).
- @param backlog: int
- """
- self.__ssl_conn.listen(backlog)
- def set_accept_state(self):
- """Set the connection to work in server mode. The handshake will be
- handled automatically by read/write"""
- self.__ssl_conn.set_accept_state()
- def accept(self):
- """Accept an SSL connection.
- @return: pair (ssl, addr) where ssl is a new SSL connection object and
- addr is the address bound to the other end of the SSL connection.
- @rtype: tuple
- """
- return self.__ssl_conn.accept()
- def set_connect_state(self):
- """Set the connection to work in client mode. The handshake will be
- handled automatically by read/write"""
- self.__ssl_conn.set_connect_state()
- def connect(self, addr):
- """Call the connect method of the underlying socket and set up SSL on
- the socket, using the Context object supplied to this Connection object
- at creation.
- @param addr: address/port number pair
- @type addr: tuple
- """
- self.__ssl_conn.connect(addr)
- def shutdown(self, how):
- """Send the shutdown message to the Connection.
- @param how: for socket.socket this flag determines whether read, write
- or both type operations are supported. OpenSSL.SSL.Connection doesn't
- support this so this parameter is IGNORED
- @return: true if the shutdown message exchange is completed and false
- otherwise (in which case you call recv() or send() when the connection
- becomes readable/writeable.
- @rtype: bool
- """
- return self.__ssl_conn.shutdown()
- def renegotiate(self):
- """Renegotiate this connection's SSL parameters."""
- return self.__ssl_conn.renegotiate()
- def pending(self):
- """@return: numbers of bytes that can be safely read from the SSL
- buffer.
- @rtype: int
- """
- return self.__ssl_conn.pending()
- def send(self, data, *flags_arg):
- """Send data to the socket. Nb. The optional flags argument is ignored.
- - retained for compatibility with socket.socket interface
- @param data: data to send down the socket
- @type data: string
- """
- return self.__ssl_conn.send(data)
- def sendall(self, data):
- self.__ssl_conn.sendall(data)
- def recv(self, size=default_buf_size):
- """Receive data from the Connection.
- @param size: The maximum amount of data to be received at once
- @type size: int
- @return: data received.
- @rtype: string
- """
- return self.__ssl_conn.recv(size)
- def setblocking(self, mode):
- """Set this connection's underlying socket blocking _mode_.
- @param mode: blocking mode
- @type mode: int
- """
- self.__ssl_conn.setblocking(mode)
- def fileno(self):
- """
- @return: file descriptor number for the underlying socket
- @rtype: int
- """
- return self.__ssl_conn.fileno()
- def getsockopt(self, *args):
- """See socket.socket.getsockopt
- """
- return self.__ssl_conn.getsockopt(*args)
- def setsockopt(self, *args):
- """See socket.socket.setsockopt
- @return: value of the given socket option
- @rtype: int/string
- """
- return self.__ssl_conn.setsockopt(*args)
- def state_string(self):
- """Return the SSL state of this connection."""
- return self.__ssl_conn.state_string()
- def makefile(self, *args):
- """Specific to Python socket API and required by httplib: convert
- response into a file-like object. This implementation reads using recv
- and copies the output into a StringIO buffer to simulate a file object
- for consumption by httplib
- Nb. Ignoring optional file open mode (StringIO is generic and will
- open for read and write unless a string is passed to the constructor)
- and buffer size - httplib set a zero buffer size which results in recv
- reading nothing
- @return: file object for data returned from socket
- @rtype: cStringIO.StringO
- """
- # Optimisation
- _buf_size = self.buf_size
- i=0
- stream = StringIO()
- startTime = datetime.utcnow()
- try:
- dat = self.__ssl_conn.recv(_buf_size)
- while dat:
- i+=1
- stream.write(dat)
- dat = self.__ssl_conn.recv(_buf_size)
- except (SSL.ZeroReturnError, SSL.SysCallError):
- # Connection is closed - assuming here that all is well and full
- # response has been received. httplib will catch an error in
- # incomplete content since it checks the content-length header
- # against the actual length of data received
- pass
- if log.getEffectiveLevel() <= logging.DEBUG:
- log.debug("Socket.makefile %d recv calls completed in %s", i,
- datetime.utcnow() - startTime)
- # Make sure to rewind the buffer otherwise consumers of the content will
- # read from the end of the buffer
- stream.seek(0)
- return stream
- def getsockname(self):
- """
- @return: the socket's own address
- @rtype:
- """
- return self.__ssl_conn.getsockname()
- def getpeername(self):
- """
- @return: remote address to which the socket is connected
- """
- return self.__ssl_conn.getpeername()
- def get_context(self):
- '''Retrieve the Context object associated with this Connection. '''
- return self.__ssl_conn.get_context()
- def get_peer_certificate(self):
- '''Retrieve the other side's certificate (if any) '''
- return self.__ssl_conn.get_peer_certificate()
|