Coding standards and documentation improvements in tls_nb.py

This commit is contained in:
Stephan Erb 2008-12-24 11:10:58 +00:00
parent 5c02a907b4
commit 1e00674505
1 changed files with 97 additions and 77 deletions

View File

@ -17,11 +17,9 @@
import socket
from client import PlugIn
from protocol import *
import sys
import os
import errno
import time
import traceback
@ -48,25 +46,27 @@ except ImportError:
print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
print >> sys.stderr, "=" * 79
def torf(cond, tv, fv):
if cond: return tv
return fv
def gattr(obj, attr, default=None):
try:
return getattr(obj, attr)
except:
except AttributeError:
return default
class SSLWrapper:
'''
Abstract SSLWrapper base class
'''
class Error(IOError):
def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
''' Generic SSL Error Wrapper '''
def __init__(self, sock=None, exc=None, errno=None, strerror=None,
peer=None):
self.parent = IOError
errno = errno or gattr(exc, 'errno')
strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
if not isinstance(strerror, basestring): strerror = repr(strerror)
if not isinstance(strerror, basestring):
strerror = repr(strerror)
self.sock = sock
self.exc = exc
@ -97,17 +97,22 @@ class SSLWrapper:
if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
and isinstance(ppeer[1], int):
self.peer = ppeer
except: pass
except:
pass
def __str__(self):
s = str(self.__class__)
if self.peer: s += " for %s:%d" % self.peer
if self.errno is not None: s += ": [Errno: %d]" % self.errno
if self.strerror: s += " (%s)" % self.strerror
if self.peer:
s += " for %s:%d" % self.peer
if self.errno is not None:
s += ": [Errno: %d]" % self.errno
if self.strerror:
s += " (%s)" % self.strerror
if self.exc_name:
s += ", Caused by %s" % self.exc_name
if self.exc_str:
if self.strerror: s += "(%s)" % self.exc_str
if self.strerror:
s += "(%s)" % self.exc_str
else: s += "(%s)" % str(self.exc_args)
return s
@ -117,18 +122,21 @@ class SSLWrapper:
log.debug("%s.__init__ called with %s", self.__class__, sslobj)
def recv(self, data, flags=None):
''' Receive wrapper for SSL object
'''
Receive wrapper for SSL object
We can return None out of this function to signal that no data is
available right now. Better than an exception, which differs
depending on which SSL lib we're using. Unfortunately returning ''
can indicate that the socket has been closed, so to be sure, we avoid
this by returning None. '''
raise NotImplementedException()
this by returning None.
'''
raise NotImplementedError
def send(self, data, flags=None, now=False):
raise NotImplementedException()
''' Send wrapper for SSL object '''
raise NotImplementedError
class PyOpenSSLWrapper(SSLWrapper):
'''Wrapper class for PyOpenSSL's recv() and send() methods'''
@ -138,21 +146,24 @@ class PyOpenSSLWrapper(SSLWrapper):
self.parent.__init__(self, *args)
def is_numtoolarge(self, e):
''' Magic methods don't need documentation '''
t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
return isinstance(e.args, (list, tuple)) and len(e.args) == 1 and \
isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and \
e.args[0][0] == e.args[0][1] == t
return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and
isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and
e.args[0][0] == e.args[0][1] == t)
def recv(self, bufsize, flags=None):
retval = None
try:
if flags is None: retval = self.sslobj.recv(bufsize)
else: retval = self.sslobj.recv(bufsize, flags)
if flags is None:
retval = self.sslobj.recv(bufsize)
else:
retval = self.sslobj.recv(bufsize, flags)
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
log.debug("Recv: Want-error: " + repr(e))
except OpenSSL.SSL.SysCallError, e:
log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
#traceback.print_exc()
log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e),
exc_info=True)
raise SSLWrapper.Error(self.sock or self.sslobj, e)
except OpenSSL.SSL.Error, e:
if self.is_numtoolarge(e):
@ -160,22 +171,21 @@ class PyOpenSSLWrapper(SSLWrapper):
log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
else:
log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
#traceback.print_exc()
#print "Current Stack:"
#traceback.print_stack()
raise SSLWrapper.Error(self.sock or self.sslobj, e)
return retval
def send(self, data, flags=None, now=False):
try:
if flags is None: return self.sslobj.send(data)
else: return self.sslobj.send(data, flags)
if flags is None:
return self.sslobj.send(data)
else:
return self.sslobj.send(data, flags)
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
#log.debug("Send: " + repr(e))
time.sleep(0.1) # prevent 100% CPU usage
except OpenSSL.SSL.SysCallError, e:
log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
#traceback.print_exc()
log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e),
exc_info=True)
raise SSLWrapper.Error(self.sock or self.sslobj, e)
except OpenSSL.SSL.Error, e:
if self.is_numtoolarge(e):
@ -183,12 +193,10 @@ class PyOpenSSLWrapper(SSLWrapper):
log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
else:
log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
#traceback.print_exc()
#print "Current Stack:"
#traceback.print_stack()
raise SSLWrapper.Error(self.sock or self.sslobj, e)
return 0
class StdlibSSLWrapper(SSLWrapper):
'''Wrapper class for Python socket.ssl read() and write() methods'''
@ -218,7 +226,12 @@ class StdlibSSLWrapper(SSLWrapper):
class NonBlockingTLS(PlugIn):
''' TLS connection used to encrypts already estabilished tcp connection.'''
'''
TLS connection used to encrypts already estabilished tcp connection.
Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or
PyOpenSSLWrapper.
'''
def __init__(self, cacerts, mycerts):
'''
@ -238,7 +251,8 @@ class NonBlockingTLS(PlugIn):
def PlugIn(self, owner):
'''
start using encryption immediately
Use to PlugIn TLS into transport and start establishing immediately
Returns True if TLS/SSL was established correctly, otherwise False.
'''
log.info('Starting TLS estabilishing')
PlugIn.PlugIn(self, owner)
@ -249,14 +263,12 @@ class NonBlockingTLS(PlugIn):
return False
return res
def _dumpX509(self, cert, stream=sys.stderr):
print >> stream, "Digest (SHA-1):", cert.digest("sha1")
print >> stream, "Digest (MD5):", cert.digest("md5")
print >> stream, "Serial #:", cert.get_serial_number()
print >> stream, "Version:", cert.get_version()
print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No")
print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No")
print >> stream, "Subject:"
self._dumpX509Name(cert.get_subject(), stream)
print >> stream, "Issuer:"
@ -267,41 +279,31 @@ class NonBlockingTLS(PlugIn):
print >> stream, "X509Name:", str(name)
def _dumpPKey(self, pkey, stream=sys.stderr):
typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
typedict = {OpenSSL.crypto.TYPE_RSA: "RSA",
OpenSSL.crypto.TYPE_DSA: "DSA"}
print >> stream, "PKey bits:", pkey.bits()
print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(),
"Unknown"), pkey.type())
def _startSSL(self):
''' Immediatedly switch socket to TLS mode. Used internally.'''
log.debug("_startSSL called")
if USE_PYOPENSSL: result = self._startSSL_pyOpenSSL()
else: result = self._startSSL_stdlib()
if USE_PYOPENSSL:
result = self._startSSL_pyOpenSSL()
else:
result = self._startSSL_stdlib()
if result:
log.debug("Synchronous handshake completed")
log.debug('Synchronous handshake completed')
return True
else:
return False
def _startSSL_pyOpenSSL(self):
log.debug("_startSSL_pyOpenSSL called")
tcpsock = self._owner
# FIXME: should method be configurable?
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
tcpsock.ssl_errnum = 0
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
try:
tcpsock._sslContext.load_verify_locations(self.cacerts)
except:
log.warning('Unable to load SSL certificates from file %s' % \
os.path.abspath(self.cacerts))
# load users certs
if os.path.isfile(self.mycerts):
store = tcpsock._sslContext.get_cert_store()
f = open(self.mycerts)
def _load_user_certs(self, cert_path, cert_store):
if not os.path.isfile(cert_path):
return
f = open(cert_path)
lines = f.readlines()
i = 0
begin = -1
@ -311,20 +313,37 @@ class NonBlockingTLS(PlugIn):
elif 'END CERTIFICATE' in line and begin > -1:
cert = ''.join(lines[begin:i+2])
try:
X509cert = OpenSSL.crypto.load_certificate(
x509cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
store.add_cert(X509cert)
cert_store.add_cert(x509cert)
except OpenSSL.crypto.Error, exception_obj:
log.warning('Unable to load a certificate from file %s: %s' %\
(self.mycerts, exception_obj.args[0][0][2]))
except:
log.warning('Unknown error while loading certificate from file %s' %\
self.mycerts)
log.warning('Unknown error while loading certificate from file%s'
% self.mycerts)
begin = -1
i += 1
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
tcpsock._sslObj.set_connect_state() # set to client mode
def _startSSL_pyOpenSSL(self):
log.debug("_startSSL_pyOpenSSL called")
tcpsock = self._owner
# FIXME: should method be configurable?
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
tcpsock.ssl_errnum = 0
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
self._ssl_verify_callback)
try:
tcpsock._sslContext.load_verify_locations(self.cacerts)
except:
log.warning('Unable to load SSL certificates from file %s' % \
os.path.abspath(self.cacerts))
self._load_user_certs(self.mycerts, tcpsock._sslContext.get_cert_store())
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext,
tcpsock._sock)
tcpsock._sslObj.set_connect_state() # set to client mode
wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
tcpsock._recv = wrapper.recv
tcpsock._send = wrapper.send
@ -340,7 +359,6 @@ class NonBlockingTLS(PlugIn):
self._owner.ssl_lib = PYOPENSSL
return True
def _startSSL_stdlib(self):
log.debug("_startSSL_stdlib called")
tcpsock=self._owner
@ -371,5 +389,7 @@ class NonBlockingTLS(PlugIn):
return True
except:
log.error("Exception caught in _ssl_info_callback:", exc_info=True)
traceback.print_exc() # Make sure something is printed, even if log is disabled.
# Make sure something is printed, even if log is disabled.
traceback.print_exc()
# vim: se ts=3: