[Twisted-Python] ssl.py patch + example client /w server certificate verify
![](https://secure.gravatar.com/avatar/8ca35506ac08cebd833ab53032896c0b.jpg?s=120&d=mm&r=g)
Howdy. Thanks to those who helped me get this bootstrapped, so that others may find the task easier... here is an example of a client which has its own certificate, and verifies a known client certificate. from twisted.internet import reactor, protocol from twisted.protocols import http from twisted.internet import ssl class Client(http.HTTPClient): def connectionMade(self): print 'Connected.' self.sendCommand('GET', "/") self.sendHeader('User-Agent', 'Twisted-Example') self.endHeaders() print 'Sent request.' def handleResponse(self, data): print 'Got response.' #print data def verifyCert(cert): return ssl.dumpCertificate(cert) == file("server.crt").read() def fetchURL(): print 'Connecting to.' context = ssl.DefaultOpenSSLContextFactory( "client.key","client.crt", verifyCallback = verifyCert) context.isClient = 1 factory = protocol.ClientFactory() factory.protocol = Client reactor.connectSSL('localhost', 8443, factory, context) if __name__ == '__main__': reactor.callLater(5,reactor.stop) reactor.callLater(1,fetchURL) reactor.run() Anyway, it's not the prettiest... in particular, why is there a separate class Default* ... could not this default behavior gone into the shorter ssl.ContextFactory? And... here is the patch to ssl.py --- ssl.py.orig Tue Mar 25 13:44:40 2003 +++ ssl.py Tue Mar 25 15:32:15 2003 @@ -36,7 +36,7 @@ """ # System imports -from OpenSSL import SSL +from OpenSSL import SSL, crypto import socket # sibling imports @@ -55,20 +55,32 @@ """Return a SSL.Context object. override in subclasses.""" raise NotImplementedError +def dumpCertificate(cert, filetype = crypto.FILETYPE_PEM ): + ''' a helper to dump an incoming cert as a PEM ''' + return crypto.dump_certificate(filetype, cert) class DefaultOpenSSLContextFactory(ContextFactory): def __init__(self, privateKeyFileName, certificateFileName, - sslmethod=SSL.SSLv23_METHOD): - self.privateKeyFileName = privateKeyFileName + sslmethod=SSL.SSLv23_METHOD, verifyCallback = None): + self.verifyCallback = (verifyCallback, ) + self.privateKeyFileName = privateKeyFileName self.certificateFileName = certificateFileName self.sslmethod = sslmethod self.cacheContext() + + def verifyCertificate(self, conn, cert, errno, depth, retcode): + cb = self.verifyCallback[0] + if cb: return cb(cert) + return 1 + def cacheContext(self): ctx = SSL.Context(self.sslmethod) ctx.use_certificate_file(self.certificateFileName) ctx.use_privatekey_file(self.privateKeyFileName) + if self.verifyCallback[0]: + ctx.set_verify(SSL.VERIFY_PEER, self.verifyCertificate) self._context = ctx def __getstate__(self):
![](https://secure.gravatar.com/avatar/8ca35506ac08cebd833ab53032896c0b.jpg?s=120&d=mm&r=g)
With some help from etrepum (Bob Ippolito), the example is smaller, and it even includes using POST. Thanks Bob. from twisted.internet import reactor, ssl from twisted.web import client import urllib HOST = 'localhost'; PORT = 8443 serverCertificate = file("server.crt").read() def verifyCert(cert): return ssl.dumpCertificate(cert) == serverCertificate def getdata(): postdata = urllib.urlencode({'some': 'argument', 'another': 'arg'}) headers = {"Content-type":"application/x-www-form-urlencoded"} context = ssl.DefaultOpenSSLContextFactory( "client.key","client.crt", verifyCallback = verifyCert) factory = client.HTTPClientFactory(HOST, "/some/path", "POST", postdata, headers ) reactor.connectSSL(HOST, PORT, factory, context) return factory.deferred def result(data): print data if __name__ == '__main__': reactor.callLater(5, reactor.stop) deferred = getdata() deferred.addCallback(result) reactor.run() Ooh, this still requires the following patch to twisted.internet.ssl --- ssl.py.orig Tue Mar 25 13:44:40 2003 +++ ssl.py Tue Mar 25 15:32:15 2003 @@ -36,7 +36,7 @@ """ # System imports -from OpenSSL import SSL +from OpenSSL import SSL, crypto import socket # sibling imports @@ -55,20 +55,32 @@ """Return a SSL.Context object. override in subclasses.""" raise NotImplementedError +def dumpCertificate(cert, filetype = crypto.FILETYPE_PEM ): + ''' a helper to dump an incoming cert as a PEM ''' + return crypto.dump_certificate(filetype, cert) class DefaultOpenSSLContextFactory(ContextFactory): def __init__(self, privateKeyFileName, certificateFileName, - sslmethod=SSL.SSLv23_METHOD): - self.privateKeyFileName = privateKeyFileName + sslmethod=SSL.SSLv23_METHOD, verifyCallback = None): + self.verifyCallback = (verifyCallback, ) + self.privateKeyFileName = privateKeyFileName self.certificateFileName = certificateFileName self.sslmethod = sslmethod self.cacheContext() + + def verifyCertificate(self, conn, cert, errno, depth, retcode): + cb = self.verifyCallback[0] + if cb: return cb(cert) + return 1 + def cacheContext(self): ctx = SSL.Context(self.sslmethod) ctx.use_certificate_file(self.certificateFileName) ctx.use_privatekey_file(self.privateKeyFileName) + if self.verifyCallback[0]: + ctx.set_verify(SSL.VERIFY_PEER, self.verifyCertificate) self._context = ctx def __getstate__(self):
![](https://secure.gravatar.com/avatar/8ca35506ac08cebd833ab53032896c0b.jpg?s=120&d=mm&r=g)
Well, as per the last post, there seems to be a 'straight-forward' version of twisted.web.client.getPage ; attached are two patches which make code like below work. from twisted.internet import reactor, ssl from twisted.web import client import urllib def result(data): print data if __name__ == '__main__': reactor.callLater(5, reactor.stop) # a very simple example deferred = client.getSecurePage("https://localhost/some/path") deferred.addCallback(result) # a more complicated one postdata = urllib.urlencode({'some': 'argument', 'another': 'arg'}) headers = {"Content-type":"application/x-www-form-urlencoded"} deferred = client.getSecurePage("https://localhost:8443/some/path", "POST", postdata, headers, privateKeyFileName = "client.key", certificateFileName = "client.crt", serverCertificateFileName="server.crt") deferred.addCallback(result) reactor.run() Best, Clark
participants (1)
-
Clark C. Evans