Package: juju
Version: 0.5.1+bzr563-0juju2~quantal1
Severity: grave
Tags: security patch upstream
Justification: user security hole

This problem with juju has been fixed in upstream trunk and so can be
considered "disclosed".

When using juju with the built in "charm store" at store.juju.ubuntu.com,
the SSL certificate is not verified. This could lead to a man in the
middle attack where an attacker could have trojaned "charms" installed
instead of the official charms.

-- System Information:
Debian Release: wheezy/sid
  APT prefers quantal-updates
  APT policy: (500, 'quantal-updates'), (500, 'quantal-security'), (500, 
'quantal'), (400, 'precise-proposed')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 3.5.0-10-generic (SMP w/2 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages juju depends on:
ii  openssh-client      1:6.0p1-2ubuntu1
ii  python              2.7.3-0ubuntu5
ii  python-oauth        1.0.1-3build1
ii  python-twisted      12.0.0-1ubuntu1
ii  python-txaws        0.2.3-1ubuntu1
ii  python-txzookeeper  0.9.5-1
ii  python-yaml         3.10-4
ii  python2.7           2.7.3-0ubuntu4
ii  tmux                1.6-2

Versions of packages juju recommends:
ii  byobu         5.21-0ubuntu1
ii  python-pydot  1.0.2-1

Versions of packages juju suggests:
ii  apt-cacher-ng  0.7.7-1ubuntu1
ii  libvirt-bin    0.9.13-0ubuntu7
ii  lxc            0.8.0~rc1-4ubuntu24
ii  zookeeper      3.3.6+dfsg-0ubuntu1

-- no debconf information
Origin: http://bazaar.launchpad.net/~juju/juju/trunk/revision/565
Bug: http://pad.lv/992447

=== modified file 'juju/charm/repository.py'
--- a/juju/charm/repository.py	2012-05-03 18:42:09 +0000
+++ b/juju/charm/repository.py	2012-08-23 23:57:09 +0000
@@ -3,11 +3,13 @@
 import os
 import tempfile
 import urllib
+import urlparse
 import yaml
 
 from twisted.internet.defer import fail, inlineCallbacks, returnValue, succeed
 from twisted.web.client import downloadPage, getPage
 from twisted.web.error import Error
+from txaws.client.ssl import VerifyingContextFactory
 
 from juju.charm.provider import get_charm_from_path
 from juju.charm.url import CharmURL
@@ -126,7 +128,8 @@
         url = "%s/charm-info?charms=%s" % (
             self.url_base, urllib.quote(charm_id))
         try:
-            all_info = json.loads((yield getPage(url)))
+            host = urlparse.urlparse(url).hostname
+            all_info = json.loads((yield getPage(url, contextFactory=VerifyingContextFactory(host))))
             charm_info = all_info[charm_id]
             for warning in charm_info.get("warnings", []):
                 log.warning("%s: %s", charm_id, warning)
@@ -147,8 +150,9 @@
             delete=False)
         f.close()
         downloading_path = f.name
+        host = urlparse.urlparse(url).hostname
         try:
-            yield downloadPage(url, downloading_path)
+            yield downloadPage(url, downloading_path, contextFactory=VerifyingContextFactory(host))
         except Error:
             raise CharmNotFound(self.url_base, charm_url)
         os.rename(downloading_path, cache_path)

=== modified file 'juju/charm/tests/test_repository.py'
--- a/juju/charm/tests/test_repository.py	2012-07-01 22:20:22 +0000
+++ b/juju/charm/tests/test_repository.py	2012-08-06 20:06:37 +0000
@@ -5,6 +5,8 @@
 
 from twisted.internet.defer import fail, inlineCallbacks, succeed
 from twisted.web.error import Error
+from txaws.client.ssl import VerifyingContextFactory
+
 
 from juju.charm.directory import CharmDirectory
 from juju.charm.errors import CharmNotFound, CharmURLError, RepositoryNotFound
@@ -16,7 +18,7 @@
 from juju.lib import under
 
 from juju.charm import tests
-from juju.lib.mocker import ANY
+from juju.lib.mocker import ANY, MATCH
 from juju.lib.testing import TestCase
 
 
@@ -280,15 +282,19 @@
         return json.dumps({url_str: info})
 
     def mock_charm_info(self, url, result):
-        self.getPage(url)
+        def match_context(value):
+            return isinstance(value, VerifyingContextFactory)
+        self.getPage(url, contextFactory=MATCH(match_context))
         self.mocker.result(result)
 
     def mock_download(self, url, error=None):
-        self.downloadPage(url, ANY)
+        def match_context(value):
+            return isinstance(value, VerifyingContextFactory)
+        self.downloadPage(url, ANY, contextFactory=MATCH(match_context))
         if error:
             return self.mocker.result(fail(error))
 
-        def download(_, path):
+        def download(_, path, contextFactory):
             self.assertTrue(path.startswith(self.download_path))
             with open(path, "wb") as f:
                 f.write(self.bundle_data)

=== modified file 'juju/control/tests/test_upgrade_charm.py'
--- a/juju/control/tests/test_upgrade_charm.py	2012-05-04 22:43:40 +0000
+++ b/juju/control/tests/test_upgrade_charm.py	2012-08-06 19:29:30 +0000
@@ -12,6 +12,7 @@
 from juju.errors import FileNotFound
 from juju.environment.environment import Environment
 from juju.unit.workflow import UnitWorkflowState
+from juju.lib.mocker import ANY
 
 from .common import MachineControlToolTest
 
@@ -481,7 +482,8 @@
         self.setup_exit(0)
         getPage = self.mocker.replace("twisted.web.client.getPage")
         getPage(
-            CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql")
+            CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql",
+            contextFactory=ANY)
         self.mocker.result(succeed(json.dumps(
             {"cs:series/mysql": {"revision": 1, "sha256": "whatever"}})))
         self.mocker.replay()
@@ -502,7 +504,8 @@
         self.setup_exit(0)
 
         getPage = self.mocker.replace("twisted.web.client.getPage")
-        getPage(CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql")
+        getPage(CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql",
+                contextFactory=ANY)
 
         self.mocker.result(succeed(json.dumps(
             {"cs:series/mysql": {"revision": 1, "sha256": "whatever"}})))

Reply via email to