Merge lp:~mvo/click/debsigs-verify into lp:click/devel
- debsigs-verify
- Merge into devel
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 |
Related bugs: |
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/
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/
(Unrelated) A fix so that "debsigs --delete" produces the exactly same deb/click as before the "debsigs --sign": https:/
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:498
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 499. By Michael Vogt
-
click/tests/
test_install. py: fix test when no debsig-verify is installed - 500. By Michael Vogt
-
tests/integrati
on/test_ signatures. py: cleanup
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:500
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
Colin Watson (cjwatson) wrote : | # |
Argh, the previous review ate my inline comments.
- 501. By Michael Vogt
-
address Colins review comments (thanks!)
- 502. By Michael Vogt
-
fix integration tests and add --allow-
unauthenticated to click audit too
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:502
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 503. By Michael Vogt
-
merged with lp:click/devel
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:503
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:503
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:503
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
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' |
187 | Binary 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' |
189 | Binary 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' |
191 | Binary 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' |
194 | Binary 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' |
196 | Binary 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' |
198 | Binary 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, "") |
FAILED: Continuous integration, rev:498 /code.launchpad .net/~mvo/ click/debsigs- verify/ +merge/ 226652/ +edit-commit- message
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:/
http:// jenkins. qa.ubuntu. com/job/ click-devel- ci/37/ jenkins. qa.ubuntu. com/job/ click-devel- utopic- amd64-ci/ 39/console jenkins. qa.ubuntu. com/job/ click-devel- utopic- armhf-ci/ 37/console jenkins. qa.ubuntu. com/job/ click-devel- utopic- i386-ci/ 37/console
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/click- devel-ci/ 37/rebuild
http://