Skip to content

Commit

Permalink
[nrfconnect] Refactor of the factory data support (project-chip#32354)
Browse files Browse the repository at this point in the history
* [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'.

* Restyled by prettier-markdown

---------

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
ArekBalysNordic and restyled-commits authored Mar 5, 2024
1 parent 3d47e35 commit 7af8313
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 76 deletions.
6 changes: 6 additions & 0 deletions config/nrfconnect/chip-module/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
43 changes: 26 additions & 17 deletions config/nrfconnect/chip-module/generate_factory_data.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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")
Expand Down
64 changes: 63 additions & 1 deletion docs/guides/nrfconnect_factory_data_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ data secure by applying hardware write protection.
- [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)

Expand Down Expand Up @@ -272,6 +273,7 @@ To use this script, complete the following steps:
```
--chip_cert_path <path to chip-cert executable>
--gen_certs
```
> **Note:** To generate new certificates, you need the `chip-cert`
Expand All @@ -293,7 +295,7 @@ To use this script, complete the following steps:
--rd_uid <rotating device ID unique ID>
```
- Generate a new ID and provide it ():
- (optional) Generate a new ID and provide it:
```
--generate_rd_uid
Expand Down Expand Up @@ -328,6 +330,17 @@ To use this script, complete the following steps:
--product_color <color>
```
j. (optional) Generate Certification Declaration for testing purposes
```
--chip_cert_path <path to chip-cert executable>
--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:
```
Expand Down Expand Up @@ -794,6 +807,55 @@ 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 the
`CONFIG_CHIP_FACTORY_DATA_CERT_SOURCE_USER=y` Kconfig option 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.
<hr>
## Programming factory data
Expand Down
131 changes: 73 additions & 58 deletions scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)<path to PAI cert .der file>,
Expand All @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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), \
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 7af8313

Please sign in to comment.