From e65e0a69dd335386a22736fb846a5bc9637cc57e Mon Sep 17 00:00:00 2001
From: Tomaz Muraus <tomaz@tomaz.me>
Date: Mon, 28 Jan 2013 20:39:35 -0800
Subject: [PATCH 1/2] Allow user to specify custom CA certificate to use for
 validating the server certificate by setting
 SSL_CERT_FILE environment variable.

---
 libcloud/security.py              |   15 +++++++++++++++
 libcloud/test/test_httplib_ssl.py |   38 ++++++++++++++++++++++++++++++++++---
 libcloud/utils/py3.py             |   11 ++++++++++-
 3 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/libcloud/security.py b/libcloud/security.py
index 8436248..ad8b162 100644
--- a/libcloud/security.py
+++ b/libcloud/security.py
@@ -23,6 +23,8 @@ Usage:
     libcloud.security.CA_CERTS_PATH.append("/path/to/cacert.txt")
 """
 
+import os
+
 VERIFY_SSL_CERT = True
 VERIFY_SSL_CERT_STRICT = True
 
@@ -42,6 +44,19 @@ CA_CERTS_PATH = [
     '/opt/local/share/curl/curl-ca-bundle.crt',
 ]
 
+# Allow user to explicitly specify which CA bundle to use, using an environment
+# variable
+environment_cert_file = os.getenv('SSL_CERT_FILE', None)
+if environment_cert_file is not None:
+    # Make sure the file exists
+    if not os.path.exists(environment_cert_file):
+        raise ValueError('Certificate file %s doesn\'t exist' %
+                         (environment_cert_file))
+
+    # If a provided file exists we ignore other common paths because we
+    # don't want to fall-back to a potentially less restrictive bundle
+    CA_CERTS_PATH = [environment_cert_file]
+
 CA_CERTS_UNAVAILABLE_WARNING_MSG = (
     'Warning: No CA Certificates were found in CA_CERTS_PATH. '
     'Toggling VERIFY_SSL_CERT to False.'
diff --git a/libcloud/test/test_httplib_ssl.py b/libcloud/test/test_httplib_ssl.py
index 3a236b1..40b7ad9 100644
--- a/libcloud/test/test_httplib_ssl.py
+++ b/libcloud/test/test_httplib_ssl.py
@@ -13,19 +13,48 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import sys
 import unittest
 import os.path
 
 import libcloud.security
+
+from libcloud.utils.py3 import reload
 from libcloud.httplib_ssl import LibcloudHTTPSConnection
 
+ORIGINAL_CA_CERS_PATH = libcloud.security.CA_CERTS_PATH
+
 class TestHttpLibSSLTests(unittest.TestCase):
 
     def setUp(self):
         libcloud.security.VERIFY_SSL_CERT = False
+        libcloud.security.CA_CERTS_PATH = ORIGINAL_CA_CERS_PATH
         self.httplib_object = LibcloudHTTPSConnection('foo.bar')
 
+    def test_custom_ca_path_using_env_var_doesnt_exist(self):
+        os.environ['SSL_CERT_FILE'] = '/foo/doesnt/exist'
+
+        try:
+            reload(libcloud.security)
+        except ValueError:
+            e = sys.exc_info()[1]
+            msg = 'Certificate file /foo/doesnt/exist doesn\'t exist'
+            self.assertEqual(str(e), msg)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_custom_ca_path_using_env_var_exist(self):
+        # When setting a path we don't actually check that a valid CA file is
+        # provied.
+        # This happens later in the code in httplib_ssl.connect method
+        file_path  = os.path.abspath(__file__)
+        os.environ['SSL_CERT_FILE'] = file_path
+
+        reload(libcloud.security)
+
+        self.assertEqual(libcloud.security.CA_CERTS_PATH, [file_path])
+
     def test_verify_hostname(self):
         cert1 = {'notAfter': 'Feb 16 16:54:50 2013 GMT',
          'subject': ((('countryName', 'US'),),
@@ -143,8 +172,10 @@ class TestHttpLibSSLTests(unittest.TestCase):
                          None)
 
     def test_setup_verify(self):
-        # @TODO: catch warnings
-        # non-strict mode,s hould just emit a warning
+        # TODO: catch warnings
+        libcloud.security.CA_CERTS_PATH = []
+
+        # non-strict mode should just emit a warning
         libcloud.security.VERIFY_SSL_CERT = True
         libcloud.security.VERIFY_SSL_CERT_STRICT = False
         self.httplib_object._setup_verify()
@@ -152,9 +183,10 @@ class TestHttpLibSSLTests(unittest.TestCase):
         # strict mode, should throw a runtime error
         libcloud.security.VERIFY_SSL_CERT = True
         libcloud.security.VERIFY_SSL_CERT_STRICT = True
+
         try:
             self.httplib_object._setup_verify()
-        except:
+        except RuntimeError:
             pass
         else:
             self.fail('Exception not thrown')
diff --git a/libcloud/utils/py3.py b/libcloud/utils/py3.py
index 648704e..4537f7a 100644
--- a/libcloud/utils/py3.py
+++ b/libcloud/utils/py3.py
@@ -24,9 +24,10 @@ import sys
 import types
 from xml.etree import ElementTree as ET
 
-PY3 = False
 PY2 = False
 PY25 = False
+PY3 = False
+PY32 = False
 
 if sys.version_info >= (2, 0) and sys.version_info < (3, 0):
     PY2 = True
@@ -37,6 +38,9 @@ if sys.version_info >= (2, 5) and sys.version_info <= (2, 6):
 if sys.version_info >= (3, 0):
     PY3 = True
 
+if sys.version_info >= (3, 2) and sys.version_info < (3, 3):
+    PY32 = True
+
 if PY3:
     import http.client as httplib
     from io import StringIO
@@ -118,3 +122,8 @@ if PY25:
         if not rel_list:
             return posixpath.curdir
         return posixpath.join(*rel_list)
+
+if PY32:
+    from imp import reload
+else:
+    from __builtin__ import reload
-- 
1.7.9.5


From 1b0d1aa22433c70e3244cfe0711cffa89e3c80ba Mon Sep 17 00:00:00 2001
From: Tomaz Muraus <tomaz@tomaz.me>
Date: Mon, 28 Jan 2013 21:32:13 -0800
Subject: [PATCH 2/2] Make sure specified path to a CA cert is a file and not
 a directory.

As discussed on the IRC with @pquerna.
---
 libcloud/httplib_ssl.py           |    2 +-
 libcloud/security.py              |    3 +++
 libcloud/test/test_httplib_ssl.py |   13 +++++++++++++
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/libcloud/httplib_ssl.py b/libcloud/httplib_ssl.py
index 1e713b2..4709f27 100644
--- a/libcloud/httplib_ssl.py
+++ b/libcloud/httplib_ssl.py
@@ -68,7 +68,7 @@ class LibcloudHTTPSConnection(httplib.HTTPSConnection):
 
         ca_certs_available = [cert
                               for cert in libcloud.security.CA_CERTS_PATH
-                              if os.path.exists(cert)]
+                              if os.path.exists(cert) and os.path.isfile(cert)]
         if ca_certs_available:
             # use first available certificate
             self.ca_cert = ca_certs_available[0]
diff --git a/libcloud/security.py b/libcloud/security.py
index ad8b162..72d5328 100644
--- a/libcloud/security.py
+++ b/libcloud/security.py
@@ -53,6 +53,9 @@ if environment_cert_file is not None:
         raise ValueError('Certificate file %s doesn\'t exist' %
                          (environment_cert_file))
 
+    if not os.path.isfile(environment_cert_file):
+        raise ValueError('Certificate file can\'t be a directory')
+
     # If a provided file exists we ignore other common paths because we
     # don't want to fall-back to a potentially less restrictive bundle
     CA_CERTS_PATH = [environment_cert_file]
diff --git a/libcloud/test/test_httplib_ssl.py b/libcloud/test/test_httplib_ssl.py
index 40b7ad9..2014eee 100644
--- a/libcloud/test/test_httplib_ssl.py
+++ b/libcloud/test/test_httplib_ssl.py
@@ -44,6 +44,19 @@ class TestHttpLibSSLTests(unittest.TestCase):
         else:
             self.fail('Exception was not thrown')
 
+    def test_custom_ca_path_using_env_var_is_directory(self):
+        file_path  = os.path.dirname(os.path.abspath(__file__))
+        os.environ['SSL_CERT_FILE'] = file_path
+
+        try:
+            reload(libcloud.security)
+        except ValueError:
+            e = sys.exc_info()[1]
+            msg = 'Certificate file can\'t be a directory'
+            self.assertEqual(str(e), msg)
+        else:
+            self.fail('Exception was not thrown')
+
     def test_custom_ca_path_using_env_var_exist(self):
         # When setting a path we don't actually check that a valid CA file is
         # provied.
-- 
1.7.9.5

