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 = [] 
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                   
405                   
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()  
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   
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   
446           
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               
457              traceback.print_exc() 
  458