summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/lib/oeqa/selftest/cases/fitimage.py1468
1 files changed, 800 insertions, 668 deletions
diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py
index 00769443e8..6f3bf296d5 100644
--- a/meta/lib/oeqa/selftest/cases/fitimage.py
+++ b/meta/lib/oeqa/selftest/cases/fitimage.py
@@ -5,27 +5,58 @@
5# 5#
6 6
7from oeqa.selftest.case import OESelftestTestCase 7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars 8from oeqa.utils.commands import runCmd, bitbake, get_bb_vars
9import os 9import os
10import re 10import re
11import shlex
12import logging
13import pprint
11 14
12class FitImageTests(OESelftestTestCase): 15class FitImageTestCase(OESelftestTestCase):
16 """Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass
13 17
14 def _setup_uboot_tools_native(self): 18 A brief summary showing the structure of a test case:
15 """build u-boot-tools-native and return ${RECIPE_SYSROOT_NATIVE}/${bindir}"""
16 bitbake("u-boot-tools-native -c addto_recipe_sysroot")
17 vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], 'u-boot-tools-native')
18 return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir'])
19 19
20 def _run_dumpimage(self, fitimage_path, uboot_tools_bindir): 20 self._test_fitimage()
21 dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage') 21 # Generate a local.conf file and bitbake the bootloader or the kernel
22 return runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) 22 self._bitbake_fit_image()
23
24 # Check if the its file contains the expected paths and attributes.
25 # The _get_req_* functions are implemented by more specific chield classes.
26 self._check_its_file()
27 req_its_paths = self._get_req_its_paths()
28 req_sigvalues_config = self._get_req_sigvalues_config()
29 req_sigvalues_image = self._get_req_sigvalues_image()
30 # Compare the its file against req_its_paths, req_sigvalues_config, req_sigvalues_image
31
32 # Call the dumpimage utiliy and check that it prints all the expected paths and attributes
33 # The _get_req_* functions are implemented by more specific chield classes.
34 self._check_fitimage()
35 self._get_req_sections()
36 # Compare the output of the dumpimage utility against
37 """
38
39 MKIMAGE_HASH_LENGTHS = { 'sha256': 64, 'sha384': 96, 'sha512': 128 }
40 MKIMAGE_SIGNATURE_LENGTHS = { 'rsa2048': 512 }
41
42 @staticmethod
43 def _gen_random_file(file_path, num_bytes=65536):
44 with open(file_path, 'wb') as file_out:
45 file_out.write(os.urandom(num_bytes))
46
47 @staticmethod
48 def _setup_native(native_recipe):
49 """Build a native recipe and return the path to its bindir in RECIPE_SYSROOT_NATIVE"""
50 bitbake(native_recipe + " -c addto_recipe_sysroot")
51 vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], native_recipe)
52 return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir'])
23 53
24 def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None): 54 def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None):
25 """Verify the signature of a fit contfiguration 55 """Verify the signature of a fit configuration
26 56
27 The fit_check_sign utility from u-boot-tools-native is called. 57 The fit_check_sign utility from u-boot-tools-native is called.
28 uboot-fit_check_sign -f fitImage -k $dtb_name -c conf-$dtb_name 58 uboot-fit_check_sign -f fitImage -k $dtb_path -c conf-$dtb_name
59 dtb_path refers to a binary device tree containing the public key.
29 """ 60 """
30 fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign') 61 fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign')
31 cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path) 62 cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path)
@@ -37,33 +68,276 @@ class FitImageTests(OESelftestTestCase):
37 68
38 @staticmethod 69 @staticmethod
39 def _find_string_in_bin_file(file_path, search_string): 70 def _find_string_in_bin_file(file_path, search_string):
40 """find stings in a binary file 71 """find strings in a binary file
41 72
42 Shell equivalent: strings "$1" | grep "$2" | wc -l 73 Shell equivalent: strings "$1" | grep "$2" | wc -l
43 return number of matches 74 return number of matches
44 """ 75 """
45 found_positions = 0 76 found_positions = 0
46 with open(file_path, 'rb') as file: 77 with open(file_path, 'rb') as file:
47 byte = file.read(1) 78 content = file.read().decode('ascii', errors='ignore')
48 current_position = 0 79 found_positions = content.count(search_string)
49 current_match = 0
50 while byte:
51 char = byte.decode('ascii', errors='ignore')
52 if char == search_string[current_match]:
53 current_match += 1
54 if current_match == len(search_string):
55 found_positions += 1
56 current_match = 0
57 else:
58 current_match = 0
59 current_position += 1
60 byte = file.read(1)
61 return found_positions 80 return found_positions
62 81
82 @staticmethod
83 def _get_uboot_mkimage_sign_args(uboot_mkimage_sign_args):
84 """Retrive the string passed via -c to the mkimage command
85
86 Example: If a build configutation defines
87 UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
88 this function returns "a smart comment"
89 """
90 a_comment = None
91 if uboot_mkimage_sign_args:
92 mkimage_args = shlex.split(uboot_mkimage_sign_args)
93 try:
94 c_index = mkimage_args.index('-c')
95 a_comment = mkimage_args[c_index+1]
96 except ValueError:
97 pass
98 return a_comment
99
100 @staticmethod
101 def _get_dtb_files(bb_vars):
102 kernel_devicetree = bb_vars['KERNEL_DEVICETREE'] or ""
103 if kernel_devicetree:
104 return [os.path.basename(dtb) for dtb in kernel_devicetree.split()]
105 return []
106
107 def _is_req_dict_in_dict(self, found_dict, req_dict):
108 """
109 Check if all key-value pairs in the required dictionary are present in the found dictionary.
110
111 This function recursively checks if the required dictionary (`req_dict`) is a subset of the found dictionary (`found_dict`).
112 It supports nested dictionaries, strings, lists, and sets as values.
113
114 Args:
115 found_dict (dict): The dictionary to search within.
116 req_dict (dict): The dictionary containing the required key-value pairs.
117 """
118 for key, value in req_dict.items():
119 self.assertIn(key, found_dict)
120 if isinstance(value, dict):
121 self._is_req_dict_in_dict(found_dict[key], value)
122 elif isinstance(value, str):
123 self.assertIn(value, found_dict[key])
124 elif isinstance(value, list):
125 self.assertLessEqual(set(value), set(found_dict[key]))
126 elif isinstance(value, set):
127 self.assertLessEqual(value, found_dict[key])
128 else:
129 self.assertEqual(value, found_dict[key])
130
131 def _check_its_file(self, bb_vars, its_file_path):
132 """Check if the its file contains the expected sections and fields"""
133 # print the its file for debugging
134 if logging.DEBUG >= self.logger.level:
135 with open(its_file_path) as its_file:
136 self.logger.debug("its file: %s" % its_file.read())
137
138 # Generate a list of expected paths in the its file
139 req_its_paths = self._get_req_its_paths(bb_vars)
140 self.logger.debug("req_its_paths:\n%s\n" % pprint.pformat(req_its_paths, indent=4))
141
142 # Generate a dict of expected configuration signature nodes
143 req_sigvalues_config = self._get_req_sigvalues_config(bb_vars)
144 self.logger.debug("req_sigvalues_config:\n%s\n" % pprint.pformat(req_sigvalues_config, indent=4))
145
146 # Generate a dict of expected image signature nodes
147 req_sigvalues_image = self._get_req_sigvalues_image(bb_vars)
148 self.logger.debug("req_sigvalues_image:\n%s\n" % pprint.pformat(req_sigvalues_image, indent=4))
149
150 # Parse the its file for paths and signatures
151 its_path = []
152 its_paths = []
153 linect = 0
154 sigs = {}
155 with open(its_file_path) as its_file:
156 for line in its_file:
157 linect += 1
158 line = line.strip()
159 if line.endswith('};'):
160 its_path.pop()
161 elif line.endswith('{'):
162 its_path.append(line[:-1].strip())
163 its_paths.append(its_path[:])
164 # kernel-fitimage uses signature-1, uboot-sign uses signature
165 elif its_path and (its_path[-1] == 'signature-1' or its_path[-1] == 'signature'):
166 itsdotpath = '.'.join(its_path)
167 if not itsdotpath in sigs:
168 sigs[itsdotpath] = {}
169 if not '=' in line or not line.endswith(';'):
170 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (its_file_path, linect, line))
171 key, value = line.split('=', 1)
172 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
173
174 # Check if all expected paths are found in the its file
175 self.logger.debug("itspaths:\n%s\n" % pprint.pformat(its_paths, indent=4))
176 for req_path in req_its_paths:
177 if not req_path in its_paths:
178 self.fail('Missing path in its file: %s (%s)' % (req_path, its_file_path))
179
180 # Check if all the expected singnature nodes (images and configurations) are found
181 self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4))
182 if req_sigvalues_config or req_sigvalues_image:
183 for its_path, values in sigs.items():
184 if 'conf-' in its_path:
185 reqsigvalues = req_sigvalues_config
186 else:
187 reqsigvalues = req_sigvalues_image
188 for reqkey, reqvalue in reqsigvalues.items():
189 value = values.get(reqkey, None)
190 if value is None:
191 self.fail('Missing key "%s" in its file signature section %s (%s)' % (reqkey, its_path, its_file_path))
192 self.assertEqual(value, reqvalue)
193
194 # Generate a list of expected fields in the its file
195 req_its_fields = self._get_req_its_fields(bb_vars)
196 self.logger.debug("req_its_fields:\n%s\n" % pprint.pformat(req_its_fields, indent=4))
197
198 # Check if all expected fields are in the its file
199 if req_its_fields:
200 field_index = 0
201 field_index_last = len(req_its_fields) - 1
202 with open(its_file_path) as its_file:
203 for line in its_file:
204 if req_its_fields[field_index] in line:
205 if field_index < field_index_last:
206 field_index +=1
207 else:
208 break
209 self.assertEqual(field_index, field_index_last,
210 "Fields in Image Tree Source File %s did not match, error in finding %s"
211 % (its_file_path, req_its_fields[field_index]))
212
213 def _check_fitimage(self, bb_vars, fitimage_path, uboot_tools_bindir):
214 """Run dumpimage on the final FIT image and parse the output into a dict"""
215 dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage')
216 cmd = '%s -l %s' % (dumpimage_path, fitimage_path)
217 self.logger.debug("Analyzing output from dumpimage: %s" % cmd)
218 dumpimage_result = runCmd(cmd)
219 in_section = None
220 sections = {}
221 self.logger.debug("dumpimage output: %s" % dumpimage_result.output)
222 for line in dumpimage_result.output.splitlines():
223 # Find potentially hashed and signed sections
224 if line.startswith((' Configuration', ' Image')):
225 in_section = re.search(r'\((.*)\)', line).groups()[0]
226 # Key value lines start with two spaces otherwise the section ended
227 elif not line.startswith(" "):
228 in_section = None
229 # Handle key value lines of this section
230 elif in_section:
231 if not in_section in sections:
232 sections[in_section] = {}
233 try:
234 key, value = line.split(':', 1)
235 key = key.strip()
236 value = value.strip()
237 except ValueError as val_err:
238 self.logger.debug("dumpimage debug: %s = %s" % (key, line))
239 # Handle multiple entries as e.g. for Loadables as a list
240 if key and line.startswith(" "):
241 value = sections[in_section][key] + "," + line.strip()
242 else:
243 raise ValueError(f"Error processing line: '{line}'. Original error: {val_err}")
244 sections[in_section][key] = value
245
246 # Check if the requested dictionary is a subset of the parsed dictionary
247 req_sections, num_signatures = self._get_req_sections(bb_vars)
248 self.logger.debug("req_sections: \n%s\n" % pprint.pformat(req_sections, indent=4))
249 self.logger.debug("dumpimage sections: \n%s\n" % pprint.pformat(sections, indent=4))
250 self._is_req_dict_in_dict(sections, req_sections)
251
252 # Call the signing related checks if the function is provided by a inherited class
253 self._check_signing(bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path)
254
255 def _get_req_its_paths(self, bb_vars):
256 self.logger.error("This function needs to be implemented")
257 return []
258
259 def _get_req_its_fields(self, bb_vars):
260 self.logger.error("This function needs to be implemented")
261 return []
262
263 def _get_req_sigvalues_config(self, bb_vars):
264 self.logger.error("This function needs to be implemented")
265 return {}
266
267 def _get_req_sigvalues_image(self, bb_vars):
268 self.logger.error("This function needs to be implemented")
269 return {}
270
271 def _get_req_sections(self, bb_vars):
272 self.logger.error("This function needs to be implemented")
273 return ({}, 0)
274
275 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
276 """Verify the signatures in the FIT image."""
277 self.fail("Function needs to be implemented by inheriting classes")
278
279 def _bitbake_fit_image(self, bb_vars):
280 """Bitbake the FIT image and return the paths to the its file and the FIT image"""
281 self.fail("Function needs to be implemented by inheriting classes")
282
283 def _test_fitimage(self, bb_vars):
284 """Check if the its file and the FIT image are created and signed correctly"""
285 fitimage_its_path, fitimage_path = self._bitbake_fit_image(bb_vars)
286 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
287 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
288
289 self.logger.debug("Checking its: %s" % fitimage_its_path)
290 self._check_its_file(bb_vars, fitimage_its_path)
291
292 # Setup u-boot-tools-native
293 uboot_tools_bindir = FitImageTestCase._setup_native('u-boot-tools-native')
294
295 # Verify the FIT image
296 self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir)
297
298
299class KernelFitImageTests(FitImageTestCase):
300 """Test cases for the kernel-fitimage bbclass"""
301
302 def _fit_get_bb_vars(self, additional_vars=[]):
303 """Retrieve BitBake variables specific to the test case.
304
305 Call the get_bb_vars function once and get all variables needed by the test case.
306 """
307 internal_used = {
308 'DEPLOY_DIR_IMAGE',
309 'FIT_DESC',
310 'FIT_HASH_ALG',
311 'FIT_KERNEL_COMP_ALG',
312 'FIT_SIGN_ALG',
313 'FIT_SIGN_INDIVIDUAL',
314 'FIT_UBOOT_ENV',
315 'INITRAMFS_IMAGE_BUNDLE',
316 'INITRAMFS_IMAGE_NAME',
317 'INITRAMFS_IMAGE',
318 'KERNEL_DEVICETREE',
319 'KERNEL_FIT_LINK_NAME',
320 'MACHINE',
321 'UBOOT_ARCH',
322 'UBOOT_ENTRYPOINT',
323 'UBOOT_LOADADDRESS',
324 'UBOOT_MKIMAGE_KERNEL_TYPE',
325 'UBOOT_MKIMAGE_SIGN_ARGS',
326 'UBOOT_RD_ENTRYPOINT',
327 'UBOOT_RD_LOADADDRESS',
328 'UBOOT_SIGN_ENABLE',
329 'UBOOT_SIGN_IMG_KEYNAME',
330 'UBOOT_SIGN_KEYDIR',
331 'UBOOT_SIGN_KEYNAME',
332 }
333 bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), "virtual/kernel")
334 return bb_vars
335
63 def _config_add_uboot_env(self, config): 336 def _config_add_uboot_env(self, config):
64 """Generate an u-boot environment 337 """Generate an u-boot environment
65 338
66 Create a boot.cmd file that is packed into the FitImage as a source-able text file. 339 Create a boot.cmd file that is packed into the FIT image as a source-able text file.
340 Updates the configuration to include the boot.cmd file.
67 """ 341 """
68 fit_uenv_file = "boot.cmd" 342 fit_uenv_file = "boot.cmd"
69 test_files_dir = "test-files" 343 test_files_dir = "test-files"
@@ -75,17 +349,253 @@ class FitImageTests(OESelftestTestCase):
75 config += 'SRC_URI:append:pn-linux-yocto = " file://${FIT_UBOOT_ENV}"' + os.linesep 349 config += 'SRC_URI:append:pn-linux-yocto = " file://${FIT_UBOOT_ENV}"' + os.linesep
76 350
77 if not os.path.isdir(test_files_dir): 351 if not os.path.isdir(test_files_dir):
78 os.mkdir(test_files_dir) 352 os.makedirs(test_files_dir)
79 self.logger.debug("Writing to: %s" % fit_uenv_path) 353 self.logger.debug("Writing to: %s" % fit_uenv_path)
80 with open(fit_uenv_path, "w") as f: 354 with open(fit_uenv_path, "w") as f:
81 f.write('echo "hello world"') 355 f.write('echo "hello world"')
82 356
83 return config 357 return config
84 358
85 def _verify_fitimage_uboot_env(self, dumpimage_result): 359 def _bitbake_fit_image(self, bb_vars):
86 """Check if the boot.cmd script is part of the fitImage""" 360 """Bitbake the kernel and return the paths to the its file and the FIT image"""
87 num_scr_images = len(re.findall(r"^ *Image +[0-9]+ +\(bootscr-boot\.cmd\)$", dumpimage_result.output, re.MULTILINE)) 361 bitbake("virtual/kernel")
88 self.assertEqual(1, num_scr_images, msg="Expected exactly 1 bootscr-boot.cmd image section in the fitImage") 362
363 # Find the right its file and the final fitImage and check if both files are available
364 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
365 initramfs_image = bb_vars['INITRAMFS_IMAGE']
366 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
367 initramfs_image_name = bb_vars['INITRAMFS_IMAGE_NAME']
368 kernel_fit_link_name = bb_vars['KERNEL_FIT_LINK_NAME']
369 if not initramfs_image and initramfs_image_bundle != "1":
370 fitimage_its_name = "fitImage-its-%s" % kernel_fit_link_name
371 fitimage_name = "fitImage"
372 elif initramfs_image and initramfs_image_bundle != "1":
373 fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
374 fitimage_name = "fitImage-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
375 elif initramfs_image and initramfs_image_bundle == "1":
376 fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
377 fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT}
378 else:
379 self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE')
380 fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name))
381 fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name))
382 return (fitimage_its_path, fitimage_path)
383
384 def _get_req_its_paths(self, bb_vars):
385 """Generate a list of expected paths in the its file
386
387 Example:
388 [
389 ['/', 'images', 'kernel-1', 'hash-1'],
390 ['/', 'images', 'kernel-1', 'signature-1'],
391 ]
392 """
393 dtb_files = FitImageTestCase._get_dtb_files(bb_vars)
394 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
395 fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
396 initramfs_image = bb_vars['INITRAMFS_IMAGE']
397 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
398 uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE']
399
400 # image nodes
401 images = [ 'kernel-1' ]
402 if dtb_files:
403 images += [ 'fdt-' + dtb for dtb in dtb_files ]
404 if fit_uboot_env:
405 images.append('bootscr-' + fit_uboot_env)
406 if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if
407 images.append('setup-1')
408 if initramfs_image and initramfs_image_bundle != "1":
409 images.append('ramdisk-1')
410
411 # configuration nodes
412 if dtb_files:
413 configurations = [ 'conf-' + conf for conf in dtb_files ]
414 else:
415 configurations = [ 'conf-1' ]
416
417 # Create a list of paths for all image and configuration nodes
418 req_its_paths = []
419 for image in images:
420 req_its_paths.append(['/', 'images', image, 'hash-1'])
421 if uboot_sign_enable == "1" and fit_sign_individual == "1":
422 req_its_paths.append(['/', 'images', image, 'signature-1'])
423 for configuration in configurations:
424 req_its_paths.append(['/', 'configurations', configuration, 'hash-1'])
425 if uboot_sign_enable == "1":
426 req_its_paths.append(['/', 'configurations', configuration, 'signature-1'])
427 return req_its_paths
428
429 def _get_req_its_fields(self, bb_vars):
430 initramfs_image = bb_vars['INITRAMFS_IMAGE']
431 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
432 uboot_rd_loadaddress = bb_vars['UBOOT_RD_LOADADDRESS']
433 uboot_rd_entrypoint = bb_vars['UBOOT_RD_ENTRYPOINT']
434
435 its_field_check = [
436 'description = "%s";' % bb_vars['FIT_DESC'],
437 'description = "Linux kernel";',
438 'data = /incbin/("linux.bin");',
439 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";',
440 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";',
441 'os = "linux";',
442 # 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal...
443 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;',
444 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;',
445 ]
446 if initramfs_image and initramfs_image_bundle != "1":
447 its_field_check.append('type = "ramdisk";')
448 if uboot_rd_loadaddress:
449 its_field_check.append("load = <%s>;" % uboot_rd_loadaddress)
450 if uboot_rd_entrypoint:
451 its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint)
452 its_field_check += [
453 # 'default = "conf-1";', needs more work
454 'kernel = "kernel-1";',
455 ]
456 if initramfs_image and initramfs_image_bundle != "1":
457 its_field_check.append('ramdisk = "ramdisk-1";')
458
459 return its_field_check
460
461 def _get_req_sigvalues_config(self, bb_vars):
462 """Generate a dictionary of expected configuration signature nodes"""
463 sign_images = '"kernel", "fdt"'
464 if bb_vars['INITRAMFS_IMAGE'] and bb_vars['INITRAMFS_IMAGE_BUNDLE'] != "1":
465 sign_images += ', "ramdisk"'
466 if bb_vars['FIT_UBOOT_ENV']:
467 sign_images += ', "bootscr"'
468 req_sigvalues_config = {
469 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
470 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_KEYNAME'],
471 'sign-images': sign_images,
472 }
473 return req_sigvalues_config
474
475 def _get_req_sigvalues_image(self, bb_vars):
476 """Generate a dictionary of expected image signature nodes"""
477 if bb_vars['FIT_SIGN_INDIVIDUAL'] != "1":
478 return {}
479 req_sigvalues_image = {
480 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
481 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_IMG_KEYNAME'],
482 }
483 return req_sigvalues_image
484
485 def _get_req_sections(self, bb_vars):
486 """Generate a dictionary of expected sections in the output of dumpimage"""
487 dtb_files = FitImageTestCase._get_dtb_files(bb_vars)
488 fit_hash_alg = bb_vars['FIT_HASH_ALG']
489 fit_sign_alg = bb_vars['FIT_SIGN_ALG']
490 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
491 fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
492 initramfs_image = bb_vars['INITRAMFS_IMAGE']
493 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
494 uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE']
495 uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
496 uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
497 num_signatures = 0
498 req_sections = {
499 "kernel-1": {
500 "Type": "Kernel Image",
501 "OS": "Linux",
502 "Load Address": bb_vars['UBOOT_LOADADDRESS'],
503 "Entry Point": bb_vars['UBOOT_ENTRYPOINT'],
504 }
505 }
506 # Create one section per DTB
507 for dtb in dtb_files:
508 req_sections['fdt-' + dtb] = {
509 "Type": "Flat Device Tree",
510 }
511 # Add a script section if there is a script
512 if fit_uboot_env:
513 req_sections['bootscr-' + fit_uboot_env] = { "Type": "Script" }
514 # Add the initramfs
515 if initramfs_image and initramfs_image_bundle != "1":
516 req_sections['ramdisk-1'] = {
517 "Type": "RAMDisk Image",
518 "Load Address": bb_vars['UBOOT_RD_LOADADDRESS'],
519 "Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT']
520 }
521 # Create a configuration section for each DTB
522 if dtb_files:
523 for dtb in dtb_files:
524 req_sections['conf-' + dtb] = {
525 "Kernel": "kernel-1",
526 "FDT": 'fdt-' + dtb,
527 }
528 if initramfs_image and initramfs_image_bundle != "1":
529 req_sections['conf-' + dtb]['Init Ramdisk'] = "ramdisk-1"
530 else:
531 req_sections['conf-1'] = {
532 "Kernel": "kernel-1"
533 }
534 if initramfs_image and initramfs_image_bundle != "1":
535 req_sections['conf-1']['Init Ramdisk'] = "ramdisk-1"
536
537 # Add signing related properties if needed
538 if uboot_sign_enable == "1":
539 for section in req_sections:
540 req_sections[section]['Hash algo'] = fit_hash_alg
541 if section.startswith('conf-'):
542 req_sections[section]['Hash value'] = "unavailable"
543 req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
544 num_signatures += 1
545 elif fit_sign_individual == "1":
546 req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
547 num_signatures += 1
548 return (req_sections, num_signatures)
549
550 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
551 """Verify the signature nodes in the FIT image"""
552 if bb_vars['UBOOT_SIGN_ENABLE'] == "1":
553 self.logger.debug("Verifying signatures in the FIT image")
554 else:
555 self.logger.debug("FIT image is not signed. Signature verification is not needed.")
556 return
557
558 fit_hash_alg = bb_vars['FIT_HASH_ALG']
559 fit_sign_alg = bb_vars['FIT_SIGN_ALG']
560 uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
561 uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
562 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
563 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
564 fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg]
565 fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg]
566 for section, values in sections.items():
567 # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
568 if section.startswith("conf"):
569 sign_algo = values.get('Sign algo', None)
570 req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
571 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
572 sign_value = values.get('Sign value', None)
573 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
574 dtb_path = os.path.join(deploy_dir_image, section.replace('conf-', ''))
575 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section)
576 else:
577 # Image nodes always need a hash which gets indirectly signed by the config signature
578 hash_algo = values.get('Hash algo', None)
579 self.assertEqual(hash_algo, fit_hash_alg)
580 hash_value = values.get('Hash value', None)
581 self.assertEqual(len(hash_value), fit_hash_alg_len, 'Hash value for section %s not expected length' % section)
582 # Optionally, if FIT_SIGN_INDIVIDUAL = 1 also the image nodes have a signature (which is redundant but possible)
583 if fit_sign_individual == "1":
584 sign_algo = values.get('Sign algo', None)
585 req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
586 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
587 sign_value = values.get('Sign value', None)
588 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
589
590 # Search for the string passed to mkimage in each signed section of the FIT image.
591 # Looks like mkimage supports to add a comment but does not support to read it back.
592 a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['UBOOT_MKIMAGE_SIGN_ARGS'])
593 self.logger.debug("a_comment: %s" % a_comment)
594 if a_comment:
595 found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
596 self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
597 (num_signatures, a_comment))
598
89 599
90 def test_fit_image(self): 600 def test_fit_image(self):
91 """ 601 """
@@ -117,76 +627,34 @@ UBOOT_LOADADDRESS = "0x80080000"
117UBOOT_ENTRYPOINT = "0x80080000" 627UBOOT_ENTRYPOINT = "0x80080000"
118FIT_DESC = "A model description" 628FIT_DESC = "A model description"
119""" 629"""
120 config = self._config_add_uboot_env(config)
121 self.write_config(config) 630 self.write_config(config)
631 bb_vars = self._fit_get_bb_vars()
632 self._test_fitimage(bb_vars)
122 633
123 # fitImage is created as part of linux recipe
124 image = "virtual/kernel"
125 bitbake(image)
126 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'INITRAMFS_IMAGE_NAME', 'KERNEL_FIT_LINK_NAME'], image)
127
128 fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
129 "fitImage-its-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
130 fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
131 "fitImage-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
132
133 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
134 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
135
136 # Check that the type, load address, entrypoint address and default
137 # values for kernel and ramdisk in Image Tree Source are as expected.
138 # The order of fields in the below array is important. Not all the
139 # fields are tested, only the key fields that wont vary between
140 # different architectures.
141 its_field_check = [
142 'description = "A model description";',
143 'type = "kernel";',
144 'load = <0x80080000>;',
145 'entry = <0x80080000>;',
146 'type = "ramdisk";',
147 'load = <0x88000000>;',
148 'entry = <0x88000000>;',
149 'default = "conf-1";',
150 'kernel = "kernel-1";',
151 'ramdisk = "ramdisk-1";'
152 ]
153
154 with open(fitimage_its_path) as its_file:
155 field_index = 0
156 for line in its_file:
157 if field_index == len(its_field_check):
158 break
159 if its_field_check[field_index] in line:
160 field_index +=1
161
162 if field_index != len(its_field_check): # if its equal, the test passed
163 self.assertTrue(field_index == len(its_field_check),
164 "Fields in Image Tree Source File %s did not match, error in finding %s"
165 % (fitimage_its_path, its_field_check[field_index]))
166
167 uboot_tools_bindir = self._setup_uboot_tools_native()
168 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
169 self._verify_fitimage_uboot_env(dumpimage_result)
170 634
171 def test_sign_fit_image(self): 635 def test_sign_fit_image(self):
172 """ 636 """
173 Summary: Check if FIT image and Image Tree Source (its) are created 637 Summary: Check if FIT image and Image Tree Source (its) are created
174 and signed correctly. 638 and all nodes are signed correctly.
175 Expected: 1) its and FIT image are built successfully 639 Expected: 1) its and FIT image are built successfully
176 2) Scanning the its file indicates signing is enabled 640 2) Scanning the its file indicates signing is enabled
177 as requested by UBOOT_SIGN_ENABLE (using keys generated 641 as requested by UBOOT_SIGN_ENABLE (using 2 keys
178 via FIT_GENERATE_KEYS) 642 generated via FIT_GENERATE_KEYS)
179 3) Dumping the FIT image indicates signature values 643 3) Dumping the FIT image indicates signature values
180 are present (including for images as enabled via 644 are present (including for images as enabled via
181 FIT_SIGN_INDIVIDUAL) 645 FIT_SIGN_INDIVIDUAL)
182 4) Examination of the do_assemble_fitimage runfile/logfile 646 4) Verify the FIT image contains the comments passed via
183 indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and 647 UBOOT_MKIMAGE_SIGN_ARGS once per image and per
184 UBOOT_MKIMAGE_SIGN_ARGS are working as expected. 648 configuration node.
649 Note: This test is mostly for backward compatibility.
650 The recommended approach is to sign the configuration nodes
651 which include also the hashes of all the images. Signing
652 all the images individually is therefore redundant.
185 Product: oe-core 653 Product: oe-core
186 Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon 654 Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon
187 work by Usama Arif <usama.arif@arm.com> 655 work by Usama Arif <usama.arif@arm.com>
188 """ 656 """
189 a_comment = "a smart comment" 657 # Generate a configuration section which gets included into the local.conf file
190 config = """ 658 config = """
191# Enable creation of fitImage 659# Enable creation of fitImage
192MACHINE = "beaglebone-yocto" 660MACHINE = "beaglebone-yocto"
@@ -198,120 +666,13 @@ UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
198UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" 666UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
199UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" 667UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
200FIT_SIGN_INDIVIDUAL = "1" 668FIT_SIGN_INDIVIDUAL = "1"
201UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'" 669UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
202""" % a_comment 670"""
203
204 config = self._config_add_uboot_env(config) 671 config = self._config_add_uboot_env(config)
205 self.write_config(config) 672 self.write_config(config)
673 bb_vars = self._fit_get_bb_vars()
674 self._test_fitimage(bb_vars)
206 675
207 # fitImage is created as part of linux recipe
208 image = "virtual/kernel"
209 bitbake(image)
210 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'KERNEL_FIT_LINK_NAME'], image)
211
212 fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
213 "fitImage-its-%s" % (bb_vars['KERNEL_FIT_LINK_NAME']))
214 fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
215 "fitImage-%s.bin" % (bb_vars['KERNEL_FIT_LINK_NAME']))
216
217 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
218 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
219
220 req_itspaths = [
221 ['/', 'images', 'kernel-1'],
222 ['/', 'images', 'kernel-1', 'signature-1'],
223 ['/', 'images', 'fdt-am335x-boneblack.dtb'],
224 ['/', 'images', 'fdt-am335x-boneblack.dtb', 'signature-1'],
225 ['/', 'configurations', 'conf-am335x-boneblack.dtb'],
226 ['/', 'configurations', 'conf-am335x-boneblack.dtb', 'signature-1'],
227 ]
228
229 itspath = []
230 itspaths = []
231 linect = 0
232 sigs = {}
233 with open(fitimage_its_path) as its_file:
234 linect += 1
235 for line in its_file:
236 line = line.strip()
237 if line.endswith('};'):
238 itspath.pop()
239 elif line.endswith('{'):
240 itspath.append(line[:-1].strip())
241 itspaths.append(itspath[:])
242 elif itspath and itspath[-1] == 'signature-1':
243 itsdotpath = '.'.join(itspath)
244 if not itsdotpath in sigs:
245 sigs[itsdotpath] = {}
246 if not '=' in line or not line.endswith(';'):
247 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
248 key, value = line.split('=', 1)
249 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
250
251 for reqpath in req_itspaths:
252 if not reqpath in itspaths:
253 self.fail('Missing section in its file: %s' % reqpath)
254
255 reqsigvalues_image = {
256 'algo': '"sha256,rsa2048"',
257 'key-name-hint': '"img-oe-selftest"',
258 }
259 reqsigvalues_config = {
260 'algo': '"sha256,rsa2048"',
261 'key-name-hint': '"cfg-oe-selftest"',
262 'sign-images': '"kernel", "fdt", "bootscr"',
263 }
264
265 for itspath, values in sigs.items():
266 if 'conf-' in itspath:
267 reqsigvalues = reqsigvalues_config
268 else:
269 reqsigvalues = reqsigvalues_image
270 for reqkey, reqvalue in reqsigvalues.items():
271 value = values.get(reqkey, None)
272 if value is None:
273 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
274 self.assertEqual(value, reqvalue)
275
276 # Dump the image to see if it really got signed
277 uboot_tools_bindir = self._setup_uboot_tools_native()
278 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
279 in_signed = None
280 signed_sections = {}
281 for line in dumpimage_result.output.splitlines():
282 if line.startswith((' Configuration', ' Image')):
283 in_signed = re.search(r'\((.*)\)', line).groups()[0]
284 elif re.match('^ *', line) in (' ', ''):
285 in_signed = None
286 elif in_signed:
287 if not in_signed in signed_sections:
288 signed_sections[in_signed] = {}
289 key, value = line.split(':', 1)
290 signed_sections[in_signed][key.strip()] = value.strip()
291 self.assertIn('kernel-1', signed_sections)
292 self.assertIn('fdt-am335x-boneblack.dtb', signed_sections)
293 self.assertIn('conf-am335x-boneblack.dtb', signed_sections)
294 for signed_section, values in signed_sections.items():
295 value = values.get('Sign algo', None)
296 if signed_section.startswith("conf"):
297 self.assertEqual(value, 'sha256,rsa2048:cfg-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
298 else:
299 self.assertEqual(value, 'sha256,rsa2048:img-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
300 value = values.get('Sign value', None)
301 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
302
303 # Check if the u-boot boot.scr script is in the fitImage
304 self._verify_fitimage_uboot_env(dumpimage_result)
305
306 # Search for the string passed to mkimage: 1 kernel + 3 DTBs + config per DTB = 7 sections
307 # Looks like mkimage supports to add a comment but does not support to read it back.
308 found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
309 self.assertEqual(found_comments, 8, "Expected 8 signed and commented section in the fitImage.")
310
311 # Verify the signature for all configurations = DTBs
312 for dtb in ['am335x-bone.dtb', 'am335x-boneblack.dtb', 'am335x-bonegreen.dtb']:
313 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
314 os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], dtb), 'conf-' + dtb)
315 676
316 def test_initramfs_bundle(self): 677 def test_initramfs_bundle(self):
317 """ 678 """
@@ -355,92 +716,224 @@ FIT_HASH_ALG = "sha256"
355""" 716"""
356 config = self._config_add_uboot_env(config) 717 config = self._config_add_uboot_env(config)
357 self.write_config(config) 718 self.write_config(config)
719 bb_vars = self._fit_get_bb_vars()
720 self._test_fitimage(bb_vars)
358 721
359 # fitImage is created as part of linux recipe
360 bitbake("virtual/kernel")
361 722
362 bb_vars = get_bb_vars([ 723class UBootFitImageTests(FitImageTestCase):
724 """Test cases for the uboot-sign bbclass"""
725
726 def _fit_get_bb_vars(self, additional_vars=[]):
727 """Get bb_vars as needed by _test_sign_fit_image
728
729 Call the get_bb_vars function once and get all variables needed by the test case.
730 """
731 internal_used = {
363 'DEPLOY_DIR_IMAGE', 732 'DEPLOY_DIR_IMAGE',
364 'FIT_HASH_ALG',
365 'FIT_KERNEL_COMP_ALG',
366 'INITRAMFS_IMAGE',
367 'MACHINE', 733 'MACHINE',
734 'SPL_MKIMAGE_SIGN_ARGS',
735 'SPL_SIGN_ENABLE',
736 'SPL_SIGN_KEYNAME',
368 'UBOOT_ARCH', 737 'UBOOT_ARCH',
369 'UBOOT_ENTRYPOINT', 738 'UBOOT_DTB_BINARY',
370 'UBOOT_LOADADDRESS', 739 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT',
371 'UBOOT_MKIMAGE_KERNEL_TYPE' 740 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS',
372 ], 741 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE',
373 'virtual/kernel') 742 'UBOOT_FIT_CONF_USER_LOADABLES',
374 fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], 743 'UBOOT_FIT_DESC',
375 "fitImage-its-%s-%s-%s" % (bb_vars['INITRAMFS_IMAGE'], bb_vars['MACHINE'], bb_vars['MACHINE'])) 744 'UBOOT_FIT_HASH_ALG',
376 fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],"fitImage") 745 'UBOOT_FIT_SIGN_ALG',
377 746 'UBOOT_FIT_TEE_ENTRYPOINT',
378 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) 747 'UBOOT_FIT_TEE_LOADADDRESS',
379 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) 748 'UBOOT_FIT_TEE',
380 749 'UBOOT_FIT_UBOOT_ENTRYPOINT',
381 its_file = open(fitimage_its_path) 750 'UBOOT_FIT_UBOOT_LOADADDRESS',
751 'UBOOT_FIT_USER_SETTINGS',
752 'UBOOT_FITIMAGE_ENABLE',
753 'UBOOT_NODTB_BINARY',
754 'UBOOT_SIGN_IMG_KEYNAME',
755 }
756 bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), "virtual/bootloader")
757 return bb_vars
382 758
383 its_lines = [line.strip() for line in its_file.readlines()] 759 def _bitbake_fit_image(self, bb_vars):
760 """Bitbake the bootloader and return the paths to the its file and the FIT image"""
761 bitbake("virtual/bootloader")
384 762
385 exp_node_lines = [ 763 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
386 'kernel-1 {', 764 machine = bb_vars['MACHINE']
387 'description = "Linux kernel";', 765 fitimage_its_path = os.path.join(deploy_dir_image, "u-boot-its-%s" % machine)
388 'data = /incbin/("linux.bin");', 766 fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % machine)
389 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";', 767 return (fitimage_its_path, fitimage_path)
390 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";', 768
391 'os = "linux";', 769 def _get_req_its_paths(self, bb_vars):
392 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', 770 # image nodes
393 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;', 771 images = [ 'uboot', 'fdt', ]
394 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;', 772 if bb_vars['UBOOT_FIT_TEE'] == "1":
395 'hash-1 {', 773 images.append('tee')
396 'algo = "' + str(bb_vars['FIT_HASH_ALG']) +'";', 774 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
397 '};', 775 images.append('atf')
398 '};' 776 # if bb_vars['UBOOT_FIT_USER_SETTINGS']:
777
778 # configuration nodes
779 configurations = [ 'conf']
780
781 # Create a list of paths for all image and configuration nodes
782 req_its_paths = []
783 for image in images:
784 req_its_paths.append(['/', 'images', image])
785 if bb_vars['SPL_SIGN_ENABLE'] == "1":
786 req_its_paths.append(['/', 'images', image, 'signature'])
787 for configuration in configurations:
788 req_its_paths.append(['/', 'configurations', configuration])
789 return req_its_paths
790
791 def _get_req_its_fields(self, bb_vars):
792 loadables = ["uboot"]
793 its_field_check = [
794 'description = "%s";' % bb_vars['UBOOT_FIT_DESC'],
795 'description = "U-Boot image";',
796 'data = /incbin/("%s");' % bb_vars['UBOOT_NODTB_BINARY'],
797 'type = "standalone";',
798 'os = "u-boot";',
799 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
800 'compression = "none";',
801 'load = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
802 'entry = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
803 'description = "U-Boot FDT";',
804 'data = /incbin/("%s");' % bb_vars['UBOOT_DTB_BINARY'],
805 'type = "flat_dt";',
806 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
807 'compression = "none";',
399 ] 808 ]
809 if bb_vars['UBOOT_FIT_TEE'] == "1":
810 its_field_check += [
811 'description = "Trusted Execution Environment";',
812 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_TEE_IMAGE'],
813 'type = "tee";',
814 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
815 'os = "tee";',
816 'load = <%s>;' % bb_vars['UBOOT_FIT_TEE_LOADADDRESS'],
817 'entry = <%s>;' % bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'],
818 'compression = "none";',
819 ]
820 loadables.insert(0, "tee")
821 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
822 its_field_check += [
823 'description = "ARM Trusted Firmware";',
824 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'],
825 'type = "firmware";',
826 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
827 'os = "arm-trusted-firmware";',
828 'load = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
829 'entry = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'],
830 'compression = "none";',
831 ]
832 loadables.insert(0, "atf")
833 its_field_check += [
834 'default = "conf";',
835 'description = "Boot with signed U-Boot FIT";',
836 'loadables = "%s";' % '", "'.join(loadables),
837 'fdt = "fdt";',
838 ]
839 return its_field_check
840
841 def _get_req_sigvalues_config(self, bb_vars):
842 # COnfigurations are not signed by uboot-sign
843 return {}
844
845 def _get_req_sigvalues_image(self, bb_vars):
846 if bb_vars['SPL_SIGN_ENABLE'] != "1":
847 return {}
848 req_sigvalues_image = {
849 'algo': '"%s,%s"' % (bb_vars['UBOOT_FIT_HASH_ALG'], bb_vars['UBOOT_FIT_SIGN_ALG']),
850 'key-name-hint': '"%s"' % bb_vars['SPL_SIGN_KEYNAME'],
851 }
852 return req_sigvalues_image
400 853
401 node_str = exp_node_lines[0] 854 def _get_req_sections(self, bb_vars):
402 855 """Generate the expected output of dumpimage for beaglebone targets
403 print ("checking kernel node\n")
404 self.assertIn(node_str, its_lines)
405
406 node_start_idx = its_lines.index(node_str)
407 node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))]
408
409 # Remove the absolute path. This refers to WORKDIR which is not always predictable.
410 re_data = re.compile(r'^data = /incbin/\(.*/linux\.bin"\);$')
411 node = [re.sub(re_data, 'data = /incbin/("linux.bin");', cfg_str) for cfg_str in node]
412
413 self.assertEqual(node, exp_node_lines, "kernel node does not match expectation")
414
415 rx_configs = re.compile("^conf-.*")
416 its_configs = list(filter(rx_configs.match, its_lines))
417
418 for cfg_str in its_configs:
419 cfg_start_idx = its_lines.index(cfg_str)
420 line_idx = cfg_start_idx + 2
421 node_end = False
422 while node_end == False:
423 if its_lines[line_idx] == "};" and its_lines[line_idx-1] == "};" :
424 node_end = True
425 line_idx = line_idx + 1
426
427 node = its_lines[cfg_start_idx:line_idx]
428 print("checking configuration " + cfg_str.rstrip(" {"))
429 rx_desc_line = re.compile(r'^description = ".*Linux kernel.*')
430 self.assertEqual(len(list(filter(rx_desc_line.match, node))), 1, "kernel keyword not found in the description line")
431
432 self.assertIn('kernel = "kernel-1";', node)
433
434 rx_sign_line = re.compile(r'^sign-images = .*kernel.*')
435 self.assertEqual(len(list(filter(rx_sign_line.match, node))), 1, "kernel hash not signed")
436 856
437 # Verify the signature 857 The dict generated by this function is supposed to be compared against
438 uboot_tools_bindir = self._setup_uboot_tools_native() 858 the dict which is generated by the _dump_fitimage function.
439 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], 'am335x-bone.dtb')) 859 """
860 loadables = ['uboot']
861 req_sections = {
862 "uboot": {
863 "Type": "Standalone Program",
864 "Load Address": bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
865 "Entry Point": bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
866 },
867 "fdt": {
868 "Type": "Flat Device Tree",
869 }
870 }
871 if bb_vars['UBOOT_FIT_TEE'] == "1":
872 loadables.insert(0, "tee")
873 req_sections['tee'] = {
874 "Type": "Trusted Execution Environment Image",
875 # "Load Address": bb_vars['UBOOT_FIT_TEE_LOADADDRESS'], not printed by mkimage?
876 # "Entry Point": bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'], not printed by mkimage?
877 }
878 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
879 loadables.insert(0, "atf")
880 req_sections['atf'] = {
881 "Type": "Firmware",
882 "Load Address": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
883 # "Entry Point": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'], not printed by mkimage?
884 }
885 req_sections["conf"] = {
886 "Kernel": "unavailable",
887 "FDT": "fdt",
888 "Loadables": ','.join(loadables),
889 }
440 890
441 # Check if the u-boot boot.scr script is in the fitImage 891 # Add signing related properties if needed
442 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir) 892 uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
443 self._verify_fitimage_uboot_env(dumpimage_result) 893 uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
894 spl_sign_enable = bb_vars['SPL_SIGN_ENABLE']
895 spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
896 num_signatures = 0
897 if spl_sign_enable == "1":
898 for section in req_sections:
899 if not section.startswith('conf'):
900 req_sections[section]['Sign algo'] = "%s,%s:%s" % \
901 (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
902 num_signatures += 1
903 return (req_sections, num_signatures)
904
905 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
906 if bb_vars['UBOOT_FITIMAGE_ENABLE'] == '1' and bb_vars['SPL_SIGN_ENABLE'] == "1":
907 self.logger.debug("Verifying signatures in the FIT image")
908 else:
909 self.logger.debug("FIT image is not signed. Signature verification is not needed.")
910 return
911
912 uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
913 uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
914 spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
915 fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[uboot_fit_sign_alg]
916 for section, values in sections.items():
917 # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
918 if section.startswith("conf"):
919 # uboot-sign does not sign configuration nodes
920 pass
921 else:
922 # uboot-sign does not add hash nodes, only image signatures
923 sign_algo = values.get('Sign algo', None)
924 req_sign_algo = "%s,%s:%s" % (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
925 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
926 sign_value = values.get('Sign value', None)
927 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
928
929 # Search for the string passed to mkimage in each signed section of the FIT image.
930 # Looks like mkimage supports to add a comment but does not support to read it back.
931 a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['SPL_MKIMAGE_SIGN_ARGS'])
932 self.logger.debug("a_comment: %s" % a_comment)
933 if a_comment:
934 found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
935 self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
936 (num_signatures, a_comment))
444 937
445 def test_uboot_fit_image(self): 938 def test_uboot_fit_image(self):
446 """ 939 """
@@ -472,47 +965,9 @@ UBOOT_ENTRYPOINT = "0x80080000"
472UBOOT_FIT_DESC = "A model description" 965UBOOT_FIT_DESC = "A model description"
473""" 966"""
474 self.write_config(config) 967 self.write_config(config)
968 bb_vars = self._fit_get_bb_vars()
969 self._test_fitimage(bb_vars)
475 970
476 # The U-Boot fitImage is created as part of the U-Boot recipe
477 bitbake("virtual/bootloader")
478
479 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
480 machine = get_bb_var('MACHINE')
481 fitimage_its_path = os.path.join(deploy_dir_image,
482 "u-boot-its-%s" % (machine,))
483 fitimage_path = os.path.join(deploy_dir_image,
484 "u-boot-fitImage-%s" % (machine,))
485
486 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
487 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
488
489 # Check that the type, load address, entrypoint address and default
490 # values for kernel and ramdisk in Image Tree Source are as expected.
491 # The order of fields in the below array is important. Not all the
492 # fields are tested, only the key fields that wont vary between
493 # different architectures.
494 its_field_check = [
495 'description = "A model description";',
496 'type = "standalone";',
497 'load = <0x80080000>;',
498 'entry = <0x80080000>;',
499 'default = "conf";',
500 'loadables = "uboot";',
501 'fdt = "fdt";'
502 ]
503
504 with open(fitimage_its_path) as its_file:
505 field_index = 0
506 for line in its_file:
507 if field_index == len(its_field_check):
508 break
509 if its_field_check[field_index] in line:
510 field_index +=1
511
512 if field_index != len(its_field_check): # if its equal, the test passed
513 self.assertTrue(field_index == len(its_field_check),
514 "Fields in Image Tree Source File %s did not match, error in finding %s"
515 % (fitimage_its_path, its_field_check[field_index]))
516 971
517 def test_sign_standalone_uboot_fit_image(self): 972 def test_sign_standalone_uboot_fit_image(self):
518 """ 973 """
@@ -533,9 +988,8 @@ UBOOT_FIT_DESC = "A model description"
533 work by Paul Eggleton <paul.eggleton@microsoft.com> and 988 work by Paul Eggleton <paul.eggleton@microsoft.com> and
534 Usama Arif <usama.arif@arm.com> 989 Usama Arif <usama.arif@arm.com>
535 """ 990 """
536 a_comment = "a smart U-Boot comment"
537 config = """ 991 config = """
538# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at 992# There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at
539# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 993# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
540MACHINE = "qemuarm" 994MACHINE = "qemuarm"
541UBOOT_MACHINE = "am57xx_evm_defconfig" 995UBOOT_MACHINE = "am57xx_evm_defconfig"
@@ -551,104 +1005,15 @@ UBOOT_LOADADDRESS = "0x80000000"
551UBOOT_DTB_LOADADDRESS = "0x82000000" 1005UBOOT_DTB_LOADADDRESS = "0x82000000"
552UBOOT_ARCH = "arm" 1006UBOOT_ARCH = "arm"
553SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1007SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
554SPL_MKIMAGE_SIGN_ARGS = "-c '%s'" 1008SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
555UBOOT_EXTLINUX = "0" 1009UBOOT_EXTLINUX = "0"
556UBOOT_FIT_GENERATE_KEYS = "1" 1010UBOOT_FIT_GENERATE_KEYS = "1"
557UBOOT_FIT_HASH_ALG = "sha256" 1011UBOOT_FIT_HASH_ALG = "sha256"
558""" % a_comment 1012"""
559
560 self.write_config(config) 1013 self.write_config(config)
1014 bb_vars = self._fit_get_bb_vars()
1015 self._test_fitimage(bb_vars)
561 1016
562 # The U-Boot fitImage is created as part of the U-Boot recipe
563 bitbake("virtual/bootloader")
564
565 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
566 machine = get_bb_var('MACHINE')
567 fitimage_its_path = os.path.join(deploy_dir_image,
568 "u-boot-its-%s" % (machine,))
569 fitimage_path = os.path.join(deploy_dir_image,
570 "u-boot-fitImage-%s" % (machine,))
571
572 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
573 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
574
575 req_itspaths = [
576 ['/', 'images', 'uboot'],
577 ['/', 'images', 'uboot', 'signature'],
578 ['/', 'images', 'fdt'],
579 ['/', 'images', 'fdt', 'signature'],
580 ]
581
582 itspath = []
583 itspaths = []
584 linect = 0
585 sigs = {}
586 with open(fitimage_its_path) as its_file:
587 linect += 1
588 for line in its_file:
589 line = line.strip()
590 if line.endswith('};'):
591 itspath.pop()
592 elif line.endswith('{'):
593 itspath.append(line[:-1].strip())
594 itspaths.append(itspath[:])
595 elif itspath and itspath[-1] == 'signature':
596 itsdotpath = '.'.join(itspath)
597 if not itsdotpath in sigs:
598 sigs[itsdotpath] = {}
599 if not '=' in line or not line.endswith(';'):
600 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
601 key, value = line.split('=', 1)
602 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
603
604 for reqpath in req_itspaths:
605 if not reqpath in itspaths:
606 self.fail('Missing section in its file: %s' % reqpath)
607
608 reqsigvalues_image = {
609 'algo': '"sha256,rsa2048"',
610 'key-name-hint': '"spl-oe-selftest"',
611 }
612
613 for itspath, values in sigs.items():
614 reqsigvalues = reqsigvalues_image
615 for reqkey, reqvalue in reqsigvalues.items():
616 value = values.get(reqkey, None)
617 if value is None:
618 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
619 self.assertEqual(value, reqvalue)
620
621 # Dump the image to see if it really got signed
622 uboot_tools_bindir = self._setup_uboot_tools_native()
623 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
624 in_signed = None
625 signed_sections = {}
626 for line in dumpimage_result.output.splitlines():
627 if line.startswith((' Image')):
628 in_signed = re.search(r'\((.*)\)', line).groups()[0]
629 elif re.match(' \w', line):
630 in_signed = None
631 elif in_signed:
632 if not in_signed in signed_sections:
633 signed_sections[in_signed] = {}
634 key, value = line.split(':', 1)
635 signed_sections[in_signed][key.strip()] = value.strip()
636 self.assertIn('uboot', signed_sections)
637 self.assertIn('fdt', signed_sections)
638 for signed_section, values in signed_sections.items():
639 value = values.get('Sign algo', None)
640 self.assertEqual(value, 'sha256,rsa2048:spl-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
641 value = values.get('Sign value', None)
642 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
643
644 # Check for SPL_MKIMAGE_SIGN_ARGS
645 # Looks like mkimage supports to add a comment but does not support to read it back.
646 found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
647 self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.")
648
649 # Verify the signature
650 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
651 os.path.join(deploy_dir_image, 'u-boot-spl.dtb'))
652 1017
653 def test_sign_cascaded_uboot_fit_image(self): 1018 def test_sign_cascaded_uboot_fit_image(self):
654 """ 1019 """
@@ -670,7 +1035,6 @@ UBOOT_FIT_HASH_ALG = "sha256"
670 work by Paul Eggleton <paul.eggleton@microsoft.com> and 1035 work by Paul Eggleton <paul.eggleton@microsoft.com> and
671 Usama Arif <usama.arif@arm.com> 1036 Usama Arif <usama.arif@arm.com>
672 """ 1037 """
673 a_comment = "a smart cascaded U-Boot comment"
674 config = """ 1038 config = """
675# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at 1039# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
676# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 1040# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
@@ -686,7 +1050,7 @@ UBOOT_DTB_BINARY = "u-boot.dtb"
686UBOOT_ENTRYPOINT = "0x80000000" 1050UBOOT_ENTRYPOINT = "0x80000000"
687UBOOT_LOADADDRESS = "0x80000000" 1051UBOOT_LOADADDRESS = "0x80000000"
688UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1052UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
689UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'" 1053UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
690UBOOT_DTB_LOADADDRESS = "0x82000000" 1054UBOOT_DTB_LOADADDRESS = "0x82000000"
691UBOOT_ARCH = "arm" 1055UBOOT_ARCH = "arm"
692SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1056SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
@@ -700,99 +1064,10 @@ UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
700UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" 1064UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
701UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" 1065UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
702FIT_SIGN_INDIVIDUAL = "1" 1066FIT_SIGN_INDIVIDUAL = "1"
703""" % a_comment 1067"""
704 self.write_config(config) 1068 self.write_config(config)
705 1069 bb_vars = self._fit_get_bb_vars()
706 # The U-Boot fitImage is created as part of the U-Boot recipe 1070 self._test_fitimage(bb_vars)
707 bitbake("virtual/bootloader")
708
709 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
710 machine = get_bb_var('MACHINE')
711 fitimage_its_path = os.path.join(deploy_dir_image,
712 "u-boot-its-%s" % (machine,))
713 fitimage_path = os.path.join(deploy_dir_image,
714 "u-boot-fitImage-%s" % (machine,))
715
716 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
717 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
718
719 req_itspaths = [
720 ['/', 'images', 'uboot'],
721 ['/', 'images', 'uboot', 'signature'],
722 ['/', 'images', 'fdt'],
723 ['/', 'images', 'fdt', 'signature'],
724 ]
725
726 itspath = []
727 itspaths = []
728 linect = 0
729 sigs = {}
730 with open(fitimage_its_path) as its_file:
731 linect += 1
732 for line in its_file:
733 line = line.strip()
734 if line.endswith('};'):
735 itspath.pop()
736 elif line.endswith('{'):
737 itspath.append(line[:-1].strip())
738 itspaths.append(itspath[:])
739 elif itspath and itspath[-1] == 'signature':
740 itsdotpath = '.'.join(itspath)
741 if not itsdotpath in sigs:
742 sigs[itsdotpath] = {}
743 if not '=' in line or not line.endswith(';'):
744 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
745 key, value = line.split('=', 1)
746 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
747
748 for reqpath in req_itspaths:
749 if not reqpath in itspaths:
750 self.fail('Missing section in its file: %s' % reqpath)
751
752 reqsigvalues_image = {
753 'algo': '"sha256,rsa2048"',
754 'key-name-hint': '"spl-cascaded-oe-selftest"',
755 }
756
757 for itspath, values in sigs.items():
758 reqsigvalues = reqsigvalues_image
759 for reqkey, reqvalue in reqsigvalues.items():
760 value = values.get(reqkey, None)
761 if value is None:
762 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
763 self.assertEqual(value, reqvalue)
764
765 # Dump the image to see if it really got signed
766 uboot_tools_bindir = self._setup_uboot_tools_native()
767 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
768 in_signed = None
769 signed_sections = {}
770 for line in dumpimage_result.output.splitlines():
771 if line.startswith((' Image')):
772 in_signed = re.search(r'\((.*)\)', line).groups()[0]
773 elif re.match(' \w', line):
774 in_signed = None
775 elif in_signed:
776 if not in_signed in signed_sections:
777 signed_sections[in_signed] = {}
778 key, value = line.split(':', 1)
779 signed_sections[in_signed][key.strip()] = value.strip()
780 self.assertIn('uboot', signed_sections)
781 self.assertIn('fdt', signed_sections)
782 for signed_section, values in signed_sections.items():
783 value = values.get('Sign algo', None)
784 self.assertEqual(value, 'sha256,rsa2048:spl-cascaded-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
785 value = values.get('Sign value', None)
786 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
787
788 # Check for SPL_MKIMAGE_SIGN_ARGS
789 # Looks like mkimage supports to add a comment but does not support to read it back.
790 found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
791 self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.")
792
793 # Verify the signature
794 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
795 os.path.join(deploy_dir_image, 'u-boot-spl.dtb'))
796 1071
797 def test_uboot_atf_tee_fit_image(self): 1072 def test_uboot_atf_tee_fit_image(self):
798 """ 1073 """
@@ -841,67 +1116,20 @@ UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
841""" 1116"""
842 self.write_config(config) 1117 self.write_config(config)
843 1118
1119 bb_vars = self._fit_get_bb_vars([
1120 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
1121 'UBOOT_FIT_TEE_IMAGE',
1122 ])
1123
844 # Create an ATF dummy image 1124 # Create an ATF dummy image
845 atf_dummy_image = os.path.join(self.builddir, "atf-dummy.bin") 1125 dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
846 cmd = 'dd if=/dev/random of=%s bs=1k count=64' % (atf_dummy_image) 1126 FitImageTestCase._gen_random_file(dummy_atf)
847 result = runCmd(cmd)
848 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
849 1127
850 # Create a TEE dummy image 1128 # Create a TEE dummy image
851 tee_dummy_image = os.path.join(self.builddir, "tee-dummy.bin") 1129 dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
852 cmd = 'dd if=/dev/random of=%s bs=1k count=64' % (tee_dummy_image) 1130 FitImageTestCase._gen_random_file(dummy_tee)
853 result = runCmd(cmd)
854 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
855
856 # The U-Boot fitImage is created as part of the U-Boot recipe
857 bitbake("virtual/bootloader")
858
859 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
860 machine = get_bb_var('MACHINE')
861 fitimage_its_path = os.path.join(deploy_dir_image,
862 "u-boot-its-%s" % (machine,))
863 fitimage_path = os.path.join(deploy_dir_image,
864 "u-boot-fitImage-%s" % (machine,))
865
866 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
867 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
868
869 # Check that the type, load address, entrypoint address and default
870 # values for u-boot, ATF and TEE in Image Tree Source are as expected.
871 # The order of fields in the below array is important. Not all the
872 # fields are tested, only the key fields that wont vary between
873 # different architectures.
874 its_field_check = [
875 'description = "A model description";',
876 'type = "standalone";',
877 'load = <0x80080000>;',
878 'entry = <0x80080000>;',
879 'description = "Trusted Execution Environment";',
880 'os = "tee";',
881 'load = <0x80180000>;',
882 'entry = <0x80180000>;',
883 'description = "ARM Trusted Firmware";',
884 'os = "arm-trusted-firmware";',
885 'load = <0x80280000>;',
886 'entry = <0x80280000>;',
887 'default = "conf";',
888 'loadables = "atf", "tee", "uboot";',
889 'fdt = "fdt";'
890 ]
891
892 with open(fitimage_its_path) as its_file:
893 field_index = 0
894 for line in its_file:
895 if field_index == len(its_field_check):
896 break
897 if its_field_check[field_index] in line:
898 field_index +=1
899
900 if field_index != len(its_field_check): # if its equal, the test passed
901 self.assertTrue(field_index == len(its_field_check),
902 "Fields in Image Tree Source File %s did not match, error in finding %s"
903 % (fitimage_its_path, its_field_check[field_index]))
904 1131
1132 self._test_fitimage(bb_vars)
905 1133
906 def test_sign_standalone_uboot_atf_tee_fit_image(self): 1134 def test_sign_standalone_uboot_atf_tee_fit_image(self):
907 """ 1135 """
@@ -921,7 +1149,6 @@ UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
921 Product: oe-core 1149 Product: oe-core
922 Author: Jamin Lin <jamin_lin@aspeedtech.com> 1150 Author: Jamin Lin <jamin_lin@aspeedtech.com>
923 """ 1151 """
924 a_comment = "a smart U-Boot ATF TEE comment"
925 config = """ 1152 config = """
926# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at 1153# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
927# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 1154# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
@@ -938,7 +1165,7 @@ UBOOT_ENTRYPOINT = "0x80000000"
938UBOOT_LOADADDRESS = "0x80000000" 1165UBOOT_LOADADDRESS = "0x80000000"
939UBOOT_ARCH = "arm" 1166UBOOT_ARCH = "arm"
940SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1167SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
941SPL_MKIMAGE_SIGN_ARGS = "-c '%s'" 1168SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot ATF TEE comment'"
942UBOOT_EXTLINUX = "0" 1169UBOOT_EXTLINUX = "0"
943UBOOT_FIT_GENERATE_KEYS = "1" 1170UBOOT_FIT_GENERATE_KEYS = "1"
944UBOOT_FIT_HASH_ALG = "sha256" 1171UBOOT_FIT_HASH_ALG = "sha256"
@@ -958,115 +1185,20 @@ UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1"
958UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin" 1185UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin"
959UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000" 1186UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000"
960UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000" 1187UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
961""" % a_comment 1188"""
962
963 self.write_config(config) 1189 self.write_config(config)
964 1190
1191 bb_vars = self._fit_get_bb_vars([
1192 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
1193 'UBOOT_FIT_TEE_IMAGE',
1194 ])
1195
965 # Create an ATF dummy image 1196 # Create an ATF dummy image
966 atf_dummy_image = os.path.join(self.builddir, "atf-dummy.bin") 1197 dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
967 cmd = 'dd if=/dev/random of=%s bs=1k count=64' % (atf_dummy_image) 1198 FitImageTestCase._gen_random_file(dummy_atf)
968 result = runCmd(cmd)
969 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
970 1199
971 # Create a TEE dummy image 1200 # Create a TEE dummy image
972 tee_dummy_image = os.path.join(self.builddir, "tee-dummy.bin") 1201 dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
973 cmd = 'dd if=/dev/random of=%s bs=1k count=64' % (tee_dummy_image) 1202 FitImageTestCase._gen_random_file(dummy_tee)
974 result = runCmd(cmd)
975 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
976
977 # The U-Boot fitImage is created as part of the U-Boot recipe
978 bitbake("virtual/bootloader")
979
980 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
981 machine = get_bb_var('MACHINE')
982 fitimage_its_path = os.path.join(deploy_dir_image,
983 "u-boot-its-%s" % (machine,))
984 fitimage_path = os.path.join(deploy_dir_image,
985 "u-boot-fitImage-%s" % (machine,))
986
987 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
988 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
989
990 req_itspaths = [
991 ['/', 'images', 'uboot'],
992 ['/', 'images', 'uboot', 'signature'],
993 ['/', 'images', 'fdt'],
994 ['/', 'images', 'fdt', 'signature'],
995 ['/', 'images', 'tee'],
996 ['/', 'images', 'tee', 'signature'],
997 ['/', 'images', 'atf'],
998 ['/', 'images', 'atf', 'signature'],
999 ]
1000
1001 itspath = []
1002 itspaths = []
1003 linect = 0
1004 sigs = {}
1005 with open(fitimage_its_path) as its_file:
1006 linect += 1
1007 for line in its_file:
1008 line = line.strip()
1009 if line.endswith('};'):
1010 itspath.pop()
1011 elif line.endswith('{'):
1012 itspath.append(line[:-1].strip())
1013 itspaths.append(itspath[:])
1014 elif itspath and itspath[-1] == 'signature':
1015 itsdotpath = '.'.join(itspath)
1016 if not itsdotpath in sigs:
1017 sigs[itsdotpath] = {}
1018 if not '=' in line or not line.endswith(';'):
1019 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
1020 key, value = line.split('=', 1)
1021 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
1022
1023 for reqpath in req_itspaths:
1024 if not reqpath in itspaths:
1025 self.fail('Missing section in its file: %s' % reqpath)
1026
1027 reqsigvalues_image = {
1028 'algo': '"sha256,rsa2048"',
1029 'key-name-hint': '"spl-oe-selftest"',
1030 }
1031
1032 for itspath, values in sigs.items():
1033 reqsigvalues = reqsigvalues_image
1034 for reqkey, reqvalue in reqsigvalues.items():
1035 value = values.get(reqkey, None)
1036 if value is None:
1037 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
1038 self.assertEqual(value, reqvalue)
1039
1040 # Dump the image to see if it really got signed
1041 uboot_tools_bindir = self._setup_uboot_tools_native()
1042 dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
1043 in_signed = None
1044 signed_sections = {}
1045 for line in dumpimage_result.output.splitlines():
1046 if line.startswith((' Image')):
1047 in_signed = re.search(r'\((.*)\)', line).groups()[0]
1048 elif re.match(' \w', line):
1049 in_signed = None
1050 elif in_signed:
1051 if not in_signed in signed_sections:
1052 signed_sections[in_signed] = {}
1053 key, value = line.split(':', 1)
1054 signed_sections[in_signed][key.strip()] = value.strip()
1055 self.assertIn('uboot', signed_sections)
1056 self.assertIn('fdt', signed_sections)
1057 self.assertIn('tee', signed_sections)
1058 self.assertIn('atf', signed_sections)
1059 for signed_section, values in signed_sections.items():
1060 value = values.get('Sign algo', None)
1061 self.assertEqual(value, 'sha256,rsa2048:spl-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
1062 value = values.get('Sign value', None)
1063 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
1064
1065 # Check for SPL_MKIMAGE_SIGN_ARGS
1066 # Looks like mkimage supports to add a comment but does not support to read it back.
1067 found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
1068 self.assertEqual(found_comments, 4, "Expected 4 signed and commented section in the fitImage.")
1069 1203
1070 # Verify the signature 1204 self._test_fitimage(bb_vars)
1071 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
1072 os.path.join(deploy_dir_image, 'u-boot-spl.dtb'))