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