diff options
Diffstat (limited to 'meta/lib/oeqa/selftest/cases/fitimage.py')
-rw-r--r-- | meta/lib/oeqa/selftest/cases/fitimage.py | 2117 |
1 files changed, 1501 insertions, 616 deletions
diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index 347c065377..3c40857747 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py | |||
@@ -4,12 +4,740 @@ | |||
4 | # SPDX-License-Identifier: MIT | 4 | # SPDX-License-Identifier: MIT |
5 | # | 5 | # |
6 | 6 | ||
7 | from oeqa.selftest.case import OESelftestTestCase | ||
8 | from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars | ||
9 | import os | 7 | import os |
10 | import re | 8 | import re |
9 | import shlex | ||
10 | import logging | ||
11 | import pprint | ||
12 | import tempfile | ||
13 | |||
14 | import oe.fitimage | ||
15 | |||
16 | from oeqa.selftest.case import OESelftestTestCase | ||
17 | from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_bb_var | ||
18 | |||
19 | |||
20 | class BbVarsMockGenKeys: | ||
21 | def __init__(self, keydir, gen_keys="0", sign_enabled="0", keyname="", sign_ind="0", img_keyname=""): | ||
22 | self.bb_vars = { | ||
23 | 'FIT_GENERATE_KEYS': gen_keys, | ||
24 | 'FIT_KEY_GENRSA_ARGS': "-F4", | ||
25 | 'FIT_KEY_REQ_ARGS': "-batch -new", | ||
26 | 'FIT_KEY_SIGN_PKCS': "-x509", | ||
27 | 'FIT_SIGN_INDIVIDUAL': sign_ind, | ||
28 | 'FIT_SIGN_NUMBITS': "2048", | ||
29 | 'UBOOT_SIGN_ENABLE': sign_enabled, | ||
30 | 'UBOOT_SIGN_IMG_KEYNAME': img_keyname, | ||
31 | 'UBOOT_SIGN_KEYDIR': keydir, | ||
32 | 'UBOOT_SIGN_KEYNAME': keyname, | ||
33 | } | ||
34 | |||
35 | def getVar(self, var): | ||
36 | return self.bb_vars[var] | ||
37 | |||
38 | class FitImageTestCase(OESelftestTestCase): | ||
39 | """Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass | ||
40 | |||
41 | A brief summary showing the structure of a test case: | ||
42 | |||
43 | self._test_fitimage() | ||
44 | # Generate a local.conf file and bitbake the bootloader or the kernel | ||
45 | self._bitbake_fit_image() | ||
46 | |||
47 | # Check if the its file contains the expected paths and attributes. | ||
48 | # The _get_req_* functions are implemented by more specific chield classes. | ||
49 | self._check_its_file() | ||
50 | req_its_paths = self._get_req_its_paths() | ||
51 | req_sigvalues_config = self._get_req_sigvalues_config() | ||
52 | req_sigvalues_image = self._get_req_sigvalues_image() | ||
53 | # Compare the its file against req_its_paths, req_sigvalues_config, req_sigvalues_image | ||
54 | |||
55 | # Call the dumpimage utiliy and check that it prints all the expected paths and attributes | ||
56 | # The _get_req_* functions are implemented by more specific chield classes. | ||
57 | self._check_fitimage() | ||
58 | self._get_req_sections() | ||
59 | # Compare the output of the dumpimage utility against | ||
60 | """ | ||
61 | |||
62 | MKIMAGE_HASH_LENGTHS = { 'sha256': 64, 'sha384': 96, 'sha512': 128 } | ||
63 | MKIMAGE_SIGNATURE_LENGTHS = { 'rsa2048': 512 } | ||
64 | |||
65 | def _gen_signing_key(self, bb_vars): | ||
66 | """Generate a key pair and a singing certificate | ||
67 | |||
68 | Generate a UBOOT_SIGN_KEYNAME in the UBOOT_SIGN_KEYDIR similar to what | ||
69 | the FIT_GENERATE_KEYS feature does. However, having a static key is | ||
70 | probably a more realistic use case than generating a random key with | ||
71 | each clean build. So this needs to be tested as well. | ||
72 | The FIT_GENERATE_KEYS generates 2 keys: The UBOOT_SIGN_KEYNAME and the | ||
73 | UBOOT_SIGN_IMG_KEYNAME. The UBOOT_SIGN_IMG_KEYNAME is used by the | ||
74 | FIT_SIGN_INDIVIDUAL feature only. Testing if everything is working if | ||
75 | there is only one key available is important as well. Therefore this | ||
76 | function generates only the keys which are really needed, not just two. | ||
77 | """ | ||
78 | |||
79 | # Define some variables which are usually defined by the kernel-fitimage.bbclass. | ||
80 | # But for testing purpose check if the uboot-sign.bbclass is independent from | ||
81 | # the kernel-fitimage.bbclass | ||
82 | fit_sign_numbits = bb_vars.get('FIT_SIGN_NUMBITS', "2048") | ||
83 | fit_key_genrsa_args = bb_vars.get('FIT_KEY_GENRSA_ARGS', "-F4") | ||
84 | fit_key_req_args = bb_vars.get('FIT_KEY_REQ_ARGS', "-batch -new") | ||
85 | fit_key_sign_pkcs = bb_vars.get('FIT_KEY_SIGN_PKCS', "-x509") | ||
86 | |||
87 | uboot_sign_keydir = bb_vars['UBOOT_SIGN_KEYDIR'] | ||
88 | sign_keys = [bb_vars['UBOOT_SIGN_KEYNAME']] | ||
89 | if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1": | ||
90 | sign_keys.append(bb_vars['UBOOT_SIGN_IMG_KEYNAME']) | ||
91 | for sign_key in sign_keys: | ||
92 | sing_key_path = os.path.join(uboot_sign_keydir, sign_key) | ||
93 | if not os.path.isdir(uboot_sign_keydir): | ||
94 | os.makedirs(uboot_sign_keydir) | ||
95 | openssl_bindir = FitImageTestCase._setup_native('openssl-native') | ||
96 | openssl_path = os.path.join(openssl_bindir, 'openssl') | ||
97 | runCmd("%s genrsa %s -out %s.key %s" % ( | ||
98 | openssl_path, | ||
99 | fit_key_genrsa_args, | ||
100 | sing_key_path, | ||
101 | fit_sign_numbits | ||
102 | )) | ||
103 | runCmd("%s req %s %s -key %s.key -out %s.crt" % ( | ||
104 | openssl_path, | ||
105 | fit_key_req_args, | ||
106 | fit_key_sign_pkcs, | ||
107 | sing_key_path, | ||
108 | sing_key_path | ||
109 | )) | ||
110 | |||
111 | @staticmethod | ||
112 | def _gen_random_file(file_path, num_bytes=65536): | ||
113 | with open(file_path, 'wb') as file_out: | ||
114 | file_out.write(os.urandom(num_bytes)) | ||
115 | |||
116 | @staticmethod | ||
117 | def _setup_native(native_recipe): | ||
118 | """Build a native recipe and return the path to its bindir in RECIPE_SYSROOT_NATIVE""" | ||
119 | bitbake(native_recipe + " -c addto_recipe_sysroot") | ||
120 | vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], native_recipe) | ||
121 | return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir']) | ||
122 | |||
123 | def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None): | ||
124 | """Verify the signature of a fit configuration | ||
125 | |||
126 | The fit_check_sign utility from u-boot-tools-native is called. | ||
127 | uboot-fit_check_sign -f fitImage -k $dtb_path -c conf-$dtb_name | ||
128 | dtb_path refers to a binary device tree containing the public key. | ||
129 | """ | ||
130 | fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign') | ||
131 | cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path) | ||
132 | if conf_name: | ||
133 | cmd += ' -c %s' % conf_name | ||
134 | result = runCmd(cmd) | ||
135 | self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output) | ||
136 | self.assertIn("Signature check OK", result.output) | ||
137 | |||
138 | def _verify_dtb_property(self, dtc_bindir, dtb_path, node_path, property_name, req_property, absent=False): | ||
139 | """Verify device tree properties | ||
140 | |||
141 | The fdtget utility from dtc-native is called and the property is compared. | ||
142 | """ | ||
143 | fdtget_path = os.path.join(dtc_bindir, 'fdtget') | ||
144 | cmd = '%s %s %s %s' % (fdtget_path, dtb_path, node_path, property_name) | ||
145 | if absent: | ||
146 | result = runCmd(cmd, ignore_status=True) | ||
147 | self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output) | ||
148 | self.assertIn("FDT_ERR_NOTFOUND", result.output) | ||
149 | else: | ||
150 | result = runCmd(cmd) | ||
151 | self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output) | ||
152 | self.assertEqual(req_property, result.output.strip()) | ||
153 | |||
154 | @staticmethod | ||
155 | def _find_string_in_bin_file(file_path, search_string): | ||
156 | """find strings in a binary file | ||
157 | |||
158 | Shell equivalent: strings "$1" | grep "$2" | wc -l | ||
159 | return number of matches | ||
160 | """ | ||
161 | found_positions = 0 | ||
162 | with open(file_path, 'rb') as file: | ||
163 | content = file.read().decode('ascii', errors='ignore') | ||
164 | found_positions = content.count(search_string) | ||
165 | return found_positions | ||
166 | |||
167 | @staticmethod | ||
168 | def _get_uboot_mkimage_sign_args(uboot_mkimage_sign_args): | ||
169 | """Retrive the string passed via -c to the mkimage command | ||
170 | |||
171 | Example: If a build configutation defines | ||
172 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | ||
173 | this function returns "a smart comment" | ||
174 | """ | ||
175 | a_comment = None | ||
176 | if uboot_mkimage_sign_args: | ||
177 | mkimage_args = shlex.split(uboot_mkimage_sign_args) | ||
178 | try: | ||
179 | c_index = mkimage_args.index('-c') | ||
180 | a_comment = mkimage_args[c_index+1] | ||
181 | except ValueError: | ||
182 | pass | ||
183 | return a_comment | ||
184 | |||
185 | @staticmethod | ||
186 | def _get_dtb_files(bb_vars): | ||
187 | """Return a list of devicetree names | ||
188 | |||
189 | The list should be used to check the dtb and conf nodes in the FIT image or its file. | ||
190 | In addition to the entries from KERNEL_DEVICETREE, the external devicetree and the | ||
191 | external devicetree overlay added by the test recipe bbb-dtbs-as-ext are handled as well. | ||
192 | """ | ||
193 | kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE') | ||
194 | all_dtbs = [] | ||
195 | dtb_symlinks = [] | ||
196 | if kernel_devicetree: | ||
197 | all_dtbs += [os.path.basename(dtb) for dtb in kernel_devicetree.split()] | ||
198 | # Support only the test recipe which provides 1 devicetree and 1 devicetree overlay | ||
199 | pref_prov_dtb = bb_vars.get('PREFERRED_PROVIDER_virtual/dtb') | ||
200 | if pref_prov_dtb == "bbb-dtbs-as-ext": | ||
201 | all_dtbs += ["am335x-bonegreen-ext.dtb", "BBORG_RELAY-00A2.dtbo"] | ||
202 | dtb_symlinks.append("am335x-bonegreen-ext-alias.dtb") | ||
203 | return (all_dtbs, dtb_symlinks) | ||
204 | |||
205 | def _is_req_dict_in_dict(self, found_dict, req_dict): | ||
206 | """ | ||
207 | Check if all key-value pairs in the required dictionary are present in the found dictionary. | ||
208 | |||
209 | This function recursively checks if the required dictionary (`req_dict`) is a subset of the found dictionary (`found_dict`). | ||
210 | It supports nested dictionaries, strings, lists, and sets as values. | ||
211 | |||
212 | Args: | ||
213 | found_dict (dict): The dictionary to search within. | ||
214 | req_dict (dict): The dictionary containing the required key-value pairs. | ||
215 | """ | ||
216 | for key, value in req_dict.items(): | ||
217 | self.assertIn(key, found_dict) | ||
218 | if isinstance(value, dict): | ||
219 | self._is_req_dict_in_dict(found_dict[key], value) | ||
220 | elif isinstance(value, str): | ||
221 | self.assertIn(value, found_dict[key]) | ||
222 | elif isinstance(value, list): | ||
223 | self.assertLessEqual(set(value), set(found_dict[key])) | ||
224 | elif isinstance(value, set): | ||
225 | self.assertLessEqual(value, found_dict[key]) | ||
226 | else: | ||
227 | self.assertEqual(value, found_dict[key]) | ||
228 | |||
229 | def _check_its_file(self, bb_vars, its_file_path): | ||
230 | """Check if the its file contains the expected sections and fields""" | ||
231 | # print the its file for debugging | ||
232 | if logging.DEBUG >= self.logger.level: | ||
233 | with open(its_file_path) as its_file: | ||
234 | self.logger.debug("its file: %s" % its_file.read()) | ||
235 | |||
236 | # Generate a list of expected paths in the its file | ||
237 | req_its_paths = self._get_req_its_paths(bb_vars) | ||
238 | self.logger.debug("req_its_paths:\n%s\n" % pprint.pformat(req_its_paths, indent=4)) | ||
239 | |||
240 | # Generate a dict of expected configuration signature nodes | ||
241 | req_sigvalues_config = self._get_req_sigvalues_config(bb_vars) | ||
242 | self.logger.debug("req_sigvalues_config:\n%s\n" % pprint.pformat(req_sigvalues_config, indent=4)) | ||
243 | |||
244 | # Generate a dict of expected image signature nodes | ||
245 | req_sigvalues_image = self._get_req_sigvalues_image(bb_vars) | ||
246 | self.logger.debug("req_sigvalues_image:\n%s\n" % pprint.pformat(req_sigvalues_image, indent=4)) | ||
247 | |||
248 | # Parse the its file for paths and signatures | ||
249 | its_path = [] | ||
250 | its_paths = [] | ||
251 | linect = 0 | ||
252 | sigs = {} | ||
253 | with open(its_file_path) as its_file: | ||
254 | for line in its_file: | ||
255 | linect += 1 | ||
256 | line = line.strip() | ||
257 | if line.endswith('};'): | ||
258 | its_path.pop() | ||
259 | elif line.endswith('{'): | ||
260 | its_path.append(line[:-1].strip()) | ||
261 | its_paths.append(its_path[:]) | ||
262 | # kernel-fitimage uses signature-1, uboot-sign uses signature | ||
263 | elif its_path and (its_path[-1] == 'signature-1' or its_path[-1] == 'signature'): | ||
264 | itsdotpath = '.'.join(its_path) | ||
265 | if not itsdotpath in sigs: | ||
266 | sigs[itsdotpath] = {} | ||
267 | if not '=' in line or not line.endswith(';'): | ||
268 | self.fail('Unexpected formatting in %s sigs section line %d:%s' % (its_file_path, linect, line)) | ||
269 | key, value = line.split('=', 1) | ||
270 | sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';') | ||
271 | |||
272 | # Check if all expected paths are found in the its file | ||
273 | self.logger.debug("itspaths:\n%s\n" % pprint.pformat(its_paths, indent=4)) | ||
274 | for req_path in req_its_paths: | ||
275 | if not req_path in its_paths: | ||
276 | self.fail('Missing path in its file: %s (%s)' % (req_path, its_file_path)) | ||
277 | |||
278 | # Check if all the expected singnature nodes (images and configurations) are found | ||
279 | self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4)) | ||
280 | if req_sigvalues_config or req_sigvalues_image: | ||
281 | for its_path, values in sigs.items(): | ||
282 | if bb_vars.get('FIT_CONF_PREFIX', "conf-") in its_path: | ||
283 | reqsigvalues = req_sigvalues_config | ||
284 | else: | ||
285 | reqsigvalues = req_sigvalues_image | ||
286 | for reqkey, reqvalue in reqsigvalues.items(): | ||
287 | value = values.get(reqkey, None) | ||
288 | if value is None: | ||
289 | self.fail('Missing key "%s" in its file signature section %s (%s)' % (reqkey, its_path, its_file_path)) | ||
290 | self.assertEqual(value, reqvalue) | ||
291 | |||
292 | # Generate a list of expected fields in the its file | ||
293 | req_its_fields = self._get_req_its_fields(bb_vars) | ||
294 | self.logger.debug("req_its_fields:\n%s\n" % pprint.pformat(req_its_fields, indent=4)) | ||
295 | |||
296 | # Check if all expected fields are in the its file | ||
297 | if req_its_fields: | ||
298 | field_index = 0 | ||
299 | field_index_last = len(req_its_fields) - 1 | ||
300 | with open(its_file_path) as its_file: | ||
301 | for line in its_file: | ||
302 | if req_its_fields[field_index] in line: | ||
303 | if field_index < field_index_last: | ||
304 | field_index +=1 | ||
305 | else: | ||
306 | break | ||
307 | self.assertEqual(field_index, field_index_last, | ||
308 | "Fields in Image Tree Source File %s did not match, error in finding %s" | ||
309 | % (its_file_path, req_its_fields[field_index])) | ||
310 | |||
311 | def _check_fitimage(self, bb_vars, fitimage_path, uboot_tools_bindir): | ||
312 | """Run dumpimage on the final FIT image and parse the output into a dict""" | ||
313 | dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage') | ||
314 | cmd = '%s -l %s' % (dumpimage_path, fitimage_path) | ||
315 | self.logger.debug("Analyzing output from dumpimage: %s" % cmd) | ||
316 | dumpimage_result = runCmd(cmd) | ||
317 | in_section = None | ||
318 | sections = {} | ||
319 | self.logger.debug("dumpimage output: %s" % dumpimage_result.output) | ||
320 | for line in dumpimage_result.output.splitlines(): | ||
321 | # Find potentially hashed and signed sections | ||
322 | if line.startswith((' Configuration', ' Image')): | ||
323 | in_section = re.search(r'\((.*)\)', line).groups()[0] | ||
324 | # Key value lines start with two spaces otherwise the section ended | ||
325 | elif not line.startswith(" "): | ||
326 | in_section = None | ||
327 | # Handle key value lines of this section | ||
328 | elif in_section: | ||
329 | if not in_section in sections: | ||
330 | sections[in_section] = {} | ||
331 | try: | ||
332 | key, value = line.split(':', 1) | ||
333 | key = key.strip() | ||
334 | value = value.strip() | ||
335 | except ValueError as val_err: | ||
336 | # Handle multiple entries as e.g. for Loadables as a list | ||
337 | if key and line.startswith(" "): | ||
338 | value = sections[in_section][key] + "," + line.strip() | ||
339 | else: | ||
340 | raise ValueError(f"Error processing line: '{line}'. Original error: {val_err}") | ||
341 | sections[in_section][key] = value | ||
342 | |||
343 | # Check if the requested dictionary is a subset of the parsed dictionary | ||
344 | req_sections, num_signatures = self._get_req_sections(bb_vars) | ||
345 | self.logger.debug("req_sections: \n%s\n" % pprint.pformat(req_sections, indent=4)) | ||
346 | self.logger.debug("dumpimage sections: \n%s\n" % pprint.pformat(sections, indent=4)) | ||
347 | self._is_req_dict_in_dict(sections, req_sections) | ||
348 | |||
349 | # Call the signing related checks if the function is provided by a inherited class | ||
350 | self._check_signing(bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path) | ||
351 | |||
352 | def _get_req_its_paths(self, bb_vars): | ||
353 | self.logger.error("This function needs to be implemented") | ||
354 | return [] | ||
355 | |||
356 | def _get_req_its_fields(self, bb_vars): | ||
357 | self.logger.error("This function needs to be implemented") | ||
358 | return [] | ||
359 | |||
360 | def _get_req_sigvalues_config(self, bb_vars): | ||
361 | self.logger.error("This function needs to be implemented") | ||
362 | return {} | ||
363 | |||
364 | def _get_req_sigvalues_image(self, bb_vars): | ||
365 | self.logger.error("This function needs to be implemented") | ||
366 | return {} | ||
367 | |||
368 | def _get_req_sections(self, bb_vars): | ||
369 | self.logger.error("This function needs to be implemented") | ||
370 | return ({}, 0) | ||
371 | |||
372 | def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path): | ||
373 | """Verify the signatures in the FIT image.""" | ||
374 | self.fail("Function needs to be implemented by inheriting classes") | ||
375 | |||
376 | def _bitbake_fit_image(self, bb_vars): | ||
377 | """Bitbake the FIT image and return the paths to the its file and the FIT image""" | ||
378 | self.fail("Function needs to be implemented by inheriting classes") | ||
379 | |||
380 | def _test_fitimage(self, bb_vars): | ||
381 | """Check if the its file and the FIT image are created and signed correctly""" | ||
382 | fitimage_its_path, fitimage_path = self._bitbake_fit_image(bb_vars) | ||
383 | self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
384 | self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) | ||
385 | |||
386 | self.logger.debug("Checking its: %s" % fitimage_its_path) | ||
387 | self._check_its_file(bb_vars, fitimage_its_path) | ||
388 | |||
389 | # Setup u-boot-tools-native | ||
390 | uboot_tools_bindir = FitImageTestCase._setup_native('u-boot-tools-native') | ||
391 | |||
392 | # Verify the FIT image | ||
393 | self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir) | ||
394 | |||
395 | class KernelFitImageBase(FitImageTestCase): | ||
396 | """Test cases for the linux-yocto-fitimage recipe""" | ||
397 | |||
398 | def _fit_get_bb_vars(self, additional_vars=[]): | ||
399 | """Retrieve BitBake variables specific to the test case. | ||
400 | |||
401 | Call the get_bb_vars function once and get all variables needed by the test case. | ||
402 | """ | ||
403 | internal_used = { | ||
404 | 'DEPLOY_DIR_IMAGE', | ||
405 | 'FIT_CONF_DEFAULT_DTB', | ||
406 | 'FIT_CONF_PREFIX', | ||
407 | 'FIT_DESC', | ||
408 | 'FIT_HASH_ALG', | ||
409 | 'FIT_KERNEL_COMP_ALG', | ||
410 | 'FIT_SIGN_ALG', | ||
411 | 'FIT_SIGN_INDIVIDUAL', | ||
412 | 'FIT_UBOOT_ENV', | ||
413 | 'INITRAMFS_IMAGE_BUNDLE', | ||
414 | 'INITRAMFS_IMAGE_NAME', | ||
415 | 'INITRAMFS_IMAGE', | ||
416 | 'KERNEL_DEPLOYSUBDIR', | ||
417 | 'KERNEL_DEVICETREE', | ||
418 | 'KERNEL_FIT_LINK_NAME', | ||
419 | 'MACHINE', | ||
420 | 'PREFERRED_PROVIDER_virtual/dtb', | ||
421 | 'UBOOT_ARCH', | ||
422 | 'UBOOT_ENTRYPOINT', | ||
423 | 'UBOOT_LOADADDRESS', | ||
424 | 'UBOOT_MKIMAGE_KERNEL_TYPE', | ||
425 | 'UBOOT_MKIMAGE_SIGN_ARGS', | ||
426 | 'UBOOT_RD_ENTRYPOINT', | ||
427 | 'UBOOT_RD_LOADADDRESS', | ||
428 | 'UBOOT_SIGN_ENABLE', | ||
429 | 'UBOOT_SIGN_IMG_KEYNAME', | ||
430 | 'UBOOT_SIGN_KEYDIR', | ||
431 | 'UBOOT_SIGN_KEYNAME', | ||
432 | } | ||
433 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), self.kernel_recipe) | ||
434 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) | ||
435 | return bb_vars | ||
436 | |||
437 | def _config_add_kernel_classes(self, config): | ||
438 | config += '# Use kernel-fit-extra-artifacts.bbclass for the creation of the vmlinux artifact' + os.linesep | ||
439 | config += 'KERNEL_CLASSES = "kernel-fit-extra-artifacts"' + os.linesep | ||
440 | return config | ||
441 | |||
442 | @property | ||
443 | def kernel_recipe(self): | ||
444 | return "linux-yocto-fitimage" | ||
445 | |||
446 | def _config_add_uboot_env(self, config): | ||
447 | """Generate an u-boot environment | ||
11 | 448 | ||
12 | class FitImageTests(OESelftestTestCase): | 449 | Create a boot.cmd file that is packed into the FIT image as a source-able text file. |
450 | Updates the configuration to include the boot.cmd file. | ||
451 | """ | ||
452 | fit_uenv_file = "boot.cmd" | ||
453 | test_files_dir = "test-files" | ||
454 | fit_uenv_path = os.path.join(self.builddir, test_files_dir, fit_uenv_file) | ||
455 | |||
456 | config += '# Add an u-boot script to the fitImage' + os.linesep | ||
457 | config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep | ||
458 | config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep | ||
459 | config += 'SRC_URI:append:pn-%s = " file://${FIT_UBOOT_ENV}"' % self.kernel_recipe + os.linesep | ||
460 | |||
461 | if not os.path.isdir(test_files_dir): | ||
462 | os.makedirs(test_files_dir) | ||
463 | self.logger.debug("Writing to: %s" % fit_uenv_path) | ||
464 | with open(fit_uenv_path, "w") as f: | ||
465 | f.write('echo "hello world"') | ||
466 | |||
467 | return config | ||
468 | |||
469 | def _bitbake_fit_image(self, bb_vars): | ||
470 | """Bitbake the kernel and return the paths to the its file and the FIT image""" | ||
471 | bitbake(self.kernel_recipe) | ||
472 | |||
473 | # Find the right its file and the final fitImage and check if both files are available | ||
474 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | ||
475 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] | ||
476 | initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE'] | ||
477 | initramfs_image_name = bb_vars['INITRAMFS_IMAGE_NAME'] | ||
478 | kernel_fit_link_name = bb_vars['KERNEL_FIT_LINK_NAME'] | ||
479 | if not initramfs_image and initramfs_image_bundle != "1": | ||
480 | fitimage_its_name = "fitImage-its-%s" % kernel_fit_link_name | ||
481 | fitimage_name = "fitImage" | ||
482 | elif initramfs_image and initramfs_image_bundle != "1": | ||
483 | fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name) | ||
484 | fitimage_name = "fitImage-%s-%s" % (initramfs_image_name, kernel_fit_link_name) | ||
485 | elif initramfs_image and initramfs_image_bundle == "1": | ||
486 | fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name) | ||
487 | fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT} | ||
488 | else: | ||
489 | self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE') | ||
490 | kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR'] | ||
491 | if kernel_deploysubdir: | ||
492 | fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_its_name)) | ||
493 | fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_name)) | ||
494 | else: | ||
495 | fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name)) | ||
496 | fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name)) | ||
497 | return (fitimage_its_path, fitimage_path) | ||
498 | |||
499 | def _get_req_its_paths(self, bb_vars): | ||
500 | """Generate a list of expected paths in the its file | ||
501 | |||
502 | Example: | ||
503 | [ | ||
504 | ['/', 'images', 'kernel-1', 'hash-1'], | ||
505 | ['/', 'images', 'kernel-1', 'signature-1'], | ||
506 | ] | ||
507 | """ | ||
508 | dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) | ||
509 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | ||
510 | fit_uboot_env = bb_vars['FIT_UBOOT_ENV'] | ||
511 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] | ||
512 | initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE'] | ||
513 | uboot_sign_enable = bb_vars.get('UBOOT_SIGN_ENABLE') | ||
514 | |||
515 | # image nodes | ||
516 | images = [ 'kernel-1' ] | ||
517 | if dtb_files: | ||
518 | images += [ 'fdt-' + dtb for dtb in dtb_files ] | ||
519 | if fit_uboot_env: | ||
520 | images.append('bootscr-' + fit_uboot_env) | ||
521 | if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if | ||
522 | images.append('setup-1') | ||
523 | if initramfs_image and initramfs_image_bundle != "1": | ||
524 | images.append('ramdisk-1') | ||
525 | |||
526 | # configuration nodes (one per DTB and also one per symlink) | ||
527 | if dtb_files: | ||
528 | configurations = [bb_vars['FIT_CONF_PREFIX'] + conf for conf in dtb_files + dtb_symlinks] | ||
529 | else: | ||
530 | configurations = [bb_vars['FIT_CONF_PREFIX'] + '1'] | ||
531 | |||
532 | # Create a list of paths for all image and configuration nodes | ||
533 | req_its_paths = [] | ||
534 | for image in images: | ||
535 | req_its_paths.append(['/', 'images', image, 'hash-1']) | ||
536 | if uboot_sign_enable == "1" and fit_sign_individual == "1": | ||
537 | req_its_paths.append(['/', 'images', image, 'signature-1']) | ||
538 | for configuration in configurations: | ||
539 | req_its_paths.append(['/', 'configurations', configuration, 'hash-1']) | ||
540 | if uboot_sign_enable == "1": | ||
541 | req_its_paths.append(['/', 'configurations', configuration, 'signature-1']) | ||
542 | return req_its_paths | ||
543 | |||
544 | def _get_req_its_fields(self, bb_vars): | ||
545 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] | ||
546 | initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE'] | ||
547 | uboot_rd_loadaddress = bb_vars.get('UBOOT_RD_LOADADDRESS') | ||
548 | uboot_rd_entrypoint = bb_vars.get('UBOOT_RD_ENTRYPOINT') | ||
549 | |||
550 | its_field_check = [ | ||
551 | 'description = "%s";' % bb_vars['FIT_DESC'], | ||
552 | 'description = "Linux kernel";', | ||
553 | 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";', | ||
554 | # 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal... | ||
555 | 'data = /incbin/("linux.bin");', | ||
556 | 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";', | ||
557 | 'os = "linux";', | ||
558 | 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;', | ||
559 | 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;', | ||
560 | ] | ||
561 | if initramfs_image and initramfs_image_bundle != "1": | ||
562 | its_field_check.append('type = "ramdisk";') | ||
563 | if uboot_rd_loadaddress: | ||
564 | its_field_check.append("load = <%s>;" % uboot_rd_loadaddress) | ||
565 | if uboot_rd_entrypoint: | ||
566 | its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint) | ||
567 | |||
568 | fit_conf_default_dtb = bb_vars.get('FIT_CONF_DEFAULT_DTB') | ||
569 | if fit_conf_default_dtb: | ||
570 | fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-") | ||
571 | its_field_check.append('default = "' + fit_conf_prefix + fit_conf_default_dtb + '";') | ||
572 | |||
573 | its_field_check.append('kernel = "kernel-1";') | ||
574 | |||
575 | if initramfs_image and initramfs_image_bundle != "1": | ||
576 | its_field_check.append('ramdisk = "ramdisk-1";') | ||
577 | |||
578 | return its_field_check | ||
579 | |||
580 | def _get_req_sigvalues_config(self, bb_vars): | ||
581 | """Generate a dictionary of expected configuration signature nodes""" | ||
582 | if bb_vars.get('UBOOT_SIGN_ENABLE') != "1": | ||
583 | return {} | ||
584 | sign_images = '"kernel", "fdt"' | ||
585 | if bb_vars['INITRAMFS_IMAGE'] and bb_vars['INITRAMFS_IMAGE_BUNDLE'] != "1": | ||
586 | sign_images += ', "ramdisk"' | ||
587 | if bb_vars['FIT_UBOOT_ENV']: | ||
588 | sign_images += ', "bootscr"' | ||
589 | req_sigvalues_config = { | ||
590 | 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']), | ||
591 | 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_KEYNAME'], | ||
592 | 'sign-images': sign_images, | ||
593 | } | ||
594 | return req_sigvalues_config | ||
595 | |||
596 | def _get_req_sigvalues_image(self, bb_vars): | ||
597 | """Generate a dictionary of expected image signature nodes""" | ||
598 | if bb_vars['FIT_SIGN_INDIVIDUAL'] != "1": | ||
599 | return {} | ||
600 | req_sigvalues_image = { | ||
601 | 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']), | ||
602 | 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_IMG_KEYNAME'], | ||
603 | } | ||
604 | return req_sigvalues_image | ||
605 | |||
606 | def _get_req_sections(self, bb_vars): | ||
607 | """Generate a dictionary of expected sections in the output of dumpimage""" | ||
608 | dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) | ||
609 | fit_hash_alg = bb_vars['FIT_HASH_ALG'] | ||
610 | fit_sign_alg = bb_vars['FIT_SIGN_ALG'] | ||
611 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | ||
612 | fit_uboot_env = bb_vars['FIT_UBOOT_ENV'] | ||
613 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] | ||
614 | initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE'] | ||
615 | uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE'] | ||
616 | uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | ||
617 | uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME'] | ||
618 | num_signatures = 0 | ||
619 | req_sections = { | ||
620 | "kernel-1": { | ||
621 | "Type": "Kernel Image", | ||
622 | "OS": "Linux", | ||
623 | "Load Address": bb_vars['UBOOT_LOADADDRESS'], | ||
624 | "Entry Point": bb_vars['UBOOT_ENTRYPOINT'], | ||
625 | } | ||
626 | } | ||
627 | # Create one section per DTB | ||
628 | for dtb in dtb_files: | ||
629 | req_sections['fdt-' + dtb] = { | ||
630 | "Type": "Flat Device Tree", | ||
631 | } | ||
632 | # Add a script section if there is a script | ||
633 | if fit_uboot_env: | ||
634 | req_sections['bootscr-' + fit_uboot_env] = { "Type": "Script" } | ||
635 | # Add the initramfs | ||
636 | if initramfs_image and initramfs_image_bundle != "1": | ||
637 | req_sections['ramdisk-1'] = { | ||
638 | "Type": "RAMDisk Image", | ||
639 | "Load Address": bb_vars['UBOOT_RD_LOADADDRESS'], | ||
640 | "Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT'] | ||
641 | } | ||
642 | # Create a configuration section for each DTB | ||
643 | if dtb_files: | ||
644 | for dtb in dtb_files + dtb_symlinks: | ||
645 | conf_name = bb_vars['FIT_CONF_PREFIX'] + dtb | ||
646 | # Assume that DTBs with an "-alias" in its name are symlink DTBs created e.g. by the | ||
647 | # bbb-dtbs-as-ext test recipe. Make the configuration node pointing to the real DTB. | ||
648 | real_dtb = dtb.replace("-alias", "") | ||
649 | # dtb overlays do not refer to a kernel (yet?) | ||
650 | if dtb.endswith('.dtbo'): | ||
651 | req_sections[conf_name] = { | ||
652 | "FDT": 'fdt-' + real_dtb, | ||
653 | } | ||
654 | else: | ||
655 | req_sections[conf_name] = { | ||
656 | "Kernel": "kernel-1", | ||
657 | "FDT": 'fdt-' + real_dtb, | ||
658 | } | ||
659 | if initramfs_image and initramfs_image_bundle != "1": | ||
660 | req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1" | ||
661 | else: | ||
662 | conf_name = bb_vars['FIT_CONF_PREFIX'] + '1' | ||
663 | req_sections[conf_name] = { | ||
664 | "Kernel": "kernel-1" | ||
665 | } | ||
666 | if initramfs_image and initramfs_image_bundle != "1": | ||
667 | req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1" | ||
668 | |||
669 | # Add signing related properties if needed | ||
670 | if uboot_sign_enable == "1": | ||
671 | for section in req_sections: | ||
672 | req_sections[section]['Hash algo'] = fit_hash_alg | ||
673 | if section.startswith(bb_vars['FIT_CONF_PREFIX']): | ||
674 | req_sections[section]['Hash value'] = "unavailable" | ||
675 | req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) | ||
676 | num_signatures += 1 | ||
677 | elif fit_sign_individual == "1": | ||
678 | req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname) | ||
679 | num_signatures += 1 | ||
680 | return (req_sections, num_signatures) | ||
681 | |||
682 | def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path): | ||
683 | """Verify the signature nodes in the FIT image""" | ||
684 | if bb_vars['UBOOT_SIGN_ENABLE'] == "1": | ||
685 | self.logger.debug("Verifying signatures in the FIT image") | ||
686 | else: | ||
687 | self.logger.debug("FIT image is not signed. Signature verification is not needed.") | ||
688 | return | ||
689 | |||
690 | fit_hash_alg = bb_vars['FIT_HASH_ALG'] | ||
691 | fit_sign_alg = bb_vars['FIT_SIGN_ALG'] | ||
692 | uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME'] | ||
693 | uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | ||
694 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | ||
695 | kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR'] | ||
696 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | ||
697 | fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg] | ||
698 | fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg] | ||
699 | for section, values in sections.items(): | ||
700 | # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1") | ||
701 | if section.startswith(bb_vars['FIT_CONF_PREFIX']): | ||
702 | sign_algo = values.get('Sign algo', None) | ||
703 | req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) | ||
704 | self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section) | ||
705 | sign_value = values.get('Sign value', None) | ||
706 | self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section) | ||
707 | dtb_file_name = section.replace(bb_vars['FIT_CONF_PREFIX'], '') | ||
708 | dtb_path = os.path.join(deploy_dir_image, dtb_file_name) | ||
709 | if kernel_deploysubdir: | ||
710 | dtb_path = os.path.join(deploy_dir_image, kernel_deploysubdir, dtb_file_name) | ||
711 | # External devicetrees created by devicetree.bbclass are in a subfolder and have priority | ||
712 | dtb_path_ext = os.path.join(deploy_dir_image, "devicetree", dtb_file_name) | ||
713 | if os.path.exists(dtb_path_ext): | ||
714 | dtb_path = dtb_path_ext | ||
715 | self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section) | ||
716 | else: | ||
717 | # Image nodes always need a hash which gets indirectly signed by the config signature | ||
718 | hash_algo = values.get('Hash algo', None) | ||
719 | self.assertEqual(hash_algo, fit_hash_alg) | ||
720 | hash_value = values.get('Hash value', None) | ||
721 | self.assertEqual(len(hash_value), fit_hash_alg_len, 'Hash value for section %s not expected length' % section) | ||
722 | # Optionally, if FIT_SIGN_INDIVIDUAL = 1 also the image nodes have a signature (which is redundant but possible) | ||
723 | if fit_sign_individual == "1": | ||
724 | sign_algo = values.get('Sign algo', None) | ||
725 | req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname) | ||
726 | self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section) | ||
727 | sign_value = values.get('Sign value', None) | ||
728 | self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section) | ||
729 | |||
730 | # Search for the string passed to mkimage in each signed section of the FIT image. | ||
731 | # Looks like mkimage supports to add a comment but does not support to read it back. | ||
732 | a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['UBOOT_MKIMAGE_SIGN_ARGS']) | ||
733 | self.logger.debug("a_comment: %s" % a_comment) | ||
734 | if a_comment: | ||
735 | found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment) | ||
736 | self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." % | ||
737 | (num_signatures, a_comment)) | ||
738 | |||
739 | class KernelFitImageRecipeTests(KernelFitImageBase): | ||
740 | """Test cases for the kernel-fitimage bbclass""" | ||
13 | 741 | ||
14 | def test_fit_image(self): | 742 | def test_fit_image(self): |
15 | """ | 743 | """ |
@@ -25,10 +753,7 @@ class FitImageTests(OESelftestTestCase): | |||
25 | Author: Usama Arif <usama.arif@arm.com> | 753 | Author: Usama Arif <usama.arif@arm.com> |
26 | """ | 754 | """ |
27 | config = """ | 755 | config = """ |
28 | # Enable creation of fitImage | ||
29 | KERNEL_IMAGETYPE = "Image" | 756 | KERNEL_IMAGETYPE = "Image" |
30 | KERNEL_IMAGETYPES += " fitImage " | ||
31 | KERNEL_CLASSES = " kernel-fitimage " | ||
32 | 757 | ||
33 | # RAM disk variables including load address and entrypoint for kernel and RAM disk | 758 | # RAM disk variables including load address and entrypoint for kernel and RAM disk |
34 | IMAGE_FSTYPES += "cpio.gz" | 759 | IMAGE_FSTYPES += "cpio.gz" |
@@ -40,79 +765,145 @@ UBOOT_RD_ENTRYPOINT = "0x88000000" | |||
40 | UBOOT_LOADADDRESS = "0x80080000" | 765 | UBOOT_LOADADDRESS = "0x80080000" |
41 | UBOOT_ENTRYPOINT = "0x80080000" | 766 | UBOOT_ENTRYPOINT = "0x80080000" |
42 | FIT_DESC = "A model description" | 767 | FIT_DESC = "A model description" |
768 | FIT_CONF_PREFIX = "foo-" | ||
43 | """ | 769 | """ |
770 | config = self._config_add_kernel_classes(config) | ||
44 | self.write_config(config) | 771 | self.write_config(config) |
772 | bb_vars = self._fit_get_bb_vars() | ||
773 | self._test_fitimage(bb_vars) | ||
45 | 774 | ||
46 | # fitImage is created as part of linux recipe | 775 | def test_get_compatible_from_dtb(self): |
47 | image = "virtual/kernel" | 776 | """Test the oe.fitimage.get_compatible_from_dtb function |
48 | bitbake(image) | ||
49 | bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'INITRAMFS_IMAGE_NAME', 'KERNEL_FIT_LINK_NAME'], image) | ||
50 | |||
51 | fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], | ||
52 | "fitImage-its-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME'])) | ||
53 | fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], | ||
54 | "fitImage-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME'])) | ||
55 | |||
56 | self.assertTrue(os.path.exists(fitimage_its_path), | ||
57 | "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
58 | self.assertTrue(os.path.exists(fitimage_path), | ||
59 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
60 | |||
61 | # Check that the type, load address, entrypoint address and default | ||
62 | # values for kernel and ramdisk in Image Tree Source are as expected. | ||
63 | # The order of fields in the below array is important. Not all the | ||
64 | # fields are tested, only the key fields that wont vary between | ||
65 | # different architectures. | ||
66 | its_field_check = [ | ||
67 | 'description = "A model description";', | ||
68 | 'type = "kernel";', | ||
69 | 'load = <0x80080000>;', | ||
70 | 'entry = <0x80080000>;', | ||
71 | 'type = "ramdisk";', | ||
72 | 'load = <0x88000000>;', | ||
73 | 'entry = <0x88000000>;', | ||
74 | 'default = "conf-1";', | ||
75 | 'kernel = "kernel-1";', | ||
76 | 'ramdisk = "ramdisk-1";' | ||
77 | ] | ||
78 | 777 | ||
79 | with open(fitimage_its_path) as its_file: | 778 | 1. bitbake bbb-dtbs-as-ext |
80 | field_index = 0 | 779 | 2. Check if symlink_points_below returns the path to the DTB |
81 | for line in its_file: | 780 | 3. Check if the expected compatible string is found by get_compatible_from_dtb() |
82 | if field_index == len(its_field_check): | 781 | """ |
83 | break | 782 | DTB_RECIPE = "bbb-dtbs-as-ext" |
84 | if its_field_check[field_index] in line: | 783 | DTB_FILE = "am335x-bonegreen-ext.dtb" |
85 | field_index +=1 | 784 | DTB_SYMLINK = "am335x-bonegreen-ext-alias.dtb" |
785 | DTBO_FILE = "BBORG_RELAY-00A2.dtbo" | ||
786 | EXPECTED_COMP = ["ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"] | ||
86 | 787 | ||
87 | if field_index != len(its_field_check): # if its equal, the test passed | 788 | config = """ |
88 | self.assertTrue(field_index == len(its_field_check), | 789 | DISTRO = "poky" |
89 | "Fields in Image Tree Source File %s did not match, error in finding %s" | 790 | MACHINE = "beaglebone-yocto" |
90 | % (fitimage_its_path, its_field_check[field_index])) | 791 | """ |
792 | self.write_config(config) | ||
793 | |||
794 | # Provide the fdtget command called by get_compatible_from_dtb | ||
795 | dtc_bindir = FitImageTestCase._setup_native('dtc-native') | ||
796 | fdtget_path = os.path.join(dtc_bindir, "fdtget") | ||
797 | self.assertExists(fdtget_path) | ||
798 | |||
799 | # bitbake an external DTB with a symlink to it and a DTB overlay | ||
800 | bitbake(DTB_RECIPE) | ||
801 | deploy_dir_image = get_bb_var("DEPLOY_DIR_IMAGE", DTB_RECIPE) | ||
802 | devicetree_dir = os.path.join(deploy_dir_image, "devicetree") | ||
803 | dtb_path = os.path.join(devicetree_dir, DTB_FILE) | ||
804 | dtb_alias_path = os.path.join(devicetree_dir, DTB_SYMLINK) | ||
805 | dtbo_file = os.path.join(devicetree_dir, DTBO_FILE) | ||
806 | self.assertExists(dtb_path) | ||
807 | self.assertExists(dtb_alias_path) | ||
808 | self.assertExists(dtbo_file) | ||
809 | |||
810 | # Test symlink_points_below | ||
811 | linked_dtb = oe.fitimage.symlink_points_below(dtb_alias_path, devicetree_dir) | ||
812 | self.assertEqual(linked_dtb, DTB_FILE) | ||
813 | |||
814 | # Check if get_compatible_from_dtb finds the expected compatible string in the DTBs | ||
815 | comp = oe.fitimage.get_compatible_from_dtb(dtb_path, fdtget_path) | ||
816 | self.assertEqual(comp, EXPECTED_COMP) | ||
817 | comp_alias = oe.fitimage.get_compatible_from_dtb(dtb_alias_path, fdtget_path) | ||
818 | self.assertEqual(comp_alias, EXPECTED_COMP) | ||
819 | # The alias is a symlink, therefore the compatible string is equal | ||
820 | self.assertEqual(comp_alias, comp) | ||
821 | |||
822 | def test_fit_image_ext_dtb_dtbo(self): | ||
823 | """ | ||
824 | Summary: Check if FIT image and Image Tree Source (its) are created correctly. | ||
825 | Expected: 1) its and FIT image are built successfully | ||
826 | 2) The its file contains also the external devicetree overlay | ||
827 | 3) Dumping the FIT image indicates the devicetree overlay | ||
828 | """ | ||
829 | config = """ | ||
830 | # Enable creation of fitImage | ||
831 | MACHINE = "beaglebone-yocto" | ||
832 | # Add a devicetree overlay which does not need kernel sources | ||
833 | PREFERRED_PROVIDER_virtual/dtb = "bbb-dtbs-as-ext" | ||
834 | """ | ||
835 | config = self._config_add_kernel_classes(config) | ||
836 | config = self._config_add_uboot_env(config) | ||
837 | self.write_config(config) | ||
838 | bb_vars = self._fit_get_bb_vars() | ||
839 | self._test_fitimage(bb_vars) | ||
840 | |||
841 | |||
842 | def test_sign_fit_image_configurations(self): | ||
843 | """ | ||
844 | Summary: Check if FIT image and Image Tree Source (its) are created | ||
845 | and the configuration nodes are signed correctly. | ||
846 | Expected: 1) its and FIT image are built successfully | ||
847 | 2) Scanning the its file indicates signing is enabled | ||
848 | as requested by UBOOT_SIGN_ENABLE | ||
849 | 3) Dumping the FIT image indicates signature values | ||
850 | are present (only for the configuration nodes as | ||
851 | FIT_SIGN_INDIVIDUAL is disabled) | ||
852 | 4) Verify the FIT image contains the comments passed via | ||
853 | UBOOT_MKIMAGE_SIGN_ARGS once per configuration node. | ||
854 | """ | ||
855 | # Generate a configuration section which gets included into the local.conf file | ||
856 | config = """ | ||
857 | # Enable creation of fitImage | ||
858 | MACHINE = "beaglebone-yocto" | ||
859 | UBOOT_SIGN_ENABLE = "1" | ||
860 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | ||
861 | UBOOT_SIGN_KEYNAME = "dev" | ||
862 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | ||
863 | FIT_CONF_DEFAULT_DTB = "am335x-bonegreen.dtb" | ||
864 | """ | ||
865 | config = self._config_add_kernel_classes(config) | ||
866 | config = self._config_add_uboot_env(config) | ||
867 | self.write_config(config) | ||
868 | |||
869 | # Retrieve some variables from bitbake | ||
870 | bb_vars = self._fit_get_bb_vars([ | ||
871 | 'FIT_KEY_GENRSA_ARGS', | ||
872 | 'FIT_KEY_REQ_ARGS', | ||
873 | 'FIT_KEY_SIGN_PKCS', | ||
874 | 'FIT_SIGN_NUMBITS', | ||
875 | 'UBOOT_SIGN_KEYDIR', | ||
876 | ]) | ||
91 | 877 | ||
878 | self._gen_signing_key(bb_vars) | ||
879 | self._test_fitimage(bb_vars) | ||
92 | 880 | ||
93 | def test_sign_fit_image(self): | 881 | def test_sign_fit_image_individual(self): |
94 | """ | 882 | """ |
95 | Summary: Check if FIT image and Image Tree Source (its) are created | 883 | Summary: Check if FIT image and Image Tree Source (its) are created |
96 | and signed correctly. | 884 | and all nodes are signed correctly. |
97 | Expected: 1) its and FIT image are built successfully | 885 | Expected: 1) its and FIT image are built successfully |
98 | 2) Scanning the its file indicates signing is enabled | 886 | 2) Scanning the its file indicates signing is enabled |
99 | as requested by UBOOT_SIGN_ENABLE (using keys generated | 887 | as requested by UBOOT_SIGN_ENABLE |
100 | via FIT_GENERATE_KEYS) | ||
101 | 3) Dumping the FIT image indicates signature values | 888 | 3) Dumping the FIT image indicates signature values |
102 | are present (including for images as enabled via | 889 | are present (including for images as enabled via |
103 | FIT_SIGN_INDIVIDUAL) | 890 | FIT_SIGN_INDIVIDUAL) |
104 | 4) Examination of the do_assemble_fitimage runfile/logfile | 891 | This also implies that FIT_GENERATE_KEYS = "1" works. |
105 | indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and | 892 | 4) Verify the FIT image contains the comments passed via |
106 | UBOOT_MKIMAGE_SIGN_ARGS are working as expected. | 893 | UBOOT_MKIMAGE_SIGN_ARGS once per image and per |
894 | configuration node. | ||
895 | Note: This test is mostly for backward compatibility. | ||
896 | The recommended approach is to sign the configuration nodes | ||
897 | which include also the hashes of all the images. Signing | ||
898 | all the images individually is therefore redundant. | ||
107 | Product: oe-core | 899 | Product: oe-core |
108 | Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon | 900 | Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon |
109 | work by Usama Arif <usama.arif@arm.com> | 901 | work by Usama Arif <usama.arif@arm.com> |
110 | """ | 902 | """ |
903 | # Generate a configuration section which gets included into the local.conf file | ||
111 | config = """ | 904 | config = """ |
112 | # Enable creation of fitImage | 905 | # Enable creation of fitImage |
113 | MACHINE = "beaglebone-yocto" | 906 | MACHINE = "beaglebone-yocto" |
114 | KERNEL_IMAGETYPES += " fitImage " | ||
115 | KERNEL_CLASSES = " kernel-fitimage test-mkimage-wrapper " | ||
116 | UBOOT_SIGN_ENABLE = "1" | 907 | UBOOT_SIGN_ENABLE = "1" |
117 | FIT_GENERATE_KEYS = "1" | 908 | FIT_GENERATE_KEYS = "1" |
118 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | 909 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" |
@@ -121,211 +912,494 @@ UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | |||
121 | FIT_SIGN_INDIVIDUAL = "1" | 912 | FIT_SIGN_INDIVIDUAL = "1" |
122 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | 913 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" |
123 | """ | 914 | """ |
915 | config = self._config_add_kernel_classes(config) | ||
916 | config = self._config_add_uboot_env(config) | ||
124 | self.write_config(config) | 917 | self.write_config(config) |
918 | bb_vars = self._fit_get_bb_vars() | ||
125 | 919 | ||
126 | # fitImage is created as part of linux recipe | 920 | # Ensure new keys are generated and FIT_GENERATE_KEYS = "1" is tested |
127 | image = "virtual/kernel" | 921 | bitbake("kernel-signing-keys-native -c compile -f") |
128 | bitbake(image) | ||
129 | bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'KERNEL_FIT_LINK_NAME'], image) | ||
130 | |||
131 | fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], | ||
132 | "fitImage-its-%s" % (bb_vars['KERNEL_FIT_LINK_NAME'])) | ||
133 | fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], | ||
134 | "fitImage-%s.bin" % (bb_vars['KERNEL_FIT_LINK_NAME'])) | ||
135 | |||
136 | self.assertTrue(os.path.exists(fitimage_its_path), | ||
137 | "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
138 | self.assertTrue(os.path.exists(fitimage_path), | ||
139 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
140 | |||
141 | req_itspaths = [ | ||
142 | ['/', 'images', 'kernel-1'], | ||
143 | ['/', 'images', 'kernel-1', 'signature-1'], | ||
144 | ['/', 'images', 'fdt-am335x-boneblack.dtb'], | ||
145 | ['/', 'images', 'fdt-am335x-boneblack.dtb', 'signature-1'], | ||
146 | ['/', 'configurations', 'conf-am335x-boneblack.dtb'], | ||
147 | ['/', 'configurations', 'conf-am335x-boneblack.dtb', 'signature-1'], | ||
148 | ] | ||
149 | 922 | ||
150 | itspath = [] | 923 | self._test_fitimage(bb_vars) |
151 | itspaths = [] | ||
152 | linect = 0 | ||
153 | sigs = {} | ||
154 | with open(fitimage_its_path) as its_file: | ||
155 | linect += 1 | ||
156 | for line in its_file: | ||
157 | line = line.strip() | ||
158 | if line.endswith('};'): | ||
159 | itspath.pop() | ||
160 | elif line.endswith('{'): | ||
161 | itspath.append(line[:-1].strip()) | ||
162 | itspaths.append(itspath[:]) | ||
163 | elif itspath and itspath[-1] == 'signature-1': | ||
164 | itsdotpath = '.'.join(itspath) | ||
165 | if not itsdotpath in sigs: | ||
166 | sigs[itsdotpath] = {} | ||
167 | if not '=' in line or not line.endswith(';'): | ||
168 | self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line)) | ||
169 | key, value = line.split('=', 1) | ||
170 | sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';') | ||
171 | 924 | ||
172 | for reqpath in req_itspaths: | 925 | def test_fit_image_sign_initramfs(self): |
173 | if not reqpath in itspaths: | ||
174 | self.fail('Missing section in its file: %s' % reqpath) | ||
175 | |||
176 | reqsigvalues_image = { | ||
177 | 'algo': '"sha256,rsa2048"', | ||
178 | 'key-name-hint': '"img-oe-selftest"', | ||
179 | } | ||
180 | reqsigvalues_config = { | ||
181 | 'algo': '"sha256,rsa2048"', | ||
182 | 'key-name-hint': '"cfg-oe-selftest"', | ||
183 | 'sign-images': '"kernel", "fdt"', | ||
184 | } | ||
185 | |||
186 | for itspath, values in sigs.items(): | ||
187 | if 'conf-' in itspath: | ||
188 | reqsigvalues = reqsigvalues_config | ||
189 | else: | ||
190 | reqsigvalues = reqsigvalues_image | ||
191 | for reqkey, reqvalue in reqsigvalues.items(): | ||
192 | value = values.get(reqkey, None) | ||
193 | if value is None: | ||
194 | self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath)) | ||
195 | self.assertEqual(value, reqvalue) | ||
196 | |||
197 | # Dump the image to see if it really got signed | ||
198 | bitbake("u-boot-tools-native -c addto_recipe_sysroot") | ||
199 | result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') | ||
200 | recipe_sysroot_native = result.output.split('=')[1].strip('"') | ||
201 | dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') | ||
202 | result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) | ||
203 | in_signed = None | ||
204 | signed_sections = {} | ||
205 | for line in result.output.splitlines(): | ||
206 | if line.startswith((' Configuration', ' Image')): | ||
207 | in_signed = re.search(r'\((.*)\)', line).groups()[0] | ||
208 | elif re.match('^ *', line) in (' ', ''): | ||
209 | in_signed = None | ||
210 | elif in_signed: | ||
211 | if not in_signed in signed_sections: | ||
212 | signed_sections[in_signed] = {} | ||
213 | key, value = line.split(':', 1) | ||
214 | signed_sections[in_signed][key.strip()] = value.strip() | ||
215 | self.assertIn('kernel-1', signed_sections) | ||
216 | self.assertIn('fdt-am335x-boneblack.dtb', signed_sections) | ||
217 | self.assertIn('conf-am335x-boneblack.dtb', signed_sections) | ||
218 | for signed_section, values in signed_sections.items(): | ||
219 | value = values.get('Sign algo', None) | ||
220 | if signed_section.startswith("conf"): | ||
221 | self.assertEqual(value, 'sha256,rsa2048:cfg-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section) | ||
222 | else: | ||
223 | self.assertEqual(value, 'sha256,rsa2048:img-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section) | ||
224 | value = values.get('Sign value', None) | ||
225 | self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) | ||
226 | |||
227 | # Check for UBOOT_MKIMAGE_SIGN_ARGS | ||
228 | result = runCmd('bitbake -e virtual/kernel | grep ^T=') | ||
229 | tempdir = result.output.split('=', 1)[1].strip().strip('') | ||
230 | result = runCmd('grep "a smart comment" %s/run.do_assemble_fitimage' % tempdir, ignore_status=True) | ||
231 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN_ARGS value did not get used') | ||
232 | |||
233 | # Check for evidence of test-mkimage-wrapper class | ||
234 | result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True) | ||
235 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') | ||
236 | result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True) | ||
237 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') | ||
238 | |||
239 | def test_uboot_fit_image(self): | ||
240 | """ | 926 | """ |
241 | Summary: Check if Uboot FIT image and Image Tree Source | 927 | Summary: Verifies the content of the initramfs node in the FIT Image Tree Source (its) |
242 | (its) are built and the Image Tree Source has the | 928 | The FIT settings are set by the test case. |
243 | correct fields. | 929 | The machine used is beaglebone-yocto. |
244 | Expected: 1. u-boot-fitImage and u-boot-its can be built | 930 | Expected: 1. The ITS is generated with initramfs support |
245 | 2. The type, load address, entrypoint address and | 931 | 2. All the fields in the kernel node are as expected (matching the |
246 | default values of U-boot image are correct in the | 932 | conf settings) |
247 | Image Tree Source. Not all the fields are tested, | 933 | 3. The kernel is included in all the available configurations and |
248 | only the key fields that wont vary between | 934 | its hash is included in the configuration signature |
249 | different architectures. | 935 | |
250 | Product: oe-core | 936 | Product: oe-core |
251 | Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> | 937 | Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> |
252 | based on work by Usama Arif <usama.arif@arm.com> | ||
253 | """ | 938 | """ |
939 | |||
254 | config = """ | 940 | config = """ |
255 | # We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set | 941 | DISTRO = "poky" |
256 | MACHINE = "qemuarm" | 942 | MACHINE = "beaglebone-yocto" |
257 | UBOOT_MACHINE = "am57xx_evm_defconfig" | 943 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" |
258 | SPL_BINARY = "MLO" | 944 | INITRAMFS_SCRIPTS = "" |
945 | UBOOT_MACHINE = "am335x_evm_defconfig" | ||
946 | UBOOT_SIGN_ENABLE = "1" | ||
947 | UBOOT_SIGN_KEYNAME = "beaglebonekey" | ||
948 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" | ||
949 | UBOOT_DTB_BINARY = "u-boot.dtb" | ||
950 | UBOOT_ENTRYPOINT = "0x80000000" | ||
951 | UBOOT_LOADADDRESS = "0x80000000" | ||
952 | UBOOT_RD_LOADADDRESS = "0x88000000" | ||
953 | UBOOT_RD_ENTRYPOINT = "0x88000000" | ||
954 | UBOOT_DTB_LOADADDRESS = "0x82000000" | ||
955 | UBOOT_ARCH = "arm" | ||
956 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | ||
957 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" | ||
958 | UBOOT_EXTLINUX = "0" | ||
959 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" | ||
960 | FIT_KERNEL_COMP_ALG = "none" | ||
961 | FIT_HASH_ALG = "sha256" | ||
962 | """ | ||
963 | config = self._config_add_kernel_classes(config) | ||
964 | config = self._config_add_uboot_env(config) | ||
965 | self.write_config(config) | ||
259 | 966 | ||
260 | # Enable creation of the U-Boot fitImage | 967 | # Retrieve some variables from bitbake |
261 | UBOOT_FITIMAGE_ENABLE = "1" | 968 | bb_vars = self._fit_get_bb_vars([ |
969 | 'FIT_KEY_GENRSA_ARGS', | ||
970 | 'FIT_KEY_REQ_ARGS', | ||
971 | 'FIT_KEY_SIGN_PKCS', | ||
972 | 'FIT_SIGN_NUMBITS', | ||
973 | 'UBOOT_SIGN_KEYDIR', | ||
974 | ]) | ||
262 | 975 | ||
263 | # (U-boot) fitImage properties | 976 | self._gen_signing_key(bb_vars) |
264 | UBOOT_LOADADDRESS = "0x80080000" | 977 | self._test_fitimage(bb_vars) |
265 | UBOOT_ENTRYPOINT = "0x80080000" | 978 | |
266 | UBOOT_FIT_DESC = "A model description" | 979 | def test_fit_image_sign_initramfs_bundle(self): |
980 | """ | ||
981 | Summary: Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its) | ||
982 | The FIT settings are set by the test case. | ||
983 | The machine used is beaglebone-yocto. | ||
984 | Expected: 1. The ITS is generated with initramfs bundle support | ||
985 | 2. All the fields in the kernel node are as expected (matching the | ||
986 | conf settings) | ||
987 | 3. The kernel is included in all the available configurations and | ||
988 | its hash is included in the configuration signature | ||
989 | |||
990 | Product: oe-core | ||
991 | Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> | ||
992 | """ | ||
267 | 993 | ||
268 | # Enable creation of Kernel fitImage | 994 | config = """ |
269 | KERNEL_IMAGETYPES += " fitImage " | 995 | DISTRO = "poky" |
270 | KERNEL_CLASSES = " kernel-fitimage" | 996 | MACHINE = "beaglebone-yocto" |
997 | INITRAMFS_IMAGE_BUNDLE = "1" | ||
998 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" | ||
999 | INITRAMFS_SCRIPTS = "" | ||
1000 | UBOOT_MACHINE = "am335x_evm_defconfig" | ||
271 | UBOOT_SIGN_ENABLE = "1" | 1001 | UBOOT_SIGN_ENABLE = "1" |
272 | FIT_GENERATE_KEYS = "1" | 1002 | UBOOT_SIGN_KEYNAME = "beaglebonekey" |
273 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | 1003 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" |
274 | UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" | 1004 | UBOOT_DTB_BINARY = "u-boot.dtb" |
275 | UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | 1005 | UBOOT_ENTRYPOINT = "0x80000000" |
276 | FIT_SIGN_INDIVIDUAL = "1" | 1006 | UBOOT_LOADADDRESS = "0x80000000" |
1007 | UBOOT_DTB_LOADADDRESS = "0x82000000" | ||
1008 | UBOOT_ARCH = "arm" | ||
1009 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | ||
1010 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" | ||
1011 | UBOOT_EXTLINUX = "0" | ||
1012 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" | ||
1013 | FIT_KERNEL_COMP_ALG = "none" | ||
1014 | FIT_HASH_ALG = "sha256" | ||
277 | """ | 1015 | """ |
1016 | config = self._config_add_kernel_classes(config) | ||
1017 | config = self._config_add_uboot_env(config) | ||
278 | self.write_config(config) | 1018 | self.write_config(config) |
1019 | bb_vars = self._fit_get_bb_vars() | ||
1020 | self._gen_signing_key(bb_vars) | ||
1021 | self._test_fitimage(bb_vars) | ||
1022 | |||
1023 | class FitImagePyTests(KernelFitImageBase): | ||
1024 | """Test cases for the fitimage.py module without calling bitbake""" | ||
1025 | |||
1026 | def _test_fitimage_py(self, bb_vars_overrides=None): | ||
1027 | topdir = os.path.join(os.environ['BUILDDIR']) | ||
1028 | fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its') | ||
1029 | |||
1030 | # Provide variables without calling bitbake | ||
1031 | bb_vars = { | ||
1032 | # image-fitimage.conf | ||
1033 | 'FIT_DESC': "Kernel fitImage for a dummy distro", | ||
1034 | 'FIT_HASH_ALG': "sha256", | ||
1035 | 'FIT_SIGN_ALG': "rsa2048", | ||
1036 | 'FIT_PAD_ALG': "pkcs-1.5", | ||
1037 | 'FIT_GENERATE_KEYS': "0", | ||
1038 | 'FIT_SIGN_NUMBITS': "2048", | ||
1039 | 'FIT_KEY_GENRSA_ARGS': "-F4", | ||
1040 | 'FIT_KEY_REQ_ARGS': "-batch -new", | ||
1041 | 'FIT_KEY_SIGN_PKCS': "-x509", | ||
1042 | 'FIT_SIGN_INDIVIDUAL': "0", | ||
1043 | 'FIT_CONF_PREFIX': "conf-", | ||
1044 | 'FIT_SUPPORTED_INITRAMFS_FSTYPES': "cpio.lz4 cpio.lzo cpio.lzma cpio.xz cpio.zst cpio.gz ext2.gz cpio", | ||
1045 | 'FIT_CONF_DEFAULT_DTB': "", | ||
1046 | 'FIT_ADDRESS_CELLS': "1", | ||
1047 | 'FIT_UBOOT_ENV': "", | ||
1048 | # kernel.bbclass | ||
1049 | 'UBOOT_ENTRYPOINT': "0x20008000", | ||
1050 | 'UBOOT_LOADADDRESS': "0x20008000", | ||
1051 | 'INITRAMFS_IMAGE': "", | ||
1052 | 'INITRAMFS_IMAGE_BUNDLE': "", | ||
1053 | # kernel-uboot.bbclass | ||
1054 | 'FIT_KERNEL_COMP_ALG': "gzip", | ||
1055 | 'FIT_KERNEL_COMP_ALG_EXTENSION': ".gz", | ||
1056 | 'UBOOT_MKIMAGE_KERNEL_TYPE': "kernel", | ||
1057 | # uboot-config.bbclass | ||
1058 | 'UBOOT_MKIMAGE_DTCOPTS': "", | ||
1059 | 'UBOOT_MKIMAGE': "uboot-mkimage", | ||
1060 | 'UBOOT_MKIMAGE_SIGN': "uboot-mkimage", | ||
1061 | 'UBOOT_MKIMAGE_SIGN_ARGS': "", | ||
1062 | 'UBOOT_SIGN_ENABLE': "0", | ||
1063 | 'UBOOT_SIGN_KEYDIR': None, | ||
1064 | 'UBOOT_SIGN_KEYNAME': None, | ||
1065 | 'UBOOT_SIGN_IMG_KEYNAME': None, | ||
1066 | # others | ||
1067 | 'MACHINE': "qemux86-64", | ||
1068 | 'UBOOT_ARCH': "x86", | ||
1069 | 'HOST_PREFIX': "x86_64-poky-linux-" | ||
1070 | } | ||
1071 | if bb_vars_overrides: | ||
1072 | bb_vars.update(bb_vars_overrides) | ||
1073 | |||
1074 | root_node = oe.fitimage.ItsNodeRootKernel( | ||
1075 | bb_vars["FIT_DESC"], bb_vars["FIT_ADDRESS_CELLS"], | ||
1076 | bb_vars['HOST_PREFIX'], bb_vars['UBOOT_ARCH'], bb_vars["FIT_CONF_PREFIX"], | ||
1077 | oe.types.boolean(bb_vars['UBOOT_SIGN_ENABLE']), bb_vars["UBOOT_SIGN_KEYDIR"], | ||
1078 | bb_vars["UBOOT_MKIMAGE"], bb_vars["UBOOT_MKIMAGE_DTCOPTS"], | ||
1079 | bb_vars["UBOOT_MKIMAGE_SIGN"], bb_vars["UBOOT_MKIMAGE_SIGN_ARGS"], | ||
1080 | bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'], bb_vars['FIT_PAD_ALG'], | ||
1081 | bb_vars['UBOOT_SIGN_KEYNAME'], | ||
1082 | oe.types.boolean(bb_vars['FIT_SIGN_INDIVIDUAL']), bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | ||
1083 | ) | ||
1084 | |||
1085 | root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", "none", | ||
1086 | bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'), | ||
1087 | bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE'), bb_vars.get("UBOOT_ENTRYSYMBOL") | ||
1088 | ) | ||
1089 | |||
1090 | dtb_files, _ = FitImageTestCase._get_dtb_files(bb_vars) | ||
1091 | for dtb in dtb_files: | ||
1092 | root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb), | ||
1093 | bb_vars.get("UBOOT_DTB_LOADADDRESS"), bb_vars.get("UBOOT_DTBO_LOADADDRESS")) | ||
1094 | |||
1095 | if bb_vars.get('FIT_UBOOT_ENV'): | ||
1096 | root_node.fitimage_emit_section_boot_script( | ||
1097 | "bootscr-" + bb_vars['FIT_UBOOT_ENV'], bb_vars['FIT_UBOOT_ENV']) | ||
1098 | |||
1099 | if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if | ||
1100 | root_node.fitimage_emit_section_setup("setup-1", "setup1.bin") | ||
1101 | |||
1102 | if bb_vars.get('INITRAMFS_IMAGE') and bb_vars.get("INITRAMFS_IMAGE_BUNDLE") != "1": | ||
1103 | root_node.fitimage_emit_section_ramdisk("ramdisk-1", "a-dir/a-initramfs-1", | ||
1104 | "core-image-minimal-initramfs", | ||
1105 | bb_vars.get("UBOOT_RD_LOADADDRESS"), bb_vars.get("UBOOT_RD_ENTRYPOINT")) | ||
1106 | |||
1107 | root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB']) | ||
1108 | root_node.write_its_file(fitimage_its_path) | ||
1109 | |||
1110 | self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
1111 | self.logger.debug("Checking its: %s" % fitimage_its_path) | ||
1112 | self._check_its_file(bb_vars, fitimage_its_path) | ||
1113 | |||
1114 | def test_fitimage_py_default(self): | ||
1115 | self._test_fitimage_py() | ||
1116 | |||
1117 | def test_fitimage_py_default_dtb(self): | ||
1118 | bb_vars_overrides = { | ||
1119 | 'KERNEL_DEVICETREE': "one.dtb two.dtb three.dtb", | ||
1120 | 'FIT_CONF_DEFAULT_DTB': "two.dtb" | ||
1121 | } | ||
1122 | self._test_fitimage_py(bb_vars_overrides) | ||
1123 | |||
1124 | |||
1125 | class UBootFitImageTests(FitImageTestCase): | ||
1126 | """Test cases for the uboot-sign bbclass""" | ||
279 | 1127 | ||
280 | # The U-Boot fitImage is created as part of the U-Boot recipe | 1128 | BOOTLOADER_RECIPE = "virtual/bootloader" |
281 | bitbake("virtual/bootloader") | 1129 | |
282 | 1130 | def _fit_get_bb_vars(self, additional_vars=[]): | |
283 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | 1131 | """Get bb_vars as needed by _test_sign_fit_image |
284 | machine = get_bb_var('MACHINE') | 1132 | |
285 | fitimage_its_path = os.path.join(deploy_dir_image, | 1133 | Call the get_bb_vars function once and get all variables needed by the test case. |
286 | "u-boot-its-%s" % (machine,)) | 1134 | """ |
287 | fitimage_path = os.path.join(deploy_dir_image, | 1135 | internal_used = { |
288 | "u-boot-fitImage-%s" % (machine,)) | 1136 | 'DEPLOY_DIR_IMAGE', |
289 | 1137 | 'FIT_HASH_ALG', | |
290 | self.assertTrue(os.path.exists(fitimage_its_path), | 1138 | 'FIT_KEY_GENRSA_ARGS', |
291 | "%s image tree source doesn't exist" % (fitimage_its_path)) | 1139 | 'FIT_KEY_REQ_ARGS', |
292 | self.assertTrue(os.path.exists(fitimage_path), | 1140 | 'FIT_KEY_SIGN_PKCS', |
293 | "%s FIT image doesn't exist" % (fitimage_path)) | 1141 | 'FIT_SIGN_ALG', |
294 | 1142 | 'FIT_SIGN_INDIVIDUAL', | |
295 | # Check that the type, load address, entrypoint address and default | 1143 | 'FIT_SIGN_NUMBITS', |
296 | # values for kernel and ramdisk in Image Tree Source are as expected. | 1144 | 'MACHINE', |
297 | # The order of fields in the below array is important. Not all the | 1145 | 'SPL_MKIMAGE_SIGN_ARGS', |
298 | # fields are tested, only the key fields that wont vary between | 1146 | 'SPL_SIGN_ENABLE', |
299 | # different architectures. | 1147 | 'SPL_SIGN_KEYNAME', |
1148 | 'UBOOT_ARCH', | ||
1149 | 'UBOOT_DTB_BINARY', | ||
1150 | 'UBOOT_DTB_IMAGE', | ||
1151 | 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT', | ||
1152 | 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS', | ||
1153 | 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE', | ||
1154 | 'UBOOT_FIT_CONF_USER_LOADABLES', | ||
1155 | 'UBOOT_FIT_DESC', | ||
1156 | 'UBOOT_FIT_HASH_ALG', | ||
1157 | 'UBOOT_FIT_SIGN_ALG', | ||
1158 | 'UBOOT_FIT_TEE_ENTRYPOINT', | ||
1159 | 'UBOOT_FIT_TEE_LOADADDRESS', | ||
1160 | 'UBOOT_FIT_TEE', | ||
1161 | 'UBOOT_FIT_UBOOT_ENTRYPOINT', | ||
1162 | 'UBOOT_FIT_UBOOT_LOADADDRESS', | ||
1163 | 'UBOOT_FIT_USER_SETTINGS', | ||
1164 | 'UBOOT_FITIMAGE_ENABLE', | ||
1165 | 'UBOOT_NODTB_BINARY', | ||
1166 | 'UBOOT_SIGN_ENABLE', | ||
1167 | 'UBOOT_SIGN_IMG_KEYNAME', | ||
1168 | 'UBOOT_SIGN_KEYDIR', | ||
1169 | 'UBOOT_SIGN_KEYNAME', | ||
1170 | } | ||
1171 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), UBootFitImageTests.BOOTLOADER_RECIPE) | ||
1172 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) | ||
1173 | return bb_vars | ||
1174 | |||
1175 | def _bitbake_fit_image(self, bb_vars): | ||
1176 | """Bitbake the bootloader and return the paths to the its file and the FIT image""" | ||
1177 | bitbake(UBootFitImageTests.BOOTLOADER_RECIPE) | ||
1178 | |||
1179 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | ||
1180 | machine = bb_vars['MACHINE'] | ||
1181 | fitimage_its_path = os.path.join(deploy_dir_image, "u-boot-its-%s" % machine) | ||
1182 | fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % machine) | ||
1183 | return (fitimage_its_path, fitimage_path) | ||
1184 | |||
1185 | def _get_req_its_paths(self, bb_vars): | ||
1186 | # image nodes | ||
1187 | images = [ 'uboot', 'fdt', ] | ||
1188 | if bb_vars['UBOOT_FIT_TEE'] == "1": | ||
1189 | images.append('tee') | ||
1190 | if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1": | ||
1191 | images.append('atf') | ||
1192 | # if bb_vars['UBOOT_FIT_USER_SETTINGS']: | ||
1193 | |||
1194 | # configuration nodes | ||
1195 | configurations = [ 'conf'] | ||
1196 | |||
1197 | # Create a list of paths for all image and configuration nodes | ||
1198 | req_its_paths = [] | ||
1199 | for image in images: | ||
1200 | req_its_paths.append(['/', 'images', image]) | ||
1201 | if bb_vars['SPL_SIGN_ENABLE'] == "1": | ||
1202 | req_its_paths.append(['/', 'images', image, 'signature']) | ||
1203 | for configuration in configurations: | ||
1204 | req_its_paths.append(['/', 'configurations', configuration]) | ||
1205 | return req_its_paths | ||
1206 | |||
1207 | def _get_req_its_fields(self, bb_vars): | ||
1208 | loadables = ["uboot"] | ||
300 | its_field_check = [ | 1209 | its_field_check = [ |
301 | 'description = "A model description";', | 1210 | 'description = "%s";' % bb_vars['UBOOT_FIT_DESC'], |
1211 | 'description = "U-Boot image";', | ||
1212 | 'data = /incbin/("%s");' % bb_vars['UBOOT_NODTB_BINARY'], | ||
302 | 'type = "standalone";', | 1213 | 'type = "standalone";', |
303 | 'load = <0x80080000>;', | 1214 | 'os = "u-boot";', |
304 | 'entry = <0x80080000>;', | 1215 | 'arch = "%s";' % bb_vars['UBOOT_ARCH'], |
305 | 'default = "conf";', | 1216 | 'compression = "none";', |
306 | 'loadables = "uboot";', | 1217 | 'load = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'], |
307 | 'fdt = "fdt";' | 1218 | 'entry = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'], |
1219 | 'description = "U-Boot FDT";', | ||
1220 | 'data = /incbin/("%s");' % bb_vars['UBOOT_DTB_BINARY'], | ||
1221 | 'type = "flat_dt";', | ||
1222 | 'arch = "%s";' % bb_vars['UBOOT_ARCH'], | ||
1223 | 'compression = "none";', | ||
1224 | ] | ||
1225 | if bb_vars['UBOOT_FIT_TEE'] == "1": | ||
1226 | its_field_check += [ | ||
1227 | 'description = "Trusted Execution Environment";', | ||
1228 | 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_TEE_IMAGE'], | ||
1229 | 'type = "tee";', | ||
1230 | 'arch = "%s";' % bb_vars['UBOOT_ARCH'], | ||
1231 | 'os = "tee";', | ||
1232 | 'load = <%s>;' % bb_vars['UBOOT_FIT_TEE_LOADADDRESS'], | ||
1233 | 'entry = <%s>;' % bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'], | ||
1234 | 'compression = "none";', | ||
308 | ] | 1235 | ] |
1236 | loadables.insert(0, "tee") | ||
1237 | if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1": | ||
1238 | its_field_check += [ | ||
1239 | 'description = "ARM Trusted Firmware";', | ||
1240 | 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'], | ||
1241 | 'type = "firmware";', | ||
1242 | 'arch = "%s";' % bb_vars['UBOOT_ARCH'], | ||
1243 | 'os = "arm-trusted-firmware";', | ||
1244 | 'load = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'], | ||
1245 | 'entry = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'], | ||
1246 | 'compression = "none";', | ||
1247 | ] | ||
1248 | loadables.insert(0, "atf") | ||
1249 | its_field_check += [ | ||
1250 | 'default = "conf";', | ||
1251 | 'description = "Boot with signed U-Boot FIT";', | ||
1252 | 'loadables = "%s";' % '", "'.join(loadables), | ||
1253 | 'fdt = "fdt";', | ||
1254 | ] | ||
1255 | return its_field_check | ||
1256 | |||
1257 | def _get_req_sigvalues_config(self, bb_vars): | ||
1258 | # COnfigurations are not signed by uboot-sign | ||
1259 | return {} | ||
1260 | |||
1261 | def _get_req_sigvalues_image(self, bb_vars): | ||
1262 | if bb_vars['SPL_SIGN_ENABLE'] != "1": | ||
1263 | return {} | ||
1264 | req_sigvalues_image = { | ||
1265 | 'algo': '"%s,%s"' % (bb_vars['UBOOT_FIT_HASH_ALG'], bb_vars['UBOOT_FIT_SIGN_ALG']), | ||
1266 | 'key-name-hint': '"%s"' % bb_vars['SPL_SIGN_KEYNAME'], | ||
1267 | } | ||
1268 | return req_sigvalues_image | ||
309 | 1269 | ||
310 | with open(fitimage_its_path) as its_file: | 1270 | def _get_req_sections(self, bb_vars): |
311 | field_index = 0 | 1271 | """Generate the expected output of dumpimage for beaglebone targets |
312 | for line in its_file: | 1272 | |
313 | if field_index == len(its_field_check): | 1273 | The dict generated by this function is supposed to be compared against |
314 | break | 1274 | the dict which is generated by the _dump_fitimage function. |
315 | if its_field_check[field_index] in line: | 1275 | """ |
316 | field_index +=1 | 1276 | loadables = ['uboot'] |
1277 | req_sections = { | ||
1278 | "uboot": { | ||
1279 | "Type": "Standalone Program", | ||
1280 | "Load Address": bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'], | ||
1281 | "Entry Point": bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'], | ||
1282 | }, | ||
1283 | "fdt": { | ||
1284 | "Type": "Flat Device Tree", | ||
1285 | } | ||
1286 | } | ||
1287 | if bb_vars['UBOOT_FIT_TEE'] == "1": | ||
1288 | loadables.insert(0, "tee") | ||
1289 | req_sections['tee'] = { | ||
1290 | "Type": "Trusted Execution Environment Image", | ||
1291 | # "Load Address": bb_vars['UBOOT_FIT_TEE_LOADADDRESS'], not printed by mkimage? | ||
1292 | # "Entry Point": bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'], not printed by mkimage? | ||
1293 | } | ||
1294 | if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1": | ||
1295 | loadables.insert(0, "atf") | ||
1296 | req_sections['atf'] = { | ||
1297 | "Type": "Firmware", | ||
1298 | "Load Address": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'], | ||
1299 | # "Entry Point": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'], not printed by mkimage? | ||
1300 | } | ||
1301 | req_sections["conf"] = { | ||
1302 | "Kernel": "unavailable", | ||
1303 | "FDT": "fdt", | ||
1304 | "Loadables": ','.join(loadables), | ||
1305 | } | ||
1306 | |||
1307 | # Add signing related properties if needed | ||
1308 | uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG'] | ||
1309 | uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG'] | ||
1310 | spl_sign_enable = bb_vars['SPL_SIGN_ENABLE'] | ||
1311 | spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME'] | ||
1312 | num_signatures = 0 | ||
1313 | if spl_sign_enable == "1": | ||
1314 | for section in req_sections: | ||
1315 | if not section.startswith('conf'): | ||
1316 | req_sections[section]['Sign algo'] = "%s,%s:%s" % \ | ||
1317 | (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname) | ||
1318 | num_signatures += 1 | ||
1319 | return (req_sections, num_signatures) | ||
1320 | |||
1321 | def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path): | ||
1322 | if bb_vars['UBOOT_FITIMAGE_ENABLE'] == '1' and bb_vars['SPL_SIGN_ENABLE'] == "1": | ||
1323 | self.logger.debug("Verifying signatures in the FIT image") | ||
1324 | else: | ||
1325 | self.logger.debug("FIT image is not signed. Signature verification is not needed.") | ||
1326 | return | ||
1327 | |||
1328 | uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG'] | ||
1329 | uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG'] | ||
1330 | spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME'] | ||
1331 | fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[uboot_fit_sign_alg] | ||
1332 | for section, values in sections.items(): | ||
1333 | # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1") | ||
1334 | if section.startswith("conf"): | ||
1335 | # uboot-sign does not sign configuration nodes | ||
1336 | pass | ||
1337 | else: | ||
1338 | # uboot-sign does not add hash nodes, only image signatures | ||
1339 | sign_algo = values.get('Sign algo', None) | ||
1340 | req_sign_algo = "%s,%s:%s" % (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname) | ||
1341 | self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section) | ||
1342 | sign_value = values.get('Sign value', None) | ||
1343 | self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section) | ||
1344 | |||
1345 | # Search for the string passed to mkimage in each signed section of the FIT image. | ||
1346 | # Looks like mkimage supports to add a comment but does not support to read it back. | ||
1347 | a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['SPL_MKIMAGE_SIGN_ARGS']) | ||
1348 | self.logger.debug("a_comment: %s" % a_comment) | ||
1349 | if a_comment: | ||
1350 | found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment) | ||
1351 | self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." % | ||
1352 | (num_signatures, a_comment)) | ||
1353 | |||
1354 | def _check_kernel_dtb(self, bb_vars): | ||
1355 | """ | ||
1356 | Check if the device-tree from U-Boot has the kernel public key(s). | ||
1357 | |||
1358 | The concat_dtb function of the uboot-sign.bbclass injects the public keys | ||
1359 | which are required for verifying the kernel at run-time into the DTB from | ||
1360 | U-Boot. The following example is from a build with FIT_SIGN_INDIVIDUAL | ||
1361 | set to "1". If it is set to "0" the key-the-kernel-image-key node is not | ||
1362 | present. | ||
1363 | / { | ||
1364 | ... | ||
1365 | signature { | ||
1366 | key-the-kernel-image-key { | ||
1367 | required = "image"; | ||
1368 | algo = "sha256,rsa2048"; | ||
1369 | ... | ||
1370 | }; | ||
1371 | key-the-kernel-config-key { | ||
1372 | required = "conf"; | ||
1373 | algo = "sha256,rsa2048"; | ||
1374 | ... | ||
1375 | }; | ||
1376 | }; | ||
1377 | """ | ||
1378 | # Setup u-boot-tools-native | ||
1379 | dtc_bindir = FitImageTestCase._setup_native('dtc-native') | ||
1380 | |||
1381 | # Check if 1 or 2 signature sections are in the DTB. | ||
1382 | uboot_dtb_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], bb_vars['UBOOT_DTB_IMAGE']) | ||
1383 | algo = "%s,%s" % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']) | ||
1384 | if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1": | ||
1385 | uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | ||
1386 | key_dtb_path = "/signature/key-" + uboot_sign_img_keyname | ||
1387 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "image") | ||
1388 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo) | ||
1389 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_img_keyname) | ||
1390 | |||
1391 | uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME'] | ||
1392 | key_dtb_path = "/signature/key-" + uboot_sign_keyname | ||
1393 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "conf") | ||
1394 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo) | ||
1395 | self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_keyname) | ||
317 | 1396 | ||
318 | if field_index != len(its_field_check): # if its equal, the test passed | ||
319 | self.assertTrue(field_index == len(its_field_check), | ||
320 | "Fields in Image Tree Source File %s did not match, error in finding %s" | ||
321 | % (fitimage_its_path, its_field_check[field_index])) | ||
322 | 1397 | ||
323 | def test_uboot_sign_fit_image(self): | 1398 | def test_uboot_fit_image(self): |
324 | """ | 1399 | """ |
325 | Summary: Check if Uboot FIT image and Image Tree Source | 1400 | Summary: Check if Uboot FIT image and Image Tree Source |
326 | (its) are built and the Image Tree Source has the | 1401 | (its) are built and the Image Tree Source has the |
327 | correct fields, in the scenario where the Kernel | 1402 | correct fields. |
328 | is also creating/signing it's fitImage. | ||
329 | Expected: 1. u-boot-fitImage and u-boot-its can be built | 1403 | Expected: 1. u-boot-fitImage and u-boot-its can be built |
330 | 2. The type, load address, entrypoint address and | 1404 | 2. The type, load address, entrypoint address and |
331 | default values of U-boot image are correct in the | 1405 | default values of U-boot image are correct in the |
@@ -349,61 +1423,10 @@ UBOOT_FITIMAGE_ENABLE = "1" | |||
349 | UBOOT_LOADADDRESS = "0x80080000" | 1423 | UBOOT_LOADADDRESS = "0x80080000" |
350 | UBOOT_ENTRYPOINT = "0x80080000" | 1424 | UBOOT_ENTRYPOINT = "0x80080000" |
351 | UBOOT_FIT_DESC = "A model description" | 1425 | UBOOT_FIT_DESC = "A model description" |
352 | KERNEL_IMAGETYPES += " fitImage " | ||
353 | KERNEL_CLASSES = " kernel-fitimage " | ||
354 | INHERIT += "test-mkimage-wrapper" | ||
355 | UBOOT_SIGN_ENABLE = "1" | ||
356 | FIT_GENERATE_KEYS = "1" | ||
357 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | ||
358 | UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" | ||
359 | UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | ||
360 | FIT_SIGN_INDIVIDUAL = "1" | ||
361 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'" | ||
362 | """ | 1426 | """ |
363 | self.write_config(config) | 1427 | self.write_config(config) |
364 | 1428 | bb_vars = self._fit_get_bb_vars() | |
365 | # The U-Boot fitImage is created as part of the U-Boot recipe | 1429 | self._test_fitimage(bb_vars) |
366 | bitbake("virtual/bootloader") | ||
367 | |||
368 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | ||
369 | machine = get_bb_var('MACHINE') | ||
370 | fitimage_its_path = os.path.join(deploy_dir_image, | ||
371 | "u-boot-its-%s" % (machine,)) | ||
372 | fitimage_path = os.path.join(deploy_dir_image, | ||
373 | "u-boot-fitImage-%s" % (machine,)) | ||
374 | |||
375 | self.assertTrue(os.path.exists(fitimage_its_path), | ||
376 | "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
377 | self.assertTrue(os.path.exists(fitimage_path), | ||
378 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
379 | |||
380 | # Check that the type, load address, entrypoint address and default | ||
381 | # values for kernel and ramdisk in Image Tree Source are as expected. | ||
382 | # The order of fields in the below array is important. Not all the | ||
383 | # fields are tested, only the key fields that wont vary between | ||
384 | # different architectures. | ||
385 | its_field_check = [ | ||
386 | 'description = "A model description";', | ||
387 | 'type = "standalone";', | ||
388 | 'load = <0x80080000>;', | ||
389 | 'entry = <0x80080000>;', | ||
390 | 'default = "conf";', | ||
391 | 'loadables = "uboot";', | ||
392 | 'fdt = "fdt";' | ||
393 | ] | ||
394 | |||
395 | with open(fitimage_its_path) as its_file: | ||
396 | field_index = 0 | ||
397 | for line in its_file: | ||
398 | if field_index == len(its_field_check): | ||
399 | break | ||
400 | if its_field_check[field_index] in line: | ||
401 | field_index +=1 | ||
402 | |||
403 | if field_index != len(its_field_check): # if its equal, the test passed | ||
404 | self.assertTrue(field_index == len(its_field_check), | ||
405 | "Fields in Image Tree Source File %s did not match, error in finding %s" | ||
406 | % (fitimage_its_path, its_field_check[field_index])) | ||
407 | 1430 | ||
408 | 1431 | ||
409 | def test_sign_standalone_uboot_fit_image(self): | 1432 | def test_sign_standalone_uboot_fit_image(self): |
@@ -426,15 +1449,11 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'" | |||
426 | Usama Arif <usama.arif@arm.com> | 1449 | Usama Arif <usama.arif@arm.com> |
427 | """ | 1450 | """ |
428 | config = """ | 1451 | config = """ |
429 | # There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at | 1452 | # There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at |
430 | # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set | 1453 | # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set |
431 | MACHINE = "qemuarm" | 1454 | MACHINE = "qemuarm" |
432 | UBOOT_MACHINE = "am57xx_evm_defconfig" | 1455 | UBOOT_MACHINE = "am57xx_evm_defconfig" |
433 | SPL_BINARY = "MLO" | 1456 | SPL_BINARY = "MLO" |
434 | # The kernel-fitimage class is a dependency even if we're only | ||
435 | # creating/signing the U-Boot fitImage | ||
436 | KERNEL_CLASSES = " kernel-fitimage" | ||
437 | INHERIT += "test-mkimage-wrapper" | ||
438 | # Enable creation and signing of the U-Boot fitImage | 1457 | # Enable creation and signing of the U-Boot fitImage |
439 | UBOOT_FITIMAGE_ENABLE = "1" | 1458 | UBOOT_FITIMAGE_ENABLE = "1" |
440 | SPL_SIGN_ENABLE = "1" | 1459 | SPL_SIGN_ENABLE = "1" |
@@ -452,106 +1471,9 @@ UBOOT_FIT_GENERATE_KEYS = "1" | |||
452 | UBOOT_FIT_HASH_ALG = "sha256" | 1471 | UBOOT_FIT_HASH_ALG = "sha256" |
453 | """ | 1472 | """ |
454 | self.write_config(config) | 1473 | self.write_config(config) |
1474 | bb_vars = self._fit_get_bb_vars() | ||
1475 | self._test_fitimage(bb_vars) | ||
455 | 1476 | ||
456 | # The U-Boot fitImage is created as part of the U-Boot recipe | ||
457 | bitbake("virtual/bootloader") | ||
458 | |||
459 | image_type = "core-image-minimal" | ||
460 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | ||
461 | machine = get_bb_var('MACHINE') | ||
462 | fitimage_its_path = os.path.join(deploy_dir_image, | ||
463 | "u-boot-its-%s" % (machine,)) | ||
464 | fitimage_path = os.path.join(deploy_dir_image, | ||
465 | "u-boot-fitImage-%s" % (machine,)) | ||
466 | |||
467 | self.assertTrue(os.path.exists(fitimage_its_path), | ||
468 | "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
469 | self.assertTrue(os.path.exists(fitimage_path), | ||
470 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
471 | |||
472 | req_itspaths = [ | ||
473 | ['/', 'images', 'uboot'], | ||
474 | ['/', 'images', 'uboot', 'signature'], | ||
475 | ['/', 'images', 'fdt'], | ||
476 | ['/', 'images', 'fdt', 'signature'], | ||
477 | ] | ||
478 | |||
479 | itspath = [] | ||
480 | itspaths = [] | ||
481 | linect = 0 | ||
482 | sigs = {} | ||
483 | with open(fitimage_its_path) as its_file: | ||
484 | linect += 1 | ||
485 | for line in its_file: | ||
486 | line = line.strip() | ||
487 | if line.endswith('};'): | ||
488 | itspath.pop() | ||
489 | elif line.endswith('{'): | ||
490 | itspath.append(line[:-1].strip()) | ||
491 | itspaths.append(itspath[:]) | ||
492 | elif itspath and itspath[-1] == 'signature': | ||
493 | itsdotpath = '.'.join(itspath) | ||
494 | if not itsdotpath in sigs: | ||
495 | sigs[itsdotpath] = {} | ||
496 | if not '=' in line or not line.endswith(';'): | ||
497 | self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line)) | ||
498 | key, value = line.split('=', 1) | ||
499 | sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';') | ||
500 | |||
501 | for reqpath in req_itspaths: | ||
502 | if not reqpath in itspaths: | ||
503 | self.fail('Missing section in its file: %s' % reqpath) | ||
504 | |||
505 | reqsigvalues_image = { | ||
506 | 'algo': '"sha256,rsa2048"', | ||
507 | 'key-name-hint': '"spl-oe-selftest"', | ||
508 | } | ||
509 | |||
510 | for itspath, values in sigs.items(): | ||
511 | reqsigvalues = reqsigvalues_image | ||
512 | for reqkey, reqvalue in reqsigvalues.items(): | ||
513 | value = values.get(reqkey, None) | ||
514 | if value is None: | ||
515 | self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath)) | ||
516 | self.assertEqual(value, reqvalue) | ||
517 | |||
518 | # Dump the image to see if it really got signed | ||
519 | bitbake("u-boot-tools-native -c addto_recipe_sysroot") | ||
520 | result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') | ||
521 | recipe_sysroot_native = result.output.split('=')[1].strip('"') | ||
522 | dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') | ||
523 | result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) | ||
524 | in_signed = None | ||
525 | signed_sections = {} | ||
526 | for line in result.output.splitlines(): | ||
527 | if line.startswith((' Image')): | ||
528 | in_signed = re.search(r'\((.*)\)', line).groups()[0] | ||
529 | elif re.match(' \w', line): | ||
530 | in_signed = None | ||
531 | elif in_signed: | ||
532 | if not in_signed in signed_sections: | ||
533 | signed_sections[in_signed] = {} | ||
534 | key, value = line.split(':', 1) | ||
535 | signed_sections[in_signed][key.strip()] = value.strip() | ||
536 | self.assertIn('uboot', signed_sections) | ||
537 | self.assertIn('fdt', signed_sections) | ||
538 | for signed_section, values in signed_sections.items(): | ||
539 | value = values.get('Sign algo', None) | ||
540 | self.assertEqual(value, 'sha256,rsa2048:spl-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section) | ||
541 | value = values.get('Sign value', None) | ||
542 | self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) | ||
543 | |||
544 | # Check for SPL_MKIMAGE_SIGN_ARGS | ||
545 | result = runCmd('bitbake -e virtual/bootloader | grep ^T=') | ||
546 | tempdir = result.output.split('=', 1)[1].strip().strip('') | ||
547 | result = runCmd('grep "a smart U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
548 | self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used') | ||
549 | |||
550 | # Check for evidence of test-mkimage-wrapper class | ||
551 | result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
552 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') | ||
553 | result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
554 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') | ||
555 | 1477 | ||
556 | def test_sign_cascaded_uboot_fit_image(self): | 1478 | def test_sign_cascaded_uboot_fit_image(self): |
557 | """ | 1479 | """ |
@@ -565,9 +1487,9 @@ UBOOT_FIT_HASH_ALG = "sha256" | |||
565 | via UBOOT_FIT_GENERATE_KEYS) | 1487 | via UBOOT_FIT_GENERATE_KEYS) |
566 | 3) Dumping the FIT image indicates signature values | 1488 | 3) Dumping the FIT image indicates signature values |
567 | are present | 1489 | are present |
568 | 4) Examination of the do_uboot_assemble_fitimage | 1490 | 4) Examination of the do_uboot_assemble_fitimage that |
569 | runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN | 1491 | UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and SPL_MKIMAGE_SIGN_ARGS |
570 | and SPL_MKIMAGE_SIGN_ARGS are working as expected. | 1492 | are working as expected. |
571 | Product: oe-core | 1493 | Product: oe-core |
572 | Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon | 1494 | Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon |
573 | work by Paul Eggleton <paul.eggleton@microsoft.com> and | 1495 | work by Paul Eggleton <paul.eggleton@microsoft.com> and |
@@ -588,7 +1510,7 @@ UBOOT_DTB_BINARY = "u-boot.dtb" | |||
588 | UBOOT_ENTRYPOINT = "0x80000000" | 1510 | UBOOT_ENTRYPOINT = "0x80000000" |
589 | UBOOT_LOADADDRESS = "0x80000000" | 1511 | UBOOT_LOADADDRESS = "0x80000000" |
590 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | 1512 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" |
591 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded Kernel comment'" | 1513 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'" |
592 | UBOOT_DTB_LOADADDRESS = "0x82000000" | 1514 | UBOOT_DTB_LOADADDRESS = "0x82000000" |
593 | UBOOT_ARCH = "arm" | 1515 | UBOOT_ARCH = "arm" |
594 | SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | 1516 | SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" |
@@ -596,251 +1518,214 @@ SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'" | |||
596 | UBOOT_EXTLINUX = "0" | 1518 | UBOOT_EXTLINUX = "0" |
597 | UBOOT_FIT_GENERATE_KEYS = "1" | 1519 | UBOOT_FIT_GENERATE_KEYS = "1" |
598 | UBOOT_FIT_HASH_ALG = "sha256" | 1520 | UBOOT_FIT_HASH_ALG = "sha256" |
599 | KERNEL_IMAGETYPES += " fitImage " | ||
600 | KERNEL_CLASSES = " kernel-fitimage " | ||
601 | INHERIT += "test-mkimage-wrapper" | ||
602 | UBOOT_SIGN_ENABLE = "1" | 1521 | UBOOT_SIGN_ENABLE = "1" |
603 | FIT_GENERATE_KEYS = "1" | ||
604 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | 1522 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" |
605 | UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" | ||
606 | UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | 1523 | UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" |
607 | FIT_SIGN_INDIVIDUAL = "1" | ||
608 | """ | 1524 | """ |
609 | self.write_config(config) | 1525 | self.write_config(config) |
1526 | bb_vars = self._fit_get_bb_vars() | ||
610 | 1527 | ||
611 | # The U-Boot fitImage is created as part of the U-Boot recipe | 1528 | self._gen_signing_key(bb_vars) |
612 | bitbake("virtual/bootloader") | 1529 | self._test_fitimage(bb_vars) |
613 | 1530 | self._check_kernel_dtb(bb_vars) | |
614 | image_type = "core-image-minimal" | ||
615 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | ||
616 | machine = get_bb_var('MACHINE') | ||
617 | fitimage_its_path = os.path.join(deploy_dir_image, | ||
618 | "u-boot-its-%s" % (machine,)) | ||
619 | fitimage_path = os.path.join(deploy_dir_image, | ||
620 | "u-boot-fitImage-%s" % (machine,)) | ||
621 | |||
622 | self.assertTrue(os.path.exists(fitimage_its_path), | ||
623 | "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
624 | self.assertTrue(os.path.exists(fitimage_path), | ||
625 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
626 | |||
627 | req_itspaths = [ | ||
628 | ['/', 'images', 'uboot'], | ||
629 | ['/', 'images', 'uboot', 'signature'], | ||
630 | ['/', 'images', 'fdt'], | ||
631 | ['/', 'images', 'fdt', 'signature'], | ||
632 | ] | ||
633 | 1531 | ||
634 | itspath = [] | 1532 | def test_uboot_atf_tee_fit_image(self): |
635 | itspaths = [] | 1533 | """ |
636 | linect = 0 | 1534 | Summary: Check if U-boot FIT image and Image Tree Source |
637 | sigs = {} | 1535 | (its) are built and the Image Tree Source has the |
638 | with open(fitimage_its_path) as its_file: | 1536 | correct fields. |
639 | linect += 1 | 1537 | Expected: 1. Create atf and tee dummy images |
640 | for line in its_file: | 1538 | 2. Both u-boot-fitImage and u-boot-its can be built |
641 | line = line.strip() | 1539 | 3. The os, load address, entrypoint address and |
642 | if line.endswith('};'): | 1540 | default values of U-boot, ATF and TEE images are |
643 | itspath.pop() | 1541 | correct in the Image Tree Source. Not all the |
644 | elif line.endswith('{'): | 1542 | fields are tested, only the key fields that wont |
645 | itspath.append(line[:-1].strip()) | 1543 | vary between different architectures. |
646 | itspaths.append(itspath[:]) | 1544 | Product: oe-core |
647 | elif itspath and itspath[-1] == 'signature': | 1545 | Author: Jamin Lin <jamin_lin@aspeedtech.com> |
648 | itsdotpath = '.'.join(itspath) | 1546 | """ |
649 | if not itsdotpath in sigs: | 1547 | config = """ |
650 | sigs[itsdotpath] = {} | 1548 | # We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set |
651 | if not '=' in line or not line.endswith(';'): | 1549 | MACHINE = "qemuarm" |
652 | self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line)) | 1550 | UBOOT_MACHINE = "am57xx_evm_defconfig" |
653 | key, value = line.split('=', 1) | 1551 | SPL_BINARY = "MLO" |
654 | sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';') | ||
655 | 1552 | ||
656 | for reqpath in req_itspaths: | 1553 | # Enable creation of the U-Boot fitImage |
657 | if not reqpath in itspaths: | 1554 | UBOOT_FITIMAGE_ENABLE = "1" |
658 | self.fail('Missing section in its file: %s' % reqpath) | ||
659 | 1555 | ||
660 | reqsigvalues_image = { | 1556 | # (U-boot) fitImage properties |
661 | 'algo': '"sha256,rsa2048"', | 1557 | UBOOT_LOADADDRESS = "0x80080000" |
662 | 'key-name-hint': '"spl-cascaded-oe-selftest"', | 1558 | UBOOT_ENTRYPOINT = "0x80080000" |
663 | } | 1559 | UBOOT_FIT_DESC = "A model description" |
664 | 1560 | ||
665 | for itspath, values in sigs.items(): | 1561 | # Enable creation of the TEE fitImage |
666 | reqsigvalues = reqsigvalues_image | 1562 | UBOOT_FIT_TEE = "1" |
667 | for reqkey, reqvalue in reqsigvalues.items(): | ||
668 | value = values.get(reqkey, None) | ||
669 | if value is None: | ||
670 | self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath)) | ||
671 | self.assertEqual(value, reqvalue) | ||
672 | |||
673 | # Dump the image to see if it really got signed | ||
674 | bitbake("u-boot-tools-native -c addto_recipe_sysroot") | ||
675 | result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') | ||
676 | recipe_sysroot_native = result.output.split('=')[1].strip('"') | ||
677 | dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') | ||
678 | result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) | ||
679 | in_signed = None | ||
680 | signed_sections = {} | ||
681 | for line in result.output.splitlines(): | ||
682 | if line.startswith((' Image')): | ||
683 | in_signed = re.search(r'\((.*)\)', line).groups()[0] | ||
684 | elif re.match(' \w', line): | ||
685 | in_signed = None | ||
686 | elif in_signed: | ||
687 | if not in_signed in signed_sections: | ||
688 | signed_sections[in_signed] = {} | ||
689 | key, value = line.split(':', 1) | ||
690 | signed_sections[in_signed][key.strip()] = value.strip() | ||
691 | self.assertIn('uboot', signed_sections) | ||
692 | self.assertIn('fdt', signed_sections) | ||
693 | for signed_section, values in signed_sections.items(): | ||
694 | value = values.get('Sign algo', None) | ||
695 | self.assertEqual(value, 'sha256,rsa2048:spl-cascaded-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section) | ||
696 | value = values.get('Sign value', None) | ||
697 | self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) | ||
698 | |||
699 | # Check for SPL_MKIMAGE_SIGN_ARGS | ||
700 | result = runCmd('bitbake -e virtual/bootloader | grep ^T=') | ||
701 | tempdir = result.output.split('=', 1)[1].strip().strip('') | ||
702 | result = runCmd('grep "a smart cascaded U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
703 | self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used') | ||
704 | |||
705 | # Check for evidence of test-mkimage-wrapper class | ||
706 | result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
707 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') | ||
708 | result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) | ||
709 | self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') | ||
710 | |||
711 | |||
712 | |||
713 | def test_initramfs_bundle(self): | ||
714 | """ | ||
715 | Summary: Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its) | ||
716 | The FIT settings are set by the test case. | ||
717 | The machine used is beaglebone-yocto. | ||
718 | Expected: 1. The ITS is generated with initramfs bundle support | ||
719 | 2. All the fields in the kernel node are as expected (matching the | ||
720 | conf settings) | ||
721 | 3. The kernel is included in all the available configurations and | ||
722 | its hash is included in the configuration signature | ||
723 | 1563 | ||
724 | Product: oe-core | 1564 | # TEE fitImage properties |
725 | Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> | 1565 | UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin" |
726 | """ | 1566 | UBOOT_FIT_TEE_LOADADDRESS = "0x80180000" |
1567 | UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000" | ||
1568 | |||
1569 | # Enable creation of the ATF fitImage | ||
1570 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1" | ||
1571 | |||
1572 | # ATF fitImage properties | ||
1573 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin" | ||
1574 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000" | ||
1575 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000" | ||
1576 | """ | ||
1577 | self.write_config(config) | ||
1578 | |||
1579 | bb_vars = self._fit_get_bb_vars([ | ||
1580 | 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE', | ||
1581 | 'UBOOT_FIT_TEE_IMAGE', | ||
1582 | ]) | ||
1583 | |||
1584 | # Create an ATF dummy image | ||
1585 | dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE']) | ||
1586 | FitImageTestCase._gen_random_file(dummy_atf) | ||
727 | 1587 | ||
1588 | # Create a TEE dummy image | ||
1589 | dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE']) | ||
1590 | FitImageTestCase._gen_random_file(dummy_tee) | ||
1591 | |||
1592 | self._test_fitimage(bb_vars) | ||
1593 | |||
1594 | def test_sign_standalone_uboot_atf_tee_fit_image(self): | ||
1595 | """ | ||
1596 | Summary: Check if U-Boot FIT image and Image Tree Source (its) are | ||
1597 | created and signed correctly for the scenario where only | ||
1598 | the U-Boot proper fitImage is being created and signed. | ||
1599 | Expected: 1. Create atf and tee dummy images | ||
1600 | 2. U-Boot its and FIT image are built successfully | ||
1601 | 3. Scanning the its file indicates signing is enabled | ||
1602 | as requested by SPL_SIGN_ENABLE (using keys generated | ||
1603 | via UBOOT_FIT_GENERATE_KEYS) | ||
1604 | 4. Dumping the FIT image indicates signature values | ||
1605 | are present | ||
1606 | 5. Examination of the do_uboot_assemble_fitimage | ||
1607 | runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN | ||
1608 | and SPL_MKIMAGE_SIGN_ARGS are working as expected. | ||
1609 | Product: oe-core | ||
1610 | Author: Jamin Lin <jamin_lin@aspeedtech.com> | ||
1611 | """ | ||
728 | config = """ | 1612 | config = """ |
729 | DISTRO="poky" | 1613 | # There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at |
730 | MACHINE = "beaglebone-yocto" | 1614 | # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set |
731 | INITRAMFS_IMAGE_BUNDLE = "1" | 1615 | MACHINE = "qemuarm" |
732 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" | 1616 | UBOOT_MACHINE = "am57xx_evm_defconfig" |
733 | INITRAMFS_SCRIPTS = "" | 1617 | SPL_BINARY = "MLO" |
734 | UBOOT_MACHINE = "am335x_evm_defconfig" | 1618 | # Enable creation and signing of the U-Boot fitImage |
735 | KERNEL_CLASSES = " kernel-fitimage " | 1619 | UBOOT_FITIMAGE_ENABLE = "1" |
736 | KERNEL_IMAGETYPES = "fitImage" | 1620 | SPL_SIGN_ENABLE = "1" |
737 | UBOOT_SIGN_ENABLE = "1" | 1621 | SPL_SIGN_KEYNAME = "spl-oe-selftest" |
738 | UBOOT_SIGN_KEYNAME = "beaglebonekey" | 1622 | SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys" |
739 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" | ||
740 | UBOOT_DTB_BINARY = "u-boot.dtb" | 1623 | UBOOT_DTB_BINARY = "u-boot.dtb" |
741 | UBOOT_ENTRYPOINT = "0x80000000" | 1624 | UBOOT_ENTRYPOINT = "0x80000000" |
742 | UBOOT_LOADADDRESS = "0x80000000" | 1625 | UBOOT_LOADADDRESS = "0x80000000" |
743 | UBOOT_DTB_LOADADDRESS = "0x82000000" | ||
744 | UBOOT_ARCH = "arm" | 1626 | UBOOT_ARCH = "arm" |
745 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | 1627 | SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" |
746 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" | 1628 | SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot ATF TEE comment'" |
747 | UBOOT_EXTLINUX = "0" | 1629 | UBOOT_EXTLINUX = "0" |
748 | FIT_GENERATE_KEYS = "1" | 1630 | UBOOT_FIT_GENERATE_KEYS = "1" |
749 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" | 1631 | UBOOT_FIT_HASH_ALG = "sha256" |
750 | FIT_KERNEL_COMP_ALG = "none" | ||
751 | FIT_HASH_ALG = "sha256" | ||
752 | """ | ||
753 | self.write_config(config) | ||
754 | 1632 | ||
755 | # fitImage is created as part of linux recipe | 1633 | # Enable creation of the TEE fitImage |
756 | bitbake("virtual/kernel") | 1634 | UBOOT_FIT_TEE = "1" |
757 | 1635 | ||
758 | image_type = get_bb_var('INITRAMFS_IMAGE') | 1636 | # TEE fitImage properties |
759 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | 1637 | UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin" |
760 | machine = get_bb_var('MACHINE') | 1638 | UBOOT_FIT_TEE_LOADADDRESS = "0x80180000" |
761 | fitimage_its_path = os.path.join(deploy_dir_image, | 1639 | UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000" |
762 | "fitImage-its-%s-%s-%s" % (image_type, machine, machine)) | ||
763 | fitimage_path = os.path.join(deploy_dir_image,"fitImage") | ||
764 | 1640 | ||
765 | self.assertTrue(os.path.exists(fitimage_its_path), | 1641 | # Enable creation of the ATF fitImage |
766 | "%s image tree source doesn't exist" % (fitimage_its_path)) | 1642 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1" |
767 | self.assertTrue(os.path.exists(fitimage_path), | ||
768 | "%s FIT image doesn't exist" % (fitimage_path)) | ||
769 | 1643 | ||
770 | kernel_load = str(get_bb_var('UBOOT_LOADADDRESS')) | 1644 | # ATF fitImage properties |
771 | kernel_entry = str(get_bb_var('UBOOT_ENTRYPOINT')) | 1645 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin" |
772 | kernel_type = str(get_bb_var('UBOOT_MKIMAGE_KERNEL_TYPE')) | 1646 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000" |
773 | kernel_compression = str(get_bb_var('FIT_KERNEL_COMP_ALG')) | 1647 | UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000" |
774 | uboot_arch = str(get_bb_var('UBOOT_ARCH')) | 1648 | """ |
775 | fit_hash_alg = str(get_bb_var('FIT_HASH_ALG')) | 1649 | self.write_config(config) |
776 | 1650 | ||
777 | its_file = open(fitimage_its_path) | 1651 | bb_vars = self._fit_get_bb_vars([ |
1652 | 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE', | ||
1653 | 'UBOOT_FIT_TEE_IMAGE', | ||
1654 | ]) | ||
778 | 1655 | ||
779 | its_lines = [line.strip() for line in its_file.readlines()] | 1656 | # Create an ATF dummy image |
1657 | dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE']) | ||
1658 | FitImageTestCase._gen_random_file(dummy_atf) | ||
780 | 1659 | ||
781 | exp_node_lines = [ | 1660 | # Create a TEE dummy image |
782 | 'kernel-1 {', | 1661 | dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE']) |
783 | 'description = "Linux kernel";', | 1662 | FitImageTestCase._gen_random_file(dummy_tee) |
784 | 'data = /incbin/("linux.bin");', | ||
785 | 'type = "' + kernel_type + '";', | ||
786 | 'arch = "' + uboot_arch + '";', | ||
787 | 'os = "linux";', | ||
788 | 'compression = "' + kernel_compression + '";', | ||
789 | 'load = <' + kernel_load + '>;', | ||
790 | 'entry = <' + kernel_entry + '>;', | ||
791 | 'hash-1 {', | ||
792 | 'algo = "' + fit_hash_alg +'";', | ||
793 | '};', | ||
794 | '};' | ||
795 | ] | ||
796 | 1663 | ||
797 | node_str = exp_node_lines[0] | 1664 | self._test_fitimage(bb_vars) |
798 | 1665 | ||
799 | test_passed = False | ||
800 | 1666 | ||
801 | print ("checking kernel node\n") | 1667 | def test_sign_uboot_kernel_individual(self): |
1668 | """ | ||
1669 | Summary: Check if the device-tree from U-Boot has two public keys | ||
1670 | for verifying the kernel FIT image created by the | ||
1671 | kernel-fitimage.bbclass included. | ||
1672 | This test sets: FIT_SIGN_INDIVIDUAL = "1" | ||
1673 | Expected: There must be two signature nodes. One is required for | ||
1674 | the individual image nodes, the other is required for the | ||
1675 | verification of the configuration section. | ||
1676 | """ | ||
1677 | config = """ | ||
1678 | # Enable creation of fitImage | ||
1679 | MACHINE = "beaglebone-yocto" | ||
1680 | UBOOT_SIGN_ENABLE = "1" | ||
1681 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | ||
1682 | UBOOT_SIGN_KEYNAME = "the-kernel-config-key" | ||
1683 | UBOOT_SIGN_IMG_KEYNAME = "the-kernel-image-key" | ||
1684 | UBOOT_MKIMAGE_DTCOPTS="-I dts -O dtb -p 2000" | ||
1685 | FIT_SIGN_INDIVIDUAL = "1" | ||
1686 | """ | ||
1687 | self.write_config(config) | ||
1688 | bb_vars = self._fit_get_bb_vars() | ||
1689 | self._gen_signing_key(bb_vars) | ||
802 | 1690 | ||
803 | if node_str in its_lines: | 1691 | bitbake(UBootFitImageTests.BOOTLOADER_RECIPE) |
804 | node_start_idx = its_lines.index(node_str) | ||
805 | node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))] | ||
806 | if node == exp_node_lines: | ||
807 | print("kernel node verified") | ||
808 | else: | ||
809 | self.assertTrue(test_passed == True,"kernel node does not match expectation") | ||
810 | |||
811 | rx_configs = re.compile("^conf-.*") | ||
812 | its_configs = list(filter(rx_configs.match, its_lines)) | ||
813 | |||
814 | for cfg_str in its_configs: | ||
815 | cfg_start_idx = its_lines.index(cfg_str) | ||
816 | line_idx = cfg_start_idx + 2 | ||
817 | node_end = False | ||
818 | while node_end == False: | ||
819 | if its_lines[line_idx] == "};" and its_lines[line_idx-1] == "};" : | ||
820 | node_end = True | ||
821 | line_idx = line_idx + 1 | ||
822 | |||
823 | node = its_lines[cfg_start_idx:line_idx] | ||
824 | print("checking configuration " + cfg_str.rstrip(" {")) | ||
825 | rx_desc_line = re.compile("^description.*1 Linux kernel.*") | ||
826 | if len(list(filter(rx_desc_line.match, node))) != 1: | ||
827 | self.assertTrue(test_passed == True,"kernel keyword not found in the description line") | ||
828 | break | ||
829 | else: | ||
830 | print("kernel keyword found in the description line") | ||
831 | 1692 | ||
832 | if 'kernel = "kernel-1";' not in node: | 1693 | # Just check the DTB of u-boot since there is no u-boot FIT image |
833 | self.assertTrue(test_passed == True,"kernel line not found") | 1694 | self._check_kernel_dtb(bb_vars) |
834 | break | ||
835 | else: | ||
836 | print("kernel line found") | ||
837 | 1695 | ||
838 | rx_sign_line = re.compile("^sign-images.*kernel.*") | ||
839 | if len(list(filter(rx_sign_line.match, node))) != 1: | ||
840 | self.assertTrue(test_passed == True,"kernel hash not signed") | ||
841 | break | ||
842 | else: | ||
843 | print("kernel hash signed") | ||
844 | 1696 | ||
845 | test_passed = True | 1697 | def test_sign_uboot_fit_image_without_spl(self): |
846 | self.assertTrue(test_passed == True,"Initramfs bundle test success") | 1698 | """ |
1699 | Summary: Check if U-Boot FIT image and Image Tree Source (its) are | ||
1700 | created and signed correctly for the scenario where only | ||
1701 | the U-Boot proper fitImage is being created and signed | ||
1702 | (no SPL included). | ||
1703 | Expected: 1) U-Boot its and FIT image are built successfully | ||
1704 | 2) Scanning the its file indicates signing is enabled | ||
1705 | as requested by SPL_SIGN_ENABLE (using keys generated | ||
1706 | via UBOOT_FIT_GENERATE_KEYS) | ||
1707 | 3) Dumping the FIT image indicates signature values | ||
1708 | are present | ||
1709 | 4) Examination of the do_uboot_assemble_fitimage | ||
1710 | runfile/logfile indicate that UBOOT_MKIMAGE and | ||
1711 | UBOOT_MKIMAGE_SIGN are working as expected. | ||
1712 | Product: oe-core | ||
1713 | Author: Jamin Lin <jamin_lin@aspeedtech.com> | ||
1714 | """ | ||
1715 | config = """ | ||
1716 | # There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at | ||
1717 | # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set | ||
1718 | MACHINE = "qemuarm" | ||
1719 | UBOOT_MACHINE = "am57xx_evm_defconfig" | ||
1720 | # Enable creation and signing of the U-Boot fitImage (no SPL) | ||
1721 | UBOOT_FITIMAGE_ENABLE = "1" | ||
1722 | SPL_DTB_BINARY = "" | ||
1723 | SPL_SIGN_ENABLE = "1" | ||
1724 | SPL_SIGN_KEYNAME = "spl-oe-selftest" | ||
1725 | SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | ||
1726 | UBOOT_FIT_GENERATE_KEYS = "1" | ||
1727 | """ | ||
1728 | self.write_config(config) | ||
1729 | bb_vars = self._fit_get_bb_vars() | ||
1730 | self._test_fitimage(bb_vars) | ||
1731 | |||