Merge lp:~apw/launchpad/signing-fit into lp:launchpad

Proposed by Andy Whitcroft
Status: Merged
Merged at revision: 18987
Proposed branch: lp:~apw/launchpad/signing-fit
Merge into: lp:launchpad
Prerequisite: lp:~apw/launchpad/signing-sipl
Diff against target: 513 lines (+208/-15)
2 files modified
lib/lp/archivepublisher/signing.py (+42/-9)
lib/lp/archivepublisher/tests/test_signing.py (+166/-6)
To merge this branch: bzr merge lp:~apw/launchpad/signing-fit
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+368277@code.launchpad.net

This proposal supersedes a proposal from 2019-06-03.

Commit message

Add u-boot Flat Image Tree signing support.

Description of the change

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.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

This looks OK to me, thanks.

Prerequisites for landing this MP:

 * u-boot security updates must be landed in xenial-updates and bionic-updates (Andy is on top of this)
 * launchpad-soyuz-dependencies 0.135 must be installed on pepo and haetae (I'll deal with this)

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

The launchpad-dependencies upgrade is https://portal.admin.canonical.com/C119320.

Revision history for this message
Andy Whitcroft (apw) wrote :

The u-boot updates containing the key-name hardening are now all released to -updates:

 u-boot | 2016.01+dfsg1-2ubuntu5 | xenial-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3~18.04.1 | bionic-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3~18.10.1 | cosmic-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3 | disco-updates | source

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 2019-06-06 10:57:20 +0000
3+++ lib/lp/archivepublisher/signing.py 2019-06-06 10:57:20 +0000
4@@ -97,6 +97,8 @@
5 self.opal_x509 = None
6 self.sipl_pem = None
7 self.sipl_x509 = None
8+ self.fit_key = None
9+ self.fit_cert = None
10 self.autokey = False
11 else:
12 self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")
13@@ -107,6 +109,11 @@
14 self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")
15 self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem")
16 self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509")
17+ # Note: the signature tool allows a collection of keys and takes
18+ # a directory name with all valid keys. Avoid mixing the
19+ # other signing types' keys with the fit keys.
20+ self.fit_key = os.path.join(pubconf.signingroot, "fit", "fit.key")
21+ self.fit_cert = os.path.join(pubconf.signingroot, "fit", "fit.crt")
22 self.autokey = pubconf.signingautokey
23
24 self.setComponents(tarfile_path)
25@@ -182,6 +189,8 @@
26 yield (os.path.join(dirpath, filename), self.signOpal)
27 elif filename.endswith(".sipl"):
28 yield (os.path.join(dirpath, filename), self.signSipl)
29+ elif filename.endswith(".fit"):
30+ yield (os.path.join(dirpath, filename), self.signFit)
31
32 def getKeys(self, which, generate, *keynames):
33 """Validate and return the uefi key and cert for encryption."""
34@@ -213,29 +222,33 @@
35 common_name = "PPA %s %s" % (owner, archive)
36 return common_name[0:64 - len(suffix)] + suffix
37
38- def generateUefiKeys(self):
39- """Generate new UEFI Keys for this archive."""
40- directory = os.path.dirname(self.uefi_key)
41+ def generateKeyCrtPair(self, key_type, key_filename, cert_filename):
42+ """Generate new Key/Crt key pairs."""
43+ directory = os.path.dirname(key_filename)
44 if not os.path.exists(directory):
45 os.makedirs(directory)
46
47 common_name = self.generateKeyCommonName(
48- self.archive.owner.name, self.archive.name)
49+ self.archive.owner.name, self.archive.name, key_type)
50 subject = '/CN=' + common_name + '/'
51
52 old_mask = os.umask(0o077)
53 try:
54 new_key_cmd = [
55 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
56- '-subj', subject, '-keyout', self.uefi_key,
57- '-out', self.uefi_cert, '-days', '3650', '-nodes', '-sha256',
58+ '-subj', subject, '-keyout', key_filename,
59+ '-out', cert_filename, '-days', '3650', '-nodes', '-sha256',
60 ]
61- self.callLog("UEFI keygen", new_key_cmd)
62+ self.callLog(key_type + " keygen", new_key_cmd)
63 finally:
64 os.umask(old_mask)
65
66- if os.path.exists(self.uefi_cert):
67- os.chmod(self.uefi_cert, 0o644)
68+ if os.path.exists(cert_filename):
69+ os.chmod(cert_filename, 0o644)
70+
71+ def generateUefiKeys(self):
72+ """Generate new UEFI Keys for this archive."""
73+ self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert)
74
75 def signUefi(self, image):
76 """Attempt to sign an image."""
77@@ -368,6 +381,26 @@
78 cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
79 return self.callLog("SIPL signing", cmdl)
80
81+ def generateFitKeys(self):
82+ """Generate new FIT Keys for this archive."""
83+ self.generateKeyCrtPair("FIT", self.fit_key, self.fit_cert)
84+
85+ def signFit(self, image):
86+ """Attempt to sign an image."""
87+ image_signed = "%s.signed" % image
88+ remove_if_exists(image_signed)
89+ (key, cert) = self.getKeys('FIT', self.generateFitKeys,
90+ self.fit_key, self.fit_cert)
91+ if not key or not cert:
92+ return
93+ self.publishPublicKey(cert)
94+ # Make a copy of the image as mkimage signs in place and in
95+ # signed-only mode we will remove the original file.
96+ shutil.copy(image, image_signed)
97+ cmdl = ["mkimage", "-F", "-k", os.path.dirname(key), "-r",
98+ image_signed]
99+ return self.callLog("FIT signing", cmdl)
100+
101 def convertToTarball(self):
102 """Convert unpacked output to signing tarball."""
103 tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")
104
105=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
106--- lib/lp/archivepublisher/tests/test_signing.py 2019-06-06 10:57:20 +0000
107+++ lib/lp/archivepublisher/tests/test_signing.py 2019-06-06 10:57:20 +0000
108@@ -85,6 +85,8 @@
109 self.callers = {
110 "UEFI signing": 0,
111 "UEFI keygen": 0,
112+ "FIT signing": 0,
113+ "FIT keygen": 0,
114 "Kmod signing": 0,
115 "Kmod keygen key": 0,
116 "Kmod keygen cert": 0,
117@@ -111,6 +113,15 @@
118 write_file(self.upload.uefi_key, b"")
119 write_file(self.upload.uefi_cert, b"")
120
121+ elif description == "FIT signing":
122+ filename = cmdl[-1]
123+ if filename.endswith(".fit"):
124+ write_file(filename + ".signed", b"")
125+
126+ elif description == "FIT keygen":
127+ write_file(self.upload.fit_key, b"")
128+ write_file(self.upload.fit_cert, b"")
129+
130 elif description == "Kmod signing":
131 filename = cmdl[-1]
132 if filename.endswith(".ko.sig"):
133@@ -191,7 +202,7 @@
134 purpose=ArchivePurpose.PPA)
135 self.signing_dir = os.path.join(
136 self.temp_dir, "signing", "signing-owner", "testing")
137- self.testcase_cn = '/CN=PPA signing-owner testing/'
138+ self.testcase_cn = 'PPA signing-owner testing'
139 pubconf = getPubConfig(self.archive)
140 if not os.path.exists(pubconf.temproot):
141 os.makedirs(pubconf.temproot)
142@@ -211,6 +222,15 @@
143 write_file(self.key, b"")
144 write_file(self.cert, b"")
145
146+ def setUpFitKeys(self, create=True):
147+ # We expect and need the fit keys to be in their own
148+ # directory as part of key protection for mkimage.
149+ self.fit_key = os.path.join(self.signing_dir, "fit", "fit.key")
150+ self.fit_cert = os.path.join(self.signing_dir, "fit", "fit.crt")
151+ if create:
152+ write_file(self.fit_key, b"")
153+ write_file(self.fit_cert, b"")
154+
155 def setUpKmodKeys(self, create=True):
156 self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem")
157 self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509")
158@@ -255,6 +275,7 @@
159 upload = SigningUpload()
160 # Under no circumstances is it safe to execute actual commands.
161 self.fake_call = FakeMethod(result=0)
162+ self.fake_copyfile = FakeMethod(result=0)
163 upload.callLog = FakeMethodCallLog(upload=upload)
164 self.useFixture(MonkeyPatch("subprocess.call", self.fake_call))
165 upload.process(self.archive, self.path, self.suite)
166@@ -269,6 +290,7 @@
167 upload.signKmod = FakeMethod()
168 upload.signOpal = FakeMethod()
169 upload.signSipl = FakeMethod()
170+ upload.signFit = FakeMethod()
171 # Under no circumstances is it safe to execute actual commands.
172 fake_call = FakeMethod(result=0)
173 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
174@@ -290,6 +312,7 @@
175 self.tarfile.add_file("1.0/empty.ko", b"")
176 self.tarfile.add_file("1.0/empty.opal", b"")
177 self.tarfile.add_file("1.0/empty.sipl", b"")
178+ self.tarfile.add_file("1.0/empty.fit", b"")
179 upload = self.process_emulate()
180 self.assertContentEqual([], upload.callLog.caller_list())
181
182@@ -301,6 +324,7 @@
183 self.tarfile.add_file("1.0/empty.ko", b"")
184 self.tarfile.add_file("1.0/empty.opal", b"")
185 self.tarfile.add_file("1.0/empty.sipl", b"")
186+ self.tarfile.add_file("1.0/empty.fit", b"")
187 upload = self.process_emulate()
188 self.assertContentEqual([], upload.callLog.caller_list())
189
190@@ -314,6 +338,7 @@
191 self.tarfile.add_file("1.0/empty.ko", b"")
192 self.tarfile.add_file("1.0/empty.opal", b"")
193 self.tarfile.add_file("1.0/empty.sipl", b"")
194+ self.tarfile.add_file("1.0/empty.fit", b"")
195 upload = self.process_emulate()
196 expected_callers = [
197 ('UEFI signing', 1),
198@@ -330,6 +355,7 @@
199 self.tarfile.add_file("1.0/empty.ko", b"")
200 self.tarfile.add_file("1.0/empty.opal", b"")
201 self.tarfile.add_file("1.0/empty.sipl", b"")
202+ self.tarfile.add_file("1.0/empty.fit", b"")
203 upload = self.process_emulate()
204 expected_callers = [
205 ('UEFI keygen', 1),
206@@ -339,10 +365,12 @@
207 ('Opal keygen cert', 1),
208 ('SIPL keygen key', 1),
209 ('SIPL keygen cert', 1),
210+ ('FIT keygen', 1),
211 ('UEFI signing', 1),
212 ('Kmod signing', 1),
213 ('Opal signing', 1),
214 ('SIPL signing', 1),
215+ ('FIT signing', 1),
216 ]
217 self.assertContentEqual(expected_callers, upload.callLog.caller_list())
218
219@@ -417,11 +445,13 @@
220 self.setUpKmodKeys()
221 self.setUpOpalKeys()
222 self.setUpSiplKeys()
223+ self.setUpFitKeys()
224 self.openArchive("test", "1.0", "amd64")
225 self.tarfile.add_file("1.0/empty.efi", b"")
226 self.tarfile.add_file("1.0/empty.ko", b"")
227 self.tarfile.add_file("1.0/empty.opal", b"")
228 self.tarfile.add_file("1.0/empty.sipl", b"")
229+ self.tarfile.add_file("1.0/empty.fit", b"")
230 self.process_emulate()
231 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
232 "1.0/SHA256SUMS",
233@@ -429,6 +459,7 @@
234 "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",
235 "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",
236 "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
237+ "1.0/empty.fit", "1.0/empty.fit.signed", "1.0/control/fit.crt",
238 ]))
239
240 def test_options_tarball(self):
241@@ -438,12 +469,14 @@
242 self.setUpKmodKeys()
243 self.setUpOpalKeys()
244 self.setUpSiplKeys()
245+ self.setUpFitKeys()
246 self.openArchive("test", "1.0", "amd64")
247 self.tarfile.add_file("1.0/control/options", b"tarball")
248 self.tarfile.add_file("1.0/empty.efi", b"")
249 self.tarfile.add_file("1.0/empty.ko", b"")
250 self.tarfile.add_file("1.0/empty.opal", b"")
251 self.tarfile.add_file("1.0/empty.sipl", b"")
252+ self.tarfile.add_file("1.0/empty.fit", b"")
253 self.process_emulate()
254 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
255 "1.0/SHA256SUMS",
256@@ -461,6 +494,8 @@
257 '1.0/control/opal.x509',
258 '1.0/empty.sipl', '1.0/empty.sipl.sig',
259 '1.0/control/sipl.x509',
260+ '1.0/empty.fit', '1.0/empty.fit.signed',
261+ '1.0/control/fit.crt',
262 ], tarball.getnames())
263
264 def test_options_signed_only(self):
265@@ -470,12 +505,14 @@
266 self.setUpKmodKeys()
267 self.setUpOpalKeys()
268 self.setUpSiplKeys()
269+ self.setUpFitKeys()
270 self.openArchive("test", "1.0", "amd64")
271 self.tarfile.add_file("1.0/control/options", b"signed-only")
272 self.tarfile.add_file("1.0/empty.efi", b"")
273 self.tarfile.add_file("1.0/empty.ko", b"")
274 self.tarfile.add_file("1.0/empty.opal", b"")
275 self.tarfile.add_file("1.0/empty.sipl", b"")
276+ self.tarfile.add_file("1.0/empty.fit", b"")
277 self.process_emulate()
278 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
279 "1.0/SHA256SUMS", "1.0/control/options",
280@@ -483,6 +520,7 @@
281 "1.0/empty.ko.sig", "1.0/control/kmod.x509",
282 "1.0/empty.opal.sig", "1.0/control/opal.x509",
283 "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
284+ "1.0/empty.fit.signed", "1.0/control/fit.crt",
285 ]))
286
287 def test_options_tarball_signed_only(self):
288@@ -493,12 +531,14 @@
289 self.setUpKmodKeys()
290 self.setUpOpalKeys()
291 self.setUpSiplKeys()
292+ self.setUpFitKeys()
293 self.openArchive("test", "1.0", "amd64")
294 self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only")
295 self.tarfile.add_file("1.0/empty.efi", b"")
296 self.tarfile.add_file("1.0/empty.ko", b"")
297 self.tarfile.add_file("1.0/empty.opal", b"")
298 self.tarfile.add_file("1.0/empty.sipl", b"")
299+ self.tarfile.add_file("1.0/empty.fit", b"")
300 self.process_emulate()
301 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
302 "1.0/SHA256SUMS",
303@@ -513,12 +553,17 @@
304 '1.0/empty.ko.sig', '1.0/control/kmod.x509',
305 '1.0/empty.opal.sig', '1.0/control/opal.x509',
306 '1.0/empty.sipl.sig', '1.0/control/sipl.x509',
307+ '1.0/empty.fit.signed', '1.0/control/fit.crt',
308 ], tarball.getnames())
309
310 def test_no_signed_files(self):
311 # Tarballs containing no *.efi files are extracted without complaint.
312 # Nothing is signed.
313 self.setUpUefiKeys()
314+ self.setUpKmodKeys()
315+ self.setUpOpalKeys()
316+ self.setUpSiplKeys()
317+ self.setUpFitKeys()
318 self.openArchive("empty", "1.0", "amd64")
319 self.tarfile.add_file("1.0/hello", b"world")
320 upload = self.process()
321@@ -528,6 +573,7 @@
322 self.assertEqual(0, upload.signKmod.call_count)
323 self.assertEqual(0, upload.signOpal.call_count)
324 self.assertEqual(0, upload.signSipl.call_count)
325+ self.assertEqual(0, upload.signFit.call_count)
326
327 def test_already_exists(self):
328 # If the target directory already exists, processing fails.
329@@ -595,7 +641,71 @@
330 args = fake_call.calls[0][0][0]
331 expected_cmd = [
332 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
333- '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert,
334+ '-subj', '/CN=' + self.testcase_cn + ' UEFI/',
335+ '-keyout', self.key, '-out', self.cert,
336+ '-days', '3650', '-nodes', '-sha256',
337+ ]
338+ self.assertEqual(expected_cmd, args)
339+
340+ def test_correct_fit_signing_command_executed(self):
341+ # Check that calling signFit() will generate the expected command
342+ # when appropriate keys are present.
343+ self.setUpFitKeys()
344+ fake_call = FakeMethod(result=0)
345+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
346+ fake_copy = FakeMethod(result=0)
347+ self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
348+ upload = SigningUpload()
349+ upload.generateFitKeys = FakeMethod()
350+ upload.setTargetDirectory(
351+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
352+ upload.signFit('t.fit')
353+ # Confirm the copy was performed.
354+ self.assertEqual(1, fake_copy.call_count)
355+ args = fake_copy.calls[0][0]
356+ expected_copy = ('t.fit', 't.fit.signed')
357+ self.assertEqual(expected_copy, args)
358+ # Assert command form.
359+ args = fake_call.calls[0][0][0]
360+ expected_cmd = [
361+ 'mkimage', '-F', '-k', os.path.dirname(self.fit_key), '-r',
362+ 't.fit.signed',
363+ ]
364+ self.assertEqual(expected_cmd, args)
365+ self.assertEqual(0, upload.generateFitKeys.call_count)
366+
367+ def test_correct_fit_signing_command_executed_no_keys(self):
368+ # Check that calling signFit() will generate no commands when
369+ # no keys are present.
370+ self.setUpFitKeys(create=False)
371+ fake_call = FakeMethod(result=0)
372+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
373+ upload = SigningUpload()
374+ upload.generateFitKeys = FakeMethod()
375+ upload.setTargetDirectory(
376+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
377+ upload.signUefi('t.fit')
378+ self.assertEqual(0, fake_call.call_count)
379+ self.assertEqual(0, upload.generateFitKeys.call_count)
380+
381+ def test_correct_fit_keygen_command_executed(self):
382+ # Check that calling generateFitKeys() will generate the
383+ # expected command.
384+ self.setUpPPA()
385+ self.setUpFitKeys(create=False)
386+ fake_call = FakeMethod(result=0)
387+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
388+ upload = SigningUpload()
389+ upload.setTargetDirectory(
390+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
391+ upload.generateFitKeys()
392+ self.assertEqual(1, fake_call.call_count)
393+ # Assert the actual command matches.
394+ args = fake_call.calls[0][0][0]
395+ expected_cmd = [
396+ 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
397+ '-subj', '/CN=' + self.testcase_cn + ' FIT/',
398+ '-keyout', self.fit_key, '-out', self.fit_cert,
399 '-days', '3650', '-nodes', '-sha256',
400 ]
401 self.assertEqual(expected_cmd, args)
402@@ -610,7 +720,7 @@
403 text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod)
404
405 id_re = re.compile(r'^# KMOD OpenSSL config\n')
406- cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Kmod')
407+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Kmod')
408 eku_re = re.compile(
409 r'\bextendedKeyUsage\s*=\s*'
410 r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b')
411@@ -696,7 +806,7 @@
412 text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal)
413
414 id_re = re.compile(r'^# OPAL OpenSSL config\n')
415- cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Opal')
416+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Opal')
417
418 self.assertIn('[ req ]', text)
419 self.assertIsNotNone(id_re.search(text))
420@@ -779,7 +889,7 @@
421 text = upload.generateOpensslConfig('SIPL', upload.openssl_config_sipl)
422
423 id_re = re.compile(r'^# SIPL OpenSSL config\n')
424- cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+SIPL')
425+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+SIPL')
426
427 self.assertIn('[ req ]', text)
428 self.assertIsNotNone(id_re.search(text))
429@@ -860,6 +970,14 @@
430 upload = self.process()
431 self.assertEqual(1, upload.signUefi.call_count)
432
433+ def test_signs_fit_image(self):
434+ # Each image in the tarball is signed.
435+ self.setUpFitKeys()
436+ self.openArchive("test", "1.0", "amd64")
437+ self.tarfile.add_file("1.0/empty.fit", b"")
438+ upload = self.process()
439+ self.assertEqual(1, upload.signFit.call_count)
440+
441 def test_signs_kmod_image(self):
442 # Each image in the tarball is signed.
443 self.setUpKmodKeys()
444@@ -886,7 +1004,6 @@
445
446 def test_signs_combo_image(self):
447 # Each image in the tarball is signed.
448- self.setUpKmodKeys()
449 self.openArchive("test", "1.0", "amd64")
450 self.tarfile.add_file("1.0/empty.efi", b"")
451 self.tarfile.add_file("1.0/empty.ko", b"")
452@@ -898,11 +1015,17 @@
453 self.tarfile.add_file("1.0/empty2.sipl", b"")
454 self.tarfile.add_file("1.0/empty3.sipl", b"")
455 self.tarfile.add_file("1.0/empty4.sipl", b"")
456+ self.tarfile.add_file("1.0/empty.fit", b"")
457+ self.tarfile.add_file("1.0/empty2.fit", b"")
458+ self.tarfile.add_file("1.0/empty3.fit", b"")
459+ self.tarfile.add_file("1.0/empty4.fit", b"")
460+ self.tarfile.add_file("1.0/empty5.fit", b"")
461 upload = self.process()
462 self.assertEqual(1, upload.signUefi.call_count)
463 self.assertEqual(2, upload.signKmod.call_count)
464 self.assertEqual(3, upload.signOpal.call_count)
465 self.assertEqual(4, upload.signSipl.call_count)
466+ self.assertEqual(5, upload.signFit.call_count)
467
468 def test_installed(self):
469 # Files in the tarball are installed correctly.
470@@ -974,6 +1097,43 @@
471 self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600)
472 self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644)
473
474+ def test_create_fit_keys_autokey_off(self):
475+ # Keys are not created.
476+ self.setUpFitKeys(create=False)
477+ self.assertFalse(os.path.exists(self.fit_key))
478+ self.assertFalse(os.path.exists(self.fit_cert))
479+ fake_call = FakeMethod(result=0)
480+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
481+ upload = SigningUpload()
482+ upload.callLog = FakeMethodCallLog(upload=upload)
483+ upload.setTargetDirectory(
484+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
485+ upload.signFit(os.path.join(self.makeTemporaryDirectory(), 'fit'))
486+ self.assertEqual(0, upload.callLog.caller_count('FIT keygen'))
487+ self.assertFalse(os.path.exists(self.fit_key))
488+ self.assertFalse(os.path.exists(self.fit_cert))
489+
490+ def test_create_fit_keys_autokey_on(self):
491+ # Keys are created on demand.
492+ self.setUpPPA()
493+ self.setUpFitKeys(create=False)
494+ self.assertFalse(os.path.exists(self.fit_key))
495+ self.assertFalse(os.path.exists(self.fit_cert))
496+ fake_call = FakeMethod(result=0)
497+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
498+ fake_copy = FakeMethod(result=0)
499+ self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
500+ upload = SigningUpload()
501+ upload.callLog = FakeMethodCallLog(upload=upload)
502+ upload.setTargetDirectory(
503+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
504+ upload.signFit(os.path.join(self.makeTemporaryDirectory(), 't.fit'))
505+ self.assertEqual(1, upload.callLog.caller_count('FIT keygen'))
506+ self.assertTrue(os.path.exists(self.fit_key))
507+ self.assertTrue(os.path.exists(self.fit_cert))
508+ self.assertEqual(stat.S_IMODE(os.stat(self.fit_key).st_mode), 0o600)
509+ self.assertEqual(stat.S_IMODE(os.stat(self.fit_cert).st_mode), 0o644)
510+
511 def test_create_kmod_keys_autokey_off(self):
512 # Keys are not created.
513 self.setUpKmodKeys(create=False)