Removed mfg_tool, related cleanups, and documentation update

`tools/mfg_tool` is moved to esp-matter-tools repo: https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool.

It is released on pypi: https://pypi.org/project/esp-matter-mfg-tool and can be installed by running `pip install esp-matter-mfg-tool`
This commit is contained in:
Shubham Patil
2024-06-07 19:11:35 +08:00
committed by Hrishikesh Dhayagude
parent d2912848b3
commit e6d74cc2e7
14 changed files with 57 additions and 1494 deletions
+5
View File
@@ -1,3 +1,8 @@
# 07-May-2024
- `tools/mfg_tool.py` is moved to https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool
and can be installed by running `python3 -m pip install esp-matter-mfg-tool`.
# 14-February-2024 # 14-February-2024
- An optional argument, `max_val_size`, has been introduced to the `esp_matter::attribute::create()` API. - An optional argument, `max_val_size`, has been introduced to the `esp_matter::attribute::create()` API.
+11 -7
View File
@@ -48,7 +48,7 @@ A test CD signed by the test CD signing keys in `connectedhomeip <https://github
For Matter Certification Test, vendors should generate their own test Product Attestation Authority (PAA) certificate, Product Attestation Intermediate (PAI) certificate, and Device Attestation Certificate (DAC), but not use the default test PAA certificate in `connectedhomeip <https://github.com/espressif/connectedhomeip/tree/v1.0.0.2/credentials/test/attestation>`__ SDK repository. So you need to generate a PAA certificate, upload it to `TestNet <https://testnet.iotledger.io/>`__ following the instruction in `DCL Primer <https://groups.csa-iot.org/wg/matter-tsg/document/24705>`__, and use it to sign and attest PAI certificates which will be used to sign and attest the DACs. The PAI certificate, DAC, and DAC's private key should be stored in the product you submit to test. For Matter Certification Test, vendors should generate their own test Product Attestation Authority (PAA) certificate, Product Attestation Intermediate (PAI) certificate, and Device Attestation Certificate (DAC), but not use the default test PAA certificate in `connectedhomeip <https://github.com/espressif/connectedhomeip/tree/v1.0.0.2/credentials/test/attestation>`__ SDK repository. So you need to generate a PAA certificate, upload it to `TestNet <https://testnet.iotledger.io/>`__ following the instruction in `DCL Primer <https://groups.csa-iot.org/wg/matter-tsg/document/24705>`__, and use it to sign and attest PAI certificates which will be used to sign and attest the DACs. The PAI certificate, DAC, and DAC's private key should be stored in the product you submit to test.
Here are the steps to generate the certificates and keys using `chip-cert <https://github.com/espressif/connectedhomeip/tree/v1.0.0.2/src/tools/chip-cert/README.md>`__ and :project_file:`mfg_tool<tools/mfg_tool/README.md>`. Here are the steps to generate the certificates and keys using `chip-cert`_ and `esp-matter-mfg-tool`_.
3.2.2.1 Generating PAA Certificate 3.2.2.1 Generating PAA Certificate
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -68,29 +68,29 @@ Generate the vendor scoped PAA certificate and key, please make sure to change t
3.2.2.2 Generating Factory Partition Binary Files 3.2.2.2 Generating Factory Partition Binary Files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After getting the PAA certificate and key, the factory partition binary files with PAI certificate, DAC, and DAC keys can be generated using mfg_tool. After getting the PAA certificate and key, the factory partition binary files with PAI certificate, DAC, and DAC keys can be generated using esp-matter-mfg-tool.
- Install the requirements and export the dependent tools path if not done already - Install the requirements and export the dependent tools path if not done already
:: ::
cd path/to/esp_matter/tools/mfg_tool cd path/to/esp_matter
python3 -m pip install -r requirements.txt python3 -m pip install -r requirements.txt
export PATH=$PATH:$PWD/../../connectedhomeip/connectedhomeip/out/host export PATH=$PATH:$PWD/connectedhomeip/connectedhomeip/out/host
- Generate factory partition binary files - Generate factory partition binary files
:: ::
./mfg_tool.py -n <count> -cn Espressif --paa -c /path/to/PAA_certificate -k /path/to/PAA_key \ esp-matter-mfg-tool -n <count> -cn Espressif --paa -c /path/to/PAA_certificate -k /path/to/PAA_key \
-cd /path/to/CD_file -v 0x131B --vendor_name Espressif -p 0x1234 \ -cd /path/to/CD_file -v 0x131B --vendor_name Espressif -p 0x1234 \
--product-name Test-light --hw-ver 1 --hw-ver-str v1.0 --product-name Test-light --hw-ver 1 --hw-ver-str v1.0
.. note:: .. note::
For more information about the arguments, you can use ``./mfg_tool.py --help`` For more information about the arguments, you can use ``esp-matter-mfg-tool --help``
The option ``-n`` (count) is the number of generated binaries. In the above command, mfg_tool will generate PAI certificate and key and then use them to generate ``count`` different DACs and keys. It will use the generated certificates and keys to generate ``count`` factory partition binaries with different DACs, discriminators, and setup pincodes. Flash the factory binary to the device's NVS partition. Then the device will send the vendor's PAI certificate and DAC to the commissioner during commissioning. The option ``-n`` (count) is the number of generated binaries. In the above command, esp-matter-mfg-tool will generate PAI certificate and key and then use them to generate ``count`` different DACs and keys. It will use the generated certificates and keys to generate ``count`` factory partition binaries with different DACs, discriminators, and setup pincodes. Flash the factory binary to the device's NVS partition. Then the device will send the vendor's PAI certificate and DAC to the commissioner during commissioning.
3.2.2.3 Using Vendor's PAA in Test Harness(TH) 3.2.2.3 Using Vendor's PAA in Test Harness(TH)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -329,3 +329,7 @@ Here are some issues that you might meet in Matter Certification Test and quick
- ``TC-SU-2.7`` - ``TC-SU-2.7``
The StateTransition event ``Applying`` might be missed because the OTA reboot time is too short. You can cherry-pick the commit from the `fixing Pull Request <https://github.com/project-chip/connectedhomeip/pull/24379>`__ to fix the issue. The StateTransition event ``Applying`` might be missed because the OTA reboot time is too short. You can cherry-pick the commit from the `fixing Pull Request <https://github.com/project-chip/connectedhomeip/pull/24379>`__ to fix the issue.
.. _`esp-matter-mfg-tool`: https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool
.. _`chip-cert`: https://github.com/espressif/connectedhomeip/tree/master/src/tools/chip-cert/README.md
+9 -10
View File
@@ -423,8 +423,7 @@ If QR code is not visible, paste the below link into the browser and scan the QR
https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT:Y.K9042C00KA0648G00 https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT:Y.K9042C00KA0648G00
If you want to use different values for commissioning the device, please use the If you want to use different values for commissioning the device, please use the
`mfg-tool <https://github.com/espressif/esp-matter/tree/main/tools/mfg_tool#readme>`__ `esp-matter-mfg-tool`_ to generate the factory partition which has to be flashed on the device.
to generate the factory partition which has to be flashed on the device.
It also generates the new pairing code and QR code image using which you can commission the device. It also generates the new pairing code and QR code image using which you can commission the device.
2.3.1.2 Post Commissioning Setup 2.3.1.2 Post Commissioning Setup
@@ -1065,15 +1064,15 @@ Export the dependent tools path
:: ::
cd esp-matter/tools/mfg_tool cd esp-matter
export PATH=$PATH:$PWD/../../connectedhomeip/connectedhomeip/out/host export PATH=$PATH:$PWD/connectedhomeip/connectedhomeip/out/host
Generate the factory partition, please use the APPROPRIATE values for ``-v`` (Vendor Id), ``-p`` (Product Id), and ``-cd`` (Certification Declaration). Generate the factory partition, please use the APPROPRIATE values for ``-v`` (Vendor Id), ``-p`` (Product Id), and ``-cd`` (Certification Declaration).
:: ::
./mfg_tool.py --passcode 89674523 \ esp-matter-mfg-tool --passcode 89674523 \
--discriminator 2245 \ --discriminator 2245 \
-cd TEST_CD_FFF1_8001.der \ -cd TEST_CD_FFF1_8001.der \
-v 0xFFF1 --vendor-name Espressif \ -v 0xFFF1 --vendor-name Espressif \
@@ -1186,18 +1185,17 @@ This cluster provides an interface for controlling a characteristic of a device
This attribute is the list of supported modes that may be selected for the CurrentMode attribute. Each item in this list represents a unique mode as indicated by the Mode field of the ModeOptionStruct. Each entry in this list SHALL have a unique value for the Mode field. This attribute is the list of supported modes that may be selected for the CurrentMode attribute. Each item in this list represents a unique mode as indicated by the Mode field of the ModeOptionStruct. Each entry in this list SHALL have a unique value for the Mode field.
ESP_MATTER uses factory partition to set the values of Supported Modes attribute. ESP_MATTER uses factory partition to set the values of Supported Modes attribute.
2.9.2 Generate Factory Partition Using mfg_tool 2.9.2 Generate Factory Partition Using esp-matter-mfg-tool
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use `mfg_tool <https://github.com/espressif/esp-matter/blob/main/tools/mfg_tool/README.md>`__ to generate factory partition of the supported modes attribute. Use `esp-matter-mfg-tool`_ to generate factory partition of the supported modes attribute.
2.9.2.1 Usage 2.9.2.1 Usage
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
:: ::
cd tools/mfg_tool esp-matter-mfg-tool -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \ -k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \ -c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \ -cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
@@ -1457,3 +1455,4 @@ The controller example offers two options for the Attestation Trust Storage whic
.. _`step by step installation guide`: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html .. _`step by step installation guide`: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html
.. _`Prerequisites for ESP-IDF`: https://docs.espressif.com/projects/esp-idf/en/v5.0.1/esp32/get-started/index.html#step-1-install-prerequisites .. _`Prerequisites for ESP-IDF`: https://docs.espressif.com/projects/esp-idf/en/v5.0.1/esp32/get-started/index.html#step-1-install-prerequisites
.. _`Prerequisites for Matter`: https://github.com/espressif/connectedhomeip/blob/v1.1-branch/docs/guides/BUILDING.md#prerequisites .. _`Prerequisites for Matter`: https://github.com/espressif/connectedhomeip/blob/v1.1-branch/docs/guides/BUILDING.md#prerequisites
.. _`esp-matter-mfg-tool`: https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool
+2 -2
View File
@@ -160,7 +160,7 @@ How to use Rotating Device Identifier
- Enable the Rotating Device Identifier support in menuconfig. - Enable the Rotating Device Identifier support in menuconfig.
- Add the ``--enable-rotating-device-id`` and add the ``--rd-id-uid`` to specify the ``Rotating ID Unique ID`` - Add the ``--enable-rotating-device-id`` and add the ``--rd-id-uid`` to specify the ``Rotating ID Unique ID``
when use the mfg_tool.py to generate partition.bin file. when use the ``esp-matter-mfg-tool`` to generate partition.bin file.
Difference between Rotating ID Unique ID and Unique ID Difference between Rotating ID Unique ID and Unique ID
@@ -232,7 +232,7 @@ is not released after the commissioning process, and the free RAM won't go up.
A1.10 How to generate Matter Onboarding Codes (QR Code and Manual Pairing Code) A1.10 How to generate Matter Onboarding Codes (QR Code and Manual Pairing Code)
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
When creating a factory partition using ``mfg_tool.py``, both the QR code and manual pairing codes are generated. When creating a factory partition using ``esp-matter-mfg-tool``, both the QR code and manual pairing codes are generated.
Along with that, there are two more methods for generating Matter onboarding codes: Along with that, there are two more methods for generating Matter onboarding codes:
+14 -11
View File
@@ -84,8 +84,9 @@ assists an *OTA requestor* to get upgraded. The SDK examples support Matter OTA
requestor role out of the box. The OTA provider could be a manufacturer specific requestor role out of the box. The OTA provider could be a manufacturer specific
phone app or any Matter node that has internet connectivity. phone app or any Matter node that has internet connectivity.
Alternatively, `ESP RainMaker OTA <https://rainmaker.espressif.com/docs/ota.html>`__ Alternatively, `ESP RainMaker OTA`_ service can also be used to upgrade the firmware
service can also be used to upgrade the firmware on the devices remotely. As opposed to the Matter OTA, ESP RainMaker OTA allows you the flexibility of delivering the OTA upgrades incrementally or to groups of devices. on the devices remotely. As opposed to the Matter OTA, ESP RainMaker OTA allows you
the flexibility of delivering the OTA upgrades incrementally or to groups of devices.
4.3 Manufacturing 4.3 Manufacturing
@@ -101,13 +102,12 @@ For commissioning a device into the Matter Fabric, the device requires the follo
- **Spake2+ parameters**: work as a proof of possession. - **Spake2+ parameters**: work as a proof of possession.
These details are generally programmed in the manufacturing partition that is unique These details are generally programmed in the manufacturing partition that is unique
per device. ESP-Matter provides a utility (mfg_tool.py) to create these partition images per device. ESP-Matter provides a utility (esp-matter-mfg-tool) to create these partition images
on a per-device basis for mass manufacturing purposes. on a per-device basis for mass manufacturing purposes.
When using the utility, by default, the above details will be included in the generated manufacturing partition image. The utility also has a provision to include additional details in the same image by using CSV files. When using the utility, by default, the above details will be included in the generated manufacturing partition image. The utility also has a provision to include additional details in the same image by using CSV files.
Details about using the mass manufacturing utility can be found here: Details about using the mass manufacturing utility can be found here: `esp-matter-mfg-tool`_
:project_file:`mfg_tool<tools/mfg_tool/README.md>`.
4.3.2 Pre-Provisioned Modules 4.3.2 Pre-Provisioned Modules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -120,8 +120,8 @@ programming the partition into the device at your end.
Please contact your Espressif contact person for more information. Please contact your Espressif contact person for more information.
4.3.3 The mfg_tool Example 4.3.3 The esp-matter-mfg-tool Example
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Espressif Matter Prep-provisioning modules, the DAC key pair, DAC and PAI certificates are pre-flashed by default. In Espressif Matter Prep-provisioning modules, the DAC key pair, DAC and PAI certificates are pre-flashed by default.
@@ -139,25 +139,25 @@ This is the example to generate factory images after pre-provisioning:
:: ::
./mfg_tool.py -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25 --passcode 19861989 --discriminator 601 --serial-num esp32c_dev3 esp-matter-mfg-tool -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25 --passcode 19861989 --discriminator 601 --serial-num esp32c_dev3
- **Generate multiple generic factory images** - **Generate multiple generic factory images**
:: ::
./mfg_tool.py -n 10 -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25 esp-matter-mfg-tool -n 10 -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25
- **Generate factory image with rotating device unique identify** - **Generate factory image with rotating device unique identify**
:: ::
./mfg_tool.py -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25 --passcode 19861989 --discriminator 601 --serial-num esp32c_dev3 --enable-rotating-device-id --rd-id-uid c0398f4980b07c9460f71c5421e1a3c5 esp-matter-mfg-tool -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --mfg-date 2022-10-25 --passcode 19861989 --discriminator 601 --serial-num esp32c_dev3 --enable-rotating-device-id --rd-id-uid c0398f4980b07c9460f71c5421e1a3c5
- **Generate multiple factory images with csv and mcsv** - **Generate multiple factory images with csv and mcsv**
:: ::
./mfg_tool.py -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --enable-rotating-device-id --mfg-date 2022-10-25 --csv mfg.csv --mcsv mfg_m.csv esp-matter-mfg-tool -cd ~/test_cert/CD/Chip-CD-131B-1000.der -v 0x131B --vendor-name ESP -p 0x1000 --product-name light --hw-ver 1 --hw-ver-str v1.0 --enable-rotating-device-id --mfg-date 2022-10-25 --csv mfg.csv --mcsv mfg_m.csv
- **The example of csv and mcsv file** - **The example of csv and mcsv file**
- CSV: - CSV:
@@ -173,3 +173,6 @@ This is the example to generate factory images after pre-provisioning:
| esp32c_dev6,c0398f4980b07c9460f71c5421e1a3c8,1237 | esp32c_dev6,c0398f4980b07c9460f71c5421e1a3c8,1237
| esp32c_dev7,c0398f4980b07c9460f71c5421e1a3c9,1238 | esp32c_dev7,c0398f4980b07c9460f71c5421e1a3c9,1238
.. _`esp-matter-mfg-tool`: https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool
.. _`ESP RainMaker OTA`: https://rainmaker.espressif.com/docs/ota.html
-5
View File
@@ -28,11 +28,6 @@ echo "Exit Matter environment"
echo "" echo ""
deactivate deactivate
echo ""
echo "Installing python dependencies for mfg_tool"
echo ""
python3 -m pip install -r ${ESP_MATTER_PATH}/tools/mfg_tool/requirements.txt
echo "" echo ""
echo "Installing python dependencies for Matter" echo "Installing python dependencies for Matter"
echo "" echo ""
+3
View File
@@ -6,3 +6,6 @@ jinja2==3.0.1
# https://github.com/psf/requests/blob/main/HISTORY.md#2300-2023-05-03 # https://github.com/psf/requests/blob/main/HISTORY.md#2300-2023-05-03
urllib3<2 urllib3<2
# mfg_tool
esp-matter-mfg-tool
@@ -92,7 +92,6 @@ pipeline {
catchError() { catchError() {
script { script {
def esp_matter = load "${WORKSPACE}/tools/jenkins/esp_matter.groovy" def esp_matter = load "${WORKSPACE}/tools/jenkins/esp_matter.groovy"
esp_matter.script_artifacts_create()
esp_matter.tools_artifacts_create() esp_matter.tools_artifacts_create()
} }
} }
-43
View File
@@ -166,49 +166,6 @@ def firmware_build_save() {
''' '''
} }
def script_artifacts_create() {
sh '''
PACKAGE_SCRIPT_PATH=${PACKAGE_PATH}/Script
SCRIPTS_DIRECTORY_NAME=manufacturing_scripts
SCRIPTS_PATH=${PACKAGE_SCRIPT_PATH}/${SCRIPTS_DIRECTORY_NAME}
mkdir -p ${SCRIPTS_PATH}
# esp-idf
mkdir -p ${SCRIPTS_PATH}/esp-idf
mkdir -p ${SCRIPTS_PATH}/esp-idf/components
mkdir -p ${SCRIPTS_PATH}/esp-idf/components/nvs_flash
cp -r ${IDF_PATH}/components/nvs_flash/nvs_partition_generator ${SCRIPTS_PATH}/esp-idf/components/nvs_flash/
mkdir -p ${SCRIPTS_PATH}/esp-idf/tools
cp -r ${IDF_PATH}/tools/mass_mfg ${SCRIPTS_PATH}/esp-idf/tools/
# esp-matter
mkdir -p ${SCRIPTS_PATH}/esp-matter
mkdir -p ${SCRIPTS_PATH}/esp-matter/tools
cp -r ${ESP_MATTER_PATH}/tools/mfg_tool ${SCRIPTS_PATH}/esp-matter/tools/
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/scripts
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/scripts/tools
cp -r ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip/scripts/tools/spake2p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/scripts/tools
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/src
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/src/setup_payload
cp -r ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip/src/setup_payload/python ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/src/setup_payload
mkdir -p ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/credentials
cp -r ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip/credentials/test ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/credentials
cp -r ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip/credentials/production ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/credentials
cp -r ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip/credentials/development ${SCRIPTS_PATH}/esp-matter/connectedhomeip/connectedhomeip/credentials
# script_description
mkdir -p ${SCRIPTS_PATH}/script_description
cp ${SCRIPTS_PATH}/esp-matter/tools/mfg_tool/requirements.txt ${SCRIPTS_PATH}/script_description/
cp ${SCRIPTS_PATH}/esp-matter/tools/mfg_tool/README.md ${SCRIPTS_PATH}/script_description/
'''
}
def tools_artifacts_create() { def tools_artifacts_create() {
sh ''' sh '''
PACKAGE_TOOLS_PATH=${PACKAGE_PATH}/Tools PACKAGE_TOOLS_PATH=${PACKAGE_PATH}/Tools
+13 -220
View File
@@ -1,227 +1,20 @@
# Manufacturing Partition Generator Utility # Manufacturing Partition Generator Utility
## Dependencies `mfg_tool` is a python utility to help generate the matter manufacturing partitions.
* [CHIP Certificate Tool](https://github.com/project-chip/connectedhomeip/tree/master/src/tools/chip-cert),
*chip-cert* provides command line interface (CLI) utility used for generating and manipulating CHIP certificates and CHIP private keys.
* [SPAKE2+](https://github.com/project-chip/connectedhomeip/tree/master/scripts/tools/spake2p), This tool has been moved to [esp-matter-tools](https://github.com/espressif/esp-matter-tools)
*spake2p.py* is used for generating PAKE verifier for device manufacturing provisioning. repository and is released on PyPi (Python Package Index) as
[esp-matter-mfg-tool](https://github.com/espressif/esp-matter-tools).
* [Setup Payload](https://github.com/project-chip/connectedhomeip/tree/master/src/setup_payload/), #### Installing esp-matter-mfg-tool
*generate_setup_payload.py* is used for generating onboarding payloads, QR code and manual pairing code.
* [Mass Manufacturing Utility](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#manufacturing-utility),
*mfg_gen.py* is used for creating factory NVS partition images.
### Install python dependencies
``` ```
cd path/to/esp-matter/tools/mfg_tool python3 -m pip install esp-matter-mfg-tool
source path/to/esp-idf/export.sh
python3 -m pip install -r requirements.txt
``` ```
#### Usage examples
`esp-matter-mfg-tool -h` lists the mandatory as well as optional arguments.
### [Build and setup tools in Matter SDK](https://github.com/project-chip/connectedhomeip/blob/master/docs/guides/BUILDING.md#build-for-the-host-os-linux-or-macos) Other usage examples can be found in
[esp-matter-tools](https://github.com/espressif/esp-matter-tools/tree/main/mfg_tool#usage-examples)
#### Build chip-cert repository
```
cd path/to/esp-matter/connectedhomeip/connectedhomeip
source scripts/activate.sh
gn gen out/host
ninja -C out/host
```
Above commands will generate chip-cert at `esp-matter/connectedhomeip/connectedhomeip/out/host`.
#### Add the tools path to $PATH
```
export PATH="$PATH:path/to/esp-matter/connectedhomeip/connectedhomeip/out/host"
```
## Configure your app
Open the project configuration menu using -
```
cd <your_app>
idf.py menuconfig
```
In the configuration menu, set the following additional configuration to use custom factory partition and different values for Data and Device Info Providers.
1. Enable **`ESP32 Factory Data Provider`** [Component config → CHIP Device Layer → Commissioning options → Use ESP32 Factory Data Provider]
> Enable config option [`CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER`](https://github.com/project-chip/connectedhomeip/blob/master/config/esp32/components/chip/Kconfig#L645) to use ESP32 specific implementation of CommissionableDataProvider and DeviceAttestationCredentialsProvider.
2. Enable **`ESP32 Device Instance Info Provider`** [Component config → CHIP Device Layer → Commissioning options → Use ESP32 Device Instance Info Provider]
> Enable config option [`ENABLE_ESP32_DEVICE_INSTANCE_INFO_PROVIDER`](https://github.com/project-chip/connectedhomeip/blob/master/config/esp32/components/chip/Kconfig#L655) to get device instance info from factory partition.
3. Enable **`Attestation - Factory`** [ Component config → ESP Matter → DAC Provider options → Attestation - Factory]
> Enable config option `CONFIG_FACTORY_PARTITION_DAC_PROVIDER` to use DAC certificates from the factory partition during Attestation.
4. Set **`chip-factory namespace partition label`** [Component config → CHIP Device Layer → Matter Manufacturing Options → chip-factory namespace partition label]
> Set config option [`CHIP_FACTORY_NAMESPACE_PARTITION_LABEL`](https://github.com/project-chip/connectedhomeip/blob/master/config/esp32/components/chip/Kconfig#L856) to choose the label of the partition to store key-values in the "chip-factory" namespace. The default chosen partition label is `nvs`.
### mfg_gen.py
`mfg_gen.py` is present at path `$IDF_PATH/tools/mass_mfg/mfg_gen.py`
## Output files and directory structure
```
out
└── fff1_8000
├── 11fe2c53-9a38-445c-b58f-2ff0554cd981
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-onb_codes.csv
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-partition.bin
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-qrcode.png
│   └── internal
│   ├── DAC_cert.der
│   ├── DAC_cert.pem
│   ├── DAC_key.pem
│   ├── DAC_private_key.bin
│   ├── DAC_public_key.bin
│   ├── PAI_cert.der
│   └── partition.csv
├── 14874525-30b5-4c66-a00e-30e4af5dfb20
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-onb_codes.csv
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-partition.bin
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-qrcode.png
│   └── internal
│   ├── DAC_cert.der
│   ├── DAC_cert.pem
│   ├── DAC_key.pem
│   ├── DAC_private_key.bin
│   ├── DAC_public_key.bin
│   ├── PAI_cert.der
│   └── partition.csv
└── staging
├── config.csv
├── master.csv
├── pai_cert.der
└── pin_disc.csv
```
Tool generates following output files:
- Partition Binary : `<uuid>-partition.bin`
- Onboarding codes : `<uuid>-onb_codes.csv`
- QR Code image : `<uuid>-qrcode.png`
Other intermediate files are stored in `internal/` directory:
- Partition CSV : `partition.csv`
- PAI Certificate : `PAI_cert.der`
- DAC Certificates : `DAC_cert.der`, `DAC_cert.pem`
- DAC Private Key : `DAC_private_key.bin`
- DAC Public Key : `DAC_public_key.bin`
Above files are stored at `out/<vid_pid>/<UUID>`. Each device is identified with an unique UUID.
Common intermediate files are stored at `out/<vid_pid>/staging`.
## Usage examples
`mfg_tool.py -h` lists the mandatory as well as optional arguments.
Below commands uses the test PAI signing certificate and key, test certificate declaration present in Matter SDK, Vendor ID: 0xFFF2, and Product ID: 0x8001.
### Generate a factory partition
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
```
### Generate a factory partition and store DAC certificate and private key in secure cert partition [Optional argument : `--dac-in-secure-cert` and `--target`]
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
--dac-in-secure-cert --target esp32
```
*NOTE*: By default, DAC certificates and private key is stored in the NVS factory partition.
### Generate a factory partition and store DAC certificate and private key in secure cert partition using DS Peripheral
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
--dac-in-secure-cert --ds-peripheral --target esp32h2 --efuse-key-id 1
```
*NOTE*: Currently, only esp32h2 supports DS peripheral.
### Generate 5 factory partitions [Optional argument : `-n`]
```
./mfg_tool.py -n 5 -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
```
### Generate factory partition using existing DAC certificate and private key [Optional arguments : `--dac-cert` and `--dac-key`]
```
./mfg_tool.py -cn "My Bulb" -v 0xFFF2 -p 0x8001 --pai \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--dac-key DAC_key.pem --dac-cert DAC_cert.pem
```
### Generate factory partitions using existing Passcode, Discriminator, and rotating device ID [Optional arguments : `--passcode`, `--discriminator`, and `--rd-id-uid`]
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--passcode 20202021 --discriminator 3840 --enable-rotating-device-id --rd-id-uid d2f351f57bb9387445a5f92a601d1c14
```
* NOTE: Script generates only one factory partition if **DAC or Discriminator or Passcode or Rotating-Device-ID** is specified.
### Generate factory partitions with extra NVS key-values specified using csv and mcsv file [Optional arguments : `--csv` and `--mcsv`]
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--csv extra_nvs_key_config.csv --mcsv extra_nvs_key_value.csv
```
Above command will generate `n` number of partitions. Where `n` is the rows in the mcsv file.
Output binary contains all the chip specific key/value and key/values specified using `--csv` and `--mcsv` option.
### Generate factory partitions without device attestation certificates and keys
```
./mfg_tool.py -v 0xFFF2 -p 0x8001 \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
```
* NOTE: These factory partitions are only for firmwares with other ways to get the certificates and sign message with the private key.
## Flashing the manufacturing binary
Please note that `mfg_tool.py` only generates manufacturing binary images which need to be flashed onto device using `esptool.py`.
* Flashing a binary image to the device
```
esptool.py -p <serial_port> write_flash <address> path/to/<uuid>-partition.bin
```
* NOTE: First flash your app firmware and then followed by the custom partition binary on the device. Please flash the manufacturing binary at the corresponding address of the configured factory partition set by [`CHIP_FACTORY_NAMESPACE_PARTITION_LABEL`](https://github.com/project-chip/connectedhomeip/blob/master/config/esp32/components/chip/Kconfig#L856) which by default is `nvs`.
## Commissioning the device
You can commission the device by using either -
1. The QR code for Matter commissioners is generated at `out/<vid_pid>/<uuid>/<uuid>-qrcode.png`. If QR code is not visible, paste the below link into the browser replacing `<qr_code>` with the **QR code string** (eg. `MT:Y.K9042C00KA0648G00` - this is also the default test QR code) and scan the QR code.
```
https://project-chip.github.io/connectedhomeip/qrcode.html?data=<qr_code>
```
2. Refer the [docs](https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#commissioning-and-control) for other methods using onboarding payload found at `out/<vid_pid>/<uuid>/<uuid>-onb_codes.csv`. This contains the `QR Code String, Manual Pairing Code, Passcode and Discriminator`.
## Encrypting NVS partition
Below are the steps for encrypting the application and factory partition but before proceeding further please READ THE DOCS FIRST. Documentation References:
- [Flash and NVS encryption](https://github.com/project-chip/connectedhomeip/blob/master/docs/guides/esp32/flash_nvs_encryption.md#flash-and-nvs-encryption)
Provide `-e` option along with other options to generate the encrypted NVS partition binary.
It will generate additional partition binary (`<uuid>-keys-partition.bin`) containing the key for decrypting encrypted partition.
- Flash the partition binary containing factory data, as NVS encryption works differently, please flash is without `--encrypt` option
```
esptool.py -p (PORT) write_flash (FACTORY_PARTITION_ADDR) path/to/factory_partition.bin
```
- Flash the partition binary containing encryption keys, these SHALL be flashed with `--encrypt` option
```
esptool.py -p (PORT) write_flash --encrypt (NVS_KEYS_PARTITION_ADDR) path/to/nvs_key_partition.bin
```
-115
View File
@@ -1,115 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This file contains the CHIP specific key along with the data type and encoding format
"""
# This map contains mandatory CHIP specific key along with the data type and encoding format
# Additionally to add more keys to chip-factory, use chip_factory_append() API
import csv
CHIP_NVS_MAP = {
'chip-factory': {
# Commissionable Data
'discriminator': {
'type': 'data',
'encoding': 'u32',
'value': None,
},
'iteration-count': {
'type': 'data',
'encoding': 'u32',
'value': None,
},
'salt': {
'type': 'data',
'encoding': 'string',
'value': None,
},
}
}
def get_dict(key, type, encoding, value):
return {
key: {
'type': type,
'encoding': encoding,
'value': value,
}
}
def get_namespace_dict(namespace):
return {
namespace: {
}
}
def chip_nvs_get_config_csv():
csv_data = ''
for k, v in CHIP_NVS_MAP.items():
csv_data += k + ',' + 'namespace,' + '\n'
for k1, v1 in v.items():
csv_data += k1 + ',' + v1['type'] + ',' + v1['encoding'] + '\n'
return csv_data
def chip_factory_append(key, type, encoding, value):
CHIP_NVS_MAP['chip-factory'].update(get_dict(key, type, encoding, value))
def chip_factory_update(key, value):
CHIP_NVS_MAP['chip-factory'][key]['value'] = value
def chip_nvs_map_update(namespace, key, type, encoding, value):
CHIP_NVS_MAP[namespace].update(get_dict(key, type, encoding, value))
def chip_nvs_map_append_config_csv(csv_path):
with open(csv_path, 'r') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
# Set current namespace to 'chip-factory' when the first line of the csv file is not 'XX,namespace,'
current_namespace = 'chip-factory'
for csv_data in csv_reader:
if 'namespace' in csv_data:
current_namespace = csv_data[0]
if (current_namespace not in list(CHIP_NVS_MAP.keys())):
CHIP_NVS_MAP.update(get_namespace_dict(current_namespace))
else:
chip_nvs_map_update(current_namespace, csv_data[0], csv_data[1], csv_data[2], None)
def chip_factory_get_val(key):
return CHIP_NVS_MAP['chip-factory'][key]['value']
def chip_get_keys_as_csv():
keys = list()
for ns in CHIP_NVS_MAP:
keys.extend(list(CHIP_NVS_MAP[ns]))
return ','.join(keys)
def chip_get_values_as_csv():
values = list()
for ns in CHIP_NVS_MAP:
for k in CHIP_NVS_MAP[ns]:
values.append(str(CHIP_NVS_MAP[ns][k]['value']))
return ','.join(values)
-744
View File
@@ -1,744 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Script to generate Matter factory NVS partition image, Onboarding codes, and QR codes.
"""
import os
import sys
import csv
import uuid
import shutil
import base64
import random
import logging
import binascii
import argparse
import pyqrcode
from chip_nvs import *
from utils import *
from datetime import datetime
from types import SimpleNamespace
from esp_secure_cert.tlv_format import *
if not os.getenv('IDF_PATH'):
logging.error("IDF_PATH environment variable is not set")
sys.exit(1)
if not os.getenv('ESP_MATTER_PATH'):
logging.error("ESP_MATTER_PATH environment variable is not set")
sys.exit(1)
sys.path.insert(0, os.path.join(os.getenv('ESP_MATTER_PATH'), 'connectedhomeip', 'connectedhomeip', 'scripts', 'tools', 'spake2p'))
from spake2p import generate_verifier
sys.path.insert(0, os.path.join(os.getenv('IDF_PATH'), 'tools', 'mass_mfg'))
from mfg_gen import generate
sys.path.insert(0, os.path.join(os.getenv('ESP_MATTER_PATH'), 'connectedhomeip', 'connectedhomeip', 'src', 'setup_payload', 'python'))
from generate_setup_payload import SetupPayload, CommissioningFlow
TOOLS = {
'chip-cert': None,
}
PAI = {
'cert_pem': None,
'cert_der': None,
'key_pem': None,
'key_der': None,
}
OUT_DIR = {
'top': None,
'chip': None,
}
OUT_FILE = {
'config_csv': None,
'mcsv': None,
'pin_csv': None,
'pin_disc_csv': None,
'cn_dac_csv': None
}
UUIDs = list()
def check_tools_exists(args):
# if the certs and keys are not in the generated partitions or the specific dac cert and key are used,
# the chip-cert is not needed.
if args.paa or (args.pai and (args.dac_cert is None and args.dac_key is None)):
TOOLS['chip-cert'] = shutil.which('chip-cert')
if TOOLS['chip-cert'] is None:
logging.error('chip-cert not found, please add chip-cert path to PATH environment variable')
sys.exit(1)
logging.debug('Using following tools:')
logging.debug('chip-cert: {}'.format(TOOLS['chip-cert']))
def generate_passcodes(args):
iter_count_max = 10000
salt_len_max = 32
with open(OUT_FILE['pin_csv'], 'w', newline='') as f:
writer = csv.writer(f)
if args.enable_dynamic_passcode:
writer.writerow(["Index", "Iteration Count", "Salt"])
else:
writer.writerow(["Index", "PIN Code", "Iteration Count", "Salt", "Verifier"])
for i in range(0, args.count):
salt = os.urandom(salt_len_max)
if args.enable_dynamic_passcode:
writer.writerow([i, iter_count_max, base64.b64encode(salt).decode('utf-8')])
else:
if args.passcode:
passcode = args.passcode
else:
passcode = random.randint(1, 99999998)
if passcode in INVALID_PASSCODES:
passcode -= 1
verifier = generate_verifier(passcode, salt, iter_count_max)
writer.writerow([i, passcode, iter_count_max, base64.b64encode(salt).decode('utf-8'), base64.b64encode(verifier).decode('utf-8')])
def generate_discriminators(args):
discriminators = list()
# If discriminator is provided, use it
if args.discriminator:
discriminators.append(args.discriminator)
else:
for i in range(args.count):
discriminators.append(random.randint(0x0000, 0x0FFF))
return discriminators
# Append discriminator to each line of the passcode file
def append_discriminator(discriminator):
with open(OUT_FILE['pin_csv'], 'r') as fd:
lines = fd.readlines()
lines[0] = ','.join([lines[0].strip(), 'Discriminator'])
for i in range(1, len(lines)):
lines[i] = ','.join([lines[i].strip(), str(discriminator[i - 1])])
with open(OUT_FILE['pin_disc_csv'], 'w') as fd:
fd.write('\n'.join(lines) + '\n')
os.remove(OUT_FILE['pin_csv'])
# Generates the csv file containing chip specific keys and keys provided by user in csv file
def generate_config_csv(args):
logging.info("Generating Config CSV...")
csv_data = chip_nvs_get_config_csv()
with open(OUT_FILE['config_csv'], 'w') as f:
f.write(csv_data)
def write_chip_mcsv_header(args):
logging.info('Writing chip manifest CSV header...')
mcsv_header = chip_get_keys_as_csv() + '\n'
with open(OUT_FILE['mcsv'], 'w') as f:
f.write(mcsv_header)
def append_chip_mcsv_row(row_data):
logging.info('Appending chip master CSV row...')
with open(OUT_FILE['mcsv'], 'a') as f:
f.write(row_data + '\n')
def generate_pai(args, ca_key, ca_cert, out_key, out_cert):
cmd = [
TOOLS['chip-cert'], 'gen-att-cert',
'--type', 'i',
'--subject-cn', '"{} PAI {}"'.format(args.cn_prefix, '00'),
'--out-key', out_key,
'--out', out_cert,
]
if args.lifetime:
cmd.extend(['--lifetime', str(args.lifetime)])
if args.valid_from:
cmd.extend(['--valid-from', str(args.valid_from)])
cmd.extend([
'--subject-vid', hex(args.vendor_id)[2:],
'--subject-pid', hex(args.product_id)[2:],
'--ca-key', ca_key,
'--ca-cert', ca_cert,
])
execute_cmd(cmd)
logging.info('Generated PAI certificate: {}'.format(out_cert))
logging.info('Generated PAI private key: {}'.format(out_key))
def generate_dac(iteration, args, ca_key, ca_cert):
out_key_pem = os.sep.join([OUT_DIR['top'], UUIDs[iteration], 'internal', 'DAC_key.pem'])
out_private_key_der = out_key_pem.replace('key.pem', 'key.der')
out_cert_pem = out_key_pem.replace('key.pem', 'cert.pem')
out_cert_der = out_key_pem.replace('key.pem', 'cert.der')
out_private_key_bin = out_key_pem.replace('key.pem', 'private_key.bin')
out_public_key_bin = out_key_pem.replace('key.pem', 'public_key.bin')
cmd = [
TOOLS['chip-cert'], 'gen-att-cert',
'--type', 'd',
'--subject-cn', UUIDs[iteration],
'--out-key', out_key_pem,
'--out', out_cert_pem,
]
if args.lifetime:
cmd.extend(['--lifetime', str(args.lifetime)])
if args.valid_from:
cmd.extend(['--valid-from', str(args.valid_from)])
cmd.extend(['--subject-vid', hex(args.vendor_id)[2:],
'--subject-pid', hex(args.product_id)[2:],
'--ca-key', ca_key,
'--ca-cert', ca_cert,
])
execute_cmd(cmd)
logging.info('Generated DAC certificate: {}'.format(out_cert_pem))
logging.info('Generated DAC private key: {}'.format(out_key_pem))
convert_x509_cert_from_pem_to_der(out_cert_pem, out_cert_der)
logging.info('Generated DAC certificate in DER format: {}'.format(out_cert_der))
generate_keypair_bin(out_key_pem, out_private_key_bin, out_public_key_bin)
logging.info('Generated DAC private key in binary format: {}'.format(out_private_key_bin))
logging.info('Generated DAC public key in binary format: {}'.format(out_public_key_bin))
convert_private_key_from_pem_to_der(out_key_pem, out_private_key_der)
return out_cert_der, out_private_key_bin, out_public_key_bin, out_private_key_der
def use_dac_from_args(args):
logging.info('Using DAC from command line arguments...')
logging.info('DAC Certificate: {}'.format(args.dac_cert))
logging.info('DAC Private Key: {}'.format(args.dac_key))
# There should be only one UUID in the UUIDs list if DAC is specified
out_cert_der = os.sep.join([OUT_DIR['top'], UUIDs[0], 'internal', 'DAC_cert.der'])
out_private_key_bin = out_cert_der.replace('cert.der', 'private_key.bin')
out_public_key_bin = out_cert_der.replace('cert.der', 'public_key.bin')
out_private_key_der = out_cert_der.replace('cert.der', 'key.der')
convert_x509_cert_from_pem_to_der(args.dac_cert, out_cert_der)
logging.info('Generated DAC certificate in DER format: {}'.format(out_cert_der))
generate_keypair_bin(args.dac_key, out_private_key_bin, out_public_key_bin)
logging.info('Generated DAC private key in binary format: {}'.format(out_private_key_bin))
logging.info('Generated DAC public key in binary format: {}'.format(out_public_key_bin))
convert_private_key_from_pem_to_der(args.dac_key, out_private_key_der)
return out_cert_der, out_private_key_bin, out_public_key_bin, out_private_key_der
def setup_out_dirs(vid, pid, count):
OUT_DIR['top'] = os.sep.join(['out', vid_pid_str(vid, pid)])
OUT_DIR['stage'] = os.sep.join(['out', vid_pid_str(vid, pid), 'staging'])
os.makedirs(OUT_DIR['top'], exist_ok=True)
os.makedirs(OUT_DIR['stage'], exist_ok=True)
OUT_FILE['config_csv'] = os.sep.join([OUT_DIR['stage'], 'config.csv'])
OUT_FILE['mcsv'] = os.sep.join([OUT_DIR['stage'], 'master.csv'])
OUT_FILE['pin_csv'] = os.sep.join([OUT_DIR['stage'], 'pin.csv'])
OUT_FILE['pin_disc_csv'] = os.sep.join([OUT_DIR['stage'], 'pin_disc.csv'])
OUT_FILE['cn_dac_csv'] = os.sep.join([OUT_DIR['top'], 'cn_dacs-{}.csv'.format(datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))])
# Create directories to store the generated files
for i in range(count):
uuid_str = str(uuid.uuid4())
UUIDs.append(uuid_str)
os.makedirs(os.sep.join([OUT_DIR['top'], uuid_str, 'internal']), exist_ok=True)
def generate_passcodes_and_discriminators(args):
# Generate passcodes using spake2p tool
generate_passcodes(args)
# Randomly generate discriminators
discriminators = generate_discriminators(args)
# Append discriminators to passcodes file
append_discriminator(discriminators)
def write_cn_dac_csv_header():
with open(OUT_FILE['cn_dac_csv'], 'a') as csv_file:
csv_file.write("CN,certs\n")
return
def write_csv_files(args):
generate_config_csv(args)
write_chip_mcsv_header(args)
write_cn_dac_csv_header()
def setup_root_certs(args):
# If PAA is passed as input, then generate PAI certificate
if args.paa:
# output file names
PAI['cert_pem'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.pem'])
PAI['cert_der'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.der'])
PAI['key_pem'] = os.sep.join([OUT_DIR['stage'], 'pai_key.pem'])
generate_pai(args, args.key, args.cert, PAI['key_pem'], PAI['cert_pem'])
convert_x509_cert_from_pem_to_der(PAI['cert_pem'], PAI['cert_der'])
logging.info('Generated PAI certificate in DER format: {}'.format(PAI['cert_der']))
# If PAI is passed as input, generate DACs
elif args.pai:
PAI['cert_pem'] = args.cert
PAI['key_pem'] = args.key
PAI['cert_der'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.der'])
convert_x509_cert_from_pem_to_der(PAI['cert_pem'], PAI['cert_der'])
logging.info('Generated PAI certificate in DER format: {}'.format(PAI['cert_der']))
def overwrite_values_in_mcsv(args, index):
with open(args.mcsv, 'r') as mcsvf:
mcsv_dict = list(csv.DictReader(mcsvf))[index]
with open(args.csv, 'r') as csvf:
csv_reader = csv.reader(csvf, delimiter=',')
current_namespace = 'chip-factory'
for csv_data in csv_reader:
if 'namespace' in csv_data:
current_namespace = csv_data[0]
else:
chip_nvs_map_update(current_namespace, csv_data[0], csv_data[1], csv_data[2], mcsv_dict[csv_data[0]])
def append_cn_dac_to_csv(common_name, cert_path):
with open(OUT_FILE['cn_dac_csv'], 'a') as csv_file:
with open(cert_path, 'r') as device_cert_file:
device_cert_contents = device_cert_file.read()
csv_file.write('{},"{}"\n'.format(common_name, device_cert_contents))
# This function generates the DACs, picks the commissionable data from the already present csv file,
# and generates the onboarding payloads, and writes everything to the master csv
def write_per_device_unique_data(args):
with open(OUT_FILE['pin_disc_csv'], 'r') as csvf:
pin_disc_dict = csv.DictReader(csvf)
for row in pin_disc_dict:
chip_factory_update('discriminator', row['Discriminator'])
chip_factory_update('iteration-count', row['Iteration Count'])
chip_factory_update('salt', row['Salt'])
if not args.enable_dynamic_passcode:
chip_factory_update('verifier', row['Verifier'])
if args.paa or args.pai:
if args.dac_key is not None and args.dac_cert is not None:
dacs = use_dac_from_args(args)
else:
dacs = generate_dac(int(row['Index']), args, PAI['key_pem'], PAI['cert_pem'])
if not args.dac_in_secure_cert:
chip_factory_update('dac-cert', os.path.abspath(dacs[0]))
chip_factory_update('dac-key', os.path.abspath(dacs[1]))
chip_factory_update('dac-pub-key', os.path.abspath(dacs[2]))
chip_factory_update('pai-cert', os.path.abspath(PAI['cert_der']))
else:
# esp secure cert partition
secure_cert_partition_file_path = os.sep.join([OUT_DIR['top'], UUIDs[int(row['Index'])], UUIDs[int(row['Index'])] + '_esp_secure_cert.bin'])
if args.ds_peripheral:
if args.target != "esp32h2":
logging.error("DS peripheral is only supported for esp32h2 target")
exit(1)
if args.efuse_key_id == -1:
logging.error("--efuse-key-id <value> is required when -ds or --ds-peripheral option is used")
exit(1)
priv_key = tlv_priv_key_t(key_type = tlv_priv_key_type_t.ESP_SECURE_CERT_ECDSA_PERIPHERAL_KEY,
key_path = os.path.abspath(dacs[3]), key_pass = None)
priv_key.priv_key_len = 256
priv_key.efuse_key_id = args.efuse_key_id
generate_partition_ds(priv_key = priv_key, device_cert = os.path.abspath(dacs[0]),
ca_cert = os.path.abspath(PAI['cert_der']), idf_target = args.target,
op_file = secure_cert_partition_file_path)
else:
priv_key = tlv_priv_key_t(key_type = tlv_priv_key_type_t.ESP_SECURE_CERT_DEFAULT_FORMAT_KEY,
key_path = os.path.abspath(dacs[3]), key_pass = None)
generate_partition_no_ds(priv_key = priv_key, device_cert = os.path.abspath(dacs[0]),
ca_cert = os.path.abspath(PAI['cert_der']), idf_target = args.target,
op_file = secure_cert_partition_file_path)
if args.dac_key is not None and args.dac_cert is not None:
append_cn_dac_to_csv(UUIDs[int(row['Index'])], args.dac_cert)
else:
append_cn_dac_to_csv(UUIDs[int(row['Index'])], os.sep.join([OUT_DIR['top'], UUIDs[int(row['Index'])], "internal", "DAC_cert.pem"]))
# If serial number is not passed, then generate one
if (args.serial_num is None):
chip_factory_update('serial-num', binascii.b2a_hex(os.urandom(SERIAL_NUMBER_LEN)).decode('utf-8'))
if (args.enable_rotating_device_id is True) and (args.rd_id_uid is None):
chip_factory_update('rd-id-uid', binascii.b2a_hex(os.urandom(int(ROTATING_DEVICE_ID_UNIQUE_ID_LEN_BITS / 8))).decode('utf-8'))
if (args.csv is not None and args.mcsv is not None):
overwrite_values_in_mcsv(args, int(row['Index']))
mcsv_row_data = chip_get_values_as_csv()
append_chip_mcsv_row(mcsv_row_data)
# Generate onboarding data
if not args.enable_dynamic_passcode:
generate_onboarding_data(args, int(row['Index']), int(chip_factory_get_val('discriminator')), int(row['PIN Code']))
if args.paa or args.pai:
logging.info("Generated CSV of Common Name and DAC: {}".format(OUT_FILE['cn_dac_csv']))
def organize_output_files(suffix, args):
for i in range(len(UUIDs)):
dest_path = os.sep.join([OUT_DIR['top'], UUIDs[i]])
internal_path = os.sep.join([dest_path, 'internal'])
replace = os.sep.join([OUT_DIR['top'], 'bin', '{}-{}.bin'.format(suffix, str(i + 1))])
replace_with = os.sep.join([dest_path, '{}-partition.bin'.format(UUIDs[i])])
os.rename(replace, replace_with)
if args.encrypt:
replace = os.sep.join([OUT_DIR['top'], 'keys', 'keys-{}-{}.bin'.format(suffix, str(i + 1))])
replace_with = os.sep.join([dest_path, '{}-keys-partition.bin'.format(UUIDs[i])])
os.rename(replace, replace_with)
replace = os.sep.join([OUT_DIR['top'], 'csv', '{}-{}.csv'.format(suffix, str(i + 1))])
replace_with = os.sep.join([internal_path, 'partition.csv'])
os.rename(replace, replace_with)
# Also copy the PAI certificate to the output directory
if args.paa or args.pai:
shutil.copy2(PAI['cert_der'], os.sep.join([internal_path, 'PAI_cert.der']))
logging.info('Generated output files at: {}'.format(os.sep.join([OUT_DIR['top'], UUIDs[i]])))
os.rmdir(os.sep.join([OUT_DIR['top'], 'bin']))
os.rmdir(os.sep.join([OUT_DIR['top'], 'csv']))
if args.encrypt:
os.rmdir(os.sep.join([OUT_DIR['top'], 'keys']))
def generate_summary(args):
master_csv = os.sep.join([OUT_DIR['stage'], 'master.csv'])
summary_csv = os.sep.join([OUT_DIR['top'], 'summary-{}.csv'.format(datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))])
summary_csv_data = ''
with open(master_csv, 'r') as mcsvf:
summary_lines = mcsvf.read().splitlines()
summary_csv_data += summary_lines[0]
if not args.enable_dynamic_passcode:
summary_csv_data += ',pincode,qrcode,manualcode\n'
else:
summary_csv_data += '\n'
with open(OUT_FILE['pin_disc_csv'], 'r') as pdcsvf:
pin_disc_dict = csv.DictReader(pdcsvf)
for row in pin_disc_dict:
if not args.enable_dynamic_passcode:
pincode = row['PIN Code']
discriminator = row['Discriminator']
payloads = SetupPayload(int(discriminator), int(pincode), 1 << args.discovery_mode, CommissioningFlow(args.commissioning_flow),
args.vendor_id, args.product_id)
qrcode = payloads.generate_qrcode()
manualcode = payloads.generate_manualcode()
# ToDo: remove this if qrcode tool can handle the standard manual code format
if args.commissioning_flow == CommissioningFlow.Standard:
manualcode = manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:]
else:
manualcode = '"' + manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:11] + '\n' + manualcode[11:15] + '-' + manualcode[15:18] + '-' + manualcode[18:20] + '-' + manualcode[20:21] + '"'
summary_csv_data += summary_lines[1 + int(row['Index'])] + ',' + pincode + ',' + qrcode + ',' + manualcode + '\n'
else:
summary_csv_data += summary_lines[1 + int(row['Index'])] + '\n'
with open(summary_csv, 'w') as scsvf:
scsvf.write(summary_csv_data)
def generate_partitions(suffix, size, encrypt):
partition_args = SimpleNamespace(fileid = None,
version = 2,
inputkey = None,
outdir = OUT_DIR['top'],
conf = OUT_FILE['config_csv'],
values = OUT_FILE['mcsv'],
size = hex(size),
prefix = suffix)
if encrypt:
partition_args.keygen = True
else:
partition_args.keygen = False
generate(partition_args)
def generate_onboarding_data(args, index, discriminator, passcode):
payloads = SetupPayload(discriminator, passcode, 1 << args.discovery_mode, CommissioningFlow(args.commissioning_flow),
args.vendor_id, args.product_id)
chip_qrcode = payloads.generate_qrcode()
chip_manualcode = payloads.generate_manualcode()
# ToDo: remove this if qrcode tool can handle the standard manual code format
if args.commissioning_flow == CommissioningFlow.Standard:
chip_manualcode = chip_manualcode[:4] + '-' + chip_manualcode[4:7] + '-' + chip_manualcode[7:]
else:
chip_manualcode = '"' + chip_manualcode[:4] + '-' + chip_manualcode[4:7] + '-' + chip_manualcode[7:11] + '\n' + chip_manualcode[11:15] + '-' + chip_manualcode[15:18] + '-' + chip_manualcode[18:20] + '-' + chip_manualcode[20:21] + '"'
logging.info('Generated QR code: ' + chip_qrcode)
logging.info('Generated manual code: ' + chip_manualcode)
csv_data = 'qrcode,manualcode,discriminator,passcode\n'
csv_data += chip_qrcode + ',' + chip_manualcode + ',' + str(discriminator) + ',' + str(passcode) + '\n'
onboarding_data_file = os.sep.join([OUT_DIR['top'], UUIDs[index], '{}-onb_codes.csv'.format(UUIDs[index])])
with open(onboarding_data_file, 'w') as f:
f.write(csv_data)
# Create QR code image as mentioned in the spec
qrcode_file = os.sep.join([OUT_DIR['top'], UUIDs[index], '{}-qrcode.png'.format(UUIDs[index])])
chip_qr = pyqrcode.create(chip_qrcode, version=2, error='M')
chip_qr.png(qrcode_file, scale=6)
logging.info('Generated onboarding data and QR Code')
def get_args():
def any_base_int(s): return int(s, 0)
parser = argparse.ArgumentParser(description='Manufacuring partition generator tool',
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=50))
g_gen = parser.add_argument_group('General options')
g_gen.add_argument('-n', '--count', type=any_base_int, default=1,
help='The number of manufacturing partition binaries to generate. Default is 1. \
If --csv and --mcsv are present, the number of lines in the mcsv file is used.')
g_gen.add_argument('--target', default='esp32',
help='The platform type of device. eg: one of esp32, esp32c3, etc.')
g_gen.add_argument('-s', '--size', type=any_base_int, default=0x6000,
help='The size of manufacturing partition binaries to generate. Default is 0x6000.')
g_gen.add_argument('-e', '--encrypt', action='store_true', required=False,
help='Encrypt the factory parititon NVS binary')
g_commissioning = parser.add_argument_group('Commisioning options')
g_commissioning.add_argument('--passcode', type=any_base_int,
help='The passcode for pairing. Randomly generated if not specified.')
g_commissioning.add_argument('--discriminator', type=any_base_int,
help='The discriminator for pairing. Randomly generated if not specified.')
g_commissioning.add_argument('-cf', '--commissioning-flow', type=any_base_int, default=0,
help='Device commissioning flow, 0:Standard, 1:User-Intent, 2:Custom. \
Default is 0.', choices=[0, 1, 2])
g_commissioning.add_argument('-dm', '--discovery-mode', type=any_base_int, default=1,
help='Commissionable device discovery networking technology. \
0:WiFi-SoftAP, 1:BLE, 2:On-network. Default is BLE.', choices=[0, 1, 2])
g_commissioning.add_argument('--enable-dynamic-passcode', action="store_true", required=False,
help='Enable dynamic passcode. If enabling this option, the generated binaries will \
not include the spake2p verifier. so this option should work with a custom \
CommissionableDataProvider which can generate random passcode and \
corresponding verifier')
g_dac = parser.add_argument_group('Device attestation credential options')
g_dac.add_argument('--dac-in-secure-cert', action="store_true", required=False,
help='Store DAC in secure cert partition. By default, DAC is stored in nvs factory partition.')
g_dac.add_argument('-lt', '--lifetime', default=4294967295, type=any_base_int,
help='Lifetime of the generated certificate. Default is 4294967295 if not specified, \
this indicate that certificate does not have well defined expiration date.')
g_dac.add_argument('-vf', '--valid-from',
help='The start date for the certificate validity period in format <YYYY>-<MM>-<DD> [ <HH>:<MM>:<SS> ]. \
Default is current date.')
g_dac.add_argument('-cn', '--cn-prefix', default='ESP32',
help='The common name prefix of the subject of the PAI certificate.')
# If DAC is present then PAI key is not required, so it is marked as not required here
# but, if DAC is not present then PAI key is required and that case is validated in validate_args()
g_dac.add_argument('-c', '--cert', help='The input certificate file in PEM format.')
g_dac.add_argument('-k', '--key', help='The input key file in PEM format.')
g_dac.add_argument('-cd', '--cert-dclrn', help='The certificate declaration file in DER format.')
g_dac.add_argument('--dac-cert', help='The input DAC certificate file in PEM format.')
g_dac.add_argument('--dac-key', help='The input DAC private key file in PEM format.')
g_dac.add_argument('-ds', '--ds-peripheral', action="store_true",
help='Use DS Peripheral in generating secure cert partition.')
g_dac.add_argument('--efuse-key-id', type=int, choices=range(0, 6), default=-1,
help='Provide the efuse key_id which contains/will contain HMAC_KEY, default is 1')
input_cert_group = g_dac.add_mutually_exclusive_group(required=False)
input_cert_group.add_argument('--paa', action='store_true', help='Use input certificate as PAA certificate.')
input_cert_group.add_argument('--pai', action='store_true', help='Use input certificate as PAI certificate.')
g_dev_inst_info = parser.add_argument_group('Device instance information options')
g_dev_inst_info.add_argument('-v', '--vendor-id', type=any_base_int, help='Vendor id')
g_dev_inst_info.add_argument('--vendor-name', help='Vendor name')
g_dev_inst_info.add_argument('-p', '--product-id', type=any_base_int, help='Product id')
g_dev_inst_info.add_argument('--product-name', help='Product name')
g_dev_inst_info.add_argument('--hw-ver', type=any_base_int, help='Hardware version')
g_dev_inst_info.add_argument('--hw-ver-str', help='Hardware version string')
g_dev_inst_info.add_argument('--mfg-date', help='Manufacturing date in format YYYY-MM-DD')
g_dev_inst_info.add_argument('--serial-num', help='Serial number')
g_dev_inst_info.add_argument('--enable-rotating-device-id', action='store_true', help='Enable Rotating device id in the generated binaries')
g_dev_inst_info.add_argument('--rd-id-uid',
help='128-bit unique identifier for generating rotating device identifier, provide 32-byte hex string, e.g. "1234567890abcdef1234567890abcdef"')
g_dev_inst = parser.add_argument_group('Device instance options')
g_dev_inst.add_argument('--calendar-types', nargs='+',
help='List of supported calendar types. Supported Calendar Types: Buddhist, Chinese, Coptic, \
Ethiopian, Gregorian, Hebrew, Indian, Islamic, Japanese, Korean, Persian, Taiwanese')
g_dev_inst.add_argument('--locales', nargs='+',
help='List of supported locales, Language Tag as defined by BCP47, eg. en-US en-GB')
g_dev_inst.add_argument('--fixed-labels', nargs='+',
help='List of fixed labels, eg: "0/orientation/up" "1/orientation/down" "2/orientation/down"')
g_dev_inst.add_argument('--supported-modes', type=str, nargs='+', required=False,
help='List of supported modes, eg: mode1/label1/ep/"tagValue1\\mfgCode, tagValue2\\mfgCode" mode2/label2/ep/"tagValue1\\mfgCode, tagValue2\\mfgCode" mode3/label3/ep/"tagValue1\\mfgCode, tagValue2\\mfgCode"')
g_basic = parser.add_argument_group('Few more Basic clusters options')
g_basic.add_argument('--product-label', help='Product label')
g_basic.add_argument('--product-url', help='Product URL')
g_extra_info = parser.add_argument_group('Extra information options using csv files')
g_extra_info.add_argument('--csv', help='CSV file containing the partition schema for extra options. \
[REF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#csv-configuration-file]')
g_extra_info.add_argument('--mcsv', help='Master CSV file containig optional/extra values specified by the user. \
[REF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#master-value-csv-file]')
return parser.parse_args()
def add_optional_KVs(args):
# Device instance information
if args.vendor_id is not None:
chip_factory_append('vendor-id', 'data', 'u32', args.vendor_id)
if args.vendor_name is not None:
chip_factory_append('vendor-name', 'data', 'string', args.vendor_name)
if args.product_id is not None:
chip_factory_append('product-id', 'data', 'u32', args.product_id)
if args.product_name is not None:
chip_factory_append('product-name', 'data', 'string', args.product_name)
if args.hw_ver is not None:
chip_factory_append('hardware-ver', 'data', 'u32', args.hw_ver)
if args.hw_ver_str is not None:
chip_factory_append('hw-ver-str', 'data', 'string', args.hw_ver_str)
if args.mfg_date is not None:
chip_factory_append('mfg-date', 'data', 'string', args.mfg_date)
if args.enable_rotating_device_id:
chip_factory_append('rd-id-uid', 'data', 'hex2bin', args.rd_id_uid)
# Add the serial-num
chip_factory_append('serial-num', 'data', 'string', args.serial_num)
# Add certificates and keys
if (args.paa or args.pai) and (not args.dac_in_secure_cert):
chip_factory_append('dac-cert', 'file', 'binary', None)
chip_factory_append('dac-key', 'file', 'binary', None)
chip_factory_append('dac-pub-key', 'file', 'binary', None)
chip_factory_append('pai-cert', 'file', 'binary', None)
if not args.enable_dynamic_passcode:
chip_factory_append('verifier', 'data', 'string', None)
# Add certificate declaration
if args.cert_dclrn:
chip_factory_append('cert-dclrn','file','binary', os.path.relpath(args.cert_dclrn))
# Add the Keys in csv files
if args.csv is not None:
chip_nvs_map_append_config_csv(args.csv)
# Device information
if args.calendar_types is not None:
chip_factory_append('cal-types', 'data', 'u32', calendar_types_to_uint32(args.calendar_types))
# Supported locale is stored as multiple entries, key format: "locale/<index>, example key: "locale/0"
if (args.locales is not None):
chip_factory_append('locale-sz', 'data', 'u32', len(args.locales))
for i in range(len(args.locales)):
chip_factory_append('locale/{:x}'.format(i), 'data', 'string', args.locales[i])
# Each endpoint can contains the fixed lables
# - fl-sz/<index> : number of fixed labels for the endpoint
# - fl-k/<ep>/<index> : fixed label key for the endpoint and index
# - fl-v/<ep>/<index> : fixed label value for the endpoint and index
if (args.fixed_labels is not None):
dict = get_fixed_label_dict(args.fixed_labels)
for key in dict.keys():
chip_factory_append('fl-sz/{:x}'.format(int(key)), 'data', 'u32', len(dict[key]))
for i in range(len(dict[key])):
entry = dict[key][i]
chip_factory_append('fl-k/{:x}/{:x}'.format(int(key), i), 'data', 'string', list(entry.keys())[0])
chip_factory_append('fl-v/{:x}/{:x}'.format(int(key), i), 'data', 'string', list(entry.values())[0])
# SupportedModes are stored as multiple entries
# - sm-sz/<ep> : number of supported modes for the endpoint
# - sm-label/<ep>/<index> : supported modes label key for the endpoint and index
# - sm-mode/<ep>/<index> : supported modes mode key for the endpoint and index
# - sm-st-sz/<ep>/<index> : supported modes SemanticTag key for the endpoint and index
# - st-v/<ep>/<index>/<ind> : semantic tag value key for the endpoint and index and ind
# - st-mfg/<ep>/<index>/<ind> : semantic tag mfg code key for the endpoint and index and ind
if (args.supported_modes is not None):
dictionary = get_supported_modes_dict(args.supported_modes)
for ep in dictionary.keys():
chip_factory_append('sm-sz/{:x}'.format(int(ep)), 'data', 'u32', len(dictionary[ep]))
for i in range(len(dictionary[ep])):
item = dictionary[ep][i]
chip_factory_append('sm-label/{:x}/{:x}'.format(int(ep), i), 'data', 'string', item["Label"])
chip_factory_append('sm-mode/{:x}/{:x}'.format(int(ep), i), 'data', 'u32', item["Mode"])
chip_factory_append('sm-st-sz/{:x}/{:x}'.format(int(ep), i), 'data', 'u32', len(item["Semantic_Tag"]))
for j in range(len(item["Semantic_Tag"])):
entry = item["Semantic_Tag"][j]
_value = {
'type': 'data',
'encoding': 'u32',
'value': entry["value"]
}
_mfg_code = {
'type': 'data',
'encoding': 'u32',
'value': entry["mfgCode"]
}
chip_factory_append('st-v/{:x}/{:x}/{:x}'.format(int(ep), i, j), 'data', 'u32', entry["value"])
chip_factory_append('st-mfg/{:x}/{:x}/{:x}'.format(int(ep), i, j), 'data', 'u32', entry["mfgCode"])
# Keys from basic clusters
if args.product_label is not None:
chip_factory_append('product-label', 'data', 'string', args.product_label)
if args.product_url is not None:
chip_factory_append('product-url', 'data', 'string', args.product_url)
def main():
logging.basicConfig(format='[%(asctime)s] [%(levelname)7s] - %(message)s', level=logging.INFO)
args = get_args()
validate_args(args)
check_tools_exists(args)
setup_out_dirs(args.vendor_id, args.product_id, args.count)
add_optional_KVs(args)
generate_passcodes_and_discriminators(args)
write_csv_files(args)
if args.paa or args.pai:
setup_root_certs(args)
write_per_device_unique_data(args)
generate_partitions('matter_partition', args.size, args.encrypt)
organize_output_files('matter_partition', args)
generate_summary(args)
if __name__ == "__main__":
main()
-9
View File
@@ -1,9 +0,0 @@
bitarray>=2.6.0
cryptography==36.0.0
cffi==1.15.0
future==0.18.2
pycparser==2.21
pypng==0.0.21
PyQRCode==1.2.1
python_stdnum==1.18
esp-secure-cert-tool==1.0.1
-327
View File
@@ -1,327 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Contains utilitiy functions for validating argument, certs/keys conversion, etc.
"""
import sys
import enum
import logging
import subprocess
from bitarray import bitarray
from bitarray.util import ba2int
import cryptography.hazmat.backends
import cryptography.x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
ROTATING_DEVICE_ID_UNIQUE_ID_LEN_BITS = 128
SERIAL_NUMBER_LEN = 16
# Lengths for manual pairing codes and qrcode
SHORT_MANUALCODE_LEN = 11
LONG_MANUALCODE_LEN = 21
QRCODE_LEN = 22
INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444, 55555555,
66666666, 77777777, 88888888, 99999999, 12345678, 87654321]
class CalendarTypes(enum.Enum):
Buddhist = 0
Chinese = 1
Coptic = 2
Ethiopian = 3
Gregorian = 4
Hebrew = 5
Indian = 6
Islamic = 7
Japanese = 8
Korean = 9
Persian = 10
Taiwanese = 11
def vid_pid_str(vid, pid):
return '_'.join([hex(vid)[2:], hex(pid)[2:]])
def disc_pin_str(discriminator, passcode):
return '_'.join([hex(discriminator)[2:], hex(passcode)[2:]])
# Checks if the input string is a valid hex string
def ishex(s):
try:
n = int(s, 16)
return True
except ValueError:
return False
# Validate the input string length against the min and max length
def check_str_range(s, min_len, max_len, name):
if s and ((len(s) < min_len) or (len(s) > max_len)):
logging.error('%s must be between %d and %d characters', name, min_len, max_len)
sys.exit(1)
# Validate the input integer range
def check_int_range(value, min_value, max_value, name):
if value and ((value < min_value) or (value > max_value)):
logging.error('%s is out of range, should be in range [%d, %d]', name, min_value, max_value)
sys.exit(1)
# Validates discriminator and passcode
def validate_commissionable_data(args):
check_int_range(args.discriminator, 0x0000, 0x0FFF, 'Discriminator')
if args.passcode is not None:
if ((args.passcode < 0x0000001 and args.passcode > 0x5F5E0FE) or (args.passcode in INVALID_PASSCODES)):
logging.error('Invalid passcode' + str(args.passcode))
sys.exit(1)
# Validate the device instance information
def validate_device_instance_info(args):
check_int_range(args.product_id, 0x0000, 0xFFFF, 'Product id')
check_int_range(args.vendor_id, 0x0000, 0xFFFF, 'Vendor id')
check_int_range(args.hw_ver, 0x0000, 0xFFFF, 'Hardware version')
check_str_range(args.serial_num, 1, SERIAL_NUMBER_LEN, 'Serial number')
check_str_range(args.vendor_name, 1, 32, 'Vendor name')
check_str_range(args.product_name, 1, 32, 'Product name')
check_str_range(args.hw_ver_str, 1, 64, 'Hardware version string')
check_str_range(args.mfg_date, 8, 16, 'Manufacturing date')
check_str_range(args.rd_id_uid, 32, 32, 'Rotating device Unique id')
# Validate the device information: calendar types and fixed labels
def validate_device_info(args):
# Validate the input calendar types
if args.calendar_types is not None:
if not (set(args.calendar_types) <= set(CalendarTypes.__members__)):
invalid_types = set(args.calendar_types).union(set(CalendarTypes.__members__)) - set(CalendarTypes.__members__)
logging.error('Unknown calendar type/s: %s', invalid_types)
logging.error('Supported calendar types: %s', ', '.join(CalendarTypes.__members__))
sys.exit(1)
if args.fixed_labels is not None:
for fl in args.fixed_labels:
_l = fl.split('/')
if len(_l) != 3:
logging.error('Invalid fixed label: %s', fl)
sys.exit(1)
if not (ishex(_l[0]) and (len(_l[1]) > 0 and len(_l[1]) < 16) and (len(_l[2]) > 0 and len(_l[2]) < 16)):
logging.error('Invalid fixed label: %s', fl)
sys.exit(1)
# Validates the attestation related arguments
def validate_attestation_info(args):
# DAC key and DAC cert both should be present or none
if (args.dac_key is not None) != (args.dac_cert is not None):
logging.error("dac_key and dac_cert should be both present or none")
sys.exit(1)
else:
# Make sure PAI certificate is present if DAC is present
if (args.dac_key is not None) and (args.pai is False):
logging.error('Please provide PAI certificate along with DAC certificate and DAC key')
sys.exit(1)
# Validate the input certificate type, if DAC is not present
if args.dac_key is None and args.dac_cert is None:
if args.paa:
logging.info('Input Root certificate type PAA')
elif args.pai:
logging.info('Input Root certificate type PAI')
else:
logging.info('Do not include the device attestation certificates and keys in partition binaries')
# Check if Key and certificate are present
if (args.paa or args.pai) and (args.key is None or args.cert is None):
logging.error('CA key and certificate are required to generate DAC key and certificate')
sys.exit(1)
# Validates few basic cluster related arguments: product-label and product-url
def validate_basic_cluster_info(args):
check_str_range(args.product_label, 1, 64, 'Product Label')
check_str_range(args.product_url, 1, 256, 'Product URL')
# Validates the input arguments, this calls the above functions
def validate_args(args):
# csv and mcsv both should present or none
if (args.csv is not None) != (args.mcsv is not None):
logging.error("csv and mcsv should be both present or none")
sys.exit(1)
else:
# Read the number of lines in mcsv file
if args.mcsv is not None:
with open(args.mcsv, 'r') as f:
lines = sum(1 for line in f)
# Subtract 1 for the header line
args.count = lines - 1
validate_commissionable_data(args)
validate_device_instance_info(args)
validate_device_info(args)
validate_attestation_info(args)
validate_basic_cluster_info(args)
# If discriminator/passcode/DAC/serial_number/rotating_device_id is present
# then we are restricting the number of partitions to 1
if (args.discriminator is not None
or args.passcode is not None
or args.dac_key is not None
or args.serial_num is not None
or args.rd_id_uid is not None):
if args.count > 1:
logging.error('Number of partitions should be 1 when discriminator or passcode or DAC or serial number or rotating device id is present')
sys.exit(1)
logging.info('Number of manufacturing NVS images to generate: {}'.format(args.count))
# Supported Calendar types is stored as a bit array in one uint32_t.
def calendar_types_to_uint32(calendar_types):
# In validate_device_info() we have already verified that the calendar types are valid
result = bitarray(32, endian='little')
result.setall(0)
for calendar_type in calendar_types:
result[CalendarTypes[calendar_type].value] = 1
return ba2int(result)
# get_fixed_label_dict() converts the list of strings to per endpoint dictionaries.
# example input : ['0/orientation/up', '1/orientation/down', '2/orientation/down']
# example outout : {'0': [{'orientation': 'up'}], '1': [{'orientation': 'down'}], '2': [{'orientation': 'down'}]}
def get_fixed_label_dict(fixed_labels):
fl_dict = {}
for fl in fixed_labels:
_l = fl.split('/')
if len(_l) != 3:
logging.error('Invalid fixed label: %s', fl)
sys.exit(1)
if not (ishex(_l[0]) and (len(_l[1]) > 0 and len(_l[1]) < 16) and (len(_l[2]) > 0 and len(_l[2]) < 16)):
logging.error('Invalid fixed label: %s', fl)
sys.exit(1)
if _l[0] not in fl_dict.keys():
fl_dict[_l[0]] = list()
fl_dict[_l[0]].append({_l[1]: _l[2]})
return fl_dict
# get_supported_modes_dict() converts the list of strings to per endpoint dictionaries.
# example with semantic tags
# input : ['0/label1/1/"1\0x8000, 2\0x8000" 1/label2/1/"1\0x8000, 2\0x8000"']
# outout : {'1': [{'Label': 'label1', 'Mode': 0, 'Semantic_Tag': [{'value': 1, 'mfgCode': 32768}, {'value': 2, 'mfgCode': 32768}]}, {'Label': 'label2', 'Mode': 1, 'Semantic_Tag': [{'value': 1, 'mfgCode': 32768}, {'value': 2, 'mfgCode': 32768}]}]}
# example without semantic tags
# input : ['0/label1/1 1/label2/1']
# outout : {'1': [{'Label': 'label1', 'Mode': 0, 'Semantic_Tag': []}, {'Label': 'label2', 'Mode': 1, 'Semantic_Tag': []}]}
def get_supported_modes_dict(supported_modes):
output_dict = {}
for mode_str in supported_modes:
mode_label_strs = mode_str.split('/')
mode = mode_label_strs[0]
label = mode_label_strs[1]
ep = mode_label_strs[2]
semantic_tags = ''
if (len(mode_label_strs) == 4):
semantic_tag_strs = mode_label_strs[3].split(', ')
semantic_tags = [{"value": int(v.split('\\')[0]), "mfgCode": int(v.split('\\')[1], 16)} for v in semantic_tag_strs]
mode_dict = {"Label": label, "Mode": int(mode), "Semantic_Tag": semantic_tags}
if ep in output_dict:
output_dict[ep].append(mode_dict)
else:
output_dict[ep] = [mode_dict]
return output_dict
# Convert the certificate in PEM format to DER format
def convert_x509_cert_from_pem_to_der(pem_file, out_der_file):
with open(pem_file, 'rb') as f:
pem_data = f.read()
pem_cert = cryptography.x509.load_pem_x509_certificate(pem_data, default_backend())
der_cert = pem_cert.public_bytes(serialization.Encoding.DER)
with open(out_der_file, 'wb') as f:
f.write(der_cert)
def convert_private_key_from_pem_to_der(pem_file, out_der_file):
with open(pem_file, 'rb') as f:
pem_data = f.read()
pem_key = serialization.load_pem_private_key(pem_data, None, default_backend())
der_key = pem_key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
with open(out_der_file, 'wb') as f:
f.write(der_key)
# Generate the Public and Private key pair binaries
def generate_keypair_bin(pem_file, out_privkey_bin, out_pubkey_bin):
with open(pem_file, 'rb') as f:
pem_data = f.read()
key_pem = cryptography.hazmat.primitives.serialization.load_pem_private_key(pem_data, None)
private_number_val = key_pem.private_numbers().private_value
public_number_x = key_pem.public_key().public_numbers().x
public_number_y = key_pem.public_key().public_numbers().y
public_key_first_byte = 0x04
with open(out_privkey_bin, 'wb') as f:
f.write(private_number_val.to_bytes(32, byteorder='big'))
with open(out_pubkey_bin, 'wb') as f:
f.write(public_key_first_byte.to_bytes(1, byteorder='big'))
f.write(public_number_x.to_bytes(32, byteorder='big'))
f.write(public_number_y.to_bytes(32, byteorder='big'))
def execute_cmd(cmd):
logging.debug('Executing Command: {}'.format(cmd))
status = subprocess.run(cmd, capture_output=True)
try:
status.check_returncode()
except subprocess.CalledProcessError as e:
if status.stderr:
logging.error('[stderr]: {}'.format(status.stderr.decode('utf-8').strip()))
logging.error('Command failed with error: {}'.format(e))
sys.exit(1)