2-way SSL with CherryPy - ssl

From CherryPy 3.0 and onwards, one-way SSL can be turned on simply by pointing to the server certificate and private key, like this:
import cherrypy
class HelloWorld(object):
def index(self):
return "Hello SSL World!"
index.exposed = True
cherrypy.server.ssl_certificate = "keys/server.crt"
cherrypy.server.ssl_private_key = "keys/server.crtkey"
cherrypy.quickstart(HelloWorld())
This enables clients to validate the server's authenticity. Does anyone know whether CherryPy supports 2-way ssl, e.g. where the server can also check client authenticity by validating a client certificate?
If yes, could anyone give an example how is that done? Or post a reference to an example?

It doesn't out of the box. You'd have to patch the wsgiserver to provide that feature. There is a ticket (and patches) in progress at http://www.cherrypy.org/ticket/1001.

I have been looking for the same thing. I know there are some patches on the CherryPy site.
I also found the following at CherryPy SSL Client Authentication. I haven't compared this vs the CherryPy patches but maybe the info will be helpful.
We recently needed to develop a quick
but resilient REST application and
found that CherryPy suited our needs
better than other Python networking
frameworks, like Twisted.
Unfortunately, its simplicity lacked a
key feature we needed, Server/Client
SSL certificate validation. Therefore
we spent a few hours writing a few
quick modifications to the current
release, 3.1.2. The following code
snippets are the modifications we
made:
cherrypy/_cpserver.py
## -55,7 +55,6 ## instance = None ssl_certificate = None ssl_private_key
= None
+ ssl_ca_certificate = None nodelay = True
def __init__(self):
cherrypy/wsgiserver/__init__.py
## -1480,6 +1480,7 ##
# Paths to certificate and private key files ssl_certificate = None ssl_private_key = None
+ ssl_ca_certificate = None
def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
## -1619,7 +1620,9 ##
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if self.nodelay: self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- if self.ssl_certificate and self.ssl_private_key:
+ if self.ssl_certificate and self.ssl_private_key and \
+ self.ssl_ca_certificate:
+ if SSL is None: raise ImportError("You must install pyOpenSSL to use HTTPS.")
## -1627,6 +1630,11 ## ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file(self.ssl_private_key) ctx.use_certificate_file(self.ssl_certificate)
+ x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
+ open(self.ssl_ca_certificate).read())
+ store = ctx.get_cert_store()
+ store.add_cert(x509)
+ ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, lambda *x:True) self.socket = SSLConnection(ctx, self.socket) self.populate_ssl_environ()
The above patches require the
inclusion of a new configuration
option inside of the CherryPy server
configuration,
server.ssl_ca_certificate. This
option identifies the certificate
authority file that connecting clients
will be validated against, if the
client does not present a valid client
certificate it will close the
connection immediately.
Our solution has advantages and
disadvantages, the primary advantage
being if the connecting client doesn’t
present a valid certificate it’s
connection is immediately closed.
This is good for security concerns as
it does not permit the client any
access into the CherryPy application
stack. However, since the restriction
is done at the socket level the
CherryPy application can never see the
client connecting and hence the
solution is somewhat inflexible.
An optimal solution would allow the
client to connect to the CherryPy
socket and send the client certificate
up into the application stack. Then a
custom CherryPy Tool would validate
the certificate inside of the
application stack and close the
connection if necessary; unfortunately
because of the structure of CherryPy’s
pyOpenSSL implementation it is
difficult to retrieve the client
certificate inside of the application
stack.
Of course the patches above should
only be used at your own risk. If you
come up with a better solution please
let us know.

If the current version of CherryPy does not support client certificate verification, it is possible to configure CherryPy to listen to 127.0.0.1:80, install HAProxy to listen to 443 and verify client side certificates and to forward traffic to 127.0.0.1:80
HAProxy is simple, light, fast and reliable.
An example of HAProxy configuration

Related

WebSocketpp handshake issue with TLS

I have been learning with WebSocket++ and built some of the server examples (Windows 10 Visual Studio 2019). The non-TLS examples work without issues, however, the TLS-enabled examples (echo_server_both.cpp and echo_server_tls.cpp) can't do the handshake. I am very new to web development in general so I know I must be doing something wrong with regards to the certificate and keys.
I am testing the servers with WebSocket King client, an extension of Google Chrome that connects correctly to other websocket servers like wss://echo.websocket.org and to my own localhost when I don't use TLS.
The echo_server_both example comes with a server.pem file, and the echo_server_tls example comes with server.pem and dh.pem. I have used the same files that come with the samples, and I have also tried generating and registering my own .pem files using openSSL. In both cases I get this when the client tries to connect:
[2021-06-29 20:51:21] [error] handle_transport_init received error: sslv3 alert certificate unknown
[2021-06-29 20:51:21] [fail] WebSocket Connection [::1]:63346 - "" - 0 asio.ssl:336151574 sslv3 alert certificate unknown
[2021-06-29 20:51:21] [info] asio async_shutdown error: asio.ssl:336462231 (shutdown while in init)
I discovered these errors after I edited handle_init() in tls.hpp, following a suggestion in another site, to look like this:
void handle_init(init_handler callback,lib::asio::error_code const & ec) {
if (ec) {
//m_ec = socket::make_error_code(socket::error::tls_handshake_failed);
m_ec = ec;
} else {
m_ec = lib::error_code();
}
callback(m_ec);
}
This change let the actual openSSL error to show in the console, otherwise it would show a generic "handshake failed" error.
I know I'm not doing what I should with the certificates, but I have no idea where else to look or what to do next. Can anyone here help please? Should I use the .pem files that come with the examples, or should I generate my own? in case I should generate my own, what would be the openSSL command to do that correctly and how do I tell my PC to recognize these as valid so that the server works?
Found the problem: WebSocket++ will not accept a self-signed certificate (the ones you can create directly in your own PC using OpenSSL or the Windows utilities). There is no way around it. You must have a valid, authority-validated and endorsed certificate. You can get such a certificate for free (valid only for 90 days) from https://zerossl.com/. The site has detailed instructions on how to request, obtain and install a certificate. After getting a valid certificate and installing it on my server, everything worked as it should.

How do I get the SSL client certificate information from a CherryPy handler?

I was able to setup the CherryPy HTTPServer to require an SSL client certificate using the following code:
ssl_certificate = os.environ.get("SSL_CERTIFICATE")
ssl_adapter = BuiltinSSLAdapter(
certificate=ssl_certificate,
private_key=os.environ["SSL_PRIVATE_KEY"],
certificate_chain=os.environ.get("SSL_CERTIFICATE_CHAIN")
)
verify_mode = ssl.CERT_REQUIRED
ssl_adapter.context.verify_mode = verify_mode
HTTPServer.ssl_adapter = ssl_adapter
Now I am trying to get the SSL client certification information from my request handler, but I can't figure how. After reading https://github.com/cherrypy/cheroot/blob/master/cheroot/ssl/builtin.py#L419 it seems that the wsgi environment variables should be populated with SSL_CLIENT* variables. I could not find any method/property from the request objectwould allow me to fetch such information
How can I obtain this variables from a request handler ?
I have learned the answer from a conversation with CherryPy maintainers on Gitter.
An CherryPy request object may contain more attributes than those documented in the API source, such attributes are set dynamically once the request object is created as part of the WSGI handling:
https://github.com/cherrypy/cherrypy/blob/master/cherrypy/_cpwsgi.py#L319
...
request.multithread = self.environ['wsgi.multithread']
request.multiprocess = self.environ['wsgi.multiprocess']
request.wsgi_environ = self.environ
...
Knowing this, to obtain the WSGI environment which includes the SSL* variables, we just need to access it by importing the request object:
import cherrypy.request
...
print(cherrypy.request.wsgi_environ)
...

Python request not attaching client certificate on the HTTPS session

I've implemented a SOAP client in python with the zeep library. Some of the endpoints require client-side certificate authentication, thus the need to attach the certificate to the python requests's session.
After googling around, I've come up with:
from zeep import Client
from zeep.transports import Transport
from django.utils import timezone
import requests
......
session = requests.Session()
session.verify = False
session.cert= ('pat_to_cert.pem','path_to_privKey.pem')
transport = Transport(session=session)
....
client = Client(wsdl=wsdl, transport=transport)
send = getattr(service, service_name)
result = send(**data)
Debbugging the TLS handshake, the server issues a Certificate Request, but the client replies with an empty certificate. I've already checked the .pem files with openssl with no errors.
Is it possible that python's requests is not attaching the certificate because it does not recognize the server name? How can I enforce to use this certificate for every request?
In your code, you are setting session.verify = False that's why TLS verification is disabled.
what you need to do is to set:
session.verify = 'path/to/my/certificate.pem'
Also, Alternatively, instead of using session.verify you can use session.cert if you just want to use an TLS client certificate:
session.verify = True
session.cert = ('my_cert', 'my_key')
please check the documentation for more details: https://docs.python-zeep.org/en/master/transport.html
hope this helps

Opensips Tls and certificates issues

I am trying to setup the certificate verification in opensips along with the blink sip client. I followed the tutorial:
https://github.com/antonraharja/book-opensips-101/blob/master/content/3.2.%20SIP%20TLS%20Secure%20Calling.mediawiki
My config look like so:
[opensips.cfg]
disable_tls = no
listen = tls:my_ip:5061
tls_verify_server= 0
tls_verify_client = 1
tls_require_client_certificate = 1
#tls_method = TLSv1
tls_method = SSLv23
tls_certificate = "/usr/local/etc/opensips/tls/server/server-cert.pem"
tls_private_key = "/usr/local/etc/opensips/tls/server/server-privkey.pem"
tls_ca_list = "/usr/local/etc/opensips/tls/server/server-calist.pem"
So i generated the rootCA and the server certificate. Then i took the server-calist.pem added the server-privkey.pem in there (otherwise blink sip client won't load it) and set it in client. I also set the server-calist.pem as a certificate authority in the blink. But when i try to login to my server i get:
Feb 4 21:02:42 user /usr/local/sbin/opensips[28065]: DBG:core:tcp_read_req: Using the global ( per process ) buff
Feb 4 21:02:42 user /usr/local/sbin/opensips[28065]: DBG:core:tls_update_fd: New fd is 17
Feb 4 21:02:42 user /usr/local/sbin/opensips[28065]: ERROR:core:tls_accept: New TLS connection from 130.85.9.114:48253 failed to accept: rejected by client
So i assume that the client doesn't accept the server certificate for some reason, although i have the "Verify server" checkbox turned off in my blink sip client! I think i have the wrong certificate authority file.
./user/user-cert.pem
./user/user-cert_req.pem
./user/user-privkey.pem
./user/user-calist.pem <- this 4 are for using opensips as a client i think
./rootCA/certs/01.pem
./rootCA/private/cakey.pem
./rootCA/cacert.pem
./server/server-privkey.pem
./server/server-calist.pem
./server/server-cert.pem
./server/server-cert_req.pem
./calist.pem
Can anybody help, did i do something wrong i the config or did i use the wrong certificate chain? What certificate exactly should be used by the client as a client cert, and ca authority cert?
Allright, i'm still not sure if it is working or not, because the authorization behaviour became weird, but after it's hanging for 5-6 minutes i get the success authorization, so this is a solution:
Generate rootCA:
opensipsctl tls rootCA
then edit server.conf file in your tls opensips folder and set the commonName = xxx.xxx.xxx.xxx where xxx.xxx.xxx.xxx is your server ip address. Other variables can be edited in any way. Generate the certificates signed by CA
opensipsctl tls userCERT server
This will produce 4 files. Download the server-calist.pem, server-cert.pem, server-privkey.pem. Open the server-privkey.pem, copy it's content and paste in the file server-cert.pem, before the actual certificate. If you are using blink, the produced server-cert.pem goes in the preferences->account->advanced. And server-calist.pem goes into the preferences->advanced. After that restart blink and after 5-6 minutes your account is gonna be logged in. But i'v observed a weird behaviour, if you run another copy of blink and try to log into the other existing account after your logged from the first one with the certificates, you can log in from other account without providing the certificates. So i don't know, but i think it's working.
P.S. I asked about the certificates in the opensips mailing list, but i guess they found my question too lame, so i didn't get the response. If you have the same problem and got better results or an answer from opensips support let me know please.

Twisted listenSSL virtualhosts

Currently using a really simple Twisted NameVirtualHost coupled with some JSON config files to serve really basic content in one Site object. The resources being served by Twisted are all WSGI objects built in flask.
I was wondering on how to go about wrapping the connections to these domains with an SSLContext, since reactor.listenSSL takes one and only one context, it isn't readily apparent how to give each domain/subdomain it's own crt/key pair. Is there any way to set up named virtual hosting with ssl for each domain that doesn't require proxying? I can't find any Twisted examples that use NameVirtualHost with SSL, and they only thing I could get to work is hook on the reactor listening on port 443 with only one domain's context?
I was wondering if anyone has attempted this?
My simple server without any SSL processing:
https://github.com/DeaconDesperado/twsrv/blob/master/service.py
TLS (the name for the modern protocol which replaces SSL) only very recently supports the feature you're looking for. The feature is called Server Name Indication (or SNI). It is supported by modern browsers on modern platforms, but not some older but still widely used platforms (see the wikipedia page for a list of browsers with support).
Twisted has no specific, built-in support for this. However, it doesn't need any. pyOpenSSL, upon which Twisted's SSL support is based, does support SNI.
The set_tlsext_servername_callback pyOpenSSL API gives you the basic mechanism to build the behavior you want. This lets you define a callback which is given access to the server name requested by the client. At this point, you can specify the key/certificate pair you want to use for the connection. You can find an example demonstrating the use of this API in pyOpenSSL's examples directory.
Here's an excerpt from that example to give you the gist:
def pick_certificate(connection):
try:
key, cert = certificates[connection.get_servername()]
except KeyError:
pass
else:
new_context = Context(TLSv1_METHOD)
new_context.use_privatekey(key)
new_context.use_certificate(cert)
connection.set_context(new_context)
server_context = Context(TLSv1_METHOD)
server_context.set_tlsext_servername_callback(pick_certificate)
You can incorporate this approach into a customized context factory and then supply that context factory to the listenSSL call.
Just to add some closure to this one, and for future searches, here is the example code for the echo server from the examples that prints the SNI:
from twisted.internet import ssl, reactor
from twisted.internet.protocol import Factory, Protocol
class Echo(Protocol):
def dataReceived(self, data):
self.transport.write(data)
def pick_cert(connection):
print('Received SNI: ', connection.get_servername())
if __name__ == '__main__':
factory = Factory()
factory.protocol = Echo
with open("keys/ca.pem") as certAuthCertFile:
certAuthCert = ssl.Certificate.loadPEM(certAuthCertFile.read())
with open("keys/server.key") as keyFile:
with open("keys/server.crt") as certFile:
serverCert = ssl.PrivateCertificate.loadPEM(
keyFile.read() + certFile.read())
contextFactory = serverCert.options(certAuthCert)
ctx = contextFactory.getContext()
ctx.set_tlsext_servername_callback(pick_cert)
reactor.listenSSL(8000, factory, contextFactory)
reactor.run()
And because getting OpenSSL to work can always be tricky, here is the OpenSSL statement you can use to connect to it:
openssl s_client -connect localhost:8000 -servername hello_world -cert keys/client.crt -key keys/client.key
Running the above python code against pyOpenSSL==0.13, and then running the s_client command above, will print this to the screen:
('Received SNI: ', 'hello_world')
There is now a txsni project that takes care of finding the right certificates per request. https://github.com/glyph/txsni