[bug] Use BrowserLikePolicyForHTTPS for checking
authorVictor Shyba <victor.shyba@gmail.com>
Wed, 27 May 2015 15:49:44 +0000 (12:49 -0300)
committerVictor Shyba <victor.shyba@gmail.com>
Tue, 2 Jun 2015 22:25:54 +0000 (19:25 -0300)
While testing the way that its implemented now, I found out that no check
is being made on certificate attributes against the host.
I found this simple way of creating a BrowserLikePolicyForHTTPS using
a self signed cert and it worked on my test. I used test_https from
Soledad for checking this (which we are fixing on another branch).

Also, we don't want to depend on twisted for other things than
leap.common.http.

pkg/requirements.pip
setup.py
src/leap/common/certs.py
src/leap/common/http.py

index f875344..1977cc8 100644 (file)
@@ -2,8 +2,6 @@ jsonschema  #<=0.8 -- are we done with this conflict?
 dirspec
 pyopenssl
 python-dateutil
-Twisted>=12.1
-zope.interface
 pyzmq
 txzmq
 
index a7de8f9..776a477 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -138,4 +138,11 @@ setup(
     tests_require=tests_requirements,
     include_package_data=True,
     zip_safe=False,
+
+    extras_require={
+        # needed for leap.common.http
+        #  service_identity needed for propper hostname identification,
+        #  see http://twistedmatrix.com/documents/current/core/howto/ssl.html
+        'Twisted':  ["Twisted>=14.0.2", "service_identity", "zope.insterface"]
+    },
 )
index db513f6..c8e0743 100644 (file)
@@ -178,3 +178,20 @@ def should_redownload(certfile, now=time.gmtime):
         return True
 
     return False
+
+
+def get_compatible_ssl_context_factory(cert_path=None):
+    import twisted
+    cert = None
+    if twisted.version.base() > '14.0.1':
+        from twisted.web.client import BrowserLikePolicyForHTTPS
+        from twisted.internet import ssl
+        if cert_path:
+            cert = ssl.Certificate.loadPEM(open(cert_path).read())
+        policy = BrowserLikePolicyForHTTPS(cert)
+        return policy
+    else:
+        raise Exception(("""
+            Twisted 14.0.2 is needed in order to have secure Client Web SSL Contexts, not %s
+            See: http://twistedmatrix.com/trac/ticket/7647
+            """) % (twisted.version.base()))
index 39f01ba..1dc5642 100644 (file)
 Twisted HTTP/HTTPS client.
 """
 
-import os
+try:
+    import twisted
+except ImportError:
+    print "*******"
+    print "Twisted is needed to use leap.common.http module"
+    print ""
+    print "Install the extra requirement of the package:"
+    print "$ pip install leap.common[Twisted]"
+    import sys
+    sys.exit(1)
 
-from zope.interface import implements
 
-from OpenSSL.crypto import load_certificate
-from OpenSSL.crypto import FILETYPE_PEM
+from leap.common.certs import get_compatible_ssl_context_factory
+
+from zope.interface import implements
 
 from twisted.internet import reactor
-from twisted.internet.ssl import ClientContextFactory
-from twisted.internet.ssl import CertificateOptions
 from twisted.internet.defer import succeed
 
 from twisted.web.client import Agent
 from twisted.web.client import HTTPConnectionPool
 from twisted.web.client import readBody
-from twisted.web.client import BrowserLikePolicyForHTTPS
 from twisted.web.http_headers import Headers
 from twisted.web.iweb import IBodyProducer
 
@@ -55,33 +61,12 @@ class HTTPClient(object):
         self._pool = HTTPConnectionPool(reactor, persistent=True)
         self._pool.maxPersistentPerHost = 10
 
-        if cert_file:
-            cert = self._load_cert(cert_file)
-            self._agent = Agent(
-                reactor,
-                HTTPClient.ClientContextFactory(cert),
-                pool=self._pool)
-        else:
-            # trust the system's CAs
-            self._agent = Agent(
-                reactor,
-                BrowserLikePolicyForHTTPS(),
-                pool=self._pool)
-
-    def _load_cert(self, cert_file):
-        """
-        Load a X509 certificate from a file.
-
-        :param cert_file: The path to the certificate file.
-        :type cert_file: str
+        policy = get_compatible_ssl_context_factory(cert_file)
 
-        :return: The X509 certificate.
-        :rtype: OpenSSL.crypto.X509
-        """
-        if os.path.exists(cert_file):
-            with open(cert_file) as f:
-                data = f.read()
-                return load_certificate(FILETYPE_PEM, data)
+        self._agent = Agent(
+            reactor,
+            policy,
+            pool=self._pool)
 
     def request(self, url, method='GET', body=None, headers={}):
         """
@@ -106,25 +91,6 @@ class HTTPClient(object):
         d.addCallback(readBody)
         return d
 
-    class ClientContextFactory(ClientContextFactory):
-        """
-        A context factory that will verify the server's certificate against a
-        given CA certificate.
-        """
-
-        def __init__(self, cacert):
-            """
-            Initialize the context factory.
-
-            :param cacert: The CA certificate.
-            :type cacert: OpenSSL.crypto.X509
-            """
-            self._cacert = cacert
-
-        def getContext(self, hostname, port):
-            opts = CertificateOptions(verify=True, caCerts=[self._cacert])
-            return opts.getContext()
-
     class StringBodyProducer(object):
         """
         A producer that writes the body of a request to a consumer.