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 +