Package nbxmpp :: Module tls_nb
[hide private]
[frames] | no frames]

Source Code for Module nbxmpp.tls_nb

  1  ##   tls_nb.py 
  2  ##       based on transports_nb.py 
  3  ## 
  4  ##   Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov 
  5  ##       modified by Dimitur Kirov <dkirov@gmail.com> 
  6  ##       modified by Tomas Karasek <tom.to.the.k@gmail.com> 
  7  ## 
  8  ##   This program is free software; you can redistribute it and/or modify 
  9  ##   it under the terms of the GNU General Public License as published by 
 10  ##   the Free Software Foundation; either version 2, or (at your option) 
 11  ##   any later version. 
 12  ## 
 13  ##   This program is distributed in the hope that it will be useful, 
 14  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  ##   GNU General Public License for more details. 
 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      #raise ImportError("Manually disabled PyOpenSSL") 
 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      # FIXME: Remove these prints before release, replace with a warning dialog. 
 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
56 -class SSLWrapper:
57 """ 58 Abstract SSLWrapper base class 59 """ 60
61 - class Error(IOError):
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
107 - def __str__(self):
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
123 - def __init__(self, sslobj, sock=None):
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
147 -class PyOpenSSLWrapper(SSLWrapper):
148 """ 149 Wrapper class for PyOpenSSL's recv() and send() methods 150 """ 151
152 - def __init__(self, *args):
153 self.parent = SSLWrapper 154 self.parent.__init__(self, *args)
155
156 - def is_numtoolarge(self, e):
157 ''' Magic methods don't need documentation ''' 158 t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') 159 return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and 160 isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and 161 e.args[0][0] == e.args[0][1] == t)
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 # end-of-connection raises ZeroReturnError instead of having the 178 # connection's .recv() method return a zero-sized result. 179 raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) 180 except OpenSSL.SSL.Error, e: 181 if self.is_numtoolarge(e): 182 # warn, but ignore this exception 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 #log.debug("Send: " + repr(e)) 197 time.sleep(0.1) # prevent 100% CPU usage 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 # warn, but ignore this exception 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
212 -class StdlibSSLWrapper(SSLWrapper):
213 """ 214 Wrapper class for Python socket.ssl read() and write() methods 215 """ 216
217 - def __init__(self, *args):
218 self.parent = SSLWrapper 219 self.parent.__init__(self, *args)
220
221 - def recv(self, bufsize, flags=None):
222 # we simply ignore flags since ssl object doesn't support it 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 # we simply ignore flags since ssl object doesn't support it 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
242 -class NonBlockingTLS(PlugIn):
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
250 - def __init__(self, cacerts, mycerts):
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 # from ssl.h (partial extract) 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
266 - def plugin(self, owner):
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
291 - def _dumpX509Name(self, name, stream=sys.stderr):
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
301 - def _startSSL(self):
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
318 - def _load_cert_file(self, cert_path, cert_store, logg=True):
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
349 - def _startSSL_pyOpenSSL(self):
350 log.debug("_startSSL_pyOpenSSL called") 351 tcpsock = self._owner 352 # NonBlockingHTTPBOSH instance has no attribute _owner 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 # FIXME make a checkbox for Client Cert / SSLv23 / TLSv1 357 # If we are going to use a client cert/key pair for authentication, 358 # we choose TLSv1 method. 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 # See http://docs.python.org/dev/library/ssl.html 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 # py-OpenSSL < 0.9 or old OpenSSL 393 flags |= 16384 394 tcpsock._sslContext.set_options(flags) 395 396 tcpsock.ssl_errnum = [] 397 tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, 398 self._ssl_verify_callback) 399 store = tcpsock._sslContext.get_cert_store() 400 self._load_cert_file(self.cacerts, store) 401 self._load_cert_file(self.mycerts, store) 402 if os.path.isdir('/etc/ssl/certs'): 403 for f in os.listdir('/etc/ssl/certs'): 404 # We don't logg because there is a lot a duplicated certs in this 405 # folder 406 self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, 407 logg=False) 408 409 tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, 410 tcpsock._sock) 411 tcpsock._sslObj.set_connect_state() # set to client mode 412 wrapper = PyOpenSSLWrapper(tcpsock._sslObj) 413 tcpsock._recv = wrapper.recv 414 tcpsock._send = wrapper.send 415 416 log.debug("Initiating handshake...") 417 try: 418 tcpsock._sslObj.do_handshake() 419 except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: 420 pass 421 except: 422 log.error('Error while TLS handshake: ', exc_info=True) 423 return False 424 self._owner.ssl_lib = PYOPENSSL 425 return True
426
427 - def _startSSL_stdlib(self):
428 log.debug("_startSSL_stdlib called") 429 tcpsock=self._owner 430 try: 431 tcpsock._sock.setblocking(True) 432 tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) 433 tcpsock._sock.setblocking(False) 434 tcpsock._sslIssuer = tcpsock._sslObj.issuer() 435 tcpsock._sslServer = tcpsock._sslObj.server() 436 wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) 437 tcpsock._recv = wrapper.recv 438 tcpsock._send = wrapper.send 439 except: 440 log.error("Exception caught in _startSSL_stdlib:", exc_info=True) 441 return False 442 self._owner.ssl_lib = PYSTDLIB 443 return True
444
445 - def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
446 # Exceptions can't propagate up through this callback, so print them here. 447 try: 448 self._owner.ssl_fingerprint_sha1.append(cert.digest('sha1')) 449 self._owner.ssl_certificate.append(cert) 450 self._owner.ssl_errnum.append(errnum) 451 self._owner.ssl_cert_pem.append(OpenSSL.crypto.dump_certificate( 452 OpenSSL.crypto.FILETYPE_PEM, cert)) 453 return True 454 except: 455 log.error("Exception caught in _ssl_info_callback:", exc_info=True) 456 # Make sure something is printed, even if log is disabled. 457 traceback.print_exc()
458