Skip to content

Commit

Permalink
[nrfconnect] Added onboarding codes generation to the factory data sc…
Browse files Browse the repository at this point in the history
…ript (#25879)

Generate onboarding parameters:
 - manual pairing code
 - QRCode using
Nrfconnect factory data script.

To generate onboarding parameters add --generate_onboarding to the
generate_nrfconnect_chip_factory_data.py script.

Added generation of onboarding parameters to the CMAKE script
  • Loading branch information
ArekBalysNordic authored and pull[bot] committed Sep 23, 2023
1 parent 6f58b45 commit 1266402
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/examples-nrfconnect.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ jobs:
run: scripts/run_in_build_env.sh "python3 scripts/setup/nrfconnect/update_ncs.py --check"
- name: Run unit tests of factory data generation script
timeout-minutes: 15
run: scripts/run_in_build_env.sh "./scripts/tools/nrfconnect/tests/test_generate_factory_data.py"
run: |
scripts/run_in_build_env.sh 'pip3 install -r scripts/setup/requirements.nrfconnect.txt'
scripts/run_in_build_env.sh "./scripts/tools/nrfconnect/tests/test_generate_factory_data.py"
- name: Build example nRF Connect SDK Lock App on nRF52840 DK
if: github.event_name == 'push' || steps.changed_paths.outputs.nrfconnect == 'true'
timeout-minutes: 15
Expand Down
8 changes: 8 additions & 0 deletions config/nrfconnect/chip-module/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ config CHIP_FACTORY_DATA_MERGE_WITH_FIRMWARE
when flashing the firmware using the west tool, includes the factory data
as well.

config CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES
bool "Generate onboarding codes during the generation of a factory data set"
help
Enables generation of onboarding codes (manual pairing code and QR code)
during the generation of a factory data set. You can provide the
onboarding codes a Matter controller to commission a device to a Matter
network.

# Select source of the certificates
choice CHIP_FACTORY_DATA_CERT_SOURCE
prompt "Attestation certificate file source"
Expand Down
4 changes: 4 additions & 0 deletions config/nrfconnect/chip-module/generate_factory_data.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ string(APPEND script_args "--passcode ${CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE}\n")
string(APPEND script_args "--include_passcode\n")
string(APPEND script_args "--overwrite\n")

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
Expand Down
55 changes: 55 additions & 0 deletions docs/guides/nrfconnect_factory_data_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ data secure by applying hardware write protection.
- [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)
Expand Down Expand Up @@ -493,6 +495,59 @@ as an additional argument. To do this, complete the following steps:
> **Note:** To learn more about the JSON schema, visit
> [this unofficial JSON Schema tool usage website](https://json-schema.org/understanding-json-schema/).
### Generating onboarding codes
The
[generate_nrfconnect_chip_factory_data.py](../../scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py)
script lets you generating a manual code and a QR code from the given factory
data parameters. You can use these codes to perform commissioning to the Matter
network over Bluetooth LE since they include all the pairing data required by
the Matter controller. You can place these codes on the device packaging or on
the device itself during production.
To generate a manual pairing code and a QR code, complete the following steps:
1. Install all required Python dependencies for Matter:
```
$ python -m pip install -r ./scripts/setup/requirements.nrfconnect.txt
```
2. Complete steps 1, 2, and 3 from the
[Creating the factory data JSON file with the first script](#creating-the-factory-data-json-file-with-the-first-script)
section to prepare the final invocation of the Python script.
3. Add the `--generate_onboarding` argument to the Python script final
invocation.
4. Run the script.
5. Navigate to the output directory provided as the `-o` argument.
The output directory contains the following files you need:
- JSON file containing the latest factory data set.
- Test file containing the generated manual code and the text version of the
QR Code.
- PNG file containing the generated QR Code as an image.
#### Enabling onboarding codes generation within the build system
You can generate onboarding codes using the nRF Connect platform build system
described in
[Building an example with factory data](#building-an-example-with-factory-data),
and build an example with the following additional option:
`-DCONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES=y`.
For example, the build command for the nRF52840 DK could look like this:
```
$ west build -b nrf52840dk_nrf52840 -- \
-DCONFIG_CHIP_FACTORY_DATA=y \
-DCONFIG_CHIP_FACTORY_DATA_BUILD=y \
-DCONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES=y
```
### Preparing factory data partition on a device
The factory data partition is an area in the device's persistent storage where a
Expand Down
5 changes: 4 additions & 1 deletion scripts/setup/requirements.nrfconnect.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
jsonschema>=4.4.0
cbor2>=5.4.3
ecdsa>=0.18.0
ecdsa>=0.18.0
qrcode==7.4.2
bitarray==2.6.0
python_stdnum==1.18
41 changes: 41 additions & 0 deletions scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_der_private_key

try:
import qrcode
from generate_setup_payload import CommissioningFlow, SetupPayload
except ImportError:
SDK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
sys.path.append(os.path.join(SDK_ROOT, "src/setup_payload/python"))
try:
import qrcode
from generate_setup_payload import CommissioningFlow, SetupPayload
except ModuleNotFoundError or ImportError:
no_onboarding_modules = True
else:
no_onboarding_modules = False
else:
no_onboarding_modules = False

try:
import jsonschema
except ImportError:
Expand Down Expand Up @@ -327,6 +343,9 @@ def generate_json(self):
except IOError:
log.error("Cannot save output file into directory: {}".format(self._args.output))

if self._args.generate_onboarding:
self._generate_onboarding_data()

def _add_entry(self, name: str, value: any):
""" Add single entry to list of tuples ("key", "value") """
if (isinstance(value, bytes) or isinstance(value, bytearray)):
Expand Down Expand Up @@ -375,6 +394,19 @@ def _process_der(self, path: str):
log.error(e)
raise e

def _generate_onboarding_data(self):
setup_payload = SetupPayload(discriminator=self._args.discriminator,
pincode=self._args.passcode,
rendezvous=2, # fixed pairing BLE
flow=CommissioningFlow.Standard,
vid=self._args.vendor_id,
pid=self._args.product_id)
with open(self._args.output[:-len(".json")] + ".txt", "w") as manual_code_file:
manual_code_file.write("Manualcode : " + setup_payload.generate_manualcode() + "\n")
manual_code_file.write("QRCode : " + setup_payload.generate_qrcode())
qr = qrcode.make(setup_payload.generate_qrcode())
qr.save(self._args.output[:-len(".json")] + ".png")


def main():
parser = argparse.ArgumentParser(description="NrfConnect Factory Data NVS generator tool")
Expand Down Expand Up @@ -481,6 +513,9 @@ def base64_str(s): return base64.b64decode(s)
help=("Provide a path to the Product Attestation Authority (PAA) key to generate "
"the PAI certificate. Without providing it, a testing PAA key stored in the Matter "
"repository will be used."))
optional_arguments.add_argument("--generate_onboarding", action="store_true",
help=("Generate a Manual Code and QR Code according to provided factory data set."
"As a result a PNG image containing QRCode and a .txt file containing Manual Code will be available within output directory"))
args = parser.parse_args()

if args.verbose:
Expand All @@ -501,6 +536,12 @@ def base64_str(s): return base64.b64decode(s)
"-r ./scripts/requirements.nrfconnect.txt from the Matter root directory."))
return

if args.generate_onboarding and no_onboarding_modules:
log.error(("Requested generation of onboarding codes, but the some modules are not installed. \n"
"Install all dependencies for Matter by invoking: pip3 install "
"-r ./scripts/requirements.nrfconnect.txt from the Matter root directory."))
return

generator = FactoryDataGenerator(args)
generator.generate_json()

Expand Down
49 changes: 49 additions & 0 deletions scripts/tools/nrfconnect/tests/test_generate_factory_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import subprocess
import tempfile
import unittest
from os.path import exists

TOOLS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))

Expand Down Expand Up @@ -120,6 +121,10 @@
0xde, 0x3d, 0xc0, 0x14, 0x3a, 0x97, 0xe1, 0x35, 0x38, 0xf7, 0xff, 0x76,
0x05, 0x5e, 0xbf, 0x27, 0x90, 0x6f, 0x50, 0x0f])

TEST_MANUAL_CODE = "Manualcode : 35442608082"

TEST_QR_CODE = "QRCode : MT:KAYA3EYF15ND8B1OA00"


def write_file(path: str, content: bytes) -> None:
with open(path, 'wb') as f:
Expand Down Expand Up @@ -258,6 +263,50 @@ def test_generate_spake2p_verifier_default(self):
'--raw'
])

def test_generate_onboarding_codes(self):
with tempfile.TemporaryDirectory() as outdir:
write_file(os.path.join(outdir, 'DAC_key.der'), DAC_DER_KEY)
write_file(os.path.join(outdir, 'DAC_cert.der'), DAC_DER_CERT)
write_file(os.path.join(outdir, 'PAI_cert.der'), PAI_DER_CERT)

subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'generate_nrfconnect_chip_factory_data.py'),
'-s', os.path.join(TOOLS_DIR, 'nrfconnect_factory_data.schema'),
'--include_passcode',
'--sn', 'SN:12345678',
'--vendor_id', '0x127F',
'--product_id', '0xABCD',
'--vendor_name', 'Nordic Semiconductor ASA',
'--product_name', 'Lock Gen2',
'--part_number', 'PCA10056',
'--product_url', 'https://example.com/lock',
'--product_label', 'Lock',
'--date', '2022-07-20',
'--hw_ver', '101',
'--hw_ver_str', 'v1.1',
'--dac_key', os.path.join(outdir, 'DAC_key.der'),
'--dac_cert', os.path.join(outdir, 'DAC_cert.der'),
'--pai_cert', os.path.join(outdir, 'PAI_cert.der'),
'--spake2_it', '2000',
'--spake2_salt', 'U1BBS0UyUCBLZXkgU2FsdA==',
'--passcode', '13243546',
'--spake2_verifier', ('WN0SgEXLfUN19BbJqp6qn4pS69EtdNLReIMZwv/CIM0ECMP7ytiAJ7txIYJ0Ovlha/'
'rQ3E+88mj3qaqqnviMaZzG+OyXEdSocDIT9ZhmkTCgWwERaHz4Vdh3G37RT6kqbw=='),
'--discriminator', '0xFED',
'--rd_uid', '91a9c12a7c80700a31ddcfa7fce63e44',
'--enable_key', '00112233445566778899aabbccddeeff',
'--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
'-o', os.path.join(outdir, 'fd.json'),
'--generate_onboarding'
])

self.assertTrue(exists(os.path.join(outdir, 'fd.txt')))
self.assertTrue(exists(os.path.join(outdir, 'fd.png')))

with open(os.path.join(outdir, 'fd.txt'), 'r') as onboarding_code_file:
onboarding = onboarding_code_file.readlines()
self.assertEqual(onboarding[0][:-1], TEST_MANUAL_CODE)
self.assertEqual(onboarding[1], TEST_QR_CODE)


if __name__ == '__main__':
unittest.main()

0 comments on commit 1266402

Please sign in to comment.