Merge lp:~apw/launchpad/uefi-auto-key into lp:launchpad
- uefi-auto-key
- Merge into devel
Proposed by
Andy Whitcroft
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Colin Watson | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 18015 | ||||
Proposed branch: | lp:~apw/launchpad/uefi-auto-key | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
443 lines (+221/-50) 4 files modified
lib/lp/archivepublisher/config.py (+3/-0) lib/lp/archivepublisher/tests/test_config.py (+5/-0) lib/lp/archivepublisher/tests/test_uefi.py (+141/-27) lib/lp/archivepublisher/uefi.py (+72/-23) |
||||
To merge this branch: | bzr merge lp:~apw/launchpad/uefi-auto-key | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Andy Whitcroft (community) | Abstain | ||
Review via email: mp+293087@code.launchpad.net |
Commit message
Move the existing raw-uefi custom upload to validating the uefi signing keys on first use. For PPAs generate the keys on first use if they are missing.
Description of the change
Update the uefi custom upload to optionally manage key generation. Also in preparation for signing generification move key validation/
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Fixing
Revision history for this message
Andy Whitcroft (apw) : | # |
review:
Abstain
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
Definitely getting there, but still some things to fix, mostly in the tests.
review:
Needs Fixing
Revision history for this message
Andy Whitcroft (apw) wrote : | # |
Ok. I pushed up another attempt. I hope this one covers your remaining review comments. As expected the majority of the diff is in the tests. Looking at the diff against mainline there is actually very little delta for the operational code (also as expected).
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/archivepublisher/config.py' | |||
2 | --- lib/lp/archivepublisher/config.py 2016-02-05 16:51:12 +0000 | |||
3 | +++ lib/lp/archivepublisher/config.py 2016-05-03 11:13:13 +0000 | |||
4 | @@ -79,12 +79,15 @@ | |||
5 | 79 | 79 | ||
6 | 80 | if archive.is_main: | 80 | if archive.is_main: |
7 | 81 | pubconf.uefiroot = pubconf.archiveroot + '-uefi' | 81 | pubconf.uefiroot = pubconf.archiveroot + '-uefi' |
8 | 82 | pubconf.uefiautokey = False | ||
9 | 82 | elif archive.is_ppa: | 83 | elif archive.is_ppa: |
10 | 83 | pubconf.uefiroot = os.path.join( | 84 | pubconf.uefiroot = os.path.join( |
11 | 84 | ppa_config.signing_keys_root, "uefi", | 85 | ppa_config.signing_keys_root, "uefi", |
12 | 85 | archive.owner.name, archive.name) | 86 | archive.owner.name, archive.name) |
13 | 87 | pubconf.uefiautokey = True | ||
14 | 86 | else: | 88 | else: |
15 | 87 | pubconf.uefiroot = None | 89 | pubconf.uefiroot = None |
16 | 90 | pubconf.uefiautokey = False | ||
17 | 88 | 91 | ||
18 | 89 | pubconf.poolroot = os.path.join(pubconf.archiveroot, 'pool') | 92 | pubconf.poolroot = os.path.join(pubconf.archiveroot, 'pool') |
19 | 90 | pubconf.distsroot = os.path.join(pubconf.archiveroot, 'dists') | 93 | pubconf.distsroot = os.path.join(pubconf.archiveroot, 'dists') |
20 | 91 | 94 | ||
21 | === modified file 'lib/lp/archivepublisher/tests/test_config.py' | |||
22 | --- lib/lp/archivepublisher/tests/test_config.py 2016-01-13 17:09:34 +0000 | |||
23 | +++ lib/lp/archivepublisher/tests/test_config.py 2016-05-03 11:13:13 +0000 | |||
24 | @@ -47,6 +47,7 @@ | |||
25 | 47 | self.assertEqual( | 47 | self.assertEqual( |
26 | 48 | self.root + "/ubuntutest-temp", primary_config.temproot) | 48 | self.root + "/ubuntutest-temp", primary_config.temproot) |
27 | 49 | self.assertEqual(archiveroot + "-uefi", primary_config.uefiroot) | 49 | self.assertEqual(archiveroot + "-uefi", primary_config.uefiroot) |
28 | 50 | self.assertFalse(primary_config.uefiautokey) | ||
29 | 50 | self.assertIs(None, primary_config.metaroot) | 51 | self.assertIs(None, primary_config.metaroot) |
30 | 51 | self.assertEqual(archiveroot + "-staging", primary_config.stagingroot) | 52 | self.assertEqual(archiveroot + "-staging", primary_config.stagingroot) |
31 | 52 | 53 | ||
32 | @@ -70,6 +71,7 @@ | |||
33 | 70 | self.assertEqual( | 71 | self.assertEqual( |
34 | 71 | self.root + "/ubuntutest-temp", partner_config.temproot) | 72 | self.root + "/ubuntutest-temp", partner_config.temproot) |
35 | 72 | self.assertEqual(archiveroot + "-uefi", partner_config.uefiroot) | 73 | self.assertEqual(archiveroot + "-uefi", partner_config.uefiroot) |
36 | 74 | self.assertFalse(partner_config.uefiautokey) | ||
37 | 73 | self.assertIs(None, partner_config.metaroot) | 75 | self.assertIs(None, partner_config.metaroot) |
38 | 74 | self.assertEqual(archiveroot + "-staging", partner_config.stagingroot) | 76 | self.assertEqual(archiveroot + "-staging", partner_config.stagingroot) |
39 | 75 | 77 | ||
40 | @@ -92,6 +94,7 @@ | |||
41 | 92 | self.assertEqual(archiveroot + "-misc", copy_config.miscroot) | 94 | self.assertEqual(archiveroot + "-misc", copy_config.miscroot) |
42 | 93 | self.assertEqual(archiveroot + "-temp", copy_config.temproot) | 95 | self.assertEqual(archiveroot + "-temp", copy_config.temproot) |
43 | 94 | self.assertIsNone(copy_config.uefiroot) | 96 | self.assertIsNone(copy_config.uefiroot) |
44 | 97 | self.assertFalse(copy_config.uefiautokey) | ||
45 | 95 | self.assertIs(None, copy_config.metaroot) | 98 | self.assertIs(None, copy_config.metaroot) |
46 | 96 | self.assertIs(None, copy_config.stagingroot) | 99 | self.assertIs(None, copy_config.stagingroot) |
47 | 97 | 100 | ||
48 | @@ -131,6 +134,7 @@ | |||
49 | 131 | uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( | 134 | uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( |
50 | 132 | self.ppa.owner.name, self.ppa.name) | 135 | self.ppa.owner.name, self.ppa.name) |
51 | 133 | self.assertEqual(uefiroot, self.ppa_config.uefiroot) | 136 | self.assertEqual(uefiroot, self.ppa_config.uefiroot) |
52 | 137 | self.assertTrue(self.ppa_config.uefiautokey) | ||
53 | 134 | self.assertIs(None, self.ppa_config.metaroot) | 138 | self.assertIs(None, self.ppa_config.metaroot) |
54 | 135 | self.assertIs(None, self.ppa_config.stagingroot) | 139 | self.assertIs(None, self.ppa_config.stagingroot) |
55 | 136 | 140 | ||
56 | @@ -165,6 +169,7 @@ | |||
57 | 165 | uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( | 169 | uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( |
58 | 166 | p3a.owner.name, p3a.name) | 170 | p3a.owner.name, p3a.name) |
59 | 167 | self.assertEqual(uefiroot, p3a_config.uefiroot) | 171 | self.assertEqual(uefiroot, p3a_config.uefiroot) |
60 | 172 | self.assertTrue(self.ppa_config.uefiautokey) | ||
61 | 168 | self.assertIs(None, p3a_config.metaroot) | 173 | self.assertIs(None, p3a_config.metaroot) |
62 | 169 | self.assertIs(None, p3a_config.stagingroot) | 174 | self.assertIs(None, p3a_config.stagingroot) |
63 | 170 | 175 | ||
64 | 171 | 176 | ||
65 | === modified file 'lib/lp/archivepublisher/tests/test_uefi.py' | |||
66 | --- lib/lp/archivepublisher/tests/test_uefi.py 2016-02-05 16:51:12 +0000 | |||
67 | +++ lib/lp/archivepublisher/tests/test_uefi.py 2016-05-03 11:13:13 +0000 | |||
68 | @@ -7,6 +7,8 @@ | |||
69 | 7 | 7 | ||
70 | 8 | import os | 8 | import os |
71 | 9 | 9 | ||
72 | 10 | from fixtures import MonkeyPatch | ||
73 | 11 | |||
74 | 10 | from lp.archivepublisher.customupload import ( | 12 | from lp.archivepublisher.customupload import ( |
75 | 11 | CustomUploadAlreadyExists, | 13 | CustomUploadAlreadyExists, |
76 | 12 | CustomUploadBadUmask, | 14 | CustomUploadBadUmask, |
77 | @@ -18,11 +20,35 @@ | |||
78 | 18 | from lp.testing.fakemethod import FakeMethod | 20 | from lp.testing.fakemethod import FakeMethod |
79 | 19 | 21 | ||
80 | 20 | 22 | ||
81 | 23 | class FakeMethodGenUefiKeys(FakeMethod): | ||
82 | 24 | """Fake execution of generation of Uefi keys pairs.""" | ||
83 | 25 | def __init__(self, upload=None, *args, **kwargs): | ||
84 | 26 | super(FakeMethodGenUefiKeys, self).__init__(*args, **kwargs) | ||
85 | 27 | self.upload = upload | ||
86 | 28 | |||
87 | 29 | def __call__(self, *args, **kwargs): | ||
88 | 30 | super(FakeMethodGenUefiKeys, self).__call__(*args, **kwargs) | ||
89 | 31 | |||
90 | 32 | write_file(self.upload.key, "") | ||
91 | 33 | write_file(self.upload.cert, "") | ||
92 | 34 | |||
93 | 35 | |||
94 | 21 | class FakeConfig: | 36 | class FakeConfig: |
99 | 22 | """A fake publisher configuration.""" | 37 | """A fake publisher configuration for the main archive.""" |
100 | 23 | def __init__(self, archiveroot, uefiroot): | 38 | def __init__(self, distroroot, uefiroot): |
101 | 24 | self.archiveroot = archiveroot | 39 | self.distroroot = distroroot |
102 | 25 | self.uefiroot = uefiroot | 40 | self.uefiroot = uefiroot |
103 | 41 | self.archiveroot = os.path.join(self.distroroot, 'ubuntu') | ||
104 | 42 | self.uefiautokey = False | ||
105 | 43 | |||
106 | 44 | |||
107 | 45 | class FakeConfigPPA: | ||
108 | 46 | """A fake publisher configuration for a PPA.""" | ||
109 | 47 | def __init__(self, distroroot, uefiroot, owner, ppa): | ||
110 | 48 | self.distroroot = distroroot | ||
111 | 49 | self.uefiroot = uefiroot | ||
112 | 50 | self.archiveroot = os.path.join(self.distroroot, owner, ppa, 'ubuntu') | ||
113 | 51 | self.uefiautokey = True | ||
114 | 26 | 52 | ||
115 | 27 | 53 | ||
116 | 28 | class TestUefi(TestCase): | 54 | class TestUefi(TestCase): |
117 | @@ -37,11 +63,17 @@ | |||
118 | 37 | old_umask = os.umask(0o022) | 63 | old_umask = os.umask(0o022) |
119 | 38 | self.addCleanup(os.umask, old_umask) | 64 | self.addCleanup(os.umask, old_umask) |
120 | 39 | 65 | ||
122 | 40 | def setUpKeyAndCert(self): | 66 | def setUpPPA(self): |
123 | 67 | self.pubconf = FakeConfigPPA(self.temp_dir, self.uefi_dir, | ||
124 | 68 | 'ubuntu-archive', 'testing') | ||
125 | 69 | self.testcase_cn = '/CN=PPA ubuntu-archive testing/' | ||
126 | 70 | |||
127 | 71 | def setUpKeyAndCert(self, create=True): | ||
128 | 41 | self.key = os.path.join(self.uefi_dir, "uefi.key") | 72 | self.key = os.path.join(self.uefi_dir, "uefi.key") |
129 | 42 | self.cert = os.path.join(self.uefi_dir, "uefi.crt") | 73 | self.cert = os.path.join(self.uefi_dir, "uefi.crt") |
132 | 43 | write_file(self.key, "") | 74 | if create: |
133 | 44 | write_file(self.cert, "") | 75 | write_file(self.key, "") |
134 | 76 | write_file(self.cert, "") | ||
135 | 45 | 77 | ||
136 | 46 | def openArchive(self, loader_type, version, arch): | 78 | def openArchive(self, loader_type, version, arch): |
137 | 47 | self.path = os.path.join( | 79 | self.path = os.path.join( |
138 | @@ -52,41 +84,48 @@ | |||
139 | 52 | def process(self): | 84 | def process(self): |
140 | 53 | self.archive.close() | 85 | self.archive.close() |
141 | 54 | self.buffer.close() | 86 | self.buffer.close() |
142 | 87 | fake_call = FakeMethod() | ||
143 | 55 | upload = UefiUpload() | 88 | upload = UefiUpload() |
146 | 56 | upload.sign = FakeMethod() | 89 | upload.signUefi = FakeMethod() |
147 | 57 | upload.process(self.pubconf, self.path, self.suite) | 90 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): |
148 | 91 | upload.process(self.pubconf, self.path, self.suite) | ||
149 | 92 | # Under no circumstances is it safe to execute actual commands. | ||
150 | 93 | self.assertEqual(0, fake_call.call_count) | ||
151 | 94 | |||
152 | 58 | return upload | 95 | return upload |
153 | 59 | 96 | ||
154 | 60 | def getUefiPath(self, loader_type, arch): | 97 | def getUefiPath(self, loader_type, arch): |
155 | 61 | return os.path.join( | 98 | return os.path.join( |
157 | 62 | self.temp_dir, "dists", self.suite, "main", "uefi", | 99 | self.pubconf.archiveroot, "dists", self.suite, "main", "uefi", |
158 | 63 | "%s-%s" % (loader_type, arch)) | 100 | "%s-%s" % (loader_type, arch)) |
159 | 64 | 101 | ||
160 | 65 | def test_unconfigured(self): | 102 | def test_unconfigured(self): |
161 | 66 | # If there is no key/cert configuration, processing succeeds but | 103 | # If there is no key/cert configuration, processing succeeds but |
163 | 67 | # nothing is signed. | 104 | # nothing is signed. Signing is attempted. |
164 | 68 | self.pubconf = FakeConfig(self.temp_dir, None) | 105 | self.pubconf = FakeConfig(self.temp_dir, None) |
165 | 69 | self.openArchive("test", "1.0", "amd64") | 106 | self.openArchive("test", "1.0", "amd64") |
166 | 70 | self.archive.add_file("1.0/empty.efi", "") | 107 | self.archive.add_file("1.0/empty.efi", "") |
167 | 71 | upload = self.process() | 108 | upload = self.process() |
169 | 72 | self.assertEqual(0, upload.sign.call_count) | 109 | self.assertEqual(1, upload.signUefi.call_count) |
170 | 73 | 110 | ||
171 | 74 | def test_missing_key_and_cert(self): | 111 | def test_missing_key_and_cert(self): |
172 | 75 | # If the configured key/cert are missing, processing succeeds but | 112 | # If the configured key/cert are missing, processing succeeds but |
174 | 76 | # nothing is signed. | 113 | # nothing is signed. Signing is attempted. |
175 | 77 | self.openArchive("test", "1.0", "amd64") | 114 | self.openArchive("test", "1.0", "amd64") |
176 | 78 | self.archive.add_file("1.0/empty.efi", "") | 115 | self.archive.add_file("1.0/empty.efi", "") |
177 | 79 | upload = self.process() | 116 | upload = self.process() |
179 | 80 | self.assertEqual(0, upload.sign.call_count) | 117 | self.assertEqual(1, upload.signUefi.call_count) |
180 | 81 | 118 | ||
181 | 82 | def test_no_efi_files(self): | 119 | def test_no_efi_files(self): |
182 | 83 | # Tarballs containing no *.efi files are extracted without complaint. | 120 | # Tarballs containing no *.efi files are extracted without complaint. |
183 | 121 | # Nothing is signed. | ||
184 | 84 | self.setUpKeyAndCert() | 122 | self.setUpKeyAndCert() |
185 | 85 | self.openArchive("empty", "1.0", "amd64") | 123 | self.openArchive("empty", "1.0", "amd64") |
186 | 86 | self.archive.add_file("1.0/hello", "world") | 124 | self.archive.add_file("1.0/hello", "world") |
188 | 87 | self.process() | 125 | upload = self.process() |
189 | 88 | self.assertTrue(os.path.exists(os.path.join( | 126 | self.assertTrue(os.path.exists(os.path.join( |
190 | 89 | self.getUefiPath("empty", "amd64"), "1.0", "hello"))) | 127 | self.getUefiPath("empty", "amd64"), "1.0", "hello"))) |
191 | 128 | self.assertEqual(0, upload.signUefi.call_count) | ||
192 | 90 | 129 | ||
193 | 91 | def test_already_exists(self): | 130 | def test_already_exists(self): |
194 | 92 | # If the target directory already exists, processing fails. | 131 | # If the target directory already exists, processing fails. |
195 | @@ -104,15 +143,60 @@ | |||
196 | 104 | os.umask(0o002) # cleanup already handled by setUp | 143 | os.umask(0o002) # cleanup already handled by setUp |
197 | 105 | self.assertRaises(CustomUploadBadUmask, self.process) | 144 | self.assertRaises(CustomUploadBadUmask, self.process) |
198 | 106 | 145 | ||
201 | 107 | def test_correct_signing_command(self): | 146 | def test_correct_uefi_signing_command_executed(self): |
202 | 108 | # getSigningCommand returns the correct command. | 147 | # Check that calling signUefi() will generate the expected command |
203 | 148 | # when appropriate keys are present. | ||
204 | 109 | self.setUpKeyAndCert() | 149 | self.setUpKeyAndCert() |
211 | 110 | upload = UefiUpload() | 150 | fake_call = FakeMethod() |
212 | 111 | upload.setTargetDirectory( | 151 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): |
213 | 112 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") | 152 | upload = UefiUpload() |
214 | 113 | expected_command = [ | 153 | upload.generateUefiKeys = FakeMethod() |
215 | 114 | "sbsign", "--key", self.key, "--cert", self.cert, "t.efi"] | 154 | upload.setTargetDirectory( |
216 | 115 | self.assertEqual(expected_command, upload.getSigningCommand("t.efi")) | 155 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
217 | 156 | upload.signUefi('t.efi') | ||
218 | 157 | self.assertEqual(1, fake_call.call_count) | ||
219 | 158 | # Assert command form. | ||
220 | 159 | args = fake_call.calls[0][0][0] | ||
221 | 160 | expected_cmd = [ | ||
222 | 161 | 'sbsign', '--key', self.key, '--cert', self.cert, 't.efi', | ||
223 | 162 | ] | ||
224 | 163 | self.assertEqual(expected_cmd, args) | ||
225 | 164 | self.assertEqual(0, upload.generateUefiKeys.call_count) | ||
226 | 165 | |||
227 | 166 | def test_correct_uefi_signing_command_executed_no_keys(self): | ||
228 | 167 | # Check that calling signUefi() will generate no commands when | ||
229 | 168 | # no keys are present. | ||
230 | 169 | self.setUpKeyAndCert(create=False) | ||
231 | 170 | fake_call = FakeMethod() | ||
232 | 171 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): | ||
233 | 172 | upload = UefiUpload() | ||
234 | 173 | upload.generateUefiKeys = FakeMethod() | ||
235 | 174 | upload.setTargetDirectory( | ||
236 | 175 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") | ||
237 | 176 | upload.signUefi('t.efi') | ||
238 | 177 | self.assertEqual(0, fake_call.call_count) | ||
239 | 178 | self.assertEqual(0, upload.generateUefiKeys.call_count) | ||
240 | 179 | |||
241 | 180 | def test_correct_uefi_keygen_command_executed(self): | ||
242 | 181 | # Check that calling generateUefiKeys() will generate the | ||
243 | 182 | # expected command. | ||
244 | 183 | self.setUpPPA() | ||
245 | 184 | self.setUpKeyAndCert(create=False) | ||
246 | 185 | fake_call = FakeMethod() | ||
247 | 186 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): | ||
248 | 187 | upload = UefiUpload() | ||
249 | 188 | upload.setTargetDirectory( | ||
250 | 189 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") | ||
251 | 190 | upload.generateUefiKeys() | ||
252 | 191 | self.assertEqual(1, fake_call.call_count) | ||
253 | 192 | # Assert the actual command matches. | ||
254 | 193 | args = fake_call.calls[0][0][0] | ||
255 | 194 | expected_cmd = [ | ||
256 | 195 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', | ||
257 | 196 | '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert, | ||
258 | 197 | '-days', '3650', '-nodes', '-sha256', | ||
259 | 198 | ] | ||
260 | 199 | self.assertEqual(expected_cmd, args) | ||
261 | 116 | 200 | ||
262 | 117 | def test_signs_image(self): | 201 | def test_signs_image(self): |
263 | 118 | # Each image in the tarball is signed. | 202 | # Each image in the tarball is signed. |
264 | @@ -120,10 +204,7 @@ | |||
265 | 120 | self.openArchive("test", "1.0", "amd64") | 204 | self.openArchive("test", "1.0", "amd64") |
266 | 121 | self.archive.add_file("1.0/empty.efi", "") | 205 | self.archive.add_file("1.0/empty.efi", "") |
267 | 122 | upload = self.process() | 206 | upload = self.process() |
272 | 123 | self.assertEqual(1, upload.sign.call_count) | 207 | self.assertEqual(1, upload.signUefi.call_count) |
269 | 124 | self.assertEqual(1, len(upload.sign.calls[0][0])) | ||
270 | 125 | self.assertEqual( | ||
271 | 126 | "empty.efi", os.path.basename(upload.sign.calls[0][0][0])) | ||
273 | 127 | 208 | ||
274 | 128 | def test_installed(self): | 209 | def test_installed(self): |
275 | 129 | # Files in the tarball are installed correctly. | 210 | # Files in the tarball are installed correctly. |
276 | @@ -133,3 +214,36 @@ | |||
277 | 133 | self.process() | 214 | self.process() |
278 | 134 | self.assertTrue(os.path.exists(os.path.join( | 215 | self.assertTrue(os.path.exists(os.path.join( |
279 | 135 | self.getUefiPath("test", "amd64"), "1.0", "empty.efi"))) | 216 | self.getUefiPath("test", "amd64"), "1.0", "empty.efi"))) |
280 | 217 | |||
281 | 218 | def test_create_uefi_keys_autokey_off(self): | ||
282 | 219 | # Keys are not created. | ||
283 | 220 | self.setUpKeyAndCert(create=False) | ||
284 | 221 | self.assertFalse(os.path.exists(self.key)) | ||
285 | 222 | self.assertFalse(os.path.exists(self.cert)) | ||
286 | 223 | fake_call = FakeMethod() | ||
287 | 224 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): | ||
288 | 225 | upload = UefiUpload() | ||
289 | 226 | upload.generateUefiKeys = FakeMethodGenUefiKeys(upload=upload) | ||
290 | 227 | upload.setTargetDirectory( | ||
291 | 228 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") | ||
292 | 229 | upload.signUefi('t.efi') | ||
293 | 230 | self.assertEqual(0, upload.generateUefiKeys.call_count) | ||
294 | 231 | self.assertFalse(os.path.exists(self.key)) | ||
295 | 232 | self.assertFalse(os.path.exists(self.cert)) | ||
296 | 233 | |||
297 | 234 | def test_create_uefi_keys_autokey_on(self): | ||
298 | 235 | # Keys are created on demand. | ||
299 | 236 | self.setUpPPA() | ||
300 | 237 | self.setUpKeyAndCert(create=False) | ||
301 | 238 | self.assertFalse(os.path.exists(self.key)) | ||
302 | 239 | self.assertFalse(os.path.exists(self.cert)) | ||
303 | 240 | fake_call = FakeMethod() | ||
304 | 241 | with self.useFixture(MonkeyPatch("subprocess.call", fake_call)): | ||
305 | 242 | upload = UefiUpload() | ||
306 | 243 | upload.generateUefiKeys = FakeMethodGenUefiKeys(upload=upload) | ||
307 | 244 | upload.setTargetDirectory( | ||
308 | 245 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") | ||
309 | 246 | upload.signUefi('t.efi') | ||
310 | 247 | self.assertEqual(1, upload.generateUefiKeys.call_count) | ||
311 | 248 | self.assertTrue(os.path.exists(self.key)) | ||
312 | 249 | self.assertTrue(os.path.exists(self.cert)) | ||
313 | 136 | 250 | ||
314 | === modified file 'lib/lp/archivepublisher/uefi.py' | |||
315 | --- lib/lp/archivepublisher/uefi.py 2013-07-25 16:45:20 +0000 | |||
316 | +++ lib/lp/archivepublisher/uefi.py 2016-05-03 11:13:13 +0000 | |||
317 | @@ -68,24 +68,17 @@ | |||
318 | 68 | self.logger.warning("No UEFI root configured for this archive") | 68 | self.logger.warning("No UEFI root configured for this archive") |
319 | 69 | self.key = None | 69 | self.key = None |
320 | 70 | self.cert = None | 70 | self.cert = None |
321 | 71 | self.autokey = False | ||
322 | 71 | else: | 72 | else: |
323 | 72 | self.key = os.path.join(pubconf.uefiroot, "uefi.key") | 73 | self.key = os.path.join(pubconf.uefiroot, "uefi.key") |
324 | 73 | self.cert = os.path.join(pubconf.uefiroot, "uefi.crt") | 74 | self.cert = os.path.join(pubconf.uefiroot, "uefi.crt") |
335 | 74 | if not os.access(self.key, os.R_OK): | 75 | self.autokey = pubconf.uefiautokey |
326 | 75 | if self.logger is not None: | ||
327 | 76 | self.logger.warning( | ||
328 | 77 | "UEFI private key %s not readable" % self.key) | ||
329 | 78 | self.key = None | ||
330 | 79 | if not os.access(self.cert, os.R_OK): | ||
331 | 80 | if self.logger is not None: | ||
332 | 81 | self.logger.warning( | ||
333 | 82 | "UEFI certificate %s not readable" % self.cert) | ||
334 | 83 | self.cert = None | ||
336 | 84 | 76 | ||
337 | 85 | self.setComponents(tarfile_path) | 77 | self.setComponents(tarfile_path) |
338 | 86 | self.targetdir = os.path.join( | 78 | self.targetdir = os.path.join( |
339 | 87 | pubconf.archiveroot, "dists", distroseries, "main", "uefi", | 79 | pubconf.archiveroot, "dists", distroseries, "main", "uefi", |
340 | 88 | "%s-%s" % (self.loader_type, self.arch)) | 80 | "%s-%s" % (self.loader_type, self.arch)) |
341 | 81 | self.archiveroot = pubconf.archiveroot | ||
342 | 89 | 82 | ||
343 | 90 | @classmethod | 83 | @classmethod |
344 | 91 | def getSeriesKey(cls, tarfile_path): | 84 | def getSeriesKey(cls, tarfile_path): |
345 | @@ -102,17 +95,74 @@ | |||
346 | 102 | if filename.endswith(".efi"): | 95 | if filename.endswith(".efi"): |
347 | 103 | yield os.path.join(dirpath, filename) | 96 | yield os.path.join(dirpath, filename) |
348 | 104 | 97 | ||
356 | 105 | def getSigningCommand(self, image): | 98 | def generateUefiKeys(self): |
357 | 106 | """Return the command used to sign an image.""" | 99 | """Generate new UEFI Keys for this archive.""" |
358 | 107 | return ["sbsign", "--key", self.key, "--cert", self.cert, image] | 100 | directory = os.path.dirname(self.key) |
359 | 108 | 101 | if not os.path.exists(directory): | |
360 | 109 | def sign(self, image): | 102 | os.makedirs(directory) |
361 | 110 | """Sign an image.""" | 103 | |
362 | 111 | if subprocess.call(self.getSigningCommand(image)) != 0: | 104 | # XXX: pull out the PPA owner and name to seed key CN |
363 | 105 | archive_name = os.path.dirname(self.archiveroot) | ||
364 | 106 | owner_name = os.path.basename(os.path.dirname(archive_name)) | ||
365 | 107 | archive_name = os.path.basename(archive_name) | ||
366 | 108 | common_name = '/CN=PPA ' + owner_name + ' ' + archive_name + '/' | ||
367 | 109 | |||
368 | 110 | old_mask = os.umask(0o077) | ||
369 | 111 | try: | ||
370 | 112 | new_key_cmd = [ | ||
371 | 113 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', | ||
372 | 114 | '-subj', common_name, '-keyout', self.key, '-out', self.cert, | ||
373 | 115 | '-days', '3650', '-nodes', '-sha256', | ||
374 | 116 | ] | ||
375 | 117 | if subprocess.call(new_key_cmd) != 0: | ||
376 | 118 | # Just log this rather than failing, since custom upload errors | ||
377 | 119 | # tend to make the publisher rather upset. | ||
378 | 120 | if self.logger is not None: | ||
379 | 121 | self.logger.warning( | ||
380 | 122 | "Failed to generate UEFI signing keys for %s" % | ||
381 | 123 | common_name) | ||
382 | 124 | finally: | ||
383 | 125 | os.umask(old_mask) | ||
384 | 126 | |||
385 | 127 | if os.path.exists(self.cert): | ||
386 | 128 | os.chmod(self.cert, 0o644) | ||
387 | 129 | |||
388 | 130 | def getUefiKeys(self): | ||
389 | 131 | """Validate and return the uefi key and cert for encryption.""" | ||
390 | 132 | |||
391 | 133 | if self.key and self.cert: | ||
392 | 134 | # If neither of the key files exists then attempt to | ||
393 | 135 | # generate them. | ||
394 | 136 | if (self.autokey and not os.path.exists(self.key) | ||
395 | 137 | and not os.path.exists(self.cert)): | ||
396 | 138 | self.generateUefiKeys() | ||
397 | 139 | |||
398 | 140 | # If we have keys, but cannot read them they are dead to us. | ||
399 | 141 | if not os.access(self.key, os.R_OK): | ||
400 | 142 | if self.logger is not None: | ||
401 | 143 | self.logger.warning( | ||
402 | 144 | "UEFI private key %s not readable" % self.key) | ||
403 | 145 | self.key = None | ||
404 | 146 | if not os.access(self.cert, os.R_OK): | ||
405 | 147 | if self.logger is not None: | ||
406 | 148 | self.logger.warning( | ||
407 | 149 | "UEFI certificate %s not readable" % self.cert) | ||
408 | 150 | self.cert = None | ||
409 | 151 | |||
410 | 152 | return (self.key, self.cert) | ||
411 | 153 | |||
412 | 154 | def signUefi(self, image): | ||
413 | 155 | """Attempt to sign an image.""" | ||
414 | 156 | (key, cert) = self.getUefiKeys() | ||
415 | 157 | if not key or not cert: | ||
416 | 158 | return | ||
417 | 159 | cmdl = ["sbsign", "--key", key, "--cert", cert, image] | ||
418 | 160 | if subprocess.call(cmdl) != 0: | ||
419 | 112 | # Just log this rather than failing, since custom upload errors | 161 | # Just log this rather than failing, since custom upload errors |
420 | 113 | # tend to make the publisher rather upset. | 162 | # tend to make the publisher rather upset. |
421 | 114 | if self.logger is not None: | 163 | if self.logger is not None: |
423 | 115 | self.logger.warning("Failed to sign %s" % image) | 164 | self.logger.warning("UEFI Signing Failed '%s'" % |
424 | 165 | " ".join(cmdl)) | ||
425 | 116 | 166 | ||
426 | 117 | def extract(self): | 167 | def extract(self): |
427 | 118 | """Copy the custom upload to a temporary directory, and sign it. | 168 | """Copy the custom upload to a temporary directory, and sign it. |
428 | @@ -120,11 +170,10 @@ | |||
429 | 120 | No actual extraction is required. | 170 | No actual extraction is required. |
430 | 121 | """ | 171 | """ |
431 | 122 | super(UefiUpload, self).extract() | 172 | super(UefiUpload, self).extract() |
437 | 123 | if self.key is not None and self.cert is not None: | 173 | efi_filenames = list(self.findEfiFilenames()) |
438 | 124 | efi_filenames = list(self.findEfiFilenames()) | 174 | for efi_filename in efi_filenames: |
439 | 125 | for efi_filename in efi_filenames: | 175 | remove_if_exists("%s.signed" % efi_filename) |
440 | 126 | remove_if_exists("%s.signed" % efi_filename) | 176 | self.signUefi(efi_filename) |
436 | 127 | self.sign(efi_filename) | ||
441 | 128 | 177 | ||
442 | 129 | def shouldInstall(self, filename): | 178 | def shouldInstall(self, filename): |
443 | 130 | return filename.startswith("%s/" % self.version) | 179 | return filename.startswith("%s/" % self.version) |
I have pushed up a number of updates on top of the previous code. I believe this should reflect your review comments. In particular I have revamps the testing to split command generation checks from flow testing. Oh and the lint roller has been applied.