You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
4.0 KiB
113 lines
4.0 KiB
"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
|
|
|
|
The ssl module must be importable for SSL functionality.
|
|
|
|
To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
|
|
``BuiltinSSLAdapter``.
|
|
"""
|
|
|
|
try:
|
|
import ssl
|
|
except ImportError:
|
|
ssl = None
|
|
|
|
try:
|
|
from _pyio import DEFAULT_BUFFER_SIZE
|
|
except ImportError:
|
|
try:
|
|
from io import DEFAULT_BUFFER_SIZE
|
|
except ImportError:
|
|
DEFAULT_BUFFER_SIZE = -1
|
|
|
|
import sys
|
|
|
|
from cherrypy import wsgiserver
|
|
|
|
|
|
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|
|
|
"""A wrapper for integrating Python's builtin ssl module with CherryPy."""
|
|
|
|
certificate = None
|
|
"""The filename of the server SSL certificate."""
|
|
|
|
private_key = None
|
|
"""The filename of the server's private key file."""
|
|
|
|
certificate_chain = None
|
|
"""The filename of the certificate chain file."""
|
|
|
|
"""The ssl.SSLContext that will be used to wrap sockets where available
|
|
(on Python > 2.7.9 / 3.3)
|
|
"""
|
|
context = None
|
|
|
|
def __init__(self, certificate, private_key, certificate_chain=None):
|
|
if ssl is None:
|
|
raise ImportError('You must install the ssl module to use HTTPS.')
|
|
self.certificate = certificate
|
|
self.private_key = private_key
|
|
self.certificate_chain = certificate_chain
|
|
if hasattr(ssl, 'create_default_context'):
|
|
self.context = ssl.create_default_context(
|
|
purpose=ssl.Purpose.CLIENT_AUTH,
|
|
cafile=certificate_chain
|
|
)
|
|
self.context.load_cert_chain(certificate, private_key)
|
|
|
|
def bind(self, sock):
|
|
"""Wrap and return the given socket."""
|
|
return sock
|
|
|
|
def wrap(self, sock):
|
|
"""Wrap and return the given socket, plus WSGI environ entries."""
|
|
try:
|
|
if self.context is not None:
|
|
s = self.context.wrap_socket(sock,do_handshake_on_connect=True,
|
|
server_side=True)
|
|
else:
|
|
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
|
server_side=True, certfile=self.certificate,
|
|
keyfile=self.private_key,
|
|
ssl_version=ssl.PROTOCOL_SSLv23,
|
|
ca_certs=self.certificate_chain)
|
|
except ssl.SSLError:
|
|
e = sys.exc_info()[1]
|
|
if e.errno == ssl.SSL_ERROR_EOF:
|
|
# This is almost certainly due to the cherrypy engine
|
|
# 'pinging' the socket to assert it's connectable;
|
|
# the 'ping' isn't SSL.
|
|
return None, {}
|
|
elif e.errno == ssl.SSL_ERROR_SSL:
|
|
if 'http request' in e.args[1]:
|
|
# The client is speaking HTTP to an HTTPS server.
|
|
raise wsgiserver.NoSSLError
|
|
elif 'unknown protocol' in e.args[1]:
|
|
# The client is speaking some non-HTTP protocol.
|
|
# Drop the conn.
|
|
return None, {}
|
|
elif 'handshake operation timed out' in e.args[0]:
|
|
# This error is thrown by builtin SSL after a timeout
|
|
# when client is speaking HTTP to an HTTPS server.
|
|
# The connection can safely be dropped.
|
|
return None, {}
|
|
raise
|
|
return s, self.get_environ(s)
|
|
|
|
# TODO: fill this out more with mod ssl env
|
|
def get_environ(self, sock):
|
|
"""Create WSGI environ entries to be merged into each request."""
|
|
cipher = sock.cipher()
|
|
ssl_environ = {
|
|
'wsgi.url_scheme': 'https',
|
|
'HTTPS': 'on',
|
|
'SSL_PROTOCOL': cipher[1],
|
|
'SSL_CIPHER': cipher[0]
|
|
# SSL_VERSION_INTERFACE string The mod_ssl program version
|
|
# SSL_VERSION_LIBRARY string The OpenSSL program version
|
|
}
|
|
return ssl_environ
|
|
|
|
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
|
return wsgiserver.CP_makefile(sock, mode, bufsize)
|