OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_pyopenssl.py
changeset 3 65432e6c6042
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_pyopenssl.py	Mon Dec 02 14:02:05 2013 +0100
     1.3 @@ -0,0 +1,256 @@
     1.4 +"""A library for integrating pyOpenSSL with CherryPy.
     1.5 +
     1.6 +The OpenSSL module must be importable for SSL functionality.
     1.7 +You can obtain it from http://pyopenssl.sourceforge.net/
     1.8 +
     1.9 +To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
    1.10 +SSLAdapter. There are two ways to use SSL:
    1.11 +
    1.12 +Method One
    1.13 +----------
    1.14 +
    1.15 + * ``ssl_adapter.context``: an instance of SSL.Context.
    1.16 +
    1.17 +If this is not None, it is assumed to be an SSL.Context instance,
    1.18 +and will be passed to SSL.Connection on bind(). The developer is
    1.19 +responsible for forming a valid Context object. This approach is
    1.20 +to be preferred for more flexibility, e.g. if the cert and key are
    1.21 +streams instead of files, or need decryption, or SSL.SSLv3_METHOD
    1.22 +is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
    1.23 +the pyOpenSSL documentation for complete options.
    1.24 +
    1.25 +Method Two (shortcut)
    1.26 +---------------------
    1.27 +
    1.28 + * ``ssl_adapter.certificate``: the filename of the server SSL certificate.
    1.29 + * ``ssl_adapter.private_key``: the filename of the server's private key file.
    1.30 +
    1.31 +Both are None by default. If ssl_adapter.context is None, but .private_key
    1.32 +and .certificate are both given and valid, they will be read, and the
    1.33 +context will be automatically created from them.
    1.34 +"""
    1.35 +
    1.36 +import socket
    1.37 +import threading
    1.38 +import time
    1.39 +
    1.40 +from cherrypy import wsgiserver
    1.41 +
    1.42 +try:
    1.43 +    from OpenSSL import SSL
    1.44 +    from OpenSSL import crypto
    1.45 +except ImportError:
    1.46 +    SSL = None
    1.47 +
    1.48 +
    1.49 +class SSL_fileobject(wsgiserver.CP_fileobject):
    1.50 +    """SSL file object attached to a socket object."""
    1.51 +    
    1.52 +    ssl_timeout = 3
    1.53 +    ssl_retry = .01
    1.54 +    
    1.55 +    def _safe_call(self, is_reader, call, *args, **kwargs):
    1.56 +        """Wrap the given call with SSL error-trapping.
    1.57 +        
    1.58 +        is_reader: if False EOF errors will be raised. If True, EOF errors
    1.59 +        will return "" (to emulate normal sockets).
    1.60 +        """
    1.61 +        start = time.time()
    1.62 +        while True:
    1.63 +            try:
    1.64 +                return call(*args, **kwargs)
    1.65 +            except SSL.WantReadError:
    1.66 +                # Sleep and try again. This is dangerous, because it means
    1.67 +                # the rest of the stack has no way of differentiating
    1.68 +                # between a "new handshake" error and "client dropped".
    1.69 +                # Note this isn't an endless loop: there's a timeout below.
    1.70 +                time.sleep(self.ssl_retry)
    1.71 +            except SSL.WantWriteError:
    1.72 +                time.sleep(self.ssl_retry)
    1.73 +            except SSL.SysCallError, e:
    1.74 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
    1.75 +                    return ""
    1.76 +                
    1.77 +                errnum = e.args[0]
    1.78 +                if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
    1.79 +                    return ""
    1.80 +                raise socket.error(errnum)
    1.81 +            except SSL.Error, e:
    1.82 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
    1.83 +                    return ""
    1.84 +                
    1.85 +                thirdarg = None
    1.86 +                try:
    1.87 +                    thirdarg = e.args[0][0][2]
    1.88 +                except IndexError:
    1.89 +                    pass
    1.90 +                
    1.91 +                if thirdarg == 'http request':
    1.92 +                    # The client is talking HTTP to an HTTPS server.
    1.93 +                    raise wsgiserver.NoSSLError()
    1.94 +                
    1.95 +                raise wsgiserver.FatalSSLAlert(*e.args)
    1.96 +            except:
    1.97 +                raise
    1.98 +            
    1.99 +            if time.time() - start > self.ssl_timeout:
   1.100 +                raise socket.timeout("timed out")
   1.101 +    
   1.102 +    def recv(self, *args, **kwargs):
   1.103 +        buf = []
   1.104 +        r = super(SSL_fileobject, self).recv
   1.105 +        while True:
   1.106 +            data = self._safe_call(True, r, *args, **kwargs)
   1.107 +            buf.append(data)
   1.108 +            p = self._sock.pending()
   1.109 +            if not p:
   1.110 +                return "".join(buf)
   1.111 +    
   1.112 +    def sendall(self, *args, **kwargs):
   1.113 +        return self._safe_call(False, super(SSL_fileobject, self).sendall,
   1.114 +                               *args, **kwargs)
   1.115 +
   1.116 +    def send(self, *args, **kwargs):
   1.117 +        return self._safe_call(False, super(SSL_fileobject, self).send,
   1.118 +                               *args, **kwargs)
   1.119 +
   1.120 +
   1.121 +class SSLConnection:
   1.122 +    """A thread-safe wrapper for an SSL.Connection.
   1.123 +    
   1.124 +    ``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
   1.125 +    """
   1.126 +    
   1.127 +    def __init__(self, *args):
   1.128 +        self._ssl_conn = SSL.Connection(*args)
   1.129 +        self._lock = threading.RLock()
   1.130 +    
   1.131 +    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
   1.132 +              'renegotiate', 'bind', 'listen', 'connect', 'accept',
   1.133 +              'setblocking', 'fileno', 'close', 'get_cipher_list',
   1.134 +              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
   1.135 +              'makefile', 'get_app_data', 'set_app_data', 'state_string',
   1.136 +              'sock_shutdown', 'get_peer_certificate', 'want_read',
   1.137 +              'want_write', 'set_connect_state', 'set_accept_state',
   1.138 +              'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
   1.139 +        exec("""def %s(self, *args):
   1.140 +        self._lock.acquire()
   1.141 +        try:
   1.142 +            return self._ssl_conn.%s(*args)
   1.143 +        finally:
   1.144 +            self._lock.release()
   1.145 +""" % (f, f))
   1.146 +    
   1.147 +    def shutdown(self, *args):
   1.148 +        self._lock.acquire()
   1.149 +        try:
   1.150 +            # pyOpenSSL.socket.shutdown takes no args
   1.151 +            return self._ssl_conn.shutdown()
   1.152 +        finally:
   1.153 +            self._lock.release()
   1.154 +
   1.155 +
   1.156 +class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
   1.157 +    """A wrapper for integrating pyOpenSSL with CherryPy."""
   1.158 +    
   1.159 +    context = None
   1.160 +    """An instance of SSL.Context."""
   1.161 +    
   1.162 +    certificate = None
   1.163 +    """The filename of the server SSL certificate."""
   1.164 +    
   1.165 +    private_key = None
   1.166 +    """The filename of the server's private key file."""
   1.167 +    
   1.168 +    certificate_chain = None
   1.169 +    """Optional. The filename of CA's intermediate certificate bundle.
   1.170 +    
   1.171 +    This is needed for cheaper "chained root" SSL certificates, and should be
   1.172 +    left as None if not required."""
   1.173 +    
   1.174 +    def __init__(self, certificate, private_key, certificate_chain=None):
   1.175 +        if SSL is None:
   1.176 +            raise ImportError("You must install pyOpenSSL to use HTTPS.")
   1.177 +        
   1.178 +        self.context = None
   1.179 +        self.certificate = certificate
   1.180 +        self.private_key = private_key
   1.181 +        self.certificate_chain = certificate_chain
   1.182 +        self._environ = None
   1.183 +    
   1.184 +    def bind(self, sock):
   1.185 +        """Wrap and return the given socket."""
   1.186 +        if self.context is None:
   1.187 +            self.context = self.get_context()
   1.188 +        conn = SSLConnection(self.context, sock)
   1.189 +        self._environ = self.get_environ()
   1.190 +        return conn
   1.191 +    
   1.192 +    def wrap(self, sock):
   1.193 +        """Wrap and return the given socket, plus WSGI environ entries."""
   1.194 +        return sock, self._environ.copy()
   1.195 +    
   1.196 +    def get_context(self):
   1.197 +        """Return an SSL.Context from self attributes."""
   1.198 +        # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
   1.199 +        c = SSL.Context(SSL.SSLv23_METHOD)
   1.200 +        c.use_privatekey_file(self.private_key)
   1.201 +        if self.certificate_chain:
   1.202 +            c.load_verify_locations(self.certificate_chain)
   1.203 +        c.use_certificate_file(self.certificate)
   1.204 +        return c
   1.205 +    
   1.206 +    def get_environ(self):
   1.207 +        """Return WSGI environ entries to be merged into each request."""
   1.208 +        ssl_environ = {
   1.209 +            "HTTPS": "on",
   1.210 +            # pyOpenSSL doesn't provide access to any of these AFAICT
   1.211 +##            'SSL_PROTOCOL': 'SSLv2',
   1.212 +##            SSL_CIPHER 	string 	The cipher specification name
   1.213 +##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
   1.214 +##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
   1.215 +            }
   1.216 +        
   1.217 +        if self.certificate:
   1.218 +            # Server certificate attributes
   1.219 +            cert = open(self.certificate, 'rb').read()
   1.220 +            cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
   1.221 +            ssl_environ.update({
   1.222 +                'SSL_SERVER_M_VERSION': cert.get_version(),
   1.223 +                'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
   1.224 +##                'SSL_SERVER_V_START': Validity of server's certificate (start time),
   1.225 +##                'SSL_SERVER_V_END': Validity of server's certificate (end time),
   1.226 +                })
   1.227 +            
   1.228 +            for prefix, dn in [("I", cert.get_issuer()),
   1.229 +                               ("S", cert.get_subject())]:
   1.230 +                # X509Name objects don't seem to have a way to get the
   1.231 +                # complete DN string. Use str() and slice it instead,
   1.232 +                # because str(dn) == "<X509Name object '/C=US/ST=...'>"
   1.233 +                dnstr = str(dn)[18:-2]
   1.234 +                
   1.235 +                wsgikey = 'SSL_SERVER_%s_DN' % prefix
   1.236 +                ssl_environ[wsgikey] = dnstr
   1.237 +                
   1.238 +                # The DN should be of the form: /k1=v1/k2=v2, but we must allow
   1.239 +                # for any value to contain slashes itself (in a URL).
   1.240 +                while dnstr:
   1.241 +                    pos = dnstr.rfind("=")
   1.242 +                    dnstr, value = dnstr[:pos], dnstr[pos + 1:]
   1.243 +                    pos = dnstr.rfind("/")
   1.244 +                    dnstr, key = dnstr[:pos], dnstr[pos + 1:]
   1.245 +                    if key and value:
   1.246 +                        wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
   1.247 +                        ssl_environ[wsgikey] = value
   1.248 +        
   1.249 +        return ssl_environ
   1.250 +    
   1.251 +    def makefile(self, sock, mode='r', bufsize=-1):
   1.252 +        if SSL and isinstance(sock, SSL.ConnectionType):
   1.253 +            timeout = sock.gettimeout()
   1.254 +            f = SSL_fileobject(sock, mode, bufsize)
   1.255 +            f.ssl_timeout = timeout
   1.256 +            return f
   1.257 +        else:
   1.258 +            return wsgiserver.CP_fileobject(sock, mode, bufsize)
   1.259 +