AMP and StartTLS

Recently, I’ve been hacking on a thing which has been using Twisted’s AMP for making remote calls. I’ve developed a bit of a fetish for SSL client certificates, so I was hoping to use that for authenticating clients. AMP does have STARTTLS support builtin, but I couldn’t find any examples! Here’s what I found and figured out, for future hackers:

Always initiate from the client

Until a bug in Twisted is resolved, only the client will be able to successfully initiate the StartTLS. Here’s a full runnable example.

(basic.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from twisted.internet import protocol, reactor
from twisted.protocols import amp
from twisted.python import log

import sys

class SupCommand(amp.Command):
    pass

class ServerProtocol(amp.AMP):
    @SupCommand.responder
    def sup(self):
        log.msg('got sup')
        return {}

class ServerFactory(protocol.ServerFactory):
    protocol = ServerProtocol

class ClientProtocol(amp.AMP):
    def connectionMade(self):
        d = self.callRemote(amp.StartTLS)
        d.addCallback(lambda ign: self.callRemote(SupCommand))

class ClientFactory(protocol.ClientFactory):
    protocol = ClientProtocol

if sys.argv[1] == 'client':
    reactor.connectTCP('localhost', 9991, ClientFactory())
elif sys.argv[1] == 'server':
    reactor.listenTCP(9991, ServerFactory())

log.startLogging(sys.stdout)
reactor.run()

Using client certificates isn’t hard

Specifying which certificate to use for both peers requires only minor modifications. Here’s another runnable example, assuming you have an ssl.pem file.

(client-cert.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from twisted.internet import protocol, reactor, ssl
from twisted.protocols import amp
from twisted.python import log

import sys

cert = ssl.PrivateCertificate.loadPEM(open('ssl.pem').read())
certParams = {'tls_localCertificate': cert}

class SupCommand(amp.Command):
    pass

class ServerProtocol(amp.AMP):
    @SupCommand.responder
    def sup(self):
        log.msg('got sup')
        return {}

    @amp.StartTLS.responder
    def startTLS(self):
        return certParams

class ServerFactory(protocol.ServerFactory):
    protocol = ServerProtocol

class ClientProtocol(amp.AMP):
    def connectionMade(self):
        d = self.callRemote(amp.StartTLS, **certParams)
        d.addCallback(lambda ign: self.callRemote(SupCommand))

class ClientFactory(protocol.ClientFactory):
    protocol = ClientProtocol

if sys.argv[1] == 'client':
    reactor.connectTCP('localhost', 9991, ClientFactory())
elif sys.argv[1] == 'server':
    reactor.listenTCP(9991, ServerFactory())

log.startLogging(sys.stdout)
reactor.run()

ssl.pem must include both the certificate and the key. Though the example doesn’t demonstrate it, certificate authorities to use can also be specified using the tls_verifyAuthorities parameter (see the API docs for StartTLS).

Comments