Merge lp:~apw/launchpad/signing-fit into lp:launchpad
- signing-fit
- Merge into devel
Proposed by
Andy Whitcroft
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~apw/launchpad/signing-fit | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
876 lines (+442/-35) 2 files modified
lib/lp/archivepublisher/signing.py (+81/-26) lib/lp/archivepublisher/tests/test_signing.py (+361/-9) |
||||
To merge this branch: | bzr merge lp:~apw/launchpad/signing-fit | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+368276@code.launchpad.net |
This proposal has been superseded by a proposal from 2019-06-03.
Commit message
Add u-boot Flat Image Tree signing support. u-boot supports generation and verification of FIT images in a similar fashion to secure boot. These signatures are expressed as signature nodes in the DTB contained within the FIT image. Add support for performing FIT signing against *.fit files in the signing custom uploads.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/archivepublisher/signing.py' |
2 | --- lib/lp/archivepublisher/signing.py 2018-08-03 16:10:41 +0000 |
3 | +++ lib/lp/archivepublisher/signing.py 2019-06-03 14:59:55 +0000 |
4 | @@ -95,6 +95,10 @@ |
5 | self.kmod_x509 = None |
6 | self.opal_pem = None |
7 | self.opal_x509 = None |
8 | + self.sipl_pem = None |
9 | + self.sipl_x509 = None |
10 | + self.fit_key = None |
11 | + self.fit_cert = None |
12 | self.autokey = False |
13 | else: |
14 | self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key") |
15 | @@ -103,6 +107,13 @@ |
16 | self.kmod_x509 = os.path.join(pubconf.signingroot, "kmod.x509") |
17 | self.opal_pem = os.path.join(pubconf.signingroot, "opal.pem") |
18 | self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509") |
19 | + self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem") |
20 | + self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509") |
21 | + # Note: the signature tool allows a collection of keys and takes |
22 | + # a directory name with all valid keys. Avoid mixing the |
23 | + # other signing types' keys with the fit keys. |
24 | + self.fit_key = os.path.join(pubconf.signingroot, "fit", "fit.key") |
25 | + self.fit_cert = os.path.join(pubconf.signingroot, "fit", "fit.crt") |
26 | self.autokey = pubconf.signingautokey |
27 | |
28 | self.setComponents(tarfile_path) |
29 | @@ -176,6 +187,10 @@ |
30 | yield (os.path.join(dirpath, filename), self.signKmod) |
31 | elif filename.endswith(".opal"): |
32 | yield (os.path.join(dirpath, filename), self.signOpal) |
33 | + elif filename.endswith(".sipl"): |
34 | + yield (os.path.join(dirpath, filename), self.signSipl) |
35 | + elif filename.endswith(".fit"): |
36 | + yield (os.path.join(dirpath, filename), self.signFit) |
37 | |
38 | def getKeys(self, which, generate, *keynames): |
39 | """Validate and return the uefi key and cert for encryption.""" |
40 | @@ -207,29 +222,33 @@ |
41 | common_name = "PPA %s %s" % (owner, archive) |
42 | return common_name[0:64 - len(suffix)] + suffix |
43 | |
44 | - def generateUefiKeys(self): |
45 | - """Generate new UEFI Keys for this archive.""" |
46 | - directory = os.path.dirname(self.uefi_key) |
47 | + def generateKeyCrtPair(self, key_type, key_filename, cert_filename): |
48 | + """Generate new Key/Crt key pairs.""" |
49 | + directory = os.path.dirname(key_filename) |
50 | if not os.path.exists(directory): |
51 | os.makedirs(directory) |
52 | |
53 | common_name = self.generateKeyCommonName( |
54 | - self.archive.owner.name, self.archive.name) |
55 | + self.archive.owner.name, self.archive.name, key_type) |
56 | subject = '/CN=' + common_name + '/' |
57 | |
58 | old_mask = os.umask(0o077) |
59 | try: |
60 | new_key_cmd = [ |
61 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', |
62 | - '-subj', subject, '-keyout', self.uefi_key, |
63 | - '-out', self.uefi_cert, '-days', '3650', '-nodes', '-sha256', |
64 | + '-subj', subject, '-keyout', key_filename, |
65 | + '-out', cert_filename, '-days', '3650', '-nodes', '-sha256', |
66 | ] |
67 | - self.callLog("UEFI keygen", new_key_cmd) |
68 | + self.callLog(key_type + " keygen", new_key_cmd) |
69 | finally: |
70 | os.umask(old_mask) |
71 | |
72 | - if os.path.exists(self.uefi_cert): |
73 | - os.chmod(self.uefi_cert, 0o644) |
74 | + if os.path.exists(cert_filename): |
75 | + os.chmod(cert_filename, 0o644) |
76 | + |
77 | + def generateUefiKeys(self): |
78 | + """Generate new UEFI Keys for this archive.""" |
79 | + self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert) |
80 | |
81 | def signUefi(self, image): |
82 | """Attempt to sign an image.""" |
83 | @@ -242,7 +261,7 @@ |
84 | cmdl = ["sbsign", "--key", key, "--cert", cert, image] |
85 | return self.callLog("UEFI signing", cmdl) |
86 | |
87 | - openssl_config_opal = textwrap.dedent(""" |
88 | + openssl_config_base = textwrap.dedent(""" |
89 | [ req ] |
90 | default_bits = 4096 |
91 | distinguished_name = req_distinguished_name |
92 | @@ -260,37 +279,35 @@ |
93 | authorityKeyIdentifier=keyid |
94 | """) |
95 | |
96 | - openssl_config_kmod = openssl_config_opal + textwrap.dedent(""" |
97 | + openssl_config_opal = "# OPAL openssl config" + openssl_config_base |
98 | + |
99 | + openssl_config_kmod = "# KMOD openssl config" + openssl_config_base + \ |
100 | + textwrap.dedent(""" |
101 | # codeSigning: specifies that this key is used to sign code. |
102 | # 1.3.6.1.4.1.2312.16.1.2: defines this key as used for |
103 | # module signing only. See https://lkml.org/lkml/2015/8/26/741. |
104 | extendedKeyUsage = codeSigning,1.3.6.1.4.1.2312.16.1.2 |
105 | """) |
106 | |
107 | - def generateOpensslConfig(self, key_type, common_name): |
108 | - if key_type == 'Kmod': |
109 | - genkey_tmpl = self.openssl_config_kmod |
110 | - elif key_type == 'Opal': |
111 | - genkey_tmpl = self.openssl_config_opal |
112 | - else: |
113 | - raise ValueError("unknown key_type " + key_type) |
114 | + openssl_config_sipl = "# SIPL openssl config" + openssl_config_base |
115 | + |
116 | + def generateOpensslConfig(self, key_type, genkey_tmpl): |
117 | + # Truncate name to 64 character maximum. |
118 | + common_name = self.generateKeyCommonName( |
119 | + self.archive.owner.name, self.archive.name, key_type) |
120 | |
121 | return genkey_tmpl.format(common_name=common_name) |
122 | |
123 | - def generatePemX509Pair(self, key_type, pem_filename, x509_filename): |
124 | + def generatePemX509Pair(self, key_type, genkey_text, pem_filename, |
125 | + x509_filename): |
126 | """Generate new pem/x509 key pairs.""" |
127 | directory = os.path.dirname(pem_filename) |
128 | if not os.path.exists(directory): |
129 | os.makedirs(directory) |
130 | |
131 | - # Truncate name to 64 character maximum. |
132 | - common_name = self.generateKeyCommonName( |
133 | - self.archive.owner.name, self.archive.name, key_type) |
134 | - |
135 | old_mask = os.umask(0o077) |
136 | try: |
137 | with tempfile.NamedTemporaryFile(suffix='.keygen') as tf: |
138 | - genkey_text = self.generateOpensslConfig(key_type, common_name) |
139 | print(genkey_text, file=tf) |
140 | |
141 | # Close out the underlying file so we know it is complete. |
142 | @@ -318,7 +335,8 @@ |
143 | |
144 | def generateKmodKeys(self): |
145 | """Generate new Kernel Signing Keys for this archive.""" |
146 | - self.generatePemX509Pair("Kmod", self.kmod_pem, self.kmod_x509) |
147 | + config = self.generateOpensslConfig("Kmod", self.openssl_config_kmod) |
148 | + self.generatePemX509Pair("Kmod", config, self.kmod_pem, self.kmod_x509) |
149 | |
150 | def signKmod(self, image): |
151 | """Attempt to sign a kernel module.""" |
152 | @@ -333,7 +351,8 @@ |
153 | |
154 | def generateOpalKeys(self): |
155 | """Generate new Opal Signing Keys for this archive.""" |
156 | - self.generatePemX509Pair("Opal", self.opal_pem, self.opal_x509) |
157 | + config = self.generateOpensslConfig("Opal", self.openssl_config_opal) |
158 | + self.generatePemX509Pair("Opal", config, self.opal_pem, self.opal_x509) |
159 | |
160 | def signOpal(self, image): |
161 | """Attempt to sign a kernel image for Opal.""" |
162 | @@ -346,6 +365,42 @@ |
163 | cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"] |
164 | return self.callLog("Opal signing", cmdl) |
165 | |
166 | + def generateSiplKeys(self): |
167 | + """Generate new Sipl Signing Keys for this archive.""" |
168 | + config = self.generateOpensslConfig("Sipl", self.openssl_config_sipl) |
169 | + self.generatePemX509Pair("Sipl", config, self.sipl_pem, self.sipl_x509) |
170 | + |
171 | + def signSipl(self, image): |
172 | + """Attempt to sign a kernel image for Sipl.""" |
173 | + remove_if_exists("%s.sig" % image) |
174 | + (pem, cert) = self.getKeys('Sipl Kernel', self.generateSiplKeys, |
175 | + self.sipl_pem, self.sipl_x509) |
176 | + if not pem or not cert: |
177 | + return |
178 | + self.publishPublicKey(cert) |
179 | + cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"] |
180 | + return self.callLog("Sipl signing", cmdl) |
181 | + |
182 | + def generateFitKeys(self): |
183 | + """Generate new Fit Keys for this archive.""" |
184 | + self.generateKeyCrtPair("Fit", self.fit_key, self.fit_cert) |
185 | + |
186 | + def signFit(self, image): |
187 | + """Attempt to sign an image.""" |
188 | + image_signed = "%s.signed" % image |
189 | + remove_if_exists(image_signed) |
190 | + (key, cert) = self.getKeys('Fit', self.generateFitKeys, |
191 | + self.fit_key, self.fit_cert) |
192 | + if not key or not cert: |
193 | + return |
194 | + self.publishPublicKey(cert) |
195 | + # Make a copy of the image as mkimage signs in place and in |
196 | + # signed-only mode we will remove the original file. |
197 | + shutil.copy(image, image_signed) |
198 | + cmdl = ["mkimage", "-F", "-k", os.path.dirname(key), "-r", |
199 | + image_signed] |
200 | + return self.callLog("Fit signing", cmdl) |
201 | + |
202 | def convertToTarball(self): |
203 | """Convert unpacked output to signing tarball.""" |
204 | tarfilename = os.path.join(self.tmpdir, "signed.tar.gz") |
205 | |
206 | === modified file 'lib/lp/archivepublisher/tests/test_signing.py' |
207 | --- lib/lp/archivepublisher/tests/test_signing.py 2019-05-24 11:10:38 +0000 |
208 | +++ lib/lp/archivepublisher/tests/test_signing.py 2019-06-03 14:59:55 +0000 |
209 | @@ -85,12 +85,17 @@ |
210 | self.callers = { |
211 | "UEFI signing": 0, |
212 | "UEFI keygen": 0, |
213 | + "Fit signing": 0, |
214 | + "Fit keygen": 0, |
215 | "Kmod signing": 0, |
216 | "Kmod keygen key": 0, |
217 | "Kmod keygen cert": 0, |
218 | "Opal signing": 0, |
219 | "Opal keygen key": 0, |
220 | "Opal keygen cert": 0, |
221 | + "Sipl signing": 0, |
222 | + "Sipl keygen key": 0, |
223 | + "Sipl keygen cert": 0, |
224 | } |
225 | |
226 | def __call__(self, *args, **kwargs): |
227 | @@ -108,6 +113,15 @@ |
228 | write_file(self.upload.uefi_key, b"") |
229 | write_file(self.upload.uefi_cert, b"") |
230 | |
231 | + elif description == "Fit signing": |
232 | + filename = cmdl[-1] |
233 | + if filename.endswith(".fit"): |
234 | + write_file(filename + ".signed", b"") |
235 | + |
236 | + elif description == "Fit keygen": |
237 | + write_file(self.upload.fit_key, b"") |
238 | + write_file(self.upload.fit_cert, b"") |
239 | + |
240 | elif description == "Kmod signing": |
241 | filename = cmdl[-1] |
242 | if filename.endswith(".ko.sig"): |
243 | @@ -130,9 +144,20 @@ |
244 | elif description == "Opal keygen key": |
245 | write_file(self.upload.opal_pem, b"") |
246 | |
247 | + elif description == "Sipl signing": |
248 | + filename = cmdl[-1] |
249 | + if filename.endswith(".sipl.sig"): |
250 | + write_file(filename, b"") |
251 | + |
252 | + elif description == "Sipl keygen cert": |
253 | + write_file(self.upload.sipl_x509, b"") |
254 | + |
255 | + elif description == "Sipl keygen key": |
256 | + write_file(self.upload.sipl_pem, b"") |
257 | + |
258 | else: |
259 | - raise AssertionError("unknown command executed cmd=(%s)" % |
260 | - " ".join(cmdl)) |
261 | + raise AssertionError("unknown command executed description=(%s) " |
262 | + "cmd=(%s)" % (description, " ".join(cmdl))) |
263 | |
264 | return 0 |
265 | |
266 | @@ -177,7 +202,7 @@ |
267 | purpose=ArchivePurpose.PPA) |
268 | self.signing_dir = os.path.join( |
269 | self.temp_dir, "signing", "signing-owner", "testing") |
270 | - self.testcase_cn = '/CN=PPA signing-owner testing/' |
271 | + self.testcase_cn = 'PPA signing-owner testing' |
272 | pubconf = getPubConfig(self.archive) |
273 | if not os.path.exists(pubconf.temproot): |
274 | os.makedirs(pubconf.temproot) |
275 | @@ -197,6 +222,15 @@ |
276 | write_file(self.key, b"") |
277 | write_file(self.cert, b"") |
278 | |
279 | + def setUpFitKeys(self, create=True): |
280 | + # We expect and need the fit keys to be in their own |
281 | + # directory as part of key protection for mkimage. |
282 | + self.fit_key = os.path.join(self.signing_dir, "fit", "fit.key") |
283 | + self.fit_cert = os.path.join(self.signing_dir, "fit", "fit.crt") |
284 | + if create: |
285 | + write_file(self.fit_key, b"") |
286 | + write_file(self.fit_cert, b"") |
287 | + |
288 | def setUpKmodKeys(self, create=True): |
289 | self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem") |
290 | self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509") |
291 | @@ -211,6 +245,13 @@ |
292 | write_file(self.opal_pem, b"") |
293 | write_file(self.opal_x509, b"") |
294 | |
295 | + def setUpSiplKeys(self, create=True): |
296 | + self.sipl_pem = os.path.join(self.signing_dir, "sipl.pem") |
297 | + self.sipl_x509 = os.path.join(self.signing_dir, "sipl.x509") |
298 | + if create: |
299 | + write_file(self.sipl_pem, b"") |
300 | + write_file(self.sipl_x509, b"") |
301 | + |
302 | def openArchive(self, loader_type, version, arch): |
303 | self.path = os.path.join( |
304 | self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch)) |
305 | @@ -234,6 +275,7 @@ |
306 | upload = SigningUpload() |
307 | # Under no circumstances is it safe to execute actual commands. |
308 | self.fake_call = FakeMethod(result=0) |
309 | + self.fake_copyfile = FakeMethod(result=0) |
310 | upload.callLog = FakeMethodCallLog(upload=upload) |
311 | self.useFixture(MonkeyPatch("subprocess.call", self.fake_call)) |
312 | upload.process(self.archive, self.path, self.suite) |
313 | @@ -247,6 +289,8 @@ |
314 | upload.signUefi = FakeMethod() |
315 | upload.signKmod = FakeMethod() |
316 | upload.signOpal = FakeMethod() |
317 | + upload.signSipl = FakeMethod() |
318 | + upload.signFit = FakeMethod() |
319 | # Under no circumstances is it safe to execute actual commands. |
320 | fake_call = FakeMethod(result=0) |
321 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
322 | @@ -267,6 +311,8 @@ |
323 | self.tarfile.add_file("1.0/empty.efi", b"") |
324 | self.tarfile.add_file("1.0/empty.ko", b"") |
325 | self.tarfile.add_file("1.0/empty.opal", b"") |
326 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
327 | + self.tarfile.add_file("1.0/empty.fit", b"") |
328 | upload = self.process_emulate() |
329 | self.assertContentEqual([], upload.callLog.caller_list()) |
330 | |
331 | @@ -277,6 +323,8 @@ |
332 | self.tarfile.add_file("1.0/empty.efi", b"") |
333 | self.tarfile.add_file("1.0/empty.ko", b"") |
334 | self.tarfile.add_file("1.0/empty.opal", b"") |
335 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
336 | + self.tarfile.add_file("1.0/empty.fit", b"") |
337 | upload = self.process_emulate() |
338 | self.assertContentEqual([], upload.callLog.caller_list()) |
339 | |
340 | @@ -289,6 +337,8 @@ |
341 | self.tarfile.add_file("1.0/empty.efi", b"") |
342 | self.tarfile.add_file("1.0/empty.ko", b"") |
343 | self.tarfile.add_file("1.0/empty.opal", b"") |
344 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
345 | + self.tarfile.add_file("1.0/empty.fit", b"") |
346 | upload = self.process_emulate() |
347 | expected_callers = [ |
348 | ('UEFI signing', 1), |
349 | @@ -304,6 +354,8 @@ |
350 | self.tarfile.add_file("1.0/empty.efi", b"") |
351 | self.tarfile.add_file("1.0/empty.ko", b"") |
352 | self.tarfile.add_file("1.0/empty.opal", b"") |
353 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
354 | + self.tarfile.add_file("1.0/empty.fit", b"") |
355 | upload = self.process_emulate() |
356 | expected_callers = [ |
357 | ('UEFI keygen', 1), |
358 | @@ -311,9 +363,14 @@ |
359 | ('Kmod keygen cert', 1), |
360 | ('Opal keygen key', 1), |
361 | ('Opal keygen cert', 1), |
362 | + ('Sipl keygen key', 1), |
363 | + ('Sipl keygen cert', 1), |
364 | + ('Fit keygen', 1), |
365 | ('UEFI signing', 1), |
366 | ('Kmod signing', 1), |
367 | ('Opal signing', 1), |
368 | + ('Sipl signing', 1), |
369 | + ('Fit signing', 1), |
370 | ] |
371 | self.assertContentEqual(expected_callers, upload.callLog.caller_list()) |
372 | |
373 | @@ -387,16 +444,22 @@ |
374 | self.setUpUefiKeys() |
375 | self.setUpKmodKeys() |
376 | self.setUpOpalKeys() |
377 | + self.setUpSiplKeys() |
378 | + self.setUpFitKeys() |
379 | self.openArchive("test", "1.0", "amd64") |
380 | self.tarfile.add_file("1.0/empty.efi", b"") |
381 | self.tarfile.add_file("1.0/empty.ko", b"") |
382 | self.tarfile.add_file("1.0/empty.opal", b"") |
383 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
384 | + self.tarfile.add_file("1.0/empty.fit", b"") |
385 | self.process_emulate() |
386 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
387 | "1.0/SHA256SUMS", |
388 | "1.0/empty.efi", "1.0/empty.efi.signed", "1.0/control/uefi.crt", |
389 | "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509", |
390 | "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509", |
391 | + "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509", |
392 | + "1.0/empty.fit", "1.0/empty.fit.signed", "1.0/control/fit.crt", |
393 | ])) |
394 | |
395 | def test_options_tarball(self): |
396 | @@ -405,11 +468,15 @@ |
397 | self.setUpUefiKeys() |
398 | self.setUpKmodKeys() |
399 | self.setUpOpalKeys() |
400 | + self.setUpSiplKeys() |
401 | + self.setUpFitKeys() |
402 | self.openArchive("test", "1.0", "amd64") |
403 | self.tarfile.add_file("1.0/control/options", b"tarball") |
404 | self.tarfile.add_file("1.0/empty.efi", b"") |
405 | self.tarfile.add_file("1.0/empty.ko", b"") |
406 | self.tarfile.add_file("1.0/empty.opal", b"") |
407 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
408 | + self.tarfile.add_file("1.0/empty.fit", b"") |
409 | self.process_emulate() |
410 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
411 | "1.0/SHA256SUMS", |
412 | @@ -425,6 +492,10 @@ |
413 | '1.0/empty.ko', '1.0/empty.ko.sig', '1.0/control/kmod.x509', |
414 | '1.0/empty.opal', '1.0/empty.opal.sig', |
415 | '1.0/control/opal.x509', |
416 | + '1.0/empty.sipl', '1.0/empty.sipl.sig', |
417 | + '1.0/control/sipl.x509', |
418 | + '1.0/empty.fit', '1.0/empty.fit.signed', |
419 | + '1.0/control/fit.crt', |
420 | ], tarball.getnames()) |
421 | |
422 | def test_options_signed_only(self): |
423 | @@ -433,17 +504,23 @@ |
424 | self.setUpUefiKeys() |
425 | self.setUpKmodKeys() |
426 | self.setUpOpalKeys() |
427 | + self.setUpSiplKeys() |
428 | + self.setUpFitKeys() |
429 | self.openArchive("test", "1.0", "amd64") |
430 | self.tarfile.add_file("1.0/control/options", b"signed-only") |
431 | self.tarfile.add_file("1.0/empty.efi", b"") |
432 | self.tarfile.add_file("1.0/empty.ko", b"") |
433 | self.tarfile.add_file("1.0/empty.opal", b"") |
434 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
435 | + self.tarfile.add_file("1.0/empty.fit", b"") |
436 | self.process_emulate() |
437 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
438 | "1.0/SHA256SUMS", "1.0/control/options", |
439 | "1.0/empty.efi.signed", "1.0/control/uefi.crt", |
440 | "1.0/empty.ko.sig", "1.0/control/kmod.x509", |
441 | "1.0/empty.opal.sig", "1.0/control/opal.x509", |
442 | + "1.0/empty.sipl.sig", "1.0/control/sipl.x509", |
443 | + "1.0/empty.fit.signed", "1.0/control/fit.crt", |
444 | ])) |
445 | |
446 | def test_options_tarball_signed_only(self): |
447 | @@ -453,11 +530,15 @@ |
448 | self.setUpUefiKeys() |
449 | self.setUpKmodKeys() |
450 | self.setUpOpalKeys() |
451 | + self.setUpSiplKeys() |
452 | + self.setUpFitKeys() |
453 | self.openArchive("test", "1.0", "amd64") |
454 | self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only") |
455 | self.tarfile.add_file("1.0/empty.efi", b"") |
456 | self.tarfile.add_file("1.0/empty.ko", b"") |
457 | self.tarfile.add_file("1.0/empty.opal", b"") |
458 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
459 | + self.tarfile.add_file("1.0/empty.fit", b"") |
460 | self.process_emulate() |
461 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
462 | "1.0/SHA256SUMS", |
463 | @@ -471,12 +552,18 @@ |
464 | '1.0/empty.efi.signed', '1.0/control/uefi.crt', |
465 | '1.0/empty.ko.sig', '1.0/control/kmod.x509', |
466 | '1.0/empty.opal.sig', '1.0/control/opal.x509', |
467 | + '1.0/empty.sipl.sig', '1.0/control/sipl.x509', |
468 | + '1.0/empty.fit.signed', '1.0/control/fit.crt', |
469 | ], tarball.getnames()) |
470 | |
471 | def test_no_signed_files(self): |
472 | # Tarballs containing no *.efi files are extracted without complaint. |
473 | # Nothing is signed. |
474 | self.setUpUefiKeys() |
475 | + self.setUpKmodKeys() |
476 | + self.setUpOpalKeys() |
477 | + self.setUpSiplKeys() |
478 | + self.setUpFitKeys() |
479 | self.openArchive("empty", "1.0", "amd64") |
480 | self.tarfile.add_file("1.0/hello", b"world") |
481 | upload = self.process() |
482 | @@ -484,6 +571,9 @@ |
483 | self.getSignedPath("empty", "amd64"), "1.0", "hello"))) |
484 | self.assertEqual(0, upload.signUefi.call_count) |
485 | self.assertEqual(0, upload.signKmod.call_count) |
486 | + self.assertEqual(0, upload.signOpal.call_count) |
487 | + self.assertEqual(0, upload.signSipl.call_count) |
488 | + self.assertEqual(0, upload.signFit.call_count) |
489 | |
490 | def test_already_exists(self): |
491 | # If the target directory already exists, processing fails. |
492 | @@ -551,7 +641,71 @@ |
493 | args = fake_call.calls[0][0][0] |
494 | expected_cmd = [ |
495 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', |
496 | - '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert, |
497 | + '-subj', '/CN=' + self.testcase_cn + ' UEFI/', |
498 | + '-keyout', self.key, '-out', self.cert, |
499 | + '-days', '3650', '-nodes', '-sha256', |
500 | + ] |
501 | + self.assertEqual(expected_cmd, args) |
502 | + |
503 | + def test_correct_fit_signing_command_executed(self): |
504 | + # Check that calling signFit() will generate the expected command |
505 | + # when appropriate keys are present. |
506 | + self.setUpFitKeys() |
507 | + fake_call = FakeMethod(result=0) |
508 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
509 | + fake_copy = FakeMethod(result=0) |
510 | + self.useFixture(MonkeyPatch("shutil.copy", fake_copy)) |
511 | + upload = SigningUpload() |
512 | + upload.generateFitKeys = FakeMethod() |
513 | + upload.setTargetDirectory( |
514 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
515 | + upload.signFit('t.fit') |
516 | + # Confirm the copy was performed. |
517 | + self.assertEqual(1, fake_copy.call_count) |
518 | + args = fake_copy.calls[0][0] |
519 | + expected_copy = ('t.fit', 't.fit.signed') |
520 | + self.assertEqual(expected_copy, args) |
521 | + # Assert command form. |
522 | + args = fake_call.calls[0][0][0] |
523 | + expected_cmd = [ |
524 | + 'mkimage', '-F', '-k', os.path.dirname(self.fit_key), '-r', |
525 | + 't.fit.signed', |
526 | + ] |
527 | + self.assertEqual(expected_cmd, args) |
528 | + self.assertEqual(0, upload.generateFitKeys.call_count) |
529 | + |
530 | + def test_correct_fit_signing_command_executed_no_keys(self): |
531 | + # Check that calling signFit() will generate no commands when |
532 | + # no keys are present. |
533 | + self.setUpFitKeys(create=False) |
534 | + fake_call = FakeMethod(result=0) |
535 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
536 | + upload = SigningUpload() |
537 | + upload.generateFitKeys = FakeMethod() |
538 | + upload.setTargetDirectory( |
539 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
540 | + upload.signUefi('t.fit') |
541 | + self.assertEqual(0, fake_call.call_count) |
542 | + self.assertEqual(0, upload.generateFitKeys.call_count) |
543 | + |
544 | + def test_correct_fit_keygen_command_executed(self): |
545 | + # Check that calling generateFitKeys() will generate the |
546 | + # expected command. |
547 | + self.setUpPPA() |
548 | + self.setUpFitKeys(create=False) |
549 | + fake_call = FakeMethod(result=0) |
550 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
551 | + upload = SigningUpload() |
552 | + upload.setTargetDirectory( |
553 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
554 | + upload.generateFitKeys() |
555 | + self.assertEqual(1, fake_call.call_count) |
556 | + # Assert the actual command matches. |
557 | + args = fake_call.calls[0][0][0] |
558 | + expected_cmd = [ |
559 | + 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', |
560 | + '-subj', '/CN=' + self.testcase_cn + ' Fit/', |
561 | + '-keyout', self.fit_key, '-out', self.fit_cert, |
562 | '-days', '3650', '-nodes', '-sha256', |
563 | ] |
564 | self.assertEqual(expected_cmd, args) |
565 | @@ -559,15 +713,20 @@ |
566 | def test_correct_kmod_openssl_config(self): |
567 | # Check that calling generateOpensslConfig() will return an appropriate |
568 | # openssl configuration. |
569 | + self.setUpPPA() |
570 | upload = SigningUpload() |
571 | - text = upload.generateOpensslConfig('Kmod', 'something-unique') |
572 | + upload.setTargetDirectory( |
573 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
574 | + text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod) |
575 | |
576 | - cn_re = re.compile(r'\bCN\s*=\s*something-unique\b') |
577 | + id_re = re.compile(r'^# KMOD openssl config\b') |
578 | + cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Kmod') |
579 | eku_re = re.compile( |
580 | r'\bextendedKeyUsage\s*=\s*' |
581 | r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b') |
582 | |
583 | self.assertIn('[ req ]', text) |
584 | + self.assertIsNotNone(id_re.search(text)) |
585 | self.assertIsNotNone(cn_re.search(text)) |
586 | self.assertIsNotNone(eku_re.search(text)) |
587 | |
588 | @@ -640,12 +799,17 @@ |
589 | def test_correct_opal_openssl_config(self): |
590 | # Check that calling generateOpensslConfig() will return an appropriate |
591 | # openssl configuration. |
592 | + self.setUpPPA() |
593 | upload = SigningUpload() |
594 | - text = upload.generateOpensslConfig('Opal', 'something-unique') |
595 | + upload.setTargetDirectory( |
596 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
597 | + text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal) |
598 | |
599 | - cn_re = re.compile(r'\bCN\s*=\s*something-unique\b') |
600 | + id_re = re.compile(r'^# OPAL openssl config\b') |
601 | + cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Opal') |
602 | |
603 | self.assertIn('[ req ]', text) |
604 | + self.assertIsNotNone(id_re.search(text)) |
605 | self.assertIsNotNone(cn_re.search(text)) |
606 | self.assertNotIn('extendedKeyUsage', text) |
607 | |
608 | @@ -715,6 +879,89 @@ |
609 | ] |
610 | self.assertEqual(expected_cmd, args) |
611 | |
612 | + def test_correct_sipl_openssl_config(self): |
613 | + # Check that calling generateOpensslConfig() will return an appropriate |
614 | + # openssl configuration. |
615 | + self.setUpPPA() |
616 | + upload = SigningUpload() |
617 | + upload.setTargetDirectory( |
618 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
619 | + text = upload.generateOpensslConfig('Sipl', upload.openssl_config_sipl) |
620 | + |
621 | + id_re = re.compile(r'^# SIPL openssl config\b') |
622 | + cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Sipl') |
623 | + |
624 | + self.assertIn('[ req ]', text) |
625 | + self.assertIsNotNone(id_re.search(text)) |
626 | + self.assertIsNotNone(cn_re.search(text)) |
627 | + self.assertNotIn('extendedKeyUsage', text) |
628 | + |
629 | + def test_correct_sipl_signing_command_executed(self): |
630 | + # Check that calling signSipl() will generate the expected command |
631 | + # when appropriate keys are present. |
632 | + self.setUpSiplKeys() |
633 | + fake_call = FakeMethod(result=0) |
634 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
635 | + upload = SigningUpload() |
636 | + upload.generateSiplKeys = FakeMethod() |
637 | + upload.setTargetDirectory( |
638 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
639 | + upload.signSipl('t.sipl') |
640 | + self.assertEqual(1, fake_call.call_count) |
641 | + # Assert command form. |
642 | + args = fake_call.calls[0][0][0] |
643 | + expected_cmd = [ |
644 | + 'kmodsign', '-D', 'sha512', self.sipl_pem, self.sipl_x509, |
645 | + 't.sipl', 't.sipl.sig' |
646 | + ] |
647 | + self.assertEqual(expected_cmd, args) |
648 | + self.assertEqual(0, upload.generateSiplKeys.call_count) |
649 | + |
650 | + def test_correct_sipl_signing_command_executed_no_keys(self): |
651 | + # Check that calling signSipl() will generate no commands when |
652 | + # no keys are present. |
653 | + self.setUpSiplKeys(create=False) |
654 | + fake_call = FakeMethod(result=0) |
655 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
656 | + upload = SigningUpload() |
657 | + upload.generateSiplKeys = FakeMethod() |
658 | + upload.setTargetDirectory( |
659 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
660 | + upload.signOpal('t.sipl') |
661 | + self.assertEqual(0, fake_call.call_count) |
662 | + self.assertEqual(0, upload.generateSiplKeys.call_count) |
663 | + |
664 | + def test_correct_sipl_keygen_command_executed(self): |
665 | + # Check that calling generateSiplKeys() will generate the |
666 | + # expected command. |
667 | + self.setUpPPA() |
668 | + self.setUpSiplKeys(create=False) |
669 | + fake_call = FakeMethod(result=0) |
670 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
671 | + upload = SigningUpload() |
672 | + upload.setTargetDirectory( |
673 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
674 | + upload.generateSiplKeys() |
675 | + self.assertEqual(2, fake_call.call_count) |
676 | + # Assert the actual command matches. |
677 | + args = fake_call.calls[0][0][0] |
678 | + # Sanitise the keygen tmp file. |
679 | + if args[11].endswith('.keygen'): |
680 | + args[11] = 'XXX.keygen' |
681 | + expected_cmd = [ |
682 | + 'openssl', 'req', '-new', '-nodes', '-utf8', '-sha512', |
683 | + '-days', '3650', '-batch', '-x509', |
684 | + '-config', 'XXX.keygen', '-outform', 'PEM', |
685 | + '-out', self.sipl_pem, '-keyout', self.sipl_pem |
686 | + ] |
687 | + self.assertEqual(expected_cmd, args) |
688 | + args = fake_call.calls[1][0][0] |
689 | + expected_cmd = [ |
690 | + 'openssl', 'x509', '-in', self.sipl_pem, '-outform', 'DER', |
691 | + '-out', self.sipl_x509 |
692 | + ] |
693 | + self.assertEqual(expected_cmd, args) |
694 | + |
695 | def test_signs_uefi_image(self): |
696 | # Each image in the tarball is signed. |
697 | self.setUpUefiKeys() |
698 | @@ -723,6 +970,14 @@ |
699 | upload = self.process() |
700 | self.assertEqual(1, upload.signUefi.call_count) |
701 | |
702 | + def test_signs_fit_image(self): |
703 | + # Each image in the tarball is signed. |
704 | + self.setUpFitKeys() |
705 | + self.openArchive("test", "1.0", "amd64") |
706 | + self.tarfile.add_file("1.0/empty.fit", b"") |
707 | + upload = self.process() |
708 | + self.assertEqual(1, upload.signFit.call_count) |
709 | + |
710 | def test_signs_kmod_image(self): |
711 | # Each image in the tarball is signed. |
712 | self.setUpKmodKeys() |
713 | @@ -739,9 +994,16 @@ |
714 | upload = self.process() |
715 | self.assertEqual(1, upload.signOpal.call_count) |
716 | |
717 | + def test_signs_sipl_image(self): |
718 | + # Each image in the tarball is signed. |
719 | + self.setUpSiplKeys() |
720 | + self.openArchive("test", "1.0", "amd64") |
721 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
722 | + upload = self.process() |
723 | + self.assertEqual(1, upload.signSipl.call_count) |
724 | + |
725 | def test_signs_combo_image(self): |
726 | # Each image in the tarball is signed. |
727 | - self.setUpKmodKeys() |
728 | self.openArchive("test", "1.0", "amd64") |
729 | self.tarfile.add_file("1.0/empty.efi", b"") |
730 | self.tarfile.add_file("1.0/empty.ko", b"") |
731 | @@ -749,10 +1011,21 @@ |
732 | self.tarfile.add_file("1.0/empty.opal", b"") |
733 | self.tarfile.add_file("1.0/empty2.opal", b"") |
734 | self.tarfile.add_file("1.0/empty3.opal", b"") |
735 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
736 | + self.tarfile.add_file("1.0/empty2.sipl", b"") |
737 | + self.tarfile.add_file("1.0/empty3.sipl", b"") |
738 | + self.tarfile.add_file("1.0/empty4.sipl", b"") |
739 | + self.tarfile.add_file("1.0/empty.fit", b"") |
740 | + self.tarfile.add_file("1.0/empty2.fit", b"") |
741 | + self.tarfile.add_file("1.0/empty3.fit", b"") |
742 | + self.tarfile.add_file("1.0/empty4.fit", b"") |
743 | + self.tarfile.add_file("1.0/empty5.fit", b"") |
744 | upload = self.process() |
745 | self.assertEqual(1, upload.signUefi.call_count) |
746 | self.assertEqual(2, upload.signKmod.call_count) |
747 | self.assertEqual(3, upload.signOpal.call_count) |
748 | + self.assertEqual(4, upload.signSipl.call_count) |
749 | + self.assertEqual(5, upload.signFit.call_count) |
750 | |
751 | def test_installed(self): |
752 | # Files in the tarball are installed correctly. |
753 | @@ -824,6 +1097,43 @@ |
754 | self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600) |
755 | self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644) |
756 | |
757 | + def test_create_fit_keys_autokey_off(self): |
758 | + # Keys are not created. |
759 | + self.setUpFitKeys(create=False) |
760 | + self.assertFalse(os.path.exists(self.fit_key)) |
761 | + self.assertFalse(os.path.exists(self.fit_cert)) |
762 | + fake_call = FakeMethod(result=0) |
763 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
764 | + upload = SigningUpload() |
765 | + upload.callLog = FakeMethodCallLog(upload=upload) |
766 | + upload.setTargetDirectory( |
767 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
768 | + upload.signFit(os.path.join(self.makeTemporaryDirectory(), 'fit')) |
769 | + self.assertEqual(0, upload.callLog.caller_count('Fit keygen')) |
770 | + self.assertFalse(os.path.exists(self.fit_key)) |
771 | + self.assertFalse(os.path.exists(self.fit_cert)) |
772 | + |
773 | + def test_create_fit_keys_autokey_on(self): |
774 | + # Keys are created on demand. |
775 | + self.setUpPPA() |
776 | + self.setUpFitKeys(create=False) |
777 | + self.assertFalse(os.path.exists(self.fit_key)) |
778 | + self.assertFalse(os.path.exists(self.fit_cert)) |
779 | + fake_call = FakeMethod(result=0) |
780 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
781 | + fake_copy = FakeMethod(result=0) |
782 | + self.useFixture(MonkeyPatch("shutil.copy", fake_copy)) |
783 | + upload = SigningUpload() |
784 | + upload.callLog = FakeMethodCallLog(upload=upload) |
785 | + upload.setTargetDirectory( |
786 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
787 | + upload.signFit(os.path.join(self.makeTemporaryDirectory(), 't.fit')) |
788 | + self.assertEqual(1, upload.callLog.caller_count('Fit keygen')) |
789 | + self.assertTrue(os.path.exists(self.fit_key)) |
790 | + self.assertTrue(os.path.exists(self.fit_cert)) |
791 | + self.assertEqual(stat.S_IMODE(os.stat(self.fit_key).st_mode), 0o600) |
792 | + self.assertEqual(stat.S_IMODE(os.stat(self.fit_cert).st_mode), 0o644) |
793 | + |
794 | def test_create_kmod_keys_autokey_off(self): |
795 | # Keys are not created. |
796 | self.setUpKmodKeys(create=False) |
797 | @@ -898,16 +1208,55 @@ |
798 | self.assertEqual(stat.S_IMODE(os.stat(self.opal_pem).st_mode), 0o600) |
799 | self.assertEqual(stat.S_IMODE(os.stat(self.opal_x509).st_mode), 0o644) |
800 | |
801 | + def test_create_sipl_keys_autokey_off(self): |
802 | + # Keys are not created. |
803 | + self.setUpSiplKeys(create=False) |
804 | + self.assertFalse(os.path.exists(self.sipl_pem)) |
805 | + self.assertFalse(os.path.exists(self.sipl_x509)) |
806 | + fake_call = FakeMethod(result=0) |
807 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
808 | + upload = SigningUpload() |
809 | + upload.callLog = FakeMethodCallLog(upload=upload) |
810 | + upload.setTargetDirectory( |
811 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
812 | + upload.signOpal(os.path.join(self.makeTemporaryDirectory(), 't.sipl')) |
813 | + self.assertEqual(0, upload.callLog.caller_count('Sipl keygen key')) |
814 | + self.assertEqual(0, upload.callLog.caller_count('Sipl keygen cert')) |
815 | + self.assertFalse(os.path.exists(self.sipl_pem)) |
816 | + self.assertFalse(os.path.exists(self.sipl_x509)) |
817 | + |
818 | + def test_create_sipl_keys_autokey_on(self): |
819 | + # Keys are created on demand. |
820 | + self.setUpPPA() |
821 | + self.setUpSiplKeys(create=False) |
822 | + self.assertFalse(os.path.exists(self.sipl_pem)) |
823 | + self.assertFalse(os.path.exists(self.sipl_x509)) |
824 | + fake_call = FakeMethod(result=0) |
825 | + self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
826 | + upload = SigningUpload() |
827 | + upload.callLog = FakeMethodCallLog(upload=upload) |
828 | + upload.setTargetDirectory( |
829 | + self.archive, "test_1.0_amd64.tar.gz", "distroseries") |
830 | + upload.signSipl(os.path.join(self.makeTemporaryDirectory(), 't.sipl')) |
831 | + self.assertEqual(1, upload.callLog.caller_count('Sipl keygen key')) |
832 | + self.assertEqual(1, upload.callLog.caller_count('Sipl keygen cert')) |
833 | + self.assertTrue(os.path.exists(self.sipl_pem)) |
834 | + self.assertTrue(os.path.exists(self.sipl_x509)) |
835 | + self.assertEqual(stat.S_IMODE(os.stat(self.sipl_pem).st_mode), 0o600) |
836 | + self.assertEqual(stat.S_IMODE(os.stat(self.sipl_x509).st_mode), 0o644) |
837 | + |
838 | def test_checksumming_tree(self): |
839 | # Specifying no options should leave us with an open tree, |
840 | # confirm it is checksummed. |
841 | self.setUpUefiKeys() |
842 | self.setUpKmodKeys() |
843 | self.setUpOpalKeys() |
844 | + self.setUpSiplKeys() |
845 | self.openArchive("test", "1.0", "amd64") |
846 | self.tarfile.add_file("1.0/empty.efi", b"") |
847 | self.tarfile.add_file("1.0/empty.ko", b"") |
848 | self.tarfile.add_file("1.0/empty.opal", b"") |
849 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
850 | self.process_emulate() |
851 | sha256file = os.path.join(self.getSignedPath("test", "amd64"), |
852 | "1.0", "SHA256SUMS") |
853 | @@ -926,6 +1275,7 @@ |
854 | self.tarfile.add_file("1.0/empty.efi", b"") |
855 | self.tarfile.add_file("1.0/empty.ko", b"") |
856 | self.tarfile.add_file("1.0/empty.opal", b"") |
857 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
858 | self.process_emulate() |
859 | sha256file = os.path.join(self.getSignedPath("test", "amd64"), |
860 | "1.0", "SHA256SUMS") |
861 | @@ -949,6 +1299,7 @@ |
862 | self.tarfile.add_file("1.0/empty.efi", b"") |
863 | self.tarfile.add_file("1.0/empty.ko", b"") |
864 | self.tarfile.add_file("1.0/empty.opal", b"") |
865 | + self.tarfile.add_file("1.0/empty.sipl", b"") |
866 | self.process_emulate() |
867 | sha256file = os.path.join(self.getSignedPath("test", "amd64"), |
868 | "1.0", "SHA256SUMS") |
869 | @@ -982,6 +1333,7 @@ |
870 | self.tarfile.add_file("1.0/empty.efi", "") |
871 | self.tarfile.add_file("1.0/empty.ko", "") |
872 | self.tarfile.add_file("1.0/empty.opal", "") |
873 | + self.tarfile.add_file("1.0/empty.sipl", "") |
874 | self.process_emulate() |
875 | sha256file = os.path.join(self.getSignedPath("test", "amd64"), |
876 | "1.0", "SHA256SUMS") |