xref: /openbmc/openbmc/poky/meta/lib/oeqa/selftest/cases/fitimage.py (revision 43a6b7c2a48b0cb1381af4d3192d22a12ead65f0)
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
9import os
10import re
11
12class FitImageTests(OESelftestTestCase):
13
14    def _setup_uboot_tools_native(self):
15        """build u-boot-tools-native and return ${RECIPE_SYSROOT_NATIVE}/${bindir}"""
16        bitbake("u-boot-tools-native -c addto_recipe_sysroot")
17        vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], 'u-boot-tools-native')
18        return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir'])
19
20    def _run_dumpimage(self, fitimage_path, uboot_tools_bindir):
21        dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage')
22        return runCmd('%s -l %s' % (dumpimage_path, fitimage_path))
23
24    def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None):
25        """Verify the signature of a fit contfiguration
26
27        The fit_check_sign utility from u-boot-tools-native is called.
28        uboot-fit_check_sign -f fitImage -k $dtb_name -c conf-$dtb_name
29        """
30        fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign')
31        cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path)
32        if conf_name:
33            cmd += ' -c %s' % conf_name
34        result = runCmd(cmd)
35        self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
36        self.assertIn("Signature check OK", result.output)
37
38    @staticmethod
39    def _find_string_in_bin_file(file_path, search_string):
40        """find stings in a binary file
41
42        Shell equivalent: strings "$1" | grep "$2" | wc -l
43        return number of matches
44        """
45        found_positions = 0
46        with open(file_path, 'rb') as file:
47            byte = file.read(1)
48            current_position = 0
49            current_match = 0
50            while byte:
51                char = byte.decode('ascii', errors='ignore')
52                if char == search_string[current_match]:
53                    current_match += 1
54                    if current_match == len(search_string):
55                        found_positions += 1
56                        current_match = 0
57                else:
58                    current_match = 0
59                current_position += 1
60                byte = file.read(1)
61        return found_positions
62
63    def _config_add_uboot_env(self, config):
64        """Generate an u-boot environment
65
66        Create a boot.cmd file that is packed into the FitImage as a source-able text file.
67        """
68        fit_uenv_file =  "boot.cmd"
69        test_files_dir = "test-files"
70        fit_uenv_path = os.path.join(self.builddir, test_files_dir, fit_uenv_file)
71
72        config += '# Add an u-boot script to the fitImage' + os.linesep
73        config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep
74        config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep
75        config += 'SRC_URI:append:pn-linux-yocto = " file://${FIT_UBOOT_ENV}"' + os.linesep
76
77        if not os.path.isdir(test_files_dir):
78            os.mkdir(test_files_dir)
79        self.logger.debug("Writing to: %s" % fit_uenv_path)
80        with open(fit_uenv_path, "w") as f:
81            f.write('echo "hello world"')
82
83        return config
84
85    def _verify_fitimage_uboot_env(self, dumpimage_result):
86        """Check if the boot.cmd script is part of the fitImage"""
87        num_scr_images = len(re.findall(r"^ *Image +[0-9]+ +\(bootscr-boot\.cmd\)$", dumpimage_result.output, re.MULTILINE))
88        self.assertEqual(1, num_scr_images, msg="Expected exactly 1 bootscr-boot.cmd image section in the fitImage")
89
90    def test_fit_image(self):
91        """
92        Summary:     Check if FIT image and Image Tree Source (its) are built
93                     and the Image Tree Source has the correct fields.
94        Expected:    1. fitImage and fitImage-its can be built
95                     2. The type, load address, entrypoint address and
96                     default values of kernel and ramdisk are as expected
97                     in the Image Tree Source. Not all the fields are tested,
98                     only the key fields that wont vary between different
99                     architectures.
100        Product:     oe-core
101        Author:      Usama Arif <usama.arif@arm.com>
102        """
103        config = """
104# Enable creation of fitImage
105KERNEL_IMAGETYPE = "Image"
106KERNEL_IMAGETYPES += " fitImage "
107KERNEL_CLASSES = " kernel-fitimage "
108
109# RAM disk variables including load address and entrypoint for kernel and RAM disk
110IMAGE_FSTYPES += "cpio.gz"
111INITRAMFS_IMAGE = "core-image-minimal"
112# core-image-minimal is used as initramfs here, drop the rootfs suffix
113IMAGE_NAME_SUFFIX:pn-core-image-minimal = ""
114UBOOT_RD_LOADADDRESS = "0x88000000"
115UBOOT_RD_ENTRYPOINT = "0x88000000"
116UBOOT_LOADADDRESS = "0x80080000"
117UBOOT_ENTRYPOINT = "0x80080000"
118FIT_DESC = "A model description"
119"""
120        config = self._config_add_uboot_env(config)
121        self.write_config(config)
122
123        # fitImage is created as part of linux recipe
124        image = "virtual/kernel"
125        bitbake(image)
126        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'INITRAMFS_IMAGE_NAME', 'KERNEL_FIT_LINK_NAME'], image)
127
128        fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
129            "fitImage-its-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
130        fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
131            "fitImage-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
132
133        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
134        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
135
136        # Check that the type, load address, entrypoint address and default
137        # values for kernel and ramdisk in Image Tree Source are as expected.
138        # The order of fields in the below array is important. Not all the
139        # fields are tested, only the key fields that wont vary between
140        # different architectures.
141        its_field_check = [
142            'description = "A model description";',
143            'type = "kernel";',
144            'load = <0x80080000>;',
145            'entry = <0x80080000>;',
146            'type = "ramdisk";',
147            'load = <0x88000000>;',
148            'entry = <0x88000000>;',
149            'default = "conf-1";',
150            'kernel = "kernel-1";',
151            'ramdisk = "ramdisk-1";'
152            ]
153
154        with open(fitimage_its_path) as its_file:
155            field_index = 0
156            for line in its_file:
157                if field_index == len(its_field_check):
158                    break
159                if its_field_check[field_index] in line:
160                    field_index +=1
161
162        if field_index != len(its_field_check): # if its equal, the test passed
163            self.assertTrue(field_index == len(its_field_check),
164                "Fields in Image Tree Source File %s did not match, error in finding %s"
165                % (fitimage_its_path, its_field_check[field_index]))
166
167        uboot_tools_bindir = self._setup_uboot_tools_native()
168        dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
169        self._verify_fitimage_uboot_env(dumpimage_result)
170
171
172    def test_sign_fit_image(self):
173        """
174        Summary:     Check if FIT image and Image Tree Source (its) are created
175                     and signed correctly.
176        Expected:    1) its and FIT image are built successfully
177                     2) Scanning the its file indicates signing is enabled
178                        as requested by UBOOT_SIGN_ENABLE (using keys generated
179                        via FIT_GENERATE_KEYS)
180                     3) Dumping the FIT image indicates signature values
181                        are present (including for images as enabled via
182                        FIT_SIGN_INDIVIDUAL)
183                     4) Examination of the do_assemble_fitimage runfile/logfile
184                        indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and
185                        UBOOT_MKIMAGE_SIGN_ARGS are working as expected.
186        Product:     oe-core
187        Author:      Paul Eggleton <paul.eggleton@microsoft.com> based upon
188                     work by Usama Arif <usama.arif@arm.com>
189        """
190        a_comment = "a smart comment"
191        config = """
192# Enable creation of fitImage
193MACHINE = "beaglebone-yocto"
194KERNEL_IMAGETYPES += " fitImage "
195KERNEL_CLASSES = " kernel-fitimage "
196UBOOT_SIGN_ENABLE = "1"
197FIT_GENERATE_KEYS = "1"
198UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
199UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
200UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
201FIT_SIGN_INDIVIDUAL = "1"
202UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'"
203""" % a_comment
204
205        config = self._config_add_uboot_env(config)
206        self.write_config(config)
207
208        # fitImage is created as part of linux recipe
209        image = "virtual/kernel"
210        bitbake(image)
211        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'KERNEL_FIT_LINK_NAME'], image)
212
213        fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
214            "fitImage-its-%s" % (bb_vars['KERNEL_FIT_LINK_NAME']))
215        fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
216            "fitImage-%s.bin" % (bb_vars['KERNEL_FIT_LINK_NAME']))
217
218        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
219        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
220
221        req_itspaths = [
222            ['/', 'images', 'kernel-1'],
223            ['/', 'images', 'kernel-1', 'signature-1'],
224            ['/', 'images', 'fdt-am335x-boneblack.dtb'],
225            ['/', 'images', 'fdt-am335x-boneblack.dtb', 'signature-1'],
226            ['/', 'configurations', 'conf-am335x-boneblack.dtb'],
227            ['/', 'configurations', 'conf-am335x-boneblack.dtb', 'signature-1'],
228        ]
229
230        itspath = []
231        itspaths = []
232        linect = 0
233        sigs = {}
234        with open(fitimage_its_path) as its_file:
235            linect += 1
236            for line in its_file:
237                line = line.strip()
238                if line.endswith('};'):
239                    itspath.pop()
240                elif line.endswith('{'):
241                    itspath.append(line[:-1].strip())
242                    itspaths.append(itspath[:])
243                elif itspath and itspath[-1] == 'signature-1':
244                    itsdotpath = '.'.join(itspath)
245                    if not itsdotpath in sigs:
246                        sigs[itsdotpath] = {}
247                    if not '=' in line or not line.endswith(';'):
248                        self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
249                    key, value = line.split('=', 1)
250                    sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
251
252        for reqpath in req_itspaths:
253            if not reqpath in itspaths:
254                self.fail('Missing section in its file: %s' % reqpath)
255
256        reqsigvalues_image = {
257            'algo': '"sha256,rsa2048"',
258            'key-name-hint': '"img-oe-selftest"',
259        }
260        reqsigvalues_config = {
261            'algo': '"sha256,rsa2048"',
262            'key-name-hint': '"cfg-oe-selftest"',
263            'sign-images': '"kernel", "fdt", "bootscr"',
264        }
265
266        for itspath, values in sigs.items():
267            if 'conf-' in itspath:
268                reqsigvalues = reqsigvalues_config
269            else:
270                reqsigvalues = reqsigvalues_image
271            for reqkey, reqvalue in reqsigvalues.items():
272                value = values.get(reqkey, None)
273                if value is None:
274                    self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
275                self.assertEqual(value, reqvalue)
276
277        # Dump the image to see if it really got signed
278        uboot_tools_bindir = self._setup_uboot_tools_native()
279        dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
280        in_signed = None
281        signed_sections = {}
282        for line in dumpimage_result.output.splitlines():
283            if line.startswith((' Configuration', ' Image')):
284                in_signed = re.search(r'\((.*)\)', line).groups()[0]
285            elif re.match('^ *', line) in (' ', ''):
286                in_signed = None
287            elif in_signed:
288                if not in_signed in signed_sections:
289                    signed_sections[in_signed] = {}
290                key, value = line.split(':', 1)
291                signed_sections[in_signed][key.strip()] = value.strip()
292        self.assertIn('kernel-1', signed_sections)
293        self.assertIn('fdt-am335x-boneblack.dtb', signed_sections)
294        self.assertIn('conf-am335x-boneblack.dtb', signed_sections)
295        for signed_section, values in signed_sections.items():
296            value = values.get('Sign algo', None)
297            if signed_section.startswith("conf"):
298                self.assertEqual(value, 'sha256,rsa2048:cfg-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
299            else:
300                self.assertEqual(value, 'sha256,rsa2048:img-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
301            value = values.get('Sign value', None)
302            self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
303
304        # Check if the u-boot boot.scr script is in the fitImage
305        self._verify_fitimage_uboot_env(dumpimage_result)
306
307        # Search for the string passed to mkimage: 1 kernel + 3 DTBs + config per DTB = 7 sections
308        # Looks like mkimage supports to add a comment but does not support to read it back.
309        found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
310        self.assertEqual(found_comments, 8, "Expected 8 signed and commented section in the fitImage.")
311
312        # Verify the signature for all configurations = DTBs
313        for dtb in ['am335x-bone.dtb', 'am335x-boneblack.dtb', 'am335x-bonegreen.dtb']:
314            self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
315                                             os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], dtb), 'conf-' + dtb)
316
317    def test_uboot_fit_image(self):
318        """
319        Summary:     Check if Uboot FIT image and Image Tree Source
320                     (its) are built and the Image Tree Source has the
321                     correct fields.
322        Expected:    1. u-boot-fitImage and u-boot-its can be built
323                     2. The type, load address, entrypoint address and
324                     default values of U-boot image are correct in the
325                     Image Tree Source. Not all the fields are tested,
326                     only the key fields that wont vary between
327                     different architectures.
328        Product:     oe-core
329        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
330                     based on work by Usama Arif <usama.arif@arm.com>
331        """
332        config = """
333# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
334MACHINE = "qemuarm"
335UBOOT_MACHINE = "am57xx_evm_defconfig"
336SPL_BINARY = "MLO"
337
338# Enable creation of the U-Boot fitImage
339UBOOT_FITIMAGE_ENABLE = "1"
340
341# (U-boot) fitImage properties
342UBOOT_LOADADDRESS = "0x80080000"
343UBOOT_ENTRYPOINT = "0x80080000"
344UBOOT_FIT_DESC = "A model description"
345
346# Enable creation of Kernel fitImage
347KERNEL_IMAGETYPES += " fitImage "
348KERNEL_CLASSES = " kernel-fitimage"
349UBOOT_SIGN_ENABLE = "1"
350FIT_GENERATE_KEYS = "1"
351UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
352UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
353UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
354FIT_SIGN_INDIVIDUAL = "1"
355"""
356        self.write_config(config)
357
358        # The U-Boot fitImage is created as part of the U-Boot recipe
359        bitbake("virtual/bootloader")
360
361        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
362        machine = get_bb_var('MACHINE')
363        fitimage_its_path = os.path.join(deploy_dir_image,
364            "u-boot-its-%s" % (machine,))
365        fitimage_path = os.path.join(deploy_dir_image,
366            "u-boot-fitImage-%s" % (machine,))
367
368        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
369        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
370
371        # Check that the type, load address, entrypoint address and default
372        # values for kernel and ramdisk in Image Tree Source are as expected.
373        # The order of fields in the below array is important. Not all the
374        # fields are tested, only the key fields that wont vary between
375        # different architectures.
376        its_field_check = [
377            'description = "A model description";',
378            'type = "standalone";',
379            'load = <0x80080000>;',
380            'entry = <0x80080000>;',
381            'default = "conf";',
382            'loadables = "uboot";',
383            'fdt = "fdt";'
384            ]
385
386        with open(fitimage_its_path) as its_file:
387            field_index = 0
388            for line in its_file:
389                if field_index == len(its_field_check):
390                    break
391                if its_field_check[field_index] in line:
392                    field_index +=1
393
394        if field_index != len(its_field_check): # if its equal, the test passed
395            self.assertTrue(field_index == len(its_field_check),
396                "Fields in Image Tree Source File %s did not match, error in finding %s"
397                % (fitimage_its_path, its_field_check[field_index]))
398
399    def test_uboot_sign_fit_image(self):
400        """
401        Summary:     Check if Uboot FIT image and Image Tree Source
402                     (its) are built and the Image Tree Source has the
403                     correct fields, in the scenario where the Kernel
404                     is also creating/signing it's fitImage.
405        Expected:    1. u-boot-fitImage and u-boot-its can be built
406                     2. The type, load address, entrypoint address and
407                     default values of U-boot image are correct in the
408                     Image Tree Source. Not all the fields are tested,
409                     only the key fields that wont vary between
410                     different architectures.
411        Product:     oe-core
412        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
413                     based on work by Usama Arif <usama.arif@arm.com>
414        """
415        config = """
416# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
417MACHINE = "qemuarm"
418UBOOT_MACHINE = "am57xx_evm_defconfig"
419SPL_BINARY = "MLO"
420
421# Enable creation of the U-Boot fitImage
422UBOOT_FITIMAGE_ENABLE = "1"
423
424# (U-boot) fitImage properties
425UBOOT_LOADADDRESS = "0x80080000"
426UBOOT_ENTRYPOINT = "0x80080000"
427UBOOT_FIT_DESC = "A model description"
428KERNEL_IMAGETYPES += " fitImage "
429KERNEL_CLASSES = " kernel-fitimage "
430UBOOT_SIGN_ENABLE = "1"
431FIT_GENERATE_KEYS = "1"
432UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
433UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
434UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
435FIT_SIGN_INDIVIDUAL = "1"
436UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
437"""
438        self.write_config(config)
439
440        # The U-Boot fitImage is created as part of the U-Boot recipe
441        bitbake("virtual/bootloader")
442
443        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
444        machine = get_bb_var('MACHINE')
445        fitimage_its_path = os.path.join(deploy_dir_image,
446            "u-boot-its-%s" % (machine,))
447        fitimage_path = os.path.join(deploy_dir_image,
448            "u-boot-fitImage-%s" % (machine,))
449
450        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
451        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
452
453        # Check that the type, load address, entrypoint address and default
454        # values for kernel and ramdisk in Image Tree Source are as expected.
455        # The order of fields in the below array is important. Not all the
456        # fields are tested, only the key fields that wont vary between
457        # different architectures.
458        its_field_check = [
459            'description = "A model description";',
460            'type = "standalone";',
461            'load = <0x80080000>;',
462            'entry = <0x80080000>;',
463            'default = "conf";',
464            'loadables = "uboot";',
465            'fdt = "fdt";'
466            ]
467
468        with open(fitimage_its_path) as its_file:
469            field_index = 0
470            for line in its_file:
471                if field_index == len(its_field_check):
472                    break
473                if its_field_check[field_index] in line:
474                    field_index +=1
475
476        if field_index != len(its_field_check): # if its equal, the test passed
477            self.assertTrue(field_index == len(its_field_check),
478                "Fields in Image Tree Source File %s did not match, error in finding %s"
479                % (fitimage_its_path, its_field_check[field_index]))
480
481
482    def test_sign_standalone_uboot_fit_image(self):
483        """
484        Summary:     Check if U-Boot FIT image and Image Tree Source (its) are
485                     created and signed correctly for the scenario where only
486                     the U-Boot proper fitImage is being created and signed.
487        Expected:    1) U-Boot its and FIT image are built successfully
488                     2) Scanning the its file indicates signing is enabled
489                        as requested by SPL_SIGN_ENABLE (using keys generated
490                        via UBOOT_FIT_GENERATE_KEYS)
491                     3) Dumping the FIT image indicates signature values
492                        are present
493                     4) Examination of the do_uboot_assemble_fitimage
494                     runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
495                     and SPL_MKIMAGE_SIGN_ARGS are working as expected.
496        Product:     oe-core
497        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
498                     work by Paul Eggleton <paul.eggleton@microsoft.com> and
499                     Usama Arif <usama.arif@arm.com>
500        """
501        a_comment = "a smart U-Boot comment"
502        config = """
503# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
504# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
505MACHINE = "qemuarm"
506UBOOT_MACHINE = "am57xx_evm_defconfig"
507SPL_BINARY = "MLO"
508# The kernel-fitimage class is a dependency even if we're only
509# creating/signing the U-Boot fitImage
510KERNEL_CLASSES = " kernel-fitimage"
511# Enable creation and signing of the U-Boot fitImage
512UBOOT_FITIMAGE_ENABLE = "1"
513SPL_SIGN_ENABLE = "1"
514SPL_SIGN_KEYNAME = "spl-oe-selftest"
515SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
516UBOOT_DTB_BINARY = "u-boot.dtb"
517UBOOT_ENTRYPOINT  = "0x80000000"
518UBOOT_LOADADDRESS = "0x80000000"
519UBOOT_DTB_LOADADDRESS = "0x82000000"
520UBOOT_ARCH = "arm"
521SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
522SPL_MKIMAGE_SIGN_ARGS = "-c '%s'"
523UBOOT_EXTLINUX = "0"
524UBOOT_FIT_GENERATE_KEYS = "1"
525UBOOT_FIT_HASH_ALG = "sha256"
526""" % a_comment
527
528        self.write_config(config)
529
530        # The U-Boot fitImage is created as part of the U-Boot recipe
531        bitbake("virtual/bootloader")
532
533        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
534        machine = get_bb_var('MACHINE')
535        fitimage_its_path = os.path.join(deploy_dir_image,
536            "u-boot-its-%s" % (machine,))
537        fitimage_path = os.path.join(deploy_dir_image,
538            "u-boot-fitImage-%s" % (machine,))
539
540        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
541        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
542
543        req_itspaths = [
544            ['/', 'images', 'uboot'],
545            ['/', 'images', 'uboot', 'signature'],
546            ['/', 'images', 'fdt'],
547            ['/', 'images', 'fdt', 'signature'],
548        ]
549
550        itspath = []
551        itspaths = []
552        linect = 0
553        sigs = {}
554        with open(fitimage_its_path) as its_file:
555            linect += 1
556            for line in its_file:
557                line = line.strip()
558                if line.endswith('};'):
559                    itspath.pop()
560                elif line.endswith('{'):
561                    itspath.append(line[:-1].strip())
562                    itspaths.append(itspath[:])
563                elif itspath and itspath[-1] == 'signature':
564                    itsdotpath = '.'.join(itspath)
565                    if not itsdotpath in sigs:
566                        sigs[itsdotpath] = {}
567                    if not '=' in line or not line.endswith(';'):
568                        self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
569                    key, value = line.split('=', 1)
570                    sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
571
572        for reqpath in req_itspaths:
573            if not reqpath in itspaths:
574                self.fail('Missing section in its file: %s' % reqpath)
575
576        reqsigvalues_image = {
577            'algo': '"sha256,rsa2048"',
578            'key-name-hint': '"spl-oe-selftest"',
579        }
580
581        for itspath, values in sigs.items():
582            reqsigvalues = reqsigvalues_image
583            for reqkey, reqvalue in reqsigvalues.items():
584                value = values.get(reqkey, None)
585                if value is None:
586                    self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
587                self.assertEqual(value, reqvalue)
588
589        # Dump the image to see if it really got signed
590        uboot_tools_bindir = self._setup_uboot_tools_native()
591        dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
592        in_signed = None
593        signed_sections = {}
594        for line in dumpimage_result.output.splitlines():
595            if line.startswith((' Image')):
596                in_signed = re.search(r'\((.*)\)', line).groups()[0]
597            elif re.match(' \w', line):
598                in_signed = None
599            elif in_signed:
600                if not in_signed in signed_sections:
601                    signed_sections[in_signed] = {}
602                key, value = line.split(':', 1)
603                signed_sections[in_signed][key.strip()] = value.strip()
604        self.assertIn('uboot', signed_sections)
605        self.assertIn('fdt', signed_sections)
606        for signed_section, values in signed_sections.items():
607            value = values.get('Sign algo', None)
608            self.assertEqual(value, 'sha256,rsa2048:spl-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
609            value = values.get('Sign value', None)
610            self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
611
612        # Check for SPL_MKIMAGE_SIGN_ARGS
613        # Looks like mkimage supports to add a comment but does not support to read it back.
614        found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
615        self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.")
616
617        # Verify the signature
618        self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
619                                         os.path.join(deploy_dir_image, 'u-boot-spl.dtb'))
620
621
622    def test_sign_cascaded_uboot_fit_image(self):
623        """
624        Summary:     Check if U-Boot FIT image and Image Tree Source (its) are
625                     created and signed correctly for the scenario where both
626                     U-Boot proper and Kernel fitImages are being created and
627                     signed.
628        Expected:    1) U-Boot its and FIT image are built successfully
629                     2) Scanning the its file indicates signing is enabled
630                        as requested by SPL_SIGN_ENABLE (using keys generated
631                        via UBOOT_FIT_GENERATE_KEYS)
632                     3) Dumping the FIT image indicates signature values
633                        are present
634                     4) Examination of the do_uboot_assemble_fitimage
635                     runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
636                     and SPL_MKIMAGE_SIGN_ARGS are working as expected.
637        Product:     oe-core
638        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
639                     work by Paul Eggleton <paul.eggleton@microsoft.com> and
640                     Usama Arif <usama.arif@arm.com>
641        """
642        a_comment = "a smart cascaded U-Boot comment"
643        config = """
644# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
645# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
646MACHINE = "qemuarm"
647UBOOT_MACHINE = "am57xx_evm_defconfig"
648SPL_BINARY = "MLO"
649# Enable creation and signing of the U-Boot fitImage
650UBOOT_FITIMAGE_ENABLE = "1"
651SPL_SIGN_ENABLE = "1"
652SPL_SIGN_KEYNAME = "spl-cascaded-oe-selftest"
653SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
654UBOOT_DTB_BINARY = "u-boot.dtb"
655UBOOT_ENTRYPOINT  = "0x80000000"
656UBOOT_LOADADDRESS = "0x80000000"
657UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
658UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'"
659UBOOT_DTB_LOADADDRESS = "0x82000000"
660UBOOT_ARCH = "arm"
661SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
662SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
663UBOOT_EXTLINUX = "0"
664UBOOT_FIT_GENERATE_KEYS = "1"
665UBOOT_FIT_HASH_ALG = "sha256"
666KERNEL_IMAGETYPES += " fitImage "
667KERNEL_CLASSES = " kernel-fitimage "
668UBOOT_SIGN_ENABLE = "1"
669FIT_GENERATE_KEYS = "1"
670UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
671UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
672UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
673FIT_SIGN_INDIVIDUAL = "1"
674""" % a_comment
675        self.write_config(config)
676
677        # The U-Boot fitImage is created as part of the U-Boot recipe
678        bitbake("virtual/bootloader")
679
680        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
681        machine = get_bb_var('MACHINE')
682        fitimage_its_path = os.path.join(deploy_dir_image,
683            "u-boot-its-%s" % (machine,))
684        fitimage_path = os.path.join(deploy_dir_image,
685            "u-boot-fitImage-%s" % (machine,))
686
687        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
688        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
689
690        req_itspaths = [
691            ['/', 'images', 'uboot'],
692            ['/', 'images', 'uboot', 'signature'],
693            ['/', 'images', 'fdt'],
694            ['/', 'images', 'fdt', 'signature'],
695        ]
696
697        itspath = []
698        itspaths = []
699        linect = 0
700        sigs = {}
701        with open(fitimage_its_path) as its_file:
702            linect += 1
703            for line in its_file:
704                line = line.strip()
705                if line.endswith('};'):
706                    itspath.pop()
707                elif line.endswith('{'):
708                    itspath.append(line[:-1].strip())
709                    itspaths.append(itspath[:])
710                elif itspath and itspath[-1] == 'signature':
711                    itsdotpath = '.'.join(itspath)
712                    if not itsdotpath in sigs:
713                        sigs[itsdotpath] = {}
714                    if not '=' in line or not line.endswith(';'):
715                        self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
716                    key, value = line.split('=', 1)
717                    sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
718
719        for reqpath in req_itspaths:
720            if not reqpath in itspaths:
721                self.fail('Missing section in its file: %s' % reqpath)
722
723        reqsigvalues_image = {
724            'algo': '"sha256,rsa2048"',
725            'key-name-hint': '"spl-cascaded-oe-selftest"',
726        }
727
728        for itspath, values in sigs.items():
729            reqsigvalues = reqsigvalues_image
730            for reqkey, reqvalue in reqsigvalues.items():
731                value = values.get(reqkey, None)
732                if value is None:
733                    self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
734                self.assertEqual(value, reqvalue)
735
736        # Dump the image to see if it really got signed
737        uboot_tools_bindir = self._setup_uboot_tools_native()
738        dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
739        in_signed = None
740        signed_sections = {}
741        for line in dumpimage_result.output.splitlines():
742            if line.startswith((' Image')):
743                in_signed = re.search(r'\((.*)\)', line).groups()[0]
744            elif re.match(' \w', line):
745                in_signed = None
746            elif in_signed:
747                if not in_signed in signed_sections:
748                    signed_sections[in_signed] = {}
749                key, value = line.split(':', 1)
750                signed_sections[in_signed][key.strip()] = value.strip()
751        self.assertIn('uboot', signed_sections)
752        self.assertIn('fdt', signed_sections)
753        for signed_section, values in signed_sections.items():
754            value = values.get('Sign algo', None)
755            self.assertEqual(value, 'sha256,rsa2048:spl-cascaded-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
756            value = values.get('Sign value', None)
757            self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
758
759        # Check for SPL_MKIMAGE_SIGN_ARGS
760        # Looks like mkimage supports to add a comment but does not support to read it back.
761        found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment)
762        self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.")
763
764        # Verify the signature
765        self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path,
766                                         os.path.join(deploy_dir_image, 'u-boot-spl.dtb'))
767
768
769    def test_initramfs_bundle(self):
770        """
771        Summary:     Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its)
772                     The FIT settings are set by the test case.
773                     The machine used is beaglebone-yocto.
774        Expected:    1. The ITS is generated with initramfs bundle support
775                     2. All the fields in the kernel node are as expected (matching the
776                        conf settings)
777                     3. The kernel is included in all the available configurations and
778                        its hash is included in the configuration signature
779
780        Product:     oe-core
781        Author:      Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
782        """
783
784        config = """
785DISTRO="poky"
786MACHINE = "beaglebone-yocto"
787INITRAMFS_IMAGE_BUNDLE = "1"
788INITRAMFS_IMAGE = "core-image-minimal-initramfs"
789INITRAMFS_SCRIPTS = ""
790UBOOT_MACHINE = "am335x_evm_defconfig"
791KERNEL_CLASSES = " kernel-fitimage "
792KERNEL_IMAGETYPES = "fitImage"
793UBOOT_SIGN_ENABLE = "1"
794UBOOT_SIGN_KEYNAME = "beaglebonekey"
795UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
796UBOOT_DTB_BINARY = "u-boot.dtb"
797UBOOT_ENTRYPOINT  = "0x80000000"
798UBOOT_LOADADDRESS = "0x80000000"
799UBOOT_DTB_LOADADDRESS = "0x82000000"
800UBOOT_ARCH = "arm"
801UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
802UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
803UBOOT_EXTLINUX = "0"
804FIT_GENERATE_KEYS = "1"
805KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
806FIT_KERNEL_COMP_ALG = "none"
807FIT_HASH_ALG = "sha256"
808"""
809        config = self._config_add_uboot_env(config)
810        self.write_config(config)
811
812        # fitImage is created as part of linux recipe
813        bitbake("virtual/kernel")
814
815        bb_vars = get_bb_vars([
816            'DEPLOY_DIR_IMAGE',
817            'FIT_HASH_ALG',
818            'FIT_KERNEL_COMP_ALG',
819            'INITRAMFS_IMAGE',
820            'MACHINE',
821            'UBOOT_ARCH',
822            'UBOOT_ENTRYPOINT',
823            'UBOOT_LOADADDRESS',
824            'UBOOT_MKIMAGE_KERNEL_TYPE'
825            ],
826            'virtual/kernel')
827        fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
828                    "fitImage-its-%s-%s-%s" % (bb_vars['INITRAMFS_IMAGE'], bb_vars['MACHINE'], bb_vars['MACHINE']))
829        fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],"fitImage")
830
831        self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
832        self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
833
834        its_file = open(fitimage_its_path)
835
836        its_lines = [line.strip() for line in its_file.readlines()]
837
838        exp_node_lines = [
839            'kernel-1 {',
840            'description = "Linux kernel";',
841            'data = /incbin/("linux.bin");',
842            'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";',
843            'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";',
844            'os = "linux";',
845            'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";',
846            'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;',
847            'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;',
848            'hash-1 {',
849            'algo = "' + str(bb_vars['FIT_HASH_ALG']) +'";',
850            '};',
851            '};'
852        ]
853
854        node_str = exp_node_lines[0]
855
856        print ("checking kernel node\n")
857        self.assertIn(node_str, its_lines)
858
859        node_start_idx = its_lines.index(node_str)
860        node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))]
861
862        # Remove the absolute path. This refers to WORKDIR which is not always predictable.
863        re_data = re.compile(r'^data = /incbin/\(.*/linux\.bin"\);$')
864        node = [re.sub(re_data, 'data = /incbin/("linux.bin");', cfg_str) for cfg_str in node]
865
866        self.assertEqual(node, exp_node_lines, "kernel node does not match expectation")
867
868        rx_configs = re.compile("^conf-.*")
869        its_configs = list(filter(rx_configs.match, its_lines))
870
871        for cfg_str in its_configs:
872            cfg_start_idx = its_lines.index(cfg_str)
873            line_idx = cfg_start_idx + 2
874            node_end = False
875            while node_end == False:
876                if its_lines[line_idx] == "};" and its_lines[line_idx-1] == "};" :
877                    node_end = True
878                line_idx = line_idx + 1
879
880            node = its_lines[cfg_start_idx:line_idx]
881            print("checking configuration " + cfg_str.rstrip(" {"))
882            rx_desc_line = re.compile(r'^description = ".*Linux kernel.*')
883            self.assertEqual(len(list(filter(rx_desc_line.match, node))), 1, "kernel keyword not found in the description line")
884
885            self.assertIn('kernel = "kernel-1";', node)
886
887            rx_sign_line = re.compile(r'^sign-images = .*kernel.*')
888            self.assertEqual(len(list(filter(rx_sign_line.match, node))), 1, "kernel hash not signed")
889
890        # Verify the signature
891        uboot_tools_bindir = self._setup_uboot_tools_native()
892        self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], 'am335x-bone.dtb'))
893
894        # Check if the u-boot boot.scr script is in the fitImage
895        dumpimage_result = self._run_dumpimage(fitimage_path, uboot_tools_bindir)
896        self._verify_fitimage_uboot_env(dumpimage_result)
897