From 1207637a90dd020113f4c8e7e8e3f3d1e2f1b0aa Mon Sep 17 00:00:00 2001 From: Marius Tache <102153746+marius-alex-tache@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:21:27 +0200 Subject: [PATCH] [K32W0] SDK 2.6.13 changes (#30678) * [K32W0] Bring platform diff Signed-off-by: marius-alex-tache * [K32W0] Add SSBL version retrieval The applications will now print the SSBL version also. If the SSBL doesn't have a version defined, the expected value is 0. Signed-off-by: marius-alex-tache * [K32W0] Fix SSBL version retrieval SSBL will always be seen as booting from address 0, thanks to the remapping mechanism. This means the SSBL version will always offset from address 0. Signed-off-by: marius-alex-tache * [K32W0] Add support for simple hash verification Signed-off-by: marius-alex-tache * [K32W] Add support for BLE callback delegate Application can register callbacks for GAP/GATT events. Signed-off-by: marius-alex-tache * [K32W0] Add example for registering BLE event callbacks Signed-off-by: marius-alex-tache * [K32W0] replace to use SecLib API and SecLib mutex * [COMMON] Fix manufacturing flow Signed-off-by: marius-alex-tache * [COMMON] Add python script to generate certificates Signed-off-by: Axel Le Bourhis * [common] Update generate_certs script * Add valid_from and lifetime optional parameters * Updated parameters descriptions * Misc improvements Signed-off-by: Axel Le Bourhis * [COMMON] Update nxp_manufacturing_flow document Updated the document to make use of the new generate_certs.py script. Signed-off-by: Axel Le Bourhis * [K32W0] Fix function signature Signed-off-by: marius-alex-tache * [K32W0] Remove duplicate reboot cause set Signed-off-by: marius-alex-tache * [K32W] Set BLE manager impl pointer before calling InitHostController When adding app BLE callbacks, the sImplInstance pointer is used, so it needs to be set beforehand. Signed-off-by: marius-alex-tache * [COMMON] Update ot-nxp to release branch Signed-off-by: marius-alex-tache * [K32W0] Update west manifest to point to 2.6.13 SDK Signed-off-by: marius-alex-tache * [K32W0] Fix ICD parameters Signed-off-by: Doru Gucea * [NXP] Bump ot-nxp to latest Signed-off-by: marius-alex-tache * [NXP] Fix tools lint errors Signed-off-by: marius-alex-tache * Restyled by whitespace Restyled by clang-format Restyled by gn Restyled by prettier-markdown Restyled by autopep8 Restyled by isort * [NXP] Add extra condition for SDK gn variable to take into account CI Signed-off-by: marius-alex-tache * Restyled by gn * [NXP] Fix some lint errors Signed-off-by: marius-alex-tache * [K32W0] Fix include header Signed-off-by: marius-alex-tache * Restyled by autopep8 * [NXP] Fix lint error Signed-off-by: marius-alex-tache * [NXP] Update docker image used by the github actions workflow for K32W Signed-off-by: marius-alex-tache * [K32W1] Add example for registering BLE event callbacks Signed-off-by: marius-alex-tache * Restyled by clang-format * [NXP] Clarify usage of hash id in factory data. Update MATTER_ROOT to use a more generic path Signed-off-by: marius-alex-tache --------- Signed-off-by: marius-alex-tache Signed-off-by: Axel Le Bourhis Signed-off-by: Doru Gucea Co-authored-by: Ethan Tan Co-authored-by: Axel Le Bourhis Co-authored-by: Doru Gucea Co-authored-by: Restyled.io --- .github/workflows/examples-k32w.yaml | 2 +- docs/guides/nxp_manufacturing_flow.md | 198 ++++--- .../nxp/k32w/k32w0/BUILD.gn | 5 + .../nxp/k32w/k32w0/README.md | 22 + .../nxp/k32w/k32w0/args.gni | 4 + .../k32w/k32w0/include/CHIPProjectConfig.h | 8 +- .../nxp/k32w/k32w0/main/AppTask.cpp | 25 + .../nxp/k32w/k32w1/main/AppTask.cpp | 17 + examples/lighting-app/nxp/k32w/k32w0/BUILD.gn | 5 + .../lighting-app/nxp/k32w/k32w0/README.md | 22 + .../k32w/k32w0/include/CHIPProjectConfig.h | 5 + .../nxp/k32w/k32w0/main/AppTask.cpp | 25 +- examples/lock-app/nxp/k32w/k32w0/args.gni | 4 + .../k32w/k32w0/include/CHIPProjectConfig.h | 7 +- .../k32w0/app/ldscripts/chip-k32w0x-linker.ld | 9 +- .../k32w0/doc/images/ssbl_simple_hash.JPG | Bin 0 -> 56397 bytes .../nxp/k32w/k32w0/scripts/sign-outdir.py | 41 +- .../nxp/factory_data_generator/generate.py | 12 +- scripts/tools/nxp/generate_certs.py | 211 ++++++++ scripts/tools/nxp/ota/crypto_utils.py | 487 ++++++++++++++++++ scripts/tools/nxp/ota/ota_image_tool.py | 59 ++- .../nxp/k32w/common/BLEManagerCommon.cpp | 24 +- .../nxp/k32w/common/BLEManagerCommon.h | 22 +- .../CHIPDevicePlatformRamStorageConfig.h | 11 + .../nxp/k32w/common/FactoryDataProvider.cpp | 10 +- .../nxp/k32w/common/FactoryDataProvider.h | 4 +- .../nxp/k32w/common/OTAImageProcessorImpl.cpp | 2 +- .../nxp/k32w/common/OTATlvProcessor.cpp | 82 ++- .../nxp/k32w/common/OTATlvProcessor.h | 8 + .../nxp/k32w/k32w0/BLEManagerImpl.cpp | 2 +- src/platform/nxp/k32w/k32w0/BLEManagerImpl.h | 2 +- .../nxp/k32w/k32w0/CHIPDevicePlatformConfig.h | 12 + .../nxp/k32w/k32w0/CHIPPlatformConfig.h | 24 + .../k32w/k32w0/KeyValueStoreManagerImpl.cpp | 4 +- src/platform/nxp/k32w/k32w0/Logging.cpp | 2 +- .../k32w/k32w0/OTAFactoryDataProcessor.cpp | 4 + .../nxp/k32w/k32w0/OTAFirmwareProcessor.cpp | 37 +- .../nxp/k32w/k32w0/OTAFirmwareProcessor.h | 3 + .../nxp/k32w/k32w1/BLEManagerImpl.cpp | 2 +- src/platform/nxp/k32w/k32w1/BLEManagerImpl.h | 2 +- third_party/nxp/k32w0_sdk/k32w0_sdk.gni | 167 ++++-- .../nxp/k32w0_sdk/repo/manifest/west.yml | 8 +- third_party/openthread/ot-nxp | 2 +- 43 files changed, 1426 insertions(+), 176 deletions(-) create mode 100755 examples/platform/nxp/k32w/k32w0/doc/images/ssbl_simple_hash.JPG create mode 100644 scripts/tools/nxp/generate_certs.py create mode 100755 scripts/tools/nxp/ota/crypto_utils.py diff --git a/.github/workflows/examples-k32w.yaml b/.github/workflows/examples-k32w.yaml index 5d811a986c757a..e2854c2e297f38 100644 --- a/.github/workflows/examples-k32w.yaml +++ b/.github/workflows/examples-k32w.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-k32w:26 + image: ghcr.io/project-chip/chip-build-k32w:27 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/docs/guides/nxp_manufacturing_flow.md b/docs/guides/nxp_manufacturing_flow.md index 141f90c8b8b57c..64bafd53c89329 100644 --- a/docs/guides/nxp_manufacturing_flow.md +++ b/docs/guides/nxp_manufacturing_flow.md @@ -11,108 +11,91 @@ using the procedure described below. ## 1. Prerequisites -Build `chip-cert` tool: - -``` -cd src/tools/chip-cert -gn gen out -ninja -C out -``` - -Build `spake2p` tool: +Generate build files from Matter root folder by running: ``` -cd src/tool/spake2p gn gen out -ninja -C out ``` -### Environment variables - -A user can customize the certificate generation by setting some environment -variables that are used within the utility scripts. Please note that the values -below are just an example and should be modified accordingly: +Build `chip-cert` tool: ``` -export FACTORY_DATA_DEST=path/factory/data/dest -export DEVICE_TYPE=100 -export DATE=$(date +"%F") -export TIME=$(date +"%T") -export LIFETIME="7305" -export VID="1037" -export PID="A220" +ninja -C out chip-cert ``` -`FACTORY_DATA_DEST` is the path where all factory related data is generated. - -`DEVICE_TYPE` should be updated according to the application device type (0x0100 -for the provided K32W0 lighting app). - -Additionally, `PAA_CERT` and `PAA_KEY` paths can be specified to use an already -existent **PAA**: +Build `spake2p` tool: ``` -export PAA_CERT=path/certs/Chip-PAA-NXP-Cert.pem -export PAA_KEY=path/certs/Chip-PAA-NXP-Key.pem +ninja -C out spake2p ``` ## 2. Generate ### a. Certificates -``` -./scripts/tools/nxp/generate_cert.sh ./src/tools/chip-cert/out/chip-cert -``` +To generate the different certificates, NXP provides a Python script +`scripts/tools/nxp/generate_certs.py`. This script will always generate the PAI +and DAC certificates/keys. It can also generate the Certification Declaration +and the PAA certificate/key depending on the parameters. -The output of the script is the **DAC**, **PAI** and **PAA** certificates. If -`FACTORY_DATA_DEST` is set, the certificates will be moved there. The **DAC** -and **PAI** certificates will be written in a special section of the internal -flash, while the **PAA** will be used by `chip-tool` as trust anchor. Please -note that for _real production manufacturing_ the "production PAA" is trusted -via the **DCL** rather than through the generated **PAA** certificate. The -**PAI** certificate may also have a different lifecycle. +| Parameter | Description | Type | Required | +| ------------------ | ------------------------------------------------------------------------------------------------- | ---------------------- | -------- | +| `--chip_cert_path` | Path to chip-cert executable | string | Yes | +| `--output` | Output path to store certificates | string | Yes | +| `--vendor_id` | Vendor Identification Number | integer or hex integer | Yes | +| `--product_id` | Product Identification Number | integer or hex integer | Yes | +| `--vendor_name` | Human-readable vendor name | string | Yes | +| `--product_name` | Human-readable product name | string | Yes | +| `--gen_cd` | Use this option to enable Certificate Declaration generation | boolean | No | +| `--cd_type` | Type of generated Certification Declaration: `0` - development, `1` - provisional, `2` - official | integer | No | +| `--device_type` | The primary device type implemented by the node | int | No | +| `--paa_cert` | Path to the Product Attestation Authority (PAA) certificate. Will be generated if not provided. | string | No | +| `--paa_key` | Path to the Product Attestation Authority (PAA) key. Will be generated if not provided. | string | No | +| `--valid_from` | The start date for the certificate's validity period. | string | No | +| `--lifetime` | The lifetime for the certificates, in whole days. | string | No | -### b. Certification declaration (CD) +You can also run the following command to get more details on the parameters and +their default value (if applicable): -``` -./src/tools/chip-cert/out/chip-cert gen-cd --key ./credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem --cert ./credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem --out $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --format-version 1 --vendor-id "0x$VID" --product-id "0x$PID" --device-type-id "0x$DEVICE_TYPE" --certificate-id "ZIG20142ZB330003-24" --security-level 0 --security-info 0 --version-number 9876 --certification-type 1 +```shell +python scripts/tools/nxp/generate_certs.py --help ``` -The command above is extracted from `./credentials/test/gen-test-cds.sh` script. -The CD generation uses predefined key and certificate found in -`./credentials/test/certification-declaration`. This **CSA** certificate is also -hard-coded as Trust Anchor in the current `chip-tool` version. +Example of a command that will generate CD, PAA, PAI and DAC certificates and +keys in both .pem and .der formats: -By default, the CD is added to the factory data section. In order to have it -integrated in the application binary, set -`CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION` to 1 in the application's -CHIPProjectConfig.h file. +```shell +python scripts/tools/nxp/generate_certs.py --gen_cd --cd_type 1 --chip_cert_path ./out/chip-cert --vendor_id 0x1037 --product_id 0xA220 --vendor_name "NXP Semiconductors" --product_name all-clusters-app --device_type 65535 --output . +``` + +> **Note**: the commands provided in this guide are just for the example and +> shall be adapted to your use case accordingly ### c. Provisioning data Generate new provisioning data and convert all the data to a binary (unencrypted data): -``` -python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x$VID" --pid "0x$PID" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "$DATE" --hw_version 1 --hw_version_str "1.0" --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --unique_id "00112233445566778899aabbccddeeff" --out $FACTORY_DATA_DEST/factory_data.bin +```shell +python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid 0x1037 --pid 0xA220 --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "2023-01-01" --hw_version 1 --hw_version_str "1.0" --cert_declaration ./Chip-Test-CD-1037-A220.der --dac_cert ./Chip-DAC-NXP-1037-A220-Cert.der --dac_key ./Chip-DAC-NXP-1037-A220-Key.der --pai_cert ./Chip-PAI-NXP-1037-A220-Cert.der --spake2p_path ./out/spake2p --unique_id "00112233445566778899aabbccddeeff" --out ./factory_data.bin ``` Same example as above, but with an already generated verifier passed as input: -``` -python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x$VID" --pid "0x$PID" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "$DATE" --hw_version 1 --hw_version_str "1.0" --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --spake2p_verifier ivD5n3L2t5+zeFt6SjW7BhHRF30gFXWZVvvXgDxgCNcE+BGuTA5AUaVm3qDZBcMMKn1a6CakI4SxyPUnJr0CpJ4pwpr0DvpTlkQKqaRvkOQfAQ1XDyf55DuavM5KVGdDrg== --unique_id "00112233445566778899aabbccddeeff" --out $FACTORY_DATA_DEST/factory_data.bin +```shell +python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x1037" --pid "0xA220" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "2023-01-01" --hw_version 1 --hw_version_str "1.0" --cert_declaration ./Chip-Test-CD-1037-A220.der --dac_cert ./Chip-DAC-NXP-1037-A220-Cert.der --dac_key ./Chip-DAC-NXP-1037-A220-Key.der --pai_cert ./Chip-PAI-NXP-1037-A220-Cert.der --spake2p_path ./out/spake2p --spake2p_verifier ivD5n3L2t5+zeFt6SjW7BhHRF30gFXWZVvvXgDxgCNcE+BGuTA5AUaVm3qDZBcMMKn1a6CakI4SxyPUnJr0CpJ4pwpr0DvpTlkQKqaRvkOQfAQ1XDyf55DuavM5KVGdDrg== --unique_id "00112233445566778899aabbccddeeff" --out ./factory_data.bin ``` Generate new provisioning data and convert all the data to a binary (encrypted data with the AES key). Add the following option to one of the above examples: -``` +```shell --aes128_key 2B7E151628AED2A6ABF7158809CF4F3C ``` Here is the interpretation of the **required** parameters: -``` +```shell -i -> SPAKE2+ iteration -s -> SPAKE2+ salt (passed as base64 encoded string) -p -> SPAKE2+ passcode @@ -127,7 +110,7 @@ Here is the interpretation of the **required** parameters: --dac_cert -> path to the DAC (der format) location --dac_key -> path to the DAC key (der format) location --pai_cert -> path to the PAI (der format) location ---spake2p_path -> path to the spake2p tool (compile it from ./src/tools/spake2p) +--spake2p_path -> path to the spake2p tool --out -> name of the binary that will be used for storing all the generated data @@ -135,18 +118,24 @@ Here is the interpretation of the **required** parameters: Here is the interpretation of the **optional** parameters: -``` ---dac_key_password -> Password to decode DAC key ---spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, - all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool - will not be used to generate a new verifier on the fly. ---aes128_key -> 128 bits AES key used to encrypt the whole dataset ---date -> Manufacturing Date (YYYY-MM-DD format) ---part_number -> Part number as string ---product_url -> Product URL as string ---product_label -> Product label as string ---serial_num -> Serial Number ---unique_id -> Unique id used for rotating device id generation +```shell +--dac_key_password -> Password to decode DAC key +--dac_key_use_sss_blob -> Used when --dac_key contains a path to an encrypted blob, instead of the + actual DAC private key. The blob metadata size is 24, so the total length + of the resulting value is private key length (32) + 24 = 56. False by default. +--spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, + all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool + will not be used to generate a new verifier on the fly. +--aes128_key -> 128 bits AES key used to encrypt the whole dataset. Please make sure + that the target application/board supports this feature: it has access to + the private key and implements a mechanism which can be used to decrypt + the factory data information. +--date -> Manufacturing Date (YYYY-MM-DD format) +--part_number -> Part number as string +--product_url -> Product URL as string +--product_label -> Product label as string +--serial_num -> Serial Number +--unique_id -> Unique id used for rotating device id generation ``` ## 3. Write provisioning data @@ -154,10 +143,20 @@ Here is the interpretation of the **optional** parameters: For the **K32W0x1** variants, the binary needs to be written in the internal flash at location **0x9D600** using `DK6Programmer.exe`: -``` +```shell DK6Programmer.exe -Y -V2 -s -P 1000000 -Y -p FLASH@0x9D600="factory_data.bin" ``` +For **K32W1** platform, the binary needs to be written in the internal flash at +location given by `__MATTER_FACTORY_DATA_START`, using `JLink`: + +``` +loadfile factory_data.bin 0xf4000 +``` + +where `0xf4000` is the value of `__MATTER_FACTORY_DATA_START` in the +corresponding .map file (can be different if using a custom linker script). + For the **RT1060**, **RT1170** and **RW61X** platform, the binary needs to be written using `MCUXpresso Flash Tool GUI` at the address value corresponding to `__FACTORY_DATA_START` (the map file of the application should be checked to get @@ -169,13 +168,13 @@ Use `chip_with_factory_data=1` when compiling to enable factory data usage. Run chip-tool with a new PAA: -``` +```shell ./chip-tool pairing ble-thread 2 hex: $hex_value 14014 1000 --paa-trust-store-path /home/ubuntu/certs/paa ``` Here is the interpretation of the parameters: -``` +```shell --paa-trust-store-path -> path to the generated PAA (der format) ``` @@ -195,4 +194,51 @@ certificates generated by `OpenSSL 1.1.1l`. Also, demo **DAC**, **PAI** and **PAA** certificates needed in case `chip_with_factory_data=1` is used can be found in -`./scripts/tools/nxp/demo_generated_certs`. +`./scripts/tools/nxp/demo_generated_certs`. + +## 6. Increased security for DAC private key + +Supported platforms: + +- K32W1 - `src/plaftorm/nxp/k32w/k32w1/FactoryDataProviderImpl.h` + +For platforms that have a secure subsystem (`SSS`), the DAC private key can be +converted to an encrypted blob. This blob will overwrite the DAC private key in +factory data and will be imported in the `SSS` at initialization, by the factory +data provider instance. + +The conversion process shall happen at manufacturing time and should be run one +time only: + +- Write factory data binary. +- Build the application with + `chip_with_factory_data=1 chip_convert_dac_private_key=1` set. +- Write the application to the board and let it run. + +After the conversion process: + +- Make sure the application is built with `chip_with_factory_data=1`, but + without `chip_convert_dac_private_key` arg, since conversion already + happened. +- Write the application to the board. + +If you are using Jlink, you can see a conversion script example in: + +```shell +./scripts/tools/nxp/factory_data_generator/k32w1/example_convert_dac_private_key.jlink +``` + +Factory data should now contain a corresponding encrypted blob instead of the +DAC private key. + +If an encrypted blob of the DAC private key is already available (e.g. obtained +previously, using other methods), then the conversion process shall be skipped. +Instead, option `--dac_key_use_sss_blob` can be used in the factory data +generation command: + +```shell +python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x1037" --pid "0xA221" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "2023-01-01" --hw_version 1 --hw_version_str "1.0" --cert_declaration ./Chip-Test-CD-1037-A221.der --dac_cert ./Chip-DAC-NXP-1037-A221-Cert.der --dac_key ./Chip-DAC-NXP-1037-A221-Key-encrypted-blob.bin --pai_cert ./Chip-PAI-NXP-1037-A221-Cert.der --spake2p_path ./out/spake2p --unique_id "00112233445566778899aabbccddeeff" --dac_key_use_sss_blob --out ./factory_data_with_blob.bin +``` + +Please note that `--dac_key` now points to a binary file that contains the +encrypted blob. diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn index e4c15436b3bd86..36f52d917f22db 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn @@ -25,6 +25,7 @@ import("${chip_root}/src/platform/device.gni") declare_args() { chip_software_version = 0 + chip_simple_hash_verification = 0 } if (chip_pw_tokenizer_logging) { @@ -177,6 +178,10 @@ action("binsign") { script = "${k32w0_platform_dir}/scripts/sign-outdir.py" output_name = "bignsign.log" outputs = [ "${root_build_dir}/${output_name}" ] + + if (chip_simple_hash_verification == 1) { + args = [ "--simple-hash" ] + } } group("default") { diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md index 9f92bc0d795142..f616ddfeeca4f9 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md @@ -440,6 +440,10 @@ SSBL demo application can be imported from the `Quickstart panel`: ![SSBL Application Select](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_select.JPG) +### Features + +#### Multi image + To support multi-image OTA feature, the SSBL project must be compiled using the following defines: @@ -457,6 +461,24 @@ Optionally, add the following defines: ![SSBL_MULTI_IMAGE](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG) +#### Simple hash verification + +When secure boot is not used, a simple hash can be appended at the end of the +image for integrity check. Applications should be built with +`chip_simple_hash_verification=1`. + +To support simple hash verification feature, the SSBL project must be compiled +with: + +- `gSimpleHashVerification=1` + +and update the post-build command to use simple hash verification instead of the +default options. Go to +`Project -> Properties -> C/C++ Build -> Settings -> Build steps` and press +`Edit` under `Post-build steps` subsection. The command should look similar to: + +![SSBL_SIMPLE_HASH_VERIFICATION](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_simple_hash.JPG) + Once compiled, the required SSBL file is called `k32w061dk6_ssbl.bin`. ![SSBL_BIN](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_bin.JPG) diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni b/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni index 4f4bba4b47cf07..02a388daab9e1a 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni @@ -22,3 +22,7 @@ k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") chip_enable_ota_requestor = true chip_stack_lock_tracking = "fatal" chip_enable_ble = true + +chip_enable_icd_server = true +chip_persist_subscriptions = true +chip_subscription_timeout_resumption = true diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h index 7d435215252b0e..b48b97c55e6f94 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h @@ -59,6 +59,11 @@ #define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0x1037 #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0xA220 +// Set the following define to use the Certification Declaration from below and not use it stored in factory data section +#ifndef CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION +#define CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION 0 +#endif + #ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION //-> format_version = 1 //-> vendor_id = 0x1037 @@ -188,8 +193,7 @@ #define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics #define CHIP_DEVICE_CONFIG_ENABLE_SED 1 -#define CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL 1000_ms32 -#define CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL 100_ms32 + /** * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER * diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp index a3656550b9434c..484ddce1b7ce1b 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -45,6 +45,8 @@ #include #endif +#include "BLEManagerImpl.h" + #include "Keyboard.h" #include "LED.h" #include "LEDWidget.h" @@ -123,6 +125,18 @@ CHIP_ERROR AppTask::StartAppTask() return err; } +static void app_gap_callback(gapGenericEvent_t * event) +{ + /* This callback is called in the context of BLE task, so event processing + * should be posted to app task. */ +} + +static void app_gatt_callback(deviceId_t id, gattServerEvent_t * event) +{ + /* This callback is called in the context of BLE task, so event processing + * should be posted to app task. */ +} + #if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR static void CheckOtaEntry() { @@ -240,6 +254,17 @@ CHIP_ERROR AppTask::Init() K32W_LOG("Current Software Version: %s, %" PRIu32, currentSoftwareVer, currentVersion); +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + /* SSBL will always be seen as booting from address 0, thanks to the remapping mechanism. + * This means the SSBL version will always offset from address 0. */ + extern uint32_t __MATTER_SSBL_VERSION_START[]; + K32W_LOG("Current SSBL Version: %ld. Found at address 0x%lx", *((uint32_t *) __MATTER_SSBL_VERSION_START), + (uint32_t) __MATTER_SSBL_VERSION_START); +#endif + + auto & bleManager = chip::DeviceLayer::Internal::BLEMgrImpl(); + bleManager.RegisterAppCallbacks(app_gap_callback, app_gatt_callback); + return err; } diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp index bce7374d7ea7ef..c7acba0f3ee1cb 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp @@ -46,6 +46,8 @@ #include #endif +#include + #include "K32W1PersistentStorageOpKeystore.h" #include "LEDWidget.h" @@ -106,6 +108,18 @@ static BDXDownloader gDownloader __attribute__((section(".data"))); constexpr uint16_t requestedOtaBlockSize = 1024; #endif +static void app_gap_callback(gapGenericEvent_t * event) +{ + /* This callback is called in the context of BLE task, so event processing + * should be posted to app task. */ +} + +static void app_gatt_callback(deviceId_t id, gattServerEvent_t * event) +{ + /* This callback is called in the context of BLE task, so event processing + * should be posted to app task. */ +} + CHIP_ERROR AppTask::StartAppTask() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -188,6 +202,9 @@ CHIP_ERROR AppTask::Init() K32W_LOG("Current Software Version: %s, %d", currentSoftwareVer, currentVersion); + auto & bleManager = chip::DeviceLayer::Internal::BLEMgrImpl(); + bleManager.RegisterAppCallbacks(app_gap_callback, app_gatt_callback); + return err; } diff --git a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn index 2ee2e42a24259e..a71bf63dc6d4e0 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn @@ -25,6 +25,7 @@ import("${chip_root}/src/platform/device.gni") declare_args() { chip_software_version = 0 + chip_simple_hash_verification = 0 } if (chip_pw_tokenizer_logging) { @@ -170,6 +171,10 @@ action("binsign") { script = "${k32w0_platform_dir}/scripts/sign-outdir.py" output_name = "bignsign.log" outputs = [ "${root_build_dir}/${output_name}" ] + + if (chip_simple_hash_verification == 1) { + args = [ "--simple-hash" ] + } } group("default") { diff --git a/examples/lighting-app/nxp/k32w/k32w0/README.md b/examples/lighting-app/nxp/k32w/k32w0/README.md index 829d4bd1e70223..b43a08622eb330 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/README.md +++ b/examples/lighting-app/nxp/k32w/k32w0/README.md @@ -457,6 +457,10 @@ SSBL demo application can be imported from the `Quickstart panel`: ![SSBL Application Select](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_select.JPG) +### Features + +#### Multi image + To support multi-image OTA feature, the SSBL project must be compiled using the following defines: @@ -474,6 +478,24 @@ Optionally, add the following defines: ![SSBL_MULTI_IMAGE](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG) +#### Simple hash verification + +When secure boot is not used, a simple hash can be appended at the end of the +image for integrity check. Applications should be built with +`chip_simple_hash_verification=1`. + +To support simple hash verification feature, the SSBL project must be compiled +with: + +- `gSimpleHashVerification=1` + +and update the post-build command to use simple hash verification instead of the +default options. Go to +`Project -> Properties -> C/C++ Build -> Settings -> Build steps` and press +`Edit` under `Post-build steps` subsection. The command should look similar to: + +![SSBL_SIMPLE_HASH_VERIFICATION](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_simple_hash.JPG) + Once compiled, the required SSBL file is called `k32w061dk6_ssbl.bin`. ![SSBL_BIN](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_bin.JPG) diff --git a/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h index 63abce6c8c0682..10019a4268cad2 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ b/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h @@ -59,6 +59,11 @@ #define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0x1037 #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0xA220 +// Set the following define to use the Certification Declaration from below and not use it stored in factory data section +#ifndef CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION +#define CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION 0 +#endif + #ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION //-> format_version = 1 //-> vendor_id = 0x1037 diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp index 02656ae62299c7..e63c35a1489ee0 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -249,6 +249,14 @@ CHIP_ERROR AppTask::Init() K32W_LOG("Current Software Version: %s, %" PRIu32, currentSoftwareVer, currentVersion); +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + /* SSBL will always be seen as booting from address 0, thanks to the remapping mechanism. + * This means the SSBL version will always offset from address 0. */ + extern uint32_t __MATTER_SSBL_VERSION_START[]; + K32W_LOG("Current SSBL Version: %ld. Found at address 0x%lx", *((uint32_t *) __MATTER_SSBL_VERSION_START), + (uint32_t) __MATTER_SSBL_VERSION_START); +#endif + return err; } @@ -897,11 +905,24 @@ void AppTask::PostTurnOnActionRequest(int32_t aActor, LightingManager::Action_t void AppTask::PostEvent(const AppEvent * aEvent) { + portBASE_TYPE taskToWake = pdFALSE; if (sAppEventQueue != NULL) { - if (!xQueueSend(sAppEventQueue, aEvent, 1)) + if (__get_IPSR()) { - K32W_LOG("Failed to post event to app task event queue"); + if (!xQueueSendToFrontFromISR(sAppEventQueue, aEvent, &taskToWake)) + { + K32W_LOG("Failed to post event to app task event queue"); + } + + portYIELD_FROM_ISR(taskToWake); + } + else + { + if (!xQueueSend(sAppEventQueue, aEvent, 1)) + { + K32W_LOG("Failed to post event to app task event queue"); + } } } } diff --git a/examples/lock-app/nxp/k32w/k32w0/args.gni b/examples/lock-app/nxp/k32w/k32w0/args.gni index dd8543b45f9f8f..a733921b547e38 100644 --- a/examples/lock-app/nxp/k32w/k32w0/args.gni +++ b/examples/lock-app/nxp/k32w/k32w0/args.gni @@ -21,3 +21,7 @@ k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") chip_stack_lock_tracking = "fatal" chip_enable_ble = true + +chip_enable_icd_server = true +chip_persist_subscriptions = true +chip_subscription_timeout_resumption = true diff --git a/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h index 046b005f78da73..c74714bded920f 100644 --- a/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ b/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h @@ -59,6 +59,11 @@ #define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0x1037 #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0xA220 +// Set the following define to use the Certification Declaration from below and not use it stored in factory data section +#ifndef CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION +#define CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION 0 +#endif + #ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION //-> format_version = 1 //-> vendor_id = 0x1037 @@ -179,8 +184,6 @@ #define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics #define CHIP_DEVICE_CONFIG_ENABLE_SED 1 -#define CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL 1000_ms32 -#define CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL 100_ms32 /** * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE diff --git a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld index c7a2e4009865e5..d3ea6ac41daf78 100644 --- a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld +++ b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld @@ -432,8 +432,13 @@ SECTIONS __StackLimit = _vStackTop - STACK_SIZE; - __FACTORY_DATA_START = FACTORY_DATA_START_ADDRESS; - __FACTORY_DATA_SIZE = m_factory_data_size; + __MATTER_FACTORY_DATA_START = FACTORY_DATA_START_ADDRESS; + __MATTER_FACTORY_DATA_SIZE = m_factory_data_size; + + /* The .ro_version section inside SSBL is set after the .m_interrupts sections, + * which is assumed to never change, so the offset remains the same across different + * SSBL versions. This symbol is used in Matter Application to retrieve the SSBL version. */ + __MATTER_SSBL_VERSION_START = 0x00000120; ASSERT(((m_app_start + m_app_size + m_app_meta_data + m_factory_data_size + m_flash_config_size) <= m_int_flash_size), "Internal flash capacity exceeded") diff --git a/examples/platform/nxp/k32w/k32w0/doc/images/ssbl_simple_hash.JPG b/examples/platform/nxp/k32w/k32w0/doc/images/ssbl_simple_hash.JPG new file mode 100755 index 0000000000000000000000000000000000000000..9ec701b0bb1d86cac72c2df63702fde227d33f77 GIT binary patch literal 56397 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 z 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 j0)*B5AvD=)=h_3Arp0p6F($CYi#A94 /dev/null) && (dos2unix " + sign_images_path + ")", shell=True) + # Give execute permission if needed + if os.access(sign_images_path, os.X_OK) is False: + os.chmod(sign_images_path, 0o766) -# Call sign_images.sh script with the output directory -subprocess.call(sign_images_path + " " + os.getcwd(), shell=True) + # Convert script to unix format if needed + subprocess.call("(file " + sign_images_path + " | grep CRLF > /dev/null) && (dos2unix " + sign_images_path + ")", shell=True) + + # Call sign_images.sh script with the output directory + cmd = sign_images_path + " " + os.getcwd() + if args.simple_hash: + cmd = cmd + " -SimpleHashVerification" + + subprocess.call(cmd, shell=True) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--simple-hash", + help="When enabled, adds a hash of the whole image at the end of the binary.", + action="store_true" + ) + args = parser.parse_args() + + main(args) diff --git a/scripts/tools/nxp/factory_data_generator/generate.py b/scripts/tools/nxp/factory_data_generator/generate.py index daf1b5d1267cb7..a203ac9dee749e 100755 --- a/scripts/tools/nxp/factory_data_generator/generate.py +++ b/scripts/tools/nxp/factory_data_generator/generate.py @@ -27,6 +27,14 @@ SetupPasscode, StrArgument, UniqueId, VendorId, VendorName, Verifier) from default import InputArgument +# A magic value used in the factory data integrity check. +# The value will be checked at runtime, before verifying the +# factory data integrity. Factory data header has the following format: +# | hash id (4 bytes) | size (4 bytes) | hash (4 bytes) | +# If the hash id check fails, it means the factory data is either missing +# or has become corrupted. +HASH_ID = "CE47BA5E" + def set_logger(): stdout_handler = logging.StreamHandler(stream=sys.stdout) @@ -155,9 +163,9 @@ def to_bin(self, klv, out, aes128_key): fullContentCipher = size.to_bytes(4, "little") + fullContentCipher # Add hash id - hashId = bytearray.fromhex("CE47BA5E") + hashId = bytearray.fromhex(HASH_ID) hashId.reverse() - fullContentCipher = hashId.reverse() + fullContentCipher + fullContentCipher = hashId + fullContentCipher size = len(fullContentCipher) diff --git a/scripts/tools/nxp/generate_certs.py b/scripts/tools/nxp/generate_certs.py new file mode 100644 index 00000000000000..7a98f7138ebe05 --- /dev/null +++ b/scripts/tools/nxp/generate_certs.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023 Project CHIP Authors +# +# 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. +# + +import argparse +import logging as log +import os +import subprocess + +MATTER_ROOT = os.path.dirname(os.path.realpath(f"{__file__}/../../../")) + + +def gen_test_certs(chip_cert_exe: str, + output: str, + vendor_id: int, + product_id: int, + device_name: str, + generate_cd: bool = False, + cd_type: int = 1, + device_type: int = 1, + paa_cert_path: str = None, + paa_key_path: str = None, + valid_from: str = "2023-01-01 00:00:00", + lifetime: str = "7305"): + """ + Generate Matter certificates according to given Vendor ID and Product ID using the chip-cert executable. + To use own Product Attestation Authority certificate provide paa_cert_path and paa_key_path arguments. + Without providing these arguments a PAA certificate will be generated in the output directory. + + Args: + chip_cert_exe (str): path to chip-cert executable + output (str): output path to store a newly generated certificates (CD, DAC, PAI) + vendor_id (int): an identification number specific to Vendor + product_id (int): an identification number specific to Product + device_name (str): human-readable device name + generate_cd (bool, optional): Generate Certificate Declaration and store it in the output directory. Defaults to False. + paa_cert_path (str, optional): provide PAA certification path. Defaults to None - the certificate and key will + be generated. + paa_key_path (str, optional): provide PAA key path. Defaults to None - the certificate and key will be generated. + """ + + CD_PATH = MATTER_ROOT + "/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem" + CD_KEY_PATH = MATTER_ROOT + "/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem" + + log.info("Generating new certificates using chip-cert...") + + if generate_cd: + log.info("Generating Certification Declaration...") + cmd = [chip_cert_exe, "gen-cd", + "--key", CD_KEY_PATH, + "--cert", CD_PATH, + "--out", output + "/Chip-Test-CD-" + f'{vendor_id:X}' + "-" + f'{product_id:X}' + ".der", + "--format-version", "1", + "--vendor-id", hex(vendor_id), + "--product-id", hex(product_id), + "--device-type-id", hex(device_type), + "--certificate-id", "ZIG20142ZB330003-24", + "--security-level", "0", + "--security-info", "0", + "--certification-type", str(cd_type), + "--version-number", "9876", + ] + subprocess.run(cmd) + + new_certificates = { + "PAA_CERT": output + "/Chip-PAA-NXP-Cert", + "PAA_KEY": output + "/Chip-PAA-NXP-Key", + "PAI_CERT": output + "/Chip-PAI-NXP-" + f'{vendor_id:X}' + "-" + f'{product_id:X}' + "-Cert", + "PAI_KEY": output + "/Chip-PAI-NXP-" + f'{vendor_id:X}' + "-" + f'{product_id:X}' + "-Key", + "DAC_CERT": output + "/Chip-DAC-NXP-" + f'{vendor_id:X}' + "-" + f'{product_id:X}' + "-Cert", + "DAC_KEY": output + "/Chip-DAC-NXP-" + f'{vendor_id:X}' + "-" + f'{product_id:X}' + "-Key", + } + + if ((paa_cert_path is None) or (paa_key_path is None)): + log.info("Generating PAA certificate...") + cmd = [ + chip_cert_exe, "gen-att-cert", + "--type", "a", + "--subject-cn", device_name, + "--out", new_certificates["PAA_CERT"] + ".pem", + "--out-key", new_certificates["PAA_KEY"] + ".pem", + "--valid-from", valid_from, + "--lifetime", lifetime + ] + subprocess.run(cmd) + PAA_PATH = new_certificates["PAA_CERT"] + ".pem" + PAA_KEY_PATH = new_certificates["PAA_KEY"] + ".pem" + else: + PAA_PATH = paa_cert_path + PAA_KEY_PATH = paa_key_path + log.info("Using PAA certificate: " + PAA_PATH) + log.info("using PAA key: " + PAA_KEY_PATH) + + log.info("Generating PAI certificate...") + cmd = [ + chip_cert_exe, "gen-att-cert", + "--type", "i", + "--subject-cn", device_name, + "--subject-vid", hex(vendor_id), + "--ca-cert", PAA_PATH, + "--ca-key", PAA_KEY_PATH, + "--out", new_certificates["PAI_CERT"] + ".pem", + "--out-key", new_certificates["PAI_KEY"] + ".pem", + "--valid-from", valid_from, + "--lifetime", lifetime + ] + subprocess.run(cmd) + + log.info("Generating DAC certificate...") + cmd = [ + chip_cert_exe, "gen-att-cert", + "--type", "d", + "--subject-cn", device_name, + "--subject-vid", hex(vendor_id), + "--subject-pid", hex(product_id), + "--ca-cert", new_certificates["PAI_CERT"] + ".pem", + "--ca-key", new_certificates["PAI_KEY"] + ".pem", + "--out", new_certificates["DAC_CERT"] + ".pem", + "--out-key", new_certificates["DAC_KEY"] + ".pem", + "--valid-from", valid_from, + "--lifetime", lifetime + ] + subprocess.run(cmd) + + log.info("Converting to .der files...") + for cert_k, cert_v in new_certificates.items(): + action_type = "convert-cert" if cert_k.find("CERT") != -1 else "convert-key" + log.info(cert_v + ".der") + cmd = [ + chip_cert_exe, + action_type, + "--x509-der", + cert_v + ".pem", + cert_v + ".der", + ] + subprocess.run(cmd) + + +def main(): + parser = argparse.ArgumentParser(description="NXP CHIP Certificates generator") + + def allow_any_int(i): return int(i, 0) + + parser.add_argument("--chip_cert_path", type=str, required=True, + help=("This tool requires a path to chip-cert executable. " + "By default you can find chip-cert in connectedhomeip/src/tools/chip-cert directory " + "and build it there.")) + parser.add_argument("-o", "--output", type=str, required=True, + help="Output path to store certificates, e.g. /path/to/my/dir") + parser.add_argument("--vendor_id", type=allow_any_int, required=True, + help="[int | hex int] Provide Vendor Identification Number") + parser.add_argument("--product_id", type=allow_any_int, required=True, + help="[int | hex int] Provide Product Identification Number") + parser.add_argument("--vendor_name", type=str, required=True, + help="[string] provide human-readable vendor name") + parser.add_argument("--product_name", type=str, required=True, + help="[string] provide human-readable product name") + parser.add_argument("--gen_cd", action="store_true", default=False, + help=("Generate a new Certificate Declaration in .der format according to used Vendor ID " + "and Product ID.")) + parser.add_argument("--cd_type", type=int, default=1, + help=("[int] Type of generated Certification Declaration: " + "0 - development, 1 - provisional, 2 - official")) + parser.add_argument("--device_type", type=int, default=0, + help=("[int] Provides the primary device type implemented by the node. " + "This must be one of the device type identifiers defined in the Matter Device Library " + "specification.")) + parser.add_argument("--paa_cert", type=str, + help=("Provide a path to the Product Attestation Authority (PAA) certificate to generate " + "the PAI certificate. Without providing it, a testing PAA certificate will be generated.")) + parser.add_argument("--paa_key", type=str, + help=("Provide a path to the Product Attestation Authority (PAA) key to generate " + "the PAI certificate. Without providing it, a testing PAA key will be generated.")) + parser.add_argument("--valid_from", type=str, default="2023-01-01 00:00:00", + help=("The start date for the certificate's validity period in" + "--
[ :: ] format. Default to 2023-01-01 00:00:00")) + parser.add_argument("--lifetime", type=str, default="7305", + help=("The lifetime for the new certificate, in whole days. Default to 7305 days.")) + args = parser.parse_args() + + log.basicConfig(format='[%(levelname)s] %(message)s', level=log.INFO) + + gen_test_certs(args.chip_cert_path, + args.output, + args.vendor_id, + args.product_id, + args.vendor_name + " " + args.product_name, + args.gen_cd, + args.cd_type, + args.device_type, + args.paa_cert, + args.paa_key, + args.valid_from, + args.lifetime) + + +if __name__ == "__main__": + main() diff --git a/scripts/tools/nxp/ota/crypto_utils.py b/scripts/tools/nxp/ota/crypto_utils.py new file mode 100755 index 00000000000000..dbab140cf57cf9 --- /dev/null +++ b/scripts/tools/nxp/ota/crypto_utils.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# 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. +# +""" +A pure python (slow) implementation of rijndael with a decent interface + +To include - + +from rijndael import rijndael + +To do a key setup - + +r = rijndael(key, block_size = 16) + +key must be a string of length 16, 24, or 32 +blocksize must be 16, 24, or 32. Default is 16 + +To use - + +ciphertext = r.encrypt(plaintext) +plaintext = r.decrypt(ciphertext) + +If any strings are of the wrong length a ValueError is thrown +""" +# ported from the Java reference code by Bram Cohen, April 2001 +# this code is public domain, unless someone makes +# an intellectual property claim against the reference +# code, in which case it can be made public domain by +# deleting all the comments and renaming all the variables + +import copy +import logging +import struct + +shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], + [[0, 0], [1, 5], [2, 4], [3, 3]], + [[0, 0], [1, 7], [3, 5], [4, 4]]] + +# [keysize][block_size] +num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} + +A = [[1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 1, 1], + [1, 1, 1, 1, 0, 0, 0, 1]] + +# produce log and alog tables, needed for multiplying in the +# field GF(2^m) (generator = 3) +alog = [1] +for i in range(255): + j = (alog[-1] << 1) ^ alog[-1] + if j & 0x100 != 0: + j ^= 0x11B + alog.append(j) + +log = [0] * 256 +for i in range(1, 255): + log[alog[i]] = i + + +# multiply two elements of GF(2^m) +def mul(a, b): + if a == 0 or b == 0: + return 0 + return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] # noqa: F821 + + +# substitution box based on F^{-1}(x) +box = [[0] * 8 for i in range(256)] +box[1][7] = 1 +for i in range(2, 256): + j = alog[255 - log[i]] + for t in range(8): + box[i][t] = (j >> (7 - t)) & 0x01 + +B = [0, 1, 1, 0, 0, 0, 1, 1] + +# affine transform: box[i] <- B + A*box[i] +cox = [[0] * 8 for i in range(256)] +for i in range(256): + for t in range(8): + cox[i][t] = B[t] + for j in range(8): + cox[i][t] ^= A[t][j] * box[i][j] + +# S-boxes and inverse S-boxes +S = [0] * 256 +Si = [0] * 256 +for i in range(256): + S[i] = cox[i][0] << 7 + for t in range(1, 8): + S[i] ^= cox[i][t] << (7-t) + Si[S[i] & 0xFF] = i + +# T-boxes +G = [[2, 1, 1, 3], + [3, 2, 1, 1], + [1, 3, 2, 1], + [1, 1, 3, 2]] + +AA = [[0] * 8 for i in range(4)] + +for i in range(4): + for j in range(4): + AA[i][j] = G[i][j] + AA[i][i+4] = 1 + +for i in range(4): + pivot = AA[i][i] + if pivot == 0: + t = i + 1 + while AA[t][i] == 0 and t < 4: + t += 1 + assert t != 4, 'G matrix must be invertible' + for j in range(8): + AA[i][j], AA[t][j] = AA[t][j], AA[i][j] + pivot = AA[i][i] + for j in range(8): + if AA[i][j] != 0: + AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] + for t in range(4): + if i != t: + for j in range(i+1, 8): + AA[t][j] ^= mul(AA[i][j], AA[t][i]) + AA[t][i] = 0 + +iG = [[0] * 4 for i in range(4)] + +for i in range(4): + for j in range(4): + iG[i][j] = AA[i][j + 4] + + +def mul4(a, bs): + if a == 0: + return 0 + r = 0 + for b in bs: + r <<= 8 + if b != 0: + r = r | mul(a, b) # noqa: F821 + return r + + +T1 = [] +T2 = [] +T3 = [] +T4 = [] +T5 = [] +T6 = [] +T7 = [] +T8 = [] +U1 = [] +U2 = [] +U3 = [] +U4 = [] + +for t in range(256): + s = S[t] + T1.append(mul4(s, G[0])) + T2.append(mul4(s, G[1])) + T3.append(mul4(s, G[2])) + T4.append(mul4(s, G[3])) + + s = Si[t] + T5.append(mul4(s, iG[0])) + T6.append(mul4(s, iG[1])) + T7.append(mul4(s, iG[2])) + T8.append(mul4(s, iG[3])) + + U1.append(mul4(t, iG[0])) + U2.append(mul4(t, iG[1])) + U3.append(mul4(t, iG[2])) + U4.append(mul4(t, iG[3])) + +# round constants +rcon = [1] +r = 1 +for t in range(1, 30): + r = mul(2, r) + rcon.append(r) + +del A +del AA +del pivot +del B +del G +del box +del log +del alog +del i +del j +del r +del s +del t +del mul +del mul4 +del cox +del iG + + +class rijndael: + def __init__(self, key, block_size=16): + if block_size != 16 and block_size != 24 and block_size != 32: + raise ValueError('Invalid block size: ' + str(block_size)) + if len(key) != 16 and len(key) != 24 and len(key) != 32: + raise ValueError('Invalid key size: ' + str(len(key))) + self.block_size = block_size + + ROUNDS = num_rounds[len(key)][block_size] + BC = int(block_size / 4) + + # encryption round keys + Ke = [[0] * BC for i in range(ROUNDS + 1)] + # decryption round keys + Kd = [[0] * BC for i in range(ROUNDS + 1)] + ROUND_KEY_COUNT = (ROUNDS + 1) * BC + KC = int(len(key) / 4) + + # copy user material bytes into temporary ints + tk = [] + for i in range(0, KC): + tk.append((key[i * 4] << 24) | (key[i * 4 + 1] << 16) | + (key[i * 4 + 2] << 8) | key[i * 4 + 3]) + + # copy values into round key arrays + t = 0 + j = 0 + while j < KC and t < ROUND_KEY_COUNT: + Ke[int(t / BC)][t % BC] = tk[j] + Kd[ROUNDS - int(t / BC)][t % BC] = tk[j] + j += 1 + t += 1 + tt = 0 + rconpointer = 0 + while t < ROUND_KEY_COUNT: + # extrapolate using phi (the round key evolution function) + tt = tk[KC - 1] + tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ + (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ + (S[tt & 0xFF] & 0xFF) << 8 ^ \ + (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ + (rcon[rconpointer] & 0xFF) << 24 + rconpointer += 1 + if KC != 8: + for i in range(1, KC): + tk[i] ^= tk[i-1] + else: + for i in range(1, KC / 2): + tk[i] ^= tk[i-1] + tt = tk[KC / 2 - 1] + tk[KC / 2] ^= (S[tt & 0xFF] & 0xFF) ^ \ + (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ + (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ + (S[(tt >> 24) & 0xFF] & 0xFF) << 24 + for i in range(KC / 2 + 1, KC): + tk[i] ^= tk[i-1] + # copy values into round key arrays + j = 0 + while j < KC and t < ROUND_KEY_COUNT: + Ke[int(t / BC)][t % BC] = tk[j] + Kd[ROUNDS - int(t / BC)][t % BC] = tk[j] + j += 1 + t += 1 + # inverse MixColumn where needed + for r in range(1, ROUNDS): + for j in range(BC): + tt = Kd[r][j] + Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ + U2[(tt >> 16) & 0xFF] ^ \ + U3[(tt >> 8) & 0xFF] ^ \ + U4[tt & 0xFF] + self.Ke = Ke + self.Kd = Kd + + def encrypt(self, plaintext): + if len(plaintext) != self.block_size: + raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) + Ke = self.Ke + + BC = int(self.block_size / 4) + ROUNDS = len(Ke) - 1 + if BC == 4: + SC = 0 + elif BC == 6: + SC = 1 + else: + SC = 2 + s1 = shifts[SC][1][0] + s2 = shifts[SC][2][0] + s3 = shifts[SC][3][0] + a = [0] * BC + # temporary work array + t = [] + # plaintext to ints + key + for i in range(BC): + t.append((ord(plaintext[i * 4]) << 24 | + ord(plaintext[i * 4 + 1]) << 16 | + ord(plaintext[i * 4 + 2]) << 8 | + ord(plaintext[i * 4 + 3])) ^ Ke[0][i]) + # apply round transforms + for r in range(1, ROUNDS): + for i in range(BC): + a[i] = (T1[(t[i] >> 24) & 0xFF] ^ + T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ + T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ + T4[t[(i + s3) % BC] & 0xFF]) ^ Ke[r][i] + t = copy.copy(a) + # last round is special + result = [] + for i in range(BC): + tt = Ke[ROUNDS][i] + result.append((S[(t[i] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((S[t[(i + s3) % BC] & 0xFF] ^ tt) & 0xFF) + return ''.join(list(map(chr, result))) + + def decrypt(self, ciphertext): + if len(ciphertext) != self.block_size: + raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(ciphertext))) + Kd = self.Kd + + BC = int(self.block_size / 4) + ROUNDS = len(Kd) - 1 + if BC == 4: + SC = 0 + elif BC == 6: + SC = 1 + else: + SC = 2 + s1 = shifts[SC][1][1] + s2 = shifts[SC][2][1] + s3 = shifts[SC][3][1] + a = [0] * BC + # temporary work array + t = [0] * BC + # ciphertext to ints + key + for i in range(BC): + t[i] = (ord(ciphertext[i * 4]) << 24 | + ord(ciphertext[i * 4 + 1]) << 16 | + ord(ciphertext[i * 4 + 2]) << 8 | + ord(ciphertext[i * 4 + 3])) ^ Kd[0][i] + # apply round transforms + for r in range(1, ROUNDS): + for i in range(BC): + a[i] = (T5[(t[i] >> 24) & 0xFF] ^ + T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ + T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ + T8[t[(i + s3) % BC] & 0xFF]) ^ Kd[r][i] + t = copy.copy(a) + # last round is special + result = [] + for i in range(BC): + tt = Kd[ROUNDS][i] + result.append((Si[(t[i] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((Si[t[(i + s3) % BC] & 0xFF] ^ tt) & 0xFF) + return ''.join(map(chr, result)) + + +def encryptFlashData(nonce, key, data, imageLen): + encyptedBlock = '' + if (imageLen % 16) != 0: + for x in range(16 - (imageLen % 16)): + data = data + bytes([255]) + imageLen = len(data) + + r = rijndael(key, block_size=16) + + for x in range(int(imageLen / 16)): + # use nonce value to create encrypted chunk + encryptNonce = '' + for i in nonce: + tempString = "%08x" % i + y = 0 + while y < 8: + encryptNonce = encryptNonce + chr(int(tempString[y:y+2], 16)) + y = y + 2 + encChunk = r.encrypt(encryptNonce) + + # increment the nonce value + if (nonce[3] == 0xffffffff): + nonce[3] = 0 + else: + nonce[3] += 1 + + # xor encypted junk with data chunk + chunk = data[x*16:(x+1)*16] # Read 16 byte chucks. 128 bits + + lchunk = chunk + lencChunk = list(map(ord, encChunk)) + + loutChunk = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + for i in range(16): + loutChunk[i] = lchunk[i] ^ lencChunk[i] + encyptedBlock = encyptedBlock + chr(lchunk[i] ^ lencChunk[i]) + + return (encyptedBlock) + + +def aParsePassKeyString(sPassKey): + lstu32Passkey = [0, 0, 0, 0] + + try: + lstStrPassKey = sPassKey.split(",") + + except Exception: + sPassKey = "0x00000000, 0x00000000, 0x00000000, 0x00000000" + lstStrPassKey = sPassKey.split(",") + + if len(lstStrPassKey) == 4: + for i in range(4): + if "0x" in lstStrPassKey[i]: + lstu32Passkey[i] = int(lstStrPassKey[i], 16) + else: + lstu32Passkey[i] = int(lstStrPassKey[i], 10) + + logging.info(f"\t-key: {lstu32Passkey[0]}, {lstu32Passkey[1]}, {lstu32Passkey[2]}, {lstu32Passkey[3]}") + abEncryptKey = struct.pack(">LLLL", lstu32Passkey[0], + lstu32Passkey[1], + lstu32Passkey[2], + lstu32Passkey[3]) + return abEncryptKey + + +def aParseNonce(sNonceValue): + lstu32Nonce = [0, 0, 0, 0] + + try: + lstStrNonce = sNonceValue.split(",") + + except Exception: + sNonceValue = "0x00000000, 0x00000000, 0x00000000, 0x00000000" + lstStrNonce = sNonceValue.split(",") + + if len(lstStrNonce) == 4: + for i in range(4): + if "0x" in lstStrNonce[i]: + lstu32Nonce[i] = int(lstStrNonce[i], 16) + else: + lstu32Nonce[i] = int(lstStrNonce[i], 10) + + logging.info(f"Nonce : {lstu32Nonce[0]}, {lstu32Nonce[1]}, {lstu32Nonce[2]}, {lstu32Nonce[3]}") + + return lstu32Nonce + + +def encryptData(sSrcData, sPassKey, aPassIv): + + sKeyString = sPassKey.strip() + assert len(sKeyString) == 32, 'the length of encryption key should be equal to 32' + sPassString = "0x" + sKeyString[:8] + ',' + "0x" + sKeyString[8:16] + \ + ',' + "0x" + sKeyString[16:24] + ',' + "0x" + sKeyString[24:32] + aPassKey = aParsePassKeyString(sPassString) + + sIvString = aPassIv.strip() + sPassString = "0x" + sIvString[:8] + ',' + "0x" + sIvString[8:16] + \ + ',' + "0x" + sIvString[16:24] + ',' + "0x" + sIvString[24:32] + aNonce = aParseNonce(sPassString) + + logging.info("Started Encrypting with key[{}] ......".format(sPassKey)) + + encryptedData = encryptFlashData(aNonce, aPassKey, sSrcData, len(sSrcData)) + + logging.info("Done") + + return encryptedData diff --git a/scripts/tools/nxp/ota/ota_image_tool.py b/scripts/tools/nxp/ota/ota_image_tool.py index bf796f1010119f..64715d784aeecd 100755 --- a/scripts/tools/nxp/ota/ota_image_tool.py +++ b/scripts/tools/nxp/ota/ota_image_tool.py @@ -36,6 +36,7 @@ import os import sys +import crypto_utils import jsonschema sys.path.insert(0, os.path.join( @@ -55,6 +56,8 @@ OTA_BOOTLOADER_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_ssbl_tlv.bin") OTA_FACTORY_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_factory_tlv.bin") +INITIALIZATION_VECTOR = "00000010111213141516171800000000" + class TAG: APPLICATION = 1 @@ -92,8 +95,15 @@ def generate_factory_data(args: object): if fields: writer = TLVWriter() writer.put(None, fields) - payload = generate_header(TAG.FACTORY_DATA, len(writer.encoding)) - payload += writer.encoding + logging.info(f"factory data encryption enable: {args.enc_enable}") + if args.enc_enable: + enc_factory_data = crypto_utils.encryptData(writer.encoding, args.input_ota_key, INITIALIZATION_VECTOR) + enc_factory_data1 = bytes([ord(x) for x in enc_factory_data]) + payload = generate_header(TAG.FACTORY_DATA, len(enc_factory_data1)) + payload += enc_factory_data1 + else: + payload = generate_header(TAG.FACTORY_DATA, len(writer.encoding)) + payload += writer.encoding write_to_temp(OTA_FACTORY_TLV_TEMP, payload) @@ -126,12 +136,23 @@ def generate_app(args: object): logging.info("App descriptor information:") descriptor = generate_descriptor(args.app_version, args.app_version_str, args.app_build_date) - file_size = os.path.getsize(args.app_input_file) - payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor + logging.info(f"App encryption enable: {args.enc_enable}") + if args.enc_enable: + inputFile = open(args.app_input_file, "rb") + enc_file = crypto_utils.encryptData(inputFile.read(), args.input_ota_key, INITIALIZATION_VECTOR) + enc_file1 = bytes([ord(x) for x in enc_file]) + file_size = len(enc_file1) + payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor + enc_file1 + else: + file_size = os.path.getsize(args.app_input_file) + logging.info(f"file size: {file_size}") + payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor write_to_temp(OTA_APP_TLV_TEMP, payload) - - return [OTA_APP_TLV_TEMP, args.app_input_file] + if args.enc_enable: + return [OTA_APP_TLV_TEMP] + else: + return [OTA_APP_TLV_TEMP, args.app_input_file] def generate_bootloader(args: object): @@ -141,12 +162,23 @@ def generate_bootloader(args: object): logging.info("SSBL descriptor information:") descriptor = generate_descriptor(args.bl_version, args.bl_version_str, args.bl_build_date) - file_size = os.path.getsize(args.bl_input_file) - payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor + logging.info(f"Bootloader encryption enable: {args.enc_enable}") + if args.enc_enable: + inputFile = open(args.bl_input_file, "rb") + enc_file = crypto_utils.encryptData(inputFile.read(), args.input_ota_key, INITIALIZATION_VECTOR) + enc_file1 = bytes([ord(x) for x in enc_file]) + file_size = len(enc_file1) + payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor + enc_file1 + else: + file_size = os.path.getsize(args.bl_input_file) + logging.info(f"file size: {file_size}") + payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor write_to_temp(OTA_BOOTLOADER_TLV_TEMP, payload) - - return [OTA_BOOTLOADER_TLV_TEMP, args.bl_input_file] + if args.enc_enable: + return [OTA_BOOTLOADER_TLV_TEMP] + else: + return [OTA_BOOTLOADER_TLV_TEMP, args.bl_input_file] def validate_json(data: str): @@ -231,6 +263,9 @@ def create_image(args: object): for filename in glob.glob(os.path.dirname(__file__) + "/ota_temp_*"): os.remove(filename) + if args.enc_enable: + for filename in glob.glob(os.path.dirname(__file__) + "/enc_ota_temp_*"): + os.remove(filename) def main(): @@ -302,6 +337,10 @@ def any_base_int(s): return int(s, 0) # Path to input JSON file which describes custom TLVs. create_parser.add_argument('--json', help="[path] Path to the JSON describing custom TLVs") + create_parser.add_argument('--enc_enable', action="store_true", help='enable ota encryption') + create_parser.add_argument('--input_ota_key', type=str, default="1234567890ABCDEFA1B2C3D4E5F6F1B4", + help='Input OTA Encryption KEY (string:16Bytes)') + create_parser.add_argument('-i', '--input_files', default=list(), help='Path to input image payload file') create_parser.add_argument('output_file', help='Path to output image file') diff --git a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp index 65b2f68cb20e90..5d20035aa99940 100644 --- a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp +++ b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp @@ -172,11 +172,11 @@ CHIP_ERROR BLEManagerCommon::_Init() xTimerCreate("bleTimeoutTmr", pdMS_TO_TICKS(CHIP_BLE_KW_CONN_TIMEOUT), pdFALSE, (void *) 0, blekw_connection_timeout_cb); VerifyOrExit(connectionTimeout != NULL, err = CHIP_ERROR_INCORRECT_STATE); + sImplInstance = GetImplInstance(); + /* BLE platform code initialization */ SuccessOrExit(err = InitHostController(&blekw_generic_cb)); - sImplInstance = GetImplInstance(); - /* Register the GATT server callback */ VerifyOrExit(GattServer_RegisterCallback(blekw_gatt_server_cb) == gBleSuccess_c, err = CHIP_ERROR_INCORRECT_STATE); @@ -914,6 +914,13 @@ void BLEManagerCommon::DoBleProcessing(void) } } +void BLEManagerCommon::RegisterAppCallbacks(BLECallbackDelegate::GapGenericCallback gapCallback, + BLECallbackDelegate::GattServerCallback gattCallback) +{ + callbackDelegate.gapCallback = gapCallback; + callbackDelegate.gattCallback = gattCallback; +} + void BLEManagerCommon::HandleConnectEvent(blekw_msg_t * msg) { uint8_t deviceId = msg->data.u8; @@ -1078,6 +1085,11 @@ void BLEManagerCommon::blekw_generic_cb(gapGenericEvent_t * pGenericEvent) /* Call BLE Conn Manager */ BleConnManager_GenericEvent(pGenericEvent); + if (sImplInstance->callbackDelegate.gapCallback) + { + sImplInstance->callbackDelegate.gapCallback(pGenericEvent); + } + switch (pGenericEvent->eventType) { case gInternalError_c: @@ -1162,6 +1174,9 @@ void BLEManagerCommon::blekw_gap_connection_cb(deviceId_t deviceId, gapConnectio if (pConnectionEvent->eventType == gConnEvtConnected_c) { +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + PWR_DisallowDeviceToSleep(); +#endif #if CHIP_DEVICE_CONFIG_BLE_SET_PHY_2M_REQ ChipLogProgress(DeviceLayer, "BLE K32W: Trying to set the PHY to 2M"); @@ -1220,6 +1235,11 @@ void BLEManagerCommon::blekw_stop_connection_timeout(void) void BLEManagerCommon::blekw_gatt_server_cb(deviceId_t deviceId, gattServerEvent_t * pServerEvent) { + if (sImplInstance->callbackDelegate.gattCallback) + { + sImplInstance->callbackDelegate.gattCallback(deviceId, pServerEvent); + } + switch (pServerEvent->eventType) { case gEvtMtuChanged_c: { diff --git a/src/platform/nxp/k32w/common/BLEManagerCommon.h b/src/platform/nxp/k32w/common/BLEManagerCommon.h index d8a6561e50b380..32ccdd762cdea4 100644 --- a/src/platform/nxp/k32w/common/BLEManagerCommon.h +++ b/src/platform/nxp/k32w/common/BLEManagerCommon.h @@ -45,10 +45,20 @@ namespace chip { namespace DeviceLayer { namespace Internal { -typedef void (*ble_generic_cb_fp)(gapGenericEvent_t * pGenericEvent); - using namespace chip::Ble; +/** + * A delegate class that can be used by the application to subscribe to BLE events. + */ +struct BLECallbackDelegate +{ + using GapGenericCallback = void (*)(gapGenericEvent_t * event); + using GattServerCallback = void (*)(deviceId_t id, gattServerEvent_t * event); + + GapGenericCallback gapCallback = nullptr; + GattServerCallback gattCallback = nullptr; +}; + /** * Base class for different platform implementations (K32W0 and K32W1 for now). */ @@ -221,9 +231,13 @@ class BLEManagerCommon : public BLEManager, protected BleLayer, private BlePlatf static bool blekw_stop_connection_internal(BLE_CONNECTION_OBJECT conId); public: - virtual CHIP_ERROR InitHostController(ble_generic_cb_fp cb_fp) = 0; - virtual BLEManagerCommon * GetImplInstance() = 0; + virtual CHIP_ERROR InitHostController(BLECallbackDelegate::GapGenericCallback cb_fp) = 0; + virtual BLEManagerCommon * GetImplInstance() = 0; void DoBleProcessing(void); + + BLECallbackDelegate callbackDelegate; + void RegisterAppCallbacks(BLECallbackDelegate::GapGenericCallback gapCallback, + BLECallbackDelegate::GattServerCallback gattCallback); }; inline BLEManager::CHIPoBLEServiceMode BLEManagerCommon::_GetCHIPoBLEServiceMode(void) diff --git a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h index 58c1c73bba5b73..33c68807ca7af9 100644 --- a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h +++ b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h @@ -145,3 +145,14 @@ #define kNvmId_FactoryDataBackup (uint16_t) 0x7000 #endif #endif // CONFIG_CHIP_LOAD_REAL_FACTORY_DATA + +/** + * @def kKVS_RamBufferSize + * + * Size of KVS values RAM storage buffer. + * This value is empirical, based on some minimal resource requirements tests. + * Applications should overwrite this value if necessary. + */ +#ifndef kKVS_RamBufferSize +#define kKVS_RamBufferSize (12 * 1024) +#endif diff --git a/src/platform/nxp/k32w/common/FactoryDataProvider.cpp b/src/platform/nxp/k32w/common/FactoryDataProvider.cpp index 98bb1653210601..315811cbf32442 100644 --- a/src/platform/nxp/k32w/common/FactoryDataProvider.cpp +++ b/src/platform/nxp/k32w/common/FactoryDataProvider.cpp @@ -40,8 +40,8 @@ static constexpr size_t kSpake2pSerializedVerifier_MaxBase64Len = BASE64_ENCODED_LEN(chip::Crypto::kSpake2p_VerifierSerialized_Length) + 1; static constexpr size_t kSpake2pSalt_MaxBase64Len = BASE64_ENCODED_LEN(chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length) + 1; -uint32_t FactoryDataProvider::kFactoryDataStart = (uint32_t) __FACTORY_DATA_START; -uint32_t FactoryDataProvider::kFactoryDataSize = (uint32_t) __FACTORY_DATA_SIZE; +uint32_t FactoryDataProvider::kFactoryDataStart = (uint32_t) __MATTER_FACTORY_DATA_START; +uint32_t FactoryDataProvider::kFactoryDataSize = (uint32_t) __MATTER_FACTORY_DATA_SIZE; uint32_t FactoryDataProvider::kFactoryDataPayloadStart = kFactoryDataStart + sizeof(FactoryDataProvider::Header); FactoryDataProvider::FactoryDataProvider() @@ -123,11 +123,17 @@ CHIP_ERROR FactoryDataProvider::SearchForId(uint8_t searchedType, uint8_t * pBuf CHIP_ERROR FactoryDataProvider::GetCertificationDeclaration(MutableByteSpan & outBuffer) { +#if CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION + constexpr uint8_t kCdForAllExamples[] = CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION; + + return CopySpanToMutableSpan(ByteSpan{ kCdForAllExamples }, outBuffer); +#else uint16_t declarationSize = 0; ReturnErrorOnFailure(SearchForId(FactoryDataId::kCertDeclarationId, outBuffer.data(), outBuffer.size(), declarationSize)); outBuffer.reduce_size(declarationSize); return CHIP_NO_ERROR; +#endif } CHIP_ERROR FactoryDataProvider::GetFirmwareInformation(MutableByteSpan & out_firmware_info_buffer) diff --git a/src/platform/nxp/k32w/common/FactoryDataProvider.h b/src/platform/nxp/k32w/common/FactoryDataProvider.h index db10a8f6accb23..99b9a1e039cf34 100644 --- a/src/platform/nxp/k32w/common/FactoryDataProvider.h +++ b/src/platform/nxp/k32w/common/FactoryDataProvider.h @@ -26,8 +26,8 @@ #include "CHIPPlatformConfig.h" /* Grab symbol for the base address from the linker file. */ -extern uint32_t __FACTORY_DATA_START[]; -extern uint32_t __FACTORY_DATA_SIZE[]; +extern uint32_t __MATTER_FACTORY_DATA_START[]; +extern uint32_t __MATTER_FACTORY_DATA_SIZE[]; namespace chip { namespace DeviceLayer { diff --git a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp index 16493144c632be..c80bf64eaa357b 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp @@ -28,7 +28,7 @@ using namespace chip::DeviceLayer; using namespace ::chip::DeviceLayer::Internal; -#if USE_SMU2_AS_SYSTEM_MEMORY +#if USE_SMU2_STATIC // The attribute specifier should not be changed. static chip::OTAImageProcessorImpl gImageProcessor __attribute__((section(".smu2"))); #else diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp index 40b068b446ead5..7571e693728c00 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp @@ -22,9 +22,15 @@ #include #include - +#if OTA_ENCRYPTION_ENABLE +#include "OtaUtils.h" +#include "rom_aes.h" +#endif namespace chip { +#if OTA_ENCRYPTION_ENABLE +constexpr uint8_t au8Iv[] = { 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00 }; +#endif CHIP_ERROR OTATlvProcessor::Process(ByteSpan & block) { CHIP_ERROR status = CHIP_NO_ERROR; @@ -57,6 +63,9 @@ void OTATlvProcessor::ClearInternal() mLength = 0; mProcessedLength = 0; mWasSelected = false; +#if OTA_ENCRYPTION_ENABLE + mIVOffset = 0; +#endif } bool OTATlvProcessor::IsError(CHIP_ERROR & status) @@ -93,4 +102,75 @@ CHIP_ERROR OTADataAccumulator::Accumulate(ByteSpan & block) return CHIP_NO_ERROR; } +#if OTA_ENCRYPTION_ENABLE +CHIP_ERROR OTATlvProcessor::vOtaProcessInternalEncryption(MutableByteSpan & block) +{ + uint8_t iv[16]; + uint8_t key[16]; + uint8_t dataOut[16] = { 0 }; + uint32_t u32IVCount; + uint32_t Offset = 0; + uint8_t data; + tsReg128 sKey; + aesContext_t Context; + + memcpy(iv, au8Iv, sizeof(au8Iv)); + + u32IVCount = (((uint32_t) iv[12]) << 24) | (((uint32_t) iv[13]) << 16) | (((uint32_t) iv[14]) << 8) | (iv[15]); + u32IVCount += (mIVOffset >> 4); + + iv[12] = (uint8_t) ((u32IVCount >> 24) & 0xff); + iv[13] = (uint8_t) ((u32IVCount >> 16) & 0xff); + iv[14] = (uint8_t) ((u32IVCount >> 8) & 0xff); + iv[15] = (uint8_t) (u32IVCount & 0xff); + + size_t len = strlen(OTA_ENCRYPTION_KEY); + + if (len != 32) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + for (size_t i = 0; i < len; i += 2) + { + char hex[3] = { OTA_ENCRYPTION_KEY[i], OTA_ENCRYPTION_KEY[i + 1], '\0' }; + key[i / 2] = (uint8_t) strtol(hex, NULL, 16); + } + ByteSpan KEY = ByteSpan(key); + Encoding::LittleEndian::Reader reader_key(KEY.data(), KEY.size()); + ReturnErrorOnFailure(reader_key.Read32(&sKey.u32register0) + .Read32(&sKey.u32register1) + .Read32(&sKey.u32register2) + .Read32(&sKey.u32register3) + .StatusCode()); + + while (Offset + 16 <= block.size()) + { + /*Encrypt the IV*/ + Context.mode = AES_MODE_ECB_ENCRYPT; + Context.pSoftwareKey = (uint32_t *) &sKey; + AES_128_ProcessBlocks(&Context, (uint32_t *) &iv[0], (uint32_t *) &dataOut[0], 1); + + /* Decrypt a block of the buffer */ + for (uint8_t i = 0; i < 16; i++) + { + data = block[Offset + i] ^ dataOut[i]; + memcpy(&block[Offset + i], &data, sizeof(uint8_t)); + } + + /* increment the IV for the next block */ + u32IVCount++; + + iv[12] = (uint8_t) ((u32IVCount >> 24) & 0xff); + iv[13] = (uint8_t) ((u32IVCount >> 16) & 0xff); + iv[14] = (uint8_t) ((u32IVCount >> 8) & 0xff); + iv[15] = (uint8_t) (u32IVCount & 0xff); + + Offset += 16; /* increment the buffer offset */ + mIVOffset += 16; + } + + return CHIP_NO_ERROR; +} +#endif } // namespace chip diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.h b/src/platform/nxp/k32w/common/OTATlvProcessor.h index 1968f983eda14b..e412e1cdd1c5fa 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.h +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.h @@ -90,6 +90,9 @@ class OTATlvProcessor void SetLength(uint32_t length) { mLength = length; } void SetWasSelected(bool selected) { mWasSelected = selected; } bool WasSelected() { return mWasSelected; } +#if OTA_ENCRYPTION_ENABLE + CHIP_ERROR vOtaProcessInternalEncryption(MutableByteSpan & block); +#endif protected: /** @@ -121,6 +124,10 @@ class OTATlvProcessor bool IsError(CHIP_ERROR & status); +#if OTA_ENCRYPTION_ENABLE + /*ota decryption*/ + uint32_t mIVOffset = 0; +#endif uint32_t mLength = 0; uint32_t mProcessedLength = 0; bool mWasSelected = false; @@ -140,6 +147,7 @@ class OTADataAccumulator CHIP_ERROR Accumulate(ByteSpan & block); inline uint8_t * data() { return mBuffer.Get(); } + inline uint32_t GetThreshold() { return mThreshold; } private: uint32_t mThreshold; diff --git a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp index a7bc09d524360e..af56961cf23dc1 100644 --- a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp @@ -51,7 +51,7 @@ BLEManagerCommon * BLEManagerImpl::GetImplInstance() { return &BLEManagerImpl::sInstance; } -CHIP_ERROR BLEManagerImpl::InitHostController(ble_generic_cb_fp cb_fp) +CHIP_ERROR BLEManagerImpl::InitHostController(BLECallbackDelegate::GapGenericCallback cb_fp) { CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.h b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.h index 52fd69007d9aa0..eb8a25804804d1 100644 --- a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.h +++ b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.h @@ -44,7 +44,7 @@ class BLEManagerImpl : public BLEManagerCommon // the implementation methods provided by this class. friend BLEManager; - CHIP_ERROR InitHostController(ble_generic_cb_fp cb_fp) override; + CHIP_ERROR InitHostController(BLECallbackDelegate::GapGenericCallback cb_fp) override; BLEManagerCommon * GetImplInstance() override; private: diff --git a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h index 0f6258fe9cd824..0c849ccc4989aa 100644 --- a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h +++ b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h @@ -176,6 +176,18 @@ #define CHIP_DEVICE_CONFIG_ENABLE_THREAD_DNS_CLIENT 1 #endif +#if CHIP_DEVICE_CONFIG_ENABLE_SED + +#ifndef CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL +#define CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL chip::System::Clock::Milliseconds32(NXP_OT_IDLE_INTERVAL) +#endif // CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL + +#ifndef CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL +#define CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL chip::System::Clock::Milliseconds32(NXP_OT_ACTIVE_INTERVAL) +#endif // CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL + +#endif + #define CHIP_DEVICE_CONFIG_ENABLE_TEST_SETUP_PARAMS 1 #include diff --git a/src/platform/nxp/k32w/k32w0/CHIPPlatformConfig.h b/src/platform/nxp/k32w/k32w0/CHIPPlatformConfig.h index 6971c329fa37c6..2f582b28171996 100644 --- a/src/platform/nxp/k32w/k32w0/CHIPPlatformConfig.h +++ b/src/platform/nxp/k32w/k32w0/CHIPPlatformConfig.h @@ -77,4 +77,28 @@ #define WDM_PUBLISHER_MAX_NOTIFIES_IN_FLIGHT 2 #endif // WDM_PUBLISHER_MAX_NOTIFIES_IN_FLIGHT +#if NXP_ICD_ENABLED + +#ifndef CHIP_CONFIG_ICD_IDLE_MODE_INTERVAL_SEC +#define CHIP_CONFIG_ICD_IDLE_MODE_INTERVAL_SEC NXP_IDLE_MODE_INTERVAL +#endif // CHIP_CONFIG_ICD_IDLE_MODE_INTERVAL_SEC + +#ifndef CHIP_CONFIG_ICD_ACTIVE_MODE_INTERVAL_MS +#define CHIP_CONFIG_ICD_ACTIVE_MODE_INTERVAL_MS NXP_ACTIVE_MODE_INTERVAL +#endif // CHIP_CONFIG_ICD_ACTIVE_MODE_INTERVAL_MS + +#ifndef CHIP_CONFIG_ICD_ACTIVE_MODE_THRESHOLD_MS +#define CHIP_CONFIG_ICD_ACTIVE_MODE_THRESHOLD_MS NXP_ACTIVE_MODE_THRESHOLD +#endif // CHIP_CONFIG_ICD_ACTIVE_MODE_THRESHOLD_MS + +#ifndef CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC +#define CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC NXP_ICD_SUPPORTED_CLIENTS_PER_FABRIC +#endif // CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC + +#ifndef CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED +#define CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED 1 +#endif + +#endif + #include "platform/nxp/common/CHIPNXPPlatformDefaultConfig.h" diff --git a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp index 8b3e7491eec3bb..0cf81fa9bf1749 100644 --- a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp @@ -81,8 +81,8 @@ CHIP_ERROR KeyValueStoreManagerImpl::Init() { ChipLogProgress(DeviceLayer, "Cannot init KVS keys storage with id: %d. Error: %s", kNvmId_KvsKeys, ErrorStr(err)); } - // Set values storage to max RAM buffer size as a temporary fix for TC-RR-1.1. - err = sValuesStorage.Init(kRamBufferMaxAllocSize, true); + // Set values storage to a big RAM buffer size as a temporary fix for TC-RR-1.1. + err = sValuesStorage.Init(kKVS_RamBufferSize, true); if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, "Cannot init KVS values storage with id: %d. Error: %s", kNvmId_KvsValues, ErrorStr(err)); diff --git a/src/platform/nxp/k32w/k32w0/Logging.cpp b/src/platform/nxp/k32w/k32w0/Logging.cpp index b00366999023ee..537ceed1181694 100644 --- a/src/platform/nxp/k32w/k32w0/Logging.cpp +++ b/src/platform/nxp/k32w/k32w0/Logging.cpp @@ -45,7 +45,7 @@ void GetMessageString(char * buf, uint8_t bufLen, const char * module, uint8_t c */ assert(bufLen >= (timestamp_max_len_bytes + category_max_len_bytes + (strlen(module) + 2) + 1)); - writtenLen = snprintf(buf, bufLen, "[%" PRIu32, otPlatAlarmMilliGetNow()); + writtenLen = snprintf(buf, bufLen, "[%ld]", otPlatAlarmMilliGetNow()); bufLen -= writtenLen; buf += writtenLen; diff --git a/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp index dbf7d09c892de4..438623dbc97d2f 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp @@ -49,6 +49,10 @@ CHIP_ERROR OTAFactoryDataProcessor::ProcessInternal(ByteSpan & block) CHIP_ERROR error = CHIP_NO_ERROR; ReturnErrorOnFailure(mAccumulator.Accumulate(block)); +#if OTA_ENCRYPTION_ENABLE + MutableByteSpan mBlock = MutableByteSpan(mAccumulator.data(), mAccumulator.GetThreshold()); + OTATlvProcessor::vOtaProcessInternalEncryption(mBlock); +#endif error = DecodeTlv(); if (error != CHIP_NO_ERROR) diff --git a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp index cf26fc8c2149ea..760c9ef2eec8fc 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include "OtaSupport.h" @@ -29,6 +30,9 @@ CHIP_ERROR OTAFirmwareProcessor::Init() { ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED); mAccumulator.Init(sizeof(Descriptor)); +#if OTA_ENCRYPTION_ENABLE + mUnalignmentNum = 0; +#endif ReturnErrorCodeIf(gOtaSuccess_c != OTA_ClientInit(), CHIP_OTA_PROCESSOR_CLIENT_INIT); auto offset = OTA_GetCurrentEepromAddressOffset(); @@ -48,6 +52,9 @@ CHIP_ERROR OTAFirmwareProcessor::Clear() OTATlvProcessor::ClearInternal(); mAccumulator.Clear(); mDescriptorProcessed = false; +#if OTA_ENCRYPTION_ENABLE + mUnalignmentNum = 0; +#endif return CHIP_NO_ERROR; } @@ -57,7 +64,32 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) if (!mDescriptorProcessed) { ReturnErrorOnFailure(ProcessDescriptor(block)); +#if OTA_ENCRYPTION_ENABLE + /* 16 bytes to used to store undecrypted data because of unalignment */ + mAccumulator.Init(requestedOtaMaxBlockSize + 16); +#endif } +#if OTA_ENCRYPTION_ENABLE + MutableByteSpan mBlock = MutableByteSpan(mAccumulator.data(), mAccumulator.GetThreshold()); + memcpy(&mBlock[0], &mBlock[requestedOtaMaxBlockSize], mUnalignmentNum); + memcpy(&mBlock[mUnalignmentNum], block.data(), block.size()); + + if (mUnalignmentNum + block.size() < requestedOtaMaxBlockSize) + { + uint32_t mAlignmentNum = (mUnalignmentNum + block.size()) / 16; + mAlignmentNum = mAlignmentNum * 16; + mUnalignmentNum = (mUnalignmentNum + block.size()) % 16; + memcpy(&mBlock[requestedOtaMaxBlockSize], &mBlock[mAlignmentNum], mUnalignmentNum); + mBlock.reduce_size(mAlignmentNum); + } + else + { + mUnalignmentNum = mUnalignmentNum + block.size() - requestedOtaMaxBlockSize; + mBlock.reduce_size(requestedOtaMaxBlockSize); + } + + OTATlvProcessor::vOtaProcessInternalEncryption(mBlock); +#endif auto status = OTA_MakeHeadRoomForNextBlock(requestedOtaMaxBlockSize, OTAImageProcessorImpl::FetchNextData, 0); if (gOtaSuccess_c != status) @@ -65,8 +97,11 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) ChipLogError(SoftwareUpdate, "Failed to make room for next block. Status: %d", status); return CHIP_OTA_PROCESSOR_MAKE_ROOM; } - +#if OTA_ENCRYPTION_ENABLE + status = OTA_PushImageChunk((uint8_t *) mBlock.data(), (uint16_t) mBlock.size(), NULL, NULL); +#else status = OTA_PushImageChunk((uint8_t *) block.data(), (uint16_t) block.size(), NULL, NULL); +#endif if (gOtaSuccess_c != status) { ChipLogError(SoftwareUpdate, "Failed to write image block. Status: %d", status); diff --git a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h index bc12394598ac89..885dc239dd22b3 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h @@ -45,6 +45,9 @@ class OTAFirmwareProcessor : public OTATlvProcessor OTADataAccumulator mAccumulator; bool mDescriptorProcessed = false; +#if OTA_ENCRYPTION_ENABLE + uint32_t mUnalignmentNum; +#endif }; } // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp index daac54fb5a16e7..95a69b717c4af0 100644 --- a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp @@ -41,7 +41,7 @@ BLEManagerCommon * BLEManagerImpl::GetImplInstance() return &BLEManagerImpl::sInstance; } -CHIP_ERROR BLEManagerImpl::InitHostController(ble_generic_cb_fp cb_fp) +CHIP_ERROR BLEManagerImpl::InitHostController(BLECallbackDelegate::GapGenericCallback cb_fp) { CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h index 7aa4e9f1604b42..bef927c8275350 100644 --- a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h +++ b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h @@ -49,7 +49,7 @@ class BLEManagerImpl final : public BLEManagerCommon // the implementation methods provided by this class. friend BLEManager; - CHIP_ERROR InitHostController(ble_generic_cb_fp cb_fp) override; + CHIP_ERROR InitHostController(BLECallbackDelegate::GapGenericCallback cb_fp) override; BLEManagerCommon * GetImplInstance() override; private: diff --git a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni index e8858022e20516..ab37141606f19a 100644 --- a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni +++ b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni @@ -47,6 +47,21 @@ declare_args() { chip_enable_ota_factory_data_processor = 0 chip_with_pdm_encryption = 1 ota_custom_entry_address = "0x000C1000" + use_antenna_diversity = 0 + + #ICD Matter Configuration flags + + chip_ot_idle_interval_ms = 2000 # 2s Idle Intervals + chip_ot_active_interval_ms = 500 # 500ms Active Intervals + + nxp_idle_mode_interval_s = 600 # 10min Idle Mode Interval + nxp_active_mode_interval_ms = 10000 # 10s Active Mode Interval + nxp_active_mode_threshold_ms = 1000 # 1s Active Mode Threshold + nxp_icd_supported_clients_per_fabric = 2 # 2 registration slots per fabric + + chip_with_ota_encryption = 0 + chip_with_ota_key = "1234567890ABCDEFA1B2C3D4E5F6F1B4" + chip_with_sdk_package = 1 } assert(k32w0_sdk_root != "", "k32w0_sdk_root must be specified") @@ -132,6 +147,12 @@ template("k32w0_sdk") { chip_with_ntag = 0 } + if (k32w0_sdk_root == "${chip_root}/third_party/nxp/k32w0_sdk/repo/core" || + k32w0_sdk_root == "/opt/sdk/core") { + chip_with_sdk_package = 0 + } else { + chip_with_sdk_package = 1 + } print("device:", device) print("board:", board) print("ntag:", chip_with_ntag) @@ -153,6 +174,7 @@ template("k32w0_sdk") { print("ECC crypto lib: ", chip_crypto_flavor) } + print("chip_with_sdk_package:", chip_with_sdk_package) device_lowercase = string_replace(board, "dk6", "") sdk_target_name = target_name @@ -204,26 +226,7 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/components/serial_manager", "${k32w0_sdk_root}/components/uart", "${k32w0_sdk_root}/devices/${device}", - "${k32w0_sdk_root}/devices/K32W061/drivers", - "${k32w0_sdk_root}/drivers/common", - "${k32w0_sdk_root}/drivers/lpc_gpio", - "${k32w0_sdk_root}/drivers/aes", - "${k32w0_sdk_root}/drivers/jn_iocon", - "${k32w0_sdk_root}/drivers/lpc_adc", - "${k32w0_sdk_root}/drivers/aes", - "${k32w0_sdk_root}/drivers/jn_flash", - "${k32w0_sdk_root}/drivers/sha", - "${k32w0_sdk_root}/drivers/flexcomm", - "${k32w0_sdk_root}/drivers/lpc_dma", - "${k32w0_sdk_root}/drivers/pint", - "${k32w0_sdk_root}/drivers/inputmux", - "${k32w0_sdk_root}/drivers/spifi", - "${k32w0_sdk_root}/drivers/jn_rtc", - "${k32w0_sdk_root}/drivers/fmeas", - "${k32w0_sdk_root}/drivers/jn_rng", - "${k32w0_sdk_root}/drivers/ctimer", - "${k32w0_sdk_root}/drivers/wwdt", - "${k32w0_sdk_root}/drivers/gint", + "${k32w0_sdk_root}/utilities/debug_console/str", "${k32w0_sdk_root}/utilities/debug_console", "${k32w0_sdk_root}/devices/${device}/utilities", @@ -267,6 +270,32 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/rtos/amazon-freertos/lib/include/private", "${k32w0_sdk_root}/rtos/amazon-freertos/lib/third_party/unity/src", ] + if (chip_with_sdk_package == 1) { + _sdk_include_dirs += [ "${k32w0_sdk_root}/devices/${device}/drivers" ] + } else { + _sdk_include_dirs += [ + "${k32w0_sdk_root}/devices/K32W061/drivers", + "${k32w0_sdk_root}/drivers/common", + "${k32w0_sdk_root}/drivers/lpc_gpio", + "${k32w0_sdk_root}/drivers/aes", + "${k32w0_sdk_root}/drivers/jn_iocon", + "${k32w0_sdk_root}/drivers/lpc_adc", + "${k32w0_sdk_root}/drivers/aes", + "${k32w0_sdk_root}/drivers/jn_flash", + "${k32w0_sdk_root}/drivers/sha", + "${k32w0_sdk_root}/drivers/flexcomm", + "${k32w0_sdk_root}/drivers/lpc_dma", + "${k32w0_sdk_root}/drivers/pint", + "${k32w0_sdk_root}/drivers/inputmux", + "${k32w0_sdk_root}/drivers/spifi", + "${k32w0_sdk_root}/drivers/jn_rtc", + "${k32w0_sdk_root}/drivers/fmeas", + "${k32w0_sdk_root}/drivers/jn_rng", + "${k32w0_sdk_root}/drivers/ctimer", + "${k32w0_sdk_root}/drivers/wwdt", + "${k32w0_sdk_root}/drivers/gint", + ] + } libs = [ "${k32w0_sdk_root}/middleware/wireless/ble_controller/lib/lib_ble_controller_peripheral_commissioning.a", @@ -317,6 +346,7 @@ template("k32w0_sdk") { "gAppUseBonding_d=0", "gAppUsePairing_d=0", "gAppUsePrivacy_d=0", + "gController_ReducedRxThoughput=1", "gPasskeyValue_c=999999", "gSupportBle=1", "SUPPORT_FOR_BLE=1", @@ -324,6 +354,9 @@ template("k32w0_sdk") { "gConnPhyUpdateReqTxPhySettings_c=(gLePhy2MFlag_c)", "gConnPhyUpdateReqRxPhySettings_c=(gLePhy2MFlag_c)", "gConnPhyUpdateReqPhyOptions_c=(gLeCodingNoPreference_c)", + "BLE_HIGH_TX_POWER=0", + "gAdvertisingPowerLeveldBm_c=0", + "gConnectPowerLeveldBm_c=0", "DUAL_MODE_APP=1", "gMainThreadStackSize_c=5096", "HEAP_SIZE=gTotalHeapSize_c", @@ -332,6 +365,15 @@ template("k32w0_sdk") { "gSecLibUseSha256Alt_d=1", "gOTA_UseSecLibAes=1", "gResetSystemReset_d=1", + + # TODO: move these platform specific defines to args.gni + "NXP_OT_IDLE_INTERVAL=${chip_ot_idle_interval_ms}", + "NXP_OT_ACTIVE_INTERVAL=${chip_ot_active_interval_ms}", + "NXP_ICD_ENABLED=1", + "NXP_ACTIVE_MODE_THRESHOLD=${nxp_active_mode_threshold_ms}", + "NXP_ACTIVE_MODE_INTERVAL=${nxp_active_mode_interval_ms}", + "NXP_IDLE_MODE_INTERVAL=${nxp_idle_mode_interval_s}", + "NXP_ICD_SUPPORTED_CLIENTS_PER_FABRIC=${nxp_icd_supported_clients_per_fabric}", ] # If OTA default processors are enabled, then OTA custom entry structure @@ -372,6 +414,15 @@ template("k32w0_sdk") { defines += [ "PDM_ENCRYPTION=0" ] } + if (chip_with_ota_encryption == 1) { + defines += [ + "OTA_ENCRYPTION_ENABLE=1", + "OTA_ENCRYPTION_KEY=\"${chip_with_ota_key}\"", + ] + } else { + defines += [ "OTA_ENCRYPTION_ENABLE=0" ] + } + if (chip_mdns == "platform") { defines += [ "OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1", @@ -539,27 +590,6 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/components/uart/usart_adapter.c", "${k32w0_sdk_root}/devices/${device}/mcuxpresso/startup_${device_lowercase}.c", "${k32w0_sdk_root}/devices/${device}/system_${device}.c", - "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_clock.c", - "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_power.c", - "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_reset.c", - "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_wtimer.c", - "${k32w0_sdk_root}/drivers/aes/fsl_aes.c", - "${k32w0_sdk_root}/drivers/common/fsl_common.c", - "${k32w0_sdk_root}/drivers/ctimer/fsl_ctimer.c", - "${k32w0_sdk_root}/drivers/flexcomm/fsl_flexcomm.c", - "${k32w0_sdk_root}/drivers/flexcomm/fsl_i2c.c", - "${k32w0_sdk_root}/drivers/flexcomm/fsl_i2c_freertos.c", - "${k32w0_sdk_root}/drivers/flexcomm/fsl_usart.c", - "${k32w0_sdk_root}/drivers/fmeas/fsl_fmeas.c", - "${k32w0_sdk_root}/drivers/inputmux/fsl_inputmux.c", - "${k32w0_sdk_root}/drivers/jn_flash/fsl_flash.c", - "${k32w0_sdk_root}/drivers/jn_rng/fsl_rng.c", - "${k32w0_sdk_root}/drivers/jn_rtc/fsl_rtc.c", - "${k32w0_sdk_root}/drivers/lpc_adc/fsl_adc.c", - "${k32w0_sdk_root}/drivers/lpc_gpio/fsl_gpio.c", - "${k32w0_sdk_root}/drivers/pint/fsl_pint.c", - "${k32w0_sdk_root}/drivers/sha/fsl_sha.c", - "${k32w0_sdk_root}/drivers/spifi/fsl_spifi.c", "${k32w0_sdk_root}/middleware/wireless/ble_controller/config/controller_config.c", "${k32w0_sdk_root}/middleware/wireless/bluetooth/application/common/ble_conn_manager.c", "${k32w0_sdk_root}/middleware/wireless/bluetooth/application/common/ble_host_tasks.c", @@ -599,9 +629,60 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/rtos/amazon-freertos/lib/FreeRTOS/queue.c", "${k32w0_sdk_root}/rtos/amazon-freertos/lib/FreeRTOS/tasks.c", "${k32w0_sdk_root}/rtos/amazon-freertos/lib/FreeRTOS/timers.c", - "${k32w0_sdk_root}/utilities/debug_console/fsl_debug_console.c", - "${k32w0_sdk_root}/utilities/debug_console/str/fsl_str.c", ] + if (chip_with_sdk_package == 1) { + sources += [ + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_adc.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_aes.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_clock.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_common.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_ctimer.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_flash.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_flexcomm.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_fmeas.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_gpio.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_i2c.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_i2c_freertos.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_inputmux.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_pint.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_power.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_reset.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_rng.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_rtc.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_sha.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_spifi.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_usart.c", + "${k32w0_sdk_root}/devices/${device}/drivers/fsl_wtimer.c", + "${k32w0_sdk_root}/devices/${device}/utilities/debug_console/fsl_debug_console.c", + "${k32w0_sdk_root}/devices/${device}/utilities/str/fsl_str.c", + ] + } else { + sources += [ + "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_clock.c", + "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_power.c", + "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_reset.c", + "${k32w0_sdk_root}/devices/K32W061/drivers/fsl_wtimer.c", + "${k32w0_sdk_root}/drivers/aes/fsl_aes.c", + "${k32w0_sdk_root}/drivers/common/fsl_common.c", + "${k32w0_sdk_root}/drivers/ctimer/fsl_ctimer.c", + "${k32w0_sdk_root}/drivers/flexcomm/fsl_flexcomm.c", + "${k32w0_sdk_root}/drivers/flexcomm/fsl_i2c.c", + "${k32w0_sdk_root}/drivers/flexcomm/fsl_i2c_freertos.c", + "${k32w0_sdk_root}/drivers/flexcomm/fsl_usart.c", + "${k32w0_sdk_root}/drivers/fmeas/fsl_fmeas.c", + "${k32w0_sdk_root}/drivers/inputmux/fsl_inputmux.c", + "${k32w0_sdk_root}/drivers/jn_flash/fsl_flash.c", + "${k32w0_sdk_root}/drivers/jn_rng/fsl_rng.c", + "${k32w0_sdk_root}/drivers/jn_rtc/fsl_rtc.c", + "${k32w0_sdk_root}/drivers/lpc_adc/fsl_adc.c", + "${k32w0_sdk_root}/drivers/lpc_gpio/fsl_gpio.c", + "${k32w0_sdk_root}/drivers/pint/fsl_pint.c", + "${k32w0_sdk_root}/drivers/sha/fsl_sha.c", + "${k32w0_sdk_root}/drivers/spifi/fsl_spifi.c", + "${k32w0_sdk_root}/utilities/debug_console/fsl_debug_console.c", + "${k32w0_sdk_root}/utilities/debug_console/str/fsl_str.c", + ] + } if (chip_with_DK6) { sources += [ diff --git a/third_party/nxp/k32w0_sdk/repo/manifest/west.yml b/third_party/nxp/k32w0_sdk/repo/manifest/west.yml index 3a00791908f057..0febc2e377bd1e 100644 --- a/third_party/nxp/k32w0_sdk/repo/manifest/west.yml +++ b/third_party/nxp/k32w0_sdk/repo/manifest/west.yml @@ -11,7 +11,7 @@ manifest: remote: nxpmicro projects: - name: mcux-sdk - revision: b1ce670e8433142a3c78dca7f4597326eb5f5342 + revision: 7219c54f5dbd5a8636242f9f1771ddb70be146c9 path: core - name: amazon-freertos url: https://github.com/NXP/amazon-freertos.git @@ -23,7 +23,7 @@ manifest: revision: 15458495823165de372f62c3dad621a8da6c86e3 - name: framework url: https://github.com/NXP/mcux-sdk-middleware-connectivity-framework.git - revision: a45ed24bc5f8d54312c4d9243c4423d226084412 + revision: 2ccc3df70ee5b73c451fceee13e6172518412518 path: core/middleware/wireless/framework - name: ble_controller url: https://github.com/NXP/mcux-sdk-middleware-bluetooth-controller.git @@ -35,9 +35,9 @@ manifest: path: core/middleware/wireless/bluetooth - name: ieee-802.15.4 url: https://github.com/NXP/mcux-sdk-middleware-ieee_802.15.4.git - revision: 7cca871d58e53c78a703a39b269c4366b03f26e4 + revision: e8c96197346a7e37ce3a872096a22bf4aee47e3d path: core/middleware/wireless/ieee-802.15.4 - name: examples url: https://github.com/nxp-mcuxpresso/mcux-sdk-examples.git - revision: 39eed09d6a8485dcf365a24cd6ef957e7cc6fbf8 + revision: 6735550f5a41dfb997d4dd3ce6eebfe1f1d41054 path: core/boards diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index 57422f917ad1ab..eb23212ca1e329 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit 57422f917ad1abc65919435558cd836c8f7bada7 +Subproject commit eb23212ca1e329a29c95c29c09438c8f2256d5c6