The boot and load extensions in the x509 certificate are required for requesting the secure entity (TIFS) to boot a core. These fields are defined in the binman node for each core that must be booted by TIFS and must be included when generating the signed certificate.
Add support to parse the boot and load extension properties from the binman node and populate them into the certificate. If any of the mandatory properties for an extension are missing, that respective extension section is NOT added to the certificate. Add test coverage for the boot and load extension functionality in ti-secure certificates. The test reads the generated config file and verifies the sysfw_boot_seq and sysfw_image_load sections contain the expected values from the DTS. The negative test verifies that partially specified boot/load extension properties trigger an error. Signed-off-by: Beleswar Padhi <[email protected]> Reviewed-by: Simon Glass <[email protected]> --- v6: Changelog: 1. None Link to v5: https://lore.kernel.org/all/[email protected]/ v5: Changelog: 1. Log missing properties and raise exception rather than returning silently. 2. Squash [PATCH v4 07/11] and [PATCH v4 08/11] into [PATCH v5 4/7] 3. Document properties in function docstring. 4. Update tests to check the load and boot sections contain the expected values from test DTS. Add negative tests to check it raises an error when some of the properties are missing. 5. Add R/B tags. Link to v4: https://lore.kernel.org/all/[email protected]/ v4: Changelog: 1. None Link to v3: https://lore.kernel.org/all/[email protected]/ v3: Changelog: 1. New patch. Add support to sign HSM firmware here in U-Boot. tools/binman/btool/openssl.py | 49 ++++++++++++-- tools/binman/etype/ti_secure.py | 65 +++++++++++++++++++ tools/binman/etype/x509_cert.py | 4 +- tools/binman/ftest.py | 45 +++++++++++++ .../test/vendor/ti_secure_boot_load_ext.dts | 23 +++++++ .../ti_secure_boot_load_ext_partial.dts | 20 ++++++ 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 tools/binman/test/vendor/ti_secure_boot_load_ext.dts create mode 100644 tools/binman/test/vendor/ti_secure_boot_load_ext_partial.dts diff --git a/tools/binman/btool/openssl.py b/tools/binman/btool/openssl.py index b26f087c447..39ea4a51356 100644 --- a/tools/binman/btool/openssl.py +++ b/tools/binman/btool/openssl.py @@ -82,7 +82,8 @@ imageSize = INTEGER:{len(indata)} return self.run_cmd(*args) def x509_cert_sysfw(self, cert_fname, input_fname, key_fname, sw_rev, - config_fname, req_dist_name_dict, firewall_cert_data): + config_fname, req_dist_name_dict, firewall_cert_data, + boot_ext_data, load_ext_data): """Create a certificate to be booted by system firmware Args: @@ -101,12 +102,52 @@ imageSize = INTEGER:{len(indata)} extended certificate - certificate (str): Extended firewall certificate with the information for the firewall configurations. + boot_ext_data (dict): + - proc_id (int): The processor ID of core being booted + - flags_set (int): The config flags to set for core being booted + - flags_clr (int): The config flags to clear for core being booted + - reset_vector (int): The location of reset vector for core being + booted + load_ext_data (dict): + - dest_addr (int): The address to which image has to be copied + - auth_type (int): Contains the host ID for core being booted and + how the image is to be copied Returns: str: Tool output """ indata = tools.read_file(input_fname) hashval = hashlib.sha512(indata).hexdigest() + + if boot_ext_data is not None: + boot_ext = f''' +[ sysfw_boot_seq ] +bootCore = INTEGER:{boot_ext_data['proc_id']} +bootCoreOpts_set = INTEGER:{boot_ext_data['flags_set']} +bootCoreOpts_clr = INTEGER:{boot_ext_data['flags_clr']} +resetVec = FORMAT:HEX,OCT:{boot_ext_data['reset_vector']:08x} +# Reserved for future use +flagsValid = FORMAT:HEX,OCT:00000000 +rsvd1 = INTEGER:0x00 +rsvd2 = INTEGER:0x00 +rsvd3 = INTEGER:0x00 +''' + else: + boot_ext = "" + + if load_ext_data is not None: + load_ext = f''' +[ sysfw_image_load ] +destAddr = FORMAT:HEX,OCT:{load_ext_data['dest_addr']:08x} +authInPlace = INTEGER:{load_ext_data['auth_type']} +''' + else: + load_ext = f''' +[ sysfw_image_load ] +destAddr = FORMAT:HEX,OCT:00000000 +authInPlace = INTEGER:{hex(firewall_cert_data['auth_in_place'])} +''' + with open(config_fname, 'w', encoding='utf-8') as outf: print(f'''[ req ] distinguished_name = req_distinguished_name @@ -138,9 +179,9 @@ shaType = OID:2.16.840.1.101.3.4.2.3 shaValue = FORMAT:HEX,OCT:{hashval} imageSize = INTEGER:{len(indata)} -[ sysfw_image_load ] -destAddr = FORMAT:HEX,OCT:00000000 -authInPlace = INTEGER:{hex(firewall_cert_data['auth_in_place'])} +{boot_ext} + +{load_ext} [ firewall ] numFirewallRegions = INTEGER:{firewall_cert_data['num_firewalls']} diff --git a/tools/binman/etype/ti_secure.py b/tools/binman/etype/ti_secure.py index f6caa0286d9..9573fab088f 100644 --- a/tools/binman/etype/ti_secure.py +++ b/tools/binman/etype/ti_secure.py @@ -59,6 +59,33 @@ class Entry_ti_secure(Entry_x509_cert): ( 0x02: Move the authenticated binary back to the header ) - Upper Byte - The Host ID of the core owning the firewall + - proc-id: (optional) The processor ID of the core being booted. + Used in TIFS Boot Extension to identify the target core. + + - flags-set: (optional) Configuration flags to set for the core being + booted. Used in TIFS Boot Extension. + + - flags-clr: (optional) Configuration flags to clear for the core being + booted. Used in TIFS Boot Extension. + + - reset-vector: (optional) The memory address where the core should + start execution after being released from reset. Used in TIFS Boot + Extension. + + - dest-addr: (optional) The destination address to which the image + should be copied. Used in TIFS Load Extension. + + - auth-type: (optional) This is an integer field that contains two pieces + of information used in TIFS Load Extension: + + - Upper Byte - The Host ID of the core being booted + - Lower Byte - Load type (e.g., 0x00: Copy to dest-addr, + 0x02: Authentication in place) + + Note: The proc-id, flags-set, flags-clr, reset-vector, dest-addr, and + auth-type properties form a group. If any are present, all must be + specified together to configure loadable core boot parameters. + Output files: - input.<unique_name> - input file passed to openssl - config.<unique_name> - input file generated for openssl (which is @@ -116,6 +143,7 @@ class Entry_ti_secure(Entry_x509_cert): if auth_in_place: self.firewall_cert_data['auth_in_place'] = auth_in_place self.ReadFirewallNode() + self.ReadLoadableCoreNode() self.sha = fdt_util.GetInt(self._node, 'sha', 512) self.req_dist_name = {'C': 'US', 'ST': 'TX', @@ -126,6 +154,43 @@ class Entry_ti_secure(Entry_x509_cert): 'emailAddress': '[email protected]'} self.debug = fdt_util.GetBool(self._node, 'debug', False) + def ReadLoadableCoreNode(self): + # Map DTS property names (with dashes) to Python dict keys (with underscores) + boot_ext_props = { + 'proc-id': 'proc_id', + 'flags-set': 'flags_set', + 'flags-clr': 'flags_clr', + 'reset-vector': 'reset_vector' + } + load_ext_props = { + 'dest-addr': 'dest_addr', + 'auth-type': 'auth_type' + } + + self.boot_ext = self.ReadIntProps(boot_ext_props) + self.load_ext = self.ReadIntProps(load_ext_props) + + def ReadIntProps(self, props_map): + props_dict = {} + missing_props = [] + + for dts_prop, dict_key in props_map.items(): + val = fdt_util.GetInt(self._node, dts_prop) + if val is None: + missing_props.append(dts_prop) + else: + props_dict[dict_key] = val + + # If all properties are missing, return None + if len(missing_props) == len(props_map): + return None + + # If some but not all properties are present, raise an error + if len(missing_props) > 0: + self.Raise(f"Loadable core configuration incomplete. Missing properties: {','.join(missing_props)}") + + return props_dict + def ReadFirewallNode(self): self.firewall_cert_data['certificate'] = "" self.firewall_cert_data['num_firewalls'] = 0 diff --git a/tools/binman/etype/x509_cert.py b/tools/binman/etype/x509_cert.py index b6e8b0b4fb0..b6028f6be84 100644 --- a/tools/binman/etype/x509_cert.py +++ b/tools/binman/etype/x509_cert.py @@ -102,7 +102,9 @@ class Entry_x509_cert(Entry_collection): config_fname=config_fname, sw_rev=self.sw_rev, req_dist_name_dict=self.req_dist_name, - firewall_cert_data=self.firewall_cert_data) + firewall_cert_data=self.firewall_cert_data, + boot_ext_data=self.boot_ext, + load_ext_data=self.load_ext) elif type == 'rom': stdout = self.openssl.x509_cert_rom( cert_fname=output_fname, diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index a0e8bde7901..f7c5abdabb0 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -7296,6 +7296,51 @@ fdt fdtmap Extract the devicetree blob from the fdtmap entry_args=entry_args)[0] self.assertGreater(len(data), len(TI_UNSECURE_DATA)) + def testPackTiSecureBootLoadExt(self): + """Test that an image with TI secured binary having boot & load extensions can be created""" + keyfile = self.TestFile('security/key.key') + entry_args = { + 'keyfile': keyfile, + } + data = self._DoReadFileDtb('vendor/ti_secure_boot_load_ext.dts', + entry_args=entry_args)[0] + self.assertGreater(len(data), len(TI_UNSECURE_DATA)) + + # Verify that boot and load extensions are correctly written to config file + image = control.images['image'] + entries = image.GetEntries() + ti_secure_entry = entries['ti-secure'] + uniq = ti_secure_entry.GetUniqueName() + config_fname = tools.get_output_filename('config.%s' % uniq) + + # Read the generated config file + with open(config_fname, 'r') as f: + config_data = f.read() + + # Verify boot extension values from the DTS + self.assertIn('[ sysfw_boot_seq ]', config_data) + self.assertIn('bootCore = INTEGER:32', config_data) # 0x20 = 32 + self.assertIn('bootCoreOpts_set = INTEGER:1', config_data) # 0x00000001 = 1 + self.assertIn('bootCoreOpts_clr = INTEGER:2', config_data) # 0x00000002 = 2 + self.assertIn('resetVec = FORMAT:HEX,OCT:70000000', config_data) # 0x70000000 + + # Verify load extension values from the DTS + self.assertIn('[ sysfw_image_load ]', config_data) + self.assertIn('destAddr = FORMAT:HEX,OCT:80000000', config_data) # 0x80000000 + self.assertIn('authInPlace = INTEGER:2', config_data) # 0x02 + + def testPackTiSecureBootLoadExtPartial(self): + """Test that partially specified boot/load extension properties raise an error""" + keyfile = self.TestFile('security/key.key') + entry_args = { + 'keyfile': keyfile, + } + with self.assertRaises(ValueError) as e: + self._DoReadFileDtb('vendor/ti_secure_boot_load_ext_partial.dts', + entry_args=entry_args) + self.assertIn('Loadable core configuration incomplete', str(e.exception)) + self.assertIn('Missing properties:', str(e.exception)) + def testEncryptedNoAlgo(self): """Test encrypted node with missing required properties""" with self.assertRaises(ValueError) as e: diff --git a/tools/binman/test/vendor/ti_secure_boot_load_ext.dts b/tools/binman/test/vendor/ti_secure_boot_load_ext.dts new file mode 100644 index 00000000000..226415f39ea --- /dev/null +++ b/tools/binman/test/vendor/ti_secure_boot_load_ext.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + ti-secure { + content = <&unsecure_binary>; + proc-id = <0x20>; + flags-set = <0x00000001>; + flags-clr = <0x00000002>; + reset-vector = <0x70000000>; + dest-addr = <0x80000000>; + auth-type = <0x02>; + }; + unsecure_binary: blob-ext { + filename = "ti_unsecure.bin"; + }; + }; +}; diff --git a/tools/binman/test/vendor/ti_secure_boot_load_ext_partial.dts b/tools/binman/test/vendor/ti_secure_boot_load_ext_partial.dts new file mode 100644 index 00000000000..e90d27cb087 --- /dev/null +++ b/tools/binman/test/vendor/ti_secure_boot_load_ext_partial.dts @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + ti-secure { + content = <&unsecure_binary>; + proc-id = <0x20>; + flags-set = <0x00000001>; + // Missing: flags-clr, reset-vector, dest-addr, auth-type + }; + unsecure_binary: blob-ext { + filename = "ti_unsecure.bin"; + }; + }; +}; -- 2.34.1
