From c03935ea7ec7f5b391d5d5e25aec7373c7333a60 Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Wed, 21 Feb 2024 16:29:04 +0100 Subject: [PATCH] [nrf fromlist][nrfconnect] Refactor of the factory data support Refactored Factory Data Support: - Added some useful tips to the Factory Data Guide. - The SPAKE2+ verifier is now generated by default with each build. - The Test Certification Declaration can now be generated separately and no longer requires the generation of the DAC and PAI certificates. - The Rotating Device ID Unique ID can be used and generated only if the CONFIG_CHIP_ROTATING_DEVICE_ID is set to 'y'. --- config/nrfconnect/chip-module/Kconfig | 6 + .../chip-module/generate_factory_data.cmake | 43 +++--- .../nrfconnect_factory_data_configuration.md | 93 +++++++++---- .../generate_nrfconnect_chip_factory_data.py | 131 ++++++++++-------- 4 files changed, 174 insertions(+), 99 deletions(-) diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index b664bfa867..ba726adab2 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -206,6 +206,11 @@ choice CHIP_FACTORY_DATA_CERT_SOURCE endchoice +config CHIP_FACTORY_DATA_GENERATE_CD + bool "Generates Certification Declaration to the output build directory" + help + Generates the new Certification Declaration and stores it to the output build directory. + if CHIP_FACTORY_DATA_CERT_SOURCE_USER config CHIP_FACTORY_DATA_USER_CERTS_DAC_CERT @@ -230,6 +235,7 @@ endif # CHIP_FACTORY_DATA_CERT_SOURCE_USER # Configs for SPAKE2+ generation config CHIP_FACTORY_DATA_GENERATE_SPAKE2_VERIFIER bool "Generate SPAKE2+ verifier" + default y help Enables the generation of the SPAKE2+ verifier for the configured SPAKE2+ passcode, iteration count and salt. diff --git a/config/nrfconnect/chip-module/generate_factory_data.cmake b/config/nrfconnect/chip-module/generate_factory_data.cmake index dc1794562d..3c286fc21d 100644 --- a/config/nrfconnect/chip-module/generate_factory_data.cmake +++ b/config/nrfconnect/chip-module/generate_factory_data.cmake @@ -48,14 +48,25 @@ string(APPEND script_args "--hw_ver ${CONFIG_CHIP_DEVICE_HARDWARE_VERSION}\n") string(APPEND script_args "--hw_ver_str \"${CONFIG_CHIP_DEVICE_HARDWARE_VERSION_STRING}\"\n") # check if Rotating Device Id Unique Id should be generated -if(NOT CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID) - if(NOT DEFINED CONFIG_CHIP_DEVICE_ROTATING_DEVICE_UID) - message(FATAL_ERROR "CHIP_DEVICE_ROTATING_DEVICE_UID was not provided. To generate it use CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID=y") +if(CONFIG_CHIP_ROTATING_DEVICE_ID) + if(NOT CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID) + if(NOT DEFINED CONFIG_CHIP_DEVICE_ROTATING_DEVICE_UID) + message(FATAL_ERROR "CHIP_DEVICE_ROTATING_DEVICE_UID was not provided. To generate it use CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID=y") + else() + string(APPEND script_args "--rd_uid \"${CONFIG_CHIP_DEVICE_ROTATING_DEVICE_UID}\"\n") + endif() else() - string(APPEND script_args "--rd_uid \"${CONFIG_CHIP_DEVICE_ROTATING_DEVICE_UID}\"\n") + string(APPEND script_args "--generate_rd_uid\n") endif() -else() - string(APPEND script_args "--generate_rd_uid\n") +endif() + +if(CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_GENERATED OR CONFIG_CHIP_FACTORY_DATA_GENERATE_CD) + find_program(chip_cert_exe NAMES chip-cert REQUIRED) + string(APPEND script_args "--chip_cert_path ${chip_cert_exe}\n") +endif() + +if(CONFIG_CHIP_FACTORY_DATA_GENERATE_CD) + string(APPEND script_args "--gen_cd\n") endif() # for development purpose user can use default certs instead of generating or providing them @@ -77,10 +88,8 @@ elseif(CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_USER) string(APPEND script_args "--dac_cert \"${CONFIG_CHIP_FACTORY_DATA_USER_CERTS_DAC_CERT}\"\n") string(APPEND script_args "--dac_key \"${CONFIG_CHIP_FACTORY_DATA_USER_CERTS_DAC_KEY}\"\n") string(APPEND script_args "--pai_cert \"${CONFIG_CHIP_FACTORY_DATA_USER_CERTS_PAI_CERT}\"\n") -else() - find_program(chip_cert_exe NAMES chip-cert REQUIRED) - string(APPEND script_args "--gen_cd\n") - string(APPEND script_args "--chip_cert_path ${chip_cert_exe}\n") +elseif(CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_GENERATED) + string(APPEND script_args "--gen_certs\n") endif() # add Password-Authenticated Key Exchange parameters @@ -90,8 +99,14 @@ string(APPEND script_args "--discriminator ${CONFIG_CHIP_DEVICE_DISCRIMINATOR}\n string(APPEND script_args "--passcode ${CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE}\n") string(APPEND script_args "--include_passcode\n") string(APPEND script_args "--overwrite\n") -string(APPEND script_args "--product_finish ${CONFIG_CHIP_DEVICE_PRODUCT_FINISH}\n") +# check if spake2 verifier should be generated using script +if(NOT CONFIG_CHIP_FACTORY_DATA_GENERATE_SPAKE2_VERIFIER) + # Spake2 verifier should be provided using kConfig + string(APPEND script_args "--spake2_verifier \"${CONFIG_CHIP_DEVICE_SPAKE2_TEST_VERIFIER}\"\n") +endif() +# Product appearance +string(APPEND script_args "--product_finish ${CONFIG_CHIP_DEVICE_PRODUCT_FINISH}\n") if(CONFIG_CHIP_DEVICE_PRODUCT_COLOR) string(APPEND script_args "--product_color ${CONFIG_CHIP_DEVICE_PRODUCT_COLOR}\n") endif() @@ -100,12 +115,6 @@ if(CONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES) string(APPEND script_args "--generate_onboarding\n") endif() -# check if spake2 verifier should be generated using script -if(NOT CONFIG_CHIP_FACTORY_DATA_GENERATE_SPAKE2_VERIFIER) - # Spake2 verifier should be provided using kConfig - string(APPEND script_args "--spake2_verifier \"${CONFIG_CHIP_DEVICE_SPAKE2_TEST_VERIFIER}\"\n") -endif() - if(CONFIG_CHIP_DEVICE_ENABLE_KEY) # Add optional EnableKey that triggers user-specific action. string(APPEND script_args "--enable_key \"${CONFIG_CHIP_DEVICE_ENABLE_KEY}\"\n") diff --git a/docs/guides/nrfconnect_factory_data_configuration.md b/docs/guides/nrfconnect_factory_data_configuration.md index 7244234735..cd20aafd2d 100644 --- a/docs/guides/nrfconnect_factory_data_configuration.md +++ b/docs/guides/nrfconnect_factory_data_configuration.md @@ -30,29 +30,30 @@ data secure by applying hardware write protection.
-- [Configuring factory data for the nRF Connect examples](#configuring-factory-data-for-the-nrf-connect-examples) - - [Overview](#overview) - - [Factory data component table](#factory-data-component-table) - - [Factory data format](#factory-data-format) - - [Appearance field description](#appearance-field-description) - - [Enabling factory data support](#enabling-factory-data-support) - - [Generating factory data](#generating-factory-data) - - [Creating the factory data JSON file with the first script](#creating-the-factory-data-json-file-with-the-first-script) - - [How to set user data](#how-to-set-user-data) - - [How to handle user data](#how-to-handle-user-data) - - [Verifying using the JSON Schema tool](#verifying-using-the-json-schema-tool) - - [Option 1: Using the php-json-schema tool](#option-1-using-the-php-json-schema-tool) - - [Option 2: Using a website validator](#option-2-using-a-website-validator) - - [Option 3: Using the nRF Connect Python script](#option-3-using-the-nrf-connect-python-script) - - [Generating onboarding codes](#generating-onboarding-codes) - - [Enabling onboarding codes generation within the build system](#enabling-onboarding-codes-generation-within-the-build-system) - - [Preparing factory data partition on a device](#preparing-factory-data-partition-on-a-device) - - [Creating a factory data partition with the second script](#creating-a-factory-data-partition-with-the-second-script) - - [Building an example with factory data](#building-an-example-with-factory-data) - - [Providing factory data parameters as a build argument list](#providing-factory-data-parameters-as-a-build-argument-list) - - [Setting factory data parameters using interactive Kconfig interfaces](#setting-factory-data-parameters-using-interactive-kconfig-interfaces) - - [Programming factory data](#programming-factory-data) - - [Using own factory data implementation](#using-own-factory-data-implementation) +- [Configuring factory data for the nRF Connect examples](#configuring-factory-data-for-the-nrf-connect-examples) + - [Overview](#overview) + - [Factory data component table](#factory-data-component-table) + - [Factory data format](#factory-data-format) + - [Appearance field description](#appearance-field-description) + - [Enabling factory data support](#enabling-factory-data-support) + - [Generating factory data](#generating-factory-data) + - [Creating the factory data JSON file with the first script](#creating-the-factory-data-json-file-with-the-first-script) + - [How to set user data](#how-to-set-user-data) + - [How to handle user data](#how-to-handle-user-data) + - [Verifying using the JSON Schema tool](#verifying-using-the-json-schema-tool) + - [Option 1: Using the php-json-schema tool](#option-1-using-the-php-json-schema-tool) + - [Option 2: Using a website validator](#option-2-using-a-website-validator) + - [Option 3: Using the nRF Connect Python script](#option-3-using-the-nrf-connect-python-script) + - [Generating onboarding codes](#generating-onboarding-codes) + - [Enabling onboarding codes generation within the build system](#enabling-onboarding-codes-generation-within-the-build-system) + - [Preparing factory data partition on a device](#preparing-factory-data-partition-on-a-device) + - [Creating a factory data partition with the second script](#creating-a-factory-data-partition-with-the-second-script) + - [Building an example with factory data](#building-an-example-with-factory-data) + - [Providing factory data parameters as a build argument list](#providing-factory-data-parameters-as-a-build-argument-list) + - [Setting factory data parameters using interactive Kconfig interfaces](#setting-factory-data-parameters-using-interactive-kconfig-interfaces) + - [Default Kconfig values and developing aspects](#default-kconfig-values-and-developing-aspects) + - [Programming factory data](#programming-factory-data) + - [Using own factory data implementation](#using-own-factory-data-implementation)
@@ -272,6 +273,7 @@ To use this script, complete the following steps: ``` --chip_cert_path + --gen_certs ``` > **Note:** To generate new certificates, you need the `chip-cert` @@ -293,7 +295,7 @@ To use this script, complete the following steps: --rd_uid ``` - - Generate a new ID and provide it (): + - (optional) Generate a new ID and provide it: ``` --generate_rd_uid @@ -328,6 +330,17 @@ To use this script, complete the following steps: --product_color ``` + j. (optional) Generate Certification Declaration for testing purposes + + ``` + --chip_cert_path + --gen_cd + ``` + + > **Note:** To generate new Certification Declaration, you need the `chip-cert` + > executable. See the note at the end of this section to learn how to get + > it. + 4. Run the script using the prepared list of arguments: ``` @@ -794,6 +807,38 @@ snippet: > interfaces, read the > [Kconfig documentation](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/build/kconfig/menuconfig.html). +### Default Kconfig values and developing aspects + +Each factory data parameter has its default value reflected in the Kconfig. +The list below shows some Kconfig settings that are configured in the nRF Connect build system and have an impact on the application. +You can modify them to achieve the desired behavior of your application. + +* The device uses the test certificates located in the credentials/development/attestation/ directory, which are generated using all default values. + If you want to change the default vendor_id, product_id, vendor_name, or device_name and generate new test certificates, add the `CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_GENERATED=y` Kconfig option. + Remember to build the `chip-cert` application and add it to the system PATH. + + For developing a production-ready product, you need to write the certificates obtained during the certification process. + To do this, add `CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_USER=y` and set the appropriate paths for the following Kconfig options: + + * `CONFIG_CHIP_FACTORY_DATA_USER_CERTS_DAC_CERT` + * `CONFIG_CHIP_FACTORY_DATA_USER_CERTS_DAC_KEY` + * `CONFIG_CHIP_FACTORY_DATA_USER_CERTS_PAI_CERT` + +* By default, the SPAKE2+ verifier is generated during each example's build. This means that this value will change automatically if you change any of the following parameters: + + * `CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE` + * `CONFIG_CHIP_DEVICE_SPAKE2_SALT` + * `CONFIG_CHIP_DEVICE_SPAKE2_IT` + + You can disable the generation of the SPAKE2+ verifier by setting the `CONFIG_CHIP_FACTORY_DATA_GENERATE_SPAKE2_VERIFIER=n` Kconfig option. + Then, you will need to provide the externally-generated SPAKE2+ verifier using the `CONFIG_CHIP_DEVICE_SPAKE2_TEST_VERIFIER` Kconfig value. + +* Generating the rotating device ID unique ID is disabled by default, but you can enable it by setting the `CONFIG_CHIP_ROTATING_DEVICE_ID=y` and `CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID=y` Kconfig values. + Moreover, if you set the `CONFIG_CHIP_ROTATING_DEVICE_ID` Kconfig option to `y` and disable the `CONFIG_CHIP_DEVICE_GENERATE_ROTATING_DEVICE_UID` Kconfig option, you will need to provide it manually using the `CONFIG_CHIP_DEVICE_ROTATING_DEVICE_UID` Kconfig value. + +* You can generate the test Certification Declaration by using the `CONFIG_CHIP_FACTORY_DATA_GENERATE_CD=y` Kconfig option. + Remember to build the `chip-cert` application and add it to the system PATH. +
## Programming factory data diff --git a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py index 6c2af9dc89..7cddb766a9 100644 --- a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py +++ b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py @@ -102,7 +102,8 @@ def gen_test_certs(chip_cert_exe: str, generate_cd: bool = False, cd_type: int = 1, paa_cert_path: str = None, - paa_key_path: str = None): + paa_key_path: str = None, + generate_all_certs: bool = False): """ Generate Matter certificates according to given Vendor ID and Product ID using the chip-cert executable. To use own Product Attestation Authority certificate provide paa_cert_path and paa_key_path arguments. @@ -120,6 +121,7 @@ def gen_test_certs(chip_cert_exe: str, /credentials/test/attestation directory. paa_key_path (str, optional): provide PAA key path. Defaults to None - a path will be set to /credentials/test/attestation directory. + generate_all_certs: Generate the new DAC and PAI certificates Returns: dictionary: ["PAI_CERT": (str), @@ -136,9 +138,10 @@ def gen_test_certs(chip_cert_exe: str, attestation_certs = namedtuple("attestation_certs", ["dac_cert", "dac_key", "pai_cert"]) - log.info("Generating new certificates using chip-cert...") - if generate_cd: + + log.info("Generating new Certification Declaration using chip-cert...") + # generate Certification Declaration cmd = [chip_cert_exe, "gen-cd", "--key", CD_KEY_PATH, @@ -162,47 +165,52 @@ def gen_test_certs(chip_cert_exe: str, "DAC_KEY": output + "/DAC_key" } - # generate PAI - cmd = [chip_cert_exe, "gen-att-cert", - "-t", "i", - "-c", device_name, - "-V", hex(vendor_id), - "-C", PAA_PATH, - "-K", PAA_KEY_PATH, - "-o", new_certificates["PAI_CERT"] + ".pem", - "-O", new_certificates["PAI_KEY"] + ".pem", - "-l", str(10000), - ] - subprocess.run(cmd) - - # generate DAC - cmd = [chip_cert_exe, "gen-att-cert", - "-t", "d", - "-c", device_name, - "-V", hex(vendor_id), - "-P", hex(product_id), - "-C", new_certificates["PAI_CERT"] + ".pem", - "-K", new_certificates["PAI_KEY"] + ".pem", - "-o", new_certificates["DAC_CERT"] + ".pem", - "-O", new_certificates["DAC_KEY"] + ".pem", - "-l", str(10000), - ] - subprocess.run(cmd) - - # convert to .der files - for cert_k, cert_v in new_certificates.items(): - action_type = "convert-cert" if cert_k.find("CERT") != -1 else "convert-key" - log.info(cert_v + ".der") - cmd = [chip_cert_exe, action_type, - cert_v + ".pem", - cert_v + ".der", - "--x509-der", + if generate_all_certs: + log.info("Generating new PAI and DAC certificates using chip-cert...") + + # generate PAI + cmd = [chip_cert_exe, "gen-att-cert", + "-t", "i", + "-c", device_name, + "-V", hex(vendor_id), + "-C", PAA_PATH, + "-K", PAA_KEY_PATH, + "-o", new_certificates["PAI_CERT"] + ".pem", + "-O", new_certificates["PAI_KEY"] + ".pem", + "-l", str(10000), + ] + subprocess.run(cmd) + + # generate DAC + cmd = [chip_cert_exe, "gen-att-cert", + "-t", "d", + "-c", device_name, + "-V", hex(vendor_id), + "-P", hex(product_id), + "-C", new_certificates["PAI_CERT"] + ".pem", + "-K", new_certificates["PAI_KEY"] + ".pem", + "-o", new_certificates["DAC_CERT"] + ".pem", + "-O", new_certificates["DAC_KEY"] + ".pem", + "-l", str(10000), ] subprocess.run(cmd) - return attestation_certs(new_certificates["DAC_CERT"] + ".der", - new_certificates["DAC_KEY"] + ".der", - new_certificates["PAI_CERT"] + ".der") + # convert to .der files + for cert_k, cert_v in new_certificates.items(): + action_type = "convert-cert" if cert_k.find("CERT") != -1 else "convert-key" + log.info(cert_v + ".der") + cmd = [chip_cert_exe, action_type, + cert_v + ".pem", + cert_v + ".der", + "--x509-der", + ] + subprocess.run(cmd) + + return attestation_certs(new_certificates["DAC_CERT"] + ".der", + new_certificates["DAC_KEY"] + ".der", + new_certificates["PAI_CERT"] + ".der") + + return attestation_certs(None, None, None) class FactoryDataGenerator: @@ -234,8 +242,8 @@ def _validate_args(self): raise AssertionError("Provided wrong user data, this is not a JSON format! {}".format(e)) assert self._args.spake2_verifier or self._args.passcode, \ "Cannot find Spake2+ verifier, to generate a new one please provide passcode (--passcode)" - assert (self._args.chip_cert_path or (self._args.dac_cert and self._args.pai_cert and self._args.dac_key)), \ - "Cannot find paths to DAC or PAI certificates .der files. To generate a new ones please provide a path to chip-cert executable (--chip_cert_path)" + assert ((self._args.gen_certs and self._args.chip_cert_path) or (self._args.dac_cert and self._args.pai_cert and self._args.dac_key)), \ + "Cannot find paths to DAC or PAI certificates .der files. To generate a new ones please provide a path to chip-cert executable (--chip_cert_path) and add --gen_certs argument" assert self._args.output.endswith(".json"), \ "Output path doesn't contain .json file path. ({})".format(self._args.output) assert not (self._args.passcode in INVALID_PASSCODES), \ @@ -273,23 +281,27 @@ def generate_json(self): # convert salt to bytestring to be coherent with Spake2+ verifier type spake_2_salt = self._args.spake2_salt - if self._args.chip_cert_path: - certs = gen_test_certs(self._args.chip_cert_path, - self._args.output[:self._args.output.rfind("/")], - self._args.vendor_id, - self._args.product_id, - self._args.vendor_name + "_" + self._args.product_name, - self._args.gen_cd, - self._args.cd_type, - self._args.paa_cert, - self._args.paa_key) - dac_cert = certs.dac_cert - pai_cert = certs.pai_cert - dac_key = certs.dac_key - else: + certs = gen_test_certs(self._args.chip_cert_path, + self._args.output[:self._args.output.rfind("/")], + self._args.vendor_id, + self._args.product_id, + self._args.vendor_name + "_" + self._args.product_name, + self._args.gen_cd, + self._args.cd_type, + self._args.paa_cert, + self._args.paa_key, + self._args.gen_certs) + + dac_cert = certs.dac_cert + pai_cert = certs.pai_cert + dac_key = certs.dac_key + + if not dac_cert: dac_cert = self._args.dac_cert - dac_key = self._args.dac_key + if not pai_cert: pai_cert = self._args.pai_cert + if not dac_key: + dac_key = self._args.dac_key # try to read DAC public and private keys dac_priv_key = get_raw_private_key_der(dac_key, self._args.dac_key_password) @@ -364,6 +376,7 @@ def _add_entry(self, name: str, value: any): def _generate_spake2_verifier(self): """ If verifier has not been provided in arguments list it should be generated via external script """ + log.info("Generating SPAKE2+ Verifier...") return generate_verifier(self._args.passcode, self._args.spake2_salt, self._args.spake2_it) def _generate_rotating_device_uid(self): @@ -479,6 +492,8 @@ def base64_str(s): return base64.b64decode(s) "This option requires a path to chip-cert executable." "By default you can find chip-cert in connectedhomeip/src/tools/chip-cert directory " "and build it there.")) + optional_arguments.add_argument("--gen_certs", action="store_true", + help="Generate a new DAC nad PAI certificates") optional_arguments.add_argument("--dac_cert", type=str, help="[.der] Provide the path to .der file containing DAC certificate.") optional_arguments.add_argument("--dac_key", type=str,