In the last post, I described how to create a keypair and self-signed certificate. Now, we need to:

  1. use them to encrypt our traffic and
  2. allow the user to install the cert in his or her browser.

Here’s how to do this using Twisted. This code creates a page at 8080 that will download the certificate to the user’s browser and a site at 8081 that uses HTTPS. Connect to http://HOSTNAME:8080 to get the cert (your browser will prompt you to install it). Then, you can connect to https://HOSTNAME:8081 and browse securely.

Note that HOSTNAME can’t be ‘localhost’. Not much you can do here – certificates are tied to the actual hostname you use.

This code assumes that the code from that last post is in a module named pki.py, btw.


from twisted.web import server, resource, http
from twisted.internet import reactor, ssl
from twisted.python import log
import sys
from OpenSSL import SSL
from pki import KEY_FILE, CERT_FILE, create_self_signed_cert

def make_ssl_context():
    create_self_signed_cert(".")
    context = ssl.DefaultOpenSSLContextFactory(KEY_FILE, CERT_FILE)
    return context

class HelloWorldPage(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return "<html><body><h1>Hello World</h1></body></html>"

class CertPage(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        request.setHeader("Content-Type", "application/x-x509-ca-cert")
        cert = open(CERT_FILE, 'rb').read()
        request.write(cert)
        request.finish()
        return server.NOT_DONE_YET

log.startLogging(sys.stdout)
context = make_ssl_context()
cert_site = server.Site(CertPage())
site = server.Site(HelloWorldPage())
reactor.listenTCP(8080, cert_site)
reactor.listenSSL(8081, site, contextFactory = context)
reactor.run()

If you’ve got an embedded web server (I’m currently writing one that will be used to configure an application), and you don’t want to pass things in the clear, you’ll need to configure your web sever to use HTTPS. But for an embedded application, that can be tricky – https expects the hostname to be signed in the certificate, but you probably don’t know it until after your app is installed.

Here’s how you can create a self-signed certificate programatically in python.  You could just run this on application start-up.  Not that first you’ll need to install pyOpenSSL (if you have easy_install set up, it’s as easy as “easy_install pyopenssl”):


from OpenSSL import crypto, SSL
from socket import gethostname
from pprint import pprint
from time import gmtime, mktime
from os.path import exists, join

CERT_FILE = "myapp.crt"
KEY_FILE = "myapp.key"

def create_self_signed_cert(cert_dir):
    """
    If datacard.crt and datacard.key don't exist in cert_dir, create a new
    self-signed cert and keypair and write them into that directory.
    """

    if not exists(join(cert_dir, CERT_FILE)) \
            or not exists(join(cert_dir, KEY_FILE)):
            
        # create a key pair
        k = crypto.PKey()
        k.generate_key(crypto.TYPE_RSA, 1024)

        # create a self-signed cert
        cert = crypto.X509()
        cert.get_subject().C = "US"
        cert.get_subject().ST = "Minnesota"
        cert.get_subject().L = "Minnetonka"
        cert.get_subject().O = "my company"
        cert.get_subject().OU = "my organization"
        cert.get_subject().CN = gethostname()
        cert.set_serial_number(1000)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10*365*24*60*60)
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(k)
        cert.sign(k, 'sha1')

        open(join(cert_dir, CERT_FILE), "wt").write(
            crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
        open(join(cert_dir, KEY_FILE), "wt").write(
            crypto.dump_privatekey(crypto.FILETYPE_PEM, k))