From 69f1f086ff33289bc5b2d6cd772e942270e1777d Mon Sep 17 00:00:00 2001 From: Robert Stypa Date: Mon, 25 Mar 2024 13:11:17 +0100 Subject: [PATCH 01/67] feat: migration to NCS Ref: NCSDK-26629 Signed-off-by: Robert Stypa --- .gitignore | 1 + ncs/Kconfig | 49 ++++- ncs/root_with_binary_nordic_top.yaml.jinja2 | 225 ++++++++++++++++++++ 3 files changed, 264 insertions(+), 11 deletions(-) create mode 100644 ncs/root_with_binary_nordic_top.yaml.jinja2 diff --git a/.gitignore b/.gitignore index 66fb8559..f4990a73 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ __pycache__ /doc/build/* /build/* +suit-generator.log* diff --git a/ncs/Kconfig b/ncs/Kconfig index a53dc2e3..92a5c3ba 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -4,6 +4,35 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +# The following nine KConfigs are maintained for backward compatibility with the sdk-nrf-next repository. +# They are slated for removal once sdk-nrf-next is no longer in use. +config SOC_NRF54H20_ENGA_CPUAPP + bool +config SOC_NRF54H20_CPUAPP + bool +config SOC_NRF54H20_CPUSEC + bool +config SOC_NRF54H20_CPUSYS + bool +config SSF_SUIT_SERVICE_ENABLED + bool +config INCLUDE_SECDOM + bool +config IS_SECURE_DOMAIN_FW + bool +config HW_REVISION_SOC1 + bool +config INCLUDE_SYSCTRL + bool + +config SUIT_ENVELOPE_TEMPLATE + string "Path to the envelope template" + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" if SOC_NRF54H20_ENGA_CPUAPP + +config SUIT_ENVELOPE_TARGET + string "Target name inside the envelope templates" + default "app" if SOC_NRF54H20_ENGA_CPUAPP + menuconfig SUIT_ENVELOPE bool "Create SUIT envelope" help @@ -26,6 +55,7 @@ config SUIT_ENVELOPE_SEQUENCE_NUM config SUIT_ENVELOPE_DEFAULT_TEMPLATE string "Path to the default envelope template (deprecated)" default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_nordic_top_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_ENGA_CPUAPP default SUIT_ENVELOPE_SYSCTRL_TEMPLATE if SOC_NRF54H20_CPUSYS default SUIT_ENVELOPE_SECDOM_TEMPLATE if SOC_NRF54H20_CPUSEC help @@ -80,7 +110,7 @@ config SUIT_ENVELOPE_802154_RPMSG_SUBIMAGE_TEMPLATE config SUIT_ENVELOPE_EDITABLE_TEMPLATES_LOCATION string "Path to the folder with envelope templates" - default "../../" + default "../" help Path to the folder containing editable templates used to create binary envelopes. Input templates are created by the build system during first build from the SUIT_ENVELOPE_DEFAULT_TEMPLATE. @@ -117,15 +147,6 @@ config SUIT_ENVELOPE_SECDOM_TEMPLATE Jinja2 template file used to generate yaml file for secure domain update. default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/secdom_update_envelope.yaml.jinja2" -config SUIT_ENVELOPE_SYSCTRL_TEMPLATE - string "Path to the default system controller envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/sysctrl_envelope.yaml.jinja2" - help - Path to the default system controller envelope template, that is used if the system controller directory does not - contain an input system controller envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - config SUIT_ENVELOPE_SECDOM_IMPRIMATUR_SICR_BIN string "Name of Imprimatur's build artifact containing SICR section needed for SDFW update" default "urot_update_sm.bin" @@ -176,8 +197,14 @@ config SUIT_ENVELOPE_TOP_TEMPLATE default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/nordic_top_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP config SUIT_ENVELOPE_ROOT_TARGET - string "Map root target to custom target to overwite output aftifacts names." + string "Map root target to custom target to overwite output artifacts names." default "secdom" if SOC_NRF54H20_CPUSEC && !HW_REVISION_SOC1 default "sysctrl" if SOC_NRF54H20_CPUSYS && !HW_REVISION_SOC1 +config SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY + string "Path to the folder with nordic top artifacts" + default "./" if SOC_NRF54H20_ENGA_CPUAPP + help + Path to the folder containing nordic-top.suit envelope. + endif # SUIT_ENVELOPE diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 new file mode 100644 index 00000000..d59e35f9 --- /dev/null +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -0,0 +1,225 @@ +{%- set component_index = 0 %} +{%- set component_list = [] %} +{%- set mpi_root_vendor_name = app['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_root_class_name = app['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} +{%- set mpi_app_vendor_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_app_class_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- if hci_rpmsg_subimage is defined %} + {% set rad = hci_rpmsg_subimage %} +{%- elif _802154_rpmsg_subimage is defined %} + {% set rad = _802154_rpmsg_subimage %} +{%- elif multiprotocol_rpmsg_subimage is defined %} + {% set rad = multiprotocol_rpmsg_subimage %} +{%- endif %} +SUIT_Envelope_Tagged: + suit-authentication-wrapper: + SuitDigest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-manifest: + suit-manifest-version: 1 + suit-manifest-sequence-number: {{ sequence_number }} + suit-common: + suit-components: + - - CAND_MFST + - 0 +{%- if rad is defined %} + {%- set component_index = component_index + 1 %} + {%- set rad_component_index = component_index %} + {{- component_list.append( rad_component_index ) or ""}} + - - INSTLD_MFST + - RFC4122_UUID: + namespace: {{ mpi_rad_vendor_name }} + name: {{ mpi_rad_class_name }} +{%- endif %} +{%- if app is defined %} + {%- set component_index = component_index + 1 %} + {%- set app_component_index = component_index %} + {{- component_list.append( app_component_index ) or ""}} + - - INSTLD_MFST + - RFC4122_UUID: + namespace: {{ mpi_app_vendor_name }} + name: {{ mpi_app_class_name }} +{%- endif %} + +{%- set component_list_without_top = component_list[:] %} +{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} + {%- set component_index = component_index + 1 %} + {%- set top_component_index = component_index %} + {{- component_list.append( top_component_index ) or ""}} + - - INSTLD_MFST + - RFC4122_UUID: + namespace: nordicsemi.com + name: nRF54H20_nordic_top +{%- endif %} + + suit-shared-sequence: +{%- if rad is defined %} + - suit-directive-set-component-index: {{ rad_component_index }} + - suit-directive-override-parameters: + suit-parameter-vendor-identifier: + RFC4122_UUID: {{ mpi_rad_vendor_name }} + suit-parameter-class-identifier: + RFC4122_UUID: + namespace: {{ mpi_rad_vendor_name }} + name: {{ mpi_rad_class_name }} +{%- endif %} +{%- if app is defined %} + - suit-directive-set-component-index: {{ app_component_index }} + - suit-directive-override-parameters: + suit-parameter-vendor-identifier: + RFC4122_UUID: {{ mpi_app_vendor_name }} + suit-parameter-class-identifier: + RFC4122_UUID: + namespace: {{ mpi_app_vendor_name }} + name: {{ mpi_app_class_name }} +{%- endif %} + +{%- if top is defined %} + - suit-directive-set-component-index: {{ top_component_index }} + - suit-directive-override-parameters: + suit-parameter-vendor-identifier: + RFC4122_UUID: nordicsemi.com + suit-parameter-class-identifier: + RFC4122_UUID: + namespace: nordicsemi.com + name: nRF54H20_nordic_top +{%- endif %} + + - suit-directive-set-component-index: [{{ component_list|join(',') }}] + - suit-condition-vendor-identifier: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-condition-class-identifier: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-dependencies: + # Key is the index of suit-components that describe the dependency manifest + "0": {} +{%- for component_element in component_list %} + "{{ component_element }}": {} +{%- endfor %} + suit-validate: + - suit-directive-set-component-index: [{{ component_list_without_top|join(',') }}] + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + + suit-invoke: + - suit-directive-set-component-index: [{{ component_list_without_top|join(',') }}] + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + + suit-install: + - suit-directive-set-component-index: 0 +{%- if rad is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ rad['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + envelope: {{ artifacts_folder ~ rad['name'] }}.suit + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if app is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ app['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + envelope: {{ artifacts_folder ~ app['name'] }}.suit + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} + - suit-directive-override-parameters: + suit-parameter-uri: '#top' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + envelope: {{ app['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic-top.suit + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} + suit-manifest-component-id: + - INSTLD_MFST + - RFC4122_UUID: + namespace: {{ mpi_root_vendor_name }} + name: {{ mpi_root_class_name }} + suit-integrated-dependencies: +{%- if rad is defined %} + '#{{ rad['name'] }}': {{ artifacts_folder ~ rad['name'] }}.suit +{%- endif %} +{%- if app is defined %} + '#{{ app['name'] }}': {{ artifacts_folder ~ app['name'] }}.suit +{%- endif %} +{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} + '#top': {{ app['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic-top.suit +{%- endif %} From 60afbb26d3a00e5b2243f1304ba2bc4a6722960d Mon Sep 17 00:00:00 2001 From: Robert Stypa Date: Thu, 4 Apr 2024 07:49:01 +0200 Subject: [PATCH 02/67] feat: align Kconfigs with the newest NCS Ref: NCSDK-26629 Signed-off-by: Robert Stypa --- ncs/Kconfig | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 92a5c3ba..52ed9718 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -6,10 +6,6 @@ # The following nine KConfigs are maintained for backward compatibility with the sdk-nrf-next repository. # They are slated for removal once sdk-nrf-next is no longer in use. -config SOC_NRF54H20_ENGA_CPUAPP - bool -config SOC_NRF54H20_CPUAPP - bool config SOC_NRF54H20_CPUSEC bool config SOC_NRF54H20_CPUSYS @@ -27,11 +23,11 @@ config INCLUDE_SYSCTRL config SUIT_ENVELOPE_TEMPLATE string "Path to the envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" if SOC_NRF54H20_ENGA_CPUAPP + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" - default "app" if SOC_NRF54H20_ENGA_CPUAPP + default "app" if SOC_NRF54H20_CPUAPP menuconfig SUIT_ENVELOPE bool "Create SUIT envelope" @@ -54,8 +50,7 @@ config SUIT_ENVELOPE_SEQUENCE_NUM config SUIT_ENVELOPE_DEFAULT_TEMPLATE string "Path to the default envelope template (deprecated)" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_nordic_top_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_ENGA_CPUAPP + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_CPUAPP default SUIT_ENVELOPE_SYSCTRL_TEMPLATE if SOC_NRF54H20_CPUSYS default SUIT_ENVELOPE_SECDOM_TEMPLATE if SOC_NRF54H20_CPUSEC help @@ -203,7 +198,7 @@ config SUIT_ENVELOPE_ROOT_TARGET config SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY string "Path to the folder with nordic top artifacts" - default "./" if SOC_NRF54H20_ENGA_CPUAPP + default "./" help Path to the folder containing nordic-top.suit envelope. From ea2e37461ca822d91aea683edd75470a43f5efe2 Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Mon, 6 May 2024 15:13:28 +0200 Subject: [PATCH 03/67] feat: migration of SUIT DFU build system to NCS (#104) * feat: migration to NCS Ref: NCSDK-26629 Signed-off-by: Robert Stypa --- ncs/802154_rad_envelope.yaml.jinja2 | 99 ------------ ncs/Kconfig | 152 +++--------------- ncs/app_envelope.yaml.jinja2 | 36 ++--- ncs/multiprotocol_rad_envelope.yaml.jinja2 | 99 ------------ ncs/nordic_top_envelope.yaml.jinja2 | 2 +- ncs/rad_envelope.yaml.jinja2 | 34 ++-- ncs/root_with_binary_nordic_top.yaml.jinja2 | 84 +++++----- ncs/root_with_nordic_top_envelope.yaml.jinja2 | 42 ++--- ncs/secdom_update_envelope.yaml.jinja2 | 4 +- ncs/sysctrl_envelope.yaml.jinja2 | 2 +- 10 files changed, 120 insertions(+), 434 deletions(-) delete mode 100644 ncs/802154_rad_envelope.yaml.jinja2 delete mode 100644 ncs/multiprotocol_rad_envelope.yaml.jinja2 diff --git a/ncs/802154_rad_envelope.yaml.jinja2 b/ncs/802154_rad_envelope.yaml.jinja2 deleted file mode 100644 index b49d9745..00000000 --- a/ncs/802154_rad_envelope.yaml.jinja2 +++ /dev/null @@ -1,99 +0,0 @@ -{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - MEM - - {{ _802154_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(_802154_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ _802154_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_rad_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ _802154_rpmsg_subimage['binary'] }} - suit-parameter-image-size: - file: {{ _802154_rpmsg_subimage['binary'] }} - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ _802154_rpmsg_subimage['binary'] }} - suit-validate: - - suit-directive-set-component-index: 0 - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-invoke: - - suit-send-record-failure - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ _802154_rpmsg_subimage['name'] }}' - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-text: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} - suit-text: - en: - '["MEM", {{ _802154_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(_802154_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ _802154_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': - suit-text-vendor-name: Nordic Semiconductor ASA - suit-text-model-name: nRF54H20_cpurad - suit-text-vendor-domain: nordicsemi.com - suit-text-model-info: The nRF54H20 radio core - suit-text-component-description: Sample radio core FW - suit-text-component-version: v1.0.0 - suit-integrated-payloads: - '#{{ _802154_rpmsg_subimage['name'] }}': {{ _802154_rpmsg_subimage['binary'] }} \ No newline at end of file diff --git a/ncs/Kconfig b/ncs/Kconfig index 52ed9718..9b5b3e0d 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -4,30 +4,27 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -# The following nine KConfigs are maintained for backward compatibility with the sdk-nrf-next repository. -# They are slated for removal once sdk-nrf-next is no longer in use. -config SOC_NRF54H20_CPUSEC - bool -config SOC_NRF54H20_CPUSYS - bool +# Kconfig below is slated for removal once SUIT service is available in the NCS. config SSF_SUIT_SERVICE_ENABLED bool -config INCLUDE_SECDOM - bool -config IS_SECURE_DOMAIN_FW - bool -config HW_REVISION_SOC1 - bool -config INCLUDE_SYSCTRL - bool config SUIT_ENVELOPE_TEMPLATE string "Path to the envelope template" default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" - default "app" if SOC_NRF54H20_CPUAPP + default "application" if SOC_NRF54H20_CPUAPP + default "radio" if SOC_NRF54H20_CPURAD + +config SUIT_ENVELOPE_OUTPUT_ARTIFACT + string "Name of the output merged artifact" + default "merged.hex" + +config SUIT_ENVELOPE_OUTPUT_MPI_MERGE + bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" + default y menuconfig SUIT_ENVELOPE bool "Create SUIT envelope" @@ -48,61 +45,15 @@ config SUIT_ENVELOPE_SEQUENCE_NUM range 0 2147483647 default 1 -config SUIT_ENVELOPE_DEFAULT_TEMPLATE - string "Path to the default envelope template (deprecated)" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_CPUAPP - default SUIT_ENVELOPE_SYSCTRL_TEMPLATE if SOC_NRF54H20_CPUSYS - default SUIT_ENVELOPE_SECDOM_TEMPLATE if SOC_NRF54H20_CPUSEC - help - Path to the root template, that is used if the application directory does not - contain an input root envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - This KConfig is available for backward compatibility and will be removed soon. - config SUIT_ENVELOPE_ROOT_TEMPLATE string "Path to the default root envelope template" - default SUIT_ENVELOPE_DEFAULT_TEMPLATE + default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_CPUAPP help Path to the default root envelope template, that is used if the application directory does not contain an input envelope template file. You can use either absolute or relative path. In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. -config SUIT_ENVELOPE_APP_TEMPLATE - string "Path to the default application envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" - help - Path to the default application envelope template, that is used if the application directory does not - contain an input application envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - -config SUIT_ENVELOPE_HCI_RPMSG_SUBIMAGE_TEMPLATE - string "Path to the default radio envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/rad_envelope.yaml.jinja2" - help - Path to the default radio envelope template, that is used if the application directory does not - contain an input radio envelope template file. - -config SUIT_ENVELOPE_MULTIPROTOCOL_RPMSG_SUBIMAGE_TEMPLATE - string "Path to the default multiprotocol radio envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/multiprotocol_rad_envelope.yaml.jinja2" - help - Path to the default multiprotocol radio envelope template, that is used if the application - directory does not contain an input radio envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - -config SUIT_ENVELOPE_802154_RPMSG_SUBIMAGE_TEMPLATE - string "Path to the default 802154 radio envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/802154_rad_envelope.yaml.jinja2" - help - Path to the default 802154 radio envelope template, that is used if the application - directory does not contain an input radio envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - config SUIT_ENVELOPE_EDITABLE_TEMPLATES_LOCATION string "Path to the folder with envelope templates" default "../" @@ -112,13 +63,6 @@ config SUIT_ENVELOPE_EDITABLE_TEMPLATES_LOCATION You can use either absolute or relative path. In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. -# TODO: Consider renaming to not cause a confusion with SUIT_PREPARE_SECDOM_UPDATE -config SUIT_ENVELOPE_SECDOM - bool "Create SUIT files required by secure domain" - help - Create SUIT storage file required by secure domain in case secure domain has been included in the build - default y if INCLUDE_SECDOM - config SUIT_ENVELOPE_SIGN_SCRIPT string "Location of SUIT sign script" depends on SUIT_ENVELOPE_SIGN @@ -131,75 +75,17 @@ config SUIT_ENVELOPE_SIGN_SCRIPT - --output-file - location of signed envelope to create by script default "modules/lib/suit-generator/ncs/sign_script.py" -config SUIT_PREPARE_SECDOM_UPDATE - bool "Create SUIT envelope for SDFW update" - depends on IS_SECURE_DOMAIN_FW - default y if !HW_REVISION_SOC1 - -config SUIT_ENVELOPE_SECDOM_TEMPLATE - string "Location of template file for preparing secdom yaml envelope" - help - Jinja2 template file used to generate yaml file for secure domain update. - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/secdom_update_envelope.yaml.jinja2" - -config SUIT_ENVELOPE_SECDOM_IMPRIMATUR_SICR_BIN - string "Name of Imprimatur's build artifact containing SICR section needed for SDFW update" - default "urot_update_sm.bin" - -config SUIT_ENVELOPE_SECDOM_IMPRIMATUR_PUBLIC_KEY_BIN - string "Name of Imprimatur's build artifact containing public key used for signing the SDFW update candidate" - default "public_key.bin" - -config SUIT_ENVELOPE_SECDOM_IMPRIMATUR_SIGNATURE_BIN - string "Name of Imprimatur's build artifact containing signature of the SDFW update candidate" - default "signature.bin" - -config SUIT_LOG_SECDOM_VERSION - bool "Log version of Secdom FW during its startup" - help - For testing purposes. - default n - -config SUIT_SECDOM_VERSION - string "Version of Secdom FW" - help - For testing Secdom FW updates. - Version of Secdom FW to be logged during its startup. - default "0.0.1" - -config SUIT_ENVELOPE_SYSCTRL - bool "Create SUIT files required by sysctrl" +config SUIT_ENVELOPE_ROOT_ARTIFACT_NAME + string "Name of the root SUIT artifact." + default "" help - Create SUIT envelope for sysctrl in case it has been included in the build - default y if INCLUDE_SYSCTRL - -config SUIT_ENVELOPE_SYSCTRL_TEMPLATE - string "Location of template file for preparing sysctrl yaml envelope" - help - Path to the sysctrl template, that is used if the application directory does not - contain an input sysctrl envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/sysctrl_envelope.yaml.jinja2" - -config SUIT_ENVELOPE_TOP_TEMPLATE - string "Location of template file for preparing nordic-top yaml envelope" - help - Path to the nordic-top template, that is used if the application directory does not - contain an input nordic-top envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/nordic_top_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP - -config SUIT_ENVELOPE_ROOT_TARGET - string "Map root target to custom target to overwite output artifacts names." - default "secdom" if SOC_NRF54H20_CPUSEC && !HW_REVISION_SOC1 - default "sysctrl" if SOC_NRF54H20_CPUSYS && !HW_REVISION_SOC1 + Name of the root SUIT artifact. If empty, 'root' name is used if this setting is empty. config SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY string "Path to the folder with nordic top artifacts" - default "./" + default "" help Path to the folder containing nordic-top.suit envelope. + nordic_top.suit won't be included in the root manifest if value is empty. endif # SUIT_ENVELOPE diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index 46c1ef65..f0abc6f5 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -1,6 +1,6 @@ -{%- set mpi_app_vendor_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_app_class_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: @@ -11,9 +11,9 @@ SUIT_Envelope_Tagged: suit-common: suit-components: - - MEM - - {{ app['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(app['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ app['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} + - {{ application['dt'].label2node['cpu'].unit_addr }} + - {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }} + - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - CAND_IMG - 0 {%- if flash_companion_subimage is defined %} @@ -26,17 +26,17 @@ SUIT_Envelope_Tagged: - suit-directive-set-component-index: 0 - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_app_vendor_name }} + RFC4122_UUID: {{ mpi_application_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: {{ mpi_app_vendor_name }} - name: {{ mpi_app_class_name }} + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ app['binary'] }} + file: {{ application['binary'] }} suit-parameter-image-size: - file: {{ app['binary'] }} + file: {{ application['binary'] }} - suit-condition-vendor-identifier: - suit-send-record-success - suit-send-record-failure @@ -52,7 +52,7 @@ SUIT_Envelope_Tagged: suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ app['binary'] }} + file: {{ application['binary'] }} {%- if flash_companion_subimage is defined %} - suit-directive-set-component-index: 2 - suit-directive-override-parameters: @@ -103,11 +103,11 @@ SUIT_Envelope_Tagged: {%- endif %} - suit-directive-set-component-index: 1 - suit-directive-override-parameters: - suit-parameter-uri: '#{{ app['name'] }}' + suit-parameter-uri: '#{{ application['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ app['binary'] }} + file: {{ application['binary'] }} - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -130,11 +130,11 @@ SUIT_Envelope_Tagged: suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: - namespace: {{ mpi_app_vendor_name }} - name: {{ mpi_app_class_name }} + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} suit-text: en: - '["MEM", {{ app['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(app['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ app['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': + '["MEM", {{ application['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': suit-text-vendor-name: Nordic Semiconductor ASA suit-text-model-name: nRF54H20_cpuapp suit-text-vendor-domain: nordicsemi.com @@ -142,7 +142,7 @@ SUIT_Envelope_Tagged: suit-text-component-description: Sample application core FW suit-text-component-version: v1.0.0 suit-integrated-payloads: - '#{{ app['name'] }}': {{ app['binary'] }} + '#{{ application['name'] }}': {{ application['binary'] }} {%- if flash_companion_subimage is defined %} '#{{ flash_companion_subimage['name'] }}': {{ flash_companion_subimage['binary'] }} {%- endif %} diff --git a/ncs/multiprotocol_rad_envelope.yaml.jinja2 b/ncs/multiprotocol_rad_envelope.yaml.jinja2 deleted file mode 100644 index 7fd756fa..00000000 --- a/ncs/multiprotocol_rad_envelope.yaml.jinja2 +++ /dev/null @@ -1,99 +0,0 @@ -{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - MEM - - {{ multiprotocol_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(multiprotocol_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ multiprotocol_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_rad_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ multiprotocol_rpmsg_subimage['binary'] }} - suit-parameter-image-size: - file: {{ multiprotocol_rpmsg_subimage['binary'] }} - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ multiprotocol_rpmsg_subimage['binary'] }} - suit-validate: - - suit-directive-set-component-index: 0 - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-invoke: - - suit-send-record-failure - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ multiprotocol_rpmsg_subimage['name'] }}' - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-text: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} - suit-text: - en: - '["MEM", {{ multiprotocol_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(multiprotocol_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ multiprotocol_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': - suit-text-vendor-name: Nordic Semiconductor ASA - suit-text-model-name: nRF54H20_cpurad - suit-text-vendor-domain: nordicsemi.com - suit-text-model-info: The nRF54H20 radio core - suit-text-component-description: Sample radio core FW - suit-text-component-version: v1.0.0 - suit-integrated-payloads: - '#{{ multiprotocol_rpmsg_subimage['name'] }}': {{ multiprotocol_rpmsg_subimage['binary'] }} \ No newline at end of file diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index 4f208bbf..bf7b342f 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -1,4 +1,4 @@ -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index 800d8fbc..3594fd09 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -1,6 +1,6 @@ -{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: @@ -11,26 +11,26 @@ SUIT_Envelope_Tagged: suit-common: suit-components: - - MEM - - {{ hci_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(hci_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ hci_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} + - {{ radio['dt'].label2node['cpu'].unit_addr }} + - {{ get_absolute_address(radio['dt'].chosen_nodes['zephyr,code-partition']) }} + - {{ radio['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - CAND_IMG - 0 suit-shared-sequence: - suit-directive-set-component-index: 0 - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_rad_vendor_name }} + RFC4122_UUID: {{ mpi_radio_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} + namespace: {{ mpi_radio_vendor_name }} + name: {{ mpi_radio_class_name }} suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ hci_rpmsg_subimage['binary'] }} + file: {{ radio['binary'] }} suit-parameter-image-size: - file: {{ hci_rpmsg_subimage['binary'] }} + file: {{ radio['binary'] }} - suit-condition-vendor-identifier: - suit-send-record-success - suit-send-record-failure @@ -46,7 +46,7 @@ SUIT_Envelope_Tagged: suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ hci_rpmsg_subimage['binary'] }} + file: {{ radio['binary'] }} suit-validate: - suit-directive-set-component-index: 0 - suit-condition-image-match: @@ -61,7 +61,7 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 1 - suit-directive-override-parameters: - suit-parameter-uri: '#{{ hci_rpmsg_subimage['name'] }}' + suit-parameter-uri: '#{{ radio['name'] }}' - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -84,11 +84,11 @@ SUIT_Envelope_Tagged: suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} + namespace: {{ mpi_radio_vendor_name }} + name: {{ mpi_radio_class_name }} suit-text: en: - '["MEM", {{ hci_rpmsg_subimage['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(hci_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ hci_rpmsg_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': + '["MEM", {{ radio['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(radio['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ radio['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': suit-text-vendor-name: Nordic Semiconductor ASA suit-text-model-name: nRF54H20_cpurad suit-text-vendor-domain: nordicsemi.com @@ -96,4 +96,4 @@ SUIT_Envelope_Tagged: suit-text-component-description: Sample radio core FW suit-text-component-version: v1.0.0 suit-integrated-payloads: - '#{{ hci_rpmsg_subimage['name'] }}': {{ hci_rpmsg_subimage['binary'] }} \ No newline at end of file + '#{{ radio['name'] }}': {{ radio['binary'] }} \ No newline at end of file diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index d59e35f9..b3a14e3e 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -1,18 +1,16 @@ {%- set component_index = 0 %} {%- set component_list = [] %} -{%- set mpi_root_vendor_name = app['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_root_class_name = app['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_app_vendor_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_app_class_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -{%- if hci_rpmsg_subimage is defined %} - {% set rad = hci_rpmsg_subimage %} -{%- elif _802154_rpmsg_subimage is defined %} - {% set rad = _802154_rpmsg_subimage %} -{%- elif multiprotocol_rpmsg_subimage is defined %} - {% set rad = multiprotocol_rpmsg_subimage %} +{%- set mpi_root_vendor_name = application['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_root_class_name = application['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} +{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in application['config'] and application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} + {%- set nordic_top = True %} +{%- else %} + {%- set nordic_top = False %} {%- endif %} SUIT_Envelope_Tagged: suit-authentication-wrapper: @@ -25,27 +23,27 @@ SUIT_Envelope_Tagged: suit-components: - - CAND_MFST - 0 -{%- if rad is defined %} +{%- if radio is defined %} {%- set component_index = component_index + 1 %} {%- set rad_component_index = component_index %} {{- component_list.append( rad_component_index ) or ""}} - - INSTLD_MFST - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} + namespace: {{ mpi_radio_vendor_name }} + name: {{ mpi_radio_class_name }} {%- endif %} -{%- if app is defined %} +{%- if application is defined %} {%- set component_index = component_index + 1 %} {%- set app_component_index = component_index %} {{- component_list.append( app_component_index ) or ""}} - - INSTLD_MFST - RFC4122_UUID: - namespace: {{ mpi_app_vendor_name }} - name: {{ mpi_app_class_name }} + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} {%- endif %} {%- set component_list_without_top = component_list[:] %} -{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} +{%- if nordic_top %} {%- set component_index = component_index + 1 %} {%- set top_component_index = component_index %} {{- component_list.append( top_component_index ) or ""}} @@ -56,28 +54,28 @@ SUIT_Envelope_Tagged: {%- endif %} suit-shared-sequence: -{%- if rad is defined %} +{%- if radio is defined %} - suit-directive-set-component-index: {{ rad_component_index }} - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_rad_vendor_name }} + RFC4122_UUID: {{ mpi_radio_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} + namespace: {{ mpi_radio_vendor_name }} + name: {{ mpi_radio_class_name }} {%- endif %} -{%- if app is defined %} +{%- if application is defined %} - suit-directive-set-component-index: {{ app_component_index }} - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_app_vendor_name }} + RFC4122_UUID: {{ mpi_application_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: {{ mpi_app_vendor_name }} - name: {{ mpi_app_class_name }} + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} {%- endif %} -{%- if top is defined %} +{%- if nordic_top is defined %} - suit-directive-set-component-index: {{ top_component_index }} - suit-directive-override-parameters: suit-parameter-vendor-identifier: @@ -133,13 +131,13 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 0 -{%- if rad is defined %} +{%- if radio is defined %} - suit-directive-override-parameters: - suit-parameter-uri: '#{{ rad['name'] }}' + suit-parameter-uri: '#{{ radio['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ artifacts_folder ~ rad['name'] }}.suit + envelope: {{ artifacts_folder ~ radio['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -158,13 +156,13 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure {%- endif %} -{%- if app is defined %} +{%- if application is defined %} - suit-directive-override-parameters: - suit-parameter-uri: '#{{ app['name'] }}' + suit-parameter-uri: '#{{ application['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ artifacts_folder ~ app['name'] }}.suit + envelope: {{ artifacts_folder ~ application['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -183,13 +181,13 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure {%- endif %} -{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} +{%- if nordic_top %} - suit-directive-override-parameters: suit-parameter-uri: '#top' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ app['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic-top.suit + envelope: {{ application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -214,12 +212,12 @@ SUIT_Envelope_Tagged: namespace: {{ mpi_root_vendor_name }} name: {{ mpi_root_class_name }} suit-integrated-dependencies: -{%- if rad is defined %} - '#{{ rad['name'] }}': {{ artifacts_folder ~ rad['name'] }}.suit +{%- if radio is defined %} + '#{{ radio['name'] }}': {{ artifacts_folder ~ radio['name'] }}.suit {%- endif %} -{%- if app is defined %} - '#{{ app['name'] }}': {{ artifacts_folder ~ app['name'] }}.suit +{%- if application is defined %} + '#{{ application['name'] }}': {{ artifacts_folder ~ application['name'] }}.suit {%- endif %} -{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in app['config'] %} - '#top': {{ app['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic-top.suit +{%- if nordic_top %} + '#top': {{ application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit {%- endif %} diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index dfbc26de..3d69b493 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -1,12 +1,12 @@ {%- set component_index = 0 %} {%- set component_list = [] %} -{%- set mpi_root_vendor_name = app['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_root_class_name = app['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_app_vendor_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_app_class_name = app['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_rad_vendor_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = app['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set mpi_root_vendor_name = application['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_root_class_name = application['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} +{%- set mpi_app_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_app_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_rad_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_rad_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} {%- if hci_rpmsg_subimage is defined %} {% set rad = hci_rpmsg_subimage %} {%- elif _802154_rpmsg_subimage is defined %} @@ -25,7 +25,7 @@ SUIT_Envelope_Tagged: suit-components: - - CAND_MFST - 0 -{%- if rad is defined %} +{%- if radio is defined %} {%- set component_index = component_index + 1 %} {%- set rad_component_index = component_index %} {{- component_list.append( rad_component_index ) or ""}} @@ -34,7 +34,7 @@ SUIT_Envelope_Tagged: namespace: {{ mpi_rad_vendor_name }} name: {{ mpi_rad_class_name }} {%- endif %} -{%- if app is defined %} +{%- if application is defined %} {%- set component_index = component_index + 1 %} {%- set app_component_index = component_index %} {{- component_list.append( app_component_index ) or ""}} @@ -56,7 +56,7 @@ SUIT_Envelope_Tagged: {%- endif %} suit-shared-sequence: -{%- if rad is defined %} +{%- if radio is defined %} - suit-directive-set-component-index: {{ rad_component_index }} - suit-directive-override-parameters: suit-parameter-vendor-identifier: @@ -66,7 +66,7 @@ SUIT_Envelope_Tagged: namespace: {{ mpi_rad_vendor_name }} name: {{ mpi_rad_class_name }} {%- endif %} -{%- if app is defined %} +{%- if application is defined %} - suit-directive-set-component-index: {{ app_component_index }} - suit-directive-override-parameters: suit-parameter-vendor-identifier: @@ -133,13 +133,13 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 0 -{%- if rad is defined %} +{%- if radio is defined %} - suit-directive-override-parameters: - suit-parameter-uri: '#{{ rad['name'] }}' + suit-parameter-uri: '#{{ radio['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ artifacts_folder ~ rad['name'] }}.suit + envelope: {{ artifacts_folder ~ radio['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -158,13 +158,13 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure {%- endif %} -{%- if app is defined %} +{%- if application is defined %} - suit-directive-override-parameters: - suit-parameter-uri: '#{{ app['name'] }}' + suit-parameter-uri: '#{{ application['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ artifacts_folder ~ app['name'] }}.suit + envelope: {{ artifacts_folder ~ application['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -214,11 +214,11 @@ SUIT_Envelope_Tagged: namespace: {{ mpi_root_vendor_name }} name: {{ mpi_root_class_name }} suit-integrated-dependencies: -{%- if rad is defined %} - '#{{ rad['name'] }}': {{ artifacts_folder ~ rad['name'] }}.suit +{%- if radio is defined %} + '#{{ radio['name'] }}': {{ artifacts_folder ~ radio['name'] }}.suit {%- endif %} -{%- if app is defined %} - '#{{ app['name'] }}': {{ artifacts_folder ~ app['name'] }}.suit +{%- if application is defined %} + '#{{ application['name'] }}': {{ artifacts_folder ~ application['name'] }}.suit {%- endif %} {%- if top is defined %} '#{{ top['name'] }}': {{ artifacts_folder ~ top['name'] }}.suit diff --git a/ncs/secdom_update_envelope.yaml.jinja2 b/ncs/secdom_update_envelope.yaml.jinja2 index 4125045f..087b63ef 100644 --- a/ncs/secdom_update_envelope.yaml.jinja2 +++ b/ncs/secdom_update_envelope.yaml.jinja2 @@ -2,7 +2,7 @@ {# secure domain build as main application #} {%- set secdom = app %} {%- endif %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: @@ -36,7 +36,7 @@ SUIT_Envelope_Tagged: - suit-send-record-failure - suit-send-sysinfo-success - suit-send-sysinfo-failure -{%- if 'CONFIG_HW_REVISION_SOC1' in app['config'] %} +{%- if 'CONFIG_HW_REVISION_SOC1' in application['config'] %} suit-install: [] {%- else %} suit-install: diff --git a/ncs/sysctrl_envelope.yaml.jinja2 b/ncs/sysctrl_envelope.yaml.jinja2 index 699d2a6b..f27973df 100644 --- a/ncs/sysctrl_envelope.yaml.jinja2 +++ b/ncs/sysctrl_envelope.yaml.jinja2 @@ -3,7 +3,7 @@ {# sysctrl domain build as main application #} {%- set sysctrl = app %} {%- endif %} -{%- set sequence_number = app['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: From c0165159dac566fafbf67be75d86ca64a34834f0 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Tue, 23 Apr 2024 16:21:05 +0200 Subject: [PATCH 04/67] fix: Class ID checks for INSTLD_MFST components The class ID check verifies if manifest with given class ID can operate on the installed manifest component, not the class ID of the component. Signed-off-by: Tomasz Chyrowicz --- .../input_files/envelope_2_hierarchical.yaml | 13 ++------ ncs/nordic_top_envelope.yaml.jinja2 | 12 ++----- ncs/root_with_binary_nordic_top.yaml.jinja2 | 33 +++---------------- ncs/root_with_nordic_top_envelope.yaml.jinja2 | 33 +++---------------- 4 files changed, 12 insertions(+), 79 deletions(-) diff --git a/examples/input_files/envelope_2_hierarchical.yaml b/examples/input_files/envelope_2_hierarchical.yaml index ad6aa1fe..f60a949c 100644 --- a/examples/input_files/envelope_2_hierarchical.yaml +++ b/examples/input_files/envelope_2_hierarchical.yaml @@ -169,23 +169,14 @@ SUIT_Envelope_Tagged: namespace: nordicsemi.com name: nRF54H20_sample_app suit-shared-sequence: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: nordicsemi.com - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_sample_rad - - suit-directive-set-component-index: 2 + - suit-directive-set-component-index: [1, 2] - suit-directive-override-parameters: suit-parameter-vendor-identifier: RFC4122_UUID: nordicsemi.com suit-parameter-class-identifier: RFC4122_UUID: namespace: nordicsemi.com - name: nRF54H20_sample_app - - suit-directive-set-component-index: [1, 2] + name: nRF54H20_sample_root - suit-condition-vendor-identifier: - suit-send-record-success - suit-send-record-failure diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index bf7b342f..028c0462 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -19,20 +19,12 @@ SUIT_Envelope_Tagged: namespace: nordicsemi.com name: nRF54H20_sys suit-shared-sequence: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_sec - - suit-directive-set-component-index: 2 + - suit-directive-set-component-index: [1,2] - suit-directive-override-parameters: suit-parameter-class-identifier: RFC4122_UUID: namespace: nordicsemi.com - name: nRF54H20_sys - - suit-directive-set-component-index: [1,2] - - suit-directive-override-parameters: + name: nRF54H20_nordic_top suit-parameter-vendor-identifier: RFC4122_UUID: nordicsemi.com - suit-condition-vendor-identifier: diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index b3a14e3e..6161afe1 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -54,39 +54,14 @@ SUIT_Envelope_Tagged: {%- endif %} suit-shared-sequence: -{%- if radio is defined %} - - suit-directive-set-component-index: {{ rad_component_index }} - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_radio_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_radio_vendor_name }} - name: {{ mpi_radio_class_name }} -{%- endif %} -{%- if application is defined %} - - suit-directive-set-component-index: {{ app_component_index }} - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_application_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} -{%- endif %} - -{%- if nordic_top is defined %} - - suit-directive-set-component-index: {{ top_component_index }} + - suit-directive-set-component-index: [{{ component_list|join(',') }}] - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: nordicsemi.com + RFC4122_UUID: {{ mpi_root_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_nordic_top -{%- endif %} - - - suit-directive-set-component-index: [{{ component_list|join(',') }}] + namespace: {{ mpi_root_vendor_name }} + name: {{ mpi_root_class_name }} - suit-condition-vendor-identifier: - suit-send-record-success - suit-send-record-failure diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index 3d69b493..3ef96334 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -56,39 +56,14 @@ SUIT_Envelope_Tagged: {%- endif %} suit-shared-sequence: -{%- if radio is defined %} - - suit-directive-set-component-index: {{ rad_component_index }} - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_rad_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_rad_vendor_name }} - name: {{ mpi_rad_class_name }} -{%- endif %} -{%- if application is defined %} - - suit-directive-set-component-index: {{ app_component_index }} - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_app_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_app_vendor_name }} - name: {{ mpi_app_class_name }} -{%- endif %} - -{%- if top is defined %} - - suit-directive-set-component-index: {{ top_component_index }} + - suit-directive-set-component-index: [{{ component_list|join(',') }}] - suit-directive-override-parameters: suit-parameter-vendor-identifier: - RFC4122_UUID: nordicsemi.com + RFC4122_UUID: {{ mpi_root_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_nordic_top -{%- endif %} - - - suit-directive-set-component-index: [{{ component_list|join(',') }}] + namespace: {{ mpi_root_vendor_name }} + name: {{ mpi_root_class_name }} - suit-condition-vendor-identifier: - suit-send-record-success - suit-send-record-failure From 1dfb3fa1676c255c9d19a5c159249b5a7e53c476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Thu, 16 May 2024 13:35:33 +0200 Subject: [PATCH 05/67] suit-generator sysbuild adjustments (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ncs: Move some options to Kconfig.sysbuild Some options fit better into sysbuild, as those options affect the entire system. For example, manifest creation involves integrating artifacts from multiple domains into one artifact. Added Kconfig that can be enabled by the local firmware build called SUIT_LOCAL_ENVELOPE_GENERATE which enables local envelope creation. Signed-off-by: Rafał Kuźnia * templates: align flash companion template variable The old name flash_companion_subimage is no longer used. Instead, the build system automatically includes all core information into manifest rendering process. The information can be accessed using the application name without suffixes. * feat (build system): require explicit Kconfig path Previously the path to .config file was derived from the edt.pickle path. In sysbuild we want to be able to access the sysbuild Kconfig namespace. However, the sysbuild run does not generate the edt.pickle file. Therefore it is necessary to be able to provide a path to the .config file without providing the edt.pickle. The edt.pickle field is now optional in --core parameter. Binary field is also optional. If left empty, the utility will not attempt to open the pickle file and devicetree and binary path information will not be supplied to the template generator. Signed-off-by: Rafał Kuźnia * templates: use Kconfigs from sysbuild namespace Some Kconfigs were moved from application to the sysbuild namespace. Modified the templates to reference the moved Kconfigs using the sysbuild target. Signed-off-by: Rafał Kuźnia --------- Signed-off-by: Rafał Kuźnia --- ncs/Kconfig | 66 +------------------ ncs/app_envelope.yaml.jinja2 | 24 +++---- ncs/build.py | 28 ++++---- ncs/nordic_top_envelope.yaml.jinja2 | 2 +- ncs/rad_envelope.yaml.jinja2 | 2 +- ncs/root_with_binary_nordic_top.yaml.jinja2 | 8 +-- ncs/root_with_nordic_top_envelope.yaml.jinja2 | 2 +- ncs/secdom_update_envelope.yaml.jinja2 | 2 +- ncs/sysctrl_envelope.yaml.jinja2 | 2 +- 9 files changed, 40 insertions(+), 96 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 9b5b3e0d..ca95483d 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -26,66 +26,6 @@ config SUIT_ENVELOPE_OUTPUT_MPI_MERGE bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" default y -menuconfig SUIT_ENVELOPE - bool "Create SUIT envelope" - help - Enable DFU SUIT envelope creation - default y if SSF_SUIT_SERVICE_ENABLED && SOC_NRF54H20_CPUAPP - -if SUIT_ENVELOPE - -config SUIT_ENVELOPE_SIGN - bool "Sign created SUIT envelope" - help - Sign created SUIT envelope by external script - default n - -config SUIT_ENVELOPE_SEQUENCE_NUM - int "Sequence number of the generated SUIT manifest" - range 0 2147483647 - default 1 - -config SUIT_ENVELOPE_ROOT_TEMPLATE - string "Path to the default root envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/root_with_binary_nordic_top.yaml.jinja2" if SOC_NRF54H20_CPUAPP - help - Path to the default root envelope template, that is used if the application directory does not - contain an input envelope template file. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - -config SUIT_ENVELOPE_EDITABLE_TEMPLATES_LOCATION - string "Path to the folder with envelope templates" - default "../" - help - Path to the folder containing editable templates used to create binary envelopes. - Input templates are created by the build system during first build from the SUIT_ENVELOPE_DEFAULT_TEMPLATE. - You can use either absolute or relative path. - In case relative path is used, the build system uses CMAKE_SOURCE_DIR directory. - -config SUIT_ENVELOPE_SIGN_SCRIPT - string "Location of SUIT sign script" - depends on SUIT_ENVELOPE_SIGN - help - Python script called to sign SUIT envelope. - You can use either absolute or relative path. - In case relative path is used, the build system uses NRF parent directory. - Script need to accept two arguments: - - --input-file - location of unsigned envelope in the build system - - --output-file - location of signed envelope to create by script - default "modules/lib/suit-generator/ncs/sign_script.py" - -config SUIT_ENVELOPE_ROOT_ARTIFACT_NAME - string "Name of the root SUIT artifact." - default "" - help - Name of the root SUIT artifact. If empty, 'root' name is used if this setting is empty. - -config SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY - string "Path to the folder with nordic top artifacts" - default "" - help - Path to the folder containing nordic-top.suit envelope. - nordic_top.suit won't be included in the root manifest if value is empty. - -endif # SUIT_ENVELOPE +config SUIT_LOCAL_ENVELOPE_GENERATE + bool "Generate local envelope" + default y diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index f0abc6f5..f5bff3ae 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -1,6 +1,6 @@ {%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: @@ -16,11 +16,11 @@ SUIT_Envelope_Tagged: - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - CAND_IMG - 0 -{%- if flash_companion_subimage is defined %} +{%- if flash_companion is defined %} - - MEM - - {{ flash_companion_subimage['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(flash_companion_subimage['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ flash_companion_subimage['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} + - {{ flash_companion['dt'].label2node['cpu'].unit_addr }} + - {{ get_absolute_address(flash_companion['dt'].chosen_nodes['zephyr,code-partition']) }} + - {{ flash_companion['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} {%- endif %} suit-shared-sequence: - suit-directive-set-component-index: 0 @@ -53,13 +53,13 @@ SUIT_Envelope_Tagged: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: file: {{ application['binary'] }} -{%- if flash_companion_subimage is defined %} +{%- if flash_companion is defined %} - suit-directive-set-component-index: 2 - suit-directive-override-parameters: suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ flash_companion_subimage['binary'] }} + file: {{ flash_companion['binary'] }} {%- endif %} suit-validate: - suit-directive-set-component-index: 0 @@ -73,14 +73,14 @@ SUIT_Envelope_Tagged: - suit-directive-invoke: - suit-send-record-failure suit-install: -{%- if flash_companion_subimage is defined %} +{%- if flash_companion is defined %} - suit-directive-set-component-index: 1 - suit-directive-override-parameters: - suit-parameter-uri: '#{{ flash_companion_subimage['name'] }}' + suit-parameter-uri: '#{{ flash_companion['name'] }}' suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - file: {{ flash_companion_subimage['binary'] }} + file: {{ flash_companion['binary'] }} - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -143,6 +143,6 @@ SUIT_Envelope_Tagged: suit-text-component-version: v1.0.0 suit-integrated-payloads: '#{{ application['name'] }}': {{ application['binary'] }} -{%- if flash_companion_subimage is defined %} - '#{{ flash_companion_subimage['name'] }}': {{ flash_companion_subimage['binary'] }} +{%- if flash_companion is defined %} + '#{{ flash_companion['name'] }}': {{ flash_companion['binary'] }} {%- endif %} diff --git a/ncs/build.py b/ncs/build.py index faf29fbf..f878ae5e 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -71,18 +71,22 @@ def read_configurations(configurations): """Read configuration stored in the pickled devicetree.""" data = {} for config in configurations: - name, binary, edt = config.split(",") - kconfig = pathlib.Path(edt).parent / ".config" - with open(edt, "rb") as edt_handler: - edt = pickle.load(edt_handler) - # add prefix _ to the names starting with digits, for example: - # 802154_rpmsg_subimage will be available in the templates as _802154_rpmsg_subimage - data[f"_{name}" if re.match("^[0-9].*]", name) else name] = { - "name": name, - "config": BuildConfiguration(kconfig), - "dt": edt, - "binary": binary, - } + name, binary, edt, kconfig = config.split(",") + edt_data = None + if edt: + with open(edt, "rb") as edt_handler: + edt_data = pickle.load(edt_handler) + # add prefix _ to the names starting with digits, for example: + # 802154_rpmsg_subimage will be available in the templates as _802154_rpmsg_subimage + image_name = f"_{name}" if re.match("^[0-9].*]", name) else name + data[image_name] = { + "name": name, + "config": BuildConfiguration(kconfig), + } + if edt_data: + data[image_name]["dt"] = edt_data + if binary: + data[image_name]["binary"] = binary data["get_absolute_address"] = get_absolute_address return data diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index 028c0462..c0fadbed 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -1,4 +1,4 @@ -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index 3594fd09..7ec42618 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -1,6 +1,6 @@ {%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index 6161afe1..0f98cb1c 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -6,8 +6,8 @@ {%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} {%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -{%- if 'CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in application['config'] and application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- if 'SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in sysbuild['config'] and sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} {%- set nordic_top = True %} {%- else %} {%- set nordic_top = False %} @@ -162,7 +162,7 @@ SUIT_Envelope_Tagged: suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: - envelope: {{ application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit + envelope: {{ sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit - suit-directive-fetch: - suit-send-record-failure - suit-condition-image-match: @@ -194,5 +194,5 @@ SUIT_Envelope_Tagged: '#{{ application['name'] }}': {{ artifacts_folder ~ application['name'] }}.suit {%- endif %} {%- if nordic_top %} - '#top': {{ application['config']['CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit + '#top': {{ sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit {%- endif %} diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index 3ef96334..e7c37ec7 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -6,7 +6,7 @@ {%- set mpi_app_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} {%- set mpi_rad_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_rad_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} {%- if hci_rpmsg_subimage is defined %} {% set rad = hci_rpmsg_subimage %} {%- elif _802154_rpmsg_subimage is defined %} diff --git a/ncs/secdom_update_envelope.yaml.jinja2 b/ncs/secdom_update_envelope.yaml.jinja2 index 087b63ef..d8ff2a61 100644 --- a/ncs/secdom_update_envelope.yaml.jinja2 +++ b/ncs/secdom_update_envelope.yaml.jinja2 @@ -2,7 +2,7 @@ {# secure domain build as main application #} {%- set secdom = app %} {%- endif %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: diff --git a/ncs/sysctrl_envelope.yaml.jinja2 b/ncs/sysctrl_envelope.yaml.jinja2 index f27973df..3fd1d526 100644 --- a/ncs/sysctrl_envelope.yaml.jinja2 +++ b/ncs/sysctrl_envelope.yaml.jinja2 @@ -3,7 +3,7 @@ {# sysctrl domain build as main application #} {%- set sysctrl = app %} {%- endif %} -{%- set sequence_number = application['config']['CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: From d49015bfe31d348656f68a3a633321fcb3384f66 Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Wed, 22 May 2024 08:45:40 +0200 Subject: [PATCH 06/67] fix: allow usage of custom vid/cid (#113) * fix: allow usage of custom vid/cid Ref: NCSDK-27373 Signed-off-by: Robert Stypa --- build_configuration/__init__.py | 0 build_configuration/configuration.py | 49 ++++++ ncs/app_envelope.yaml.jinja2 | 4 +- ncs/build.py | 48 +----- ncs/rad_envelope.yaml.jinja2 | 4 +- ncs/root_with_binary_nordic_top.yaml.jinja2 | 8 +- pyproject.toml | 2 +- suit_generator/cmd_image.py | 47 +++++- tests/test_cmd_image.py | 173 +++++++++++++++++++- 9 files changed, 279 insertions(+), 56 deletions(-) create mode 100644 build_configuration/__init__.py create mode 100644 build_configuration/configuration.py diff --git a/build_configuration/__init__.py b/build_configuration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build_configuration/configuration.py b/build_configuration/configuration.py new file mode 100644 index 00000000..b00d4dfe --- /dev/null +++ b/build_configuration/configuration.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""build_configuration module to create and parse build configuration.""" + +import re +import string + + +class BuildConfiguration(dict): + """Represents a build system configuration, providing access to KConfig values. + + This class reads configuration data from a specified file and parses it. + Configuration data is accessible as a dictionary. + """ + + config_value_pattern = re.compile(r"(?P[A-Za-z0-9_]+)=(?P.*)") + + def __init__(self, input_file: str = ".config") -> None: + """Initialize a BuildConfiguration object.""" + super().__init__() + try: + with open(input_file, "r") as fh: + self._config_data = fh.readlines() + except FileNotFoundError as e: + raise SystemExit(e) + self._parse() + + def _parse(self) -> None: + """Parse input .config file and populate the configuration dictionary.""" + for config_line in self._config_data: + if re_result := self.config_value_pattern.match(config_line): + kconfig_name = re_result.group("kconfig_name") + kconfig_value = re_result.group("kconfig_value") + if kconfig_value == "y": + # boolean value + kconfig_value = True + elif kconfig_value.startswith("0x") and all(c in string.hexdigits for c in kconfig_value[2:]): + # hexadecimal value + kconfig_value = int(kconfig_value, base=16) + elif kconfig_value.startswith('"') and kconfig_value.endswith('"'): + # string value + kconfig_value = kconfig_value[1:-1] + elif kconfig_value.isdecimal(): + # int value + kconfig_value = int(kconfig_value, base=10) + super().__setitem__(kconfig_name, kconfig_value) \ No newline at end of file diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index f5bff3ae..321c6d2a 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -1,5 +1,5 @@ -{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} {%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: diff --git a/ncs/build.py b/ncs/build.py index f878ae5e..7936266d 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -19,6 +19,7 @@ sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) from suit_generator.cmd_image import ImageCreator # noqa: E402 +from build_configuration.configuration import BuildConfiguration TEMPLATE_CMD = "template" STORAGE_CMD = "storage" @@ -27,46 +28,6 @@ dir_path = pathlib.Path(__file__).parent.absolute() -class BuildConfiguration(dict): - """Represents a build system configuration, providing access to KConfig values. - - This class reads configuration data from a specified file and parses it. - Configuration data is accessible as a dictionary. - """ - - config_value_pattern = re.compile(r"(?P[A-Za-z0-9_]+)=(?P.*)") - - def __init__(self, input_file: str = ".config") -> None: - """Initialize a BuildConfiguration object.""" - super().__init__() - try: - with open(input_file, "r") as fh: - self._config_data = fh.readlines() - except FileNotFoundError as e: - raise SystemExit(e) - self._parse() - - def _parse(self) -> None: - """Parse input .config file and populate the configuration dictionary.""" - for config_line in self._config_data: - if re_result := self.config_value_pattern.match(config_line): - kconfig_name = re_result.group("kconfig_name") - kconfig_value = re_result.group("kconfig_value") - if kconfig_value == "y": - # boolean value - kconfig_value = True - elif kconfig_value.startswith("0x"): - # hexadecimal value - kconfig_value = int(kconfig_value, base=16) - elif kconfig_value.startswith('"') and kconfig_value.endswith('"'): - # string value - kconfig_value = kconfig_value[1:-1] - elif kconfig_value.isdecimal(): - # int value - kconfig_value = int(kconfig_value, base=10) - super().__setitem__(kconfig_name, kconfig_value) - - def read_configurations(configurations): """Read configuration stored in the pickled devicetree.""" data = {} @@ -145,6 +106,12 @@ def get_absolute_address(node, use_offset: bool = True): cmd_storage_arg_parser.add_argument( "--storage-output-directory", required=True, help="Directory path to store hex files with SUIT storage contents" ) + cmd_storage_arg_parser.add_argument( + "--config-file", + required=False, + default=None, + help=f"Path to KConfig file", + ) cmd_update_arg_parser = subparsers.add_parser( UPDATE_CMD, help="Generate files needed for Secure Domain update", parents=[parent_parser] @@ -186,6 +153,7 @@ def get_absolute_address(node, use_offset: bool = True): envelope_slot_count=ImageCreator.default_envelope_slot_count, update_candidate_info_address=ImageCreator.default_update_candidate_info_address, dfu_max_caches=ImageCreator.default_dfu_max_caches, + config_file=arguments.config_file ) elif arguments.command == UPDATE_CMD: ImageCreator.create_files_for_update( diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index 7ec42618..5f24f2a7 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -1,5 +1,5 @@ -{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} {%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index 0f98cb1c..f7e3eba8 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -2,10 +2,10 @@ {%- set component_list = [] %} {%- set mpi_root_vendor_name = application['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_root_class_name = application['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APPlication_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RADio_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} {%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} {%- if 'SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in sysbuild['config'] and sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} {%- set nordic_top = True %} diff --git a/pyproject.toml b/pyproject.toml index 70454349..6133895d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dynamic = ["version","dependencies"] dependencies = {file = ["requirements.txt"]} [tool.setuptools] -packages=["suit_generator"] +packages=["suit_generator","build_configuration"] [project.scripts] suit-generator = "suit_generator.cli:main" diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 0bee664a..622b309d 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -10,6 +10,7 @@ import os import struct import uuid +import re from enum import Enum from cbor2 import dumps as cbor_dumps @@ -19,6 +20,7 @@ from suit_generator.exceptions import GeneratorError from suit_generator.exceptions import SUITError from suit_generator.suit.manifest import SuitManifest +from build_configuration.configuration import BuildConfiguration def add_arguments(parser): @@ -78,6 +80,12 @@ def add_arguments(parser): default=ImageCreator.default_dfu_max_caches, help=f"Max number of DFU caches. Default: {ImageCreator.default_dfu_max_caches}", ) + cmd_image_boot.add_argument( + "--config-file", + required=False, + default=None, + help=f"Path to KConfig file", + ) cmd_image_update.add_argument("--input-file", required=True, help="Input SUIT file; an envelope") cmd_image_update.add_argument( @@ -217,7 +225,7 @@ class EnvelopeStorage: }, ] - def __init__(self, base_address: int, load_defaults=True): + def __init__(self, base_address: int, load_defaults=True, kconfig=None): """Create object generating binary SUIT storage.""" self._assignments = [] self._base_address = base_address @@ -227,6 +235,25 @@ def __init__(self, base_address: int, load_defaults=True): for entry in self._CLASS_ROLE_ASSIGNMENTS: self.assign_role(entry["vendor_name"], entry["class_name"], entry["role"]) + if kconfig: + for entry in self._get_role_assignments_from_kconfig(kconfig): + self.assign_role(entry["vendor_name"], entry["class_name"], entry["role"]) + + @staticmethod + def _get_role_assignments_from_kconfig(kconfig: str) -> list: + config = BuildConfiguration(input_file=kconfig) + kconfig_assignments = [] + for key, value in config.items(): + if re_value := re.match(r'^CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$', key): + manifest = re_value.group('manifest') + data = { + "vendor_name": config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"], + "class_name": config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"], + "role": ManifestRole[f"APP_{manifest}" if manifest == 'ROOT' else manifest], + } + kconfig_assignments.append(data) + return kconfig_assignments + def assign_role(self, vendor_name: str, class_name: str, role: ManifestRole): """Assign role to envelope, identified by vendor and class name.""" vid = uuid.uuid5(uuid.NAMESPACE_DNS, vendor_name) @@ -238,13 +265,13 @@ def assign_role(self, vendor_name: str, class_name: str, role: ManifestRole): } ) - def _find_class(self, role: ManifestRole) -> bytes: + def _find_class(self, role: ManifestRole) -> bytes | None: for entry in self._assignments: if entry["role"] == role: return entry["class_id"] return None - def _find_role(self, class_id: bytes) -> ManifestRole: + def _find_role(self, class_id: bytes) -> ManifestRole | None: for entry in self._assignments: if entry["class_id"].hex() == class_id.hex(): return entry["role"] @@ -492,6 +519,7 @@ def _create_suit_storage_file_for_boot_legacy( envelope_slot_count: int, dir_name: str, dfu_max_caches: int, + config_file: str ) -> None: # Update candidate info # In the boot path it is used to inform no update candidate is pending. @@ -502,7 +530,7 @@ def _create_suit_storage_file_for_boot_legacy( combined_hex = IntelHex(uci_hex) - storage = EnvelopeStorageNrf54h20(installed_envelope_address) + storage = EnvelopeStorageNrf54h20(installed_envelope_address, kconfig=config_file) for envelope in envelopes: storage.add_envelope(envelope) combined_hex.merge(storage.as_intelhex()) @@ -518,6 +546,7 @@ def _create_suit_storage_files_for_boot( envelope_slot_count: int, dir_name: str, dfu_max_caches: int, + config_file: str, ) -> None: # Update candidate info # In the boot path it is used to inform no update candidate is pending. @@ -526,7 +555,7 @@ def _create_suit_storage_files_for_boot( ImageCreator._prepare_update_candidate_info_for_boot(dfu_max_caches), update_candidate_info_address ) - storage = EnvelopeStorageNrf54h20(installed_envelope_address) + storage = EnvelopeStorageNrf54h20(installed_envelope_address, kconfig=config_file) for envelope in envelopes: storage.add_envelope(envelope) @@ -568,16 +597,18 @@ def create_files_for_boot( envelope_slot_size: int, envelope_slot_count: int, dfu_max_caches: int, + config_file: str | None, ) -> None: """Create storage and payload hex files allowing boot execution path. - :param input_file: file path to SUIT envelope + :param input_files: file paths to SUIT envelope :param storage_output_directory: directory path to store hex files with SUIT storage contents :param update_candidate_info_address: address of SUIT storage update candidate info :param envelope_address: address of installed envelope in SUIT storage :param envelope_slot_size: number of bytes, reserved to store a single envelope, :param envelope_slot_count: number of envelope slots in SUIT storage, :param dfu_max_caches: maximum number of caches, allowed to be passed inside update candidate info, + :param config_file: path to KConfig file containing MPI settings """ try: envelopes = [] @@ -596,6 +627,7 @@ def create_files_for_boot( envelope_slot_count, storage_output_directory, dfu_max_caches, + config_file, ) ImageCreator._create_suit_storage_files_for_boot( envelopes, @@ -605,6 +637,7 @@ def create_files_for_boot( envelope_slot_count, storage_output_directory, dfu_max_caches, + config_file, ) except FileNotFoundError as error: raise GeneratorError(error) @@ -627,6 +660,7 @@ def create_files_for_update( :param dfu_partition_output_file: file path to hex file with DFU partition contents (the SUIT envelope) :param update_candidate_info_address: address of SUIT storage update candidate info :param dfu_partition_address: address of partition where DFU update candidate is stored + :param dfu_max_caches: maximum number of caches """ try: ImageCreator._create_suit_storage_file_for_update( @@ -663,6 +697,7 @@ def main(**kwargs) -> None: kwargs["envelope_slot_size"], kwargs["envelope_slot_count"], kwargs["dfu_max_caches"], + kwargs["config_file"], ) elif kwargs["image"] == ImageCreator.IMAGE_CMD_UPDATE: ImageCreator.create_files_for_update( diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 45664f94..55f1ab73 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -5,13 +5,20 @@ """Unit tests for cmd_image.py implementation.""" import pytest +import pathlib +import os -from suit_generator.cmd_image import ImageCreator, EnvelopeStorage +import yaml + +from suit_generator.cmd_image import ImageCreator, EnvelopeStorage, EnvelopeStorageNrf54h20 from suit_generator.cmd_image import main as cmd_image_main from suit_generator.exceptions import GeneratorError, SUITError +from suit_generator.suit.envelope import SuitEnvelopeTagged from unittest.mock import _Call +TEMP_DIRECTORY = pathlib.Path("test_test_data") + MAX_CACHE_COUNT = 16 addresses = {0x00000000: b"\x00\x00\x00\x00", 0xDEADBEEF: b"\xEF\xBE\xAD\xDE", 0xFFFFFFFF: b"\xFF\xFF\xFF\xFF"} @@ -583,6 +590,80 @@ ":00000001FF\n" ) +MPI_KCONFIG_TEMPLATE = """ +CONFIG_SUIT_MPI_ROOT_VENDOR_NAME="{root_vendor_name}" +CONFIG_SUIT_MPI_ROOT_CLASS_NAME="{root_class_name}" +CONFIG_SUIT_MPI_APP_LOCAL_1=y +CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME="{app_local_1_vendor_name}" +CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME="{app_local_1_class_name}" +CONFIG_SUIT_MPI_APP_LOCAL_2 is not set +CONFIG_SUIT_MPI_APP_LOCAL_3 is not set +CONFIG_SUIT_MPI_RAD_RECOVERY is not set +CONFIG_SUIT_MPI_RAD_LOCAL_1=y +CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME="{rad_local_1_vendor_name}" +CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME="{rad_local_1_class_name}" +CONFIG_SUIT_MPI_RAD_LOCAL_2 is not set +""" + +INPUT_ENVELOPE_YAML = """SUIT_Envelope_Tagged: + suit-authentication-wrapper: + SuitDigest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: abe742c95d30b5d0dcc33e03cc939e563b41673cd9c6d0c6d06a5300c9af182e + suit-manifest: + suit-manifest-version: 1 + suit-manifest-sequence-number: 1 + suit-common: + suit-components: + - - TEST + - 1 + - 2 + - 3 + suit-manifest-component-id: + - INSTLD_MFST + - RFC4122_UUID: + namespace: {component_id_namespace} + name: {component_id_name} +""" + + +@pytest.fixture +def setup_and_teardown(tmp_path_factory): + """Create and cleanup environment.""" + # Setup environment + # - create temp directory + # - input files + start_directory = os.getcwd() + path = tmp_path_factory.mktemp(TEMP_DIRECTORY) + os.chdir(path) + with open(".config", "w") as fh: + fh.write( + MPI_KCONFIG_TEMPLATE.format( + root_vendor_name="root_custom_vendor", + root_class_name="root_custom_class", + app_local_1_vendor_name="app_local_1_custom_vendor", + app_local_1_class_name="app_local_1_custom_class", + rad_local_1_vendor_name="rad_local_1_custom_vendor", + rad_local_1_class_name="rad_local_1_custom_class", + ) + ) + for item_name in ["root", "app_local_1", "rad_local_1"]: + with open(f"custom_{item_name}_component_id.suit", "wb") as fh: + envelope_data = SuitEnvelopeTagged.from_obj( + yaml.load( + INPUT_ENVELOPE_YAML.format( + component_id_namespace=f"{item_name}_custom_vendor", + component_id_name=f"{item_name}_custom_class", + ), + Loader=yaml.FullLoader + ) + ).to_cbor() + fh.write(envelope_data) + yield + # Cleanup environment + # - remove temp directory + os.chdir(start_directory) + def prepare_calls(data): """Split data by lines and wrap each line using _Call object for easy assertions; get rid of last newline""" @@ -677,6 +758,7 @@ def test_boot_subcommand_nonexisting_input_file(): dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0, + config_file=None, ) @@ -696,6 +778,7 @@ def test_boot_subcommand_manifest_without_component_id(mocker): dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=4, + config_file=None, ) @@ -714,6 +797,7 @@ def test_boot_subcommand_success(mocker): dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=6, + config_file=None, ) io_mock().read.assert_called_once() @@ -776,6 +860,7 @@ def test_malformed_envelope(mocker): dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0, + config_file=None, ) @@ -808,3 +893,89 @@ def bin2hex_mock(*args, **kwargs): dfu_partition_address=0x0E100000, dfu_max_caches=4, ) + + +def test_nrf54_storage_no_defaults(): + storage = EnvelopeStorage(base_address=0xFF, load_defaults=False, kconfig=None) + assert storage._assignments == [] + + +def test_nrf54_storage_defaults(): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=None) + assert len(storage._assignments) == 6 + + +def test_nrf54_storage_custom_config_defaults(setup_and_teardown): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config") + assert len(storage._assignments) == 9 + + +def test_nrf54_storage_custom_config_no_defaults(setup_and_teardown): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=False, kconfig=".config") + assert len(storage._assignments) == 3 + + +def test_generate_boot_images_for_default_vid_cid(): + pass + + +@pytest.mark.parametrize( + "input_envelope, expected_storage", + [ + ("custom_app_local_1_component_id.suit", "storage_application.hex"), + ("custom_rad_local_1_component_id.suit", "storage_radio.hex"), + ("custom_root_component_id.suit", "storage_application.hex"), + ], +) +def test_generate_boot_images_for_custom_vid_cid_separately(setup_and_teardown, input_envelope, expected_storage): + """Test generating boot images for custom VID/CID separately.""" + ImageCreator.create_files_for_boot( + input_files=[input_envelope], + storage_output_directory="./", + update_candidate_info_address=0, + envelope_address=0, + envelope_slot_size=2048, + envelope_slot_count=8, + dfu_max_caches=0, + config_file=".config", + ) + assert pathlib.Path(expected_storage).is_file() + + +def test_generate_boot_images_for_custom_vid_cid_all_envelopes_in_one_request(setup_and_teardown): + """Test generating boot images for custom VID/CID in one request.""" + ImageCreator.create_files_for_boot( + input_files=[ + "custom_app_local_1_component_id.suit", + "custom_rad_local_1_component_id.suit", + "custom_root_component_id.suit", + ], + storage_output_directory="./", + update_candidate_info_address=0, + envelope_address=0, + envelope_slot_size=2048, + envelope_slot_count=8, + dfu_max_caches=0, + config_file=".config", + ) + assert pathlib.Path("storage_application.hex").is_file() + assert pathlib.Path("storage_radio.hex").is_file() + + +def test_generate_update_images_for_custom_non_defined_vid_cid(setup_and_teardown): + """Test generating update images for custom VID/CID when VID/CID is not known.""" + with pytest.raises(GeneratorError): + ImageCreator.create_files_for_boot( + input_files=[ + "custom_app_local_1_component_id.suit", + "custom_rad_local_1_component_id.suit", + "custom_root_component_id.suit", + ], + storage_output_directory="./", + update_candidate_info_address=0, + envelope_address=0, + envelope_slot_size=2048, + envelope_slot_count=8, + dfu_max_caches=0, + config_file=None, + ) From 1b156bbfc25bd7b2a69308057e94a2d59a73b924 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 15 May 2024 15:09:59 +0200 Subject: [PATCH 07/67] ncs: Allow to pass addresses as args Add optional arguments, allowing to provide SUIT storage address as well as update candidate info address through command line. Ref: NCSDK-26649 Signed-off-by: Tomasz Chyrowicz --- ncs/build.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/ncs/build.py b/ncs/build.py index 7936266d..d1e2e853 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -106,6 +106,13 @@ def get_absolute_address(node, use_offset: bool = True): cmd_storage_arg_parser.add_argument( "--storage-output-directory", required=True, help="Directory path to store hex files with SUIT storage contents" ) + cmd_storage_arg_parser.add_argument( + "--storage-address", + required=False, + type=lambda x: int(x, 0), + default=ImageCreator.default_storage_address, + help="Absolute address of the SUIT storage area", + ) cmd_storage_arg_parser.add_argument( "--config-file", required=False, @@ -117,6 +124,20 @@ def get_absolute_address(node, use_offset: bool = True): UPDATE_CMD, help="Generate files needed for Secure Domain update", parents=[parent_parser] ) + cmd_update_arg_parser.add_argument( + "--update-candidate-info-address", + required=False, + type=lambda x: int(x, 0), + default=ImageCreator.default_update_candidate_info_address, + help="Address of SUIT storage update candidate info.", + ) + cmd_update_arg_parser.add_argument( + "--dfu-max-caches", + required=False, + type=int, + default=ImageCreator.default_dfu_max_caches, + help="Maximum number of caches, allowed to be passed inside update candidate info.", + ) cmd_update_arg_parser.add_argument("--input-file", required=True, help="SUIT envelope in binary format") cmd_update_arg_parser.add_argument( "--storage-output-file", required=True, help="SUIT storage output file in HEX format" @@ -144,15 +165,10 @@ def get_absolute_address(node, use_offset: bool = True): output_file.write(output_suit_content) elif arguments.command == STORAGE_CMD: - # fixme: envelope_address, update_candidate_info_address and dfu_max_caches shall be extracted from DTS ImageCreator.create_files_for_boot( input_files=arguments.input_envelope, storage_output_directory=arguments.storage_output_directory, - envelope_address=ImageCreator.default_envelope_address, - envelope_slot_size=ImageCreator.default_envelope_slot_size, - envelope_slot_count=ImageCreator.default_envelope_slot_count, - update_candidate_info_address=ImageCreator.default_update_candidate_info_address, - dfu_max_caches=ImageCreator.default_dfu_max_caches, + storage_address=arguments.storage_address, config_file=arguments.config_file ) elif arguments.command == UPDATE_CMD: @@ -160,7 +176,7 @@ def get_absolute_address(node, use_offset: bool = True): input_file=arguments.input_file, storage_output_file=arguments.storage_output_file, dfu_partition_output_file=arguments.dfu_partition_output_file, - update_candidate_info_address=ImageCreator.default_update_candidate_info_address, + update_candidate_info_address=arguments.update_candidate_info_address, dfu_partition_address=arguments.dfu_partition_address, - dfu_max_caches=ImageCreator.default_dfu_max_caches, + dfu_max_caches=arguments.dfu_max_caches, ) From 2b796f1a07ad41bb3cee0996a3f69d51022025b5 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 15 May 2024 15:11:19 +0200 Subject: [PATCH 08/67] cmd_image: Remove legacy commands Remove legacy commands from the SUIT generator commands. Ref: NCSDK-26649 Signed-off-by: Tomasz Chyrowicz --- build_configuration/configuration.py | 2 +- ncs/build.py | 4 +- suit_generator/cmd_image.py | 207 ++++----------------------- suit_generator/cmd_mpi.py | 2 +- tests/test_cmd_image.py | 75 ++++------ 5 files changed, 59 insertions(+), 231 deletions(-) diff --git a/build_configuration/configuration.py b/build_configuration/configuration.py index b00d4dfe..2a40de1e 100644 --- a/build_configuration/configuration.py +++ b/build_configuration/configuration.py @@ -46,4 +46,4 @@ def _parse(self) -> None: elif kconfig_value.isdecimal(): # int value kconfig_value = int(kconfig_value, base=10) - super().__setitem__(kconfig_name, kconfig_value) \ No newline at end of file + super().__setitem__(kconfig_name, kconfig_value) diff --git a/ncs/build.py b/ncs/build.py index d1e2e853..0b1b4ba3 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -117,7 +117,7 @@ def get_absolute_address(node, use_offset: bool = True): "--config-file", required=False, default=None, - help=f"Path to KConfig file", + help="Path to KConfig file", ) cmd_update_arg_parser = subparsers.add_parser( @@ -169,7 +169,7 @@ def get_absolute_address(node, use_offset: bool = True): input_files=arguments.input_envelope, storage_output_directory=arguments.storage_output_directory, storage_address=arguments.storage_address, - config_file=arguments.config_file + config_file=arguments.config_file, ) elif arguments.command == UPDATE_CMD: ImageCreator.create_files_for_update( diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 622b309d..f3287b9f 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -45,46 +45,17 @@ def add_arguments(parser): "--storage-output-directory", required=True, help="Output hex file with SUIT storage contents" ) cmd_image_boot.add_argument( - "--update-candidate-info-address", + "--storage-address", required=False, type=lambda x: int(x, 0), - default=ImageCreator.default_update_candidate_info_address, - help=f"Address of SUIT storage update candidate info. " - f"Default: 0x{ImageCreator.default_update_candidate_info_address:08X}", - ) - cmd_image_boot.add_argument( - "--envelope-address", - required=False, - type=lambda x: int(x, 0), - default=ImageCreator.default_envelope_address, - help=f"Address of installed envelope in SUIT storage. Default: 0x{ImageCreator.default_envelope_address:08X}", - ) - cmd_image_boot.add_argument( - "--envelope-slot-size", - required=False, - type=lambda x: int(x, 0), - default=ImageCreator.default_envelope_slot_size, - help=f"Envelope slot size in SUIT storage. Default: 0x{ImageCreator.default_envelope_slot_size:08X}", - ) - cmd_image_boot.add_argument( - "--envelope-slot-count", - required=False, - type=lambda x: int(x, 0), - default=ImageCreator.default_envelope_slot_count, - help=f"Max number of envelope slots in SUIT storage. Default: {ImageCreator.default_envelope_slot_count}", - ) - cmd_image_boot.add_argument( - "--dfu-max-caches", - required=False, - type=int, - default=ImageCreator.default_dfu_max_caches, - help=f"Max number of DFU caches. Default: {ImageCreator.default_dfu_max_caches}", + default=ImageCreator.default_storage_address, + help=f"Address of SUIT storage. Default: 0x{ImageCreator.default_storage_address:08X}", ) cmd_image_boot.add_argument( "--config-file", required=False, default=None, - help=f"Path to KConfig file", + help="Path to KConfig file", ) cmd_image_update.add_argument("--input-file", required=True, help="Input SUIT file; an envelope") @@ -145,51 +116,14 @@ class ManifestDomain(Enum): class EnvelopeStorage: - """Class generating SUIT storage binary in legacy format.""" + """Base class for generating SUIT storage binary.""" ENVELOPE_SLOT_VERSION = 1 ENVELOPE_SLOT_VERSION_KEY = 0 ENVELOPE_SLOT_CLASS_ID_OFFSET_KEY = 1 ENVELOPE_SLOT_ENVELOPE_BSTR_KEY = 2 - _LAYOUT = [ - { - "role": ManifestRole.APP_ROOT, - "offset": 2048 * 0, - "size": 2048, - "domain": ManifestDomain.APPLICATION, - }, - { - "role": ManifestRole.APP_LOCAL_1, - "offset": 2048 * 1, - "size": 2048, - "domain": ManifestDomain.APPLICATION, - }, - { - "role": ManifestRole.RAD_LOCAL_1, - "offset": 2048 * 2, - "size": 2048, - "domain": ManifestDomain.RADIO, - }, - { - "role": ManifestRole.SEC_TOP, - "offset": 2048 * 3, - "size": 2048, - "domain": ManifestDomain.SECURE, - }, - { - "role": ManifestRole.SEC_SDFW, - "offset": 2048 * 4, - "size": 2048, - "domain": ManifestDomain.SECURE, - }, - { - "role": ManifestRole.SEC_SYSCTRL, - "offset": 2048 * 5, - "size": 2048, - "domain": ManifestDomain.SECURE, - }, - ] + _LAYOUT = [] # Default manifest role assignments _CLASS_ROLE_ASSIGNMENTS = [ @@ -244,12 +178,12 @@ def _get_role_assignments_from_kconfig(kconfig: str) -> list: config = BuildConfiguration(input_file=kconfig) kconfig_assignments = [] for key, value in config.items(): - if re_value := re.match(r'^CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$', key): - manifest = re_value.group('manifest') + if re_value := re.match(r"^CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$", key): + manifest = re_value.group("manifest") data = { "vendor_name": config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"], "class_name": config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"], - "role": ManifestRole[f"APP_{manifest}" if manifest == 'ROOT' else manifest], + "role": ManifestRole[f"APP_{manifest}" if manifest == "ROOT" else manifest], } kconfig_assignments.append(data) return kconfig_assignments @@ -441,10 +375,8 @@ class EnvelopeStorageNrf54h20(EnvelopeStorage): class ImageCreator: """Helper class for extracting data from SUIT envelope and creating hex files.""" - default_update_candidate_info_address = 0x0E1E9340 - default_envelope_address = 0x0E1E7000 - default_envelope_slot_size = 2048 - default_envelope_slot_count = 8 + default_update_candidate_info_address = 0x0E1ED340 + default_storage_address = 0x0E1EB000 default_dfu_partition_address = 0x0E155000 default_dfu_max_caches = 6 @@ -462,21 +394,6 @@ def _prepare_suit_storage_struct_format(dfu_max_caches: int) -> str: # (void*, size_t) for each cache return "<" + "IIII" + dfu_max_caches * "II" - @staticmethod - def _prepare_update_candidate_info_for_boot(dfu_max_caches: int) -> bytes: - uci = struct.Struct(ImageCreator._prepare_suit_storage_struct_format(dfu_max_caches)) - - all_cache_values = dfu_max_caches * [0, 0] # address, size - struct_values = [ - ImageCreator.UPDATE_MAGIC_VALUE_AVAILABLE, # Update candidate info magic - 0, # Nb of memory regions - 0, # SUIT envelope address - 0, # SUIT envelope size - *all_cache_values, # Values for all the caches - ] - - return uci.pack(*struct_values) - @staticmethod def _prepare_update_candidate_info_for_update( dfu_partition_address: int, candidate_size: int, dfu_max_caches: int @@ -499,71 +416,32 @@ def _create_single_domain_storage_file_for_boot( storage: EnvelopeStorage, domain: ManifestDomain, dir_name: str, - additional_hex, ) -> None: combined_hex = IntelHex() - if additional_hex is not None: - combined_hex = IntelHex(additional_hex) envelopes_hex = storage.as_intelhex(domain) if envelopes_hex is not None: combined_hex.merge(envelopes_hex) - combined_hex.write_hex_file(dir_name + "/storage_" + domain.name.lower() + ".hex") - - def _create_suit_storage_file_for_boot_legacy( - envelopes: list[SuitEnvelope], - update_candidate_info_address: int, - installed_envelope_address: int, - envelope_slot_size: int, - envelope_slot_count: int, - dir_name: str, - dfu_max_caches: int, - config_file: str - ) -> None: - # Update candidate info - # In the boot path it is used to inform no update candidate is pending. - uci_hex = IntelHex() - uci_hex.frombytes( - ImageCreator._prepare_update_candidate_info_for_boot(dfu_max_caches), update_candidate_info_address - ) - - combined_hex = IntelHex(uci_hex) - - storage = EnvelopeStorageNrf54h20(installed_envelope_address, kconfig=config_file) - for envelope in envelopes: - storage.add_envelope(envelope) - combined_hex.merge(storage.as_intelhex()) - - combined_hex.write_hex_file(dir_name + "/storage.hex") + combined_hex.write_hex_file(dir_name + "/suit_installed_envelopes_" + domain.name.lower() + "_merged.hex") @staticmethod def _create_suit_storage_files_for_boot( envelopes: list[SuitEnvelope], - update_candidate_info_address: int, - installed_envelope_address: int, - envelope_slot_size: int, - envelope_slot_count: int, + storage_address: int, dir_name: str, - dfu_max_caches: int, config_file: str, ) -> None: - # Update candidate info - # In the boot path it is used to inform no update candidate is pending. - uci_hex = IntelHex() - uci_hex.frombytes( - ImageCreator._prepare_update_candidate_info_for_boot(dfu_max_caches), update_candidate_info_address - ) - - storage = EnvelopeStorageNrf54h20(installed_envelope_address, kconfig=config_file) + storage = EnvelopeStorageNrf54h20(storage_address, kconfig=config_file) for envelope in envelopes: storage.add_envelope(envelope) for domain in ManifestDomain: - additional_hex = None - if domain == ManifestDomain.APPLICATION: - additional_hex = uci_hex - ImageCreator._create_single_domain_storage_file_for_boot(storage, domain, dir_name, additional_hex) + ImageCreator._create_single_domain_storage_file_for_boot( + storage, + domain, + dir_name, + ) @staticmethod def _create_suit_storage_file_for_update( @@ -592,22 +470,14 @@ def _create_dfu_partition_hex_file(input_file: str, dfu_partition_output_file: s def create_files_for_boot( input_files: list[str], storage_output_directory: str, - update_candidate_info_address: int, - envelope_address: int, - envelope_slot_size: int, - envelope_slot_count: int, - dfu_max_caches: int, + storage_address: int, config_file: str | None, ) -> None: """Create storage and payload hex files allowing boot execution path. :param input_files: file paths to SUIT envelope :param storage_output_directory: directory path to store hex files with SUIT storage contents - :param update_candidate_info_address: address of SUIT storage update candidate info - :param envelope_address: address of installed envelope in SUIT storage - :param envelope_slot_size: number of bytes, reserved to store a single envelope, - :param envelope_slot_count: number of envelope slots in SUIT storage, - :param dfu_max_caches: maximum number of caches, allowed to be passed inside update candidate info, + :param storage_address: address of SUIT storage :param config_file: path to KConfig file containing MPI settings """ try: @@ -619,24 +489,10 @@ def create_files_for_boot( envelope.sever() envelopes.append(envelope) - ImageCreator._create_suit_storage_file_for_boot_legacy( - envelopes, - update_candidate_info_address, - envelope_address, - envelope_slot_size, - envelope_slot_count, - storage_output_directory, - dfu_max_caches, - config_file, - ) ImageCreator._create_suit_storage_files_for_boot( envelopes, - update_candidate_info_address, - envelope_address, - envelope_slot_size, - envelope_slot_count, + storage_address, storage_output_directory, - dfu_max_caches, config_file, ) except FileNotFoundError as error: @@ -681,22 +537,21 @@ def main(**kwargs) -> None: :Keyword Arguments: * **image** - subcommand to be executed * **input_file** - file path to SUIT envelope - * **storage_output_file** - file path to hex file with SUIT storage contents - for "update" command * **storage_output_directory** - directory path to store hex files with storage contents - for "boot" command - * **update_candidate_info_address** - address of SUIT storage update candidate info - * **envelope_address** - address of installed envelope in SUIT storage - * **dfu_partition_output_file** - file path to hex file with DFU partition contents (the SUIT envelope) - * **dfu_partition_address** - address of partition where DFU update candidate is stored + * **storage_address** - address of SUIT storage - for "boot" command + * **storage_output_file** - file path to hex file with SUIT storage contents - for "update" command + * **update_candidate_info_address** - address of SUIT storage update candidate info - for "update" command + * **dfu_partition_output_file** - file path to hex file with DFU partition contents (the SUIT envelope), + for "update" command + * **dfu_partition_address** - address of partition where DFU update candidate is stored - for "update" command + * **dfu_max_caches** - maximum number of caches, allowed to be passed inside update candidate info, + for "update" command """ if kwargs["image"] == ImageCreator.IMAGE_CMD_BOOT: ImageCreator.create_files_for_boot( kwargs["input_file"], kwargs["storage_output_directory"], - kwargs["update_candidate_info_address"], - kwargs["envelope_address"], - kwargs["envelope_slot_size"], - kwargs["envelope_slot_count"], - kwargs["dfu_max_caches"], + kwargs["storage_address"], kwargs["config_file"], ) elif kwargs["image"] == ImageCreator.IMAGE_CMD_UPDATE: diff --git a/suit_generator/cmd_mpi.py b/suit_generator/cmd_mpi.py index f3e4c0a5..d51610ee 100644 --- a/suit_generator/cmd_mpi.py +++ b/suit_generator/cmd_mpi.py @@ -90,7 +90,7 @@ def add_arguments(parser): class MpiGenerator: - """Class geenrating SUIT Manifest Provisioning Information.""" + """Class generating SUIT Manifest Provisioning Information.""" BYTE_ORDER = "little" diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 55f1ab73..73fbc879 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -10,7 +10,7 @@ import yaml -from suit_generator.cmd_image import ImageCreator, EnvelopeStorage, EnvelopeStorageNrf54h20 +from suit_generator.cmd_image import ImageCreator, EnvelopeStorageNrf54h20 from suit_generator.cmd_image import main as cmd_image_main from suit_generator.exceptions import GeneratorError, SUITError from suit_generator.suit.envelope import SuitEnvelopeTagged @@ -68,14 +68,10 @@ malformed_envelope_input = b"\x00" expected_boot_storage = ( - # Empty update candidate info (0x0E1E9340) - ":020000040E1ECE\n" - ":10934000AA55AA550000000000000000000000001F\n" - ":10935000000000000000000000000000000000000D\n" - ":1093600000000000000000000000000000000000FD\n" - ":1093700000000000000000000000000000000000ED\n" + # Empty update candidate info (0x0E1E9340 - 0x0E1E9380) # Uninitialized NVV area (0x0E1E9380 - 0x0E1E9400) # Empty root manifest slot (0x0E1E9400) + ":020000040E1ECE\n" ":10940000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C\n" ":10941000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C\n" ":10942000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C\n" @@ -655,7 +651,7 @@ def setup_and_teardown(tmp_path_factory): component_id_namespace=f"{item_name}_custom_vendor", component_id_name=f"{item_name}_custom_class", ), - Loader=yaml.FullLoader + Loader=yaml.FullLoader, ) ).to_cbor() fh.write(envelope_data) @@ -677,17 +673,6 @@ def test_struct_format(nb_of_caches): assert ImageCreator._prepare_suit_storage_struct_format(nb_of_caches) == format -@pytest.mark.parametrize("nb_of_caches", range(MAX_CACHE_COUNT + 1)) -def test_update_candidate_info_for_boot(nb_of_caches): - suit_storage_bytes = b"\xAA\x55\xAA\x55\x00\x00\x00\x00" - envelope_address_size_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00" - caches_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00" * nb_of_caches - - expected_bytes = suit_storage_bytes + envelope_address_size_bytes + caches_bytes - - assert ImageCreator._prepare_update_candidate_info_for_boot(nb_of_caches) == expected_bytes - - def test_update_candidate_info_verify_class_id_offset(): from suit_generator.envelope import SuitEnvelope from suit_generator.suit.envelope import SuitEnvelopeTagged @@ -698,14 +683,14 @@ def test_update_candidate_info_verify_class_id_offset(): envelope._envelope = SuitEnvelopeTagged.from_cbor(signed_envelope_input).to_obj() # Generate storage envelope slot for the envelope - storage = EnvelopeStorage(0) + storage = EnvelopeStorageNrf54h20(0) storage.add_envelope(envelope) (envelope_role, envelope_cbor) = storage._envelopes.popitem() # Extract the class ID, based on the offset and minified envelope storage_dict = cbor_loads(envelope_cbor) - offset = storage_dict[EnvelopeStorage.ENVELOPE_SLOT_CLASS_ID_OFFSET_KEY] - envelope_bstr = storage_dict[EnvelopeStorage.ENVELOPE_SLOT_ENVELOPE_BSTR_KEY] + offset = storage_dict[EnvelopeStorageNrf54h20.ENVELOPE_SLOT_CLASS_ID_OFFSET_KEY] + envelope_bstr = storage_dict[EnvelopeStorageNrf54h20.ENVELOPE_SLOT_ENVELOPE_BSTR_KEY] # RFC4122 uuid5(nordic_vid, 'nRF54H20_sample_app') exp_class_id = b"\x08\xc1\xb5\x99\x55\xe8\x5f\xbc\x9e\x76\x7b\xc2\x9c\xe1\xb0\x4d" @@ -738,7 +723,7 @@ def test_unsupported_image_subcommand(): input_file="", storage_output_file="", update_candidate_info_address=0, - envelope_address=0, + storage_address=0, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0, @@ -752,7 +737,7 @@ def test_boot_subcommand_nonexisting_input_file(): input_file=["nonexisting"], storage_output_directory="", update_candidate_info_address=0, - envelope_address=0, + storage_address=0, envelope_slot_size=2048, envelope_slot_count=8, dfu_partition_output_file="", @@ -772,7 +757,7 @@ def test_boot_subcommand_manifest_without_component_id(mocker): input_file=["some_input"], storage_output_directory="some_output", update_candidate_info_address=0x0E1EEC00, - envelope_address=0x0E1EED80, + storage_address=0x0E1EED80, envelope_slot_size=2048, envelope_slot_count=8, dfu_partition_output_file="", @@ -791,7 +776,7 @@ def test_boot_subcommand_success(mocker): input_file=["some_input"], storage_output_directory="some_output", update_candidate_info_address=0x0E1E9340, - envelope_address=0x0E1E7000, + storage_address=0x0E1E7000, envelope_slot_size=2048, envelope_slot_count=1, dfu_partition_output_file="", @@ -811,7 +796,7 @@ def test_update_subcommand_nonexisting_input_file(): input_file="nonexisting", storage_output_file="", update_candidate_info_address=0, - envelope_address=0, + storage_address=0, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0, @@ -832,7 +817,7 @@ def test_update_subcommand_success(mocker): input_file="some_input", storage_output_file="some_storage_output", update_candidate_info_address=0x0E1EEC00, - envelope_address=0x0E1EED80, + storage_address=0x0E1EED80, dfu_partition_output_file="some_dfu_partition_output", dfu_partition_address=0x0E100000, dfu_max_caches=4, @@ -854,7 +839,7 @@ def test_malformed_envelope(mocker): input_file=["some_input"], storage_output_directory="some_output", update_candidate_info_address=0x0E1FE000, - envelope_address=0x0E1FF000, + storage_address=0x0E1FF000, envelope_slot_size=2048, envelope_slot_count=8, dfu_partition_output_file="", @@ -888,7 +873,7 @@ def bin2hex_mock(*args, **kwargs): input_file="some_input", storage_output_file="some_storage_output", update_candidate_info_address=0x0E1EEC00, - envelope_address=0x0E1EED80, + storage_address=0x0E1EED80, dfu_partition_output_file="some_dfu_partition_output", dfu_partition_address=0x0E100000, dfu_max_caches=4, @@ -896,7 +881,7 @@ def bin2hex_mock(*args, **kwargs): def test_nrf54_storage_no_defaults(): - storage = EnvelopeStorage(base_address=0xFF, load_defaults=False, kconfig=None) + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=False, kconfig=None) assert storage._assignments == [] @@ -922,9 +907,9 @@ def test_generate_boot_images_for_default_vid_cid(): @pytest.mark.parametrize( "input_envelope, expected_storage", [ - ("custom_app_local_1_component_id.suit", "storage_application.hex"), - ("custom_rad_local_1_component_id.suit", "storage_radio.hex"), - ("custom_root_component_id.suit", "storage_application.hex"), + ("custom_app_local_1_component_id.suit", "suit_installed_envelopes_application_merged.hex"), + ("custom_rad_local_1_component_id.suit", "suit_installed_envelopes_radio_merged.hex"), + ("custom_root_component_id.suit", "suit_installed_envelopes_application_merged.hex"), ], ) def test_generate_boot_images_for_custom_vid_cid_separately(setup_and_teardown, input_envelope, expected_storage): @@ -932,11 +917,7 @@ def test_generate_boot_images_for_custom_vid_cid_separately(setup_and_teardown, ImageCreator.create_files_for_boot( input_files=[input_envelope], storage_output_directory="./", - update_candidate_info_address=0, - envelope_address=0, - envelope_slot_size=2048, - envelope_slot_count=8, - dfu_max_caches=0, + storage_address=0, config_file=".config", ) assert pathlib.Path(expected_storage).is_file() @@ -951,15 +932,11 @@ def test_generate_boot_images_for_custom_vid_cid_all_envelopes_in_one_request(se "custom_root_component_id.suit", ], storage_output_directory="./", - update_candidate_info_address=0, - envelope_address=0, - envelope_slot_size=2048, - envelope_slot_count=8, - dfu_max_caches=0, + storage_address=0, config_file=".config", ) - assert pathlib.Path("storage_application.hex").is_file() - assert pathlib.Path("storage_radio.hex").is_file() + assert pathlib.Path("suit_installed_envelopes_application_merged.hex").is_file() + assert pathlib.Path("suit_installed_envelopes_radio_merged.hex").is_file() def test_generate_update_images_for_custom_non_defined_vid_cid(setup_and_teardown): @@ -972,10 +949,6 @@ def test_generate_update_images_for_custom_non_defined_vid_cid(setup_and_teardow "custom_root_component_id.suit", ], storage_output_directory="./", - update_candidate_info_address=0, - envelope_address=0, - envelope_slot_size=2048, - envelope_slot_count=8, - dfu_max_caches=0, + storage_address=0, config_file=None, ) From e969378eb92bebc8628d2c0f89221004d4b61dbb Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Wed, 29 May 2024 07:25:43 +0200 Subject: [PATCH 09/67] feat: remove workarounds for broken DTS parent address (#117) * feat: remove workarounds for broken DTS parent address * feat: remove unused suit envelope templates Ref: NCSDK-22645 Signed-off-by: Robert Stypa --- ncs/build.py | 4 +- ncs/secdom_update_envelope.yaml.jinja2 | 60 ------------------- ncs/sysctrl_envelope.yaml.jinja2 | 82 -------------------------- 3 files changed, 1 insertion(+), 145 deletions(-) delete mode 100644 ncs/secdom_update_envelope.yaml.jinja2 delete mode 100644 ncs/sysctrl_envelope.yaml.jinja2 diff --git a/ncs/build.py b/ncs/build.py index 0b1b4ba3..7e994252 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -61,10 +61,8 @@ def render_template(template_location, data): def get_absolute_address(node, use_offset: bool = True): """Get absolute address of passed node.""" - # fixme: hardcoded value for parent node due to bug in DTS - # return node.parent.parent.regs[0].addr + node.regs[0].addr if use_offset: - return 0xE000000 + node.regs[0].addr + return node.parent.parent.regs[0].addr + node.regs[0].addr return node.regs[0].addr diff --git a/ncs/secdom_update_envelope.yaml.jinja2 b/ncs/secdom_update_envelope.yaml.jinja2 deleted file mode 100644 index d8ff2a61..00000000 --- a/ncs/secdom_update_envelope.yaml.jinja2 +++ /dev/null @@ -1,60 +0,0 @@ -{%- if secdom is not defined %} - {# secure domain build as main application #} - {%- set secdom = app %} -{%- endif %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - SOC_SPEC - - 1 - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: - name: nordicsemi.com - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_sec - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- if 'CONFIG_HW_REVISION_SOC1' in application['config'] %} - suit-install: [] -{%- else %} - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ secdom['name'] }}' - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure -{%- endif %} - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_sec - suit-integrated-payloads: - '#{{ secdom['name'] }}': {{ secdom['binary'] }} \ No newline at end of file diff --git a/ncs/sysctrl_envelope.yaml.jinja2 b/ncs/sysctrl_envelope.yaml.jinja2 deleted file mode 100644 index 3fd1d526..00000000 --- a/ncs/sysctrl_envelope.yaml.jinja2 +++ /dev/null @@ -1,82 +0,0 @@ -{# example template - need to be updated #} -{%- if sysctrl is not defined %} - {# sysctrl domain build as main application #} - {%- set sysctrl = app %} -{%- endif %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - # [0], RAM - - - MEM - - {{ sysctrl['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(sysctrl['dt'].chosen_nodes['zephyr,flash'], use_offset=False) }} - - {{ sysctrl['dt'].chosen_nodes['zephyr,flash'].regs[0].size }} - # [1], MRAM - - - MEM - - -1 - - {{ get_absolute_address(sysctrl['dt'].label2node['sysctrl_mram0'], use_offset=False) }} - - {{ sysctrl['dt'].label2node['sysctrl_mram0'].regs[0].size }} - # [2], Pseudo-component, used to verify the integrity of the incoming image. - # It is used as a target component for all fetch operations that download firmware. - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: [0, 1, 2] - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ sysctrl['binary'] }} - suit-validate: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-size: - file: {{ sysctrl['binary'] }} - - suit-condition-image-match: [] - suit-load: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-size: - file: {{ sysctrl['binary'] }} - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - - suit-directive-copy: [] - - suit-condition-image-match: [] - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-image-size: - file: {{ sysctrl['binary'] }} - - suit-condition-image-match: [] - - suit-directive-invoke: [] - suit-payload-fetch: - - suit-directive-set-component-index: 2 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ sysctrl['name'] }}' - - suit-directive-fetch: [] - - suit-condition-image-match: [] - suit-install: - - suit-directive-set-component-index: 2 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ sysctrl['name'] }}' - - suit-directive-fetch: [] - - suit-condition-image-match: [] - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-source-component: 2 - - suit-directive-copy: [] - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_sys - suit-integrated-payloads: - '#{{ sysctrl['name'] }}': {{ sysctrl['binary'] }} From 02f5362ecce78689cbfc14baf49a70b9a5ab6059 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 20 May 2024 14:15:12 +0200 Subject: [PATCH 10/67] Changes of storage generation needed for recovery Ref: NCSDK-26627 Signed-off-by: Artur Hadasz --- suit_generator/cmd_image.py | 12 +- tests/test_cmd_image.py | 393 +----------------------------------- 2 files changed, 13 insertions(+), 392 deletions(-) diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index f3287b9f..4dde7766 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -137,11 +137,21 @@ class EnvelopeStorage: "class_name": "nRF54H20_sample_app", "role": ManifestRole.APP_LOCAL_1, }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF54H20_sample_app_recovery", + "role": ManifestRole.APP_RECOVERY, + }, { "vendor_name": "nordicsemi.com", "class_name": "nRF54H20_sample_rad", "role": ManifestRole.RAD_LOCAL_1, }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF54H20_sample_rad_recovery", + "role": ManifestRole.RAD_RECOVERY, + }, { "vendor_name": "nordicsemi.com", "class_name": "nRF54H20_nordic_top", @@ -286,7 +296,7 @@ def as_intelhex(self, storage_domain: ManifestDomain = None): envelope_bytes = self._envelopes[role].ljust(max_size, b"\xFF") envelope_count += 1 else: - envelope_bytes = b"\xFF" * max_size + continue envelope_hex = IntelHex() envelope_hex.frombytes(envelope_bytes, self._base_address + entry["offset"]) diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 73fbc879..a9649b74 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -70,265 +70,6 @@ expected_boot_storage = ( # Empty update candidate info (0x0E1E9340 - 0x0E1E9380) # Uninitialized NVV area (0x0E1E9380 - 0x0E1E9400) - # Empty root manifest slot (0x0E1E9400) - ":020000040E1ECE\n" - ":10940000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C\n" - ":10941000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C\n" - ":10942000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C\n" - ":10943000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C\n" - ":10944000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C\n" - ":10945000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C\n" - ":10946000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C\n" - ":10947000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC\n" - ":10948000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC\n" - ":10949000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC\n" - ":1094A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC\n" - ":1094B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC\n" - ":1094C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC\n" - ":1094D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C\n" - ":1094E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C\n" - ":1094F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C\n" - ":10950000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B\n" - ":10951000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B\n" - ":10952000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B\n" - ":10953000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B\n" - ":10954000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B\n" - ":10955000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B\n" - ":10956000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B\n" - ":10957000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB\n" - ":10958000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB\n" - ":10959000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB\n" - ":1095A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB\n" - ":1095B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB\n" - ":1095C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB\n" - ":1095D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B\n" - ":1095E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B\n" - ":1095F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B\n" - ":10960000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A\n" - ":10961000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A\n" - ":10962000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A\n" - ":10963000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A\n" - ":10964000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A\n" - ":10965000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A\n" - ":10966000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A\n" - ":10967000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA\n" - ":10968000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA\n" - ":10969000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA\n" - ":1096A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA\n" - ":1096B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA\n" - ":1096C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA\n" - ":1096D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A\n" - ":1096E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A\n" - ":1096F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A\n" - ":10970000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69\n" - ":10971000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59\n" - ":10972000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49\n" - ":10973000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39\n" - ":10974000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29\n" - ":10975000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19\n" - ":10976000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09\n" - ":10977000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9\n" - ":10978000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9\n" - ":10979000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9\n" - ":1097A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9\n" - ":1097B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9\n" - ":1097C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9\n" - ":1097D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99\n" - ":1097E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89\n" - ":1097F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79\n" - ":10980000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68\n" - ":10981000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58\n" - ":10982000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48\n" - ":10983000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38\n" - ":10984000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28\n" - ":10985000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18\n" - ":10986000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08\n" - ":10987000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8\n" - ":10988000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8\n" - ":10989000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8\n" - ":1098A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8\n" - ":1098B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8\n" - ":1098C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8\n" - ":1098D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98\n" - ":1098E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88\n" - ":1098F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78\n" - ":10990000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67\n" - ":10991000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57\n" - ":10992000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47\n" - ":10993000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37\n" - ":10994000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27\n" - ":10995000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17\n" - ":10996000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07\n" - ":10997000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7\n" - ":10998000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7\n" - ":10999000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7\n" - ":1099A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7\n" - ":1099B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7\n" - ":1099C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7\n" - ":1099D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97\n" - ":1099E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87\n" - ":1099F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77\n" - ":109A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66\n" - ":109A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56\n" - ":109A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46\n" - ":109A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36\n" - ":109A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26\n" - ":109A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16\n" - ":109A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06\n" - ":109A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6\n" - ":109A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6\n" - ":109A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6\n" - ":109AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6\n" - ":109AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6\n" - ":109AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6\n" - ":109AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96\n" - ":109AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86\n" - ":109AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76\n" - ":109B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65\n" - ":109B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55\n" - ":109B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45\n" - ":109B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35\n" - ":109B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25\n" - ":109B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15\n" - ":109B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05\n" - ":109B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5\n" - ":109B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5\n" - ":109B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5\n" - ":109BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5\n" - ":109BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5\n" - ":109BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5\n" - ":109BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95\n" - ":109BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85\n" - ":109BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75\n" - # Application recovery manifest slot (0x0E1E9C00) - ":109C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64\n" - ":109C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54\n" - ":109C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44\n" - ":109C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34\n" - ":109C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24\n" - ":109C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14\n" - ":109C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04\n" - ":109C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4\n" - ":109C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4\n" - ":109C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4\n" - ":109CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4\n" - ":109CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4\n" - ":109CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4\n" - ":109CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94\n" - ":109CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84\n" - ":109CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74\n" - ":109D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63\n" - ":109D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53\n" - ":109D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43\n" - ":109D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33\n" - ":109D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23\n" - ":109D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13\n" - ":109D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03\n" - ":109D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3\n" - ":109D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3\n" - ":109D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3\n" - ":109DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3\n" - ":109DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3\n" - ":109DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3\n" - ":109DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93\n" - ":109DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83\n" - ":109DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73\n" - ":109E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62\n" - ":109E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52\n" - ":109E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42\n" - ":109E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32\n" - ":109E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22\n" - ":109E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12\n" - ":109E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02\n" - ":109E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2\n" - ":109E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2\n" - ":109E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2\n" - ":109EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2\n" - ":109EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2\n" - ":109EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2\n" - ":109ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92\n" - ":109EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82\n" - ":109EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72\n" - ":109F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61\n" - ":109F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51\n" - ":109F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41\n" - ":109F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31\n" - ":109F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21\n" - ":109F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11\n" - ":109F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01\n" - ":109F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1\n" - ":109F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1\n" - ":109F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1\n" - ":109FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1\n" - ":109FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1\n" - ":109FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1\n" - ":109FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91\n" - ":109FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81\n" - ":109FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71\n" - ":10A00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60\n" - ":10A01000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50\n" - ":10A02000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40\n" - ":10A03000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30\n" - ":10A04000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20\n" - ":10A05000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10\n" - ":10A06000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00\n" - ":10A07000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0\n" - ":10A08000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0\n" - ":10A09000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0\n" - ":10A0A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0\n" - ":10A0B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0\n" - ":10A0C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0\n" - ":10A0D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90\n" - ":10A0E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80\n" - ":10A0F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70\n" - ":10A10000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F\n" - ":10A11000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F\n" - ":10A12000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F\n" - ":10A13000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F\n" - ":10A14000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F\n" - ":10A15000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F\n" - ":10A16000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n" - ":10A17000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF\n" - ":10A18000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF\n" - ":10A19000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF\n" - ":10A1A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF\n" - ":10A1B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF\n" - ":10A1C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F\n" - ":10A1D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F\n" - ":10A1E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F\n" - ":10A1F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F\n" - ":10A20000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E\n" - ":10A21000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E\n" - ":10A22000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E\n" - ":10A23000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E\n" - ":10A24000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E\n" - ":10A25000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E\n" - ":10A26000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE\n" - ":10A27000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE\n" - ":10A28000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE\n" - ":10A29000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE\n" - ":10A2A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE\n" - ":10A2B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE\n" - ":10A2C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E\n" - ":10A2D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E\n" - ":10A2E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E\n" - ":10A2F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E\n" - ":10A30000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D\n" - ":10A31000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D\n" - ":10A32000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D\n" - ":10A33000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D\n" - ":10A34000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D\n" - ":10A35000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D\n" - ":10A36000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD\n" - ":10A37000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED\n" - ":10A38000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD\n" - ":10A39000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD\n" - ":10A3A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD\n" - ":10A3B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD\n" - ":10A3C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D\n" - ":10A3D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D\n" - ":10A3E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D\n" - ":10A3F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D\n" # Application local 1 manifest slot (0x0E1EA400) ":10A40000A300010118FB0259010BD86BA2025827C7\n" ":10A41000815824822F58203045487BD1B451ABF568\n" @@ -394,136 +135,6 @@ ":10A7D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89\n" ":10A7E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79\n" ":10A7F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69\n" - # Application local 2 manifest slot (0x0E1EA800) - ":10A80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58\n" - ":10A81000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48\n" - ":10A82000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38\n" - ":10A83000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28\n" - ":10A84000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18\n" - ":10A85000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08\n" - ":10A86000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8\n" - ":10A87000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8\n" - ":10A88000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8\n" - ":10A89000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8\n" - ":10A8A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8\n" - ":10A8B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8\n" - ":10A8C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98\n" - ":10A8D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88\n" - ":10A8E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78\n" - ":10A8F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68\n" - ":10A90000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57\n" - ":10A91000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47\n" - ":10A92000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37\n" - ":10A93000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27\n" - ":10A94000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17\n" - ":10A95000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07\n" - ":10A96000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7\n" - ":10A97000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7\n" - ":10A98000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7\n" - ":10A99000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7\n" - ":10A9A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7\n" - ":10A9B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7\n" - ":10A9C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97\n" - ":10A9D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87\n" - ":10A9E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77\n" - ":10A9F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67\n" - ":10AA0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56\n" - ":10AA1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46\n" - ":10AA2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36\n" - ":10AA3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26\n" - ":10AA4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16\n" - ":10AA5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06\n" - ":10AA6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6\n" - ":10AA7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6\n" - ":10AA8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6\n" - ":10AA9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6\n" - ":10AAA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6\n" - ":10AAB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6\n" - ":10AAC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96\n" - ":10AAD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86\n" - ":10AAE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76\n" - ":10AAF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66\n" - ":10AB0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55\n" - ":10AB1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45\n" - ":10AB2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35\n" - ":10AB3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25\n" - ":10AB4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15\n" - ":10AB5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05\n" - ":10AB6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5\n" - ":10AB7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5\n" - ":10AB8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5\n" - ":10AB9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5\n" - ":10ABA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5\n" - ":10ABB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5\n" - ":10ABC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95\n" - ":10ABD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85\n" - ":10ABE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75\n" - ":10ABF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65\n" - # Application local 3 manifest slot (0x0E1EAC00) - ":10AC0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54\n" - ":10AC1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44\n" - ":10AC2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34\n" - ":10AC3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24\n" - ":10AC4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14\n" - ":10AC5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04\n" - ":10AC6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4\n" - ":10AC7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4\n" - ":10AC8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4\n" - ":10AC9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4\n" - ":10ACA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4\n" - ":10ACB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4\n" - ":10ACC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94\n" - ":10ACD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84\n" - ":10ACE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74\n" - ":10ACF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64\n" - ":10AD0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53\n" - ":10AD1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43\n" - ":10AD2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33\n" - ":10AD3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23\n" - ":10AD4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13\n" - ":10AD5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03\n" - ":10AD6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3\n" - ":10AD7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3\n" - ":10AD8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3\n" - ":10AD9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3\n" - ":10ADA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3\n" - ":10ADB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3\n" - ":10ADC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93\n" - ":10ADD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83\n" - ":10ADE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73\n" - ":10ADF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63\n" - ":10AE0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52\n" - ":10AE1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42\n" - ":10AE2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32\n" - ":10AE3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22\n" - ":10AE4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12\n" - ":10AE5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02\n" - ":10AE6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2\n" - ":10AE7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2\n" - ":10AE8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2\n" - ":10AE9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2\n" - ":10AEA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2\n" - ":10AEB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2\n" - ":10AEC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92\n" - ":10AED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82\n" - ":10AEE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72\n" - ":10AEF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62\n" - ":10AF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51\n" - ":10AF1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41\n" - ":10AF2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31\n" - ":10AF3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21\n" - ":10AF4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11\n" - ":10AF5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01\n" - ":10AF6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1\n" - ":10AF7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1\n" - ":10AF8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1\n" - ":10AF9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1\n" - ":10AFA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1\n" - ":10AFB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1\n" - ":10AFC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91\n" - ":10AFD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81\n" - ":10AFE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71\n" - ":10AFF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61\n" # Application area end (0x0E1EB000) ":00000001FF\n" ) @@ -887,12 +498,12 @@ def test_nrf54_storage_no_defaults(): def test_nrf54_storage_defaults(): storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=None) - assert len(storage._assignments) == 6 + assert len(storage._assignments) == 8 def test_nrf54_storage_custom_config_defaults(setup_and_teardown): storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config") - assert len(storage._assignments) == 9 + assert len(storage._assignments) == 11 def test_nrf54_storage_custom_config_no_defaults(setup_and_teardown): From 72e873da8201d8f48e2821ee0ae1b076e081cc5d Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Wed, 29 May 2024 23:07:36 +0200 Subject: [PATCH 11/67] feat: enable local envelope generation only for rad and app (#116) * feat: enable local envelope generation only for rad and app * fix: workaround for radio build as main application Ref: NCSDK-27372 Signed-off-by: Robert Stypa --- ncs/Kconfig | 2 +- ncs/rad_envelope.yaml.jinja2 | 9 +++++++-- ncs/root_with_binary_nordic_top.yaml.jinja2 | 17 +++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index ca95483d..5b4e085b 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -28,4 +28,4 @@ config SUIT_ENVELOPE_OUTPUT_MPI_MERGE config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" - default y + default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index 5f24f2a7..24be0ef3 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -1,5 +1,10 @@ -{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- if application is defined %} + {%- set main_config = application %} +{%- else %} + {%- set main_config = radio %} +{%- endif %} +{%- set mpi_radio_vendor_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} {%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index f7e3eba8..fc3ce02e 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -1,11 +1,16 @@ {%- set component_index = 0 %} {%- set component_list = [] %} -{%- set mpi_root_vendor_name = application['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_root_class_name = application['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_radio_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- if application is not defined %} + {%- set main_config = radio %} +{%- else %} + {%- set main_config = application %} +{%- endif %} +{%- set mpi_root_vendor_name = main_config['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_root_class_name = main_config['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} +{%- set mpi_application_vendor_name = main_config['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = main_config['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_radio_vendor_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_radio_class_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} {%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} {%- if 'SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in sysbuild['config'] and sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} {%- set nordic_top = True %} From 15711cccd7700652ca3906b86d4d58cfe340e70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Mon, 3 Jun 2024 13:46:42 +0200 Subject: [PATCH 12/67] ncs: prevent duplicate target names (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit suit-generator now prints an error when the build has two images with the same CONFIG_SUIT_ENVELOPE_TARGET. Signed-off-by: Rafał Kuźnia --- ncs/build.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ncs/build.py b/ncs/build.py index 7e994252..e9f6a2c3 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -40,6 +40,12 @@ def read_configurations(configurations): # add prefix _ to the names starting with digits, for example: # 802154_rpmsg_subimage will be available in the templates as _802154_rpmsg_subimage image_name = f"_{name}" if re.match("^[0-9].*]", name) else name + + if image_name in data: + existing_binary = data[image_name]["binary"] + raise ValueError("Two images have the same CONFIG_SUIT_ENVELOPE_TARGET value: " + f"{binary} and {existing_binary}") + data[image_name] = { "name": name, "config": BuildConfiguration(kconfig), From 0b06da29af3d4e60e0eb0781f0e2a85ed964f0ee Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 5 Jun 2024 14:51:39 +0200 Subject: [PATCH 13/67] ncs: envelopes: Verify child manifests before install It is worth checking if all dependency manifests were correctly downloaded prior suit-install sequence execution. Ref: NCSDK-NONE Signed-off-by: Tomasz Chyrowicz --- ncs/nordic_top_envelope.yaml.jinja2 | 33 ++++++++++++ ncs/root_with_binary_nordic_top.yaml.jinja2 | 52 +++++++++++++++++++ ncs/root_with_nordic_top_envelope.yaml.jinja2 | 52 +++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index c0fadbed..6146a7e5 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -88,8 +88,40 @@ SUIT_Envelope_Tagged: - suit-send-record-failure - suit-send-sysinfo-success - suit-send-sysinfo-failure + suit-install: - suit-directive-set-component-index: 0 + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ secdom['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ sysctrl['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + + suit-candidate-verification: + - suit-directive-set-component-index: 0 - suit-directive-override-parameters: suit-parameter-uri: '#{{ secdom['name'] }}' suit-parameter-image-digest: @@ -136,6 +168,7 @@ SUIT_Envelope_Tagged: - suit-send-record-failure - suit-send-sysinfo-success - suit-send-sysinfo-failure + suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index fc3ce02e..86f6872d 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -111,6 +111,57 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 0 +{%- if radio is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ radio['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if application is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ application['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if nordic_top %} + - suit-directive-override-parameters: + suit-parameter-uri: '#top' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} + + suit-candidate-verification: + - suit-directive-set-component-index: 0 {%- if radio is defined %} - suit-directive-override-parameters: suit-parameter-uri: '#{{ radio['name'] }}' @@ -186,6 +237,7 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure {%- endif %} + suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index e7c37ec7..aa6412e7 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -108,6 +108,57 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 0 +{%- if radio is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ radio['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if application is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ application['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} +{%- if top is defined %} + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ top['name'] }}' + - suit-directive-fetch: + - suit-send-record-failure + - suit-condition-dependency-integrity: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-process-dependency: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure +{%- endif %} + + suit-candidate-verification: + - suit-directive-set-component-index: 0 {%- if radio is defined %} - suit-directive-override-parameters: suit-parameter-uri: '#{{ radio['name'] }}' @@ -183,6 +234,7 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure {%- endif %} + suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: From 623d6c9d9bd25ef534aa39bbd7bad3c40a3a54c0 Mon Sep 17 00:00:00 2001 From: "Blamowski, Andrzej" Date: Mon, 3 Jun 2024 16:09:26 +0200 Subject: [PATCH 14/67] Split CLI and module logger configuration --- suit_generator/__init__.py | 16 ------- suit_generator/args.py | 8 +++- suit_generator/cli.py | 34 ++++++++++++++- suit_generator/logger.py | 83 +++++++++++++++++++++++++++++++++++++ suit_generator/logging.yaml | 11 +++-- tests/test_args.py | 5 ++- tests/test_cli.py | 6 +-- 7 files changed, 136 insertions(+), 27 deletions(-) diff --git a/suit_generator/__init__.py b/suit_generator/__init__.py index f302f4e2..12e7f21e 100644 --- a/suit_generator/__init__.py +++ b/suit_generator/__init__.py @@ -23,19 +23,3 @@ Version of tool is autogenerated using setuptools_scm. To create new version just add git tag v.X.Y.Z """ -import logging -from logging.config import dictConfig - -import yaml -import os -import pathlib - -dir_path = pathlib.Path(os.path.dirname(os.path.realpath(__file__))) - -with open(dir_path / "logging.yaml", "r") as stream: - config = yaml.load(stream, Loader=yaml.FullLoader) - -logging.config.dictConfig(config) - -logger = logging.getLogger(__name__) -logger.debug("suit-generator initialized and logging configuration loaded") diff --git a/suit_generator/args.py b/suit_generator/args.py index 4c765765..f58964b4 100644 --- a/suit_generator/args.py +++ b/suit_generator/args.py @@ -19,6 +19,8 @@ def _parser() -> ArgumentParser: parser = ArgumentParser() + parser.add_argument("--log-filename", default=None, help="Log file path (it will override cli defaults).") + subparsers = parser.add_subparsers(dest="command", required=True, help="Choose subcommand:") create_args(subparsers) parse_args(subparsers) @@ -35,13 +37,15 @@ def parse_arguments() -> Tuple: Parse passed CLI parameters and return argparse.Namespace - :return: Tuple contains command and it's parameters + :return: Tuple contains command, it's parameters and log filename """ parser = _parser() arguments = parser.parse_args() cmd = str(arguments.command) + log_filename = arguments.log_filename # remove unnecessary arguments to simplify command calling del arguments.command + del arguments.log_filename - return cmd, arguments + return cmd, arguments, log_filename diff --git a/suit_generator/cli.py b/suit_generator/cli.py index 5527b407..3f77aba6 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -13,11 +13,16 @@ from suit_generator import cmd_parse, cmd_keys, cmd_convert, cmd_create, cmd_image, cmd_mpi, args from suit_generator.exceptions import GeneratorError, SUITError +from suit_generator.logger import configure_logging import logging +import yaml + +from pathlib import Path logger = logging.getLogger(__name__) + COMMAND_EXECUTORS = { cmd_parse.PARSE_CMD: cmd_parse.main, cmd_create.CREATE_CMD: cmd_create.main, @@ -28,9 +33,36 @@ } +def configure_cli_logging(log_file_name: str = None): + """ + Configure logging for CLI. + + :param log_file_name: log file name to be used (override default log file name from logging.yaml) + """ + dir_path = Path(__file__).resolve().parent + + with open(dir_path / "logging.yaml", "r") as stream: + config = yaml.load(stream, Loader=yaml.FullLoader) + + # override log file name if passed as argument + if log_file_name: + config['handlers']['file']['filename'] = log_file_name + + configure_logging(config) + + # any logger initialized before call to 'configure_logging' will be disabled because logging.yaml + # contains entry 'disable_existing_loggers: true', if yaml has no explicit configuration for it + # so we need to defer logger creation (call to 'getLogger') after 'configure_logging' call or enable it manually + logger.disabled = False + logger.debug("CLI logging configured.") + + def main() -> None: """Parse input arguments and call passed CMD executor.""" - command, arguments = args.parse_arguments() + command, arguments, log_file = args.parse_arguments() + + configure_cli_logging(log_file) + # passing arguments as kwargs used to simplify commands calling, improve documentation and error handling try: COMMAND_EXECUTORS[command](**vars(arguments)) diff --git a/suit_generator/logger.py b/suit_generator/logger.py index b692675f..a4b68027 100644 --- a/suit_generator/logger.py +++ b/suit_generator/logger.py @@ -4,13 +4,23 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # """Logger helper methods.""" +from __future__ import annotations + import functools import inspect import logging +import logging.config + +from typing import Any +from pathlib import Path + logger = logging.getLogger(__name__) +DEFAULT_LOG_FORMAT: str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +DEFAULT_LOG_FILE_PATH: Path = 'suit-generator.log' + def log_call(func): """Decorate function or method if call shall be logged.""" @@ -28,3 +38,76 @@ def inner_func(*args, **kwargs): raise return inner_func + + +def get_default_logger_config() -> dict[str, Any]: + """ + Get default logger configuration. + Use variables DEFAULT_LOG_FORMAT and DEFAULT_LOG_FILE_PATH to override log format and log file path. + + :return: Default logger configuration dictionary + """ + config = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'simple': { + 'format': DEFAULT_LOG_FORMAT, + }, + }, + 'handlers': { + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'formatter': 'simple', + 'level': 'DEBUG', + 'filename': DEFAULT_LOG_FILE_PATH, + 'mode': 'a', + 'backupCount': 10, # max 10 files + 'maxBytes': 10485760 # max ten mega bytes (1024*1024*10) + }, + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'ERROR', + 'formatter': 'simple', + 'stream': 'ext://sys.stdout', + } + }, + 'loggers': { + 'suit_generator': { + 'level': 'DEBUG', + 'handlers': ['file', 'console'], + 'propagate': False, + }, + 'suit_generator.suit.types.common': { + 'level': 'DEBUG', + 'handlers': ['file'], + 'propagate': False, + }, + 'suit_generator.logger': { + 'level': 'DEBUG', + 'handlers': ['file'], + 'propagate': False, + }, + 'ncs': { + 'level': 'DEBUG', + 'handlers': ['file'], + 'propagate': False, + }, + } + } + + return config + + +def configure_logging(config: dict[str, Any] | None = None) -> None: + """ + Function configures logging. If no configuration is passed, default configuration is used. + + :param config: Logging configuration + """ + if config is None: + config = get_default_logger_config() + + logging.config.dictConfig(config) + + logger.debug("*** suit-generator initialized and logging configuration loaded") diff --git a/suit_generator/logging.yaml b/suit_generator/logging.yaml index d7ef563d..7549a422 100644 --- a/suit_generator/logging.yaml +++ b/suit_generator/logging.yaml @@ -21,12 +21,15 @@ loggers: suit_generator.suit.types.common: level: DEBUG handlers: [file] + propagate: no suit_generator.logger: level: DEBUG handlers: [file] + propagate: no ncs: level: DEBUG - handler: [file] -root: - level: DEBUG - handlers: [file, console] \ No newline at end of file + handlers: [file] + propagate: no + root: + level: DEBUG + handlers: [file, console] diff --git a/tests/test_args.py b/tests/test_args.py index ec51f58d..ee8fe2e5 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -14,13 +14,16 @@ @mock.patch( "argparse.ArgumentParser.parse_args", - return_value=argparse.Namespace(command="create", input_file="test1.json", output_file="test2.suit"), + return_value=argparse.Namespace( + command="create", input_file="test1.json", output_file="test2.suit", log_filename='test.log' + ), ) def test_create_cmd_mode_auto(mock_args): """Test arguments parsing.""" args = parse_arguments() assert args[0] == "create" assert vars(args[1]) == {"input_file": "test1.json", "output_file": "test2.suit"} + assert args[2] == 'test.log' @pytest.mark.parametrize( diff --git a/tests/test_cli.py b/tests/test_cli.py index 602688c0..4e5094d8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -20,19 +20,19 @@ def __init__(self, **kwargs): def monkey_patched_parse_arguments(cmd): """Monkey patched argument parser.""" args = Namespace(output_file="test1", input_file="test2", input_format="json") - return cmd, args + return cmd, args, None def monkey_patched_create_arguments(cmd): """Monkey patched argument parser.""" args = Namespace(input_file="test1", output_format="test2", output_file="test3") - return cmd, args + return cmd, args, None def monkey_patched_keys_arguments(cmd): """Monkey patched argument parser.""" args = Namespace(output_file="test1", key_type="test2") - return cmd, args + return cmd, args, None def monkey_patched_main_create(input_file: str, output_format: str, output_file: str): From 34583134444365cbf35a591c7f8c5c497a10feef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Mon, 10 Jun 2024 14:08:53 +0200 Subject: [PATCH 15/67] feat: add binary file name value to template generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file name component of the binary file can now be used in manifest generator. Signed-off-by: Rafał Kuźnia --- ncs/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ncs/build.py b/ncs/build.py index e9f6a2c3..ec228f7d 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -53,6 +53,7 @@ def read_configurations(configurations): if edt_data: data[image_name]["dt"] = edt_data if binary: + data[image_name]["filename"] = pathlib.Path(binary).name data[image_name]["binary"] = binary data["get_absolute_address"] = get_absolute_address return data From fcba04abb5a85786f75e3186c19ec8502ff0e68e Mon Sep 17 00:00:00 2001 From: "Blamowski, Andrzej" Date: Wed, 19 Jun 2024 15:16:53 +0200 Subject: [PATCH 16/67] remove logger setup for module, clean cli config --- suit_generator/cli.py | 7 ++-- suit_generator/logger.py | 73 ------------------------------------- suit_generator/logging.yaml | 10 +---- 3 files changed, 4 insertions(+), 86 deletions(-) diff --git a/suit_generator/cli.py b/suit_generator/cli.py index 3f77aba6..dea2284b 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -13,16 +13,15 @@ from suit_generator import cmd_parse, cmd_keys, cmd_convert, cmd_create, cmd_image, cmd_mpi, args from suit_generator.exceptions import GeneratorError, SUITError -from suit_generator.logger import configure_logging import logging +import logging.config import yaml from pathlib import Path logger = logging.getLogger(__name__) - COMMAND_EXECUTORS = { cmd_parse.PARSE_CMD: cmd_parse.main, cmd_create.CREATE_CMD: cmd_create.main, @@ -48,13 +47,13 @@ def configure_cli_logging(log_file_name: str = None): if log_file_name: config['handlers']['file']['filename'] = log_file_name - configure_logging(config) + logging.config.dictConfig(config) # any logger initialized before call to 'configure_logging' will be disabled because logging.yaml # contains entry 'disable_existing_loggers: true', if yaml has no explicit configuration for it # so we need to defer logger creation (call to 'getLogger') after 'configure_logging' call or enable it manually logger.disabled = False - logger.debug("CLI logging configured.") + logger.debug("*** suit-generator initialized and logging configuration loaded") def main() -> None: diff --git a/suit_generator/logger.py b/suit_generator/logger.py index a4b68027..d16ea629 100644 --- a/suit_generator/logger.py +++ b/suit_generator/logger.py @@ -38,76 +38,3 @@ def inner_func(*args, **kwargs): raise return inner_func - - -def get_default_logger_config() -> dict[str, Any]: - """ - Get default logger configuration. - Use variables DEFAULT_LOG_FORMAT and DEFAULT_LOG_FILE_PATH to override log format and log file path. - - :return: Default logger configuration dictionary - """ - config = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'simple': { - 'format': DEFAULT_LOG_FORMAT, - }, - }, - 'handlers': { - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'formatter': 'simple', - 'level': 'DEBUG', - 'filename': DEFAULT_LOG_FILE_PATH, - 'mode': 'a', - 'backupCount': 10, # max 10 files - 'maxBytes': 10485760 # max ten mega bytes (1024*1024*10) - }, - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'ERROR', - 'formatter': 'simple', - 'stream': 'ext://sys.stdout', - } - }, - 'loggers': { - 'suit_generator': { - 'level': 'DEBUG', - 'handlers': ['file', 'console'], - 'propagate': False, - }, - 'suit_generator.suit.types.common': { - 'level': 'DEBUG', - 'handlers': ['file'], - 'propagate': False, - }, - 'suit_generator.logger': { - 'level': 'DEBUG', - 'handlers': ['file'], - 'propagate': False, - }, - 'ncs': { - 'level': 'DEBUG', - 'handlers': ['file'], - 'propagate': False, - }, - } - } - - return config - - -def configure_logging(config: dict[str, Any] | None = None) -> None: - """ - Function configures logging. If no configuration is passed, default configuration is used. - - :param config: Logging configuration - """ - if config is None: - config = get_default_logger_config() - - logging.config.dictConfig(config) - - logger.debug("*** suit-generator initialized and logging configuration loaded") diff --git a/suit_generator/logging.yaml b/suit_generator/logging.yaml index 7549a422..6f06bb55 100644 --- a/suit_generator/logging.yaml +++ b/suit_generator/logging.yaml @@ -18,18 +18,10 @@ handlers: backupCount: 10 # max 10 files maxBytes: 10485760 # max ten mega bytes (1024*1024*10) loggers: - suit_generator.suit.types.common: + suit_generator: level: DEBUG - handlers: [file] - propagate: no - suit_generator.logger: - level: DEBUG - handlers: [file] - propagate: no ncs: level: DEBUG - handlers: [file] - propagate: no root: level: DEBUG handlers: [file, console] From dbd12dc884aecedf03580967e6ab7d2bbf22dedb Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:55:29 +0200 Subject: [PATCH 17/67] fix: set ERROR as default log level (#127) Ref: NONE Signed-off-by: Robert Stypa --- suit_generator/logger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/suit_generator/logger.py b/suit_generator/logger.py index d16ea629..e533314d 100644 --- a/suit_generator/logger.py +++ b/suit_generator/logger.py @@ -15,7 +15,6 @@ from typing import Any from pathlib import Path - logger = logging.getLogger(__name__) DEFAULT_LOG_FORMAT: str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' @@ -34,7 +33,7 @@ def inner_func(*args, **kwargs): logger.debug(f"{info.filename}:{info.function}:{info.lineno}:{func.__name__}({args=},{kwargs=})") return func(*args, **kwargs) except Exception as e: - logger.warning(f"{info.filename}:{info.function}:{info.lineno}:{func.__name__}({args=},{kwargs=}):\n{e}") + logger.debug(f"Unable to parse data: {args=},{kwargs=}") raise return inner_func From e5c9bad03412b7c1d51132695c627b9cf4f4d640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Sun, 16 Jun 2024 22:41:37 +0200 Subject: [PATCH 18/67] fix: calculate envelope digest by specified algorithm Previously, the envelope digest used in the parent envelope was calculated using the same algorithm as the digest for the child envelope authentication block. Now the digest algorithms used in the parent and child manifests are separate. --- suit_generator/suit/authentication.py | 2 +- suit_generator/suit/envelope.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/suit_generator/suit/authentication.py b/suit_generator/suit/authentication.py index 417596ad..7281e371 100644 --- a/suit_generator/suit/authentication.py +++ b/suit_generator/suit/authentication.py @@ -147,7 +147,7 @@ def from_obj(cls, obj: dict) -> SuitDigestRaw: sub_envelope = SuitEnvelopeTagged.from_cbor(fh.read()) sub_envelope.update_severable_digests() sub_envelope.update_digest() - obj[suit_digest_bytes.name] = sub_envelope.get_digest().SuitDigestRaw[1].SuitDigestBytes.hex() + obj[suit_digest_bytes.name] = sub_envelope.get_manifest_digest(obj[suit_digest_algorithm_id.name]).hex() elif "raw" in digest_dict.keys(): obj[suit_digest_bytes.name] = digest_dict["raw"] else: diff --git a/suit_generator/suit/envelope.py b/suit_generator/suit/envelope.py index d7601abb..451332cb 100644 --- a/suit_generator/suit/envelope.py +++ b/suit_generator/suit/envelope.py @@ -78,17 +78,24 @@ def update_digest(self): alg = ( self.value.value.value[suit_authentication_wrapper].SuitAuthentication[0].SuitDigest.SuitDigestRaw[0].value ) - manifest = self.value.value.value[suit_manifest].to_cbor() - hash_func = SuitHash(alg) - new_digest = binascii.a2b_hex(hash_func.hash(manifest)) self.value.value.value[suit_authentication_wrapper].SuitAuthentication[0].SuitDigest.SuitDigestRaw[ 1 - ].SuitDigestBytes = new_digest + ].SuitDigestBytes = self.get_manifest_digest(alg) def get_digest(self): """Return digest from parsed envelope.""" return self.value.value.value[suit_authentication_wrapper].SuitAuthentication[0].SuitDigest + def get_manifest(self): + """Return manifest from parsed envelope.""" + return self.value.value.value[suit_manifest] + + def get_manifest_digest(self, alg): + """Return digest from parsed envelope.""" + manifest = self.get_manifest().to_cbor() + hash_func = SuitHash(alg) + return binascii.a2b_hex(hash_func.hash(manifest)) + def update_severable_digests(self): """Update digest in the envelope for severed elements.""" severable_elements = [ From bcee2a1e8b129e847c21c5cfbf3aba4831be750f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Mon, 17 Jun 2024 12:15:49 +0200 Subject: [PATCH 19/67] sign: add der key format support to sign script The sign script can now decode private keys in der format. The key format is now recognized by the file extension. --- ncs/sign_script.py | 12 ++++++++++-- tests/test_ncs_sign_script.py | 12 ++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ncs/sign_script.py b/ncs/sign_script.py index 5203ba12..d458ced9 100644 --- a/ncs/sign_script.py +++ b/ncs/sign_script.py @@ -22,7 +22,7 @@ from argparse import ArgumentParser from pathlib import Path from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_der_private_key from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey @@ -162,8 +162,16 @@ def _get_key_id_for_manifest_class(self): def sign(self, private_key_path: Path) -> None: """Add signature to the envelope.""" + loaders = { + ".pem": load_pem_private_key, + ".der": load_der_private_key, + } + try: + loader = loaders[private_key_path.suffix] + except KeyError as e: + raise ValueError("Unrecognized private key format. Extension must be {per,der}") from e with open(private_key_path, "rb") as private_key: - self._key = load_pem_private_key(private_key.read(), None) + self._key = loader(private_key.read(), None) sign_method = self._get_sign_method() protected = { SuitIds.COSE_ALG.value: SuitAlgorithms[self._algorithm_name].value, diff --git a/tests/test_ncs_sign_script.py b/tests/test_ncs_sign_script.py index abd393b9..813bdb8a 100644 --- a/tests/test_ncs_sign_script.py +++ b/tests/test_ncs_sign_script.py @@ -190,7 +190,7 @@ def test_ncs_signing(setup_and_teardown, private_key): """Test if is possible to sign manifest.""" signer = Signer() signer.load_envelope("test_envelope.suit") - signer.sign(f"key_private_{private_key}.pem") + signer.sign(pathlib.Path(f"key_private_{private_key}.pem")) signer.save_envelope("test_envelope_signed.suit") with open("test_envelope_signed.suit", "rb") as fh: @@ -233,7 +233,7 @@ def test_envelope_sign_and_verify(setup_and_teardown, input_data, amount_of_payl signer = Signer() signer.load_envelope("test_envelope.suit") - signer.sign("key_private_es_256.pem") + signer.sign(pathlib.Path("key_private_es_256.pem")) signer.save_envelope("test_envelope_signed.suit") with open("test_envelope_signed.suit", "rb") as fh: @@ -276,7 +276,7 @@ def test_ncs_signing_unsupported(setup_and_teardown): signer = Signer() signer.load_envelope("test_envelope.suit") with pytest.raises(SignerError): - signer.sign("key_private_rs2048.pem") + signer.sign(pathlib.Path("key_private_rs2048.pem")) @patch("ncs.sign_script.DEFAULT_KEY_ID", 0x0C0FFE) @@ -291,7 +291,7 @@ def test_ncs_signing_manifest_component_id_known_default_key_used(setup_and_tear assert parsed_manifest_id == expected_manifest_id - signer.sign("key_private_es_256.pem") + signer.sign(pathlib.Path("key_private_es_256.pem")) signer.save_envelope("test_envelope_signed.suit") with open("test_envelope_signed.suit", "rb") as fh: @@ -320,7 +320,7 @@ def test_ncs_signing_manifest_component_id_known_non_default(setup_and_teardown) signer = Signer() signer.load_envelope("test_envelope_manifest_component_id.suit") - signer.sign("key_private_es_256.pem") + signer.sign(pathlib.Path("key_private_es_256.pem")) signer.save_envelope("test_envelope_signed.suit") with open("test_envelope_signed.suit", "rb") as fh: @@ -342,7 +342,7 @@ def test_ncs_signing_manifest_component_id_unknown(setup_and_teardown): signer = Signer() signer.load_envelope("test_envelope_manifest_component_id.suit") - signer.sign("key_private_es_256.pem") + signer.sign(pathlib.Path("key_private_es_256.pem")) signer.save_envelope("test_envelope_signed.suit") with open("test_envelope_signed.suit", "rb") as fh: From e7fa9c44f9fb5b5f7723a86b7b5da7381c90e79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Mon, 17 Jun 2024 13:04:57 +0200 Subject: [PATCH 20/67] sign: add ed448 support Added support for ed448 EdDSA algorithm. --- ncs/sign_script.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ncs/sign_script.py b/ncs/sign_script.py index d458ced9..bfa688b2 100644 --- a/ncs/sign_script.py +++ b/ncs/sign_script.py @@ -27,6 +27,7 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey from collections import defaultdict from enum import Enum, unique @@ -118,7 +119,7 @@ def _get_sign_method(self) -> callable: """Return sign method based on key type.""" if isinstance(self._key, EllipticCurvePrivateKey): return self._create_cose_es_signature - elif isinstance(self._key, Ed25519PrivateKey): + elif isinstance(self._key, Ed25519PrivateKey) or isinstance(self._key, Ed448PrivateKey): return self._create_cose_ed_signature else: raise SignerError(f"Key {type(self._key)} not supported") @@ -129,7 +130,7 @@ def _algorithm_name(self) -> str: hash_alg = SuitAlgorithms(self.get_digest()[0]) if isinstance(self._key, EllipticCurvePrivateKey): return f"COSE_ALG_ES_{self._key.key_size}" - elif isinstance(self._key, Ed25519PrivateKey): + elif isinstance(self._key, Ed25519PrivateKey) or isinstance(self._key, Ed448PrivateKey): return "COSE_ALG_EdDSA" else: raise SignerError(f"Key {type(self._key)} with {hash_alg} is not supported") From ec45c78e07b7a6958f94f84d6cde266ba048fca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Wed, 19 Jun 2024 09:55:27 +0200 Subject: [PATCH 21/67] sign: add user note to rename the key file when using the DER format. --- ncs/sign_script.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ncs/sign_script.py b/ncs/sign_script.py index bfa688b2..9e5e297b 100644 --- a/ncs/sign_script.py +++ b/ncs/sign_script.py @@ -31,6 +31,10 @@ from collections import defaultdict from enum import Enum, unique +# +# User note: +# Rename the file to 'key_private.der' if you are using keys in DER format. +# PRIVATE_KEY = Path(__file__).parent / "key_private.pem" From a83a195c8a69afb5a9ad05baec121ef3b654214c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Fri, 21 Jun 2024 14:54:47 +0200 Subject: [PATCH 22/67] templates: remove flash companion from default template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flash companion is not used in default configurations. Signed-off-by: Rafał Kuźnia --- ncs/app_envelope.yaml.jinja2 | 45 ------------------------------------ 1 file changed, 45 deletions(-) diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index 321c6d2a..4b154eaf 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -16,12 +16,6 @@ SUIT_Envelope_Tagged: - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - CAND_IMG - 0 -{%- if flash_companion is defined %} - - - MEM - - {{ flash_companion['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(flash_companion['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ flash_companion['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} -{%- endif %} suit-shared-sequence: - suit-directive-set-component-index: 0 - suit-directive-override-parameters: @@ -53,14 +47,6 @@ SUIT_Envelope_Tagged: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: file: {{ application['binary'] }} -{%- if flash_companion is defined %} - - suit-directive-set-component-index: 2 - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ flash_companion['binary'] }} -{%- endif %} suit-validate: - suit-directive-set-component-index: 0 - suit-condition-image-match: @@ -73,34 +59,6 @@ SUIT_Envelope_Tagged: - suit-directive-invoke: - suit-send-record-failure suit-install: -{%- if flash_companion is defined %} - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ flash_companion['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ flash_companion['binary'] }} - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 2 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-invoke: - - suit-send-record-failure -{%- endif %} - suit-directive-set-component-index: 1 - suit-directive-override-parameters: suit-parameter-uri: '#{{ application['name'] }}' @@ -143,6 +101,3 @@ SUIT_Envelope_Tagged: suit-text-component-version: v1.0.0 suit-integrated-payloads: '#{{ application['name'] }}': {{ application['binary'] }} -{%- if flash_companion is defined %} - '#{{ flash_companion['name'] }}': {{ flash_companion['binary'] }} -{%- endif %} From 79c867b6c0f5dbb37c1a93de13ebbf9dd2a9744d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Fri, 21 Jun 2024 15:07:40 +0200 Subject: [PATCH 23/67] templates: add retries for streaming operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Streaming operations, such as calculating the digest or suit-directive-copy are now executed at least twice. This increases the robustness of the update process and may prevent the device from entering the recovery state when a bit flip occurs during the streaming operation, but the update candidate is correct and uncorrupted. In such cases, the bit flip is recoverable. The device will repeat the streaming operation with the hope that the bit flip does not occur again. One source of recoverable bit flips may be interference on the SPI bus when streaming from external memory, or a bit flip when writing a candidate to NVM. Signed-off-by: Rafał Kuźnia --- ncs/app_envelope.yaml.jinja2 | 64 ++++++++++++++------ ncs/rad_envelope.yaml.jinja2 | 66 +++++++++++++++------ ncs/root_with_binary_nordic_top.yaml.jinja2 | 60 ++++++++++++++----- 3 files changed, 140 insertions(+), 50 deletions(-) diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index 4b154eaf..ab84a5e1 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -49,11 +49,21 @@ SUIT_Envelope_Tagged: file: {{ application['binary'] }} suit-validate: - suit-directive-set-component-index: 0 - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure suit-invoke: - suit-directive-set-component-index: 0 - suit-directive-invoke: @@ -68,21 +78,41 @@ SUIT_Envelope_Tagged: file: {{ application['binary'] }} - suit-directive-fetch: - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure - suit-directive-set-component-index: 0 - suit-directive-override-parameters: suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # When copying the data it is worth to retry the sequence of + # suit-directive-copy and suit-condition-image-match at least once. + # If a bit flip occurs, it might be due to a transport issue, not + # a corrupted candidate image. In this case the bit flip is recoverable + # and it is worth retrying the operation. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure suit-text: suit-digest-algorithm-id: cose-alg-sha-256 suit-manifest-component-id: diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index 24be0ef3..d1e6d3e2 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -54,11 +54,21 @@ SUIT_Envelope_Tagged: file: {{ radio['binary'] }} suit-validate: - suit-directive-set-component-index: 0 - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure suit-invoke: - suit-directive-set-component-index: 0 - suit-directive-invoke: @@ -69,21 +79,41 @@ SUIT_Envelope_Tagged: suit-parameter-uri: '#{{ radio['name'] }}' - suit-directive-fetch: - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure - suit-directive-set-component-index: 0 - suit-directive-override-parameters: suit-parameter-source-component: 1 - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # When copying the data it is worth to retry the sequence of + # suit-directive-copy and suit-condition-image-match at least once. + # If a bit flip occurs, it might be due to a transport issue, not + # a corrupted candidate image. In this case the bit flip is recoverable + # and it is worth retrying the operation. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure suit-text: suit-digest-algorithm-id: cose-alg-sha-256 suit-manifest-component-id: @@ -101,4 +131,4 @@ SUIT_Envelope_Tagged: suit-text-component-description: Sample radio core FW suit-text-component-version: v1.0.0 suit-integrated-payloads: - '#{{ radio['name'] }}': {{ radio['binary'] }} \ No newline at end of file + '#{{ radio['name'] }}': {{ radio['binary'] }} diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 index 86f6872d..22606db8 100644 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ b/ncs/root_with_binary_nordic_top.yaml.jinja2 @@ -171,11 +171,21 @@ SUIT_Envelope_Tagged: envelope: {{ artifacts_folder ~ radio['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure - suit-condition-dependency-integrity: - suit-send-record-success - suit-send-record-failure @@ -196,11 +206,21 @@ SUIT_Envelope_Tagged: envelope: {{ artifacts_folder ~ application['name'] }}.suit - suit-directive-fetch: - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure - suit-condition-dependency-integrity: - suit-send-record-success - suit-send-record-failure @@ -221,11 +241,21 @@ SUIT_Envelope_Tagged: envelope: {{ sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit - suit-directive-fetch: - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure - suit-condition-dependency-integrity: - suit-send-record-success - suit-send-record-failure From bfae7b4582d3057c64cbe24f15a98cda8d5d76a5 Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Wed, 3 Jul 2024 07:45:02 +0200 Subject: [PATCH 24/67] Feat/custom vid cid duplicates (#130) * feat (cmd_image): store only unique cid to role mapping Ref: NCSDK-27289 Signed-off-by: Robert Stypa --- ncs/build.py | 5 ++-- suit_generator/cli.py | 2 +- suit_generator/cmd_image.py | 22 ++++++-------- suit_generator/logger.py | 4 +-- tests/test_args.py | 4 +-- tests/test_cmd_image.py | 57 +++++++++++++++++++++++++++++++++++-- 6 files changed, 72 insertions(+), 22 deletions(-) diff --git a/ncs/build.py b/ncs/build.py index ec228f7d..004a679b 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -43,8 +43,9 @@ def read_configurations(configurations): if image_name in data: existing_binary = data[image_name]["binary"] - raise ValueError("Two images have the same CONFIG_SUIT_ENVELOPE_TARGET value: " - f"{binary} and {existing_binary}") + raise ValueError( + "Two images have the same CONFIG_SUIT_ENVELOPE_TARGET value: " f"{binary} and {existing_binary}" + ) data[image_name] = { "name": name, diff --git a/suit_generator/cli.py b/suit_generator/cli.py index dea2284b..2586d02b 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -45,7 +45,7 @@ def configure_cli_logging(log_file_name: str = None): # override log file name if passed as argument if log_file_name: - config['handlers']['file']['filename'] = log_file_name + config["handlers"]["file"]["filename"] = log_file_name logging.config.dictConfig(config) diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 4dde7766..dbf0f5dd 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -171,7 +171,7 @@ class EnvelopeStorage: def __init__(self, base_address: int, load_defaults=True, kconfig=None): """Create object generating binary SUIT storage.""" - self._assignments = [] + self._assignments = {} self._base_address = base_address self._envelopes = {} @@ -201,25 +201,21 @@ def _get_role_assignments_from_kconfig(kconfig: str) -> list: def assign_role(self, vendor_name: str, class_name: str, role: ManifestRole): """Assign role to envelope, identified by vendor and class name.""" vid = uuid.uuid5(uuid.NAMESPACE_DNS, vendor_name) - self._assignments.append( - { - "vendor_id": vid.bytes, - "class_id": uuid.uuid5(vid, class_name).bytes, - "role": role, - } - ) + cid = uuid.uuid5(vid, class_name) + self._assignments[cid.hex] = { + "vendor_id": vid.bytes, + "class_id": cid.bytes, + "role": role, + } def _find_class(self, role: ManifestRole) -> bytes | None: - for entry in self._assignments: + for entry in self._assignments.values(): if entry["role"] == role: return entry["class_id"] return None def _find_role(self, class_id: bytes) -> ManifestRole | None: - for entry in self._assignments: - if entry["class_id"].hex() == class_id.hex(): - return entry["role"] - return None + return self._assignments[class_id.hex()]["role"] if class_id.hex() in self._assignments else None def _find_slot(self, class_id: bytes) -> (int, int): role = self._find_role(class_id) diff --git a/suit_generator/logger.py b/suit_generator/logger.py index e533314d..6e3fd0ca 100644 --- a/suit_generator/logger.py +++ b/suit_generator/logger.py @@ -17,8 +17,8 @@ logger = logging.getLogger(__name__) -DEFAULT_LOG_FORMAT: str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' -DEFAULT_LOG_FILE_PATH: Path = 'suit-generator.log' +DEFAULT_LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +DEFAULT_LOG_FILE_PATH: Path = "suit-generator.log" def log_call(func): diff --git a/tests/test_args.py b/tests/test_args.py index ee8fe2e5..136b3c56 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -15,7 +15,7 @@ @mock.patch( "argparse.ArgumentParser.parse_args", return_value=argparse.Namespace( - command="create", input_file="test1.json", output_file="test2.suit", log_filename='test.log' + command="create", input_file="test1.json", output_file="test2.suit", log_filename="test.log" ), ) def test_create_cmd_mode_auto(mock_args): @@ -23,7 +23,7 @@ def test_create_cmd_mode_auto(mock_args): args = parse_arguments() assert args[0] == "create" assert vars(args[1]) == {"input_file": "test1.json", "output_file": "test2.suit"} - assert args[2] == 'test.log' + assert args[2] == "test.log" @pytest.mark.parametrize( diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index a9649b74..010a041f 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -254,6 +254,39 @@ def setup_and_teardown(tmp_path_factory): rad_local_1_class_name="rad_local_1_custom_class", ) ) + with open(".config_with_defaults", "w") as fh: + fh.write( + MPI_KCONFIG_TEMPLATE.format( + root_vendor_name="nordicsemi.com", + root_class_name="nRF54H20_sample_root", + app_local_1_vendor_name="nordicsemi.com", + app_local_1_class_name="nRF54H20_sample_app", + rad_local_1_vendor_name="rad_local_1_custom_vendor", + rad_local_1_class_name="rad_local_1_custom_class", + ) + ) + with open(".config_duplicates", "w") as fh: + fh.write( + MPI_KCONFIG_TEMPLATE.format( + root_vendor_name="nordicsemi.com", + root_class_name="nRF54H20_sample_root", + app_local_1_vendor_name="nordicsemi.com", + app_local_1_class_name="nRF54H20_sample_app", + rad_local_1_vendor_name="nordicsemi.com", + rad_local_1_class_name="nRF54H20_sample_app", + ) + ) + with open(".config_role_exchanged", "w") as fh: + fh.write( + MPI_KCONFIG_TEMPLATE.format( + root_vendor_name="nordicsemi.com", + root_class_name="nRF54H20_sample_root", + app_local_1_vendor_name="nordicsemi.com", + app_local_1_class_name="nRF54H20_sample_rad", + rad_local_1_vendor_name="nordicsemi.com", + rad_local_1_class_name="nRF54H20_sample_app", + ) + ) for item_name in ["root", "app_local_1", "rad_local_1"]: with open(f"custom_{item_name}_component_id.suit", "wb") as fh: envelope_data = SuitEnvelopeTagged.from_obj( @@ -493,10 +526,10 @@ def bin2hex_mock(*args, **kwargs): def test_nrf54_storage_no_defaults(): storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=False, kconfig=None) - assert storage._assignments == [] + assert storage._assignments == {} -def test_nrf54_storage_defaults(): +def test_nrf54_storage_with_defaults(): storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=None) assert len(storage._assignments) == 8 @@ -506,6 +539,26 @@ def test_nrf54_storage_custom_config_defaults(setup_and_teardown): assert len(storage._assignments) == 11 +def test_nrf54_storage_custom_config_with_defaults_overwrite(setup_and_teardown): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config_with_defaults") + # 8 default assignments + 3 custom assignments: 2 default values + one custom value, + # 2 custom assignments with default values are overwritten so final result is 8 + 1 = 9 + assert len(storage._assignments) == 9 + + +def test_nrf54_storage_custom_config_with_role_change_duplicates(setup_and_teardown): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config_duplicates") + # 8 default assignments + 3 custom assignments: root uses default value, APP and RAD uses the same role. + # APP entry will be overwritten by RAD to there will be no APP role in the dictionary, only RAD but duplicated. + assert len(storage._assignments) == 8 + + +def test_nrf54_storage_custom_config_with_role_change_no_duplicates(setup_and_teardown): + storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config_role_exchanged") + # 8 default assignments + 3 custom assignment: root uses default value, APP and RAD have exchanged roles. + assert len(storage._assignments) == 8 + + def test_nrf54_storage_custom_config_no_defaults(setup_and_teardown): storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=False, kconfig=".config") assert len(storage._assignments) == 3 From bd0bf7c5c7ed7c5e1132bc97fc3aaeede47e9f21 Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:00:38 +0200 Subject: [PATCH 25/67] feat: custom vid/cid validation (#133) Add validation for duplicate vid/cid combinations in KConfig file and update corresponding test Ref: NCSDK-28254 Signed-off-by: Robert Stypa --- suit_generator/cmd_image.py | 9 +++++++++ tests/test_cmd_image.py | 7 +++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index dbf0f5dd..d623401d 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -190,6 +190,15 @@ def _get_role_assignments_from_kconfig(kconfig: str) -> list: for key, value in config.items(): if re_value := re.match(r"^CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$", key): manifest = re_value.group("manifest") + # ensure that the same combination of vid/cid has not been set for different role + for item in kconfig_assignments: + if ( + item["vendor_name"] == config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"] + and item["class_name"] == config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"] + ): + raise GeneratorError( + "Duplicate vid/cid combination for different roles detected in the KConfig file." + ) data = { "vendor_name": config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"], "class_name": config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"], diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 010a041f..4a29b63e 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -547,10 +547,9 @@ def test_nrf54_storage_custom_config_with_defaults_overwrite(setup_and_teardown) def test_nrf54_storage_custom_config_with_role_change_duplicates(setup_and_teardown): - storage = EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config_duplicates") - # 8 default assignments + 3 custom assignments: root uses default value, APP and RAD uses the same role. - # APP entry will be overwritten by RAD to there will be no APP role in the dictionary, only RAD but duplicated. - assert len(storage._assignments) == 8 + with pytest.raises(GeneratorError): + # GeneratorError shall be raised due to duplicated assignments + EnvelopeStorageNrf54h20(base_address=0xFF, load_defaults=True, kconfig=".config_duplicates") def test_nrf54_storage_custom_config_with_role_change_no_duplicates(setup_and_teardown): From 7ce38bd38a24dd544f0ca750b49640fd4bbe3b38 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Fri, 5 Jul 2024 12:37:51 +0200 Subject: [PATCH 26/67] refactor: rename "authentication.py" to "security.py" The features from this file can be reused for encryption, so keeping the "authentication" name would be misleading. Ref: NCSDK-28258 Signed-off-by: Artur Hadasz --- suit_generator/suit/envelope.py | 2 +- suit_generator/suit/manifest.py | 2 +- suit_generator/suit/{authentication.py => security.py} | 0 tests/test_envelope.py | 6 +++--- tests/test_ncs_sign_script.py | 2 +- tests/test_suit_authentication.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename suit_generator/suit/{authentication.py => security.py} (100%) diff --git a/suit_generator/suit/envelope.py b/suit_generator/suit/envelope.py index 451332cb..c7ccf9e1 100644 --- a/suit_generator/suit/envelope.py +++ b/suit_generator/suit/envelope.py @@ -14,7 +14,7 @@ from suit_generator.suit.payloads import SuitIntegratedPayloadMap from suit_generator.suit.types.common import SuitKeyValue, SuitTag, Tag, Metadata, SuitBstr, cbstr -from suit_generator.suit.authentication import SuitDelegationChain, SuitAuthentication, SuitHash +from suit_generator.suit.security import SuitDelegationChain, SuitAuthentication, SuitHash from suit_generator.suit.manifest import SuitManifest, SuitCommandSequence, SuitTextMap from suit_generator.suit.types.keys import ( suit_manifest, diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 38f3dcf0..0c3a8bca 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -28,7 +28,7 @@ SuitBchar, cbstr, ) -from suit_generator.suit.authentication import SuitDigest +from suit_generator.suit.security import SuitDigest from suit_generator.suit.types.keys import ( suit_parameter_vendor_identifier, suit_parameter_class_identifier, diff --git a/suit_generator/suit/authentication.py b/suit_generator/suit/security.py similarity index 100% rename from suit_generator/suit/authentication.py rename to suit_generator/suit/security.py diff --git a/tests/test_envelope.py b/tests/test_envelope.py index a7d64dac..19be64d5 100644 --- a/tests/test_envelope.py +++ b/tests/test_envelope.py @@ -15,7 +15,7 @@ from suit_generator.envelope import SuitEnvelope from suit_generator.exceptions import GeneratorError from suit_generator.input_output import FileTypeException -from suit_generator.suit.authentication import SuitDigest, SuitAuthenticationBlock +from suit_generator.suit.security import SuitDigest, SuitAuthenticationBlock from suit_generator.suit.types.common import Metadata, cbstr TEMP_DIRECTORY = pathlib.Path("test_test_data") @@ -681,7 +681,7 @@ def test_envelope_signed_twice_parsing(setup_and_teardown): @patch( - "suit_generator.suit.authentication.SuitAuthentication._metadata", + "suit_generator.suit.security.SuitAuthentication._metadata", Metadata(map={"SuitDigest*": cbstr(SuitDigest), "SuitAuthentication*": SuitAuthenticationBlock}), ) def test_envelope_parsing_wrong_internal_structure_dynamic_element_twice(setup_and_teardown): @@ -692,7 +692,7 @@ def test_envelope_parsing_wrong_internal_structure_dynamic_element_twice(setup_a @patch( - "suit_generator.suit.authentication.SuitAuthentication._metadata", + "suit_generator.suit.security.SuitAuthentication._metadata", Metadata(map={"SuitDigest*": cbstr(SuitDigest), "SuitAuthentication": SuitAuthenticationBlock}), ) def test_envelope_parsing_wrong_internal_structure_dynamic_element_at_the_beginning(setup_and_teardown): diff --git a/tests/test_ncs_sign_script.py b/tests/test_ncs_sign_script.py index 813bdb8a..73bc1521 100644 --- a/tests/test_ncs_sign_script.py +++ b/tests/test_ncs_sign_script.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature -from suit_generator.suit.authentication import CoseSigStructure +from suit_generator.suit.security import CoseSigStructure from suit_generator.suit.envelope import SuitEnvelopeTagged from suit_generator.suit.types.keys import ( diff --git a/tests/test_suit_authentication.py b/tests/test_suit_authentication.py index f36d91c8..c3b0b8d1 100644 --- a/tests/test_suit_authentication.py +++ b/tests/test_suit_authentication.py @@ -6,7 +6,7 @@ """Unit tests for suit authentication parsing.""" import binascii import pytest -from suit_generator.suit.authentication import SuitAuthentication, CoseSigStructure +from suit_generator.suit.security import SuitAuthentication, CoseSigStructure from suit_generator.suit.types.keys import suit_cose_algorithm_id TEST_DATA = { From 9a37152253ed9bae50bcb946519acdfee133df53 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 8 Jul 2024 09:10:40 +0200 Subject: [PATCH 27/67] Added structures for CoseEncrypt Ref: NCSDK-28258 Signed-off-by: Artur Hadasz --- suit_generator/suit/security.py | 108 ++++++++++++++++++++++++++-- suit_generator/suit/types/common.py | 15 ++++ suit_generator/suit/types/keys.py | 40 +++++++++++ tests/test_ncs_sign_script.py | 6 +- tests/test_suit_authentication.py | 2 +- 5 files changed, 163 insertions(+), 8 deletions(-) diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index 7281e371..2ed2097a 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -19,6 +19,7 @@ SuitKeyValue, SuitList, SuitBstr, + SuitEmptyBstr, SuitTag, Tag, cbstr, @@ -29,6 +30,7 @@ from suit_generator.suit.types.keys import ( suit_cose_algorithm_id, suit_cose_key_id, + suit_cose_iv, suit_issuer, suit_subject, suit_audience, @@ -45,6 +47,12 @@ cose_alg_es_384, cose_alg_es_521, cose_alg_eddsa, + cose_alg_aes_gcm_128, + cose_alg_aes_gcm_192, + cose_alg_aes_gcm_256, + cose_alg_a256kw, + cose_alg_a192kw, + cose_alg_a128kw, suit_digest_algorithm_id, suit_digest_bytes, ) @@ -162,10 +170,23 @@ class SuitDigest(SuitUnion): _metadata = Metadata(children=[SuitDigestRaw, SuitDigestExt]) -class SuitcoseSignAlg(SuitEnum): +class SuitcoseAlg(SuitEnum): """Representation of SUIT COSE sign algorithm.""" - _metadata = Metadata(children=[cose_alg_es_256, cose_alg_es_384, cose_alg_es_521, cose_alg_eddsa]) + _metadata = Metadata(children=[cose_alg_es_256, cose_alg_es_384, cose_alg_es_521, cose_alg_eddsa, + cose_alg_aes_gcm_128, cose_alg_aes_gcm_192, cose_alg_aes_gcm_256, + cose_alg_a256kw, cose_alg_a192kw, cose_alg_a128kw, + ]) + +class SuitcoseKeyId(SuitUnion): + """Representation of a KEY ID item.""" + + _metadata = Metadata( + children=[ + cbstr(SuitInt), + SuitBstr, + ] + ) class SuitHeaderMap(SuitKeyValue): @@ -173,11 +194,34 @@ class SuitHeaderMap(SuitKeyValue): _metadata = Metadata( map={ - suit_cose_algorithm_id: SuitcoseSignAlg, - suit_cose_key_id: cbstr(SuitInt), + suit_cose_algorithm_id: SuitcoseAlg, + suit_cose_key_id: SuitcoseKeyId, + suit_cose_iv: SuitHex, } ) +class SuitHeaderMapOptional(SuitUnion): + """Representation of COSE_Encrypt_ciphertext item.""" + + _metadata = Metadata( + children=[ + SuitHeaderMap, + SuitEmptyBstr, + ] + ) + + @classmethod + def from_obj(cls, obj) -> SuitUnion: + """Restore SUIT representation from passed object.""" + value = None + if isinstance(obj, dict): + value = SuitEmptyBstr.from_obj("") if obj == {} else SuitHeaderMap.from_obj(obj) + elif obj == '' or obj == b'': + value = SuitEmptyBstr.from_obj('') + else: + raise ValueError(f"Expected dict empty string or empty sequence of bytes received: {obj}") + + return cls(value) class SuitHeaderData(SuitUnion): """Abstract element to define possible sub-elements.""" @@ -271,3 +315,59 @@ class SuitDelegationChain(SuitList): """Representation of SUIT delegation chain.""" _metadata = Metadata(children=[SuitDelegation]) + + +### Encryption + +class SuitCiphertextBytes(SuitHex): + """Representation of SUIT ciphertext bytes.""" + + pass + +class CoseEncryptCiphertext(SuitUnion): + """Representation of COSE_Encrypt_ciphertext item.""" + + _metadata = Metadata( + children=[ + SuitNull, + SuitCiphertextBytes, + ] + ) + +class CoseRecipient(SuitTupleNamed): + """Representation of COSE_Recipient item.""" + + _metadata = Metadata( + map={ + "protected": cbstr(SuitHeaderMapOptional), + "unprotected": SuitHeaderData, + "ciphertext": CoseEncryptCiphertext, + "recipients*": SuitList, + } + ) + +class CoseRecipientList(SuitList): + """Representation of a list of COSE_Recipient items.""" + + _metadata = Metadata(children=[CoseRecipient]) + + +# Fix cyclic dependencies between types +CoseRecipient._metadata.map["recipients*"] = CoseRecipientList + +class CoseEncrypt(SuitTupleNamed): + """Representation of COSE_Encrypt item.""" + + _metadata = Metadata( + map={ + "protected": cbstr(SuitHeaderMap), + "unprotected": SuitHeaderData, + "ciphertext": CoseEncryptCiphertext, + "recipients": CoseRecipientList, + } + ) + +class CoseEncryptTagged(SuitTag): + """Representation of COSE_Encrypt_Tagged item.""" + + _metadata = Metadata(children=[CoseEncrypt], tag=Tag(96, "CoseEncryptTagged")) diff --git a/suit_generator/suit/types/common.py b/suit_generator/suit/types/common.py index 114c1bdc..ab4ac30c 100644 --- a/suit_generator/suit/types/common.py +++ b/suit_generator/suit/types/common.py @@ -134,6 +134,10 @@ def validate_cbor(cbstr: bytes) -> None: does not contain data which will cause that cbor2 library will request huge amount of memory. """ requested_memory_len = None + if (len(cbstr) < 1): + raise ValueError( + f"The cbstr parsed object is empty" + ) cbor_item_type = cbstr[0] >> 5 cbor_item_count = cbstr[0] & 31 # fixme: do not validate CBORTag length @@ -657,6 +661,17 @@ def to_obj(self) -> str: """Dump SUIT representation to object.""" return self.value.hex() +class SuitEmptyBstr(SuitBstr): + + @classmethod + def from_cbor(cls, cbstr: bytes) -> SuitBstr: + """Restore SUIT representation from passed CBOR.""" + if (len(cbstr) > 0): + raise ValueError(f"{cbstr} is not empty") + return cls(cbstr) + + def to_cbor(self) -> bytes: + return b'' class SuitHex(SuitBstr): """Representation of hex type.""" diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 6c18c1df..5fda5f3c 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -574,6 +574,11 @@ class suit_cose_key_id(suit_key): id = 4 name = "suit-cose-key-id" +class suit_cose_iv(suit_key): + """suit-cose-iv metadata.""" + + id = 5 + name = "suit-cose-iv" class suit_issuer(suit_key): """CWT Issuer metadata.""" @@ -686,6 +691,41 @@ class cose_alg_eddsa(suit_key): id = -8 name = "cose-alg-eddsa" +class cose_alg_aes_gcm_128(suit_key): + """Cose algorithm metadata.""" + + id = 1 + name = "cose-alg-aes-gcm-128" + +class cose_alg_aes_gcm_192(suit_key): + """Cose algorithm metadata.""" + + id = 2 + name = "cose-alg-aes-gcm-192" + +class cose_alg_aes_gcm_256(suit_key): + """Cose algorithm metadata.""" + + id = 3 + name = "cose-alg-aes-gcm-256" + +class cose_alg_a256kw(suit_key): + """Cose algorithm metadata.""" + + id = -5 + name = "cose-alg-a256kw" + +class cose_alg_a192kw(suit_key): + """Cose algorithm metadata.""" + + id = -4 + name = "cose-alg-a192kw" + +class cose_alg_a128kw(suit_key): + """Cose algorithm metadata.""" + + id = -3 + name = "cose-alg-a128kw" class suit_send_record_success(suit_key): """Reporting policy bit.""" diff --git a/tests/test_ncs_sign_script.py b/tests/test_ncs_sign_script.py index 73bc1521..321e40fe 100644 --- a/tests/test_ncs_sign_script.py +++ b/tests/test_ncs_sign_script.py @@ -309,7 +309,7 @@ def test_ncs_signing_manifest_component_id_known_default_key_used(setup_and_tear .SuitAuthentication[1] .SuitAuthenticationBlock.CoseSign1Tagged.value.CoseSign1[0] .SuitHeaderMap[suit_cose_key_id] - .value + .value.value == 0x0C0FFE ) @@ -331,7 +331,7 @@ def test_ncs_signing_manifest_component_id_known_non_default(setup_and_teardown) .SuitAuthentication[1] .SuitAuthenticationBlock.CoseSign1Tagged.value.CoseSign1[0] .SuitHeaderMap[suit_cose_key_id] - .value + .value.value == 0xFFEEDDBB ) @@ -353,7 +353,7 @@ def test_ncs_signing_manifest_component_id_unknown(setup_and_teardown): .SuitAuthentication[1] .SuitAuthenticationBlock.CoseSign1Tagged.value.CoseSign1[0] .SuitHeaderMap[suit_cose_key_id] - .value + .value.value == 0xDEADBEEF ) diff --git a/tests/test_suit_authentication.py b/tests/test_suit_authentication.py index c3b0b8d1..11c66fe0 100644 --- a/tests/test_suit_authentication.py +++ b/tests/test_suit_authentication.py @@ -136,7 +136,7 @@ def test_sig_structure(): assert hasattr(structure, "CoseSigStructure") assert len(structure.CoseSigStructure) == 4 assert structure.CoseSigStructure[0].value == "Signature1" - assert structure.CoseSigStructure[1].SuitHeaderMap[suit_cose_algorithm_id].SuitcoseSignAlg == "cose-alg-es-256" + assert structure.CoseSigStructure[1].SuitHeaderMap[suit_cose_algorithm_id].SuitcoseAlg == "cose-alg-es-256" assert structure.CoseSigStructure[2].SuitHex == b"" assert structure.CoseSigStructure[3].to_cbor().hex().upper() == "4C822F49AAABBBCCCDDDEEEFFF" assert hex_value is not None From ca06e6589adb10debfb97b2fd1b42ea93fd58700 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 9 Jul 2024 10:12:15 +0200 Subject: [PATCH 28/67] Added tests for COSE_Encrypt structure Ref: NCSDK-28258 Signed-off-by: Artur Hadasz --- tests/test_suit_encryption.py | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/test_suit_encryption.py diff --git a/tests/test_suit_encryption.py b/tests/test_suit_encryption.py new file mode 100644 index 00000000..ccd673c6 --- /dev/null +++ b/tests/test_suit_encryption.py @@ -0,0 +1,94 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""Unit tests for suit encryption parsing.""" +import binascii +import pytest + +from suit_generator.suit.security import CoseEncryptTagged + +TEST_DATA = { + "COSE_ENCRYPT_FROM_SPEC": ( + "D8608443A10101A10550F14AAB9D81D51F7AD943FE87AF4F70CDF6818340" + "A2012204456B69642D31581875603FFC9518D794713C8CA8A115A7FB3256" + "5A6D59534D62" + ), +} + +TEST_DATA_OBJECT = { + "COSE_ENCRYPT_FROM_SPEC": { + "CoseEncryptTagged": { + "protected": { + "suit-cose-algorithm-id": "cose-alg-aes-gcm-128", + }, + "unprotected": { + "suit-cose-iv": "f14aab9d81d51f7ad943fe87af4f70cd" + }, + "ciphertext": None, + "recipients": [ + { + "protected": {}, + "unprotected": { + "suit-cose-algorithm-id": "cose-alg-a128kw", + "suit-cose-key-id": "6b69642d31" # "kid-1" + }, + "ciphertext": "75603ffc9518d794713c8ca8a115a7fb32565a6d59534d62", + }, + ], + } + }, +} + +@pytest.mark.parametrize( + "input_data", + [ + "COSE_ENCRYPT_FROM_SPEC", + ], +) +def test_suit_cose_encrypt_content_from_obj(input_data): + suit_obj = CoseEncryptTagged.from_obj(TEST_DATA_OBJECT[input_data]) + assert suit_obj.value is not None + +@pytest.mark.parametrize( + "input_data", + [ + "COSE_ENCRYPT_FROM_SPEC", + ], +) +def test_suit_cose_encrypt_from_obj(input_data): + suit_obj = CoseEncryptTagged.from_obj(TEST_DATA_OBJECT[input_data]) + suit_binary = suit_obj.to_cbor() + assert suit_obj.value is not None + assert suit_binary.hex() == CoseEncryptTagged.from_cbor(suit_binary).to_cbor().hex() + +@pytest.mark.parametrize( + "input_data", + [ + "COSE_ENCRYPT_FROM_SPEC", + ], +) +def test_suit_cose_encrypt_from_cbor(input_data): + suit_obj = CoseEncryptTagged.from_cbor(binascii.a2b_hex(TEST_DATA[input_data])) + assert suit_obj.value is not None + +@pytest.mark.parametrize( + "input_data", + [ + "COSE_ENCRYPT_FROM_SPEC", + ], +) +def test_suit_cose_encrypt_from_cbor_parse_and_dump(input_data): + suit_obj = CoseEncryptTagged.from_cbor(binascii.a2b_hex(TEST_DATA[input_data])) + assert suit_obj.to_cbor().hex().upper() == TEST_DATA[input_data].upper() + +@pytest.mark.parametrize( + "input_data", + [ + "COSE_ENCRYPT_FROM_SPEC", + ], +) +def test_suit_cose_encrypt_to_cbor_result(input_data): + suit_obj = CoseEncryptTagged.from_obj(TEST_DATA_OBJECT[input_data]) + assert suit_obj.to_cbor().hex().upper() == TEST_DATA[input_data].upper() From 4590dc79d267ef9148a43d56695fd7e5278336e4 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 9 Jul 2024 15:51:40 +0200 Subject: [PATCH 29/67] Add suit-encryption-info parameter Ref: NCSDK-2825 Signed-off-by: Artur Hadasz --- suit_generator/suit/manifest.py | 4 +++- suit_generator/suit/types/keys.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 0c3a8bca..b1af0638 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -28,7 +28,7 @@ SuitBchar, cbstr, ) -from suit_generator.suit.security import SuitDigest +from suit_generator.suit.security import CoseEncryptTagged, SuitDigest from suit_generator.suit.types.keys import ( suit_parameter_vendor_identifier, suit_parameter_class_identifier, @@ -38,6 +38,7 @@ suit_parameter_soft_failure, suit_parameter_image_size, suit_parameter_content, + suit_parameter_encryption_info, suit_parameter_uri, suit_parameter_source_component, suit_parameter_invoke_args, @@ -252,6 +253,7 @@ class SuitParameters(SuitKeyValue): suit_parameter_soft_failure: SuitBool, suit_parameter_image_size: SuitImageSize, suit_parameter_content: SuitBstr, + suit_parameter_encryption_info: cbstr(CoseEncryptTagged), suit_parameter_uri: SuitTstr, suit_parameter_source_component: SuitUint, suit_parameter_invoke_args: SuitBstr, diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 5fda5f3c..1594a9ad 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -203,6 +203,11 @@ class suit_parameter_content(suit_key): id = 18 name = "suit-parameter-content" +class suit_parameter_encryption_info(suit_key): + """suit-parameter-encryption-info metadata.""" + + id = 19 + name = "suit-parameter-encryption-info" class suit_parameter_uri(suit_key): """suit-parameter-uri metadata.""" From c96146fd5045b1ab9065c65e1ae04b3b0e907d25 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Tue, 13 Aug 2024 15:35:58 +0200 Subject: [PATCH 30/67] manifest: Fix component version encoding The version comparison value should be encoded using array, not map. Ref: NCSDK-28657 Signed-off-by: Tomasz Chyrowicz --- suit_generator/suit/manifest.py | 27 +++++++-------------------- suit_generator/suit/types/keys.py | 25 +++++++++++-------------- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index b1af0638..0b709126 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -44,8 +44,6 @@ suit_parameter_invoke_args, suit_parameter_device_identifier, suit_parameter_version, - suit_parameter_version_comparison_type, - suit_parameter_version_comparison_value, suit_directive_set_component_index, suit_directive_try_each, suit_directive_write, @@ -209,33 +207,22 @@ def from_obj(cls, obj: dict) -> SuitUint: raise ValueError(f"Unable to parse image size: {obj}") -class SuitConditionVersionComparisonType(SuitEnum): - """Representation of available SUIT condition version comparison types.""" - - _metadata = Metadata( - children=[ - suit_condition_version_comparison_greater, - suit_condition_version_comparison_greater_equal, - suit_condition_version_comparison_equal, - suit_condition_version_comparison_lesser_equal, - suit_condition_version_comparison_lesser, - ] - ) - - class SuitComponentVersion(SuitList): """Representation of a single component version.""" _metadata = Metadata(children=[SuitInt]) -class SuitParameterVersion(SuitKeyValue): +class SuitParameterVersion(SuitKeyValueTuple): """Representation of SUIT version parameter.""" _metadata = Metadata( map={ - suit_parameter_version_comparison_type: SuitConditionVersionComparisonType, - suit_parameter_version_comparison_value: SuitComponentVersion, + suit_condition_version_comparison_greater: SuitComponentVersion, + suit_condition_version_comparison_greater_equal: SuitComponentVersion, + suit_condition_version_comparison_equal: SuitComponentVersion, + suit_condition_version_comparison_lesser_equal: SuitComponentVersion, + suit_condition_version_comparison_lesser: SuitComponentVersion, } ) @@ -258,7 +245,7 @@ class SuitParameters(SuitKeyValue): suit_parameter_source_component: SuitUint, suit_parameter_invoke_args: SuitBstr, suit_parameter_device_identifier: SuitUUID, - suit_parameter_version: SuitParameterVersion, + suit_parameter_version: cbstr(SuitParameterVersion), } ) diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 1594a9ad..57b52d32 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -134,20 +134,6 @@ class suit_parameter_version(suit_key): name = "suit-parameter-version" -class suit_parameter_version_comparison_type(suit_key): - """suit-parameter-version-comparison-type metadata.""" - - id = 1 - name = "suit-parameter-version-comparison-type" - - -class suit_parameter_version_comparison_value(suit_key): - """suit-parameter-version-comparison-value metadata.""" - - id = 2 - name = "suit-parameter-version-comparison-value" - - class suit_parameter_vendor_identifier(suit_key): """suit-parameter-vendor-identifier metadata.""" @@ -203,12 +189,14 @@ class suit_parameter_content(suit_key): id = 18 name = "suit-parameter-content" + class suit_parameter_encryption_info(suit_key): """suit-parameter-encryption-info metadata.""" id = 19 name = "suit-parameter-encryption-info" + class suit_parameter_uri(suit_key): """suit-parameter-uri metadata.""" @@ -579,12 +567,14 @@ class suit_cose_key_id(suit_key): id = 4 name = "suit-cose-key-id" + class suit_cose_iv(suit_key): """suit-cose-iv metadata.""" id = 5 name = "suit-cose-iv" + class suit_issuer(suit_key): """CWT Issuer metadata.""" @@ -696,42 +686,49 @@ class cose_alg_eddsa(suit_key): id = -8 name = "cose-alg-eddsa" + class cose_alg_aes_gcm_128(suit_key): """Cose algorithm metadata.""" id = 1 name = "cose-alg-aes-gcm-128" + class cose_alg_aes_gcm_192(suit_key): """Cose algorithm metadata.""" id = 2 name = "cose-alg-aes-gcm-192" + class cose_alg_aes_gcm_256(suit_key): """Cose algorithm metadata.""" id = 3 name = "cose-alg-aes-gcm-256" + class cose_alg_a256kw(suit_key): """Cose algorithm metadata.""" id = -5 name = "cose-alg-a256kw" + class cose_alg_a192kw(suit_key): """Cose algorithm metadata.""" id = -4 name = "cose-alg-a192kw" + class cose_alg_a128kw(suit_key): """Cose algorithm metadata.""" id = -3 name = "cose-alg-a128kw" + class suit_send_record_success(suit_key): """Reporting policy bit.""" From 2cc6a95508b64996406abeb1ed5ce31132e957f7 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 14 Aug 2024 08:30:07 +0200 Subject: [PATCH 31/67] black: Run over encryption files Fix formatting issues in files related to encryption. Ref: NCSDK-28657 Signed-off-by: Tomasz Chyrowicz --- suit_generator/suit/security.py | 31 +++++++++++++++++++++++------ suit_generator/suit/types/common.py | 12 +++++------ tests/test_suit_encryption.py | 11 ++++++---- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index 2ed2097a..7d9470da 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -173,10 +173,21 @@ class SuitDigest(SuitUnion): class SuitcoseAlg(SuitEnum): """Representation of SUIT COSE sign algorithm.""" - _metadata = Metadata(children=[cose_alg_es_256, cose_alg_es_384, cose_alg_es_521, cose_alg_eddsa, - cose_alg_aes_gcm_128, cose_alg_aes_gcm_192, cose_alg_aes_gcm_256, - cose_alg_a256kw, cose_alg_a192kw, cose_alg_a128kw, - ]) + _metadata = Metadata( + children=[ + cose_alg_es_256, + cose_alg_es_384, + cose_alg_es_521, + cose_alg_eddsa, + cose_alg_aes_gcm_128, + cose_alg_aes_gcm_192, + cose_alg_aes_gcm_256, + cose_alg_a256kw, + cose_alg_a192kw, + cose_alg_a128kw, + ] + ) + class SuitcoseKeyId(SuitUnion): """Representation of a KEY ID item.""" @@ -200,6 +211,7 @@ class SuitHeaderMap(SuitKeyValue): } ) + class SuitHeaderMapOptional(SuitUnion): """Representation of COSE_Encrypt_ciphertext item.""" @@ -216,13 +228,14 @@ def from_obj(cls, obj) -> SuitUnion: value = None if isinstance(obj, dict): value = SuitEmptyBstr.from_obj("") if obj == {} else SuitHeaderMap.from_obj(obj) - elif obj == '' or obj == b'': - value = SuitEmptyBstr.from_obj('') + elif obj == "" or obj == b"": + value = SuitEmptyBstr.from_obj("") else: raise ValueError(f"Expected dict empty string or empty sequence of bytes received: {obj}") return cls(value) + class SuitHeaderData(SuitUnion): """Abstract element to define possible sub-elements.""" @@ -319,11 +332,13 @@ class SuitDelegationChain(SuitList): ### Encryption + class SuitCiphertextBytes(SuitHex): """Representation of SUIT ciphertext bytes.""" pass + class CoseEncryptCiphertext(SuitUnion): """Representation of COSE_Encrypt_ciphertext item.""" @@ -334,6 +349,7 @@ class CoseEncryptCiphertext(SuitUnion): ] ) + class CoseRecipient(SuitTupleNamed): """Representation of COSE_Recipient item.""" @@ -346,6 +362,7 @@ class CoseRecipient(SuitTupleNamed): } ) + class CoseRecipientList(SuitList): """Representation of a list of COSE_Recipient items.""" @@ -355,6 +372,7 @@ class CoseRecipientList(SuitList): # Fix cyclic dependencies between types CoseRecipient._metadata.map["recipients*"] = CoseRecipientList + class CoseEncrypt(SuitTupleNamed): """Representation of COSE_Encrypt item.""" @@ -367,6 +385,7 @@ class CoseEncrypt(SuitTupleNamed): } ) + class CoseEncryptTagged(SuitTag): """Representation of COSE_Encrypt_Tagged item.""" diff --git a/suit_generator/suit/types/common.py b/suit_generator/suit/types/common.py index ab4ac30c..ba6ba127 100644 --- a/suit_generator/suit/types/common.py +++ b/suit_generator/suit/types/common.py @@ -134,10 +134,8 @@ def validate_cbor(cbstr: bytes) -> None: does not contain data which will cause that cbor2 library will request huge amount of memory. """ requested_memory_len = None - if (len(cbstr) < 1): - raise ValueError( - f"The cbstr parsed object is empty" - ) + if len(cbstr) < 1: + raise ValueError(f"The cbstr parsed object is empty") cbor_item_type = cbstr[0] >> 5 cbor_item_count = cbstr[0] & 31 # fixme: do not validate CBORTag length @@ -661,17 +659,19 @@ def to_obj(self) -> str: """Dump SUIT representation to object.""" return self.value.hex() + class SuitEmptyBstr(SuitBstr): @classmethod def from_cbor(cls, cbstr: bytes) -> SuitBstr: """Restore SUIT representation from passed CBOR.""" - if (len(cbstr) > 0): + if len(cbstr) > 0: raise ValueError(f"{cbstr} is not empty") return cls(cbstr) def to_cbor(self) -> bytes: - return b'' + return b"" + class SuitHex(SuitBstr): """Representation of hex type.""" diff --git a/tests/test_suit_encryption.py b/tests/test_suit_encryption.py index ccd673c6..9a25c62e 100644 --- a/tests/test_suit_encryption.py +++ b/tests/test_suit_encryption.py @@ -23,16 +23,14 @@ "protected": { "suit-cose-algorithm-id": "cose-alg-aes-gcm-128", }, - "unprotected": { - "suit-cose-iv": "f14aab9d81d51f7ad943fe87af4f70cd" - }, + "unprotected": {"suit-cose-iv": "f14aab9d81d51f7ad943fe87af4f70cd"}, "ciphertext": None, "recipients": [ { "protected": {}, "unprotected": { "suit-cose-algorithm-id": "cose-alg-a128kw", - "suit-cose-key-id": "6b69642d31" # "kid-1" + "suit-cose-key-id": "6b69642d31", # "kid-1" }, "ciphertext": "75603ffc9518d794713c8ca8a115a7fb32565a6d59534d62", }, @@ -41,6 +39,7 @@ }, } + @pytest.mark.parametrize( "input_data", [ @@ -51,6 +50,7 @@ def test_suit_cose_encrypt_content_from_obj(input_data): suit_obj = CoseEncryptTagged.from_obj(TEST_DATA_OBJECT[input_data]) assert suit_obj.value is not None + @pytest.mark.parametrize( "input_data", [ @@ -63,6 +63,7 @@ def test_suit_cose_encrypt_from_obj(input_data): assert suit_obj.value is not None assert suit_binary.hex() == CoseEncryptTagged.from_cbor(suit_binary).to_cbor().hex() + @pytest.mark.parametrize( "input_data", [ @@ -73,6 +74,7 @@ def test_suit_cose_encrypt_from_cbor(input_data): suit_obj = CoseEncryptTagged.from_cbor(binascii.a2b_hex(TEST_DATA[input_data])) assert suit_obj.value is not None + @pytest.mark.parametrize( "input_data", [ @@ -83,6 +85,7 @@ def test_suit_cose_encrypt_from_cbor_parse_and_dump(input_data): suit_obj = CoseEncryptTagged.from_cbor(binascii.a2b_hex(TEST_DATA[input_data])) assert suit_obj.to_cbor().hex().upper() == TEST_DATA[input_data].upper() + @pytest.mark.parametrize( "input_data", [ From b750d417a322c647a9c802225395e5b266b63530 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Fri, 2 Aug 2024 18:39:15 +0200 Subject: [PATCH 32/67] fix: Improve docstring in build configuration module Signed-off-by: Tomasz Chyrowicz --- build_configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_configuration/configuration.py b/build_configuration/configuration.py index 2a40de1e..1956ba7a 100644 --- a/build_configuration/configuration.py +++ b/build_configuration/configuration.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -"""build_configuration module to create and parse build configuration.""" +"""The module to create and parse build configuration.""" import re import string From 14f2a23e88ae18ef3d6e1b748953a10c0fd5b26f Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Wed, 10 Jul 2024 14:34:50 +0200 Subject: [PATCH 33/67] Added Enc structure structure to suit-generator Although the structure is not directly used during manifest creation, as the hardcoded value is used by encrypt_script.py, but it is useful for encoding the structure in case other algorithms for key wrap/key derivation/encryption are used in the future. Ref: NCSDK-28265 Signed-off-by: Artur Hadasz --- suit_generator/suit/security.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index 7d9470da..5757e338 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -390,3 +390,15 @@ class CoseEncryptTagged(SuitTag): """Representation of COSE_Encrypt_Tagged item.""" _metadata = Metadata(children=[CoseEncrypt], tag=Tag(96, "CoseEncryptTagged")) + + +class CoseEncStructure(SuitTupleNamed): + """Representation of COSE Enc_structure.""" + + _metadata = Metadata( + map={ + "context": SuitTstr, + "protected": cbstr(SuitHeaderMapOptional), + "external_aad": SuitBstr, + } + ) From 5c4125a8f8569bfaafd6b8fb76a61419e1af426c Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Thu, 11 Jul 2024 17:11:52 +0200 Subject: [PATCH 34/67] Added encrypt_script.py for generating encryption artifacts Ref: NCSDK-28259 Signed-off-by: Artur Hadasz --- ncs/encrypt_script.py | 312 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 ncs/encrypt_script.py diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py new file mode 100644 index 00000000..a39b92fd --- /dev/null +++ b/ncs/encrypt_script.py @@ -0,0 +1,312 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +""" +This script allows to output artifacts needed by a SUIT envelope for encrypted firmware. +""" + +import os +import subprocess +import cbor2 +from argparse import ArgumentParser +from argparse import RawTextHelpFormatter +from pathlib import Path +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from tempfile import TemporaryDirectory +from enum import Enum, unique + + +@unique +class SuitAlgorithms(Enum): + """Suit algorithms.""" + + COSE_ALG_AES_GCM_128 = 1 + COSE_ALG_AES_GCM_192 = 2 + COSE_ALG_AES_GCM_256 = 3 + COSE_ALG_A128KW = -3 + COSE_ALG_A192KW = -4 + COSE_ALG_A256KW = -5 + + +class SuitIds(Enum): + """Suit elements identifiers.""" + + COSE_ALG = 1 + COSE_KEY_ID = 4 + COSE_IV = 5 + + +class SuitDomains(Enum): + """Suit domains.""" + + APPLICATION = "application" + RADIO = "radio" + CELL = "cell" + WIFI = "wifi" + + def __str__(self): + return self.value + + +class SuitDigestAlgorithms(Enum): + """Suit digest algorithms.""" + + SHA_256 = "sha-256" + SHA_384 = "sha-384" + SHA_512 = "sha-512" + SHAKE128 = "shake128" + SHAKE256 = "shake256" + + def __str__(self): + return self.value + + +KEY_IDS = { + SuitDomains.APPLICATION.value: 0x40020200, + SuitDomains.RADIO.value: 0x40030200, + SuitDomains.CELL.value: 0x40040200, + SuitDomains.WIFI.value: 0x40060200, +} + + +class DigestGenerator: + + _hash_func = { + SuitDigestAlgorithms.SHA_256.value: hashes.SHA256(), + SuitDigestAlgorithms.SHAKE128.value: hashes.SHAKE128(16), + SuitDigestAlgorithms.SHA_384.value: hashes.SHA384(), + SuitDigestAlgorithms.SHA_512.value: hashes.SHA512(), + SuitDigestAlgorithms.SHAKE256.value: hashes.SHAKE256(32), + } + + def __init__(self, hash_name: str): + """Initialize object.""" + if hash_name not in self._hash_func: + raise ValueError(f"Unsupported hash algorithm: {hash_name}") + self._hash_name = hash_name + + def generate_digest_size_for_plain_text(self, plaintext_file_path: Path, output_directory: Path): + plaintext = [] + with open(plaintext_file_path, "rb") as plaintext_file: + plaintext = plaintext_file.read() + + func = hashes.Hash(self._hash_func[self._hash_name], backend=default_backend()) + func.update(plaintext) + digest = func.finalize() + with open(os.path.join(output_directory, "plain_text_digest.bin"), "wb") as file: + file.write(digest) + with open(os.path.join(output_directory, "plain_text_size.txt"), "w") as file: + file.write(str(len(plaintext))) + + +class Encryptor: + def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, context: str): + # KMS generates a UUID which it uses in the generated + # file names, however there does not seem to be a way to calculate it. Therefore + # a temporary directory is used to store the generated files. The files are then + # searched in the directory using a wildcard. + temp_dir = TemporaryDirectory() + + # Enc structure: + # { + # "context": "Encrypt", + # "protected": {"suit-cose-algorithm-id": "cose-alg-aes-gcm-256"}, + # "external_aad": "", + # } + enc_structure_encoded = "8367456e637279707443a1010340" + + subprocess.run( + "nrfkms wrap" + + " -k " + + key_name + + " -f " + + str(plaintext_file_path) + + " -c " + + context + + " --format native -t aes" + + " --aad " + + enc_structure_encoded + + " -o " + + temp_dir.name, + shell=True, + check=True, + ) + encrypted_asset = None + encrypted_cek = None + for root, dirs, file in os.walk(temp_dir.name): + for f in file: + if "encrypted_asset" in f: + encrypted_asset_file = temp_dir.name + "/" + f + with open(encrypted_asset_file, "rb") as file: + encrypted_asset = file.read() + if "wrapped_aek-aes" in f: + encrypted_cek_file = temp_dir.name + "/" + f + with open(encrypted_cek_file, "rb") as file: + encrypted_cek = file.read() + temp_dir.cleanup() + return encrypted_asset, encrypted_cek + + def parse_encrypted_assets(self, asset_bytes): + init_vector = asset_bytes[:12] # the names init vector and nonce are used interchangeably in this case + # NOTE - it is not yet clear if this is a temporary bug or a difference in format, + # but nrfkms wrap returns the encrypted data in format nonce|encrypted_data|tag, instead of nonce|tag|encrypted_data + # which is returned by nrfkms encrypt + encrypted_content = asset_bytes[12:-16] + tag = asset_bytes[-16:] + return init_vector, tag, encrypted_content + + def generate_encrypted_payload(self, encrypted_content, tag, output_directory: Path): + with open(os.path.join(output_directory, "encrypted_content.bin"), "wb") as file: + file.write(encrypted_content + tag) + + def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_directory: Path): + + Cose_Encrypt = [ + # protected + cbor2.dumps( + { + SuitIds.COSE_ALG.value: SuitAlgorithms.COSE_ALG_AES_GCM_256.value, + } + ), + # unprotected + { + SuitIds.COSE_IV.value: bytes(iv), + }, + # ciphertext + None, + # recipients + [ + [ + # protected + b"", + # unprotected + { + SuitIds.COSE_ALG.value: SuitAlgorithms.COSE_ALG_A256KW.value, + SuitIds.COSE_KEY_ID.value: cbor2.dumps(KEY_IDS[domain]), + }, + # ciphertext + encrypted_cek, + ] + ], + ] + + Cose_Encrypt_Tagged = cbor2.CBORTag(96, Cose_Encrypt) + encryption_info = cbor2.dumps(cbor2.dumps(Cose_Encrypt_Tagged)) + + with open(os.path.join(output_directory, "suit_encryption_info.bin"), "wb") as file: + file.write(encryption_info) + + def generate_encryption_info_and_encrypted_payload( + self, encrypted_asset: Path, encrypted_cek: Path, output_directory: Path, domain: str + ): + init_vector, tag, encrypted_content = self.parse_encrypted_assets(encrypted_asset) + self.generate_encrypted_payload(encrypted_content, tag, output_directory) + self.generate_suit_encryption_info(init_vector, encrypted_cek, domain, output_directory) + + +def create_encrypt_and_generate_subparser(top_parser): + parser = top_parser.add_parser( + "encrypt-and-generate", help="First encrypt the command using nrfkms, then generate the files." + ) + + parser.add_argument("--firmware", required=True, type=Path, help="Input, plaintext firmware.") + parser.add_argument("--key-name", required=True, type=str, help="Name of the key used to derive the key by nrfkms.") + parser.add_argument( + "--domain", + required=True, + type=SuitDomains, + choices=list(SuitDomains), + help="The SoC domain of the firmware. Used to determine the key ID.", + ) + parser.add_argument( + "--context", + required=True, + type=str, + help="Context string used to derive the key. See nrfkms documentation for more information.", + ) + parser.add_argument("--output-dir", required=True, type=Path, help="Directory to store the output files") + parser.add_argument( + "--hash-alg", + default=SuitDigestAlgorithms.SHA_256.value, + type=SuitDigestAlgorithms, + choices=list(SuitDigestAlgorithms), + help="Algorithm used to create plaintext digest.", + ) + + +def create_generate_subparser(top_parser): + parser = top_parser.add_parser("generate", help="Only generate files based on encrypted firmware") + + parser.add_argument( + "--encrypted-firmware", + required=True, + type=Path, + help="Input, encrypted firmware in form iv|tag|encrypted_firmware", + ) + parser.add_argument("--encrypted-key", required=True, type=Path, help="Encrypted content/asset encryption key") + parser.add_argument( + "--domain", + required=True, + type=SuitDomains, + choices=list(SuitDomains), + help="The SoC domain of the firmware. Used to determine the key ID.", + ) + parser.add_argument("--output-dir", required=True, type=Path, help="Directory to store the output files") + + +def create_subparsers(parser): + subparsers = parser.add_subparsers(dest="command", required=True, help="Choose subcommand:") + + create_encrypt_and_generate_subparser(subparsers) + create_generate_subparser(subparsers) + + +if __name__ == "__main__": + parser = ArgumentParser( + description="""This script allows to output artifacts needed by a SUIT envelope for encrypted firmware. + +It has two modes of operation: + - encrypt-and-generate: First encrypt the command using nrfkms, then generate the files. + - generate: Only generate files based on encrypted firmware and the encrypted content/asset encryption key. + Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware. + +In both cases the output files are: + encrypted_content.bin - encrypted content of the firmware concatenated with the tag (encrypted firmware|16 byte tag). + This file is used as the payload in the SUIT envelope. + suit_encryption_info.bin - The binary contents which should be included in the SUIT envelope as the contents of the suit-encryption-info parameter. + +Additionally, the encrypt-and-generate mode generates the following file: + plain_text_digest.bin - The digest of the plaintext firmware. + plain_text_size.txt - The size of the plaintext firmware in bytes. + """, + formatter_class=RawTextHelpFormatter, + ) + + create_subparsers(parser) + + arguments = parser.parse_args() + + encrypted_asset = None + encrypted_cek = None + + encryptor = Encryptor() + + if arguments.command == "encrypt-and-generate": + digest_generator = DigestGenerator(arguments.hash_alg.value) + digest_generator.generate_digest_size_for_plain_text(arguments.firmware, arguments.output_dir) + encrypted_asset, encrypted_cek = encryptor.generate_kms_artifacts( + arguments.firmware, arguments.key_name, arguments.context + ) + + if arguments.command == "generate": + with open(arguments.encrypted_firmware, "rb") as file: + encrypted_asset = file.read() + with open(arguments.encrypted_key, "rb") as file: + encrypted_cek = file.read() + + encryptor.generate_encryption_info_and_encrypted_payload( + encrypted_asset, encrypted_cek, arguments.output_dir, arguments.domain.value + ) From 2d9a9825c525f8b559ddc48bb61aa7d8158ff354 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 15 Jul 2024 14:35:39 +0200 Subject: [PATCH 35/67] Manifest template for encrypted update Ref: NCSDK-28259 Signed-off-by: Artur Hadasz --- ncs/app_envelope_encrypted.yaml.jinja2 | 152 +++++++++++++++++++++++++ ncs/build.py | 16 ++- ncs/encrypt_script.py | 16 ++- suit_generator/suit/manifest.py | 9 +- suit_generator/suit/security.py | 39 +++++++ 5 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 ncs/app_envelope_encrypted.yaml.jinja2 diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 new file mode 100644 index 00000000..214e6ba3 --- /dev/null +++ b/ncs/app_envelope_encrypted.yaml.jinja2 @@ -0,0 +1,152 @@ +{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} +SUIT_Envelope_Tagged: + suit-authentication-wrapper: + SuitDigest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-manifest: + suit-manifest-version: 1 + suit-manifest-sequence-number: {{ sequence_number }} + suit-common: + suit-components: + - - MEM + - {{ application['dt'].label2node['cpu'].unit_addr }} + - {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }} + - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} + - - CAND_IMG + - 0 + suit-shared-sequence: + - suit-directive-set-component-index: 0 + - suit-directive-override-parameters: + suit-parameter-vendor-identifier: + RFC4122_UUID: {{ mpi_application_vendor_name }} + suit-parameter-class-identifier: + RFC4122_UUID: + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file_direct: {{ application['encryption_artifacts_dir'] }}/plain_text_digest.bin + suit-parameter-image-size: + file_direct: {{ application['encryption_artifacts_dir'] }}/plain_text_size.txt + - suit-condition-vendor-identifier: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-condition-class-identifier: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-validate: + - suit-directive-set-component-index: 0 + # In the case of streaming operations it is worth to retry them at least once. + # This increases the robustness against bit flips on the transport, + # for example when storing the data on an external memory device. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-invoke: + - suit-directive-set-component-index: 0 + - suit-directive-invoke: + - suit-send-record-failure + suit-install: + - suit-directive-set-component-index: 1 + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ application['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file: {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin + - suit-directive-fetch: + - suit-send-record-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - suit-directive-set-component-index: 0 + - suit-directive-override-parameters: + suit-parameter-source-component: 1 + suit-parameter-encryption-info: + file: {{ application['encryption_artifacts_dir'] }}/suit_encryption_info.bin + # When copying the data it is worth to retry the sequence of + # suit-directive-copy and suit-condition-image-match at least once. + # If a bit flip occurs, it might be due to a transport issue, not + # a corrupted candidate image. In this case the bit flip is recoverable + # and it is worth retrying the operation. + # The suit-directive-try-each will complete on the first successful subsequence. + - suit-directive-try-each: + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-directive-copy: + - suit-send-record-failure + - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-text: + suit-digest-algorithm-id: cose-alg-sha-256 + + suit-candidate-verification: + - suit-directive-set-component-index: 1 + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ application['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file: {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin + - suit-directive-fetch: + - suit-send-record-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + + suit-manifest-component-id: + - INSTLD_MFST + - RFC4122_UUID: + namespace: {{ mpi_application_vendor_name }} + name: {{ mpi_application_class_name }} + suit-text: + en: + '["MEM", {{ application['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': + suit-text-vendor-name: Nordic Semiconductor ASA + suit-text-model-name: nRF54H20_cpuapp + suit-text-vendor-domain: nordicsemi.com + suit-text-model-info: The nRF54H20 application core + suit-text-component-description: Sample application core encrypted FW + suit-text-component-version: v1.0.0 + suit-integrated-payloads: + '#{{ application['name'] }}': {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin diff --git a/ncs/build.py b/ncs/build.py index 004a679b..23596c26 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -32,7 +32,19 @@ def read_configurations(configurations): """Read configuration stored in the pickled devicetree.""" data = {} for config in configurations: - name, binary, edt, kconfig = config.split(",") + args = config.split(",") + if len(args) < 4: + raise ValueError("Invalid number of input arguments") + + # Parse obligatory arguments + name, binary, edt, kconfig = args[:4] + + # Parse optional arguments + if len(args) > 4: + encryption_artifacts_dir = args[4] + else: + encryption_artifacts_dir = None + edt_data = None if edt: with open(edt, "rb") as edt_handler: @@ -56,6 +68,8 @@ def read_configurations(configurations): if binary: data[image_name]["filename"] = pathlib.Path(binary).name data[image_name]["binary"] = binary + if encryption_artifacts_dir: + data[image_name]["encryption_artifacts_dir"] = encryption_artifacts_dir data["get_absolute_address"] = get_absolute_address return data diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index a39b92fd..a30bc818 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -116,7 +116,8 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte # "protected": {"suit-cose-algorithm-id": "cose-alg-aes-gcm-256"}, # "external_aad": "", # } - enc_structure_encoded = "8367456e637279707443a1010340" + # bytes(hex): 8367456e637279707443a1010340 + enc_structure_encoded = "\\x83gEncryptC\\xa1\\x01\\x03@" subprocess.run( "nrfkms wrap" @@ -127,10 +128,8 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte + " -c " + context + " --format native -t aes" - + " --aad " - + enc_structure_encoded - + " -o " - + temp_dir.name, + # + " --aad \"`echo -e \"" + enc_structure_encoded + "\"`\"" + + " --aad $" + enc_structure_encoded + " -o " + temp_dir.name, shell=True, check=True, ) @@ -235,6 +234,13 @@ def create_encrypt_and_generate_subparser(top_parser): choices=list(SuitDigestAlgorithms), help="Algorithm used to create plaintext digest.", ) + parser.add_argument("--kms-backend", required=True, type=str, help="KMS backend to use - vault/local.") + parser.add_argument("--kms-vault-url", type=str, help='URL of the KMS vault - only if kms-backend set to "vault".') + parser.add_argument("--kms-token", type=str, help='KMS token - only if kms-backend set to "vault"') + parser.add_argument("--kms-dir", type=str, help='Local backend directory - only if kms-backend set to "local".') + parser.add_argument( + "--kms-local-password", type=str, help='KMS local backend password - only if kms-backend set to "local".' + ) def create_generate_subparser(top_parser): diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 0b709126..77edb91a 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -28,7 +28,7 @@ SuitBchar, cbstr, ) -from suit_generator.suit.security import CoseEncryptTagged, SuitDigest +from suit_generator.suit.security import CoseEncryptTagged, SuitDigest, SuitEncryptionInfo from suit_generator.suit.types.keys import ( suit_parameter_vendor_identifier, suit_parameter_class_identifier, @@ -203,6 +203,11 @@ def from_obj(cls, obj: dict) -> SuitUint: binary_data = SuitEnvelopeTagged.return_processed_binary_data(obj["envelope"]) return super().from_obj(len(binary_data)) + elif "file_direct" in obj.keys(): + img_size = 0 + with open(obj["file_direct"], "r") as file: + img_size = int(file.read()) + return super().from_obj(img_size) else: raise ValueError(f"Unable to parse image size: {obj}") @@ -240,7 +245,7 @@ class SuitParameters(SuitKeyValue): suit_parameter_soft_failure: SuitBool, suit_parameter_image_size: SuitImageSize, suit_parameter_content: SuitBstr, - suit_parameter_encryption_info: cbstr(CoseEncryptTagged), + suit_parameter_encryption_info: SuitEncryptionInfo, suit_parameter_uri: SuitTstr, suit_parameter_source_component: SuitUint, suit_parameter_invoke_args: SuitBstr, diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index 5757e338..abff2ead 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -12,6 +12,7 @@ SuitNull, SuitEnum, SuitInt, + SuitObject, SuitTstr, SuitHex, SuitUnion, @@ -158,6 +159,9 @@ def from_obj(cls, obj: dict) -> SuitDigestRaw: obj[suit_digest_bytes.name] = sub_envelope.get_manifest_digest(obj[suit_digest_algorithm_id.name]).hex() elif "raw" in digest_dict.keys(): obj[suit_digest_bytes.name] = digest_dict["raw"] + elif "file_direct" in digest_dict.keys(): + with open(digest_dict["file_direct"], "rb") as fd: + obj[suit_digest_bytes.name] = fd.read().hex() else: raise ValueError(f"Unable to calculate digest from: {digest_dict}") @@ -402,3 +406,38 @@ class CoseEncStructure(SuitTupleNamed): "external_aad": SuitBstr, } ) + + +class SuitEncryptionInfoExt(SuitBstr): + """Representation of SUIT encryption info ext.""" + + @classmethod + def to_obj(self) -> dict: + raise ValueError("Encryption info should be expanded to full structure by to_obj method") + + @classmethod + def from_obj(cls, obj: dict) -> SuitBstr: + """Restore SUIT representation from passed object.""" + if not isinstance(obj, dict): + raise ValueError(f"Expected dict, received: {obj}") + enc_info_bytes = b"" + if "raw" in obj.keys(): + enc_info_bytes = bytes.fromhex(obj["raw"]) # TODO: check this + elif "file" in obj.keys(): + with open(obj["file"], "rb") as fd: + enc_info_bytes = fd.read() + else: + raise ValueError(f"Unable to parse encryption info: {obj}") + # the value in enc_info_bytes is already bstr wrapped - we have + # to deserialize it, so that the SuitBstr to_cbor method returns the correct value + return super().from_cbor(super().deserialize_cbor(enc_info_bytes)) + + @classmethod + def from_cbor(self) -> dict: + raise ValueError(f"Encryption info should be created as serialized CoseEncryptTagged object from cbor") + + +class SuitEncryptionInfo(SuitUnion): + """Representation of SUIT digest.""" + + _metadata = Metadata(children=[cbstr(CoseEncryptTagged), SuitEncryptionInfoExt]) From f0f58d2b6816f07b388664bd71439121e576f6a4 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 22 Jul 2024 14:11:00 +0200 Subject: [PATCH 36/67] SUIT encryption: tag placed at the start of the firmware This allows for a much easier documentation on the device side. The streaming version of PSA API needs to pass the tag separately. It makes it much easier to identify the tag if it is placed at the start of the encrypted payload, as it doesn't require to know the whole payload size in advanced. At the same time no scenario is planned in which the non-streaming version of psa decryption API is used by SUIT, so placing the tag at the end of the payload is not required. Ref: NCSDK-28265 Signed-off-by: Artur Hadasz --- ncs/encrypt_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index a30bc818..f4ed40b7 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -159,7 +159,7 @@ def parse_encrypted_assets(self, asset_bytes): def generate_encrypted_payload(self, encrypted_content, tag, output_directory: Path): with open(os.path.join(output_directory, "encrypted_content.bin"), "wb") as file: - file.write(encrypted_content + tag) + file.write(tag + encrypted_content) def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_directory: Path): From ccc74092b11f02c38e800ddd6d1ffae78785be32 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 22 Jul 2024 16:09:02 +0200 Subject: [PATCH 37/67] Used pynrfkms instead of command line kms in encrypt_script.py It is (by the current state of knowledge) impossible to pass a binary AAD (containing non printable sign) to KMS via the command line API. The required AAD contains such bytes, which is why the pynrfkms frontend has to be used. Note that the limitation of the pynrfkms frontend is that it cannot used the already initialized KMS backend. This is why additional parameters have been added to encrypt_script.py to allow for backend initialization. Ref: NCSDK-28259 Signed-off-by: Artur Hadasz --- ncs/encrypt_script.py | 87 ++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index f4ed40b7..00522d57 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -8,15 +8,15 @@ """ import os -import subprocess import cbor2 from argparse import ArgumentParser from argparse import RawTextHelpFormatter from pathlib import Path from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend -from tempfile import TemporaryDirectory from enum import Enum, unique +from pynrfkms.kms import KMS +import getpass @unique @@ -64,6 +64,16 @@ def __str__(self): return self.value +class EncryptionKMSBackends(Enum): + """KMS backends.""" + + VAULT = "vault" + LOCAL = "local" + + def __str__(self): + return self.value + + KEY_IDS = { SuitDomains.APPLICATION.value: 0x40020200, SuitDomains.RADIO.value: 0x40030200, @@ -103,13 +113,19 @@ def generate_digest_size_for_plain_text(self, plaintext_file_path: Path, output_ class Encryptor: - def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, context: str): - # KMS generates a UUID which it uses in the generated - # file names, however there does not seem to be a way to calculate it. Therefore - # a temporary directory is used to store the generated files. The files are then - # searched in the directory using a wildcard. - temp_dir = TemporaryDirectory() + kms = None + + def init_kms_backend(self, cli_arguments): + if cli_arguments.kms_backend == EncryptionKMSBackends.VAULT: + self.kms = KMS(backend="vault", url=cli_arguments.kms_vault_url, token=cli_arguments.kms_token) + elif cli_arguments.kms_backend == EncryptionKMSBackends.LOCAL: + pswd = cli_arguments.kms_local_password + if pswd is None: + pswd = getpass.getpass("Enter password for local KMS backend: ") + self.kms = KMS(backend="local", dir=cli_arguments.kms_dir, password=pswd, encoding="der") + del pswd + def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, context: str): # Enc structure: # { # "context": "Encrypt", @@ -117,35 +133,21 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte # "external_aad": "", # } # bytes(hex): 8367456e637279707443a1010340 - enc_structure_encoded = "\\x83gEncryptC\\xa1\\x01\\x03@" - - subprocess.run( - "nrfkms wrap" - + " -k " - + key_name - + " -f " - + str(plaintext_file_path) - + " -c " - + context - + " --format native -t aes" - # + " --aad \"`echo -e \"" + enc_structure_encoded + "\"`\"" - + " --aad $" + enc_structure_encoded + " -o " + temp_dir.name, - shell=True, - check=True, + enc_structure_encoded = bytes( + [0x83, 0x67, 0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x43, 0xA1, 0x01, 0x03, 0x40] + ) + + asset_plaintext = [] + with open(plaintext_file_path, "rb") as plaintext_file: + asset_plaintext = plaintext_file.read() + + encrypted_asset, encrypted_cek = self.kms.aes_key_wrap( + key_name=key_name, + context=context, + plaintext=asset_plaintext, + aek_type="aes", + aad=enc_structure_encoded, ) - encrypted_asset = None - encrypted_cek = None - for root, dirs, file in os.walk(temp_dir.name): - for f in file: - if "encrypted_asset" in f: - encrypted_asset_file = temp_dir.name + "/" + f - with open(encrypted_asset_file, "rb") as file: - encrypted_asset = file.read() - if "wrapped_aek-aes" in f: - encrypted_cek_file = temp_dir.name + "/" + f - with open(encrypted_cek_file, "rb") as file: - encrypted_cek = file.read() - temp_dir.cleanup() return encrypted_asset, encrypted_cek def parse_encrypted_assets(self, asset_bytes): @@ -234,12 +236,20 @@ def create_encrypt_and_generate_subparser(top_parser): choices=list(SuitDigestAlgorithms), help="Algorithm used to create plaintext digest.", ) - parser.add_argument("--kms-backend", required=True, type=str, help="KMS backend to use - vault/local.") + parser.add_argument( + "--kms-backend", + required=True, + type=EncryptionKMSBackends, + choices=list(EncryptionKMSBackends), + help="KMS backend to use.", + ) parser.add_argument("--kms-vault-url", type=str, help='URL of the KMS vault - only if kms-backend set to "vault".') parser.add_argument("--kms-token", type=str, help='KMS token - only if kms-backend set to "vault"') parser.add_argument("--kms-dir", type=str, help='Local backend directory - only if kms-backend set to "local".') parser.add_argument( - "--kms-local-password", type=str, help='KMS local backend password - only if kms-backend set to "local".' + "--kms-local-password", + type=str, + help='KMS local backend password - only if kms-backend set to "local". If not provided, the script will prompt for it.', ) @@ -301,6 +311,7 @@ def create_subparsers(parser): encryptor = Encryptor() if arguments.command == "encrypt-and-generate": + encryptor.init_kms_backend(arguments) digest_generator = DigestGenerator(arguments.hash_alg.value) digest_generator.generate_digest_size_for_plain_text(arguments.firmware, arguments.output_dir) encrypted_asset, encrypted_cek = encryptor.generate_kms_artifacts( From 4d4954b38ce1285d6b671b2f7191cf5c421cf2c8 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 14 Aug 2024 13:38:08 +0200 Subject: [PATCH 38/67] ncs: Introduce candidate-verification sequence Use candidate-verification sequence to validate integrated payloads. Signed-off-by: Tomasz Chyrowicz --- ncs/app_envelope.yaml.jinja2 | 29 ++++++++++++++++++++++------ ncs/rad_envelope.yaml.jinja2 | 37 ++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 index ab84a5e1..b35092e7 100644 --- a/ncs/app_envelope.yaml.jinja2 +++ b/ncs/app_envelope.yaml.jinja2 @@ -41,12 +41,6 @@ SUIT_Envelope_Tagged: - suit-send-record-failure - suit-send-sysinfo-success - suit-send-sysinfo-failure - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['binary'] }} suit-validate: - suit-directive-set-component-index: 0 # In the case of streaming operations it is worth to retry them at least once. @@ -115,6 +109,29 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-failure suit-text: suit-digest-algorithm-id: cose-alg-sha-256 + + suit-candidate-verification: + - suit-directive-set-component-index: 1 + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ application['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file: {{ application['binary'] }} + - suit-directive-fetch: + - suit-send-record-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 index d1e6d3e2..adc8623c 100644 --- a/ncs/rad_envelope.yaml.jinja2 +++ b/ncs/rad_envelope.yaml.jinja2 @@ -28,8 +28,8 @@ SUIT_Envelope_Tagged: RFC4122_UUID: {{ mpi_radio_vendor_name }} suit-parameter-class-identifier: RFC4122_UUID: - namespace: {{ mpi_radio_vendor_name }} - name: {{ mpi_radio_class_name }} + namespace: {{ mpi_radio_vendor_name }} + name: {{ mpi_radio_class_name }} suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: @@ -46,12 +46,6 @@ SUIT_Envelope_Tagged: - suit-send-record-failure - suit-send-sysinfo-success - suit-send-sysinfo-failure - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ radio['binary'] }} suit-validate: - suit-directive-set-component-index: 0 # In the case of streaming operations it is worth to retry them at least once. @@ -77,6 +71,10 @@ SUIT_Envelope_Tagged: - suit-directive-set-component-index: 1 - suit-directive-override-parameters: suit-parameter-uri: '#{{ radio['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file: {{ radio['binary'] }} - suit-directive-fetch: - suit-send-record-failure - suit-directive-try-each: @@ -116,6 +114,29 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-failure suit-text: suit-digest-algorithm-id: cose-alg-sha-256 + + suit-candidate-verification: + - suit-directive-set-component-index: 1 + - suit-directive-override-parameters: + suit-parameter-uri: '#{{ radio['name'] }}' + suit-parameter-image-digest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: + file: {{ radio['binary'] }} + - suit-directive-fetch: + - suit-send-record-failure + - suit-directive-try-each: + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + - - suit-condition-image-match: + - suit-send-record-success + - suit-send-record-failure + - suit-send-sysinfo-success + - suit-send-sysinfo-failure + suit-manifest-component-id: - INSTLD_MFST - RFC4122_UUID: From dcd58e681a98d5b8b49ec56a065315f53e21a2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ku=C5=BAnia?= Date: Thu, 27 Jun 2024 09:05:16 +0200 Subject: [PATCH 39/67] templates: move templates to sdk-nrf and adjust Kconfigs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest templates have been moved to sdk-nrf repository. The Kconfig name and description was adjusted to the new build system behavior. Signed-off-by: Rafał Kuźnia --- ncs/Kconfig | 6 +- ncs/app_envelope.yaml.jinja2 | 150 ----------- ncs/rad_envelope.yaml.jinja2 | 155 ----------- ncs/root_with_binary_nordic_top.yaml.jinja2 | 285 -------------------- 4 files changed, 3 insertions(+), 593 deletions(-) delete mode 100644 ncs/app_envelope.yaml.jinja2 delete mode 100644 ncs/rad_envelope.yaml.jinja2 delete mode 100644 ncs/root_with_binary_nordic_top.yaml.jinja2 diff --git a/ncs/Kconfig b/ncs/Kconfig index 5b4e085b..32e4e441 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -8,10 +8,10 @@ config SSF_SUIT_SERVICE_ENABLED bool -config SUIT_ENVELOPE_TEMPLATE +config SUIT_ENVELOPE_TEMPLATE_FILENAME string "Path to the envelope template" - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP - default "${ZEPHYR_SUIT_GENERATOR_MODULE_DIR}/ncs/rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD + default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP + default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" diff --git a/ncs/app_envelope.yaml.jinja2 b/ncs/app_envelope.yaml.jinja2 deleted file mode 100644 index b35092e7..00000000 --- a/ncs/app_envelope.yaml.jinja2 +++ /dev/null @@ -1,150 +0,0 @@ -{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - MEM - - {{ application['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_application_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['binary'] }} - suit-parameter-image-size: - file: {{ application['binary'] }} - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-validate: - - suit-directive-set-component-index: 0 - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-invoke: - - suit-send-record-failure - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ application['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['binary'] }} - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - # When copying the data it is worth to retry the sequence of - # suit-directive-copy and suit-condition-image-match at least once. - # If a bit flip occurs, it might be due to a transport issue, not - # a corrupted candidate image. In this case the bit flip is recoverable - # and it is worth retrying the operation. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-text: - suit-digest-algorithm-id: cose-alg-sha-256 - - suit-candidate-verification: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ application['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['binary'] }} - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} - suit-text: - en: - '["MEM", {{ application['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': - suit-text-vendor-name: Nordic Semiconductor ASA - suit-text-model-name: nRF54H20_cpuapp - suit-text-vendor-domain: nordicsemi.com - suit-text-model-info: The nRF54H20 application core - suit-text-component-description: Sample application core FW - suit-text-component-version: v1.0.0 - suit-integrated-payloads: - '#{{ application['name'] }}': {{ application['binary'] }} diff --git a/ncs/rad_envelope.yaml.jinja2 b/ncs/rad_envelope.yaml.jinja2 deleted file mode 100644 index adc8623c..00000000 --- a/ncs/rad_envelope.yaml.jinja2 +++ /dev/null @@ -1,155 +0,0 @@ -{%- if application is defined %} - {%- set main_config = application %} -{%- else %} - {%- set main_config = radio %} -{%- endif %} -{%- set mpi_radio_vendor_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - MEM - - {{ radio['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(radio['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ radio['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_radio_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_radio_vendor_name }} - name: {{ mpi_radio_class_name }} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ radio['binary'] }} - suit-parameter-image-size: - file: {{ radio['binary'] }} - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-validate: - - suit-directive-set-component-index: 0 - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-invoke: - - suit-send-record-failure - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ radio['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ radio['binary'] }} - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - # When copying the data it is worth to retry the sequence of - # suit-directive-copy and suit-condition-image-match at least once. - # If a bit flip occurs, it might be due to a transport issue, not - # a corrupted candidate image. In this case the bit flip is recoverable - # and it is worth retrying the operation. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-text: - suit-digest-algorithm-id: cose-alg-sha-256 - - suit-candidate-verification: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ radio['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ radio['binary'] }} - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_radio_vendor_name }} - name: {{ mpi_radio_class_name }} - suit-text: - en: - '["MEM", {{ radio['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(radio['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ radio['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': - suit-text-vendor-name: Nordic Semiconductor ASA - suit-text-model-name: nRF54H20_cpurad - suit-text-vendor-domain: nordicsemi.com - suit-text-model-info: The nRF54H20 radio core - suit-text-component-description: Sample radio core FW - suit-text-component-version: v1.0.0 - suit-integrated-payloads: - '#{{ radio['name'] }}': {{ radio['binary'] }} diff --git a/ncs/root_with_binary_nordic_top.yaml.jinja2 b/ncs/root_with_binary_nordic_top.yaml.jinja2 deleted file mode 100644 index 22606db8..00000000 --- a/ncs/root_with_binary_nordic_top.yaml.jinja2 +++ /dev/null @@ -1,285 +0,0 @@ -{%- set component_index = 0 %} -{%- set component_list = [] %} -{%- if application is not defined %} - {%- set main_config = radio %} -{%- else %} - {%- set main_config = application %} -{%- endif %} -{%- set mpi_root_vendor_name = main_config['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_root_class_name = main_config['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_application_vendor_name = main_config['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = main_config['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_radio_vendor_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_radio_class_name = main_config['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} -{%- if 'SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY' in sysbuild['config'] and sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] != '' %} - {%- set nordic_top = True %} -{%- else %} - {%- set nordic_top = False %} -{%- endif %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} - suit-common: - suit-components: - - - CAND_MFST - - 0 -{%- if radio is defined %} - {%- set component_index = component_index + 1 %} - {%- set rad_component_index = component_index %} - {{- component_list.append( rad_component_index ) or ""}} - - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_radio_vendor_name }} - name: {{ mpi_radio_class_name }} -{%- endif %} -{%- if application is defined %} - {%- set component_index = component_index + 1 %} - {%- set app_component_index = component_index %} - {{- component_list.append( app_component_index ) or ""}} - - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} -{%- endif %} - -{%- set component_list_without_top = component_list[:] %} -{%- if nordic_top %} - {%- set component_index = component_index + 1 %} - {%- set top_component_index = component_index %} - {{- component_list.append( top_component_index ) or ""}} - - - INSTLD_MFST - - RFC4122_UUID: - namespace: nordicsemi.com - name: nRF54H20_nordic_top -{%- endif %} - - suit-shared-sequence: - - suit-directive-set-component-index: [{{ component_list|join(',') }}] - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_root_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_root_vendor_name }} - name: {{ mpi_root_class_name }} - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-dependencies: - # Key is the index of suit-components that describe the dependency manifest - "0": {} -{%- for component_element in component_list %} - "{{ component_element }}": {} -{%- endfor %} - suit-validate: - - suit-directive-set-component-index: [{{ component_list_without_top|join(',') }}] - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-invoke: - - suit-directive-set-component-index: [{{ component_list_without_top|join(',') }}] - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-install: - - suit-directive-set-component-index: 0 -{%- if radio is defined %} - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ radio['name'] }}' - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} -{%- if application is defined %} - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ application['name'] }}' - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} -{%- if nordic_top %} - - suit-directive-override-parameters: - suit-parameter-uri: '#top' - - suit-directive-fetch: - - suit-send-record-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} - - suit-candidate-verification: - - suit-directive-set-component-index: 0 -{%- if radio is defined %} - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ radio['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - envelope: {{ artifacts_folder ~ radio['name'] }}.suit - - suit-directive-fetch: - - suit-send-record-failure - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} -{%- if application is defined %} - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ application['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - envelope: {{ artifacts_folder ~ application['name'] }}.suit - - suit-directive-fetch: - - suit-send-record-failure - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} -{%- if nordic_top %} - - suit-directive-override-parameters: - suit-parameter-uri: '#top' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - envelope: {{ sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit - - suit-directive-fetch: - - suit-send-record-failure - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-dependency-integrity: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-process-dependency: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure -{%- endif %} - - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_root_vendor_name }} - name: {{ mpi_root_class_name }} - suit-integrated-dependencies: -{%- if radio is defined %} - '#{{ radio['name'] }}': {{ artifacts_folder ~ radio['name'] }}.suit -{%- endif %} -{%- if application is defined %} - '#{{ application['name'] }}': {{ artifacts_folder ~ application['name'] }}.suit -{%- endif %} -{%- if nordic_top %} - '#top': {{ sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_NORDIC_TOP_DIRECTORY'] }}/nordic_top.suit -{%- endif %} From 60fb0315309c23c07ec4a96f500f27243a989348 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Fri, 16 Aug 2024 11:54:43 +0200 Subject: [PATCH 40/67] flake8: Fix small issues Fix small findingd from the flake8 tool. Ref: NCSDK-NONE Signed-off-by: Tomasz Chyrowicz --- ncs/build.py | 2 +- suit_generator/logger.py | 3 +-- suit_generator/suit/security.py | 2 +- suit_generator/suit/types/common.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ncs/build.py b/ncs/build.py index 23596c26..761bf9bb 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -19,7 +19,7 @@ sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) from suit_generator.cmd_image import ImageCreator # noqa: E402 -from build_configuration.configuration import BuildConfiguration +from build_configuration.configuration import BuildConfiguration # noqa: E402 TEMPLATE_CMD = "template" STORAGE_CMD = "storage" diff --git a/suit_generator/logger.py b/suit_generator/logger.py index 6e3fd0ca..1bb67b97 100644 --- a/suit_generator/logger.py +++ b/suit_generator/logger.py @@ -12,7 +12,6 @@ import logging import logging.config -from typing import Any from pathlib import Path logger = logging.getLogger(__name__) @@ -32,7 +31,7 @@ def inner_func(*args, **kwargs): info = inspect.getframeinfo(frame) logger.debug(f"{info.filename}:{info.function}:{info.lineno}:{func.__name__}({args=},{kwargs=})") return func(*args, **kwargs) - except Exception as e: + except Exception: logger.debug(f"Unable to parse data: {args=},{kwargs=}") raise diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index abff2ead..d6db652e 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -334,7 +334,7 @@ class SuitDelegationChain(SuitList): _metadata = Metadata(children=[SuitDelegation]) -### Encryption +# Encryption class SuitCiphertextBytes(SuitHex): diff --git a/suit_generator/suit/types/common.py b/suit_generator/suit/types/common.py index ba6ba127..808c3c4e 100644 --- a/suit_generator/suit/types/common.py +++ b/suit_generator/suit/types/common.py @@ -135,7 +135,7 @@ def validate_cbor(cbstr: bytes) -> None: """ requested_memory_len = None if len(cbstr) < 1: - raise ValueError(f"The cbstr parsed object is empty") + raise ValueError("The cbstr parsed object is empty") cbor_item_type = cbstr[0] >> 5 cbor_item_count = cbstr[0] & 31 # fixme: do not validate CBORTag length From 513b7c5e286a0e3979b407144f69d9067cfb40aa Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 14 Aug 2024 15:43:38 +0200 Subject: [PATCH 41/67] build: Add possibility to pass VERSION file Add a possibility to pass the VERSION file path to the SUIT manifest template renderer as well as specifying the semantic version through strings. Ref: NCSDK-28657 Signed-off-by: Tomasz Chyrowicz --- ncs/app_envelope_encrypted.yaml.jinja2 | 12 +++++-- ncs/build.py | 16 ++++++++++ ncs/nordic_top_envelope.yaml.jinja2 | 11 +++++-- ncs/root_with_nordic_top_envelope.yaml.jinja2 | 11 +++++-- suit_generator/suit/manifest.py | 32 +++++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 index 214e6ba3..b94eed09 100644 --- a/ncs/app_envelope_encrypted.yaml.jinja2 +++ b/ncs/app_envelope_encrypted.yaml.jinja2 @@ -1,13 +1,16 @@ {%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: suit-digest-algorithm-id: cose-alg-sha-256 suit-manifest: suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} +{%- if APP_LOCAL_1_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ APP_LOCAL_1_SEQ_NUM }} +{%- else %} + suit-manifest-sequence-number: 1 +{%- endif %} suit-common: suit-components: - - MEM @@ -62,6 +65,11 @@ SUIT_Envelope_Tagged: - suit-directive-set-component-index: 0 - suit-directive-invoke: - suit-send-record-failure + +{%- if APP_LOCAL_1_VERSION is defined %} + suit-current-version: {{ APP_LOCAL_1_VERSION }} +{%- endif %} + suit-install: - suit-directive-set-component-index: 1 - suit-directive-override-parameters: diff --git a/ncs/build.py b/ncs/build.py index 761bf9bb..c523f650 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -15,6 +15,7 @@ from jinja2 import Template from argparse import ArgumentParser +from configparser import ConfigParser sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) @@ -74,6 +75,16 @@ def read_configurations(configurations): return data +def read_version_file(version_file): + """Read values from the VERSION configuration file.""" + with open(version_file, "r") as ver_values: + cfg = ConfigParser() + cfg.optionxform = lambda option: option + cfg.read_string("[VERSION]\n" + ver_values.read()) + return cfg.items("VERSION") + return {} + + def render_template(template_location, data): """Render template using passed data.""" with open(template_location) as template_file: @@ -119,6 +130,9 @@ def get_absolute_address(node, use_offset: bool = True): cmd_template_arg_parser.add_argument("--artifacts-folder", required=True, help="Output artifact folder.") cmd_template_arg_parser.add_argument("--template-suit", required=True, help="Input SUIT jinja2 template.") cmd_template_arg_parser.add_argument("--output-suit", required=True, help="Output SUIT configuration.") + cmd_template_arg_parser.add_argument( + "--version_file", required=False, default=None, help="Path to the VERSION file to use." + ) cmd_storage_arg_parser.add_argument( "--input-envelope", required=True, action="append", help="Location of input envelope(s)." @@ -178,6 +192,8 @@ def get_absolute_address(node, use_offset: bool = True): configuration = read_configurations(arguments.core) if arguments.command == TEMPLATE_CMD: + if arguments.version_file is not None: + configuration.update(read_version_file(arguments.version_file)) configuration["output_envelope"] = arguments.output_suit configuration["artifacts_folder"] = arguments.artifacts_folder output_suit_content = render_template(arguments.template_suit, configuration) diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index 6146a7e5..a866dbd5 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -1,11 +1,14 @@ -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: suit-digest-algorithm-id: cose-alg-sha-256 suit-manifest: suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} +{%- if NORDIC_TOP_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ NORDIC_TOP_SEQ_NUM }} +{%- else %} + suit-manifest-sequence-number: 1 +{%- endif %} suit-common: suit-components: - - CAND_MFST @@ -89,6 +92,10 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure +{%- if NORDIC_TOP_VERSION is defined %} + suit-current-version: {{ NORDIC_TOP_VERSION }} +{%- endif %} + suit-install: - suit-directive-set-component-index: 0 - suit-directive-override-parameters: diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index aa6412e7..ee52f8b5 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -6,7 +6,6 @@ {%- set mpi_app_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} {%- set mpi_rad_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} {%- set mpi_rad_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} -{%- set sequence_number = sysbuild['config']['SB_CONFIG_SUIT_ENVELOPE_SEQUENCE_NUM'] %} {%- if hci_rpmsg_subimage is defined %} {% set rad = hci_rpmsg_subimage %} {%- elif _802154_rpmsg_subimage is defined %} @@ -20,7 +19,11 @@ SUIT_Envelope_Tagged: suit-digest-algorithm-id: cose-alg-sha-256 suit-manifest: suit-manifest-version: 1 - suit-manifest-sequence-number: {{ sequence_number }} +{%- if APP_ROOT_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ APP_ROOT_SEQ_NUM }} +{%- else %} + suit-manifest-sequence-number: 1 +{%- endif %} suit-common: suit-components: - - CAND_MFST @@ -106,6 +109,10 @@ SUIT_Envelope_Tagged: - suit-send-sysinfo-success - suit-send-sysinfo-failure +{%- if APP_ROOT_VERSION is defined %} + suit-current-version: {{ APP_ROOT_VERSION }} +{%- endif %} + suit-install: - suit-directive-set-component-index: 0 {%- if radio is defined %} diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 77edb91a..995adab4 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -7,8 +7,10 @@ Code inspired by/based on https://github.com/tomchy/suit-composer. """ +from enum import Enum import uuid from os.path import getsize +from typing import List, Union from suit_generator.suit.types.common import ( SuitInt, @@ -105,6 +107,7 @@ suit_uninstall, suit_text, ) +from suit_generator.logger import log_call class SuitIndex(SuitUnion): @@ -217,6 +220,35 @@ class SuitComponentVersion(SuitList): _metadata = Metadata(children=[SuitInt]) + @staticmethod + def _convert_version_part(part): + class PrereleaseType(Enum): + alpha = -3 + beta = -2 + rc = -1 + + if isinstance(part, str): + if part.isnumeric(): + return int(part) + else: + try: + prerelease = getattr(PrereleaseType, part) + return prerelease.value + except AttributeError: + raise ValueError(f"Unsupported prerelease type: {part}") + elif isinstance(part, int): + return part + else: + raise ValueError(f"Unsupported version part: {part}") + + @classmethod + @log_call + def from_obj(cls, obj: Union[List[int], str]) -> SuitList: + """Restore SUIT representation from passed object.""" + if isinstance(obj, str): + obj = [cls._convert_version_part(part) for part in obj.replace("-", ".").split(".")] + return super().from_obj(obj) + class SuitParameterVersion(SuitKeyValueTuple): """Representation of SUIT version parameter.""" From 97ef5bdd41716aa7fa21a0f0c149a66d1f91ab8d Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Tue, 20 Aug 2024 16:50:03 +0200 Subject: [PATCH 42/67] build: Add a support for default versions Add a possibility to use Zephyr-defined version to calculate manifest sequence number as well as manifest current (semantic) version. Ref: NCSDK-28657 Signed-off-by: Tomasz Chyrowicz --- ncs/app_envelope_encrypted.yaml.jinja2 | 4 ++ ncs/build.py | 37 +++++++++++++++++++ ncs/nordic_top_envelope.yaml.jinja2 | 4 ++ ncs/root_with_nordic_top_envelope.yaml.jinja2 | 4 ++ 4 files changed, 49 insertions(+) diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 index b94eed09..69a20103 100644 --- a/ncs/app_envelope_encrypted.yaml.jinja2 +++ b/ncs/app_envelope_encrypted.yaml.jinja2 @@ -8,6 +8,8 @@ SUIT_Envelope_Tagged: suit-manifest-version: 1 {%- if APP_LOCAL_1_SEQ_NUM is defined %} suit-manifest-sequence-number: {{ APP_LOCAL_1_SEQ_NUM }} +{%- elif DEFAULT_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ DEFAULT_SEQ_NUM }} {%- else %} suit-manifest-sequence-number: 1 {%- endif %} @@ -68,6 +70,8 @@ SUIT_Envelope_Tagged: {%- if APP_LOCAL_1_VERSION is defined %} suit-current-version: {{ APP_LOCAL_1_VERSION }} +{%- elif DEFAULT_VERSION is defined %} + suit-current-version: {{ DEFAULT_VERSION }} {%- endif %} suit-install: diff --git a/ncs/build.py b/ncs/build.py index c523f650..ce14f307 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -75,12 +75,49 @@ def read_configurations(configurations): return data +def append_default_version_values(cfg): + """Generate DEFAULT_SEQ_NUM and DEFAULT_VERSION variables.""" + version = cfg["VERSION"] + if "APP_ROOT_VERSION" in version: + default_version = version["APP_ROOT_VERSION"] + elif ("VERSION_MAJOR" in version) and ("VERSION_MINOR" in version) and ("PATCHLEVEL" in version): + default_version = version["VERSION_MAJOR"] + "." + version["VERSION_MINOR"] + "." + version["PATCHLEVEL"] + if "EXTRAVERSION" in version: + if version["EXTRAVERSION"] in ("alpha", "beta", "rc"): + default_version += "-" + version["EXTRAVERSION"] + elif len(version["EXTRAVERSION"]) > 0: + # Use the least important pre-release tag for unsupported values + default_version += "-alpha" + else: + default_version = None + + if "APP_ROOT_SEQ_NUM" in version: + default_seq_num = version["APP_ROOT_SEQ_NUM"] + elif ("VERSION_MAJOR" in version) and ("VERSION_MINOR" in version) and ("PATCHLEVEL" in version): + default_seq_num = ( + (int(version["VERSION_MAJOR"]) << 24) + + (int(version["VERSION_MINOR"]) << 16) + + (int(version["PATCHLEVEL"]) << 8) + ) + if "VERSION_TWEAK" in version: + default_seq_num += int(version["VERSION_TWEAK"]) + else: + default_seq_num = 1 + + if "DEFAULT_VERSION" not in version: + if default_version is not None: + cfg["VERSION"]["DEFAULT_VERSION"] = default_version + if "DEFAULT_SEQ_NUM" not in version: + cfg["VERSION"]["DEFAULT_SEQ_NUM"] = f"{default_seq_num}" + + def read_version_file(version_file): """Read values from the VERSION configuration file.""" with open(version_file, "r") as ver_values: cfg = ConfigParser() cfg.optionxform = lambda option: option cfg.read_string("[VERSION]\n" + ver_values.read()) + append_default_version_values(cfg) return cfg.items("VERSION") return {} diff --git a/ncs/nordic_top_envelope.yaml.jinja2 b/ncs/nordic_top_envelope.yaml.jinja2 index a866dbd5..fe64e43a 100644 --- a/ncs/nordic_top_envelope.yaml.jinja2 +++ b/ncs/nordic_top_envelope.yaml.jinja2 @@ -6,6 +6,8 @@ SUIT_Envelope_Tagged: suit-manifest-version: 1 {%- if NORDIC_TOP_SEQ_NUM is defined %} suit-manifest-sequence-number: {{ NORDIC_TOP_SEQ_NUM }} +{%- elif DEFAULT_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ DEFAULT_SEQ_NUM }} {%- else %} suit-manifest-sequence-number: 1 {%- endif %} @@ -94,6 +96,8 @@ SUIT_Envelope_Tagged: {%- if NORDIC_TOP_VERSION is defined %} suit-current-version: {{ NORDIC_TOP_VERSION }} +{%- elif DEFAULT_VERSION is defined %} + suit-current-version: {{ DEFAULT_VERSION }} {%- endif %} suit-install: diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index ee52f8b5..f9aea4d4 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -21,6 +21,8 @@ SUIT_Envelope_Tagged: suit-manifest-version: 1 {%- if APP_ROOT_SEQ_NUM is defined %} suit-manifest-sequence-number: {{ APP_ROOT_SEQ_NUM }} +{%- elif DEFAULT_SEQ_NUM is defined %} + suit-manifest-sequence-number: {{ DEFAULT_SEQ_NUM }} {%- else %} suit-manifest-sequence-number: 1 {%- endif %} @@ -111,6 +113,8 @@ SUIT_Envelope_Tagged: {%- if APP_ROOT_VERSION is defined %} suit-current-version: {{ APP_ROOT_VERSION }} +{%- elif DEFAULT_VERSION is defined %} + suit-current-version: {{ DEFAULT_VERSION }} {%- endif %} suit-install: From ad33f06f219e2e353332136e1b2c285c2ea0bba4 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 20 Aug 2024 15:37:10 +0200 Subject: [PATCH 43/67] Added "direct" kdf alg in suit-generator Ref: NCSDK-28784 Signed-off-by: Artur Hadasz --- suit_generator/suit/security.py | 2 ++ suit_generator/suit/types/keys.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index d6db652e..6ff87bd6 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -54,6 +54,7 @@ cose_alg_a256kw, cose_alg_a192kw, cose_alg_a128kw, + cose_alg_direct, suit_digest_algorithm_id, suit_digest_bytes, ) @@ -189,6 +190,7 @@ class SuitcoseAlg(SuitEnum): cose_alg_a256kw, cose_alg_a192kw, cose_alg_a128kw, + cose_alg_direct, ] ) diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 57b52d32..0b9f053a 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -728,6 +728,12 @@ class cose_alg_a128kw(suit_key): id = -3 name = "cose-alg-a128kw" +class cose_alg_direct(suit_key): + """Cose algorithm metadata.""" + + id = -6 + name = "cose-alg-direct" + class suit_send_record_success(suit_key): """Reporting policy bit.""" From cea206ca62f8d2da76709905da3af45c16002537 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 20 Aug 2024 16:40:12 +0200 Subject: [PATCH 44/67] Added a "direct" kw option (no AES KW) to encrypt_script.py Ref: NCSDK-28784 Signed-off-by: Artur Hadasz --- ncs/encrypt_script.py | 84 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index 00522d57..10fb2426 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -29,6 +29,7 @@ class SuitAlgorithms(Enum): COSE_ALG_A128KW = -3 COSE_ALG_A192KW = -4 COSE_ALG_A256KW = -5 + COSE_ALG_DIRECT = -6 class SuitIds(Enum): @@ -63,6 +64,15 @@ class SuitDigestAlgorithms(Enum): def __str__(self): return self.value +class SuitKWAlgorithms(Enum): + """Supported SUIT Key wrap/derivation algorithms.""" + + A256KW = "aes-kw-256" + DIRECT = "direct" + + def __str__(self): + return self.value + class EncryptionKMSBackends(Enum): """KMS backends.""" @@ -115,6 +125,13 @@ def generate_digest_size_for_plain_text(self, plaintext_file_path: Path, output_ class Encryptor: kms = None + def __init__(self, kw_alg: SuitKWAlgorithms): + if kw_alg == SuitKWAlgorithms.A256KW: + self.cose_kw_alg = SuitAlgorithms.COSE_ALG_A256KW.value + else: + self.cose_kw_alg = SuitAlgorithms.COSE_ALG_DIRECT.value + pass + def init_kms_backend(self, cli_arguments): if cli_arguments.kms_backend == EncryptionKMSBackends.VAULT: self.kms = KMS(backend="vault", url=cli_arguments.kms_vault_url, token=cli_arguments.kms_token) @@ -141,22 +158,39 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte with open(plaintext_file_path, "rb") as plaintext_file: asset_plaintext = plaintext_file.read() - encrypted_asset, encrypted_cek = self.kms.aes_key_wrap( - key_name=key_name, - context=context, - plaintext=asset_plaintext, - aek_type="aes", - aad=enc_structure_encoded, - ) + encrypted_asset = None + encrypted_cek = None + + if (self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value): + encrypted_asset, encrypted_cek = self.kms.aes_key_wrap( + key_name=key_name, + context=context, + plaintext=asset_plaintext, + aek_type="aes", + aad=enc_structure_encoded, + ) + elif (self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value): + encrypted_asset = self.kms.encrypt( + plaintext=asset_plaintext, + key_name=key_name, + context=context, + aad=enc_structure_encoded, + ) + return encrypted_asset, encrypted_cek def parse_encrypted_assets(self, asset_bytes): - init_vector = asset_bytes[:12] # the names init vector and nonce are used interchangeably in this case - # NOTE - it is not yet clear if this is a temporary bug or a difference in format, - # but nrfkms wrap returns the encrypted data in format nonce|encrypted_data|tag, instead of nonce|tag|encrypted_data - # which is returned by nrfkms encrypt - encrypted_content = asset_bytes[12:-16] - tag = asset_bytes[-16:] + if self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value: + init_vector = asset_bytes[:12] # the names init vector and nonce are used interchangeably in this case + # nrfkms wrap returns the encrypted data in format nonce|encrypted_data|tag + encrypted_content = asset_bytes[12:-16] + tag = asset_bytes[-16:] + elif self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value: + # nrfkms encrypt returns the encrypted data in format nonce|tag|encrypted_data + init_vector = asset_bytes[:12] + tag = asset_bytes[12:12+16] + encrypted_content = asset_bytes[12+16:] + return init_vector, tag, encrypted_content def generate_encrypted_payload(self, encrypted_content, tag, output_directory: Path): @@ -185,7 +219,7 @@ def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_direct b"", # unprotected { - SuitIds.COSE_ALG.value: SuitAlgorithms.COSE_ALG_A256KW.value, + SuitIds.COSE_ALG.value: self.cose_kw_alg, SuitIds.COSE_KEY_ID.value: cbor2.dumps(KEY_IDS[domain]), }, # ciphertext @@ -236,6 +270,13 @@ def create_encrypt_and_generate_subparser(top_parser): choices=list(SuitDigestAlgorithms), help="Algorithm used to create plaintext digest.", ) + parser.add_argument( + "--kw-alg", + default=SuitKWAlgorithms.DIRECT.value, + type=SuitKWAlgorithms, + choices=list(SuitKWAlgorithms), + help="Key wrap algorithm used to wrap the CEK.", + ) parser.add_argument( "--kms-backend", required=True, @@ -260,7 +301,8 @@ def create_generate_subparser(top_parser): "--encrypted-firmware", required=True, type=Path, - help="Input, encrypted firmware in form iv|tag|encrypted_firmware", + help="Input, encrypted firmware in form iv|tag|encrypted_firmware for kw-alg=direct (as output from nrfkms encrypt)" + + "or iv|encrypted_firmware|tag for kw-alg=aes-kw256 (as output from nrfkms wrap).", ) parser.add_argument("--encrypted-key", required=True, type=Path, help="Encrypted content/asset encryption key") parser.add_argument( @@ -270,6 +312,13 @@ def create_generate_subparser(top_parser): choices=list(SuitDomains), help="The SoC domain of the firmware. Used to determine the key ID.", ) + parser.add_argument( + "--kw-alg", + default=SuitKWAlgorithms.DIRECT.value, + type=SuitKWAlgorithms, + choices=list(SuitKWAlgorithms), + help="Key wrap algorithm used to wrap the CEK.", + ) parser.add_argument("--output-dir", required=True, type=Path, help="Directory to store the output files") @@ -287,7 +336,8 @@ def create_subparsers(parser): It has two modes of operation: - encrypt-and-generate: First encrypt the command using nrfkms, then generate the files. - generate: Only generate files based on encrypted firmware and the encrypted content/asset encryption key. - Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware. + Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware if kw-alg=direct is used + (format output from nrfkms encrypt) or iv|encrypted_firmware|tag if kw-alg=aes-kw256 is used (format output from nrfkms wrap). In both cases the output files are: encrypted_content.bin - encrypted content of the firmware concatenated with the tag (encrypted firmware|16 byte tag). @@ -308,7 +358,7 @@ def create_subparsers(parser): encrypted_asset = None encrypted_cek = None - encryptor = Encryptor() + encryptor = Encryptor(arguments.kw_alg) if arguments.command == "encrypt-and-generate": encryptor.init_kms_backend(arguments) From 3f2b8ddf9ab4280670ca39ecb3de7ea3861142c7 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 27 Aug 2024 10:31:28 +0200 Subject: [PATCH 45/67] encrypt_script.py: Fixed KEY_IDS MKEK key IDs where incorrectly used, while FWENC key IDs should have been used. Ref: NCSDK-28784 Signed-off-by: Artur Hadasz --- ncs/encrypt_script.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index 10fb2426..87ef7fa6 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -85,10 +85,10 @@ def __str__(self): KEY_IDS = { - SuitDomains.APPLICATION.value: 0x40020200, - SuitDomains.RADIO.value: 0x40030200, - SuitDomains.CELL.value: 0x40040200, - SuitDomains.WIFI.value: 0x40060200, + SuitDomains.APPLICATION.value: 0x40022000, + SuitDomains.RADIO.value: 0x40032000, + SuitDomains.CELL.value: 0x40042000, + SuitDomains.WIFI.value: 0x40062000, } From 4aae1db6987706bba5f50555353206ebbdb76ddc Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 26 Aug 2024 14:48:31 +0200 Subject: [PATCH 46/67] Extended suit-generator with cache_create command The command allows to create a raw DFU cache structure which can then be directly flashed to the devices SUIT DFU cache. Ref: NCSDK-28880 Signed-off-by: Artur Hadasz --- suit_generator/args.py | 3 +- suit_generator/cli.py | 3 +- suit_generator/cmd_cache_create.py | 96 ++++++++++++++++++++++++++++++ tests/test_cmd_cache_create.py | 89 +++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 suit_generator/cmd_cache_create.py create mode 100644 tests/test_cmd_cache_create.py diff --git a/suit_generator/args.py b/suit_generator/args.py index f58964b4..f6064297 100644 --- a/suit_generator/args.py +++ b/suit_generator/args.py @@ -15,7 +15,7 @@ from suit_generator.cmd_image import add_arguments as image_args from suit_generator.cmd_convert import add_arguments as convert_args from suit_generator.cmd_mpi import add_arguments as mpi_args - +from suit_generator.cmd_cache_create import add_arguments as cache_create_args def _parser() -> ArgumentParser: parser = ArgumentParser() @@ -28,6 +28,7 @@ def _parser() -> ArgumentParser: image_args(subparsers) convert_args(subparsers) mpi_args(subparsers) + cache_create_args(subparsers) return parser diff --git a/suit_generator/cli.py b/suit_generator/cli.py index 2586d02b..52b95217 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -11,7 +11,7 @@ sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) -from suit_generator import cmd_parse, cmd_keys, cmd_convert, cmd_create, cmd_image, cmd_mpi, args +from suit_generator import cmd_parse, cmd_keys, cmd_convert, cmd_create, cmd_image, cmd_mpi, cmd_cache_create, args from suit_generator.exceptions import GeneratorError, SUITError import logging @@ -29,6 +29,7 @@ cmd_convert.CONVERT_CMD: cmd_convert.main, cmd_image.ImageCreator.IMAGE_CMD: cmd_image.main, cmd_mpi.MPI_CMD: cmd_mpi.main, + cmd_cache_create.CACHE_CREATE_CMD: cmd_cache_create.main } diff --git a/suit_generator/cmd_cache_create.py b/suit_generator/cmd_cache_create.py new file mode 100644 index 00000000..8bdc04c8 --- /dev/null +++ b/suit_generator/cmd_cache_create.py @@ -0,0 +1,96 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""CMD_CACHE_CREATE CLI command entry point.""" + +import logging +import cbor2 +import math + +log = logging.getLogger(__name__) + +CACHE_CREATE_CMD = "cache_create" + +def add_arguments(parser): + """Add additional arguments to the passed parser.""" + cmd_cache_create_arg_parser = parser.add_parser(CACHE_CREATE_CMD, help="Create raw cache structure.") + + cmd_cache_create_arg_parser.add_argument( + "--input", required=True, action="append", + help="Input binary with corresponding URI, passed in format ,." + + "Multiple inputs can be passed." + ) + cmd_cache_create_arg_parser.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") + cmd_cache_create_arg_parser.add_argument("--eb-size", type=int, help="Erase block size in bytes (used for padding).", + default=16) + + +def add_padding(data: bytes, eb_size: int) -> bytes: + data_len = len(data) + + rounded_up_size = math.ceil(len(data) / eb_size) * eb_size + padding_size = rounded_up_size - len(data) + padded_data = data + + # minimum padding size is 2 bytes + if padding_size == 1: + padding_size += eb_size + rounded_up_size += eb_size + + if padding_size == 0: + return data + + padded_data += bytes([0x60]) + + if padding_size <= 23: + header_len = 2 + padded_data += bytes([0x40 + (padding_size - header_len)]) + elif padding_size <= 0xFFFF: + header_len = 4 + padded_data += bytes([0x59]) + (padding_size - header_len).to_bytes(2, byteorder='big') + else: + raise ValueError("Number of required padding bytes exceeds assumed max size 0xFFFF") + + return padded_data.ljust(rounded_up_size, b'\x00') + +def main(input: list[str], output_file: str, eb_size: int) -> None: + # """Create raw SUIT DFU Cache. + + # :param input: list of input files with corresponding URIs in format , + # :param output_file: output file path + + cache_data = bytes() + first_slot = True + + for single_input in input: + args = single_input.split(",") + if len(args) < 2: + raise ValueError("Invalid number of input arguments: " + single_input) + uri, input_file = args + + data = None + with open(input_file, "rb") as f: + data = f.read() + + slot_data = bytes() + if first_slot: + # Open the cache - it is an indefinite length CBOR map (0xBF) + slot_data = bytes([0xBF]) + first_slot = False + + # uri as key + slot_data += cbor2.dumps(uri) + + # Size must be encoded in 4 bytes, thus cannot use cbor2.dumps + slot_data += bytes([0x5A]) + len(data).to_bytes(4, byteorder='big') + data + # Add padding for single slot + slot_data = add_padding(slot_data, eb_size) + + cache_data += slot_data + + cache_data += bytes([0xFF]) # Finish the indefinite length map + + with open(output_file, "wb") as f: + f.write(cache_data) diff --git a/tests/test_cmd_cache_create.py b/tests/test_cmd_cache_create.py new file mode 100644 index 00000000..2cba0d70 --- /dev/null +++ b/tests/test_cmd_cache_create.py @@ -0,0 +1,89 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""Unit tests for cmd_cache_create.py implementation.""" + +import pytest +import pathlib +import os +from suit_generator.cmd_cache_create import main as cmd_cache_create_main + +TEMP_DIRECTORY = pathlib.Path("test_test_data") + +BINARY_CONTENT_1 = bytes([0x01, 0x02, 0x03, 0x04]) +BINARY_CONTENT_2 = bytes([0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11]) + +URI1 = "#first" # [0x23, 0x66, 0x69, 0x72, 0x73, 0x74] +URI2 = "#second" # [0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64] + +EXPECTED_CACHE_EB_8 = bytes([0xBF, + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 + 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 + 0x60, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 + 0x60, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + 0xFF + ]) + +EXPECTED_CACHE_EB_64 = bytes([0xBF, + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 + 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 + 0x60, 0x59, 0x00, 0x2B, # padding 47 bytes (4 bytes header + 43 bytes padding) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 + 0x60, 0x59, 0x00, 0x25, # padding 41 bytes (4 bytes header + 37 bytes padding) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF + ]) + +@pytest.fixture +def setup_and_teardown(tmp_path_factory): + """Create and cleanup environment.""" + # Setup environment + # - create temp directory + # - input files + start_directory = os.getcwd() + path = tmp_path_factory.mktemp(TEMP_DIRECTORY) + os.chdir(path) + with open("first.bin", "wb") as fh: + fh.write(BINARY_CONTENT_1) + with open("second.bin", "wb") as fh: + fh.write(BINARY_CONTENT_2) + + yield + # Cleanup environment + # - remove temp directory + os.chdir(start_directory) + + +@pytest.mark.parametrize( + "eb_size, output_content", + [ + (8, EXPECTED_CACHE_EB_8), + (64, EXPECTED_CACHE_EB_64), + ], +) +def test_cache_create(setup_and_teardown, eb_size, output_content): + """Verify if is possible to create binary envelope from json input.""" + + cmd_cache_create_main(input=[f"{URI1},first.bin", f"{URI2},second.bin"], output_file="test_cache.bin", eb_size=eb_size) + + with open("test_cache.bin", "rb") as f: + assert f.read() == output_content From 66131a13bf92261c43f647d1b86267cca4e60a32 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Wed, 28 Aug 2024 13:07:59 +0200 Subject: [PATCH 47/67] Kconfig options needed for extracting firmware to raw caches Ref: NCSDK-28881 Signed-off-by: Artur Hadasz --- ncs/Kconfig | 24 ++++++++++++++++++++++++ ncs/app_envelope_encrypted.yaml.jinja2 | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/ncs/Kconfig b/ncs/Kconfig index 32e4e441..c41f741e 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -29,3 +29,27 @@ config SUIT_ENVELOPE_OUTPUT_MPI_MERGE config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD + +config SUIT_DFU_CACHE_EXTRACT_IMAGE + bool "Extract firmware image to DFU cache" + help + Extracts the firmware image to a DFU cache file, which can be then flashed separately + to the device (instead of being integrated into the SUIT envelope). If using the default + SUIT envelope template, this will also remove the firmware image from the SUIT envelope + integrated payloads. + +if SUIT_DFU_CACHE_EXTRACT_IMAGE + +config SUIT_DFU_CACHE_EXTRACT_IMAGE_PARTITION + int "The number of the DFU partition to which the image will be extracted" + help + This option will ensure that images which set it to the same number will be extracted + to the same dfu cache file. + default 1 + +config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI + string "The URI used as key for the image in the DFU cache" + default "cache://application.bin" if SOC_NRF54H20_CPUAPP + default "cache://radio.bin" if SOC_NRF54H20_CPURAD + +endif # SUIT_DFU_CACHE_EXTRACT_IMAGE diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 index 69a20103..5b02951f 100644 --- a/ncs/app_envelope_encrypted.yaml.jinja2 +++ b/ncs/app_envelope_encrypted.yaml.jinja2 @@ -77,7 +77,11 @@ SUIT_Envelope_Tagged: suit-install: - suit-directive-set-component-index: 1 - suit-directive-override-parameters: +{%- if 'CONFIG_SUIT_IMAGE_DFU_CACHE_URI' in application['config'] and application['config']['CONFIG_SUIT_IMAGE_DFU_CACHE_URI'] != '' %} + suit-parameter-uri: '{{ application['config']['CONFIG_SUIT_IMAGE_DFU_CACHE_URI'] }}' +{%- else %} suit-parameter-uri: '#{{ application['name'] }}' +{%- endif %} suit-parameter-image-digest: suit-digest-algorithm-id: cose-alg-sha-256 suit-digest-bytes: @@ -160,5 +164,7 @@ SUIT_Envelope_Tagged: suit-text-model-info: The nRF54H20 application core suit-text-component-description: Sample application core encrypted FW suit-text-component-version: v1.0.0 +{%- if 'CONFIG_SUIT_EXTRACT_IMAGE_TO_DFU_CACHE' not in application['config'] or application['config']['CONFIG_SUIT_EXTRACT_IMAGE_TO_DFU_CACHE'] == '' %} suit-integrated-payloads: '#{{ application['name'] }}': {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin +{%- endif %} From 4372d27e940798cb596d18d90affa9d62d9d2564 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 3 Sep 2024 11:39:32 +0200 Subject: [PATCH 48/67] Fix indentation in ncs/Kconfig The indents should be tabs (for help: tab + 2 spaces) Ref: NCSDK-28881 Signed-off-by: Artur Hadasz --- ncs/Kconfig | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index c41f741e..a1f3204c 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -6,50 +6,50 @@ # Kconfig below is slated for removal once SUIT service is available in the NCS. config SSF_SUIT_SERVICE_ENABLED - bool + bool config SUIT_ENVELOPE_TEMPLATE_FILENAME - string "Path to the envelope template" - default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP - default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD + string "Path to the envelope template" + default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP + default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD config SUIT_ENVELOPE_TARGET - string "Target name inside the envelope templates" - default "application" if SOC_NRF54H20_CPUAPP - default "radio" if SOC_NRF54H20_CPURAD + string "Target name inside the envelope templates" + default "application" if SOC_NRF54H20_CPUAPP + default "radio" if SOC_NRF54H20_CPURAD config SUIT_ENVELOPE_OUTPUT_ARTIFACT - string "Name of the output merged artifact" - default "merged.hex" + string "Name of the output merged artifact" + default "merged.hex" config SUIT_ENVELOPE_OUTPUT_MPI_MERGE - bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" - default y + bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" + default y config SUIT_LOCAL_ENVELOPE_GENERATE - bool "Generate local envelope" - default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD + bool "Generate local envelope" + default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD config SUIT_DFU_CACHE_EXTRACT_IMAGE - bool "Extract firmware image to DFU cache" - help - Extracts the firmware image to a DFU cache file, which can be then flashed separately - to the device (instead of being integrated into the SUIT envelope). If using the default - SUIT envelope template, this will also remove the firmware image from the SUIT envelope - integrated payloads. + bool "Extract firmware image to DFU cache" + help + Extracts the firmware image to a DFU cache file, which can be then flashed separately + to the device (instead of being integrated into the SUIT envelope). If using the default + SUIT envelope template, this will also remove the firmware image from the SUIT envelope + integrated payloads. if SUIT_DFU_CACHE_EXTRACT_IMAGE config SUIT_DFU_CACHE_EXTRACT_IMAGE_PARTITION - int "The number of the DFU partition to which the image will be extracted" - help - This option will ensure that images which set it to the same number will be extracted - to the same dfu cache file. - default 1 + int "The number of the DFU partition to which the image will be extracted" + help + This option will ensure that images which set it to the same number will be extracted + to the same dfu cache file. + default 1 config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI - string "The URI used as key for the image in the DFU cache" - default "cache://application.bin" if SOC_NRF54H20_CPUAPP - default "cache://radio.bin" if SOC_NRF54H20_CPURAD + string "The URI used as key for the image in the DFU cache" + default "cache://application.bin" if SOC_NRF54H20_CPUAPP + default "cache://radio.bin" if SOC_NRF54H20_CPURAD endif # SUIT_DFU_CACHE_EXTRACT_IMAGE From f4cd00b541fbe7c844814a8983c9462dde66f1ca Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Fri, 6 Sep 2024 12:19:27 +0200 Subject: [PATCH 49/67] build.py: Add handlers for SCFW version file SCFW uses dedicated VERSION file with different schema, so it is necessary to add dedicated handlers for it. Ref: NCSDK-28945 Signed-off-by: Tomasz Chyrowicz --- ncs/build.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ncs/build.py b/ncs/build.py index ce14f307..c6f6f1c1 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -110,6 +110,32 @@ def append_default_version_values(cfg): if "DEFAULT_SEQ_NUM" not in version: cfg["VERSION"]["DEFAULT_SEQ_NUM"] = f"{default_seq_num}" + # Handle SCFW versioning schema - it is customized by overwriting the Zephyr's version.cmake file. + if ("SYSCTRL_VERSION_MAJOR" in version) and ("SYSCTRL_VERSION_MINOR" in version) and ("SYSCTRL_VERSION_PATCH" in version): + default_scfw_version = version["SYSCTRL_VERSION_MAJOR"] + "." + version["SYSCTRL_VERSION_MINOR"] + "." + version["SYSCTRL_VERSION_PATCH"] + if "SYSCTRL_VERSION_EXTRA" in version: + if version["SYSCTRL_VERSION_EXTRA"] in ("alpha", "beta", "rc"): + default_scfw_version += "-" + version["SYSCTRL_VERSION_EXTRA"] + elif len(version["SYSCTRL_VERSION_EXTRA"]) > 0: + # Use the least important pre-release tag for unsupported values + default_scfw_version += "-alpha" + + default_scfw_seq_num = ( + (int(version["SYSCTRL_VERSION_MAJOR"]) << 24) + + (int(version["SYSCTRL_VERSION_MINOR"]) << 16) + + (int(version["SYSCTRL_VERSION_PATCH"]) << 8) + ) + if "SYSCTRL_VERSION_TWEAK" in version: + default_scfw_seq_num += int(version["SYSCTRL_VERSION_TWEAK"]) + else: + default_scfw_version = None + default_scfw_seq_num = 1 + + if "SCFW_VERSION" not in version: + if default_scfw_version is not None: + cfg["VERSION"]["SCFW_VERSION"] = default_scfw_version + if "SCFW_SEQ_NUM" not in version: + cfg["VERSION"]["SCFW_SEQ_NUM"] = f"{default_scfw_seq_num}" def read_version_file(version_file): """Read values from the VERSION configuration file.""" From e4d1fa0c0dc23f62be05d98254e9d502baead8bd Mon Sep 17 00:00:00 2001 From: Robert Stypa <90257220+robertstypa@users.noreply.github.com> Date: Mon, 9 Sep 2024 05:41:53 +0200 Subject: [PATCH 50/67] refactor: alignments with Flake8, Black and Pydocstyle linters (#140) * refactor: alignments with Flake8, Black and Pydocstyle linters * refactor: enable static analysis for ncs branch Ref: None Signed-off-by: Robert Stypa --- .github/workflows/static_analysis.yml | 4 +- ncs/encrypt_script.py | 58 +++++++++++++++++++++------ suit_generator/args.py | 1 + suit_generator/cli.py | 2 +- suit_generator/cmd_cache_create.py | 53 +++++++++++++++++------- suit_generator/suit/manifest.py | 2 +- suit_generator/suit/security.py | 5 ++- suit_generator/suit/types/common.py | 2 + suit_generator/suit/types/keys.py | 1 + tests/test_cmd_cache_create.py | 49 +++++++++++----------- 10 files changed, 121 insertions(+), 56 deletions(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index f810dbb9..bf09d825 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -2,9 +2,9 @@ name: Code scan on: push: - branches: [ main, develop ] + branches: [ main, develop, ncs ] pull_request: - branches: [ main, develop ] + branches: [ main, develop, ncs ] jobs: code-check: diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index 87ef7fa6..157feeed 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -3,9 +3,7 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -""" -This script allows to output artifacts needed by a SUIT envelope for encrypted firmware. -""" +"""Script to create artifacts needed by a SUIT envelope for encrypted firmware.""" import os import cbor2 @@ -64,6 +62,7 @@ class SuitDigestAlgorithms(Enum): def __str__(self): return self.value + class SuitKWAlgorithms(Enum): """Supported SUIT Key wrap/derivation algorithms.""" @@ -93,6 +92,7 @@ def __str__(self): class DigestGenerator: + """Class to generate digests for plaintext files using specified hash algorithms.""" _hash_func = { SuitDigestAlgorithms.SHA_256.value: hashes.SHA256(), @@ -109,6 +109,7 @@ def __init__(self, hash_name: str): self._hash_name = hash_name def generate_digest_size_for_plain_text(self, plaintext_file_path: Path, output_directory: Path): + """Class to generate digests for plaintext files using specified hash algorithms.""" plaintext = [] with open(plaintext_file_path, "rb") as plaintext_file: plaintext = plaintext_file.read() @@ -123,9 +124,12 @@ def generate_digest_size_for_plain_text(self, plaintext_file_path: Path, output_ class Encryptor: + """Class to handle encryption operations using specified key wrap algorithms.""" + kms = None def __init__(self, kw_alg: SuitKWAlgorithms): + """Initialize the Encryptor with a specified key wrap algorithm.""" if kw_alg == SuitKWAlgorithms.A256KW: self.cose_kw_alg = SuitAlgorithms.COSE_ALG_A256KW.value else: @@ -133,6 +137,7 @@ def __init__(self, kw_alg: SuitKWAlgorithms): pass def init_kms_backend(self, cli_arguments): + """Initialize the KMS backend based on command line arguments.""" if cli_arguments.kms_backend == EncryptionKMSBackends.VAULT: self.kms = KMS(backend="vault", url=cli_arguments.kms_vault_url, token=cli_arguments.kms_token) elif cli_arguments.kms_backend == EncryptionKMSBackends.LOCAL: @@ -143,6 +148,12 @@ def init_kms_backend(self, cli_arguments): del pswd def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, context: str): + """Generate encrypted artifacts using KMS. + + This method reads the plaintext file, encrypts it using the specified key wrap algorithm, + and returns the encrypted asset and encrypted content encryption key (CEK). + + """ # Enc structure: # { # "context": "Encrypt", @@ -161,7 +172,7 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte encrypted_asset = None encrypted_cek = None - if (self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value): + if self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value: encrypted_asset, encrypted_cek = self.kms.aes_key_wrap( key_name=key_name, context=context, @@ -169,7 +180,7 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte aek_type="aes", aad=enc_structure_encoded, ) - elif (self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value): + elif self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value: encrypted_asset = self.kms.encrypt( plaintext=asset_plaintext, key_name=key_name, @@ -180,6 +191,7 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte return encrypted_asset, encrypted_cek def parse_encrypted_assets(self, asset_bytes): + """Parse the encrypted assets to extract initialization vector, tag, and encrypted content.""" if self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value: init_vector = asset_bytes[:12] # the names init vector and nonce are used interchangeably in this case # nrfkms wrap returns the encrypted data in format nonce|encrypted_data|tag @@ -188,17 +200,24 @@ def parse_encrypted_assets(self, asset_bytes): elif self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value: # nrfkms encrypt returns the encrypted data in format nonce|tag|encrypted_data init_vector = asset_bytes[:12] - tag = asset_bytes[12:12+16] - encrypted_content = asset_bytes[12+16:] + tag = asset_bytes[12 : 12 + 16] + encrypted_content = asset_bytes[12 + 16 :] return init_vector, tag, encrypted_content def generate_encrypted_payload(self, encrypted_content, tag, output_directory: Path): + """Generate the encrypted payload file. + + This method writes the encrypted content and authentication tag to a binary file. + """ with open(os.path.join(output_directory, "encrypted_content.bin"), "wb") as file: file.write(tag + encrypted_content) def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_directory: Path): + """Generate the SUIT encryption information file. + This method creates a CBOR-encoded SUIT encryption information structure and writes it to a binary file. + """ Cose_Encrypt = [ # protected cbor2.dumps( @@ -237,12 +256,18 @@ def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_direct def generate_encryption_info_and_encrypted_payload( self, encrypted_asset: Path, encrypted_cek: Path, output_directory: Path, domain: str ): + """Generate encryption information and encrypted payload files. + + This method parses the encrypted asset to extract the initialization vector, tag, and encrypted content. + It then generates the encrypted payload file and the SUIT encryption information file. + """ init_vector, tag, encrypted_content = self.parse_encrypted_assets(encrypted_asset) self.generate_encrypted_payload(encrypted_content, tag, output_directory) self.generate_suit_encryption_info(init_vector, encrypted_cek, domain, output_directory) def create_encrypt_and_generate_subparser(top_parser): + """Create a subparser for the 'encrypt-and-generate' command.""" parser = top_parser.add_parser( "encrypt-and-generate", help="First encrypt the command using nrfkms, then generate the files." ) @@ -290,19 +315,22 @@ def create_encrypt_and_generate_subparser(top_parser): parser.add_argument( "--kms-local-password", type=str, - help='KMS local backend password - only if kms-backend set to "local". If not provided, the script will prompt for it.', + help='KMS local backend password - only if kms-backend set to "local". If not provided, ' + "the script will prompt for it.", ) def create_generate_subparser(top_parser): + """Create a subparser for the 'generate' command.""" parser = top_parser.add_parser("generate", help="Only generate files based on encrypted firmware") parser.add_argument( "--encrypted-firmware", required=True, type=Path, - help="Input, encrypted firmware in form iv|tag|encrypted_firmware for kw-alg=direct (as output from nrfkms encrypt)" + - "or iv|encrypted_firmware|tag for kw-alg=aes-kw256 (as output from nrfkms wrap).", + help="Input, encrypted firmware in form iv|tag|encrypted_firmware for kw-alg=direct " + "(as output from nrfkms encrypt) or iv|encrypted_firmware|tag for kw-alg=aes-kw256 " + "(as output from nrfkms wrap).", ) parser.add_argument("--encrypted-key", required=True, type=Path, help="Encrypted content/asset encryption key") parser.add_argument( @@ -323,6 +351,10 @@ def create_generate_subparser(top_parser): def create_subparsers(parser): + """Create subparsers for the main parser. + + This function adds subparsers for different commands to the main parser. + """ subparsers = parser.add_subparsers(dest="command", required=True, help="Choose subcommand:") create_encrypt_and_generate_subparser(subparsers) @@ -336,8 +368,8 @@ def create_subparsers(parser): It has two modes of operation: - encrypt-and-generate: First encrypt the command using nrfkms, then generate the files. - generate: Only generate files based on encrypted firmware and the encrypted content/asset encryption key. - Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware if kw-alg=direct is used - (format output from nrfkms encrypt) or iv|encrypted_firmware|tag if kw-alg=aes-kw256 is used (format output from nrfkms wrap). + Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware if + kw-alg=direct is used (format output from nrfkms encrypt) or iv|encrypted_firmware|tag if kw-alg=aes-kw256 is used (format output from nrfkms wrap). In both cases the output files are: encrypted_content.bin - encrypted content of the firmware concatenated with the tag (encrypted firmware|16 byte tag). @@ -347,7 +379,7 @@ def create_subparsers(parser): Additionally, the encrypt-and-generate mode generates the following file: plain_text_digest.bin - The digest of the plaintext firmware. plain_text_size.txt - The size of the plaintext firmware in bytes. - """, + """, # noqa: W291, E501 formatter_class=RawTextHelpFormatter, ) diff --git a/suit_generator/args.py b/suit_generator/args.py index f6064297..30b60206 100644 --- a/suit_generator/args.py +++ b/suit_generator/args.py @@ -17,6 +17,7 @@ from suit_generator.cmd_mpi import add_arguments as mpi_args from suit_generator.cmd_cache_create import add_arguments as cache_create_args + def _parser() -> ArgumentParser: parser = ArgumentParser() parser.add_argument("--log-filename", default=None, help="Log file path (it will override cli defaults).") diff --git a/suit_generator/cli.py b/suit_generator/cli.py index 52b95217..6e515825 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -29,7 +29,7 @@ cmd_convert.CONVERT_CMD: cmd_convert.main, cmd_image.ImageCreator.IMAGE_CMD: cmd_image.main, cmd_mpi.MPI_CMD: cmd_mpi.main, - cmd_cache_create.CACHE_CREATE_CMD: cmd_cache_create.main + cmd_cache_create.CACHE_CREATE_CMD: cmd_cache_create.main, } diff --git a/suit_generator/cmd_cache_create.py b/suit_generator/cmd_cache_create.py index 8bdc04c8..3b6feaa5 100644 --- a/suit_generator/cmd_cache_create.py +++ b/suit_generator/cmd_cache_create.py @@ -13,23 +13,38 @@ CACHE_CREATE_CMD = "cache_create" + def add_arguments(parser): """Add additional arguments to the passed parser.""" cmd_cache_create_arg_parser = parser.add_parser(CACHE_CREATE_CMD, help="Create raw cache structure.") cmd_cache_create_arg_parser.add_argument( - "--input", required=True, action="append", + "--input", + required=True, + action="append", help="Input binary with corresponding URI, passed in format ,." - + "Multiple inputs can be passed." + + "Multiple inputs can be passed.", ) cmd_cache_create_arg_parser.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") - cmd_cache_create_arg_parser.add_argument("--eb-size", type=int, help="Erase block size in bytes (used for padding).", - default=16) + cmd_cache_create_arg_parser.add_argument( + "--eb-size", type=int, help="Erase block size in bytes (used for padding).", default=16 + ) def add_padding(data: bytes, eb_size: int) -> bytes: - data_len = len(data) - + """ + Add padding to the given data to align it to the specified erase block size. + + This function ensures that the data is padded to a size that is a multiple of the erase block size (eb_size). + The padding is done by appending a CBOR key-value pair with empty URI as the key and + byte-string-encoded zeros as the value. + + :param data: The input data to be padded. + :type data: bytes + :param eb_size: The erase block size in bytes. + :type eb_size: int + :return: The padded data. + """ rounded_up_size = math.ceil(len(data) / eb_size) * eb_size padding_size = rounded_up_size - len(data) padded_data = data @@ -49,18 +64,28 @@ def add_padding(data: bytes, eb_size: int) -> bytes: padded_data += bytes([0x40 + (padding_size - header_len)]) elif padding_size <= 0xFFFF: header_len = 4 - padded_data += bytes([0x59]) + (padding_size - header_len).to_bytes(2, byteorder='big') + padded_data += bytes([0x59]) + (padding_size - header_len).to_bytes(2, byteorder="big") else: raise ValueError("Number of required padding bytes exceeds assumed max size 0xFFFF") - return padded_data.ljust(rounded_up_size, b'\x00') + return padded_data.ljust(rounded_up_size, b"\x00") -def main(input: list[str], output_file: str, eb_size: int) -> None: - # """Create raw SUIT DFU Cache. - - # :param input: list of input files with corresponding URIs in format , - # :param output_file: output file path +def main(input: list[str], output_file: str, eb_size: int) -> None: + """ + Create a raw SUIT DFU cache file from input binaries. + + This function processes a list of input binaries, each associated with a URI, and creates a raw SUIT DFU cache file. + The data is padded to align with the specified erase block size (eb_size). + + :param input: List of input binaries with corresponding URIs, passed in the format ,. + :type input: list[str] + :param output_file: Path to the output raw SUIT DFU cache file. + :type output_file: str + :param eb_size: Erase block size in bytes (used for padding). + :type eb_size: int + :return: None + """ cache_data = bytes() first_slot = True @@ -84,7 +109,7 @@ def main(input: list[str], output_file: str, eb_size: int) -> None: slot_data += cbor2.dumps(uri) # Size must be encoded in 4 bytes, thus cannot use cbor2.dumps - slot_data += bytes([0x5A]) + len(data).to_bytes(4, byteorder='big') + data + slot_data += bytes([0x5A]) + len(data).to_bytes(4, byteorder="big") + data # Add padding for single slot slot_data = add_padding(slot_data, eb_size) diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 995adab4..5ba54359 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -30,7 +30,7 @@ SuitBchar, cbstr, ) -from suit_generator.suit.security import CoseEncryptTagged, SuitDigest, SuitEncryptionInfo +from suit_generator.suit.security import SuitDigest, SuitEncryptionInfo from suit_generator.suit.types.keys import ( suit_parameter_vendor_identifier, suit_parameter_class_identifier, diff --git a/suit_generator/suit/security.py b/suit_generator/suit/security.py index 6ff87bd6..dd8687bf 100644 --- a/suit_generator/suit/security.py +++ b/suit_generator/suit/security.py @@ -12,7 +12,6 @@ SuitNull, SuitEnum, SuitInt, - SuitObject, SuitTstr, SuitHex, SuitUnion, @@ -415,6 +414,7 @@ class SuitEncryptionInfoExt(SuitBstr): @classmethod def to_obj(self) -> dict: + """Dump SUIT representation to object.""" raise ValueError("Encryption info should be expanded to full structure by to_obj method") @classmethod @@ -436,7 +436,8 @@ def from_obj(cls, obj: dict) -> SuitBstr: @classmethod def from_cbor(self) -> dict: - raise ValueError(f"Encryption info should be created as serialized CoseEncryptTagged object from cbor") + """Restore SUIT representation from passed CBOR string.""" + raise ValueError("Encryption info should be created as serialized CoseEncryptTagged object from cbor") class SuitEncryptionInfo(SuitUnion): diff --git a/suit_generator/suit/types/common.py b/suit_generator/suit/types/common.py index 808c3c4e..6b7ae807 100644 --- a/suit_generator/suit/types/common.py +++ b/suit_generator/suit/types/common.py @@ -661,6 +661,7 @@ def to_obj(self) -> str: class SuitEmptyBstr(SuitBstr): + """Representation of empty byte string.""" @classmethod def from_cbor(cls, cbstr: bytes) -> SuitBstr: @@ -670,6 +671,7 @@ def from_cbor(cls, cbstr: bytes) -> SuitBstr: return cls(cbstr) def to_cbor(self) -> bytes: + """Dump SUIT representation to cbor encoded bytes.""" return b"" diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 0b9f053a..8a7bf3d4 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -728,6 +728,7 @@ class cose_alg_a128kw(suit_key): id = -3 name = "cose-alg-a128kw" + class cose_alg_direct(suit_key): """Cose algorithm metadata.""" diff --git a/tests/test_cmd_cache_create.py b/tests/test_cmd_cache_create.py index 2cba0d70..b4c3b085 100644 --- a/tests/test_cmd_cache_create.py +++ b/tests/test_cmd_cache_create.py @@ -15,43 +15,44 @@ BINARY_CONTENT_1 = bytes([0x01, 0x02, 0x03, 0x04]) BINARY_CONTENT_2 = bytes([0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11]) -URI1 = "#first" # [0x23, 0x66, 0x69, 0x72, 0x73, 0x74] -URI2 = "#second" # [0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64] +URI1 = "#first" # [0x23, 0x66, 0x69, 0x72, 0x73, 0x74] +URI2 = "#second" # [0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64] +# fmt: off EXPECTED_CACHE_EB_8 = bytes([0xBF, - 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" - 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 - 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 - 0x60, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, # padding - 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" - 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 - 0x60, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding - 0xFF - ]) + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 + 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 + 0x60, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 + 0x60, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + 0xFF]) EXPECTED_CACHE_EB_64 = bytes([0xBF, - 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" - 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 - 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 - 0x60, 0x59, 0x00, 0x2B, # padding 47 bytes (4 bytes header + 43 bytes padding) + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 + 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 + 0x60, 0x59, 0x00, 0x2B, # padding 47 bytes (4 bytes header + 43 bytes padding) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" - 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 - 0x60, 0x59, 0x00, 0x25, # padding 41 bytes (4 bytes header + 37 bytes padding) + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 + 0x60, 0x59, 0x00, 0x25, # padding 41 bytes (4 bytes header + 37 bytes padding) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF - ]) + 0xFF]) +# fmt: on + @pytest.fixture def setup_and_teardown(tmp_path_factory): @@ -83,7 +84,9 @@ def setup_and_teardown(tmp_path_factory): def test_cache_create(setup_and_teardown, eb_size, output_content): """Verify if is possible to create binary envelope from json input.""" - cmd_cache_create_main(input=[f"{URI1},first.bin", f"{URI2},second.bin"], output_file="test_cache.bin", eb_size=eb_size) + cmd_cache_create_main( + input=[f"{URI1},first.bin", f"{URI2},second.bin"], output_file="test_cache.bin", eb_size=eb_size + ) with open("test_cache.bin", "rb") as f: assert f.read() == output_content From d1a37eca44dd3741f1e737b4f812a708daa46508 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Wed, 11 Sep 2024 12:02:25 +0200 Subject: [PATCH 51/67] build.py: Handle Zephyr notation of prerelease id Zephyr uses extraversion to pass both prerelease type and number. Ref: NCSDK-28945 Signed-off-by: Tomasz Chyrowicz --- ncs/build.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ncs/build.py b/ncs/build.py index c6f6f1c1..e747c6a9 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -77,14 +77,17 @@ def read_configurations(configurations): def append_default_version_values(cfg): """Generate DEFAULT_SEQ_NUM and DEFAULT_VERSION variables.""" + extraversion_re = r"^(alpha|beta|rc)[\.]{0,1}([0-9]+){0,1}$" version = cfg["VERSION"] + if "APP_ROOT_VERSION" in version: default_version = version["APP_ROOT_VERSION"] elif ("VERSION_MAJOR" in version) and ("VERSION_MINOR" in version) and ("PATCHLEVEL" in version): default_version = version["VERSION_MAJOR"] + "." + version["VERSION_MINOR"] + "." + version["PATCHLEVEL"] if "EXTRAVERSION" in version: - if version["EXTRAVERSION"] in ("alpha", "beta", "rc"): - default_version += "-" + version["EXTRAVERSION"] + extra = re.match(extraversion_re, version["EXTRAVERSION"]) + if extra is not None: + default_version += "-" + ".".join([v for v in extra.groups() if v is not None]) elif len(version["EXTRAVERSION"]) > 0: # Use the least important pre-release tag for unsupported values default_version += "-alpha" @@ -111,11 +114,22 @@ def append_default_version_values(cfg): cfg["VERSION"]["DEFAULT_SEQ_NUM"] = f"{default_seq_num}" # Handle SCFW versioning schema - it is customized by overwriting the Zephyr's version.cmake file. - if ("SYSCTRL_VERSION_MAJOR" in version) and ("SYSCTRL_VERSION_MINOR" in version) and ("SYSCTRL_VERSION_PATCH" in version): - default_scfw_version = version["SYSCTRL_VERSION_MAJOR"] + "." + version["SYSCTRL_VERSION_MINOR"] + "." + version["SYSCTRL_VERSION_PATCH"] + if ( + ("SYSCTRL_VERSION_MAJOR" in version) + and ("SYSCTRL_VERSION_MINOR" in version) + and ("SYSCTRL_VERSION_PATCH" in version) + ): + default_scfw_version = ( + version["SYSCTRL_VERSION_MAJOR"] + + "." + + version["SYSCTRL_VERSION_MINOR"] + + "." + + version["SYSCTRL_VERSION_PATCH"] + ) if "SYSCTRL_VERSION_EXTRA" in version: - if version["SYSCTRL_VERSION_EXTRA"] in ("alpha", "beta", "rc"): - default_scfw_version += "-" + version["SYSCTRL_VERSION_EXTRA"] + extra = re.match(extraversion_re, version["SYSCTRL_VERSION_EXTRA"]) + if extra is not None: + default_scfw_version += "-" + ".".join([v for v in extra.groups() if v is not None]) elif len(version["SYSCTRL_VERSION_EXTRA"]) > 0: # Use the least important pre-release tag for unsupported values default_scfw_version += "-alpha" @@ -137,6 +151,7 @@ def append_default_version_values(cfg): if "SCFW_SEQ_NUM" not in version: cfg["VERSION"]["SCFW_SEQ_NUM"] = f"{default_scfw_seq_num}" + def read_version_file(version_file): """Read values from the VERSION configuration file.""" with open(version_file, "r") as ver_values: From 35f1ab74d886dc4795ea737596040854ea3b524e Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Thu, 12 Sep 2024 11:03:27 +0200 Subject: [PATCH 52/67] Modifications of sign script to allow multiple KEY IDs Also modifications to the suit-generator convert command due to API changes. Ref: NCSDK-29056 Signed-off-by: Artur Hadasz --- ncs/sign_script.py | 27 ++++++++++++++++++++++----- suit_generator/cmd_convert.py | 28 ++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/ncs/sign_script.py b/ncs/sign_script.py index 9e5e297b..2225aa44 100644 --- a/ncs/sign_script.py +++ b/ncs/sign_script.py @@ -33,9 +33,14 @@ # # User note: -# Rename the file to 'key_private.der' if you are using keys in DER format. +# Rename the files to 'key_private_.der' if you are using keys in DER format. # -PRIVATE_KEY = Path(__file__).parent / "key_private.pem" +PRIVATE_KEYS = { + 0x7FFFFFE0: Path(__file__).parent / "key_private.pem", + 0x4000AA00: Path(__file__).parent / "key_private_OEM_ROOT_GEN1.pem", + 0x40022100: Path(__file__).parent / "key_private_APPLICATION_GEN1.pem", + 0x40032100: Path(__file__).parent / "key_private_RADIO_GEN1.pem", +} @unique @@ -65,7 +70,11 @@ class SuitIds(Enum): DEFAULT_KEY_ID = 0x7FFFFFE0 -KEY_IDS = {"nRF54H20_sample_root": 0x7FFFFFE0, "nRF54H20_sample_app": 0x7FFFFFE0, "nRF54H20_sample_rad": 0x7FFFFFE0} +KEY_IDS = { + "nRF54H20_sample_root": 0x4000AA00, # MANIFEST_PUBKEY_OEM_ROOT_GEN1 + "nRF54H20_sample_app": 0x40022100, # MANIFEST_PUBKEY_APPLICATION_GEN1 + "nRF54H20_sample_rad": 0x40032100, +} # MANIFEST_PUBKEY_RADIO_GEN1 DOMAIN_NAME = "nordicsemi.com" @@ -165,12 +174,20 @@ def _get_manifest_class_id(self): def _get_key_id_for_manifest_class(self): return self._key_ids[self._get_manifest_class_id()] - def sign(self, private_key_path: Path) -> None: + def _get_private_key_path_for_manifest_class(self) -> Path: + key_id = self._key_ids[self._get_manifest_class_id()] + return PRIVATE_KEYS[key_id] + + def sign(self, private_key_path: Path = None) -> None: """Add signature to the envelope.""" loaders = { ".pem": load_pem_private_key, ".der": load_der_private_key, } + + if private_key_path is None: + private_key_path = self._get_private_key_path_for_manifest_class() + try: loader = loaders[private_key_path.suffix] except KeyError as e: @@ -196,5 +213,5 @@ def sign(self, private_key_path: Path) -> None: signer = Signer() signer.load_envelope(arguments.input_file) - signer.sign(PRIVATE_KEY) + signer.sign() signer.save_envelope(arguments.output_file) diff --git a/suit_generator/cmd_convert.py b/suit_generator/cmd_convert.py index 24ff6bb9..678a6720 100644 --- a/suit_generator/cmd_convert.py +++ b/suit_generator/cmd_convert.py @@ -250,17 +250,29 @@ def _get_public_key_data(self) -> bytes: # TODO: Consider adding support for keys protected by password private_key = serialization.load_pem_private_key(data=fd.read(), password=None) - public_key_numbers = private_key.public_key().public_numbers() + public_key_bytes = None - # Make sure that if bit length is not aligned to 8, full bytes will be used - x_byte_length = (public_key_numbers.x.bit_length() + 7) // 8 - y_byte_length = (public_key_numbers.y.bit_length() + 7) // 8 + try: + public_key_numbers = private_key.public_key().public_numbers() - # Convert the numbers into bytes - x_bytes = public_key_numbers.x.to_bytes(length=x_byte_length, byteorder="big") - y_bytes = public_key_numbers.y.to_bytes(length=y_byte_length, byteorder="big") + # Make sure that if bit length is not aligned to 8, full bytes will be used + x_byte_length = (public_key_numbers.x.bit_length() + 7) // 8 + y_byte_length = (public_key_numbers.y.bit_length() + 7) // 8 - return x_bytes + y_bytes + # Convert the numbers into bytes + x_bytes = public_key_numbers.x.to_bytes(length=x_byte_length, byteorder="big") + y_bytes = public_key_numbers.y.to_bytes(length=y_byte_length, byteorder="big") + + public_key_bytes = x_bytes + y_bytes + except AttributeError: + # Depending on the key type, some attributes may not be available + # Thus, the method of getting the public key data is different + public_key_bytes = private_key.public_key().public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw, + ) + + return public_key_bytes def _split_bytes_per_row(self, data: bytes) -> list[bytes]: return [data[i : i + self._columns_count] for i in range(0, len(data), self._columns_count)] From 11ac20ff63e256aa3a0facccdf001be2463bfc2a Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 23 Sep 2024 14:38:47 +0200 Subject: [PATCH 53/67] Added SUIT_RECOVERY Kconfig option Needed mostly for creation of a separate .zip file, but also allows to simplify the configuration of a recovery application. Ref: NCSDK-29206 Signed-off-by: Artur Hadasz --- ncs/Kconfig | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index a1f3204c..7b1d22d6 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -10,25 +10,36 @@ config SSF_SUIT_SERVICE_ENABLED config SUIT_ENVELOPE_TEMPLATE_FILENAME string "Path to the envelope template" - default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP - default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD + default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY + default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY + default "rad_recovery_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" - default "application" if SOC_NRF54H20_CPUAPP - default "radio" if SOC_NRF54H20_CPURAD + default "application" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY + default "radio" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY + default "app_recovery" if SOC_NRF54H20_CPUAPP && SUIT_RECOVERY + default "rad_recovery" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY config SUIT_ENVELOPE_OUTPUT_ARTIFACT string "Name of the output merged artifact" default "merged.hex" +config SUIT_RECOVERY + bool "The given image is part of a SUIT recovery application" + depends on !NRF_REGTOOL_GENERATE_UICR + config SUIT_ENVELOPE_OUTPUT_MPI_MERGE bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" - default y + default y if !SUIT_RECOVERY config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" - default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD + # In case of the recovery application, the "application" envelope, containing + # the application firmware image, is actually the root envelope from + # the build system's point of view. + # That is why no local envelope is generated for the application. + default y if (SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY) || SOC_NRF54H20_CPURAD config SUIT_DFU_CACHE_EXTRACT_IMAGE bool "Extract firmware image to DFU cache" @@ -49,7 +60,9 @@ config SUIT_DFU_CACHE_EXTRACT_IMAGE_PARTITION config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI string "The URI used as key for the image in the DFU cache" - default "cache://application.bin" if SOC_NRF54H20_CPUAPP - default "cache://radio.bin" if SOC_NRF54H20_CPURAD + default "cache://application.bin" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY + default "cache://radio.bin" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY + default "cache://app_recovery.bin" if SOC_NRF54H20_CPUAPP && SUIT_RECOVERY + default "cache://rad_recovery.bin" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY endif # SUIT_DFU_CACHE_EXTRACT_IMAGE From 199ed3c77536e5552fb8a6505639c465655ec775 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 8 Oct 2024 08:55:19 +0200 Subject: [PATCH 54/67] Aligned to new default volatile KEY_ID in sign_script Signed-off-by: Artur Hadasz --- ncs/sign_script.py | 8 ++++---- tests/test_envelope_api.py | 2 +- tests/test_ncs_sign_script.py | 2 +- tests/test_suit_envelope.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ncs/sign_script.py b/ncs/sign_script.py index 2225aa44..92ea6820 100644 --- a/ncs/sign_script.py +++ b/ncs/sign_script.py @@ -36,7 +36,7 @@ # Rename the files to 'key_private_.der' if you are using keys in DER format. # PRIVATE_KEYS = { - 0x7FFFFFE0: Path(__file__).parent / "key_private.pem", + 0x40000000: Path(__file__).parent / "key_private.pem", 0x4000AA00: Path(__file__).parent / "key_private_OEM_ROOT_GEN1.pem", 0x40022100: Path(__file__).parent / "key_private_APPLICATION_GEN1.pem", 0x40032100: Path(__file__).parent / "key_private_RADIO_GEN1.pem", @@ -68,13 +68,13 @@ class SuitIds(Enum): SUIT_MANIFEST_COMPONENT_ID = 5 -DEFAULT_KEY_ID = 0x7FFFFFE0 +DEFAULT_KEY_ID = 0x40000000 KEY_IDS = { "nRF54H20_sample_root": 0x4000AA00, # MANIFEST_PUBKEY_OEM_ROOT_GEN1 "nRF54H20_sample_app": 0x40022100, # MANIFEST_PUBKEY_APPLICATION_GEN1 - "nRF54H20_sample_rad": 0x40032100, -} # MANIFEST_PUBKEY_RADIO_GEN1 + "nRF54H20_sample_rad": 0x40032100, # MANIFEST_PUBKEY_RADIO_GEN1 +} DOMAIN_NAME = "nordicsemi.com" diff --git a/tests/test_envelope_api.py b/tests/test_envelope_api.py index bf54bf72..8a20a96d 100644 --- a/tests/test_envelope_api.py +++ b/tests/test_envelope_api.py @@ -99,7 +99,7 @@ CoseSign1Tagged: protected: suit-cose-algorithm-id: cose-alg-es-256 - suit-cose-key-id: 0x7fffffe0 + suit-cose-key-id: 0x40000000 unprotected: {} payload: None signature: DEADBEEF diff --git a/tests/test_ncs_sign_script.py b/tests/test_ncs_sign_script.py index 321e40fe..1cf6bf5d 100644 --- a/tests/test_ncs_sign_script.py +++ b/tests/test_ncs_sign_script.py @@ -260,7 +260,7 @@ def test_envelope_sign_and_verify(setup_and_teardown, input_data, amount_of_payl cose_structure = CoseSigStructure.from_obj( { "context": "Signature1", - "body_protected": {"suit-cose-algorithm-id": algorithm_name, "suit-cose-key-id": 0x7FFFFFE0}, + "body_protected": {"suit-cose-algorithm-id": algorithm_name, "suit-cose-key-id": 0x40000000}, "external_add": "", "payload": digest_object, } diff --git a/tests/test_suit_envelope.py b/tests/test_suit_envelope.py index c9a40a57..26053d41 100644 --- a/tests/test_suit_envelope.py +++ b/tests/test_suit_envelope.py @@ -649,7 +649,7 @@ CoseSign1Tagged: protected: suit-cose-algorithm-id: cose-alg-es-256 - suit-cose-key-id: 0x7fffffe0 + suit-cose-key-id: 0x40000000 unprotected: {} payload: None, signature: DEADBEEF, From 6d31d4f4c761b8fb7c6dc5c8a3c09bd354943957 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Thu, 10 Oct 2024 16:16:57 +0200 Subject: [PATCH 55/67] keys: Add support for nes suit-install key Add support for generating manifests with new suit-install key value. Ref: NCSDK-NONE Signed-off-by: Tomasz Chyrowicz --- suit_generator/suit/envelope.py | 4 ++++ suit_generator/suit/manifest.py | 2 ++ suit_generator/suit/types/keys.py | 9 ++++++++- tests/test_suit_envelope_severable.py | 4 ++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/suit_generator/suit/envelope.py b/suit_generator/suit/envelope.py index c7ccf9e1..c446bd89 100644 --- a/suit_generator/suit/envelope.py +++ b/suit_generator/suit/envelope.py @@ -24,6 +24,7 @@ suit_candidate_verification, suit_payload_fetch, suit_install, + suit_install_legacy, suit_text, suit_integrated_payloads, suit_integrated_dependencies, @@ -42,6 +43,7 @@ class SuitEnvelopeSimplified(SuitKeyValue): suit_payload_fetch: SuitBstr, suit_candidate_verification: SuitBstr, suit_install: SuitBstr, + suit_install_legacy: SuitBstr, suit_text: SuitBstr, suit_integrated_payloads: SuitIntegratedPayloadMap, suit_integrated_dependencies: SuitIntegratedPayloadMap, @@ -62,6 +64,7 @@ class SuitEnvelope(SuitKeyValue): suit_payload_fetch: cbstr(SuitCommandSequence), suit_candidate_verification: cbstr(SuitCommandSequence), suit_install: cbstr(SuitCommandSequence), + suit_install_legacy: cbstr(SuitCommandSequence), suit_text: cbstr(SuitTextMap), suit_integrated_payloads: SuitIntegratedPayloadMap, suit_integrated_dependencies: SuitIntegratedPayloadMap, @@ -104,6 +107,7 @@ def update_severable_digests(self): suit_payload_fetch, suit_candidate_verification, suit_install, + suit_install_legacy, ] for severable_element in severable_elements: if severable_element in self.SuitEnvelopeTagged.value.SuitEnvelope[suit_manifest].SuitManifest and hasattr( diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 5ba54359..67c1e721 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -92,6 +92,7 @@ suit_invoke, suit_payload_fetch, suit_install, + suit_install_legacy, suit_text_manifest_description, suit_text_manifest_json_source, suit_text_manifest_yaml_source, @@ -468,6 +469,7 @@ class SuitManifest(SuitKeyValue): suit_invoke: cbstr(SuitCommandSequence), suit_payload_fetch: SuitSeverableCommandSequence, suit_install: SuitSeverableCommandSequence, + suit_install_legacy: SuitSeverableCommandSequence, suit_text: SuitSeverableText, suit_dependency_resolution: SuitSeverableCommandSequence, suit_candidate_verification: SuitSeverableCommandSequence, diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 8a7bf3d4..8fba815c 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -302,10 +302,17 @@ class suit_payload_fetch(suit_key): name = "suit-payload-fetch" +class suit_install_legacy(suit_key): + """suit-install metadata (before v27).""" + + id = 17 + name = "suit-install-legacy" + + class suit_install(suit_key): """suit-install metadata.""" - id = 17 + id = 20 name = "suit-install" diff --git a/tests/test_suit_envelope_severable.py b/tests/test_suit_envelope_severable.py index 1c003ba7..429709d4 100644 --- a/tests/test_suit_envelope_severable.py +++ b/tests/test_suit_envelope_severable.py @@ -376,9 +376,9 @@ "ENVELOPE_2_SEVERED_TEXT_FETCH_INSTALL": ( "d86ba602458143822f400358cea601010201035857a2028384414d4218ff451a0e054000451a0005600084414" "d410e451a2e054000451a000560008241444100045829840c0114a201507617daa571fd5a858f94e28d735ce9" - "f40250d622bafd4337518590bc6368cda7fbca11822f5820f0b65173c03a9c481a0fe3ea62ed744bcfb0bef21" + "f40250d622bafd4337518590bc6368cda7fbca14822f5820f0b65173c03a9c481a0fe3ea62ed744bcfb0bef21" "43d4380e52dd67d5ee4200d10822f5820814a3a9e09cf691e5fa77c315c0a69a9929ee86601d5dfdca8b24819" - "e0be745917822f5820aac171d7a184ecd31c01495ac6b656b2e60a5aa43de6f6d1c9acdac29bb540c211581e9" + "e0be745917822f5820aac171d7a184ecd31c01495ac6b656b2e60a5aa43de6f6d1c9acdac29bb540c214581e9" "00c0214a115692366696c652e62696e150003000c0114a11602160003001050840c0214a115692366696c652e" "62696e175896a162656ea184414d4102451a0e0aa000451a00056000a60178184e6f726469632053656d69636" "f6e647563746f7220415341026e6e5246353432305f637075617070036e6e6f7264696373656d692e636f6d04" From c2e394f319823125a78ea2b0281b27cc7b3acbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Furre=20Amundsen?= Date: Tue, 22 Oct 2024 05:48:16 +0200 Subject: [PATCH 56/67] kconfig: support engb (#148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref: NCSDK-NONE Signed-off-by: Håkon Amundsen --- ncs/Kconfig | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 7b1d22d6..2b18e401 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -10,16 +10,16 @@ config SSF_SUIT_SERVICE_ENABLED config SUIT_ENVELOPE_TEMPLATE_FILENAME string "Path to the envelope template" - default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY - default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY - default "rad_recovery_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY + default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY + default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY + default "rad_recovery_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" - default "application" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY - default "radio" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY - default "app_recovery" if SOC_NRF54H20_CPUAPP && SUIT_RECOVERY - default "rad_recovery" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY + default "application" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY + default "radio" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY + default "app_recovery" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY + default "rad_recovery" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY config SUIT_ENVELOPE_OUTPUT_ARTIFACT string "Name of the output merged artifact" @@ -39,7 +39,7 @@ config SUIT_LOCAL_ENVELOPE_GENERATE # the application firmware image, is actually the root envelope from # the build system's point of view. # That is why no local envelope is generated for the application. - default y if (SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY) || SOC_NRF54H20_CPURAD + default y if (SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY) || SOC_NRF54H20_CPURAD_COMMON config SUIT_DFU_CACHE_EXTRACT_IMAGE bool "Extract firmware image to DFU cache" @@ -60,9 +60,9 @@ config SUIT_DFU_CACHE_EXTRACT_IMAGE_PARTITION config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI string "The URI used as key for the image in the DFU cache" - default "cache://application.bin" if SOC_NRF54H20_CPUAPP && !SUIT_RECOVERY - default "cache://radio.bin" if SOC_NRF54H20_CPURAD && !SUIT_RECOVERY - default "cache://app_recovery.bin" if SOC_NRF54H20_CPUAPP && SUIT_RECOVERY - default "cache://rad_recovery.bin" if SOC_NRF54H20_CPURAD && SUIT_RECOVERY + default "cache://application.bin" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY + default "cache://radio.bin" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY + default "cache://app_recovery.bin" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY + default "cache://rad_recovery.bin" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY endif # SUIT_DFU_CACHE_EXTRACT_IMAGE From e383e6cc5f041eee351284b811cf7195090dfd69 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 14 Oct 2024 18:19:33 +0200 Subject: [PATCH 57/67] Added suit-generator payload_extract command The payload allows for extracting/removing/replacing integrated payloads in the envelope. Ref: NCSDK-29708 Signed-off-by: Artur Hadasz --- suit_generator/args.py | 2 + suit_generator/cli.py | 13 +++++- suit_generator/cmd_payload_extract.py | 67 +++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 suit_generator/cmd_payload_extract.py diff --git a/suit_generator/args.py b/suit_generator/args.py index 30b60206..26be64f2 100644 --- a/suit_generator/args.py +++ b/suit_generator/args.py @@ -16,6 +16,7 @@ from suit_generator.cmd_convert import add_arguments as convert_args from suit_generator.cmd_mpi import add_arguments as mpi_args from suit_generator.cmd_cache_create import add_arguments as cache_create_args +from suit_generator.cmd_payload_extract import add_arguments as payload_extract_args def _parser() -> ArgumentParser: @@ -30,6 +31,7 @@ def _parser() -> ArgumentParser: convert_args(subparsers) mpi_args(subparsers) cache_create_args(subparsers) + payload_extract_args(subparsers) return parser diff --git a/suit_generator/cli.py b/suit_generator/cli.py index 6e515825..6518e945 100644 --- a/suit_generator/cli.py +++ b/suit_generator/cli.py @@ -11,7 +11,17 @@ sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) -from suit_generator import cmd_parse, cmd_keys, cmd_convert, cmd_create, cmd_image, cmd_mpi, cmd_cache_create, args +from suit_generator import ( + cmd_parse, + cmd_keys, + cmd_convert, + cmd_create, + cmd_image, + cmd_mpi, + cmd_cache_create, + cmd_payload_extract, + args, +) from suit_generator.exceptions import GeneratorError, SUITError import logging @@ -30,6 +40,7 @@ cmd_image.ImageCreator.IMAGE_CMD: cmd_image.main, cmd_mpi.MPI_CMD: cmd_mpi.main, cmd_cache_create.CACHE_CREATE_CMD: cmd_cache_create.main, + cmd_payload_extract.PAYLOAD_EXTRACT_CMD: cmd_payload_extract.main, } diff --git a/suit_generator/cmd_payload_extract.py b/suit_generator/cmd_payload_extract.py new file mode 100644 index 00000000..6b9fa9b9 --- /dev/null +++ b/suit_generator/cmd_payload_extract.py @@ -0,0 +1,67 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""CMD_PAYLOAD_EXTRACT CLI command entry point.""" + +import cbor2 +import logging + +log = logging.getLogger(__name__) + +PAYLOAD_EXTRACT_CMD = "payload_extract" + + +def add_arguments(parser): + """Add additional arguments to the passed parser.""" + cmd_payload_extract_arg_parser = parser.add_parser(PAYLOAD_EXTRACT_CMD, help="Create raw cache structure.") + + cmd_payload_extract_arg_parser.add_argument("--input-envelope", required=True, help="Input envelope file path.") + cmd_payload_extract_arg_parser.add_argument("--output-envelope", required=True, help="Output envelope file path.") + cmd_payload_extract_arg_parser.add_argument( + "--payload-name", required=True, help="Name of the integrated payload to extract." + ) + cmd_payload_extract_arg_parser.add_argument( + "--output-payload-file", + required=False, + help="Output payload file path to store the extracted payload." + + "If not provided, the payload will not be stored to a file.", + ) + + cmd_payload_extract_arg_parser.add_argument( + "--payload-replace-path", + help="Path to the integrated payload to replace the extracted payload with." + + "If not provided, the payload will be removed from the envelope.", + ) + + +def main( + input_envelope: str, output_envelope: str, payload_name: str, output_payload_file: str, payload_replace_path: str +) -> None: + """Extract an integrated payload from a SUIT envelope. + + :param input_envelope: input envelope file path + :param output_envelope: output envelope file path + :param payload_name: name of the integrated payload to extract + :param output_payload_file: output file path to store the extracted payload + None if the payload should not be stored to a file + :param payload_replace_path: Path to the integrated payload to replace the extracted payload with. + None if the payload should be removed from the envelope. + """ + with open(input_envelope, "rb") as fh: + envelope = cbor2.load(fh) + extracted_payload = envelope.value.pop(payload_name, None) + + if extracted_payload is None: + log.log(logging.ERROR, 'Payload "%s" not found in envelope', payload_name) + + if payload_replace_path is not None: + with open(payload_replace_path, "rb") as fh: + envelope.value[payload_name] = fh.read() + + with open(output_envelope, "wb") as fh: + cbor2.dump(envelope, fh) + if output_payload_file is not None: + with open(output_payload_file, "wb") as fh: + fh.write(extracted_payload) From 7bf6a6bfd3df4249e198f83cf6582c34912cce03 Mon Sep 17 00:00:00 2001 From: Krzysztof Szromek Date: Wed, 6 Nov 2024 15:35:21 +0100 Subject: [PATCH 58/67] fix: new default addresses for ImageCreator Ref: NCSDK-30031 Signed-off-by: Krzysztof Szromek --- suit_generator/cmd_image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index d623401d..820ed08a 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -390,9 +390,9 @@ class EnvelopeStorageNrf54h20(EnvelopeStorage): class ImageCreator: """Helper class for extracting data from SUIT envelope and creating hex files.""" - default_update_candidate_info_address = 0x0E1ED340 - default_storage_address = 0x0E1EB000 - default_dfu_partition_address = 0x0E155000 + default_update_candidate_info_address = 0x0E1EF340 + default_storage_address = 0x0E1ED000 + default_dfu_partition_address = 0x0E100000 default_dfu_max_caches = 6 UPDATE_MAGIC_VALUE_AVAILABLE = 0x55AA55AA From 9c5e8f16ef49e5ffbdeabc87ffd60b82b1fd72b0 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Tue, 5 Nov 2024 17:21:21 +0100 Subject: [PATCH 59/67] Changes needed for recovery run from APP_LOCAL_3 manifest Ref: NCSDK-29998 Signed-off-by: Artur Hadasz --- ncs/Kconfig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 2b18e401..39cd863c 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -12,13 +12,14 @@ config SUIT_ENVELOPE_TEMPLATE_FILENAME string "Path to the envelope template" default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY + default "app_recovery_local_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY default "rad_recovery_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" default "application" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY default "radio" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY - default "app_recovery" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY + default "app_recovery_img" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY default "rad_recovery" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY config SUIT_ENVELOPE_OUTPUT_ARTIFACT @@ -35,11 +36,7 @@ config SUIT_ENVELOPE_OUTPUT_MPI_MERGE config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" - # In case of the recovery application, the "application" envelope, containing - # the application firmware image, is actually the root envelope from - # the build system's point of view. - # That is why no local envelope is generated for the application. - default y if (SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY) || SOC_NRF54H20_CPURAD_COMMON + default y if SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF54H20_CPURAD_COMMON config SUIT_DFU_CACHE_EXTRACT_IMAGE bool "Extract firmware image to DFU cache" From 8f9ce9039e6fb8584dc915944b2bc275856757c0 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Wed, 6 Nov 2024 16:09:20 +0100 Subject: [PATCH 60/67] Added parameters for suit_invoke_args Ref: NCSDK-29998 Signed-off-by: Artur Hadasz --- suit_generator/suit/manifest.py | 15 ++++++++++++++- suit_generator/suit/types/keys.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 67c1e721..0750fe14 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -107,6 +107,8 @@ suit_candidate_verification, suit_uninstall, suit_text, + suit_synchronous_invoke, + suit_timeout, ) from suit_generator.logger import log_call @@ -265,6 +267,17 @@ class SuitParameterVersion(SuitKeyValueTuple): ) +class SuitParameterInvokeArgs(SuitKeyValue): + """Representation of SUIT version parameter.""" + + _metadata = Metadata( + map={ + suit_synchronous_invoke: SuitBool, + suit_timeout: SuitUint, + } + ) + + class SuitParameters(SuitKeyValue): """Representation of SUIT parameters.""" @@ -281,7 +294,7 @@ class SuitParameters(SuitKeyValue): suit_parameter_encryption_info: SuitEncryptionInfo, suit_parameter_uri: SuitTstr, suit_parameter_source_component: SuitUint, - suit_parameter_invoke_args: SuitBstr, + suit_parameter_invoke_args: cbstr(SuitParameterInvokeArgs), suit_parameter_device_identifier: SuitUUID, suit_parameter_version: cbstr(SuitParameterVersion), } diff --git a/suit_generator/suit/types/keys.py b/suit_generator/suit/types/keys.py index 8fba815c..e98721d6 100644 --- a/suit_generator/suit/types/keys.py +++ b/suit_generator/suit/types/keys.py @@ -769,3 +769,17 @@ class suit_send_sysinfo_failure(suit_key): id = 8 name = "suit-send-sysinfo-failure" + + +class suit_synchronous_invoke(suit_key): + """Synchronous invoke argument.""" + + id = 1 + name = "suit-synchronous-invoke" + + +class suit_timeout(suit_key): + """Timeout invoke argument.""" + + id = 2 + name = "suit-timeout" From 0e660519a58adba119d552e90fcbca8e430490a4 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Wed, 20 Nov 2024 17:34:27 +0100 Subject: [PATCH 61/67] cache_create: Extended the command to extract from envelope This commit extends the suit-generator cache_create command. It now has three subcommands: from_payloads, from_envelopei and merge. from_payload has the same functionality as the old version of the cache_create command from_envelope allows extracting payloads directly from the envelope - also with the possibility to parse the envelope hierarchically and omit certain payloads. merge allows to merge multiple cache partition files into one. Ref: NCSDK-28932 Signed-off-by: Artur Hadasz --- suit_generator/cmd_cache_create.py | 353 +++++++++++++++++++++++------ tests/test_cmd_cache_create.py | 132 ++++++++++- 2 files changed, 404 insertions(+), 81 deletions(-) diff --git a/suit_generator/cmd_cache_create.py b/suit_generator/cmd_cache_create.py index 3b6feaa5..2713ca6f 100644 --- a/suit_generator/cmd_cache_create.py +++ b/suit_generator/cmd_cache_create.py @@ -6,104 +6,151 @@ """CMD_CACHE_CREATE CLI command entry point.""" import logging -import cbor2 import math +import cbor2 +import re -log = logging.getLogger(__name__) +from suit_generator.exceptions import GeneratorError CACHE_CREATE_CMD = "cache_create" +CACHE_CREATE_FROM_PAYLOADS_CMD = "from_payloads" +CACHE_CREATE_FROM_ENVELOPE_CMD = "from_envelope" +CACHE_MERGE_CMD = "merge" + +log = logging.getLogger(__name__) def add_arguments(parser): """Add additional arguments to the passed parser.""" - cmd_cache_create_arg_parser = parser.add_parser(CACHE_CREATE_CMD, help="Create raw cache structure.") + cmd_cache_create = parser.add_parser(CACHE_CREATE_CMD, help="Create raw cache structure.") - cmd_cache_create_arg_parser.add_argument( + cmd_cache_create_subparsers = cmd_cache_create.add_subparsers( + dest="cache_create_subcommand", required=True, help="Choose cache_create subcommand" + ) + cmd_cache_create_from_payloads = cmd_cache_create_subparsers.add_parser( + CACHE_CREATE_FROM_PAYLOADS_CMD, + help="Create a cache partition from the provided binaries containing raw payloads.", + ) + + cmd_cache_create_from_payloads.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") + cmd_cache_create_from_payloads.add_argument( + "--eb-size", type=int, help="Erase block size in bytes (used for padding).", default=16 + ) + + cmd_cache_create_from_payloads.add_argument( "--input", required=True, action="append", help="Input binary with corresponding URI, passed in format ,." + "Multiple inputs can be passed.", ) - cmd_cache_create_arg_parser.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") - cmd_cache_create_arg_parser.add_argument( + + cmd_cache_create_from_envelope = cmd_cache_create_subparsers.add_parser( + CACHE_CREATE_FROM_ENVELOPE_CMD, help="Create a cache partition from the payloads inside the provided envelope." + ) + + cmd_cache_create_from_envelope.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") + cmd_cache_create_from_envelope.add_argument( "--eb-size", type=int, help="Erase block size in bytes (used for padding).", default=16 ) + cmd_cache_create_from_envelope.add_argument("--input-envelope", required=True, help="Input envelope file path.") -def add_padding(data: bytes, eb_size: int) -> bytes: - """ - Add padding to the given data to align it to the specified erase block size. - - This function ensures that the data is padded to a size that is a multiple of the erase block size (eb_size). - The padding is done by appending a CBOR key-value pair with empty URI as the key and - byte-string-encoded zeros as the value. - - :param data: The input data to be padded. - :type data: bytes - :param eb_size: The erase block size in bytes. - :type eb_size: int - :return: The padded data. - """ - rounded_up_size = math.ceil(len(data) / eb_size) * eb_size - padding_size = rounded_up_size - len(data) - padded_data = data - - # minimum padding size is 2 bytes - if padding_size == 1: - padding_size += eb_size - rounded_up_size += eb_size - - if padding_size == 0: - return data - - padded_data += bytes([0x60]) - - if padding_size <= 23: - header_len = 2 - padded_data += bytes([0x40 + (padding_size - header_len)]) - elif padding_size <= 0xFFFF: - header_len = 4 - padded_data += bytes([0x59]) + (padding_size - header_len).to_bytes(2, byteorder="big") - else: - raise ValueError("Number of required padding bytes exceeds assumed max size 0xFFFF") + cmd_cache_create_from_envelope.add_argument( + "--output-envelope", required=True, help="Output envelope file path (envelope with removed extracted payloads)." + ) + + cmd_cache_create_from_envelope.add_argument( + "--omit-payload-regex", + help="Integrated payloads matching the regular expression will not be extracted to the cache.", + ) - return padded_data.ljust(rounded_up_size, b"\x00") + cmd_cache_create_from_envelope.add_argument( + "--dependency-regex", + help="Integrated payloads matching the regular expression will be treated as dependency" + + "envelopes and parsed hierarchically. " + + "The payloads extracted from the dependency envelopes will be added to the cache.", + ) + cmd_cache_merge = cmd_cache_create_subparsers.add_parser( + CACHE_MERGE_CMD, help="Merge multiple cache partitions into a single cache partition." + ) -def main(input: list[str], output_file: str, eb_size: int) -> None: - """ - Create a raw SUIT DFU cache file from input binaries. + cmd_cache_merge.add_argument("--input", required=True, action="append", help="Input raw SUIT DFU cache file.") + cmd_cache_merge.add_argument("--output-file", required=True, help="Output raw SUIT DFU cache file.") + cmd_cache_merge.add_argument( + "--eb-size", type=int, help="Erase block size in bytes (used for padding).", default=16 + ) - This function processes a list of input binaries, each associated with a URI, and creates a raw SUIT DFU cache file. - The data is padded to align with the specified erase block size (eb_size). - :param input: List of input binaries with corresponding URIs, passed in the format ,. - :type input: list[str] - :param output_file: Path to the output raw SUIT DFU cache file. - :type output_file: str - :param eb_size: Erase block size in bytes (used for padding). - :type eb_size: int - :return: None - """ - cache_data = bytes() - first_slot = True +class CachePartition: + """Class generating SUIT DFU Cache Partition.""" - for single_input in input: - args = single_input.split(",") - if len(args) < 2: - raise ValueError("Invalid number of input arguments: " + single_input) - uri, input_file = args + def __init__(self, eb_size: int): + """Initialize a CachePartition object.""" + self.first_slot = True + self.cache_data = bytes() + self.eb_size = eb_size + self.uris = [] - data = None - with open(input_file, "rb") as f: - data = f.read() + def add_padding(self, data: bytes) -> bytes: + """ + Add padding to the given data to align it to the specified erase block size. + + This method ensures that the data is padded to a size that is a multiple of the erase block size (self.eb_size). + The padding is done by appending a CBOR key-value pair with empty URI as the key and + byte-string-encoded zeros as the value. + + :param data: The input data to be padded. + :type data: bytes + :return: The padded data. + """ + rounded_up_size = math.ceil(len(data) / self.eb_size) * self.eb_size + padding_size = rounded_up_size - len(data) + padded_data = data + + # minimum padding size is 2 bytes + if padding_size == 1: + padding_size += self.eb_size + rounded_up_size += self.eb_size + if padding_size == 0: + return data + + padded_data += bytes([0x60]) + + if padding_size <= 23: + header_len = 2 + padded_data += bytes([0x40 + (padding_size - header_len)]) + elif padding_size <= 0xFFFF: + header_len = 4 + padded_data += bytes([0x59]) + (padding_size - header_len).to_bytes(2, byteorder="big") + else: + raise ValueError("Number of required padding bytes exceeds assumed max size 0xFFFF") + + return padded_data.ljust(rounded_up_size, b"\x00") + + def add_cache_slot(self, uri: str, data: bytes): + """ + Add a cache slot to the cache from the given URI and data. + + This function creates a cache slot from the given URI and data, and pads the data to align with the specified + erase block size (eb_size). The first slot in the cache is created with indefinite length CBOR map. + + :param uri: The URI associated with the data. + :type uri: str + :param data: The data to be included in the cache slot. + :type data: bytes + """ slot_data = bytes() - if first_slot: + if self.first_slot: # Open the cache - it is an indefinite length CBOR map (0xBF) slot_data = bytes([0xBF]) - first_slot = False + self.first_slot = False + + if uri in self.uris: + raise ValueError(f"URI {uri} already exists in the cache!") + self.uris.append(uri) # uri as key slot_data += cbor2.dumps(uri) @@ -111,11 +158,171 @@ def main(input: list[str], output_file: str, eb_size: int) -> None: # Size must be encoded in 4 bytes, thus cannot use cbor2.dumps slot_data += bytes([0x5A]) + len(data).to_bytes(4, byteorder="big") + data # Add padding for single slot - slot_data = add_padding(slot_data, eb_size) + slot_data = self.add_padding(slot_data) + + self.cache_data += slot_data - cache_data += slot_data + def close_and_save_cache(self, output_file: str): + """ + Close the cache and save it to the specified output file. - cache_data += bytes([0xFF]) # Finish the indefinite length map + This function closes the cache by adding the end-of-map byte (0xFF) and saves the cache to the specified output + file. + + :param output_file: Path to the output raw SUIT DFU cache file. + :type output_file: str + """ + self.cache_data += bytes([0xFF]) + with open(output_file, "wb") as f: + f.write(self.cache_data) + + def merge_single_cache_file(self, cache_input_file: str): + """ + Merge the contents of the provided single cache file into the current cache. + + :param cache_input_file: Path to the input raw SUIT DFU cache file. + """ + with open(cache_input_file, "rb") as f: + data = f.read() + + cache_dict = cbor2.loads(data) + + for k in cache_dict.keys(): + if len(k) == 0: + continue # Empty key means padding - skip + self.add_cache_slot(k, cache_dict[k]) + + +class CacheFromPayloads: + """Class generating SUIT DFU Cache Partition from payloads.""" + + def fill_cache_from_payloads(cache: CachePartition, input: list[str]) -> None: + """ + Process list of input binaries, each associated with a URI, and fill the SUIT DFU cache with the data. + + :param cache: CachePartition object to fill with the data + :param input: List of input binaries with corresponding URIs, passed in the format , + """ + for single_input in input: + args = single_input.split(",") + if len(args) < 2: + raise ValueError("Invalid number of input arguments: " + single_input) + uri, input_file = args + + with open(input_file, "rb") as f: + data = f.read() + + cache.add_cache_slot(uri, data) + + +class CacheFromEnvelope: + """Class generating SUIT DFU Cache Partition from envelope.""" + + def fill_cache_from_envelope_data( + cache: CachePartition, envelope_data: bytes, omit_payload_regex: str, dependency_regex: str + ) -> bytes: + """ + Fill the cache partition with data from the payloads inside the provided envelope binary data. + + This function is called recursively for dependency envelopes. + :param cache: CachePartition object to fill with the data + :param envelope_data: Binary data of the envelope to extract the payloads from + :param omit_payload_regex: Integrated payloads matching the regular expression will not be extracted to the + cache + :param dependency_regex: Integrated payloads matching the regular expression will be treated as dependency + envelopes + """ + try: + envelope = cbor2.loads(envelope_data) + except Exception: + raise GeneratorError("The provided envelope/dependency envelope is not a valid envelope!") + + if isinstance(envelope, cbor2.CBORTag) and isinstance(envelope.value, dict): + integrated = [k for k in envelope.value.keys() if isinstance(k, str)] + else: + raise GeneratorError("The provided envelope/dependency envelope is not a valid envelope!") + + if dependency_regex is not None: + integrated_dependencies = [k for k in integrated if not re.fullmatch(dependency_regex, k) is None] + for dep in integrated_dependencies: + integrated.remove(dep) + else: + integrated_dependencies = [] + + if omit_payload_regex is None: + payloads_to_extract = integrated + else: + payloads_to_extract = [k for k in integrated if re.fullmatch(omit_payload_regex, k) is None] + + for payload in payloads_to_extract: + cache.add_cache_slot(payload, envelope.value.pop(payload)) + + for dependency in integrated_dependencies: + try: + new_dependency_data = CacheFromEnvelope.fill_cache_from_envelope_data( + cache, envelope.value[dependency], omit_payload_regex, dependency_regex + ) + except GeneratorError as e: + log.log(logging.ERROR, "Failed to extract payloads from dependency %s: %s", dependency, repr(e)) + raise GeneratorError("Failed to extract payloads from envelope!") + + envelope.value[dependency] = new_dependency_data + + return cbor2.dumps(envelope) + + def fill_cache_from_envelope( + cache: CachePartition, input_envelope: str, output_envelope: str, omit_payload_regex: str, dependency_regex: str + ) -> None: + """ + Extract the payloads from the provided envelope to the cache partition file. + + param cache: CachePartition object to fill with the data + param input_envelope: Path to the input envelope file + param output_envelope: Path to the output envelope file (envelope with removed extracted payloads) + param omit_payload_regex: Integrated payloads matching the regular expression will not be extracted to the cache + param dependency_regex: Integrated payloads matching the regular expression will be treated as dependency + envelopes + """ + with open(input_envelope, "rb") as fh: + data = fh.read() + output_envelope_data = CacheFromEnvelope.fill_cache_from_envelope_data( + cache, data, omit_payload_regex, dependency_regex + ) + with open(output_envelope, "wb") as fh: + fh.write(output_envelope_data) + + +class CacheMerge: + """Class merging SUIT DFU Cache Partitions.""" + + def merge_cache_files(cache: CachePartition, input: list[str]) -> None: + """ + Merge the contents of the provided cache files into the cache partition. + + :param cache: CachePartition object to merge the cache files into + :param input: List of paths to the input raw SUIT DFU cache files + """ + for single_input in input: + cache.merge_single_cache_file(single_input) + + +def main(**kwargs) -> None: + """Create a raw SUIT DFU cache file.""" + cache = CachePartition(kwargs["eb_size"]) + + if kwargs["cache_create_subcommand"] == CACHE_CREATE_FROM_PAYLOADS_CMD: + CacheFromPayloads.fill_cache_from_payloads(cache, kwargs["input"]) + elif kwargs["cache_create_subcommand"] == CACHE_CREATE_FROM_ENVELOPE_CMD: + CacheFromEnvelope.fill_cache_from_envelope( + cache, + kwargs["input_envelope"], + kwargs["output_envelope"], + kwargs["omit_payload_regex"], + kwargs["dependency_regex"], + ) + elif kwargs["cache_create_subcommand"] == CACHE_MERGE_CMD: + CacheMerge.merge_cache_files(cache, kwargs["input"]) + else: + raise GeneratorError(f"Invalid 'cache_create' subcommand: {kwargs['cache_create_subcommand']}") - with open(output_file, "wb") as f: - f.write(cache_data) + cache.close_and_save_cache(kwargs["output_file"]) diff --git a/tests/test_cmd_cache_create.py b/tests/test_cmd_cache_create.py index b4c3b085..ded25748 100644 --- a/tests/test_cmd_cache_create.py +++ b/tests/test_cmd_cache_create.py @@ -8,30 +8,67 @@ import pytest import pathlib import os +import cbor2 from suit_generator.cmd_cache_create import main as cmd_cache_create_main +from suit_generator.cmd_create import main as cmd_create_main TEMP_DIRECTORY = pathlib.Path("test_test_data") BINARY_CONTENT_1 = bytes([0x01, 0x02, 0x03, 0x04]) BINARY_CONTENT_2 = bytes([0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11]) +BINARY_CONTENT_3 = bytes( + [ + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ] +) URI1 = "#first" # [0x23, 0x66, 0x69, 0x72, 0x73, 0x74] URI2 = "#second" # [0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64] +URI3 = "#third" # [0x23, 0x74, 0x68, 0x69, 0x72, 0x64] +ENVELOPE_ROOT_YAML = """SUIT_Envelope_Tagged: + suit-authentication-wrapper: + SuitDigest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: 60198229d4c07c866094a3d19c2d8b15b5dd552cd5bba5cf8f78e492ccbb3327 + suit-manifest: + suit-manifest-component-id: + - raw: aa + suit-integrated-dependencies: + 'dependency_manifest': dep.suit + '#first': first.bin +""" + +ENVELOPE_DEP_YAML = """SUIT_Envelope_Tagged: + suit-authentication-wrapper: + SuitDigest: + suit-digest-algorithm-id: cose-alg-sha-256 + suit-digest-bytes: b2afeba5d8172371661b7ab5d7242c2ba6797ae27e713114619d90663ab6a2ec + suit-manifest: + suit-manifest-component-id: + - raw: bb + suit-integrated-dependencies: + '#second': second.bin + '#third': third.bin +""" # fmt: off EXPECTED_CACHE_EB_8 = bytes([0xBF, - 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "#first" 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 0x60, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, # padding - 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "#second" 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 0x60, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding 0xFF]) EXPECTED_CACHE_EB_64 = bytes([0xBF, - 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "first" + 0x66, 0x23, 0x66, 0x69, 0x72, 0x73, 0x74, # tstr "#first" 0x5A, 0x00, 0x00, 0x00, 0x04, # bstr size 4 0x01, 0x02, 0x03, 0x04, # BINARY_CONTENT_1 0x60, 0x59, 0x00, 0x2B, # padding 47 bytes (4 bytes header + 43 bytes padding) @@ -41,7 +78,7 @@ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "second" + 0x67, 0x23, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, # tstr "#second" 0x5A, 0x00, 0x00, 0x00, 0x0a, # bstr size 10 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, # BINARY_CONTENT_2 0x60, 0x59, 0x00, 0x25, # padding 41 bytes (4 bytes header + 37 bytes padding) @@ -67,7 +104,8 @@ def setup_and_teardown(tmp_path_factory): fh.write(BINARY_CONTENT_1) with open("second.bin", "wb") as fh: fh.write(BINARY_CONTENT_2) - + with open("third.bin", "wb") as fh: + fh.write(BINARY_CONTENT_3) yield # Cleanup environment # - remove temp directory @@ -81,12 +119,90 @@ def setup_and_teardown(tmp_path_factory): (64, EXPECTED_CACHE_EB_64), ], ) -def test_cache_create(setup_and_teardown, eb_size, output_content): - """Verify if is possible to create binary envelope from json input.""" +def test_cache_create_from_payloads(setup_and_teardown, eb_size, output_content): + """Verify if the cache is correctly created from payloads.""" cmd_cache_create_main( - input=[f"{URI1},first.bin", f"{URI2},second.bin"], output_file="test_cache.bin", eb_size=eb_size + cache_create_subcommand="from_payloads", + input=[f"{URI1},first.bin", f"{URI2},second.bin"], + output_file="test_cache.bin", + eb_size=eb_size, ) with open("test_cache.bin", "rb") as f: assert f.read() == output_content + + +def test_cache_create_merge(setup_and_teardown): + """Verify if the cache is correctly created from two other cache partitions.""" + + cmd_cache_create_main( + cache_create_subcommand="from_payloads", + input=[f"{URI1},first.bin", f"{URI2},second.bin"], + output_file="test_cache1.bin", + eb_size=8, + ) + cmd_cache_create_main( + cache_create_subcommand="from_payloads", input=[f"{URI3},third.bin"], output_file="test_cache2.bin", eb_size=4 + ) + + cmd_cache_create_main( + cache_create_subcommand="from_payloads", + input=[f"{URI1},first.bin", f"{URI2},second.bin", f"{URI3},third.bin"], + output_file="test_cache_merged_expected.bin", + eb_size=16, + ) + + cmd_cache_create_main( + cache_create_subcommand="merge", + input=["test_cache1.bin", "test_cache2.bin"], + output_file="test_cache_merged.bin", + eb_size=16, + ) + + with open("test_cache_merged.bin", "rb") as f: + result = f.read() + + with open("test_cache_merged_expected.bin", "rb") as f: + expected = f.read() + + # Assert that cache resulting from merging two caches is the same as if the cache was created from the payloads + assert result == expected + + +def test_cache_create_merge_from_envelope(setup_and_teardown): + # Prepare envelope files + with open("root.yaml", "w") as fh: + fh.write(ENVELOPE_ROOT_YAML) + with open("dep.yaml", "w") as fh: + fh.write(ENVELOPE_DEP_YAML) + cmd_create_main(input_file="dep.yaml", output_file="dep.suit", input_format="AUTO") + cmd_create_main(input_file="root.yaml", output_file="root.suit", input_format="AUTO") + + cmd_cache_create_main( + cache_create_subcommand="from_envelope", + input_envelope="root.suit", + output_envelope="root_stripped.suit", + output_file="test_cache_from_envelope.bin", + eb_size=8, + omit_payload_regex=".*third", + dependency_regex="dep.*", + ) + + with open("test_cache_from_envelope.bin", "rb") as f: + assert f.read() == EXPECTED_CACHE_EB_8 + + with open("root_stripped.suit", "rb") as fh: + envelope_stripped = cbor2.load(fh) + + assert "#first" not in envelope_stripped.value.keys() + + dependency = envelope_stripped.value.pop("dependency_manifest", None) + assert dependency is not None + dependency = cbor2.loads(dependency).value + + assert "#second" not in dependency.keys() + + not_extracted = dependency.pop("#third", None) + assert not_extracted is not None + assert not_extracted == BINARY_CONTENT_3 From 9bd5f35396bea61f6c8e6aaebc00635364522ce6 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Thu, 21 Nov 2024 13:57:00 +0100 Subject: [PATCH 62/67] suit: Move MPI to sysbuild Kconfig Use sysbuild to build and configure SUIT Manifest Provisioning Information. Ref: NCSDK-30461 Signed-off-by: Tomasz Chyrowicz --- ncs/Kconfig | 4 ---- ncs/app_envelope_encrypted.yaml.jinja2 | 4 ++-- ncs/root_with_nordic_top_envelope.yaml.jinja2 | 12 +++++----- suit_generator/cmd_image.py | 10 ++++---- tests/test_cmd_image.py | 24 +++++++++---------- 5 files changed, 25 insertions(+), 29 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 39cd863c..d6f7f1a4 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -30,10 +30,6 @@ config SUIT_RECOVERY bool "The given image is part of a SUIT recovery application" depends on !NRF_REGTOOL_GENERATE_UICR -config SUIT_ENVELOPE_OUTPUT_MPI_MERGE - bool "Merge MPI files into final SUIT_ENVELOPE_OUTPUT_ARTIFACT" - default y if !SUIT_RECOVERY - config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" default y if SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF54H20_CPURAD_COMMON diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 index 5b02951f..8c42801a 100644 --- a/ncs/app_envelope_encrypted.yaml.jinja2 +++ b/ncs/app_envelope_encrypted.yaml.jinja2 @@ -1,5 +1,5 @@ -{%- set mpi_application_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_application_vendor_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_application_class_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} SUIT_Envelope_Tagged: suit-authentication-wrapper: SuitDigest: diff --git a/ncs/root_with_nordic_top_envelope.yaml.jinja2 b/ncs/root_with_nordic_top_envelope.yaml.jinja2 index f9aea4d4..f385e8b5 100644 --- a/ncs/root_with_nordic_top_envelope.yaml.jinja2 +++ b/ncs/root_with_nordic_top_envelope.yaml.jinja2 @@ -1,11 +1,11 @@ {%- set component_index = 0 %} {%- set component_list = [] %} -{%- set mpi_root_vendor_name = application['config']['CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_root_class_name = application['config']['CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} -{%- set mpi_app_vendor_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_app_class_name = application['config']['CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -{%- set mpi_rad_vendor_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_rad_class_name = application['config']['CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} +{%- set mpi_root_vendor_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_ROOT_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_root_class_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_ROOT_CLASS_NAME']|default('nRF54H20_sample_root') %} +{%- set mpi_app_vendor_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_app_class_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} +{%- set mpi_rad_vendor_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} +{%- set mpi_rad_class_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_rad') %} {%- if hci_rpmsg_subimage is defined %} {% set rad = hci_rpmsg_subimage %} {%- elif _802154_rpmsg_subimage is defined %} diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 820ed08a..420a0c48 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -188,20 +188,20 @@ def _get_role_assignments_from_kconfig(kconfig: str) -> list: config = BuildConfiguration(input_file=kconfig) kconfig_assignments = [] for key, value in config.items(): - if re_value := re.match(r"^CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$", key): + if re_value := re.match(r"^SB_CONFIG_SUIT_MPI_(?P[A-Z1-9_]+)_VENDOR_NAME$", key): manifest = re_value.group("manifest") # ensure that the same combination of vid/cid has not been set for different role for item in kconfig_assignments: if ( - item["vendor_name"] == config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"] - and item["class_name"] == config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"] + item["vendor_name"] == config[f"SB_CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"] + and item["class_name"] == config[f"SB_CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"] ): raise GeneratorError( "Duplicate vid/cid combination for different roles detected in the KConfig file." ) data = { - "vendor_name": config[f"CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"], - "class_name": config[f"CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"], + "vendor_name": config[f"SB_CONFIG_SUIT_MPI_{manifest}_VENDOR_NAME"], + "class_name": config[f"SB_CONFIG_SUIT_MPI_{manifest}_CLASS_NAME"], "role": ManifestRole[f"APP_{manifest}" if manifest == "ROOT" else manifest], } kconfig_assignments.append(data) diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 4a29b63e..c2b76acc 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -198,18 +198,18 @@ ) MPI_KCONFIG_TEMPLATE = """ -CONFIG_SUIT_MPI_ROOT_VENDOR_NAME="{root_vendor_name}" -CONFIG_SUIT_MPI_ROOT_CLASS_NAME="{root_class_name}" -CONFIG_SUIT_MPI_APP_LOCAL_1=y -CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME="{app_local_1_vendor_name}" -CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME="{app_local_1_class_name}" -CONFIG_SUIT_MPI_APP_LOCAL_2 is not set -CONFIG_SUIT_MPI_APP_LOCAL_3 is not set -CONFIG_SUIT_MPI_RAD_RECOVERY is not set -CONFIG_SUIT_MPI_RAD_LOCAL_1=y -CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME="{rad_local_1_vendor_name}" -CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME="{rad_local_1_class_name}" -CONFIG_SUIT_MPI_RAD_LOCAL_2 is not set +SB_CONFIG_SUIT_MPI_ROOT_VENDOR_NAME="{root_vendor_name}" +SB_CONFIG_SUIT_MPI_ROOT_CLASS_NAME="{root_class_name}" +SB_CONFIG_SUIT_MPI_APP_LOCAL_1=y +SB_CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME="{app_local_1_vendor_name}" +SB_CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME="{app_local_1_class_name}" +SB_CONFIG_SUIT_MPI_APP_LOCAL_2 is not set +SB_CONFIG_SUIT_MPI_APP_LOCAL_3 is not set +SB_CONFIG_SUIT_MPI_RAD_RECOVERY is not set +SB_CONFIG_SUIT_MPI_RAD_LOCAL_1=y +SB_CONFIG_SUIT_MPI_RAD_LOCAL_1_VENDOR_NAME="{rad_local_1_vendor_name}" +SB_CONFIG_SUIT_MPI_RAD_LOCAL_1_CLASS_NAME="{rad_local_1_class_name}" +SB_CONFIG_SUIT_MPI_RAD_LOCAL_2 is not set """ INPUT_ENVELOPE_YAML = """SUIT_Envelope_Tagged: From aa3da7d6c15960b3346cdaf3587ca666c21db953 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Fri, 6 Dec 2024 14:13:35 +0100 Subject: [PATCH 63/67] Encryption script using python based libraries Removed the usage of nrfkms from the script. Instead, a dedicated python script containing a SuitKMS class should be used. Ref: NCSDK-30800 Signed-off-by: Artur Hadasz --- ncs/basic_kms.py | 65 ++++++++++++++++++ ncs/encrypt_script.py | 114 +++++++++++++------------------- suit_generator/suit_kms_base.py | 35 ++++++++++ 3 files changed, 145 insertions(+), 69 deletions(-) create mode 100644 ncs/basic_kms.py create mode 100644 suit_generator/suit_kms_base.py diff --git a/ncs/basic_kms.py b/ncs/basic_kms.py new file mode 100644 index 00000000..6ccebb23 --- /dev/null +++ b/ncs/basic_kms.py @@ -0,0 +1,65 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""A basic KMS based on keys stored in files on the local drive.""" + +import os + +from pathlib import Path +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from suit_generator.suit_kms_base import SuitKMSBase +import json + + +class SuitKMS(SuitKMSBase): + """Implementation of the KMS.""" + + def parse_context(self, context): + """Parse the provided context string.""" + if context is None: + self.keys_directory = Path(__file__).parent + return None + + context_loaded = json.loads(context) + self.keys_directory = Path(context_loaded["keys_directory"]) + + def init_kms(self, context) -> None: + """ + Initialize the KMS. + + :param context: The context to be used + """ + self.parse_context(context) + + def encrypt(self, plaintext, key_name, context, aad) -> tuple[bytes, bytes, bytes]: + """ + Encrypt the plainext with an AES key. + + :param plaintext: The plaintext to be encrypted. + :param key_name: The name of the key to be used. + :param context: The context to be used + If it is passed, it is used to point to the directory where the keys are stored. + In this case, it must be a JSON string in te format '{ "keys_directory":"" }'. + :param aad: The additional authenticated data to be used. + :return: The nonce, tag and ciphertext. + :rtype: tuple[bytes, bytes, bytes] + """ + key_file_name = key_name + ".bin" + key_file = self.keys_directory / key_file_name + + with open(key_file, "rb") as f: + key_data = f.read() + aesgcm = AESGCM(key_data) + nonce = os.urandom(12) + ciphertext_response = aesgcm.encrypt(nonce, plaintext, aad) + ciphertext = ciphertext_response[:-16] + tag = ciphertext_response[-16:] + + return nonce, tag, ciphertext + + +def suit_kms_factory(): + """Get a KMS object.""" + return SuitKMS() diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index 157feeed..17a28233 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -7,14 +7,15 @@ import os import cbor2 +import importlib.util +import sys from argparse import ArgumentParser from argparse import RawTextHelpFormatter from pathlib import Path from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from enum import Enum, unique -from pynrfkms.kms import KMS -import getpass +from suit_generator.suit_kms_base import SuitKMSBase @unique @@ -73,16 +74,6 @@ def __str__(self): return self.value -class EncryptionKMSBackends(Enum): - """KMS backends.""" - - VAULT = "vault" - LOCAL = "local" - - def __str__(self): - return self.value - - KEY_IDS = { SuitDomains.APPLICATION.value: 0x40022000, SuitDomains.RADIO.value: 0x40032000, @@ -91,6 +82,15 @@ def __str__(self): } +def _import_module_from_path(module_name, file_path): + # Helper function to import a python module from a file path. + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module + + class DigestGenerator: """Class to generate digests for plaintext files using specified hash algorithms.""" @@ -136,19 +136,17 @@ def __init__(self, kw_alg: SuitKWAlgorithms): self.cose_kw_alg = SuitAlgorithms.COSE_ALG_DIRECT.value pass - def init_kms_backend(self, cli_arguments): - """Initialize the KMS backend based on command line arguments.""" - if cli_arguments.kms_backend == EncryptionKMSBackends.VAULT: - self.kms = KMS(backend="vault", url=cli_arguments.kms_vault_url, token=cli_arguments.kms_token) - elif cli_arguments.kms_backend == EncryptionKMSBackends.LOCAL: - pswd = cli_arguments.kms_local_password - if pswd is None: - pswd = getpass.getpass("Enter password for local KMS backend: ") - self.kms = KMS(backend="local", dir=cli_arguments.kms_dir, password=pswd, encoding="der") - del pswd + def init_kms_backend(self, kms_script, context): + """Initialize the KMS from the provided script backend based on the passed context.""" + module_name = "SuitKMS_module" + kms_module = _import_module_from_path(module_name, kms_script) + self.kms = kms_module.suit_kms_factory() + if not isinstance(self.kms, SuitKMSBase): + raise ValueError(f"Class {type(self.kms)} does not implement the required SuitKMSBase interface") + self.kms.init_kms(context) def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, context: str): - """Generate encrypted artifacts using KMS. + """Generate encrypted artifacts using the key management system. This method reads the plaintext file, encrypts it using the specified key wrap algorithm, and returns the encrypted asset and encrypted content encryption key (CEK). @@ -169,39 +167,31 @@ def generate_kms_artifacts(self, plaintext_file_path: Path, key_name: str, conte with open(plaintext_file_path, "rb") as plaintext_file: asset_plaintext = plaintext_file.read() - encrypted_asset = None + nonce = None + tag = None + ciphertext = None encrypted_cek = None if self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value: - encrypted_asset, encrypted_cek = self.kms.aes_key_wrap( - key_name=key_name, - context=context, - plaintext=asset_plaintext, - aek_type="aes", - aad=enc_structure_encoded, - ) + raise ValueError("AES Key Wrap 256 is not supported yet") elif self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value: - encrypted_asset = self.kms.encrypt( + nonce, tag, ciphertext = self.kms.encrypt( plaintext=asset_plaintext, key_name=key_name, context=context, aad=enc_structure_encoded, ) + encrypted_asset = nonce + tag + ciphertext + return encrypted_asset, encrypted_cek def parse_encrypted_assets(self, asset_bytes): """Parse the encrypted assets to extract initialization vector, tag, and encrypted content.""" - if self.cose_kw_alg == SuitAlgorithms.COSE_ALG_A256KW.value: - init_vector = asset_bytes[:12] # the names init vector and nonce are used interchangeably in this case - # nrfkms wrap returns the encrypted data in format nonce|encrypted_data|tag - encrypted_content = asset_bytes[12:-16] - tag = asset_bytes[-16:] - elif self.cose_kw_alg == SuitAlgorithms.COSE_ALG_DIRECT.value: - # nrfkms encrypt returns the encrypted data in format nonce|tag|encrypted_data - init_vector = asset_bytes[:12] - tag = asset_bytes[12 : 12 + 16] - encrypted_content = asset_bytes[12 + 16 :] + # Encrypted data is returned in format nonce|tag|encrypted_data + init_vector = asset_bytes[:12] + tag = asset_bytes[12 : 12 + 16] + encrypted_content = asset_bytes[12 + 16 :] return init_vector, tag, encrypted_content @@ -268,12 +258,12 @@ def generate_encryption_info_and_encrypted_payload( def create_encrypt_and_generate_subparser(top_parser): """Create a subparser for the 'encrypt-and-generate' command.""" - parser = top_parser.add_parser( - "encrypt-and-generate", help="First encrypt the command using nrfkms, then generate the files." - ) + parser = top_parser.add_parser("encrypt-and-generate", help="First encrypt the payload, then generate the files.") parser.add_argument("--firmware", required=True, type=Path, help="Input, plaintext firmware.") - parser.add_argument("--key-name", required=True, type=str, help="Name of the key used to derive the key by nrfkms.") + parser.add_argument( + "--key-name", required=True, type=str, help="Name of the key used by the KMS to identify the key." + ) parser.add_argument( "--domain", required=True, @@ -283,9 +273,8 @@ def create_encrypt_and_generate_subparser(top_parser): ) parser.add_argument( "--context", - required=True, type=str, - help="Context string used to derive the key. See nrfkms documentation for more information.", + help="Any context information that should be passed to the KMS backend during initialization and encryption.", ) parser.add_argument("--output-dir", required=True, type=Path, help="Directory to store the output files") parser.add_argument( @@ -303,20 +292,10 @@ def create_encrypt_and_generate_subparser(top_parser): help="Key wrap algorithm used to wrap the CEK.", ) parser.add_argument( - "--kms-backend", - required=True, - type=EncryptionKMSBackends, - choices=list(EncryptionKMSBackends), - help="KMS backend to use.", - ) - parser.add_argument("--kms-vault-url", type=str, help='URL of the KMS vault - only if kms-backend set to "vault".') - parser.add_argument("--kms-token", type=str, help='KMS token - only if kms-backend set to "vault"') - parser.add_argument("--kms-dir", type=str, help='Local backend directory - only if kms-backend set to "local".') - parser.add_argument( - "--kms-local-password", - type=str, - help='KMS local backend password - only if kms-backend set to "local". If not provided, ' - "the script will prompt for it.", + "--kms-script", + # required=True, + default=Path(__file__).parent / "basic_kms.py", + help="Python script containing a SuitKMS class with an encrypt function - used to communicate with a KMS.", ) @@ -328,9 +307,7 @@ def create_generate_subparser(top_parser): "--encrypted-firmware", required=True, type=Path, - help="Input, encrypted firmware in form iv|tag|encrypted_firmware for kw-alg=direct " - "(as output from nrfkms encrypt) or iv|encrypted_firmware|tag for kw-alg=aes-kw256 " - "(as output from nrfkms wrap).", + help="Input, encrypted firmware in form iv|tag|encrypted_firmware", ) parser.add_argument("--encrypted-key", required=True, type=Path, help="Encrypted content/asset encryption key") parser.add_argument( @@ -366,10 +343,9 @@ def create_subparsers(parser): description="""This script allows to output artifacts needed by a SUIT envelope for encrypted firmware. It has two modes of operation: - - encrypt-and-generate: First encrypt the command using nrfkms, then generate the files. + - encrypt-and-generate: First encrypt the payload, then generate the files. - generate: Only generate files based on encrypted firmware and the encrypted content/asset encryption key. - Note the encrypted firmware should match the format generated by nrfkms iv|tag|encrypted_firmware if - kw-alg=direct is used (format output from nrfkms encrypt) or iv|encrypted_firmware|tag if kw-alg=aes-kw256 is used (format output from nrfkms wrap). + Note the encrypted firmware should match the format iv|tag|encrypted_firmware In both cases the output files are: encrypted_content.bin - encrypted content of the firmware concatenated with the tag (encrypted firmware|16 byte tag). @@ -393,7 +369,7 @@ def create_subparsers(parser): encryptor = Encryptor(arguments.kw_alg) if arguments.command == "encrypt-and-generate": - encryptor.init_kms_backend(arguments) + encryptor.init_kms_backend(arguments.kms_script, arguments.context) digest_generator = DigestGenerator(arguments.hash_alg.value) digest_generator.generate_digest_size_for_plain_text(arguments.firmware, arguments.output_dir) encrypted_asset, encrypted_cek = encryptor.generate_kms_artifacts( diff --git a/suit_generator/suit_kms_base.py b/suit_generator/suit_kms_base.py new file mode 100644 index 00000000..d6632f86 --- /dev/null +++ b/suit_generator/suit_kms_base.py @@ -0,0 +1,35 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +"""A base abstract class for any KMS implementations used by the SUIT encrypt/sign scripts.""" + +from abc import ABC, abstractmethod + + +class SuitKMSBase(ABC): + """Base abstract class for the KMS implementations.""" + + @abstractmethod + def init_kms(self, context) -> None: + """ + Initialize the KMS. + + :param context: The context to be used + """ + pass + + @abstractmethod + def encrypt(self, plaintext, key_name, context, aad) -> tuple[bytes, bytes, bytes]: + """ + Encrypt the plainext with an AES key. + + :param plaintext: The plaintext to be encrypted. + :param key_name: The name of the key to be used. + :param context: The context to be used + :param aad: The additional authenticated data to be used. + :return: The nonce, tag and ciphertext. + :rtype: tuple[bytes, bytes, bytes] + """ + pass From 7dd0e0f9baf1877690d4feb345cfaebfddec17f9 Mon Sep 17 00:00:00 2001 From: Mikko Parpala <114905865+mparpala@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:52:37 +0200 Subject: [PATCH 64/67] suit: Add nRF9280 support (#151) Add nRF9280 to suit-generator. Signed-off-by: Mikko Parpala --- ncs/Kconfig | 26 ++++---- ncs/build.py | 8 +++ suit_generator/cmd_image.py | 129 +++++++++++++++++++++++++++++++++++- 3 files changed, 148 insertions(+), 15 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index d6f7f1a4..296f9fa8 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -10,17 +10,17 @@ config SSF_SUIT_SERVICE_ENABLED config SUIT_ENVELOPE_TEMPLATE_FILENAME string "Path to the envelope template" - default "app_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY - default "rad_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY - default "app_recovery_local_envelope.yaml.jinja2" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY - default "rad_recovery_envelope.yaml.jinja2" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY + default "app_envelope.yaml.jinja2" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && !SUIT_RECOVERY + default "rad_envelope.yaml.jinja2" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && !SUIT_RECOVERY + default "app_recovery_local_envelope.yaml.jinja2" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && SUIT_RECOVERY + default "rad_recovery_envelope.yaml.jinja2" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && SUIT_RECOVERY config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" - default "application" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY - default "radio" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY - default "app_recovery_img" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY - default "rad_recovery" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY + default "application" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && !SUIT_RECOVERY + default "radio" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && !SUIT_RECOVERY + default "app_recovery" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && SUIT_RECOVERY + default "rad_recovery" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && SUIT_RECOVERY config SUIT_ENVELOPE_OUTPUT_ARTIFACT string "Name of the output merged artifact" @@ -32,7 +32,7 @@ config SUIT_RECOVERY config SUIT_LOCAL_ENVELOPE_GENERATE bool "Generate local envelope" - default y if SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF54H20_CPURAD_COMMON + default y if SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPUAPP || SOC_NRF9230_ENGB_CPURAD config SUIT_DFU_CACHE_EXTRACT_IMAGE bool "Extract firmware image to DFU cache" @@ -53,9 +53,9 @@ config SUIT_DFU_CACHE_EXTRACT_IMAGE_PARTITION config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI string "The URI used as key for the image in the DFU cache" - default "cache://application.bin" if SOC_NRF54H20_CPUAPP_COMMON && !SUIT_RECOVERY - default "cache://radio.bin" if SOC_NRF54H20_CPURAD_COMMON && !SUIT_RECOVERY - default "cache://app_recovery.bin" if SOC_NRF54H20_CPUAPP_COMMON && SUIT_RECOVERY - default "cache://rad_recovery.bin" if SOC_NRF54H20_CPURAD_COMMON && SUIT_RECOVERY + default "cache://application.bin" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && !SUIT_RECOVERY + default "cache://radio.bin" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && !SUIT_RECOVERY + default "cache://app_recovery.bin" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && SUIT_RECOVERY + default "cache://rad_recovery.bin" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && SUIT_RECOVERY endif # SUIT_DFU_CACHE_EXTRACT_IMAGE diff --git a/ncs/build.py b/ncs/build.py index e747c6a9..96818b7a 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -231,6 +231,13 @@ def get_absolute_address(node, use_offset: bool = True): default=None, help="Path to KConfig file", ) + cmd_storage_arg_parser.add_argument( + "--soc", + required=False, + type=str, + default="nrf54h20", + help="SoC device (nrf54h20 or nrf9280)", + ) cmd_update_arg_parser = subparsers.add_parser( UPDATE_CMD, help="Generate files needed for Secure Domain update", parents=[parent_parser] @@ -284,6 +291,7 @@ def get_absolute_address(node, use_offset: bool = True): storage_output_directory=arguments.storage_output_directory, storage_address=arguments.storage_address, config_file=arguments.config_file, + soc=arguments.soc, ) elif arguments.command == UPDATE_CMD: ImageCreator.create_files_for_update( diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 420a0c48..f6dd1a98 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -167,6 +167,46 @@ class EnvelopeStorage: "class_name": "nRF54H20_sys", "role": ManifestRole.SEC_SYSCTRL, }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_root", + "role": ManifestRole.APP_ROOT, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_app", + "role": ManifestRole.APP_LOCAL_1, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_app_recovery", + "role": ManifestRole.APP_RECOVERY, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_rad", + "role": ManifestRole.RAD_LOCAL_1, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_rad_recovery", + "role": ManifestRole.RAD_RECOVERY, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_nordic_top", + "role": ManifestRole.SEC_TOP, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sec", + "role": ManifestRole.SEC_SDFW, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sys", + "role": ManifestRole.SEC_SYSCTRL, + }, ] def __init__(self, base_address: int, load_defaults=True, kconfig=None): @@ -273,7 +313,9 @@ def add_envelope(self, envelope: SuitEnvelope): raise GeneratorError(f"Unable to find slot for manifest with class id {class_id.hex()}") if slot[1] < len(envelope_bytes): - raise GeneratorError(f"Unable to fit manifest with class id ({len(class_id.hex())} > {slot})") + raise GeneratorError( + f"Unable to fit manifest with class id {class_id.hex()} ({len(envelope_bytes)} > {slot[1]})" + ) if role in self._envelopes.keys(): raise GeneratorError(f"Manifest with role {role} already added") @@ -387,6 +429,79 @@ class EnvelopeStorageNrf54h20(EnvelopeStorage): ] +class EnvelopeStorageNrf9280(EnvelopeStorage): + """Class generating SUIT storage binary in upcoming format.""" + + _LAYOUT = [ + { + "role": ManifestRole.SEC_TOP, + "offset": 4096, + "size": 1536, + "domain": ManifestDomain.SECURE, + }, + { + "role": ManifestRole.SEC_SDFW, + "offset": 2048, + "size": 1024, + "domain": ManifestDomain.SECURE, + }, + { + "role": ManifestRole.SEC_SYSCTRL, + "offset": 3072, + "size": 1024, + "domain": ManifestDomain.SECURE, + }, + { + "role": ManifestRole.RAD_RECOVERY, + "offset": 8192 + 1024 * 1, + "size": 1024, + "domain": ManifestDomain.RADIO, + }, + { + "role": ManifestRole.RAD_LOCAL_1, + "offset": 8192 + 1024 * 2, + "size": 1024, + "domain": ManifestDomain.RADIO, + }, + { + "role": ManifestRole.RAD_LOCAL_2, + "offset": 8192 + 1024 * 3, + "size": 1024, + "domain": ManifestDomain.RADIO, + }, + { + "role": ManifestRole.APP_ROOT, + "offset": 12288 + 1024 * 1, + "size": 2048, + "domain": ManifestDomain.APPLICATION, + }, + { + "role": ManifestRole.APP_RECOVERY, + "offset": 12288 + 1024 * 3, + "size": 2048, + "domain": ManifestDomain.APPLICATION, + }, + { + "role": ManifestRole.APP_LOCAL_1, + "offset": 12288 + 1024 * 5, + "size": 1024, + "domain": ManifestDomain.APPLICATION, + }, + { + "role": ManifestRole.APP_LOCAL_2, + "offset": 12288 + 1024 * 6, + "size": 1024, + "domain": ManifestDomain.APPLICATION, + }, + { + "role": ManifestRole.APP_LOCAL_3, + "offset": 12288 + 1024 * 7, + "size": 1024, + "domain": ManifestDomain.APPLICATION, + }, + ] + + class ImageCreator: """Helper class for extracting data from SUIT envelope and creating hex files.""" @@ -446,8 +561,15 @@ def _create_suit_storage_files_for_boot( storage_address: int, dir_name: str, config_file: str, + soc: str = "nrf54h20", ) -> None: - storage = EnvelopeStorageNrf54h20(storage_address, kconfig=config_file) + if soc == "nrf54h20": + storage = EnvelopeStorageNrf54h20(storage_address, kconfig=config_file) + elif soc == "nrf9280": + storage = EnvelopeStorageNrf9280(storage_address, kconfig=config_file) + else: + raise GeneratorError(f"Unknown soc: {soc}") + for envelope in envelopes: storage.add_envelope(envelope) @@ -487,6 +609,7 @@ def create_files_for_boot( storage_output_directory: str, storage_address: int, config_file: str | None, + soc: str = "nrf54h20", ) -> None: """Create storage and payload hex files allowing boot execution path. @@ -494,6 +617,7 @@ def create_files_for_boot( :param storage_output_directory: directory path to store hex files with SUIT storage contents :param storage_address: address of SUIT storage :param config_file: path to KConfig file containing MPI settings + :param soc: soc in use, nrf54h20 or nrf9280 """ try: envelopes = [] @@ -509,6 +633,7 @@ def create_files_for_boot( storage_address, storage_output_directory, config_file, + soc, ) except FileNotFoundError as error: raise GeneratorError(error) From 638243b82efb7acde4e3e7cd4fcbea822a1dd9b8 Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Fri, 3 Jan 2025 09:55:34 +0100 Subject: [PATCH 65/67] Fix CI after nRF92 changes The previous changes were merged with red CI Signed-off-by: Artur Hadasz --- ncs/Kconfig | 2 +- suit_generator/cmd_image.py | 83 +++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/ncs/Kconfig b/ncs/Kconfig index 296f9fa8..076b0665 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -19,7 +19,7 @@ config SUIT_ENVELOPE_TARGET string "Target name inside the envelope templates" default "application" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && !SUIT_RECOVERY default "radio" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && !SUIT_RECOVERY - default "app_recovery" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && SUIT_RECOVERY + default "app_recovery_img" if (SOC_NRF54H20_CPUAPP_COMMON || SOC_NRF9230_ENGB_CPUAPP) && SUIT_RECOVERY default "rad_recovery" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && SUIT_RECOVERY config SUIT_ENVELOPE_OUTPUT_ARTIFACT diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index f6dd1a98..dea42305 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -167,46 +167,6 @@ class EnvelopeStorage: "class_name": "nRF54H20_sys", "role": ManifestRole.SEC_SYSCTRL, }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sample_root", - "role": ManifestRole.APP_ROOT, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sample_app", - "role": ManifestRole.APP_LOCAL_1, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sample_app_recovery", - "role": ManifestRole.APP_RECOVERY, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sample_rad", - "role": ManifestRole.RAD_LOCAL_1, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sample_rad_recovery", - "role": ManifestRole.RAD_RECOVERY, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_nordic_top", - "role": ManifestRole.SEC_TOP, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sec", - "role": ManifestRole.SEC_SDFW, - }, - { - "vendor_name": "nordicsemi.com", - "class_name": "nRF9280_sys", - "role": ManifestRole.SEC_SYSCTRL, - }, ] def __init__(self, base_address: int, load_defaults=True, kconfig=None): @@ -432,6 +392,49 @@ class EnvelopeStorageNrf54h20(EnvelopeStorage): class EnvelopeStorageNrf9280(EnvelopeStorage): """Class generating SUIT storage binary in upcoming format.""" + _CLASS_ROLE_ASSIGNMENTS = [ + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_root", + "role": ManifestRole.APP_ROOT, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_app", + "role": ManifestRole.APP_LOCAL_1, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_app_recovery", + "role": ManifestRole.APP_RECOVERY, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_rad", + "role": ManifestRole.RAD_LOCAL_1, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sample_rad_recovery", + "role": ManifestRole.RAD_RECOVERY, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_nordic_top", + "role": ManifestRole.SEC_TOP, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sec", + "role": ManifestRole.SEC_SDFW, + }, + { + "vendor_name": "nordicsemi.com", + "class_name": "nRF9280_sys", + "role": ManifestRole.SEC_SYSCTRL, + }, + ] + _LAYOUT = [ { "role": ManifestRole.SEC_TOP, From f07d308f03e039bc73767c145f4cff4a70e74442 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Tue, 10 Dec 2024 13:00:55 +0100 Subject: [PATCH 66/67] manifest: Allow to use int as MFST_VAR values Allow to set content using integer values inside manifest templates. Ref: NCSDK-30807 Signed-off-by: Tomasz Chyrowicz --- suit_generator/suit/manifest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/suit_generator/suit/manifest.py b/suit_generator/suit/manifest.py index 0750fe14..61868c63 100644 --- a/suit_generator/suit/manifest.py +++ b/suit_generator/suit/manifest.py @@ -278,6 +278,12 @@ class SuitParameterInvokeArgs(SuitKeyValue): ) +class SuitParameterContent(SuitUnion): + """Abstract element to define possible sub-elements.""" + + _metadata = Metadata(children=[cbstr(SuitUint), SuitBstr]) + + class SuitParameters(SuitKeyValue): """Representation of SUIT parameters.""" @@ -290,7 +296,7 @@ class SuitParameters(SuitKeyValue): suit_parameter_strict_order: SuitBool, suit_parameter_soft_failure: SuitBool, suit_parameter_image_size: SuitImageSize, - suit_parameter_content: SuitBstr, + suit_parameter_content: SuitParameterContent, suit_parameter_encryption_info: SuitEncryptionInfo, suit_parameter_uri: SuitTstr, suit_parameter_source_component: SuitUint, From f26d3e0295fd9fa73c3fc2ea7f70a3d96e36a44e Mon Sep 17 00:00:00 2001 From: Artur Hadasz Date: Mon, 16 Dec 2024 08:32:16 +0100 Subject: [PATCH 67/67] Encryption changes needed for NCS build system Ref: NCSDK-30935 Signed-off-by: Artur Hadasz --- ncs/Kconfig | 47 +++++++ ncs/app_envelope_encrypted.yaml.jinja2 | 170 ------------------------- ncs/basic_kms.py | 21 ++- ncs/build.py | 8 -- ncs/encrypt_script.py | 52 ++++---- 5 files changed, 86 insertions(+), 212 deletions(-) delete mode 100644 ncs/app_envelope_encrypted.yaml.jinja2 diff --git a/ncs/Kconfig b/ncs/Kconfig index 076b0665..d4d709d3 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -59,3 +59,50 @@ config SUIT_DFU_CACHE_EXTRACT_IMAGE_URI default "cache://rad_recovery.bin" if (SOC_NRF54H20_CPURAD_COMMON || SOC_NRF9230_ENGB_CPURAD) && SUIT_RECOVERY endif # SUIT_DFU_CACHE_EXTRACT_IMAGE + +config SUIT_ENVELOPE_TARGET_ENCRYPT + bool "Encrypt the target image" + +if SUIT_ENVELOPE_TARGET_ENCRYPT + +config SUIT_ENVELOPE_TARGET_ENCRYPT_STRING_KEY_ID + string "The string key ID used to identify the encryption key on the device" + default "FWENC_APPLICATION_GEN1" if SOC_NRF54H20_CPUAPP_COMMON + default "FWENC_RADIOCORE_GEN1" if SOC_NRF54H20_CPURAD_COMMON + help + This string is translated to the numeric KEY ID by the encryption script + +config SUIT_ENVELOPE_TARGET_ENCRYPT_KEY_NAME + string "Name of the key used for encryption - to identify the key in the KMS" + default SUIT_ENVELOPE_TARGET_ENCRYPT_STRING_KEY_ID + +choice SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG + prompt "Algorithm used to calculate the digest of the plaintext firmware" + default SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA256 + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA256 + bool "Use the SHA-256 algorithm" + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA384 + bool "Use the SHA-384 algorithm" + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA512 + bool "Use the SHA-512 algorithm" + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHAKE128 + bool "Use the SHAKE128 algorithm" + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHAKE256 + bool "Use the SHAKE256 algorithm" + +endchoice + +config SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_NAME + string + default "sha-256" if SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA256 + default "sha-384" if SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA384 + default "sha-512" if SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHA512 + default "shake128" if SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHAKE128 + default "shake256" if SUIT_ENVELOPE_TARGET_ENCRYPT_PLAINTEXT_HASH_ALG_SHAKE256 + +endif # SUIT_ENVELOPE_TARGET_ENCRYPT \ No newline at end of file diff --git a/ncs/app_envelope_encrypted.yaml.jinja2 b/ncs/app_envelope_encrypted.yaml.jinja2 deleted file mode 100644 index 8c42801a..00000000 --- a/ncs/app_envelope_encrypted.yaml.jinja2 +++ /dev/null @@ -1,170 +0,0 @@ -{%- set mpi_application_vendor_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_VENDOR_NAME']|default('nordicsemi.com') %} -{%- set mpi_application_class_name = sysbuild['config']['SB_CONFIG_SUIT_MPI_APP_LOCAL_1_CLASS_NAME']|default('nRF54H20_sample_app') %} -SUIT_Envelope_Tagged: - suit-authentication-wrapper: - SuitDigest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-manifest: - suit-manifest-version: 1 -{%- if APP_LOCAL_1_SEQ_NUM is defined %} - suit-manifest-sequence-number: {{ APP_LOCAL_1_SEQ_NUM }} -{%- elif DEFAULT_SEQ_NUM is defined %} - suit-manifest-sequence-number: {{ DEFAULT_SEQ_NUM }} -{%- else %} - suit-manifest-sequence-number: 1 -{%- endif %} - suit-common: - suit-components: - - - MEM - - {{ application['dt'].label2node['cpu'].unit_addr }} - - {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }} - - {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }} - - - CAND_IMG - - 0 - suit-shared-sequence: - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-vendor-identifier: - RFC4122_UUID: {{ mpi_application_vendor_name }} - suit-parameter-class-identifier: - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file_direct: {{ application['encryption_artifacts_dir'] }}/plain_text_digest.bin - suit-parameter-image-size: - file_direct: {{ application['encryption_artifacts_dir'] }}/plain_text_size.txt - - suit-condition-vendor-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-condition-class-identifier: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-validate: - - suit-directive-set-component-index: 0 - # In the case of streaming operations it is worth to retry them at least once. - # This increases the robustness against bit flips on the transport, - # for example when storing the data on an external memory device. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-invoke: - - suit-directive-set-component-index: 0 - - suit-directive-invoke: - - suit-send-record-failure - -{%- if APP_LOCAL_1_VERSION is defined %} - suit-current-version: {{ APP_LOCAL_1_VERSION }} -{%- elif DEFAULT_VERSION is defined %} - suit-current-version: {{ DEFAULT_VERSION }} -{%- endif %} - - suit-install: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: -{%- if 'CONFIG_SUIT_IMAGE_DFU_CACHE_URI' in application['config'] and application['config']['CONFIG_SUIT_IMAGE_DFU_CACHE_URI'] != '' %} - suit-parameter-uri: '{{ application['config']['CONFIG_SUIT_IMAGE_DFU_CACHE_URI'] }}' -{%- else %} - suit-parameter-uri: '#{{ application['name'] }}' -{%- endif %} - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-directive-set-component-index: 0 - - suit-directive-override-parameters: - suit-parameter-source-component: 1 - suit-parameter-encryption-info: - file: {{ application['encryption_artifacts_dir'] }}/suit_encryption_info.bin - # When copying the data it is worth to retry the sequence of - # suit-directive-copy and suit-condition-image-match at least once. - # If a bit flip occurs, it might be due to a transport issue, not - # a corrupted candidate image. In this case the bit flip is recoverable - # and it is worth retrying the operation. - # The suit-directive-try-each will complete on the first successful subsequence. - - suit-directive-try-each: - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-directive-copy: - - suit-send-record-failure - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - suit-text: - suit-digest-algorithm-id: cose-alg-sha-256 - - suit-candidate-verification: - - suit-directive-set-component-index: 1 - - suit-directive-override-parameters: - suit-parameter-uri: '#{{ application['name'] }}' - suit-parameter-image-digest: - suit-digest-algorithm-id: cose-alg-sha-256 - suit-digest-bytes: - file: {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin - - suit-directive-fetch: - - suit-send-record-failure - - suit-directive-try-each: - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - - suit-condition-image-match: - - suit-send-record-success - - suit-send-record-failure - - suit-send-sysinfo-success - - suit-send-sysinfo-failure - - suit-manifest-component-id: - - INSTLD_MFST - - RFC4122_UUID: - namespace: {{ mpi_application_vendor_name }} - name: {{ mpi_application_class_name }} - suit-text: - en: - '["MEM", {{ application['dt'].label2node['cpu'].unit_addr }}, {{ get_absolute_address(application['dt'].chosen_nodes['zephyr,code-partition']) }}, {{ application['dt'].chosen_nodes['zephyr,code-partition'].regs[0].size }}]': - suit-text-vendor-name: Nordic Semiconductor ASA - suit-text-model-name: nRF54H20_cpuapp - suit-text-vendor-domain: nordicsemi.com - suit-text-model-info: The nRF54H20 application core - suit-text-component-description: Sample application core encrypted FW - suit-text-component-version: v1.0.0 -{%- if 'CONFIG_SUIT_EXTRACT_IMAGE_TO_DFU_CACHE' not in application['config'] or application['config']['CONFIG_SUIT_EXTRACT_IMAGE_TO_DFU_CACHE'] == '' %} - suit-integrated-payloads: - '#{{ application['name'] }}': {{ application['encryption_artifacts_dir'] }}/encrypted_content.bin -{%- endif %} diff --git a/ncs/basic_kms.py b/ncs/basic_kms.py index 6ccebb23..95456051 100644 --- a/ncs/basic_kms.py +++ b/ncs/basic_kms.py @@ -22,8 +22,21 @@ def parse_context(self, context): self.keys_directory = Path(__file__).parent return None - context_loaded = json.loads(context) - self.keys_directory = Path(context_loaded["keys_directory"]) + # Check if context is a valid path + context_path = Path(context) + if context_path.is_dir(): + self.keys_directory = context_path + return + + try: + context_loaded = json.loads(context) + except json.JSONDecodeError: + raise ValueError(f"The provided context '{context}' is neither a valid path nor a valid JSON string.") + + try: + self.keys_directory = Path(context_loaded["keys_directory"]) + except KeyError: + raise ValueError(f"The provided json context '{context}' does not contain the 'keys_directory' key.") def init_kms(self, context) -> None: """ @@ -35,13 +48,13 @@ def init_kms(self, context) -> None: def encrypt(self, plaintext, key_name, context, aad) -> tuple[bytes, bytes, bytes]: """ - Encrypt the plainext with an AES key. + Encrypt the plaintext with an AES key. :param plaintext: The plaintext to be encrypted. :param key_name: The name of the key to be used. :param context: The context to be used If it is passed, it is used to point to the directory where the keys are stored. - In this case, it must be a JSON string in te format '{ "keys_directory":"" }'. + It can either be a path or a JSON string in the format '{ "keys_directory":"" }'. :param aad: The additional authenticated data to be used. :return: The nonce, tag and ciphertext. :rtype: tuple[bytes, bytes, bytes] diff --git a/ncs/build.py b/ncs/build.py index 96818b7a..329a7ce8 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -40,12 +40,6 @@ def read_configurations(configurations): # Parse obligatory arguments name, binary, edt, kconfig = args[:4] - # Parse optional arguments - if len(args) > 4: - encryption_artifacts_dir = args[4] - else: - encryption_artifacts_dir = None - edt_data = None if edt: with open(edt, "rb") as edt_handler: @@ -69,8 +63,6 @@ def read_configurations(configurations): if binary: data[image_name]["filename"] = pathlib.Path(binary).name data[image_name]["binary"] = binary - if encryption_artifacts_dir: - data[image_name]["encryption_artifacts_dir"] = encryption_artifacts_dir data["get_absolute_address"] = get_absolute_address return data diff --git a/ncs/encrypt_script.py b/ncs/encrypt_script.py index 17a28233..e200dff5 100644 --- a/ncs/encrypt_script.py +++ b/ncs/encrypt_script.py @@ -39,18 +39,6 @@ class SuitIds(Enum): COSE_IV = 5 -class SuitDomains(Enum): - """Suit domains.""" - - APPLICATION = "application" - RADIO = "radio" - CELL = "cell" - WIFI = "wifi" - - def __str__(self): - return self.value - - class SuitDigestAlgorithms(Enum): """Suit digest algorithms.""" @@ -75,10 +63,14 @@ def __str__(self): KEY_IDS = { - SuitDomains.APPLICATION.value: 0x40022000, - SuitDomains.RADIO.value: 0x40032000, - SuitDomains.CELL.value: 0x40042000, - SuitDomains.WIFI.value: 0x40062000, + "FWENC_APPLICATION_GEN1": 0x40022000, + "FWENC_APPLICATION_GEN2": 0x40022001, + "FWENC_RADIOCORE_GEN1": 0x40032000, + "FWENC_RADIOCORE_GEN2": 0x40032001, + "FWENC_CELL_GEN1": 0x40042000, + "FWENC_CELL_GEN2": 0x40042001, + "FWENC_WIFICORE_GEN1": 0x40062000, + "FWENC_WIFICORE_GEN2": 0x40062001, } @@ -203,7 +195,7 @@ def generate_encrypted_payload(self, encrypted_content, tag, output_directory: P with open(os.path.join(output_directory, "encrypted_content.bin"), "wb") as file: file.write(tag + encrypted_content) - def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_directory: Path): + def generate_suit_encryption_info(self, iv, encrypted_cek, string_key_id, output_directory: Path): """Generate the SUIT encryption information file. This method creates a CBOR-encoded SUIT encryption information structure and writes it to a binary file. @@ -229,7 +221,7 @@ def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_direct # unprotected { SuitIds.COSE_ALG.value: self.cose_kw_alg, - SuitIds.COSE_KEY_ID.value: cbor2.dumps(KEY_IDS[domain]), + SuitIds.COSE_KEY_ID.value: cbor2.dumps(KEY_IDS[string_key_id]), }, # ciphertext encrypted_cek, @@ -244,7 +236,7 @@ def generate_suit_encryption_info(self, iv, encrypted_cek, domain, output_direct file.write(encryption_info) def generate_encryption_info_and_encrypted_payload( - self, encrypted_asset: Path, encrypted_cek: Path, output_directory: Path, domain: str + self, encrypted_asset: Path, encrypted_cek: Path, output_directory: Path, string_key_id: str ): """Generate encryption information and encrypted payload files. @@ -253,7 +245,7 @@ def generate_encryption_info_and_encrypted_payload( """ init_vector, tag, encrypted_content = self.parse_encrypted_assets(encrypted_asset) self.generate_encrypted_payload(encrypted_content, tag, output_directory) - self.generate_suit_encryption_info(init_vector, encrypted_cek, domain, output_directory) + self.generate_suit_encryption_info(init_vector, encrypted_cek, string_key_id, output_directory) def create_encrypt_and_generate_subparser(top_parser): @@ -265,11 +257,12 @@ def create_encrypt_and_generate_subparser(top_parser): "--key-name", required=True, type=str, help="Name of the key used by the KMS to identify the key." ) parser.add_argument( - "--domain", + "--string-key-id", required=True, - type=SuitDomains, - choices=list(SuitDomains), - help="The SoC domain of the firmware. Used to determine the key ID.", + type=str, + choices=KEY_IDS.keys(), + metavar="STRING_KEY_ID", + help="The string key ID used to identify the key on the device - translated to a numeric KEY ID.", ) parser.add_argument( "--context", @@ -293,7 +286,6 @@ def create_encrypt_and_generate_subparser(top_parser): ) parser.add_argument( "--kms-script", - # required=True, default=Path(__file__).parent / "basic_kms.py", help="Python script containing a SuitKMS class with an encrypt function - used to communicate with a KMS.", ) @@ -311,11 +303,11 @@ def create_generate_subparser(top_parser): ) parser.add_argument("--encrypted-key", required=True, type=Path, help="Encrypted content/asset encryption key") parser.add_argument( - "--domain", + "--string-key-id", required=True, - type=SuitDomains, - choices=list(SuitDomains), - help="The SoC domain of the firmware. Used to determine the key ID.", + type=str, + choices=KEY_IDS.keys(), + help="The string key ID used to identify the key on the device - translated to a numeric KEY ID.", ) parser.add_argument( "--kw-alg", @@ -383,5 +375,5 @@ def create_subparsers(parser): encrypted_cek = file.read() encryptor.generate_encryption_info_and_encrypted_payload( - encrypted_asset, encrypted_cek, arguments.output_dir, arguments.domain.value + encrypted_asset, encrypted_cek, arguments.output_dir, arguments.string_key_id )