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