SSL/HTTPS – Grade A+ python SimpleHTTPServer

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

Leave a Reply

Your email address will not be published. Required fields are marked *