diff --git a/ncs/Kconfig b/ncs/Kconfig index 076b066..a22670a 100755 --- a/ncs/Kconfig +++ b/ncs/Kconfig @@ -59,3 +59,51 @@ 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 "String name of the algorithm used to calculate the digest of the plaintext firmware" + 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 8c42801..0000000 --- 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 6ccebb2..9545605 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 96818b7..329a7ce 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 17a2823..b92fb71 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 )