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