1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import socket
19 from plugin import PlugIn
20
21 import sys
22 import os
23 import time
24
25 import traceback
26
27 import logging
28 log = logging.getLogger('nbxmpp.tls_nb')
29
30 USE_PYOPENSSL = False
31
32 PYOPENSSL = 'PYOPENSSL'
33 PYSTDLIB = 'PYSTDLIB'
34
35 try:
36
37 import OpenSSL.SSL
38 import OpenSSL.crypto
39 USE_PYOPENSSL = True
40 log.info("PyOpenSSL loaded")
41 except ImportError:
42 log.debug("Import of PyOpenSSL failed:", exc_info=True)
43
44
45 print >> sys.stderr, "=" * 79
46 print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
47 print >> sys.stderr, "=" * 79
48
49 -def gattr(obj, attr, default=None):
50 try:
51 return getattr(obj, attr)
52 except AttributeError:
53 return default
54
55
57 """
58 Abstract SSLWrapper base class
59 """
60
62 """
63 Generic SSL Error Wrapper
64 """
65
66 - def __init__(self, sock=None, exc=None, errno=None, strerror=None,
67 peer=None):
68 self.parent = IOError
69
70 errno = errno or gattr(exc, 'errno') or exc[0]
71 strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
72 if not isinstance(strerror, basestring):
73 strerror = repr(strerror)
74
75 self.sock = sock
76 self.exc = exc
77 self.peer = peer
78 self.exc_name = None
79 self.exc_args = None
80 self.exc_str = None
81 self.exc_repr = None
82
83 if self.exc is not None:
84 self.exc_name = str(self.exc.__class__)
85 self.exc_args = gattr(self.exc, 'args')
86 self.exc_str = str(self.exc)
87 self.exc_repr = repr(self.exc)
88 if not errno:
89 try:
90 if isinstance(exc, OpenSSL.SSL.SysCallError):
91 if self.exc_args[0] > 0:
92 errno = self.exc_args[0]
93 strerror = self.exc_args[1]
94 except: pass
95
96 self.parent.__init__(self, errno, strerror)
97
98 if self.peer is None and sock is not None:
99 try:
100 ppeer = self.sock.getpeername()
101 if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
102 and isinstance(ppeer[1], int):
103 self.peer = ppeer
104 except:
105 pass
106
108 s = str(self.__class__)
109 if self.peer:
110 s += " for %s:%d" % self.peer
111 if self.errno is not None:
112 s += ": [Errno: %d]" % self.errno
113 if self.strerror:
114 s += " (%s)" % self.strerror
115 if self.exc_name:
116 s += ", Caused by %s" % self.exc_name
117 if self.exc_str:
118 if self.strerror:
119 s += "(%s)" % self.exc_str
120 else: s += "(%s)" % str(self.exc_args)
121 return s
122
124 self.sslobj = sslobj
125 self.sock = sock
126 log.debug("%s.__init__ called with %s", self.__class__, sslobj)
127
128 - def recv(self, data, flags=None):
129 """
130 Receive wrapper for SSL object
131
132 We can return None out of this function to signal that no data is
133 available right now. Better than an exception, which differs
134 depending on which SSL lib we're using. Unfortunately returning ''
135 can indicate that the socket has been closed, so to be sure, we avoid
136 this by returning None.
137 """
138 raise NotImplementedError
139
140 - def send(self, data, flags=None, now=False):
141 """
142 Send wrapper for SSL object
143 """
144 raise NotImplementedError
145
146
148 """
149 Wrapper class for PyOpenSSL's recv() and send() methods
150 """
151
155
162
163 - def recv(self, bufsize, flags=None):
164 retval = None
165 try:
166 if flags is None:
167 retval = self.sslobj.recv(bufsize)
168 else:
169 retval = self.sslobj.recv(bufsize, flags)
170 except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
171 log.debug("Recv: Want-error: " + repr(e))
172 except OpenSSL.SSL.SysCallError, e:
173 log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e),
174 exc_info=True)
175 raise SSLWrapper.Error(self.sock or self.sslobj, e)
176 except OpenSSL.SSL.ZeroReturnError, e:
177
178
179 raise SSLWrapper.Error(self.sock or self.sslobj, e, -1)
180 except OpenSSL.SSL.Error, e:
181 if self.is_numtoolarge(e):
182
183 log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
184 else:
185 log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
186 raise SSLWrapper.Error(self.sock or self.sslobj, e)
187 return retval
188
189 - def send(self, data, flags=None, now=False):
190 try:
191 if flags is None:
192 return self.sslobj.send(data)
193 else:
194 return self.sslobj.send(data, flags)
195 except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
196
197 time.sleep(0.1)
198 except OpenSSL.SSL.SysCallError, e:
199 log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e),
200 exc_info=True)
201 raise SSLWrapper.Error(self.sock or self.sslobj, e)
202 except OpenSSL.SSL.Error, e:
203 if self.is_numtoolarge(e):
204
205 log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
206 else:
207 log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
208 raise SSLWrapper.Error(self.sock or self.sslobj, e)
209 return 0
210
211
213 """
214 Wrapper class for Python socket.ssl read() and write() methods
215 """
216
220
221 - def recv(self, bufsize, flags=None):
222
223 try:
224 return self.sslobj.read(bufsize)
225 except socket.sslerror, e:
226 log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True)
227 if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
228 raise SSLWrapper.Error(self.sock or self.sslobj, e)
229 return None
230
231 - def send(self, data, flags=None, now=False):
232
233 try:
234 return self.sslobj.write(data)
235 except socket.sslerror, e:
236 log.debug("Send: Caught socket.sslerror:", exc_info=True)
237 if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
238 raise SSLWrapper.Error(self.sock or self.sslobj, e)
239 return 0
240
241
243 """
244 TLS connection used to encrypts already estabilished tcp connection
245
246 Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or
247 PyOpenSSLWrapper.
248 """
249
251 """
252 :param cacerts: path to pem file with certificates of known XMPP servers
253 :param mycerts: path to pem file with certificates of user trusted servers
254 """
255 PlugIn.__init__(self)
256 self.cacerts = cacerts
257 self.mycerts = mycerts
258
259
260 ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
261 "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
262 "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
263 "SSL_CB_ALERT": 0x4000,
264 "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
265
267 """
268 Use to PlugIn TLS into transport and start establishing immediately.
269 Returns True if TLS/SSL was established correctly, otherwise False
270 """
271 log.info('Starting TLS estabilishing')
272 try:
273 res = self._startSSL()
274 except Exception, e:
275 log.error("PlugIn: while trying _startSSL():", exc_info=True)
276 return False
277 return res
278
279 - def _dumpX509(self, cert, stream=sys.stderr):
280 print >> stream, "Digest (SHA-1):", cert.digest("sha1")
281 print >> stream, "Digest (MD5):", cert.digest("md5")
282 print >> stream, "Serial #:", cert.get_serial_number()
283 print >> stream, "Version:", cert.get_version()
284 print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No")
285 print >> stream, "Subject:"
286 self._dumpX509Name(cert.get_subject(), stream)
287 print >> stream, "Issuer:"
288 self._dumpX509Name(cert.get_issuer(), stream)
289 self._dumpPKey(cert.get_pubkey(), stream)
290
292 print >> stream, "X509Name:", str(name)
293
294 - def _dumpPKey(self, pkey, stream=sys.stderr):
295 typedict = {OpenSSL.crypto.TYPE_RSA: "RSA",
296 OpenSSL.crypto.TYPE_DSA: "DSA"}
297 print >> stream, "PKey bits:", pkey.bits()
298 print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(),
299 "Unknown"), pkey.type())
300
302 """
303 Immediatedly switch socket to TLS mode. Used internally
304 """
305 log.debug("_startSSL called")
306
307 if USE_PYOPENSSL:
308 result = self._startSSL_pyOpenSSL()
309 else:
310 result = self._startSSL_stdlib()
311
312 if result:
313 log.debug('Synchronous handshake completed')
314 return True
315 else:
316 return False
317
319 if not os.path.isfile(cert_path):
320 return
321 try:
322 f = open(cert_path)
323 except IOError, e:
324 log.warning('Unable to open certificate file %s: %s' % \
325 (cert_path, str(e)))
326 return
327 lines = f.readlines()
328 i = 0
329 begin = -1
330 for line in lines:
331 if 'BEGIN CERTIFICATE' in line:
332 begin = i
333 elif 'END CERTIFICATE' in line and begin > -1:
334 cert = ''.join(lines[begin:i+2])
335 try:
336 x509cert = OpenSSL.crypto.load_certificate(
337 OpenSSL.crypto.FILETYPE_PEM, cert)
338 cert_store.add_cert(x509cert)
339 except OpenSSL.crypto.Error, exception_obj:
340 if logg:
341 log.warning('Unable to load a certificate from file %s: %s' %\
342 (cert_path, exception_obj.args[0][0][2]))
343 except:
344 log.warning('Unknown error while loading certificate from file '
345 '%s' % cert_path)
346 begin = -1
347 i += 1
348
350 log.debug("_startSSL_pyOpenSSL called")
351 tcpsock = self._owner
352
353 if hasattr(tcpsock, '_owner') and tcpsock._owner._caller.client_cert \
354 and os.path.exists(tcpsock._owner._caller.client_cert):
355 conn = tcpsock._owner._caller
356
357
358
359 tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
360 log.debug('Using client cert and key from %s' % conn.client_cert)
361 try:
362 p12 = OpenSSL.crypto.load_pkcs12(open(conn.client_cert).read(),
363 conn.client_cert_passphrase)
364 except OpenSSL.crypto.Error, exception_obj:
365 log.warning('Unable to load client pkcs12 certificate from '
366 'file %s: %s ... Is it a valid PKCS12 cert?' % \
367 (conn.client_cert, exception_obj.args))
368 except:
369 log.warning('Unknown error while loading certificate from file '
370 '%s' % conn.client_cert)
371 else:
372 log.info('PKCS12 Client cert loaded OK')
373 try:
374 tcpsock._sslContext.use_certificate(p12.get_certificate())
375 tcpsock._sslContext.use_privatekey(p12.get_privatekey())
376 log.info('p12 cert and key loaded')
377 except OpenSSL.crypto.Error, exception_obj:
378 log.warning('Unable to extract client certificate from '
379 'file %s' % conn.client_cert)
380 except Exception, msg:
381 log.warning('Unknown error extracting client certificate '
382 'from file %s: %s' % (conn.client_cert, msg))
383 else:
384 log.info('client cert and key loaded OK')
385 else:
386
387 tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
388 flags = OpenSSL.SSL.OP_NO_SSLv2
389 try:
390 flags |= OpenSSL.SSL.OP_NO_TICKET
391 except AttributeError, e:
392
393 flags |= 16384
394 tcpsock._sslContext.set_options(flags)
395
396 tcpsock.ssl_errnum = 0
397 tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
398 self._ssl_verify_callback)
399 try:
400 tcpsock._sslContext.load_verify_locations(self.cacerts)
401 except:
402 log.warning('Unable to load SSL certificates from file %s' % \
403 os.path.abspath(self.cacerts))
404 store = tcpsock._sslContext.get_cert_store()
405 self._load_cert_file(self.mycerts, store)
406 if os.path.isdir('/etc/ssl/certs'):
407 for f in os.listdir('/etc/ssl/certs'):
408
409
410 self._load_cert_file(os.path.join('/etc/ssl/certs', f), store,
411 logg=False)
412
413 tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext,
414 tcpsock._sock)
415 tcpsock._sslObj.set_connect_state()
416 wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
417 tcpsock._recv = wrapper.recv
418 tcpsock._send = wrapper.send
419
420 log.debug("Initiating handshake...")
421 tcpsock._sslObj.setblocking(True)
422 try:
423 tcpsock._sslObj.do_handshake()
424 except:
425 log.error('Error while TLS handshake: ', exc_info=True)
426 return False
427 tcpsock._sslObj.setblocking(False)
428 self._owner.ssl_lib = PYOPENSSL
429 return True
430
432 log.debug("_startSSL_stdlib called")
433 tcpsock=self._owner
434 try:
435 tcpsock._sock.setblocking(True)
436 tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
437 tcpsock._sock.setblocking(False)
438 tcpsock._sslIssuer = tcpsock._sslObj.issuer()
439 tcpsock._sslServer = tcpsock._sslObj.server()
440 wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
441 tcpsock._recv = wrapper.recv
442 tcpsock._send = wrapper.send
443 except:
444 log.error("Exception caught in _startSSL_stdlib:", exc_info=True)
445 return False
446 self._owner.ssl_lib = PYSTDLIB
447 return True
448
450
451 try:
452 self._owner.ssl_fingerprint_sha1 = cert.digest('sha1')
453 self._owner.ssl_certificate = cert
454 self._owner.ssl_errnum = errnum
455 self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
456 OpenSSL.crypto.FILETYPE_PEM, cert)
457 return True
458 except:
459 log.error("Exception caught in _ssl_info_callback:", exc_info=True)
460
461 traceback.print_exc()
462