Merge lp:~apw/launchpad/signing-fit into lp:launchpad
- signing-fit
- Merge into devel
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 | ||||
Related bugs: |
|
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.
Colin Watson (cjwatson) wrote : | # |
The launchpad-
Andy Whitcroft (apw) wrote : | # |
The u-boot updates containing the key-name hardening are now all released to -updates:
u-boot | 2016.01+
u-boot | 2018.07~
u-boot | 2018.07~
u-boot | 2018.07~
Preview Diff
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 | 97 | self.opal_x509 = None | 97 | self.opal_x509 = None |
6 | 98 | self.sipl_pem = None | 98 | self.sipl_pem = None |
7 | 99 | self.sipl_x509 = None | 99 | self.sipl_x509 = None |
8 | 100 | self.fit_key = None | ||
9 | 101 | self.fit_cert = None | ||
10 | 100 | self.autokey = False | 102 | self.autokey = False |
11 | 101 | else: | 103 | else: |
12 | 102 | self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key") | 104 | self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key") |
13 | @@ -107,6 +109,11 @@ | |||
14 | 107 | self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509") | 109 | self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509") |
15 | 108 | self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem") | 110 | self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem") |
16 | 109 | self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509") | 111 | self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509") |
17 | 112 | # Note: the signature tool allows a collection of keys and takes | ||
18 | 113 | # a directory name with all valid keys. Avoid mixing the | ||
19 | 114 | # other signing types' keys with the fit keys. | ||
20 | 115 | self.fit_key = os.path.join(pubconf.signingroot, "fit", "fit.key") | ||
21 | 116 | self.fit_cert = os.path.join(pubconf.signingroot, "fit", "fit.crt") | ||
22 | 110 | self.autokey = pubconf.signingautokey | 117 | self.autokey = pubconf.signingautokey |
23 | 111 | 118 | ||
24 | 112 | self.setComponents(tarfile_path) | 119 | self.setComponents(tarfile_path) |
25 | @@ -182,6 +189,8 @@ | |||
26 | 182 | yield (os.path.join(dirpath, filename), self.signOpal) | 189 | yield (os.path.join(dirpath, filename), self.signOpal) |
27 | 183 | elif filename.endswith(".sipl"): | 190 | elif filename.endswith(".sipl"): |
28 | 184 | yield (os.path.join(dirpath, filename), self.signSipl) | 191 | yield (os.path.join(dirpath, filename), self.signSipl) |
29 | 192 | elif filename.endswith(".fit"): | ||
30 | 193 | yield (os.path.join(dirpath, filename), self.signFit) | ||
31 | 185 | 194 | ||
32 | 186 | def getKeys(self, which, generate, *keynames): | 195 | def getKeys(self, which, generate, *keynames): |
33 | 187 | """Validate and return the uefi key and cert for encryption.""" | 196 | """Validate and return the uefi key and cert for encryption.""" |
34 | @@ -213,29 +222,33 @@ | |||
35 | 213 | common_name = "PPA %s %s" % (owner, archive) | 222 | common_name = "PPA %s %s" % (owner, archive) |
36 | 214 | return common_name[0:64 - len(suffix)] + suffix | 223 | return common_name[0:64 - len(suffix)] + suffix |
37 | 215 | 224 | ||
41 | 216 | def generateUefiKeys(self): | 225 | def generateKeyCrtPair(self, key_type, key_filename, cert_filename): |
42 | 217 | """Generate new UEFI Keys for this archive.""" | 226 | """Generate new Key/Crt key pairs.""" |
43 | 218 | directory = os.path.dirname(self.uefi_key) | 227 | directory = os.path.dirname(key_filename) |
44 | 219 | if not os.path.exists(directory): | 228 | if not os.path.exists(directory): |
45 | 220 | os.makedirs(directory) | 229 | os.makedirs(directory) |
46 | 221 | 230 | ||
47 | 222 | common_name = self.generateKeyCommonName( | 231 | common_name = self.generateKeyCommonName( |
49 | 223 | self.archive.owner.name, self.archive.name) | 232 | self.archive.owner.name, self.archive.name, key_type) |
50 | 224 | subject = '/CN=' + common_name + '/' | 233 | subject = '/CN=' + common_name + '/' |
51 | 225 | 234 | ||
52 | 226 | old_mask = os.umask(0o077) | 235 | old_mask = os.umask(0o077) |
53 | 227 | try: | 236 | try: |
54 | 228 | new_key_cmd = [ | 237 | new_key_cmd = [ |
55 | 229 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', | 238 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', |
58 | 230 | '-subj', subject, '-keyout', self.uefi_key, | 239 | '-subj', subject, '-keyout', key_filename, |
59 | 231 | '-out', self.uefi_cert, '-days', '3650', '-nodes', '-sha256', | 240 | '-out', cert_filename, '-days', '3650', '-nodes', '-sha256', |
60 | 232 | ] | 241 | ] |
62 | 233 | self.callLog("UEFI keygen", new_key_cmd) | 242 | self.callLog(key_type + " keygen", new_key_cmd) |
63 | 234 | finally: | 243 | finally: |
64 | 235 | os.umask(old_mask) | 244 | os.umask(old_mask) |
65 | 236 | 245 | ||
68 | 237 | if os.path.exists(self.uefi_cert): | 246 | if os.path.exists(cert_filename): |
69 | 238 | os.chmod(self.uefi_cert, 0o644) | 247 | os.chmod(cert_filename, 0o644) |
70 | 248 | |||
71 | 249 | def generateUefiKeys(self): | ||
72 | 250 | """Generate new UEFI Keys for this archive.""" | ||
73 | 251 | self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert) | ||
74 | 239 | 252 | ||
75 | 240 | def signUefi(self, image): | 253 | def signUefi(self, image): |
76 | 241 | """Attempt to sign an image.""" | 254 | """Attempt to sign an image.""" |
77 | @@ -368,6 +381,26 @@ | |||
78 | 368 | cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"] | 381 | cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"] |
79 | 369 | return self.callLog("SIPL signing", cmdl) | 382 | return self.callLog("SIPL signing", cmdl) |
80 | 370 | 383 | ||
81 | 384 | def generateFitKeys(self): | ||
82 | 385 | """Generate new FIT Keys for this archive.""" | ||
83 | 386 | self.generateKeyCrtPair("FIT", self.fit_key, self.fit_cert) | ||
84 | 387 | |||
85 | 388 | def signFit(self, image): | ||
86 | 389 | """Attempt to sign an image.""" | ||
87 | 390 | image_signed = "%s.signed" % image | ||
88 | 391 | remove_if_exists(image_signed) | ||
89 | 392 | (key, cert) = self.getKeys('FIT', self.generateFitKeys, | ||
90 | 393 | self.fit_key, self.fit_cert) | ||
91 | 394 | if not key or not cert: | ||
92 | 395 | return | ||
93 | 396 | self.publishPublicKey(cert) | ||
94 | 397 | # Make a copy of the image as mkimage signs in place and in | ||
95 | 398 | # signed-only mode we will remove the original file. | ||
96 | 399 | shutil.copy(image, image_signed) | ||
97 | 400 | cmdl = ["mkimage", "-F", "-k", os.path.dirname(key), "-r", | ||
98 | 401 | image_signed] | ||
99 | 402 | return self.callLog("FIT signing", cmdl) | ||
100 | 403 | |||
101 | 371 | def convertToTarball(self): | 404 | def convertToTarball(self): |
102 | 372 | """Convert unpacked output to signing tarball.""" | 405 | """Convert unpacked output to signing tarball.""" |
103 | 373 | tarfilename = os.path.join(self.tmpdir, "signed.tar.gz") | 406 | tarfilename = os.path.join(self.tmpdir, "signed.tar.gz") |
104 | 374 | 407 | ||
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 | 85 | self.callers = { | 85 | self.callers = { |
110 | 86 | "UEFI signing": 0, | 86 | "UEFI signing": 0, |
111 | 87 | "UEFI keygen": 0, | 87 | "UEFI keygen": 0, |
112 | 88 | "FIT signing": 0, | ||
113 | 89 | "FIT keygen": 0, | ||
114 | 88 | "Kmod signing": 0, | 90 | "Kmod signing": 0, |
115 | 89 | "Kmod keygen key": 0, | 91 | "Kmod keygen key": 0, |
116 | 90 | "Kmod keygen cert": 0, | 92 | "Kmod keygen cert": 0, |
117 | @@ -111,6 +113,15 @@ | |||
118 | 111 | write_file(self.upload.uefi_key, b"") | 113 | write_file(self.upload.uefi_key, b"") |
119 | 112 | write_file(self.upload.uefi_cert, b"") | 114 | write_file(self.upload.uefi_cert, b"") |
120 | 113 | 115 | ||
121 | 116 | elif description == "FIT signing": | ||
122 | 117 | filename = cmdl[-1] | ||
123 | 118 | if filename.endswith(".fit"): | ||
124 | 119 | write_file(filename + ".signed", b"") | ||
125 | 120 | |||
126 | 121 | elif description == "FIT keygen": | ||
127 | 122 | write_file(self.upload.fit_key, b"") | ||
128 | 123 | write_file(self.upload.fit_cert, b"") | ||
129 | 124 | |||
130 | 114 | elif description == "Kmod signing": | 125 | elif description == "Kmod signing": |
131 | 115 | filename = cmdl[-1] | 126 | filename = cmdl[-1] |
132 | 116 | if filename.endswith(".ko.sig"): | 127 | if filename.endswith(".ko.sig"): |
133 | @@ -191,7 +202,7 @@ | |||
134 | 191 | purpose=ArchivePurpose.PPA) | 202 | purpose=ArchivePurpose.PPA) |
135 | 192 | self.signing_dir = os.path.join( | 203 | self.signing_dir = os.path.join( |
136 | 193 | self.temp_dir, "signing", "signing-owner", "testing") | 204 | self.temp_dir, "signing", "signing-owner", "testing") |
138 | 194 | self.testcase_cn = '/CN=PPA signing-owner testing/' | 205 | self.testcase_cn = 'PPA signing-owner testing' |
139 | 195 | pubconf = getPubConfig(self.archive) | 206 | pubconf = getPubConfig(self.archive) |
140 | 196 | if not os.path.exists(pubconf.temproot): | 207 | if not os.path.exists(pubconf.temproot): |
141 | 197 | os.makedirs(pubconf.temproot) | 208 | os.makedirs(pubconf.temproot) |
142 | @@ -211,6 +222,15 @@ | |||
143 | 211 | write_file(self.key, b"") | 222 | write_file(self.key, b"") |
144 | 212 | write_file(self.cert, b"") | 223 | write_file(self.cert, b"") |
145 | 213 | 224 | ||
146 | 225 | def setUpFitKeys(self, create=True): | ||
147 | 226 | # We expect and need the fit keys to be in their own | ||
148 | 227 | # directory as part of key protection for mkimage. | ||
149 | 228 | self.fit_key = os.path.join(self.signing_dir, "fit", "fit.key") | ||
150 | 229 | self.fit_cert = os.path.join(self.signing_dir, "fit", "fit.crt") | ||
151 | 230 | if create: | ||
152 | 231 | write_file(self.fit_key, b"") | ||
153 | 232 | write_file(self.fit_cert, b"") | ||
154 | 233 | |||
155 | 214 | def setUpKmodKeys(self, create=True): | 234 | def setUpKmodKeys(self, create=True): |
156 | 215 | self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem") | 235 | self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem") |
157 | 216 | self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509") | 236 | self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509") |
158 | @@ -255,6 +275,7 @@ | |||
159 | 255 | upload = SigningUpload() | 275 | upload = SigningUpload() |
160 | 256 | # Under no circumstances is it safe to execute actual commands. | 276 | # Under no circumstances is it safe to execute actual commands. |
161 | 257 | self.fake_call = FakeMethod(result=0) | 277 | self.fake_call = FakeMethod(result=0) |
162 | 278 | self.fake_copyfile = FakeMethod(result=0) | ||
163 | 258 | upload.callLog = FakeMethodCallLog(upload=upload) | 279 | upload.callLog = FakeMethodCallLog(upload=upload) |
164 | 259 | self.useFixture(MonkeyPatch("subprocess.call", self.fake_call)) | 280 | self.useFixture(MonkeyPatch("subprocess.call", self.fake_call)) |
165 | 260 | upload.process(self.archive, self.path, self.suite) | 281 | upload.process(self.archive, self.path, self.suite) |
166 | @@ -269,6 +290,7 @@ | |||
167 | 269 | upload.signKmod = FakeMethod() | 290 | upload.signKmod = FakeMethod() |
168 | 270 | upload.signOpal = FakeMethod() | 291 | upload.signOpal = FakeMethod() |
169 | 271 | upload.signSipl = FakeMethod() | 292 | upload.signSipl = FakeMethod() |
170 | 293 | upload.signFit = FakeMethod() | ||
171 | 272 | # Under no circumstances is it safe to execute actual commands. | 294 | # Under no circumstances is it safe to execute actual commands. |
172 | 273 | fake_call = FakeMethod(result=0) | 295 | fake_call = FakeMethod(result=0) |
173 | 274 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | 296 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
174 | @@ -290,6 +312,7 @@ | |||
175 | 290 | self.tarfile.add_file("1.0/empty.ko", b"") | 312 | self.tarfile.add_file("1.0/empty.ko", b"") |
176 | 291 | self.tarfile.add_file("1.0/empty.opal", b"") | 313 | self.tarfile.add_file("1.0/empty.opal", b"") |
177 | 292 | self.tarfile.add_file("1.0/empty.sipl", b"") | 314 | self.tarfile.add_file("1.0/empty.sipl", b"") |
178 | 315 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
179 | 293 | upload = self.process_emulate() | 316 | upload = self.process_emulate() |
180 | 294 | self.assertContentEqual([], upload.callLog.caller_list()) | 317 | self.assertContentEqual([], upload.callLog.caller_list()) |
181 | 295 | 318 | ||
182 | @@ -301,6 +324,7 @@ | |||
183 | 301 | self.tarfile.add_file("1.0/empty.ko", b"") | 324 | self.tarfile.add_file("1.0/empty.ko", b"") |
184 | 302 | self.tarfile.add_file("1.0/empty.opal", b"") | 325 | self.tarfile.add_file("1.0/empty.opal", b"") |
185 | 303 | self.tarfile.add_file("1.0/empty.sipl", b"") | 326 | self.tarfile.add_file("1.0/empty.sipl", b"") |
186 | 327 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
187 | 304 | upload = self.process_emulate() | 328 | upload = self.process_emulate() |
188 | 305 | self.assertContentEqual([], upload.callLog.caller_list()) | 329 | self.assertContentEqual([], upload.callLog.caller_list()) |
189 | 306 | 330 | ||
190 | @@ -314,6 +338,7 @@ | |||
191 | 314 | self.tarfile.add_file("1.0/empty.ko", b"") | 338 | self.tarfile.add_file("1.0/empty.ko", b"") |
192 | 315 | self.tarfile.add_file("1.0/empty.opal", b"") | 339 | self.tarfile.add_file("1.0/empty.opal", b"") |
193 | 316 | self.tarfile.add_file("1.0/empty.sipl", b"") | 340 | self.tarfile.add_file("1.0/empty.sipl", b"") |
194 | 341 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
195 | 317 | upload = self.process_emulate() | 342 | upload = self.process_emulate() |
196 | 318 | expected_callers = [ | 343 | expected_callers = [ |
197 | 319 | ('UEFI signing', 1), | 344 | ('UEFI signing', 1), |
198 | @@ -330,6 +355,7 @@ | |||
199 | 330 | self.tarfile.add_file("1.0/empty.ko", b"") | 355 | self.tarfile.add_file("1.0/empty.ko", b"") |
200 | 331 | self.tarfile.add_file("1.0/empty.opal", b"") | 356 | self.tarfile.add_file("1.0/empty.opal", b"") |
201 | 332 | self.tarfile.add_file("1.0/empty.sipl", b"") | 357 | self.tarfile.add_file("1.0/empty.sipl", b"") |
202 | 358 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
203 | 333 | upload = self.process_emulate() | 359 | upload = self.process_emulate() |
204 | 334 | expected_callers = [ | 360 | expected_callers = [ |
205 | 335 | ('UEFI keygen', 1), | 361 | ('UEFI keygen', 1), |
206 | @@ -339,10 +365,12 @@ | |||
207 | 339 | ('Opal keygen cert', 1), | 365 | ('Opal keygen cert', 1), |
208 | 340 | ('SIPL keygen key', 1), | 366 | ('SIPL keygen key', 1), |
209 | 341 | ('SIPL keygen cert', 1), | 367 | ('SIPL keygen cert', 1), |
210 | 368 | ('FIT keygen', 1), | ||
211 | 342 | ('UEFI signing', 1), | 369 | ('UEFI signing', 1), |
212 | 343 | ('Kmod signing', 1), | 370 | ('Kmod signing', 1), |
213 | 344 | ('Opal signing', 1), | 371 | ('Opal signing', 1), |
214 | 345 | ('SIPL signing', 1), | 372 | ('SIPL signing', 1), |
215 | 373 | ('FIT signing', 1), | ||
216 | 346 | ] | 374 | ] |
217 | 347 | self.assertContentEqual(expected_callers, upload.callLog.caller_list()) | 375 | self.assertContentEqual(expected_callers, upload.callLog.caller_list()) |
218 | 348 | 376 | ||
219 | @@ -417,11 +445,13 @@ | |||
220 | 417 | self.setUpKmodKeys() | 445 | self.setUpKmodKeys() |
221 | 418 | self.setUpOpalKeys() | 446 | self.setUpOpalKeys() |
222 | 419 | self.setUpSiplKeys() | 447 | self.setUpSiplKeys() |
223 | 448 | self.setUpFitKeys() | ||
224 | 420 | self.openArchive("test", "1.0", "amd64") | 449 | self.openArchive("test", "1.0", "amd64") |
225 | 421 | self.tarfile.add_file("1.0/empty.efi", b"") | 450 | self.tarfile.add_file("1.0/empty.efi", b"") |
226 | 422 | self.tarfile.add_file("1.0/empty.ko", b"") | 451 | self.tarfile.add_file("1.0/empty.ko", b"") |
227 | 423 | self.tarfile.add_file("1.0/empty.opal", b"") | 452 | self.tarfile.add_file("1.0/empty.opal", b"") |
228 | 424 | self.tarfile.add_file("1.0/empty.sipl", b"") | 453 | self.tarfile.add_file("1.0/empty.sipl", b"") |
229 | 454 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
230 | 425 | self.process_emulate() | 455 | self.process_emulate() |
231 | 426 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ | 456 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
232 | 427 | "1.0/SHA256SUMS", | 457 | "1.0/SHA256SUMS", |
233 | @@ -429,6 +459,7 @@ | |||
234 | 429 | "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509", | 459 | "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509", |
235 | 430 | "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509", | 460 | "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509", |
236 | 431 | "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509", | 461 | "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509", |
237 | 462 | "1.0/empty.fit", "1.0/empty.fit.signed", "1.0/control/fit.crt", | ||
238 | 432 | ])) | 463 | ])) |
239 | 433 | 464 | ||
240 | 434 | def test_options_tarball(self): | 465 | def test_options_tarball(self): |
241 | @@ -438,12 +469,14 @@ | |||
242 | 438 | self.setUpKmodKeys() | 469 | self.setUpKmodKeys() |
243 | 439 | self.setUpOpalKeys() | 470 | self.setUpOpalKeys() |
244 | 440 | self.setUpSiplKeys() | 471 | self.setUpSiplKeys() |
245 | 472 | self.setUpFitKeys() | ||
246 | 441 | self.openArchive("test", "1.0", "amd64") | 473 | self.openArchive("test", "1.0", "amd64") |
247 | 442 | self.tarfile.add_file("1.0/control/options", b"tarball") | 474 | self.tarfile.add_file("1.0/control/options", b"tarball") |
248 | 443 | self.tarfile.add_file("1.0/empty.efi", b"") | 475 | self.tarfile.add_file("1.0/empty.efi", b"") |
249 | 444 | self.tarfile.add_file("1.0/empty.ko", b"") | 476 | self.tarfile.add_file("1.0/empty.ko", b"") |
250 | 445 | self.tarfile.add_file("1.0/empty.opal", b"") | 477 | self.tarfile.add_file("1.0/empty.opal", b"") |
251 | 446 | self.tarfile.add_file("1.0/empty.sipl", b"") | 478 | self.tarfile.add_file("1.0/empty.sipl", b"") |
252 | 479 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
253 | 447 | self.process_emulate() | 480 | self.process_emulate() |
254 | 448 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ | 481 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
255 | 449 | "1.0/SHA256SUMS", | 482 | "1.0/SHA256SUMS", |
256 | @@ -461,6 +494,8 @@ | |||
257 | 461 | '1.0/control/opal.x509', | 494 | '1.0/control/opal.x509', |
258 | 462 | '1.0/empty.sipl', '1.0/empty.sipl.sig', | 495 | '1.0/empty.sipl', '1.0/empty.sipl.sig', |
259 | 463 | '1.0/control/sipl.x509', | 496 | '1.0/control/sipl.x509', |
260 | 497 | '1.0/empty.fit', '1.0/empty.fit.signed', | ||
261 | 498 | '1.0/control/fit.crt', | ||
262 | 464 | ], tarball.getnames()) | 499 | ], tarball.getnames()) |
263 | 465 | 500 | ||
264 | 466 | def test_options_signed_only(self): | 501 | def test_options_signed_only(self): |
265 | @@ -470,12 +505,14 @@ | |||
266 | 470 | self.setUpKmodKeys() | 505 | self.setUpKmodKeys() |
267 | 471 | self.setUpOpalKeys() | 506 | self.setUpOpalKeys() |
268 | 472 | self.setUpSiplKeys() | 507 | self.setUpSiplKeys() |
269 | 508 | self.setUpFitKeys() | ||
270 | 473 | self.openArchive("test", "1.0", "amd64") | 509 | self.openArchive("test", "1.0", "amd64") |
271 | 474 | self.tarfile.add_file("1.0/control/options", b"signed-only") | 510 | self.tarfile.add_file("1.0/control/options", b"signed-only") |
272 | 475 | self.tarfile.add_file("1.0/empty.efi", b"") | 511 | self.tarfile.add_file("1.0/empty.efi", b"") |
273 | 476 | self.tarfile.add_file("1.0/empty.ko", b"") | 512 | self.tarfile.add_file("1.0/empty.ko", b"") |
274 | 477 | self.tarfile.add_file("1.0/empty.opal", b"") | 513 | self.tarfile.add_file("1.0/empty.opal", b"") |
275 | 478 | self.tarfile.add_file("1.0/empty.sipl", b"") | 514 | self.tarfile.add_file("1.0/empty.sipl", b"") |
276 | 515 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
277 | 479 | self.process_emulate() | 516 | self.process_emulate() |
278 | 480 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ | 517 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
279 | 481 | "1.0/SHA256SUMS", "1.0/control/options", | 518 | "1.0/SHA256SUMS", "1.0/control/options", |
280 | @@ -483,6 +520,7 @@ | |||
281 | 483 | "1.0/empty.ko.sig", "1.0/control/kmod.x509", | 520 | "1.0/empty.ko.sig", "1.0/control/kmod.x509", |
282 | 484 | "1.0/empty.opal.sig", "1.0/control/opal.x509", | 521 | "1.0/empty.opal.sig", "1.0/control/opal.x509", |
283 | 485 | "1.0/empty.sipl.sig", "1.0/control/sipl.x509", | 522 | "1.0/empty.sipl.sig", "1.0/control/sipl.x509", |
284 | 523 | "1.0/empty.fit.signed", "1.0/control/fit.crt", | ||
285 | 486 | ])) | 524 | ])) |
286 | 487 | 525 | ||
287 | 488 | def test_options_tarball_signed_only(self): | 526 | def test_options_tarball_signed_only(self): |
288 | @@ -493,12 +531,14 @@ | |||
289 | 493 | self.setUpKmodKeys() | 531 | self.setUpKmodKeys() |
290 | 494 | self.setUpOpalKeys() | 532 | self.setUpOpalKeys() |
291 | 495 | self.setUpSiplKeys() | 533 | self.setUpSiplKeys() |
292 | 534 | self.setUpFitKeys() | ||
293 | 496 | self.openArchive("test", "1.0", "amd64") | 535 | self.openArchive("test", "1.0", "amd64") |
294 | 497 | self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only") | 536 | self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only") |
295 | 498 | self.tarfile.add_file("1.0/empty.efi", b"") | 537 | self.tarfile.add_file("1.0/empty.efi", b"") |
296 | 499 | self.tarfile.add_file("1.0/empty.ko", b"") | 538 | self.tarfile.add_file("1.0/empty.ko", b"") |
297 | 500 | self.tarfile.add_file("1.0/empty.opal", b"") | 539 | self.tarfile.add_file("1.0/empty.opal", b"") |
298 | 501 | self.tarfile.add_file("1.0/empty.sipl", b"") | 540 | self.tarfile.add_file("1.0/empty.sipl", b"") |
299 | 541 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
300 | 502 | self.process_emulate() | 542 | self.process_emulate() |
301 | 503 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ | 543 | self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([ |
302 | 504 | "1.0/SHA256SUMS", | 544 | "1.0/SHA256SUMS", |
303 | @@ -513,12 +553,17 @@ | |||
304 | 513 | '1.0/empty.ko.sig', '1.0/control/kmod.x509', | 553 | '1.0/empty.ko.sig', '1.0/control/kmod.x509', |
305 | 514 | '1.0/empty.opal.sig', '1.0/control/opal.x509', | 554 | '1.0/empty.opal.sig', '1.0/control/opal.x509', |
306 | 515 | '1.0/empty.sipl.sig', '1.0/control/sipl.x509', | 555 | '1.0/empty.sipl.sig', '1.0/control/sipl.x509', |
307 | 556 | '1.0/empty.fit.signed', '1.0/control/fit.crt', | ||
308 | 516 | ], tarball.getnames()) | 557 | ], tarball.getnames()) |
309 | 517 | 558 | ||
310 | 518 | def test_no_signed_files(self): | 559 | def test_no_signed_files(self): |
311 | 519 | # Tarballs containing no *.efi files are extracted without complaint. | 560 | # Tarballs containing no *.efi files are extracted without complaint. |
312 | 520 | # Nothing is signed. | 561 | # Nothing is signed. |
313 | 521 | self.setUpUefiKeys() | 562 | self.setUpUefiKeys() |
314 | 563 | self.setUpKmodKeys() | ||
315 | 564 | self.setUpOpalKeys() | ||
316 | 565 | self.setUpSiplKeys() | ||
317 | 566 | self.setUpFitKeys() | ||
318 | 522 | self.openArchive("empty", "1.0", "amd64") | 567 | self.openArchive("empty", "1.0", "amd64") |
319 | 523 | self.tarfile.add_file("1.0/hello", b"world") | 568 | self.tarfile.add_file("1.0/hello", b"world") |
320 | 524 | upload = self.process() | 569 | upload = self.process() |
321 | @@ -528,6 +573,7 @@ | |||
322 | 528 | self.assertEqual(0, upload.signKmod.call_count) | 573 | self.assertEqual(0, upload.signKmod.call_count) |
323 | 529 | self.assertEqual(0, upload.signOpal.call_count) | 574 | self.assertEqual(0, upload.signOpal.call_count) |
324 | 530 | self.assertEqual(0, upload.signSipl.call_count) | 575 | self.assertEqual(0, upload.signSipl.call_count) |
325 | 576 | self.assertEqual(0, upload.signFit.call_count) | ||
326 | 531 | 577 | ||
327 | 532 | def test_already_exists(self): | 578 | def test_already_exists(self): |
328 | 533 | # If the target directory already exists, processing fails. | 579 | # If the target directory already exists, processing fails. |
329 | @@ -595,7 +641,71 @@ | |||
330 | 595 | args = fake_call.calls[0][0][0] | 641 | args = fake_call.calls[0][0][0] |
331 | 596 | expected_cmd = [ | 642 | expected_cmd = [ |
332 | 597 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', | 643 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', |
334 | 598 | '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert, | 644 | '-subj', '/CN=' + self.testcase_cn + ' UEFI/', |
335 | 645 | '-keyout', self.key, '-out', self.cert, | ||
336 | 646 | '-days', '3650', '-nodes', '-sha256', | ||
337 | 647 | ] | ||
338 | 648 | self.assertEqual(expected_cmd, args) | ||
339 | 649 | |||
340 | 650 | def test_correct_fit_signing_command_executed(self): | ||
341 | 651 | # Check that calling signFit() will generate the expected command | ||
342 | 652 | # when appropriate keys are present. | ||
343 | 653 | self.setUpFitKeys() | ||
344 | 654 | fake_call = FakeMethod(result=0) | ||
345 | 655 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | ||
346 | 656 | fake_copy = FakeMethod(result=0) | ||
347 | 657 | self.useFixture(MonkeyPatch("shutil.copy", fake_copy)) | ||
348 | 658 | upload = SigningUpload() | ||
349 | 659 | upload.generateFitKeys = FakeMethod() | ||
350 | 660 | upload.setTargetDirectory( | ||
351 | 661 | self.archive, "test_1.0_amd64.tar.gz", "distroseries") | ||
352 | 662 | upload.signFit('t.fit') | ||
353 | 663 | # Confirm the copy was performed. | ||
354 | 664 | self.assertEqual(1, fake_copy.call_count) | ||
355 | 665 | args = fake_copy.calls[0][0] | ||
356 | 666 | expected_copy = ('t.fit', 't.fit.signed') | ||
357 | 667 | self.assertEqual(expected_copy, args) | ||
358 | 668 | # Assert command form. | ||
359 | 669 | args = fake_call.calls[0][0][0] | ||
360 | 670 | expected_cmd = [ | ||
361 | 671 | 'mkimage', '-F', '-k', os.path.dirname(self.fit_key), '-r', | ||
362 | 672 | 't.fit.signed', | ||
363 | 673 | ] | ||
364 | 674 | self.assertEqual(expected_cmd, args) | ||
365 | 675 | self.assertEqual(0, upload.generateFitKeys.call_count) | ||
366 | 676 | |||
367 | 677 | def test_correct_fit_signing_command_executed_no_keys(self): | ||
368 | 678 | # Check that calling signFit() will generate no commands when | ||
369 | 679 | # no keys are present. | ||
370 | 680 | self.setUpFitKeys(create=False) | ||
371 | 681 | fake_call = FakeMethod(result=0) | ||
372 | 682 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | ||
373 | 683 | upload = SigningUpload() | ||
374 | 684 | upload.generateFitKeys = FakeMethod() | ||
375 | 685 | upload.setTargetDirectory( | ||
376 | 686 | self.archive, "test_1.0_amd64.tar.gz", "distroseries") | ||
377 | 687 | upload.signUefi('t.fit') | ||
378 | 688 | self.assertEqual(0, fake_call.call_count) | ||
379 | 689 | self.assertEqual(0, upload.generateFitKeys.call_count) | ||
380 | 690 | |||
381 | 691 | def test_correct_fit_keygen_command_executed(self): | ||
382 | 692 | # Check that calling generateFitKeys() will generate the | ||
383 | 693 | # expected command. | ||
384 | 694 | self.setUpPPA() | ||
385 | 695 | self.setUpFitKeys(create=False) | ||
386 | 696 | fake_call = FakeMethod(result=0) | ||
387 | 697 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | ||
388 | 698 | upload = SigningUpload() | ||
389 | 699 | upload.setTargetDirectory( | ||
390 | 700 | self.archive, "test_1.0_amd64.tar.gz", "distroseries") | ||
391 | 701 | upload.generateFitKeys() | ||
392 | 702 | self.assertEqual(1, fake_call.call_count) | ||
393 | 703 | # Assert the actual command matches. | ||
394 | 704 | args = fake_call.calls[0][0][0] | ||
395 | 705 | expected_cmd = [ | ||
396 | 706 | 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048', | ||
397 | 707 | '-subj', '/CN=' + self.testcase_cn + ' FIT/', | ||
398 | 708 | '-keyout', self.fit_key, '-out', self.fit_cert, | ||
399 | 599 | '-days', '3650', '-nodes', '-sha256', | 709 | '-days', '3650', '-nodes', '-sha256', |
400 | 600 | ] | 710 | ] |
401 | 601 | self.assertEqual(expected_cmd, args) | 711 | self.assertEqual(expected_cmd, args) |
402 | @@ -610,7 +720,7 @@ | |||
403 | 610 | text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod) | 720 | text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod) |
404 | 611 | 721 | ||
405 | 612 | id_re = re.compile(r'^# KMOD OpenSSL config\n') | 722 | id_re = re.compile(r'^# KMOD OpenSSL config\n') |
407 | 613 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Kmod') | 723 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Kmod') |
408 | 614 | eku_re = re.compile( | 724 | eku_re = re.compile( |
409 | 615 | r'\bextendedKeyUsage\s*=\s*' | 725 | r'\bextendedKeyUsage\s*=\s*' |
410 | 616 | r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b') | 726 | r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b') |
411 | @@ -696,7 +806,7 @@ | |||
412 | 696 | text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal) | 806 | text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal) |
413 | 697 | 807 | ||
414 | 698 | id_re = re.compile(r'^# OPAL OpenSSL config\n') | 808 | id_re = re.compile(r'^# OPAL OpenSSL config\n') |
416 | 699 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Opal') | 809 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Opal') |
417 | 700 | 810 | ||
418 | 701 | self.assertIn('[ req ]', text) | 811 | self.assertIn('[ req ]', text) |
419 | 702 | self.assertIsNotNone(id_re.search(text)) | 812 | self.assertIsNotNone(id_re.search(text)) |
420 | @@ -779,7 +889,7 @@ | |||
421 | 779 | text = upload.generateOpensslConfig('SIPL', upload.openssl_config_sipl) | 889 | text = upload.generateOpensslConfig('SIPL', upload.openssl_config_sipl) |
422 | 780 | 890 | ||
423 | 781 | id_re = re.compile(r'^# SIPL OpenSSL config\n') | 891 | id_re = re.compile(r'^# SIPL OpenSSL config\n') |
425 | 782 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+SIPL') | 892 | cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+SIPL') |
426 | 783 | 893 | ||
427 | 784 | self.assertIn('[ req ]', text) | 894 | self.assertIn('[ req ]', text) |
428 | 785 | self.assertIsNotNone(id_re.search(text)) | 895 | self.assertIsNotNone(id_re.search(text)) |
429 | @@ -860,6 +970,14 @@ | |||
430 | 860 | upload = self.process() | 970 | upload = self.process() |
431 | 861 | self.assertEqual(1, upload.signUefi.call_count) | 971 | self.assertEqual(1, upload.signUefi.call_count) |
432 | 862 | 972 | ||
433 | 973 | def test_signs_fit_image(self): | ||
434 | 974 | # Each image in the tarball is signed. | ||
435 | 975 | self.setUpFitKeys() | ||
436 | 976 | self.openArchive("test", "1.0", "amd64") | ||
437 | 977 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
438 | 978 | upload = self.process() | ||
439 | 979 | self.assertEqual(1, upload.signFit.call_count) | ||
440 | 980 | |||
441 | 863 | def test_signs_kmod_image(self): | 981 | def test_signs_kmod_image(self): |
442 | 864 | # Each image in the tarball is signed. | 982 | # Each image in the tarball is signed. |
443 | 865 | self.setUpKmodKeys() | 983 | self.setUpKmodKeys() |
444 | @@ -886,7 +1004,6 @@ | |||
445 | 886 | 1004 | ||
446 | 887 | def test_signs_combo_image(self): | 1005 | def test_signs_combo_image(self): |
447 | 888 | # Each image in the tarball is signed. | 1006 | # Each image in the tarball is signed. |
448 | 889 | self.setUpKmodKeys() | ||
449 | 890 | self.openArchive("test", "1.0", "amd64") | 1007 | self.openArchive("test", "1.0", "amd64") |
450 | 891 | self.tarfile.add_file("1.0/empty.efi", b"") | 1008 | self.tarfile.add_file("1.0/empty.efi", b"") |
451 | 892 | self.tarfile.add_file("1.0/empty.ko", b"") | 1009 | self.tarfile.add_file("1.0/empty.ko", b"") |
452 | @@ -898,11 +1015,17 @@ | |||
453 | 898 | self.tarfile.add_file("1.0/empty2.sipl", b"") | 1015 | self.tarfile.add_file("1.0/empty2.sipl", b"") |
454 | 899 | self.tarfile.add_file("1.0/empty3.sipl", b"") | 1016 | self.tarfile.add_file("1.0/empty3.sipl", b"") |
455 | 900 | self.tarfile.add_file("1.0/empty4.sipl", b"") | 1017 | self.tarfile.add_file("1.0/empty4.sipl", b"") |
456 | 1018 | self.tarfile.add_file("1.0/empty.fit", b"") | ||
457 | 1019 | self.tarfile.add_file("1.0/empty2.fit", b"") | ||
458 | 1020 | self.tarfile.add_file("1.0/empty3.fit", b"") | ||
459 | 1021 | self.tarfile.add_file("1.0/empty4.fit", b"") | ||
460 | 1022 | self.tarfile.add_file("1.0/empty5.fit", b"") | ||
461 | 901 | upload = self.process() | 1023 | upload = self.process() |
462 | 902 | self.assertEqual(1, upload.signUefi.call_count) | 1024 | self.assertEqual(1, upload.signUefi.call_count) |
463 | 903 | self.assertEqual(2, upload.signKmod.call_count) | 1025 | self.assertEqual(2, upload.signKmod.call_count) |
464 | 904 | self.assertEqual(3, upload.signOpal.call_count) | 1026 | self.assertEqual(3, upload.signOpal.call_count) |
465 | 905 | self.assertEqual(4, upload.signSipl.call_count) | 1027 | self.assertEqual(4, upload.signSipl.call_count) |
466 | 1028 | self.assertEqual(5, upload.signFit.call_count) | ||
467 | 906 | 1029 | ||
468 | 907 | def test_installed(self): | 1030 | def test_installed(self): |
469 | 908 | # Files in the tarball are installed correctly. | 1031 | # Files in the tarball are installed correctly. |
470 | @@ -974,6 +1097,43 @@ | |||
471 | 974 | self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600) | 1097 | self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600) |
472 | 975 | self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644) | 1098 | self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644) |
473 | 976 | 1099 | ||
474 | 1100 | def test_create_fit_keys_autokey_off(self): | ||
475 | 1101 | # Keys are not created. | ||
476 | 1102 | self.setUpFitKeys(create=False) | ||
477 | 1103 | self.assertFalse(os.path.exists(self.fit_key)) | ||
478 | 1104 | self.assertFalse(os.path.exists(self.fit_cert)) | ||
479 | 1105 | fake_call = FakeMethod(result=0) | ||
480 | 1106 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | ||
481 | 1107 | upload = SigningUpload() | ||
482 | 1108 | upload.callLog = FakeMethodCallLog(upload=upload) | ||
483 | 1109 | upload.setTargetDirectory( | ||
484 | 1110 | self.archive, "test_1.0_amd64.tar.gz", "distroseries") | ||
485 | 1111 | upload.signFit(os.path.join(self.makeTemporaryDirectory(), 'fit')) | ||
486 | 1112 | self.assertEqual(0, upload.callLog.caller_count('FIT keygen')) | ||
487 | 1113 | self.assertFalse(os.path.exists(self.fit_key)) | ||
488 | 1114 | self.assertFalse(os.path.exists(self.fit_cert)) | ||
489 | 1115 | |||
490 | 1116 | def test_create_fit_keys_autokey_on(self): | ||
491 | 1117 | # Keys are created on demand. | ||
492 | 1118 | self.setUpPPA() | ||
493 | 1119 | self.setUpFitKeys(create=False) | ||
494 | 1120 | self.assertFalse(os.path.exists(self.fit_key)) | ||
495 | 1121 | self.assertFalse(os.path.exists(self.fit_cert)) | ||
496 | 1122 | fake_call = FakeMethod(result=0) | ||
497 | 1123 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) | ||
498 | 1124 | fake_copy = FakeMethod(result=0) | ||
499 | 1125 | self.useFixture(MonkeyPatch("shutil.copy", fake_copy)) | ||
500 | 1126 | upload = SigningUpload() | ||
501 | 1127 | upload.callLog = FakeMethodCallLog(upload=upload) | ||
502 | 1128 | upload.setTargetDirectory( | ||
503 | 1129 | self.archive, "test_1.0_amd64.tar.gz", "distroseries") | ||
504 | 1130 | upload.signFit(os.path.join(self.makeTemporaryDirectory(), 't.fit')) | ||
505 | 1131 | self.assertEqual(1, upload.callLog.caller_count('FIT keygen')) | ||
506 | 1132 | self.assertTrue(os.path.exists(self.fit_key)) | ||
507 | 1133 | self.assertTrue(os.path.exists(self.fit_cert)) | ||
508 | 1134 | self.assertEqual(stat.S_IMODE(os.stat(self.fit_key).st_mode), 0o600) | ||
509 | 1135 | self.assertEqual(stat.S_IMODE(os.stat(self.fit_cert).st_mode), 0o644) | ||
510 | 1136 | |||
511 | 977 | def test_create_kmod_keys_autokey_off(self): | 1137 | def test_create_kmod_keys_autokey_off(self): |
512 | 978 | # Keys are not created. | 1138 | # Keys are not created. |
513 | 979 | self.setUpKmodKeys(create=False) | 1139 | self.setUpKmodKeys(create=False) |
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) soyuz-dependenc ies 0.135 must be installed on pepo and haetae (I'll deal with this)
* launchpad-