After adding SSL to my HTTPWebSocket server, I found that originally it was not so secure due to the fact the the linux distribution I used did not get essential security updates. I used Qualsys SSL Labs: https://www.ssllabs.com/ssltest/analyze.html to analyze the security level of my server. Well, it started out with ‘grade E’. Finally, I ended up with ‘grade A+’. These are the steps to follow:
OpenSSL
Use recent versions of SSL and Python. required is:
openSSL >= 1.0.1j
Python >= 2.7.9
Normally this requirement is met for recent linux distributions. If not, here is how to build them from source.
Now we are at a grade A or B, depending on the certificate setup. Although not exactly with a 100% score.
Certificates
When using a certificate from an official CA, then some work must be done compared to other webservers. Python can only handle one certificate file, so the three or four different files (private key, server certificate, CA intermediate certificate and CA root certificate) need to be concatenated:
cat my_site.crt intermediate-ca.crt root-ca.crt > server.pem
Protocols and Ciphers
Now we are at ‘grade A’, but not with perfect scores yet. That is for two reasons: By default TLSv1 is still enabled, and some weaker ciphers can still be used. To fix this, the ssl_wrapping of the socket connection in the server needs to be configured. The standard SSL wrapping setup in python like this
server = ThreadedHTTPServer(('', port), SimpleHTTPServer) server.socket = ssl.wrap_socket (server.socket, certfile='./server.pem', server_side=True)
Limiting to TLSv1.2 already improves the protocols to 100%, but leaves the ciphers at 90%:
server = ThreadedHTTPServer(('', port), SimpleHTTPServer) server.socket = ssl.wrap_socket (server.socket, certfile='./server.pem', server_side=True, ssl_version=ssl.PROTOCOL_TLSv1_2)
Achieving 100% score requires to limit to both TLSv1.2, and ciphers with >=256 bits encryption. Configuration is a bit more elaborate and requires the definition of a ssl context (ssl.SSLContext
). Only 3 ciphers are need:
server = ThreadedHTTPServer(('', port), SimpleHTTPServer) ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ctx.load_cert_chain(certfile="./server.pem") ctx.options |= ssl.OP_NO_TLSv1 ctx.options |= ssl.OP_NO_TLSv1_1 ctx.options |= ssl.OP_CIPHER_SERVER_PREFERENCE ctx.set_ciphers('ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA') server.socket = ctx.wrap_socket(server.socket, server_side=True)
Now we are at a ‘grade A’ server with 100% scores.
bonus: grade A+
To achieve ‘grade A+’, all webserver responses to request need to have an additional header line to configure the life time of a renegotiation request for HTTP Strict Transport Security (HSTS). This can be achieved by a bit of a trick. It requires one to specialize SimpleHTTPRequestHandler
and override the end_headers()
method:
from SimpleHTTPServer import SimpleHTTPRequestHandler import ssl class GradeAplusSSLHandler(SimpleHTTPRequestHandler): def end_headers(self): self.send_header("Strict-Transport-Security", "max-age=63072000; includeSubDomains") SimpleHTTPRequestHandler.end_headers(self)
I real-life working example can be found in the web application Plugwise-2-web.py
in the Plugwise-2-py repository:
https://github.com/SevenW/Plugwise-2-py/blob/master/Plugwise-2-web.py