Merge lp:click/devel into lp:click

Proposed by Colin Watson
Status: Merged
Approved by: Michael Vogt
Approved revision: 501
Merged at revision: 442
Proposed branch: lp:click/devel
Merge into: lp:click
Diff against target: 734 lines (+503/-19)
13 files modified
Makefile.am.coverage (+1/-0)
click/commands/info.py (+25/-9)
click/commands/install.py (+6/-1)
click/commands/verify.py (+6/-1)
click/install.py (+53/-1)
click/tests/test_install.py (+23/-0)
debian/changelog (+12/-0)
debian/tests/control (+1/-1)
tests/integration/test_hook.py (+2/-1)
tests/integration/test_info.py (+17/-1)
tests/integration/test_install.py (+5/-2)
tests/integration/test_signatures.py (+348/-0)
tests/integration/test_verify.py (+4/-2)
To merge this branch: bzr merge lp:click/devel
Reviewer Review Type Date Requested Status
click hackers Pending
Review via email: mp+230523@code.launchpad.net

Commit message

Click 0.4.31: "click info <file in unpacked package>", and basic support for package signing.

Description of the change

  [ Michael Vogt ]
  * Add "click info" interface to get manifest corresponding to file in
    installed package (LP: #1324853).
  * Add support for click package gpg signatures (LP: #1330770).

  [ Colin Watson ]
  * Ugly hack to get coverage reporting working again with gcovr 3.1.
  * Pass --allow-unauthenticated to "click install" from the PackageKit
    plugin for now. Revert this when a keyring and signing policies are in
    place on the phone images and confirmed to work.

To post a comment you must log in.
lp:click/devel updated
501. By Michael Vogt

trivial pep8 fix

502. By Michael Vogt

bump version number for citrain

503. By Michael Vogt

tests/integration/: fix tests by adding --allow-unauthenticated

504. By Michael Vogt

bump version number

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am.coverage'
2--- Makefile.am.coverage 2014-07-02 16:51:32 +0000
3+++ Makefile.am.coverage 2014-08-22 17:12:39 +0000
4@@ -38,6 +38,7 @@
5 generate-coverage-gcovr:
6 @echo Generating coverage GCOVR report
7 $(GCOVR) -x -r $(top_builddir) -o coverage-c.xml
8+ sed -i 's/\(<package .*name=\)""/\1"lib.click..libs"/' coverage-c.xml
9
10 clean-coverage-gcovr: clean-gcda
11 rm -f coverage-c.xml
12
13=== modified file 'click/commands/info.py'
14--- click/commands/info.py 2014-04-03 08:52:02 +0000
15+++ click/commands/info.py 2014-08-22 17:12:39 +0000
16@@ -18,8 +18,10 @@
17 from __future__ import print_function
18
19 from contextlib import closing
20+import glob
21 import json
22 from optparse import OptionParser
23+import os
24 import sys
25
26 from gi.repository import Click
27@@ -28,6 +30,15 @@
28 from click.json_helpers import json_object_to_python
29
30
31+def _load_manifest(manifest_file):
32+ manifest = json.load(manifest_file)
33+ keys = list(manifest)
34+ for key in keys:
35+ if key.startswith("_"):
36+ del manifest[key]
37+ return manifest
38+
39+
40 def get_manifest(options, arg):
41 if "/" not in arg:
42 db = Click.DB()
43@@ -38,15 +49,20 @@
44 if registry.has_package_name(arg):
45 return json_object_to_python(registry.get_manifest(arg))
46
47- with closing(DebFile(filename=arg)) as package:
48- with package.control.get_file(
49- "manifest", encoding="UTF-8") as manifest_file:
50- manifest = json.load(manifest_file)
51- keys = list(manifest)
52- for key in keys:
53- if key.startswith("_"):
54- del manifest[key]
55- return manifest
56+ if arg.endswith(".click"):
57+ with closing(DebFile(filename=arg)) as package:
58+ with package.control.get_file(
59+ "manifest", encoding="UTF-8") as manifest_file:
60+ return _load_manifest(manifest_file)
61+ else:
62+ pkgdir = Click.find_package_directory(arg)
63+ manifest_path = glob.glob(
64+ os.path.join(pkgdir, ".click", "info", "*.manifest"))
65+ if len(manifest_path) > 1:
66+ raise Exception("Multiple manifest files found in '%s'" % (
67+ manifest_path))
68+ with open(manifest_path[0]) as f:
69+ return _load_manifest(f)
70
71
72 def run(argv):
73
74=== modified file 'click/commands/install.py'
75--- click/commands/install.py 2014-04-03 08:52:02 +0000
76+++ click/commands/install.py 2014-08-22 17:12:39 +0000
77@@ -43,6 +43,9 @@
78 parser.add_option(
79 "--all-users", default=False, action="store_true",
80 help="register package for all users")
81+ parser.add_option(
82+ "--allow-unauthenticated", default=False, action="store_true",
83+ help="allow installing packages with no sigantures")
84 options, args = parser.parse_args(argv)
85 if len(args) < 1:
86 parser.error("need package file name")
87@@ -51,7 +54,9 @@
88 if options.root is not None:
89 db.add(options.root)
90 package_path = args[0]
91- installer = ClickInstaller(db, options.force_missing_framework)
92+ installer = ClickInstaller(
93+ db=db, force_missing_framework=options.force_missing_framework,
94+ allow_unauthenticated=options.allow_unauthenticated)
95 try:
96 installer.install(
97 package_path, user=options.user, all_users=options.all_users)
98
99=== modified file 'click/commands/verify.py'
100--- click/commands/verify.py 2013-09-23 21:24:37 +0000
101+++ click/commands/verify.py 2014-08-22 17:12:39 +0000
102@@ -29,10 +29,15 @@
103 parser.add_option(
104 "--force-missing-framework", action="store_true", default=False,
105 help="ignore missing system framework")
106+ parser.add_option(
107+ "--allow-unauthenticated", action="store_true", default=False,
108+ help="allow installing packages with no sigantures")
109 options, args = parser.parse_args(argv)
110 if len(args) < 1:
111 parser.error("need package file name")
112 package_path = args[0]
113- installer = ClickInstaller(None, options.force_missing_framework)
114+ installer = ClickInstaller(
115+ db=None, force_missing_framework=options.force_missing_framework,
116+ allow_unauthenticated=options.allow_unauthenticated)
117 installer.audit(package_path, slow=True)
118 return 0
119
120=== modified file 'click/install.py'
121--- click/install.py 2014-05-05 13:10:19 +0000
122+++ click/install.py 2014-08-22 17:12:39 +0000
123@@ -30,6 +30,7 @@
124 import grp
125 import inspect
126 import json
127+import logging
128 import os
129 import pwd
130 import shutil
131@@ -74,6 +75,45 @@
132 apt_pkg.init_system()
133
134
135+class DebsigVerifyError(Exception):
136+ pass
137+
138+
139+class DebsigVerify:
140+ """Tiny wrapper around the debsig-verify commandline"""
141+ # from debsig-verify-0.9/debsigs.h
142+ DS_SUCCESS = 0
143+ DS_FAIL_NOSIGS = 10
144+ DS_FAIL_UNKNOWN_ORIGIN = 11
145+ DS_FAIL_NOPOLICIES = 12
146+ DS_FAIL_BADSIG = 13
147+ DS_FAIL_INTERNAL = 14
148+
149+ # should be a property, but python does not support support
150+ # class properties easily
151+ @classmethod
152+ def available(cls):
153+ return Click.find_on_path("debsig-verify")
154+
155+ @classmethod
156+ def verify(cls, path, allow_unauthenticated):
157+ command = ["debsig-verify"] + [path]
158+ try:
159+ subprocess.check_output(command, universal_newlines=True)
160+ except subprocess.CalledProcessError as e:
161+ if (allow_unauthenticated and
162+ e.returncode in (DebsigVerify.DS_FAIL_NOSIGS,
163+ DebsigVerify.DS_FAIL_UNKNOWN_ORIGIN,
164+ DebsigVerify.DS_FAIL_NOPOLICIES)):
165+ logging.warning(
166+ "Signature check failed, but installing anyway "
167+ "as requested")
168+ else:
169+ raise DebsigVerifyError(
170+ "Signature verification error: %s" % e.output)
171+ return True
172+
173+
174 class ClickInstallerError(Exception):
175 pass
176
177@@ -87,9 +127,11 @@
178
179
180 class ClickInstaller:
181- def __init__(self, db, force_missing_framework=False):
182+ def __init__(self, db, force_missing_framework=False,
183+ allow_unauthenticated=False):
184 self.db = db
185 self.force_missing_framework = force_missing_framework
186+ self.allow_unauthenticated = allow_unauthenticated
187
188 def _preload_path(self):
189 if "CLICK_PACKAGE_PRELOAD" in os.environ:
190@@ -125,6 +167,16 @@
191 subprocess.check_call(command, env=env, **kwargs)
192
193 def audit(self, path, slow=False, check_arch=False):
194+ # always do the signature check first
195+ if DebsigVerify.available():
196+ try:
197+ DebsigVerify.verify(path, self.allow_unauthenticated)
198+ except DebsigVerifyError as e:
199+ raise ClickInstallerAuditError(str(e))
200+ else:
201+ logging.warning(
202+ "debsig-verify not available; cannot check signatures")
203+
204 with closing(DebFile(filename=path)) as package:
205 control_fields = package.control.debcontrol()
206
207
208=== modified file 'click/tests/test_install.py'
209--- click/tests/test_install.py 2014-05-19 13:08:57 +0000
210+++ click/tests/test_install.py 2014-08-22 17:12:39 +0000
211@@ -75,6 +75,12 @@
212 self.use_temp_dir()
213 self.db = Click.DB()
214 self.db.add(self.temp_dir)
215+ # mock signature checks during the tests
216+ self.debsig_patcher = mock.patch("click.install.DebsigVerify")
217+ self.debsig_patcher.start()
218+
219+ def tearDown(self):
220+ self.debsig_patcher.stop()
221
222 def make_fake_package(self, control_fields=None, manifest=None,
223 control_scripts=None, data_files=None):
224@@ -232,6 +238,23 @@
225 'Framework "missing" not present on system.*',
226 ClickInstaller(self.db).audit, path)
227
228+ # FIXME: we really want a unit test with a valid signature too
229+ def test_audit_no_signature(self):
230+ if not Click.find_on_path("debsig-verify"):
231+ self.skipTest("this test needs debsig-verify")
232+ path = self.make_fake_package(
233+ control_fields={"Click-Version": "0.4"},
234+ manifest={
235+ "name": "test-package",
236+ "version": "1.0",
237+ "framework": "",
238+ })
239+ self.debsig_patcher.stop()
240+ self.assertRaisesRegex(
241+ ClickInstallerAuditError, "Signature verification error",
242+ ClickInstaller(self.db).audit, path)
243+ self.debsig_patcher.start()
244+
245 @disable_logging
246 def test_audit_missing_framework_force(self):
247 with self.run_in_subprocess(
248
249=== modified file 'debian/changelog'
250--- debian/changelog 2014-08-06 23:33:23 +0000
251+++ debian/changelog 2014-08-22 17:12:39 +0000
252@@ -1,3 +1,15 @@
253+click (0.4.31.2) UNRELEASED; urgency=medium
254+
255+ [ Michael Vogt ]
256+ * Add "click info" interface to get manifest corresponding to file in
257+ installed package (LP: #1324853).
258+ * Add support for click package gpg signatures (LP: #1330770).
259+
260+ [ Colin Watson ]
261+ * Ugly hack to get coverage reporting working again with gcovr 3.1.
262+
263+ -- Colin Watson <cjwatson@ubuntu.com> Tue, 12 Aug 2014 14:32:38 +0100
264+
265 click (0.4.30) utopic; urgency=medium
266
267 [ Colin Watson ]
268
269=== modified file 'debian/tests/control'
270--- debian/tests/control 2014-06-30 14:03:36 +0000
271+++ debian/tests/control 2014-08-22 17:12:39 +0000
272@@ -1,3 +1,3 @@
273 Tests: run-tests.sh
274-Depends: @, iputils-ping, click-dev, schroot, debootstrap, sudo, bzr
275+Depends: @, iputils-ping, click-dev, schroot, debootstrap, sudo, bzr, debsigs, debsig-verify
276 Restrictions: needs-root allow-stderr
277
278=== added directory 'tests/integration/data'
279=== added directory 'tests/integration/data/evil-keyring'
280=== added file 'tests/integration/data/evil-keyring/pubring.gpg'
281Binary files tests/integration/data/evil-keyring/pubring.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/evil-keyring/pubring.gpg 2014-08-22 17:12:39 +0000 differ
282=== added file 'tests/integration/data/evil-keyring/secring.gpg'
283Binary files tests/integration/data/evil-keyring/secring.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/evil-keyring/secring.gpg 2014-08-22 17:12:39 +0000 differ
284=== added file 'tests/integration/data/evil-keyring/trustdb.gpg'
285Binary files tests/integration/data/evil-keyring/trustdb.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/evil-keyring/trustdb.gpg 2014-08-22 17:12:39 +0000 differ
286=== added directory 'tests/integration/data/origin-keyring'
287=== added file 'tests/integration/data/origin-keyring/pubring.gpg'
288Binary files tests/integration/data/origin-keyring/pubring.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/origin-keyring/pubring.gpg 2014-08-22 17:12:39 +0000 differ
289=== added file 'tests/integration/data/origin-keyring/secring.gpg'
290Binary files tests/integration/data/origin-keyring/secring.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/origin-keyring/secring.gpg 2014-08-22 17:12:39 +0000 differ
291=== added file 'tests/integration/data/origin-keyring/trustdb.gpg'
292Binary files tests/integration/data/origin-keyring/trustdb.gpg 1970-01-01 00:00:00 +0000 and tests/integration/data/origin-keyring/trustdb.gpg 2014-08-22 17:12:39 +0000 differ
293=== modified file 'tests/integration/test_hook.py'
294--- tests/integration/test_hook.py 2014-08-03 13:10:48 +0000
295+++ tests/integration/test_hook.py 2014-08-22 17:12:39 +0000
296@@ -64,7 +64,8 @@
297 click_pkg_name, framework="", hooks=hooks)
298 user = os.environ.get("USER", "root")
299 subprocess.check_call(
300- [self.click_binary, "install", "--user=%s" % user, click_pkg],
301+ [self.click_binary, "install", "--allow-unauthenticated",
302+ "--user=%s" % user, click_pkg],
303 universal_newlines=True)
304 self.addCleanup(
305 subprocess.check_call,
306
307=== modified file 'tests/integration/test_info.py'
308--- tests/integration/test_info.py 2014-06-26 12:00:09 +0000
309+++ tests/integration/test_info.py 2014-08-22 17:12:39 +0000
310@@ -27,4 +27,20 @@
311 path_to_click = self._make_click(name)
312 output = subprocess.check_output([
313 self.click_binary, "info", path_to_click], universal_newlines=True)
314- self.assertEqual(json.loads(output)["name"], name)
315+ self.assertEqual(name, json.loads(output)["name"])
316+
317+ def test_info_file_in_package(self):
318+ name = "org.example.info"
319+ version = "1.0"
320+ click_pkg = self._make_click(name=name, version=version, framework="")
321+ subprocess.check_call(
322+ [self.click_binary, "install", "--allow-unauthenticated",
323+ "--all-users", click_pkg])
324+ self.addCleanup(
325+ subprocess.check_call,
326+ [self.click_binary, "unregister", "--all-users", name])
327+ output = subprocess.check_output(
328+ [self.click_binary, "info",
329+ "/opt/click.ubuntu.com/%s/%s/README" % (name, version)],
330+ universal_newlines=True)
331+ self.assertEqual(name, json.loads(output)["name"])
332
333=== modified file 'tests/integration/test_install.py'
334--- tests/integration/test_install.py 2014-06-26 12:02:18 +0000
335+++ tests/integration/test_install.py 2014-08-22 17:12:39 +0000
336@@ -60,6 +60,7 @@
337 # install it
338 subprocess.check_call([
339 self.click_binary, "install", "--user=%s" % self.USER_1,
340+ "--allow-unauthenticated",
341 click_pkg], universal_newlines=True)
342 self.addCleanup(self.click_unregister, self.USER_1, "foo-1")
343 # ensure that user-1 has it
344@@ -82,7 +83,8 @@
345 click_pkg = self._make_click(name="foo-2", framework="")
346 # install it
347 subprocess.check_call(
348- [self.click_binary, "install", "--all-users", click_pkg],
349+ [self.click_binary, "install", "--all-users",
350+ "--allow-unauthenticated", click_pkg],
351 universal_newlines=True)
352 self.addCleanup(self.click_unregister, "@all", "foo-2")
353 # ensure all users see it
354@@ -95,7 +97,8 @@
355 def test_pkgdir_after_install(self):
356 click_pkg = self._make_click(name="foo-2", version="1.2", framework="")
357 subprocess.check_call(
358- [self.click_binary, "install", "--all-users", click_pkg],
359+ [self.click_binary, "install", "--all-users",
360+ "--allow-unauthenticated", click_pkg],
361 universal_newlines=True)
362 self.addCleanup(self.click_unregister, "@all", "foo-2")
363 # from the path
364
365=== added file 'tests/integration/test_signatures.py'
366--- tests/integration/test_signatures.py 1970-01-01 00:00:00 +0000
367+++ tests/integration/test_signatures.py 2014-08-22 17:12:39 +0000
368@@ -0,0 +1,348 @@
369+# Copyright (C) 2014 Canonical Ltd.
370+# Author: Michael Vogt <michael.vogt@ubuntu.com>
371+
372+# This program is free software: you can redistribute it and/or modify
373+# it under the terms of the GNU General Public License as published by
374+# the Free Software Foundation; version 3 of the License.
375+#
376+# This program is distributed in the hope that it will be useful,
377+# but WITHOUT ANY WARRANTY; without even the implied warranty of
378+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
379+# GNU General Public License for more details.
380+#
381+# You should have received a copy of the GNU General Public License
382+# along with this program. If not, see <http://www.gnu.org/licenses/>.
383+
384+"""Integration tests for the click signature checking."""
385+
386+import copy
387+import os
388+import shutil
389+import subprocess
390+import tarfile
391+import unittest
392+from textwrap import dedent
393+
394+from .helpers import (
395+ is_root,
396+ ClickTestCase,
397+)
398+
399+def makedirs(path):
400+ try:
401+ os.makedirs(path)
402+ except OSError:
403+ pass
404+
405+def get_keyid_from_gpghome(gpg_home):
406+ """Return the public keyid of a given gpg home dir"""
407+ output = subprocess.check_output(
408+ ["gpg", "--home", gpg_home, "--list-keys", "--with-colons"],
409+ universal_newlines=True)
410+ for line in output.splitlines():
411+ if not line.startswith("pub:"):
412+ continue
413+ return line.split(":")[4]
414+ raise ValueError("Cannot find public key in output: '%s'" % output)
415+
416+
417+class Debsigs:
418+ """Tiny wrapper around the debsigs CLI"""
419+ def __init__(self, gpghome, keyid):
420+ self.keyid = keyid
421+ self.gpghome = gpghome
422+ self.policy = "/etc/debsig/policies/%s/generic.pol" % self.keyid
423+
424+ def sign(self, filepath, signature_type="origin"):
425+ """Sign the click at filepath"""
426+ env = copy.copy(os.environ)
427+ env["GNUPGHOME"] = os.path.abspath(self.gpghome)
428+ subprocess.check_call(
429+ ["debsigs",
430+ "--sign=%s" % signature_type,
431+ "--default-key=%s" % self.keyid,
432+ filepath], env=env)
433+
434+ def install_signature_policy(self):
435+ """Install/update the system-wide signature policy"""
436+ xmls = dedent("""\
437+ <?xml version="1.0"?>
438+ <!DOCTYPE Policy SYSTEM "http://www.debian.org/debsig/1.0/policy.dtd">
439+ <Policy xmlns="http://www.debian.org/debsig/1.0/">
440+
441+ <Origin Name="test-origin" id="{keyid}" Description="Example policy"/>
442+ <Selection>
443+ <Required Type="origin" File="{filename}" id="{keyid}"/>
444+ </Selection>
445+
446+ <Verification>
447+ <Required Type="origin" File="{filename}" id="{keyid}"/>
448+ </Verification>
449+ </Policy>
450+ """.format(keyid=self.keyid, filename="origin.pub"))
451+ makedirs(os.path.dirname(self.policy))
452+ with open(self.policy, "w") as f:
453+ f.write(xmls)
454+ self.pubkey_path = (
455+ "/usr/share/debsig/keyrings/%s/origin.pub" % self.keyid)
456+ makedirs(os.path.dirname(self.pubkey_path))
457+ shutil.copy(os.path.join(self.gpghome, "pubring.gpg"), self.pubkey_path)
458+
459+ def uninstall_signature_policy(self):
460+ # FIXME: update debsig-verify so that it can work from a different
461+ # root than "/" so that the tests do not have to use the
462+ # system root
463+ os.remove(self.policy)
464+ os.remove(self.pubkey_path)
465+
466+
467+@unittest.skipIf(not is_root(), "This tests needs to run as root")
468+class ClickSignaturesTestCase(ClickTestCase):
469+ def assertClickNoSignatureError(self, cmd_args):
470+ with self.assertRaises(subprocess.CalledProcessError) as cm:
471+ output = subprocess.check_output(
472+ [self.click_binary] + cmd_args,
473+ stderr=subprocess.STDOUT, universal_newlines=True)
474+ output = cm.exception.output
475+ expected_error_message = ("debsig: Origin Signature check failed. "
476+ "This deb might not be signed.")
477+ self.assertIn(expected_error_message, output)
478+
479+ def assertClickInvalidSignatureError(self, cmd_args):
480+ with self.assertRaises(subprocess.CalledProcessError) as cm:
481+ output = subprocess.check_output(
482+ [self.click_binary] + cmd_args,
483+ stderr=subprocess.STDOUT, universal_newlines=True)
484+ print(output)
485+
486+ output = cm.exception.output
487+ expected_error_message = "Signature verification error: "
488+ self.assertIn(expected_error_message, output)
489+
490+
491+@unittest.skipIf(not is_root(), "This tests needs to run as root")
492+class TestSignatureVerificationNoSignature(ClickSignaturesTestCase):
493+ def test_debsig_verify_no_sig(self):
494+ name = "org.example.debsig-no-sig"
495+ path_to_click = self._make_click(name, framework="")
496+ self.assertClickNoSignatureError(["verify", path_to_click])
497+
498+ def test_debsig_install_no_sig(self):
499+ name = "org.example.debsig-no-sig"
500+ path_to_click = self._make_click(name, framework="")
501+ self.assertClickNoSignatureError(["install", path_to_click])
502+
503+ def test_debsig_install_can_install_with_sig_override(self):
504+ name = "org.example.debsig-no-sig"
505+ path_to_click = self._make_click(name, framework="")
506+ user = os.environ.get("USER", "root")
507+ subprocess.check_call(
508+ [self.click_binary, "install",
509+ "--allow-unauthenticated", "--user=%s" % user,
510+ path_to_click])
511+ self.addCleanup(
512+ subprocess.call, [self.click_binary, "unregister",
513+ "--user=%s" % user, name])
514+
515+
516+@unittest.skipIf(not is_root(), "This tests needs to run as root")
517+class TestSignatureVerification(ClickSignaturesTestCase):
518+ def setUp(self):
519+ super(TestSignatureVerification, self).setUp()
520+ self.user = os.environ.get("USER", "root")
521+ # the valid origin keyring
522+ self.datadir = os.path.join(os.path.dirname(__file__), "data")
523+ origin_keyring_dir = os.path.abspath(
524+ os.path.join(self.datadir, "origin-keyring"))
525+ keyid = get_keyid_from_gpghome(origin_keyring_dir)
526+ self.debsigs = Debsigs(origin_keyring_dir, keyid)
527+ self.debsigs.install_signature_policy()
528+
529+ def tearDown(self):
530+ self.debsigs.uninstall_signature_policy()
531+
532+ def test_debsig_install_valid_signature(self):
533+ name = "org.example.debsig-valid-sig"
534+ path_to_click = self._make_click(name, framework="")
535+ self.debsigs.sign(path_to_click)
536+ subprocess.check_call(
537+ [self.click_binary, "install",
538+ "--user=%s" % self.user,
539+ path_to_click])
540+ self.addCleanup(
541+ subprocess.call, [self.click_binary, "unregister",
542+ "--user=%s" % self.user, name])
543+ output = subprocess.check_output(
544+ [self.click_binary, "list", "--user=%s" % self.user],
545+ universal_newlines=True)
546+ self.assertIn(name, output)
547+
548+ def test_debsig_install_signature_not_in_keyring(self):
549+ name = "org.example.debsig-no-keyring-sig"
550+ path_to_click = self._make_click(name, framework="")
551+ evil_keyring_dir = os.path.join(self.datadir, "evil-keyring")
552+ keyid = get_keyid_from_gpghome(evil_keyring_dir)
553+ debsig_bad = Debsigs(evil_keyring_dir, keyid)
554+ debsig_bad.sign(path_to_click)
555+ # and ensure its really not there
556+ self.assertClickInvalidSignatureError(["install", path_to_click])
557+ output = subprocess.check_output(
558+ [self.click_binary, "list", "--user=%s" % self.user],
559+ universal_newlines=True)
560+ self.assertNotIn(name, output)
561+
562+ def test_debsig_install_not_a_signature(self):
563+ name = "org.example.debsig-invalid-sig"
564+ path_to_click = self._make_click(name, framework="")
565+ invalid_sig = os.path.join(self.temp_dir, "_gpgorigin")
566+ with open(invalid_sig, "w") as f:
567+ f.write("no-valid-signature")
568+ # add a invalid sig
569+ subprocess.check_call(["ar", "-r", path_to_click, invalid_sig])
570+ self.assertClickInvalidSignatureError(["install", path_to_click])
571+ output = subprocess.check_output(
572+ [self.click_binary, "list", "--user=%s" % self.user],
573+ universal_newlines=True)
574+ self.assertNotIn(name, output)
575+
576+ def test_debsig_install_signature_altered_click(self):
577+ def modify_ar_member(member):
578+ subprocess.check_call(
579+ ["ar", "-x", path_to_click, "control.tar.gz"],
580+ cwd=self.temp_dir)
581+ altered_member = os.path.join(self.temp_dir, member)
582+ with open(altered_member, "ba") as f:
583+ f.write(b"\0")
584+ subprocess.check_call(["ar", "-r", path_to_click, altered_member])
585+
586+ # ensure that all members we care about are checked by debsig-verify
587+ for member in ["control.tar.gz", "data.tar.gz", "debian-binary"]:
588+ name = "org.example.debsig-altered-click"
589+ path_to_click = self._make_click(name, framework="")
590+ self.debsigs.sign(path_to_click)
591+ modify_ar_member(member)
592+ self.assertClickInvalidSignatureError(["install", path_to_click])
593+ output = subprocess.check_output(
594+ [self.click_binary, "list", "--user=%s" % self.user],
595+ universal_newlines=True)
596+ self.assertNotIn(name, output)
597+
598+ def make_nasty_data_tar(self, compression):
599+ new_data_tar = os.path.join(self.temp_dir, "data.tar." + compression)
600+ evilfile = os.path.join(self.temp_dir, "README.evil")
601+ with open(evilfile, "w") as f:
602+ f.write("I am a nasty README")
603+ with tarfile.open(new_data_tar, "w:"+compression) as tar:
604+ tar.add(evilfile)
605+ return new_data_tar
606+
607+ def test_debsig_install_signature_injected_data_tar(self):
608+ name = "org.example.debsig-injected-data-click"
609+ path_to_click = self._make_click(name, framework="")
610+ self.debsigs.sign(path_to_click)
611+ new_data = self.make_nasty_data_tar("bz2")
612+ # insert before the real data.tar.gz and ensure this is caught
613+ # NOTE: that right now this will not be caught by debsig-verify
614+ # but later in audit() by debian.debfile.DebFile()
615+ subprocess.check_call(["ar",
616+ "-r",
617+ "-b", "data.tar.gz",
618+ path_to_click,
619+ new_data])
620+ output = subprocess.check_output(
621+ ["ar", "-t", path_to_click], universal_newlines=True)
622+ self.assertEqual(output.splitlines(),
623+ ["debian-binary",
624+ "_click-binary",
625+ "control.tar.gz",
626+ "data.tar.bz2",
627+ "data.tar.gz",
628+ "_gpgorigin"])
629+ with self.assertRaises(subprocess.CalledProcessError):
630+ output = subprocess.check_output(
631+ [self.click_binary, "install", path_to_click],
632+ stderr=subprocess.STDOUT, universal_newlines=True)
633+ output = subprocess.check_output(
634+ [self.click_binary, "list", "--user=%s" % self.user],
635+ universal_newlines=True)
636+ self.assertNotIn(name, output)
637+
638+ def test_debsig_install_signature_replaced_data_tar(self):
639+ name = "org.example.debsig-replaced-data-click"
640+ path_to_click = self._make_click(name, framework="")
641+ self.debsigs.sign(path_to_click)
642+ new_data = self.make_nasty_data_tar("bz2")
643+ # replace data.tar.gz with data.tar.bz2 and ensure this is caught
644+ subprocess.check_call(["ar",
645+ "-d",
646+ path_to_click,
647+ "data.tar.gz",
648+ ])
649+ subprocess.check_call(["ar",
650+ "-r",
651+ path_to_click,
652+ new_data])
653+ output = subprocess.check_output(
654+ ["ar", "-t", path_to_click], universal_newlines=True)
655+ self.assertEqual(output.splitlines(),
656+ ["debian-binary",
657+ "_click-binary",
658+ "control.tar.gz",
659+ "_gpgorigin",
660+ "data.tar.bz2",
661+ ])
662+ with self.assertRaises(subprocess.CalledProcessError) as cm:
663+ output = subprocess.check_output(
664+ [self.click_binary, "install", path_to_click],
665+ stderr=subprocess.STDOUT, universal_newlines=True)
666+ self.assertIn("Signature verification error", cm.exception.output)
667+ output = subprocess.check_output(
668+ [self.click_binary, "list", "--user=%s" % self.user],
669+ universal_newlines=True)
670+ self.assertNotIn(name, output)
671+
672+ def test_debsig_install_signature_prepend_sig(self):
673+ # this test is probably not really needed, it tries to trick
674+ # the system by prepending a valid signature that is not
675+ # in the keyring. But given that debsig-verify only reads
676+ # the first packet of any given _gpg$foo signature its
677+ # equivalent to test_debsig_install_signature_not_in_keyring test
678+ name = "org.example.debsig-replaced-data-prepend-sig-click"
679+ path_to_click = self._make_click(name, framework="")
680+ self.debsigs.sign(path_to_click)
681+ new_data = self.make_nasty_data_tar("gz")
682+ # replace data.tar.gz
683+ subprocess.check_call(["ar",
684+ "-r",
685+ path_to_click,
686+ new_data,
687+ ])
688+ # get previous good _gpgorigin for the old data
689+ subprocess.check_call(
690+ ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir)
691+ with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f:
692+ good_gpg_origin = f.read()
693+ # and append a valid signature from a non-keyring key
694+ evil_keyring_dir = os.path.join(self.datadir, "evil-keyring")
695+ debsig_bad = Debsigs(evil_keyring_dir, "18B38B9AC1B67A0D")
696+ debsig_bad.sign(path_to_click)
697+ subprocess.check_call(
698+ ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir)
699+ with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f:
700+ evil_gpg_origin = f.read()
701+ with open(os.path.join(self.temp_dir, "_gpgorigin"), "wb") as f:
702+ f.write(evil_gpg_origin)
703+ f.write(good_gpg_origin)
704+ subprocess.check_call(
705+ ["ar", "-r", path_to_click, "_gpgorigin"], cwd=self.temp_dir)
706+ # now ensure that the verification fails as well
707+ with self.assertRaises(subprocess.CalledProcessError) as cm:
708+ output = subprocess.check_output(
709+ [self.click_binary, "install", path_to_click],
710+ stderr=subprocess.STDOUT, universal_newlines=True)
711+ self.assertIn("Signature verification error", cm.exception.output)
712+ output = subprocess.check_output(
713+ [self.click_binary, "list", "--user=%s" % self.user],
714+ universal_newlines=True)
715+ self.assertNotIn(name, output)
716+
717
718=== modified file 'tests/integration/test_verify.py'
719--- tests/integration/test_verify.py 2014-06-26 12:00:09 +0000
720+++ tests/integration/test_verify.py 2014-08-22 17:12:39 +0000
721@@ -22,9 +22,11 @@
722
723 class TestVerify(ClickTestCase):
724 def test_verify_ok(self):
725- name = "com.ubuntu.verify-ok"
726+ name = "com.example.verify-ok"
727 path_to_click = self._make_click(name)
728 output = subprocess.check_output([
729- self.click_binary, "verify", "--force-missing-framework",
730+ self.click_binary, "verify",
731+ "--force-missing-framework",
732+ "--allow-unauthenticated",
733 path_to_click], universal_newlines=True)
734 self.assertEqual(output, "")

Subscribers

People subscribed via source and target branches

to all changes: