[Twisted-Python] SSL Cert Verification howto
Eli Criffield
elicriffield at gmail.com
Fri May 11 14:36:07 MDT 2007
I was going to post to the twisted wiki, but apparently there isn't one?
So here is how I made a twisted based xmlrpc client and server that do
all verification via SSL Certs.
The model works like this, the RPC Server accepts all requests from
anyone who connects with a cert signed by the CA it trusts. The client
verifies the server is valid because the server's cert is signed by the
CA the client trusts.
Some kind of key verification is the way to go if servers need to
authenticate other servers. Any password would have to be stored in a
file somewhere.
Once you have an authenticated server-to-server rpc connection there's
no need to only show some functions to some servers and some to
others. You're in charge of both server and client, so you're trusted as
a local superuser at that point.
A base authentication model like this would work great for a
centralized network management protocol, very much like how Puppet
works, or a "Super Cron" that might do things like check workload and
send scheduled jobs to places that can handle it. You could work in a
different request handler to http put files and use it to distribute
files or even distribute files then execute a job to process them --
any kind of server-to-server automation, really.
Here's how you set it up...
Openssl packages have a script called CA.sh that's for demonstrating
how to manage a CA. You will be the CA, anything you bless with your
signature might as well have the root password.
You'll want to customize the CA.sh script and the openssl.conf for
your setup, but it will work something like this:
# Make a new CA, it makes a private key and a public key then signs
# the public key with its own private key (a signed public key is a
# cert)
CA.sh -newca
# This makes a private key and a public key, the public key is what
# you need to sign to make a cert
CA.sh -newreq
# This makes signs the public key you just created
CA.sh -sign
# Combine the private key and the signed public key (The Cert) and you
# have the pem file needed for your program
cat newkey.pem newcert.pem > server.pem
# Don't need these anymore
rm newcert.pem newkey.pem newreq.pem
# same for the client key
CA.sh -newreq
CA.sh -sign
cat newkey.pem newcert.pem > client.pem
rm newcert.pem newkey.pem newreq.pem
# the cacert.pem is what we use to check if the person connecting is
# friend or foe
cp demoCA/cacert.pem .
--- the code ---
#!/usr/bin/env python
import sys
from twisted.web import xmlrpc, server
from twisted.internet import reactor, ssl
from twisted.python import log
def makeSSLContext(myKey,trustedCA):
'''Returns an ssl Context Object
@param myKey a pem formated key and certifcate with for my current host
the other end of this connection must have the cert from the CA
that signed this key
@param trustedCA a pem formated certificat from a CA you trust
you will only allow connections from clients signed by this CA
and you will only allow connections to a server signed by this CA
'''
# our goal in here is to make a SSLContext object to pass to connectSSL
# or listenSSL
# Why these functioins... Not sure...
fd = open(myKey,'r')
theCert = ssl.PrivateCertificate.loadPEM(fd.read())
fd.close()
fd = open(trustedCA,'r')
theCA = ssl.Certificate.loadPEM(fd.read())
fd.close()
ctx = theCert.options(theCA)
# Now the options you can set look like Standard OpenSSL Library options
# The SSL protocol to use, one of SSLv23_METHOD, SSLv2_METHOD,
# SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
ctx.method = ssl.SSL.TLSv1_METHOD
# If True, verify certificates received from the peer and fail
# the handshake if verification fails. Otherwise, allow anonymous
# sessions and sessions with certificates which fail validation.
ctx.verify = True
# Depth in certificate chain down to which to verify.
ctx.verifyDepth = 1
# If True, do not allow anonymous sessions.
ctx.requireCertification = True
# If True, do not re-verify the certificate on session resumption.
ctx.verifyOnce = True
# If True, generate a new key whenever ephemeral DH parameters are used
# to prevent small subgroup attacks.
ctx.enableSingleUseKeys = True
# If True, set a session ID on each context. This allows a shortened
# handshake to be used when a known client reconnects.
ctx.enableSessions = True
# If True, enable various non-spec protocol fixes for broken
# SSL implementations.
ctx.fixBrokenPeers = False
return ctx
class Example(xmlrpc.XMLRPC):
"""An example object to be published.
see: http://twistedmatrix.com/projects/web/documentation/howto/xmlrpc.html
"""
def xmlrpc_echo(self, x):
"""Return all passed args."""
log.msg('xmlrpc call echo, %s'%x)
return x
class Proxy(xmlrpc.Proxy):
''' See: http://twistedmatrix.com/projects/web/documentation/howto/xmlrpc.html
this is eacly like the xmlrpc.Proxy included in twisted but you can
give it a SSLContext object insted of just accepting the defaults..
'''
def setSSLClientContext(self,SSLClientContext):
self.SSLClientContext = SSLClientContext
def callRemote(self, method, *args):
factory = xmlrpc._QueryFactory(
self.path, self.host, method, self.user,
self.password, self.allowNone, args)
if self.secure:
from twisted.internet import ssl
try:
self.SSLClientContext
except NameError:
print "Must Set a SSL Context"
print "use self.setSSLClientContext() first"
# Its very bad to connect to ssl without some kind of
# verfication of who your talking to
# Using the default sslcontext without verification
# Can lead to man in the middle attacks
reactor.connectSSL(self.host, self.port or 443,
factory,self.SSLClientContext)
else:
reactor.connectTCP(self.host, self.port or 80, factory)
return factory.deferred
def printValue(value):
print repr(value)
reactor.stop()
def printError(error):
print 'error', error
reactor.stop()
if __name__ == '__main__':
# this should look pretty much like the examples given in the twisted
# documents
print "running as", sys.argv[1]
if sys.argv[1] == 'server':
log.startLogging(sys.stdout)
ctx = makeSSLContext(myKey='server.pem',trustedCA='cacert.pem')
r = Example()
reactor.listenSSL(7080, server.Site(r),ctx)
reactor.run()
elif sys.argv[1] == 'client':
ctx = makeSSLContext(myKey='client.pem', trustedCA='cacert.pem')
proxy = Proxy('https://localhost:7080/')
proxy.setSSLClientContext(ctx)
proxy.callRemote('echo',
'hello world').addCallbacks(printValue, printError)
reactor.run()
More information about the Twisted-Python
mailing list