Merge lp:~mvo/click/debsigs-verify into lp:click/devel

Proposed by Michael Vogt
Status: Merged
Approved by: Colin Watson
Approved revision: 503
Merged at revision: 496
Proposed branch: lp:~mvo/click/debsigs-verify
Merge into: lp:click/devel
Diff against target: 566 lines (+438/-6)
7 files modified
click/commands/install.py (+6/-1)
click/commands/verify.py (+6/-1)
click/install.py (+52/-1)
click/tests/test_install.py (+23/-0)
debian/tests/control (+1/-1)
tests/integration/test_signatures.py (+346/-0)
tests/integration/test_verify.py (+4/-2)
To merge this branch: bzr merge lp:~mvo/click/debsigs-verify
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Colin Watson Approve
Review via email: mp+226652@code.launchpad.net

Commit message

Add support for click package gpg signatures (LP: #1330770)

Description of the change

This branch adds signature checking via debsigs/debsig-verfiy to click.

Source package for the new default policy at: lp:~mvo/click/click-ubuntu-policy (needs the real store pubkey)

It needs a MIR for debsigs/debsig-verify before we can activate it as it requires both as test dependencies for the integration test.

(Unrelated) A fix so that "debsigs --delete" produces the exactly same deb/click as before the "debsigs --sign": https://gitorious.org/debsigs/debsigs/merge_requests/1

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:498
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~mvo/click/debsigs-verify/+merge/226652/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/click-devel-ci/37/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-amd64-ci/39/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/37/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-i386-ci/37/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/click-devel-ci/37/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mvo/click/debsigs-verify updated
499. By Michael Vogt

click/tests/test_install.py: fix test when no debsig-verify is installed

500. By Michael Vogt

tests/integration/test_signatures.py: cleanup

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Colin Watson (cjwatson) wrote :

This looks generally great, thanks. It would be good if you could fix up the various nitpicks here; once you've done that, feel free to merge to lp:click/devel.

Note that the autopkgtest infrastructure doesn't restrict packages in main to be tested using only packages in main, so there's no need to deal with MIRs for debsigs or debsig-verify before landing this.

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

Argh, the previous review ate my inline comments.

lp:~mvo/click/debsigs-verify updated
501. By Michael Vogt

address Colins review comments (thanks!)

502. By Michael Vogt

fix integration tests and add --allow-unauthenticated to click audit too

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mvo/click/debsigs-verify updated
503. By Michael Vogt

merged with lp:click/devel

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: