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 test_fit_image(self):
15        """
16        Summary:     Check if FIT image and Image Tree Source (its) are built
17                     and the Image Tree Source has the correct fields.
18        Expected:    1. fitImage and fitImage-its can be built
19                     2. The type, load address, entrypoint address and
20                     default values of kernel and ramdisk are as expected
21                     in the Image Tree Source. Not all the fields are tested,
22                     only the key fields that wont vary between different
23                     architectures.
24        Product:     oe-core
25        Author:      Usama Arif <usama.arif@arm.com>
26        """
27        config = """
28# Enable creation of fitImage
29KERNEL_IMAGETYPE = "Image"
30KERNEL_IMAGETYPES += " fitImage "
31KERNEL_CLASSES = " kernel-fitimage "
32
33# RAM disk variables including load address and entrypoint for kernel and RAM disk
34IMAGE_FSTYPES += "cpio.gz"
35INITRAMFS_IMAGE = "core-image-minimal"
36# core-image-minimal is used as initramfs here, drop the rootfs suffix
37IMAGE_NAME_SUFFIX:pn-core-image-minimal = ""
38UBOOT_RD_LOADADDRESS = "0x88000000"
39UBOOT_RD_ENTRYPOINT = "0x88000000"
40UBOOT_LOADADDRESS = "0x80080000"
41UBOOT_ENTRYPOINT = "0x80080000"
42FIT_DESC = "A model description"
43"""
44        self.write_config(config)
45
46        # fitImage is created as part of linux recipe
47        image = "virtual/kernel"
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
79        with open(fitimage_its_path) as its_file:
80            field_index = 0
81            for line in its_file:
82                if field_index == len(its_field_check):
83                    break
84                if its_field_check[field_index] in line:
85                    field_index +=1
86
87        if field_index != len(its_field_check): # if its equal, the test passed
88            self.assertTrue(field_index == len(its_field_check),
89                "Fields in Image Tree Source File %s did not match, error in finding %s"
90                % (fitimage_its_path, its_field_check[field_index]))
91
92
93    def test_sign_fit_image(self):
94        """
95        Summary:     Check if FIT image and Image Tree Source (its) are created
96                     and signed correctly.
97        Expected:    1) its and FIT image are built successfully
98                     2) Scanning the its file indicates signing is enabled
99                        as requested by UBOOT_SIGN_ENABLE (using keys generated
100                        via FIT_GENERATE_KEYS)
101                     3) Dumping the FIT image indicates signature values
102                        are present (including for images as enabled via
103                        FIT_SIGN_INDIVIDUAL)
104                     4) Examination of the do_assemble_fitimage runfile/logfile
105                        indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and
106                        UBOOT_MKIMAGE_SIGN_ARGS are working as expected.
107        Product:     oe-core
108        Author:      Paul Eggleton <paul.eggleton@microsoft.com> based upon
109                     work by Usama Arif <usama.arif@arm.com>
110        """
111        config = """
112# Enable creation of fitImage
113MACHINE = "beaglebone-yocto"
114KERNEL_IMAGETYPES += " fitImage "
115KERNEL_CLASSES = " kernel-fitimage test-mkimage-wrapper "
116UBOOT_SIGN_ENABLE = "1"
117FIT_GENERATE_KEYS = "1"
118UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
119UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
120UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
121FIT_SIGN_INDIVIDUAL = "1"
122UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
123"""
124        self.write_config(config)
125
126        # fitImage is created as part of linux recipe
127        image = "virtual/kernel"
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
150        itspath = []
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
172        for reqpath in req_itspaths:
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        """
241        Summary:     Check if Uboot FIT image and Image Tree Source
242                     (its) are built and the Image Tree Source has the
243                     correct fields.
244        Expected:    1. u-boot-fitImage and u-boot-its can be built
245                     2. The type, load address, entrypoint address and
246                     default values of U-boot image are correct in the
247                     Image Tree Source. Not all the fields are tested,
248                     only the key fields that wont vary between
249                     different architectures.
250        Product:     oe-core
251        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
252                     based on work by Usama Arif <usama.arif@arm.com>
253        """
254        config = """
255# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
256MACHINE = "qemuarm"
257UBOOT_MACHINE = "am57xx_evm_defconfig"
258SPL_BINARY = "MLO"
259
260# Enable creation of the U-Boot fitImage
261UBOOT_FITIMAGE_ENABLE = "1"
262
263# (U-boot) fitImage properties
264UBOOT_LOADADDRESS = "0x80080000"
265UBOOT_ENTRYPOINT = "0x80080000"
266UBOOT_FIT_DESC = "A model description"
267
268# Enable creation of Kernel fitImage
269KERNEL_IMAGETYPES += " fitImage "
270KERNEL_CLASSES = " kernel-fitimage"
271UBOOT_SIGN_ENABLE = "1"
272FIT_GENERATE_KEYS = "1"
273UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
274UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
275UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
276FIT_SIGN_INDIVIDUAL = "1"
277"""
278        self.write_config(config)
279
280        # The U-Boot fitImage is created as part of the U-Boot recipe
281        bitbake("virtual/bootloader")
282
283        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
284        machine = get_bb_var('MACHINE')
285        fitimage_its_path = os.path.join(deploy_dir_image,
286            "u-boot-its-%s" % (machine,))
287        fitimage_path = os.path.join(deploy_dir_image,
288            "u-boot-fitImage-%s" % (machine,))
289
290        self.assertTrue(os.path.exists(fitimage_its_path),
291            "%s image tree source doesn't exist" % (fitimage_its_path))
292        self.assertTrue(os.path.exists(fitimage_path),
293            "%s FIT image doesn't exist" % (fitimage_path))
294
295        # Check that the type, load address, entrypoint address and default
296        # values for kernel and ramdisk in Image Tree Source are as expected.
297        # The order of fields in the below array is important. Not all the
298        # fields are tested, only the key fields that wont vary between
299        # different architectures.
300        its_field_check = [
301            'description = "A model description";',
302            'type = "standalone";',
303            'load = <0x80080000>;',
304            'entry = <0x80080000>;',
305            'default = "conf";',
306            'loadables = "uboot";',
307            'fdt = "fdt";'
308            ]
309
310        with open(fitimage_its_path) as its_file:
311            field_index = 0
312            for line in its_file:
313                if field_index == len(its_field_check):
314                    break
315                if its_field_check[field_index] in line:
316                    field_index +=1
317
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
323    def test_uboot_sign_fit_image(self):
324        """
325        Summary:     Check if Uboot FIT image and Image Tree Source
326                     (its) are built and the Image Tree Source has the
327                     correct fields, in the scenario where the Kernel
328                     is also creating/signing it's fitImage.
329        Expected:    1. u-boot-fitImage and u-boot-its can be built
330                     2. The type, load address, entrypoint address and
331                     default values of U-boot image are correct in the
332                     Image Tree Source. Not all the fields are tested,
333                     only the key fields that wont vary between
334                     different architectures.
335        Product:     oe-core
336        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
337                     based on work by Usama Arif <usama.arif@arm.com>
338        """
339        config = """
340# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
341MACHINE = "qemuarm"
342UBOOT_MACHINE = "am57xx_evm_defconfig"
343SPL_BINARY = "MLO"
344
345# Enable creation of the U-Boot fitImage
346UBOOT_FITIMAGE_ENABLE = "1"
347
348# (U-boot) fitImage properties
349UBOOT_LOADADDRESS = "0x80080000"
350UBOOT_ENTRYPOINT = "0x80080000"
351UBOOT_FIT_DESC = "A model description"
352KERNEL_IMAGETYPES += " fitImage "
353KERNEL_CLASSES = " kernel-fitimage "
354INHERIT += "test-mkimage-wrapper"
355UBOOT_SIGN_ENABLE = "1"
356FIT_GENERATE_KEYS = "1"
357UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
358UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
359UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
360FIT_SIGN_INDIVIDUAL = "1"
361UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
362"""
363        self.write_config(config)
364
365        # The U-Boot fitImage is created as part of the U-Boot recipe
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
408
409    def test_sign_standalone_uboot_fit_image(self):
410        """
411        Summary:     Check if U-Boot FIT image and Image Tree Source (its) are
412                     created and signed correctly for the scenario where only
413                     the U-Boot proper fitImage is being created and signed.
414        Expected:    1) U-Boot its and FIT image are built successfully
415                     2) Scanning the its file indicates signing is enabled
416                        as requested by SPL_SIGN_ENABLE (using keys generated
417                        via UBOOT_FIT_GENERATE_KEYS)
418                     3) Dumping the FIT image indicates signature values
419                        are present
420                     4) Examination of the do_uboot_assemble_fitimage
421                     runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
422                     and SPL_MKIMAGE_SIGN_ARGS are working as expected.
423        Product:     oe-core
424        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
425                     work by Paul Eggleton <paul.eggleton@microsoft.com> and
426                     Usama Arif <usama.arif@arm.com>
427        """
428        config = """
429# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
430# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
431MACHINE = "qemuarm"
432UBOOT_MACHINE = "am57xx_evm_defconfig"
433SPL_BINARY = "MLO"
434# The kernel-fitimage class is a dependency even if we're only
435# creating/signing the U-Boot fitImage
436KERNEL_CLASSES = " kernel-fitimage"
437INHERIT += "test-mkimage-wrapper"
438# Enable creation and signing of the U-Boot fitImage
439UBOOT_FITIMAGE_ENABLE = "1"
440SPL_SIGN_ENABLE = "1"
441SPL_SIGN_KEYNAME = "spl-oe-selftest"
442SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
443UBOOT_DTB_BINARY = "u-boot.dtb"
444UBOOT_ENTRYPOINT  = "0x80000000"
445UBOOT_LOADADDRESS = "0x80000000"
446UBOOT_DTB_LOADADDRESS = "0x82000000"
447UBOOT_ARCH = "arm"
448SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
449SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
450UBOOT_EXTLINUX = "0"
451UBOOT_FIT_GENERATE_KEYS = "1"
452UBOOT_FIT_HASH_ALG = "sha256"
453"""
454        self.write_config(config)
455
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
556    def test_sign_cascaded_uboot_fit_image(self):
557        """
558        Summary:     Check if U-Boot FIT image and Image Tree Source (its) are
559                     created and signed correctly for the scenario where both
560                     U-Boot proper and Kernel fitImages are being created and
561                     signed.
562        Expected:    1) U-Boot its and FIT image are built successfully
563                     2) Scanning the its file indicates signing is enabled
564                        as requested by SPL_SIGN_ENABLE (using keys generated
565                        via UBOOT_FIT_GENERATE_KEYS)
566                     3) Dumping the FIT image indicates signature values
567                        are present
568                     4) Examination of the do_uboot_assemble_fitimage
569                     runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
570                     and SPL_MKIMAGE_SIGN_ARGS are working as expected.
571        Product:     oe-core
572        Author:      Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
573                     work by Paul Eggleton <paul.eggleton@microsoft.com> and
574                     Usama Arif <usama.arif@arm.com>
575        """
576        config = """
577# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
578# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
579MACHINE = "qemuarm"
580UBOOT_MACHINE = "am57xx_evm_defconfig"
581SPL_BINARY = "MLO"
582# Enable creation and signing of the U-Boot fitImage
583UBOOT_FITIMAGE_ENABLE = "1"
584SPL_SIGN_ENABLE = "1"
585SPL_SIGN_KEYNAME = "spl-cascaded-oe-selftest"
586SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
587UBOOT_DTB_BINARY = "u-boot.dtb"
588UBOOT_ENTRYPOINT  = "0x80000000"
589UBOOT_LOADADDRESS = "0x80000000"
590UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
591UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded Kernel comment'"
592UBOOT_DTB_LOADADDRESS = "0x82000000"
593UBOOT_ARCH = "arm"
594SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
595SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
596UBOOT_EXTLINUX = "0"
597UBOOT_FIT_GENERATE_KEYS = "1"
598UBOOT_FIT_HASH_ALG = "sha256"
599KERNEL_IMAGETYPES += " fitImage "
600KERNEL_CLASSES = " kernel-fitimage "
601INHERIT += "test-mkimage-wrapper"
602UBOOT_SIGN_ENABLE = "1"
603FIT_GENERATE_KEYS = "1"
604UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
605UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
606UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
607FIT_SIGN_INDIVIDUAL = "1"
608"""
609        self.write_config(config)
610
611        # The U-Boot fitImage is created as part of the U-Boot recipe
612        bitbake("virtual/bootloader")
613
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
634        itspath = []
635        itspaths = []
636        linect = 0
637        sigs = {}
638        with open(fitimage_its_path) as its_file:
639            linect += 1
640            for line in its_file:
641                line = line.strip()
642                if line.endswith('};'):
643                    itspath.pop()
644                elif line.endswith('{'):
645                    itspath.append(line[:-1].strip())
646                    itspaths.append(itspath[:])
647                elif itspath and itspath[-1] == 'signature':
648                    itsdotpath = '.'.join(itspath)
649                    if not itsdotpath in sigs:
650                        sigs[itsdotpath] = {}
651                    if not '=' in line or not line.endswith(';'):
652                        self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
653                    key, value = line.split('=', 1)
654                    sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
655
656        for reqpath in req_itspaths:
657            if not reqpath in itspaths:
658                self.fail('Missing section in its file: %s' % reqpath)
659
660        reqsigvalues_image = {
661            'algo': '"sha256,rsa2048"',
662            'key-name-hint': '"spl-cascaded-oe-selftest"',
663        }
664
665        for itspath, values in sigs.items():
666            reqsigvalues = reqsigvalues_image
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
724        Product:     oe-core
725        Author:      Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
726        """
727
728        config = """
729DISTRO="poky"
730MACHINE = "beaglebone-yocto"
731INITRAMFS_IMAGE_BUNDLE = "1"
732INITRAMFS_IMAGE = "core-image-minimal-initramfs"
733INITRAMFS_SCRIPTS = ""
734UBOOT_MACHINE = "am335x_evm_defconfig"
735KERNEL_CLASSES = " kernel-fitimage "
736KERNEL_IMAGETYPES = "fitImage"
737UBOOT_SIGN_ENABLE = "1"
738UBOOT_SIGN_KEYNAME = "beaglebonekey"
739UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
740UBOOT_DTB_BINARY = "u-boot.dtb"
741UBOOT_ENTRYPOINT  = "0x80000000"
742UBOOT_LOADADDRESS = "0x80000000"
743UBOOT_DTB_LOADADDRESS = "0x82000000"
744UBOOT_ARCH = "arm"
745UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
746UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
747UBOOT_EXTLINUX = "0"
748FIT_GENERATE_KEYS = "1"
749KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
750FIT_KERNEL_COMP_ALG = "none"
751FIT_HASH_ALG = "sha256"
752"""
753        self.write_config(config)
754
755        # fitImage is created as part of linux recipe
756        bitbake("virtual/kernel")
757
758        image_type = get_bb_var('INITRAMFS_IMAGE')
759        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
760        machine = get_bb_var('MACHINE')
761        fitimage_its_path = os.path.join(deploy_dir_image,
762                    "fitImage-its-%s-%s-%s" % (image_type, machine, machine))
763        fitimage_path = os.path.join(deploy_dir_image,"fitImage")
764
765        self.assertTrue(os.path.exists(fitimage_its_path),
766            "%s image tree source doesn't exist" % (fitimage_its_path))
767        self.assertTrue(os.path.exists(fitimage_path),
768            "%s FIT image doesn't exist" % (fitimage_path))
769
770        kernel_load = str(get_bb_var('UBOOT_LOADADDRESS'))
771        kernel_entry = str(get_bb_var('UBOOT_ENTRYPOINT'))
772        kernel_type = str(get_bb_var('UBOOT_MKIMAGE_KERNEL_TYPE'))
773        kernel_compression = str(get_bb_var('FIT_KERNEL_COMP_ALG'))
774        uboot_arch = str(get_bb_var('UBOOT_ARCH'))
775        fit_hash_alg = str(get_bb_var('FIT_HASH_ALG'))
776
777        its_file = open(fitimage_its_path)
778
779        its_lines = [line.strip() for line in its_file.readlines()]
780
781        exp_node_lines = [
782            'kernel-1 {',
783            'description = "Linux kernel";',
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
797        node_str = exp_node_lines[0]
798
799        test_passed = False
800
801        print ("checking kernel node\n")
802
803        if node_str in its_lines:
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
832            if 'kernel = "kernel-1";' not in node:
833                self.assertTrue(test_passed == True,"kernel line not found")
834                break
835            else:
836                print("kernel line found")
837
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
845            test_passed = True
846            self.assertTrue(test_passed == True,"Initramfs bundle test success")
847