[Twisted-Python] ssl.py/client.py patches to implement getSecurePage()

Clark C. Evans cce at clarkevans.com
Tue Mar 25 19:19:22 EST 2003


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
-------------- next part --------------
--- 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):
-------------- next part --------------
--- client.py.orig	Tue Mar 25 18:10:28 2003
+++ client.py	Tue Mar 25 18:55:08 2003
@@ -63,7 +63,8 @@
         l = self.headers.get('location')
         if not l:
             self.handleStatusDefault()
-        host, port, url = _parse(l[0])
+        https, host, port, url = _parse(l[0])
+        assert not https, "https redirects not implemented yet"
         self.factory.host, self.factory.url = host, url
         reactor.connectTCP(host, port, self.factory)
         self.quietLoss = 1
@@ -105,7 +106,9 @@
 
     protocol = HTTPPageGetter
 
-    def __init__(self, host, url, method='GET', postdata=None, headers=None, agent="Twisted PageGetter"):
+    def __init__(self, host, url, method='GET', postdata=None, 
+                 headers=None, agent=None):
+        if agent is None: agent = "Twisted PageGetter"
         if headers is not None:
             self.headers = headers
         if postdata is not None:
@@ -141,8 +144,10 @@
     protocol = HTTPPageDownloader
     value = None
 
-    def __init__(self, host, url, fileName, method='GET', postdata=None, headers=None, agent="Twisted client"):
-        HTTPClientFactory.__init__(self, host, url, method=method, postdata=postdata, headers=headers, agent=agent)
+    def __init__(self, host, url, fileName, method='GET', postdata=None, 
+                 headers=None, agent = None):
+        HTTPClientFactory.__init__(self, host, url, method, 
+                                   postdata, headers, agent)
         self.fileName = fileName
         self.deferred = defer.Deferred()
         self.waiting = 1
@@ -166,26 +171,59 @@
 def _parse(url):
     parsed = urlparse.urlparse(url)
     url = urlparse.urlunparse(('','')+parsed[2:])
-    host, port = parsed[1], 80
+    host = parsed[1]
+    if 'https' == parsed[0]:
+        https, port = 1, 443
+    else:
+        https, port = 0, 80
     if ':' in host:
         host, port = host.split(':')
         port = int(port)
-    return host, port, url
+    return https, host, port, url
 
-def getPage(url, *args, **kwargs):
+def getPage(url, method='GET', postdata=None, headers=None, agent = None):
     '''download a web page
 
     Download a page. Return a deferred, which will
     callback with a page or errback with a description
     of the error.
     '''
-    host, port, url = _parse(url)
-    factory = HTTPClientFactory(host, url, *args, **kwargs)
+    https, host, port, path = _parse(url)
+    if https: return getSecurePage(url, method, postdata, headers, agent)
+    factory = HTTPClientFactory(host, path, method, postdata, headers, agent)
     reactor.connectTCP(host, port, factory)
     return factory.deferred
 
+try:
+    from twisted.internet import ssl
+    def getSecurePage(url, method='GET', postdata=None, headers=None, 
+                      agent = None,
+                      privateKeyFileName = None, certificateFileName = None,
+                      sslmethod=ssl.SSL.SSLv23_METHOD, verifyCallback = None, 
+                      serverCertificateFileName = None):
+        """ download a secure page """
+        https, host, port, path = _parse(url)
+        if serverCertificateFileName:
+            cert = file(serverCertificateFileName).read()
+            verifyCallback = lambda rhs: ssl.dumpCertificate(cert) == rhs
+        if verifyCallback or certificateFileName or privateKeyFileName:
+            context = ssl.DefaultOpenSSLContextFactory(
+                          privateKeyFileName, certificateFileName,
+                          sslmethod, verifyCallback)
+            context.isClient = 1
+        else:
+            context = ssl.ClientContextFactory()
+        factory = HTTPClientFactory(host, path, method, postdata, headers)
+        reactor.connectSSL(host, port, factory, context)
+        return factory.deferred
+except:
+    def getSecurePage(*args,**kwargs):
+        raise Exception("ssl not available")
+
 def downloadPage(url, file, *args, **kwargs):
-    host, port, url = _parse(url)
-    factory = HTTPDownloader(host, url, file, *args, **kwargs)
+    https, host, port, path = _parse(url)
+    assert not https, "https is not yet implemented here"
+    factory = HTTPDownloader(host, path, file, *args, **kwargs)
     reactor.connectTCP(host, port, factory)
     return factory.deferred
+


More information about the Twisted-Python mailing list