diff --git a/config/esp32/components/chip/CMakeLists.txt b/config/esp32/components/chip/CMakeLists.txt index 672cf849af1244..e18679198fa33a 100644 --- a/config/esp32/components/chip/CMakeLists.txt +++ b/config/esp32/components/chip/CMakeLists.txt @@ -30,6 +30,7 @@ if(NOT CHIP_ROOT) endif() include(${CMAKE_CURRENT_LIST_DIR}/ota-image.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/factory.cmake) set(CHIP_REQUIRE_COMPONENTS esp_eth freertos lwip bt mbedtls fatfs app_update console openthread nvs_flash spi_flash) @@ -547,6 +548,11 @@ if(CONFIG_ENABLE_PW_RPC) endforeach() endif() +if(CONFIG_ENABLE_BUILD_TIME_PARTITION_SCRIPT) + generate_build_time_partition(fctry esp_secure_cert ${CHIP_ROOT} FLASH_IN_PROJECT) + add_dependencies(build_time_partition app) +endif() + # Build Matter OTA image if (CONFIG_CHIP_OTA_IMAGE_BUILD) chip_ota_image(chip-ota-image diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index 1c0b5afe2bcb61..d3f782502db9a9 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -951,7 +951,7 @@ menu "CHIP Device Layer" NVS namespace. If this option is enabled, the application can use an API to set a CD, the configured CD will be used for subsequent CD reads. - config ENABLE_ESP_INSIGHTS_TRACE + config ENABLE_ESP_INSIGHTS_TRACE bool "Enable Matter ESP Insights" depends on ESP_INSIGHTS_ENABLED default y @@ -960,14 +960,14 @@ menu "CHIP Device Layer" Enabling the above option will enable the esp32 specific tracing functionality and report the diagnostic information to the insights cloud. - config ENABLE_ESP_INSIGHTS_SYSTEM_STATS + config ENABLE_ESP_INSIGHTS_SYSTEM_STATS bool "Enable System Stats for insights" depends on ESP_INSIGHTS_ENABLED default n help This option enables the system statistics to be sent to the insights cloud. - config MAX_PERMIT_LIST_SIZE + config MAX_PERMIT_LIST_SIZE int "Set permit list size for Insights traces" range 5 30 depends on ESP_INSIGHTS_ENABLED @@ -975,6 +975,13 @@ menu "CHIP Device Layer" help Maximum number of group entries that can be included in the permit list for reporting the traces to insights. + config ENABLE_BUILD_TIME_PARTITION_SCRIPT + bool "Enable script to build fctry and esp_secure_cert partition in during build time." + default n + help + Enables the cmake script to generate and map the factory parition bin and esp-secure-cert + partition bin to their corresponding addresses specified in partitions.csv and flash the partitons + in a single idf.py flash command. endmenu diff --git a/config/esp32/components/chip/factory.cmake b/config/esp32/components/chip/factory.cmake new file mode 100644 index 00000000000000..59079db6386e30 --- /dev/null +++ b/config/esp32/components/chip/factory.cmake @@ -0,0 +1,183 @@ +# +# 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. +# + +function(set_default_value VAR DEFAULT_VALUE) + get_property(VAR_CACHE_TYPE CACHE ${VAR} PROPERTY TYPE) + message(status "var cache type : ${VAR} ${VAR_CACHE_TYPE}") + if (VAR_CACHE_TYPE STREQUAL "UNINITIALIZED") + set(${VAR}_EXPLICITLY_SET TRUE CACHE BOOL "${VAR} is not explicitly set") + set(${VAR} ${DEFAULT_VALUE} CACHE STRING ${VAR}) + else() + set(${VAR}_EXPLICITLY_SET FALSE CACHE BOOL "${VAR} is explicitly set.") + set(${VAR} ${DEFAULT_VALUE} CACHE STRING ${VAR}) + endif() +endfunction() + +function(set_values) + # Set variables with default values if not defined + set_default_value(DEVICE_NAME "My bulb") + set_default_value(VENDOR_NAME "Test-vendor") + set_default_value(DISCRIMINATOR 3841) + set_default_value(PASSCODE 20202020) + set_default_value(VENDOR_ID 0xFFF2) + set_default_value(PRODUCT_ID 0x8001) + set_default_value(HARDWARE_VERSION 1) + set_default_value(DISCOVERY_MODE 2) + set_default_value(HARDWARE_VERSION_STR "Devkit") + set_default_value(DAC_CERT "${CHIP_ROOT}/credentials/test/attestation/Chip-Test-DAC-FFF2-8001-0008-Cert.der") + set_default_value(DAC_KEY "${CHIP_ROOT}/credentials/test/attestation/Chip-Test-DAC-FFF2-8001-0008-Key.der") + set_default_value(PAI_CERT "${CHIP_ROOT}/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.der") + set_default_value(CERT_DCLRN "${CHIP_ROOT}/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der") +endfunction() + +function(generate_build_time_partition fctry_partition esp_secure_cert_partition chip_root) + set(options FLASH_IN_PROJECT) + set(multi DEPENDS) + cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") + get_filename_component(chip_root_abs_path ${chip_root} ABSOLUTE) + + set(generate_esp32_chip_factory_bin.py ${PYTHON} ${chip_root}/scripts/tools/generate_esp32_chip_factory_bin.py) + set(gen_att_certs.py ${PYTHON} ${chip_root}/scripts/tools/gen_att_certs.py) + + partition_table_get_partition_info(fctry_partition_size "--partition-name ${fctry_partition}" "size") + partition_table_get_partition_info(fctry_partition_offset "--partition-name ${fctry_partition}" "offset") + + partition_table_get_partition_info(secure_cert_partition_size "--partition-name ${esp_secure_cert_partition}" "size") + partition_table_get_partition_info(secure_cert_partition_offset "--partition-name ${esp_secure_cert_partition}" "offset") + + message(STATUS "fctry_partition_size : ${fctry_partition_size}") + message(STATUS "fctry_partition_offset : ${fctry_partition_offset}") + message(STATUS "secure_cert_partition_size : ${secure_cert_partition_size}") + message(STATUS "secure_cert_partition_offset : ${secure_cert_partition_offset}") + + if("${fctry_partition_size}" AND "${fctry_partition_offset}") + set_values() + message(STATUS "Vendor id set: ${VENDOR_ID_EXPLICITLY_SET}") + message(STATUS "Product id set: ${PRODUCT_ID_EXPLICITLY_SET}") + + if ("${VENDOR_ID_EXPLICITLY_SET}" AND "${PRODUCT_ID_EXPLICITLY_SET}") + string(RANDOM LENGTH 8 ALPHABET 0123456789 OUTPUT_VARIABLE RANDOM_PASSCODE) + set(PASSCODE ${RANDOM_PASSCODE}) + message(STATUS "random passcode : ${RANDOM_PASSCODE}") + + math(EXPR PASSCODE_MOD "${RANDOM_PASSCODE} % 999999998") + message(STATUS "Random passcode Mod: ${PASSCODE_MOD}") + set(PASSCODE ${PASSCODE_MOD}) + + string(RANDOM LENGTH 4 ALPHABET 0123456789 OUTPUT_VARIABLE RANDOM_DISCRIMINATOR) + set(DISCRIMINATOR ${RANDOM_DISCRIMINATOR}) + message(STATUS "random discriminator : ${RANDOM_DISCRIMINATOR}") + + math(EXPR DISCRIMINATOR_MOD "${RANDOM_DISCRIMINATOR} % 4096") + message(STATUS "Random discriminator Mod: ${DISCRIMINATOR_MOD}") + set(DISCRIMINATOR ${DISCRIMINATOR_MOD}) + + math(EXPR VENDOR_DEC ${VENDOR_ID} OUTPUT_FORMAT DECIMAL) + math(EXPR PRODUCT_DEC ${PRODUCT_ID} OUTPUT_FORMAT DECIMAL) + + message(STATUS "Vendor Decimal: ${VENDOR_DEC}") + message(STATUS "Product Decimal: ${PRODUCT_DEC}") + + set(OUTDIR attestation_${VENDOR_DEC}_${PRODUCT_DEC}) + message(STATUS "Outdir: ${OUTDIR}") + + execute_process(COMMAND ${gen_att_certs.py} --vendor-id ${VENDOR_ID} --product-id ${PRODUCT_ID} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + + set(DAC_CERT ${CMAKE_BINARY_DIR}/certs/${OUTDIR}/DAC_cert.der) + set(DAC_KEY ${CMAKE_BINARY_DIR}/certs/${OUTDIR}/DAC_key.der) + set(PAI_CERT ${CMAKE_BINARY_DIR}/certs/${OUTDIR}/PAI_cert.der) + set(CERT_DCLRN ${CMAKE_BINARY_DIR}/certs/${OUTDIR}/CD.der) + endif() + + set(PREVIOUS_VALUES_FILE "${CMAKE_BINARY_DIR}/previous_values.txt") + + if (NOT EXISTS ${PREVIOUS_VALUES_FILE}) + file(WRITE ${PREVIOUS_VALUES_FILE} "") + endif() + + file(READ ${PREVIOUS_VALUES_FILE} PREVIOUS_VALUES) + + set(CURRENT_VALUES_STRING + "${DEVICE_NAME}${VENDOR_NAME}${DISCRIMINATOR}${PASSCODE}${VENDOR_ID}${PRODUCT_ID}${HARDWARE_VERSION}${HARDWARE_VERSION_STR}${DAC_CERT}${DAC_KEY}${PAI_CERT}${CERT_DCLRN}") + + message(STATUS "Vendor id set: ${VENDOR_ID_EXPLICITLY_SET}") + message(STATUS "Bulb Name: ${DEVICE_NAME}") + message(STATUS "Vendor Name: ${VENDOR_NAME}") + message(STATUS "Hardware Version: ${HARDWARE_VERSION}") + message(STATUS "Hardware Version String: ${HARDWARE_VERSION_STR}") + message(STATUS "Vendor ID: ${VENDOR_ID}") + message(STATUS "Product ID: ${PRODUCT_ID}") + message(STATUS "Discovery Mode : ${DISCOVERY_MODE}") + message(STATUS "DAC Cert: ${DAC_CERT}") + message(STATUS "DAC Key: ${DAC_KEY}") + message(STATUS "PAI Cert: ${PAI_CERT}") + message(STATUS "Certification Declaration: ${CERT_DCLRN}") + message(STATUS "Passcode: ${PASSCODE}") + message(STATUS "Discriminator: ${DISCRIMINATOR}") + + + if (NOT "${CURRENT_VALUES_STRING}" STREQUAL "${PREVIOUS_VALUES}") + message(STATUS "Values have changed. Triggering add_custom_target.") + add_custom_target(build_time_partition ALL + COMMAND ${generate_esp32_chip_factory_bin.py} -d ${DISCRIMINATOR} + -p ${PASSCODE} + --product-name "${DEVICE_NAME}" + --vendor-name "${VENDOR_NAME}" + --vendor-id ${VENDOR_ID} + --product-id ${PRODUCT_ID} + --hw-ver ${HARDWARE_VERSION} + --hw-ver-str "${HARDWARE_VERSION_STR}" + --dac-cert ${DAC_CERT} + --dac-key ${DAC_KEY} + --pai-cert ${PAI_CERT} + --cd ${CERT_DCLRN} + --discovery-mode ${DISCOVERY_MODE} + --dac-in-secure-cert + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + file(WRITE ${PREVIOUS_VALUES_FILE} ${CURRENT_VALUES_STRING}) + else() + add_custom_target(build_time_partition) + message(STATUS "Values have not changed. Skipping add_custom_target.") + endif() + + set(factory_partition_bin ${CMAKE_BINARY_DIR}/bin/factory_partition.bin) + set(esp_secure_cert_partition_bin ${CMAKE_BINARY_DIR}/bin/esp_secure_cert_partititon.bin) + idf_component_get_property(main_args esptool_py FLASH_ARGS) + idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) + + esptool_py_flash_target(${fctry_partition}-flash "${main_args}" "${sub_args}" ALWAYS_PLAINTEXT) + esptool_py_flash_to_partition(${fctry_partition}-flash "${fctry_partition}" "${factory_partition_bin}") + + esptool_py_flash_target(${esp_secure_cert_partition}-flash "${main_args}" "${sub_args}" ALWAYS_PLAINTEXT) + esptool_py_flash_to_partition(${esp_secure_cert_partition}-flash "${esp_secure_cert_partition}" "${esp_secure_cert_partition_bin}") + + add_dependencies(${fctry_partition}-flash build_time_partition) + add_dependencies(${esp_secure_cert_partition}-flash build_time_partition) + + if(arg_FLASH_IN_PROJECT) + esptool_py_flash_to_partition(flash "${fctry_partition}" "${factory_partition_bin}") + esptool_py_flash_to_partition(flash "${esp_secure_cert_partition}" "${esp_secure_cert_partition_bin}") + add_dependencies(flash build_time_partition) + endif() + else() + set(message "Failed to create Factory partition image for partition '${partition}'. " + "Check project configuration if using the correct partition table file.") + fail_at_build_time(factory_${partition}_bin "${message}") + + endif() +endfunction() diff --git a/scripts/setup/requirements.esp32.txt b/scripts/setup/requirements.esp32.txt index c9043a3f0418c1..ed336bb29d67c4 100644 --- a/scripts/setup/requirements.esp32.txt +++ b/scripts/setup/requirements.esp32.txt @@ -10,6 +10,8 @@ bitarray==2.6.0 bitstring>=3.1.6,<4 ecdsa>=0.16.0 construct==2.10.54 +pypng==0.0.21 +PyQRCode==1.2.1 python-socketio<5 itsdangerous<2.1 ; python_version < "3.11" esp_idf_monitor==1.1.1 diff --git a/scripts/tools/gen_att_certs.py b/scripts/tools/gen_att_certs.py new file mode 100755 index 00000000000000..930f6a23d54ed0 --- /dev/null +++ b/scripts/tools/gen_att_certs.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022 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. +# + +import argparse +import base64 +import json +import logging as log +import os +import subprocess +import sys +from collections import namedtuple +from os.path import exists + +CHIP_ROOT = os.path.dirname(os.path.realpath(__file__))[:-len(os.path.join('scripts', 'tools'))] +chip_cert_exe = os.path.join(CHIP_ROOT, 'out', 'host', 'chip-cert') + +if not os.path.exists(chip_cert_exe): + print("Error: chip-cert executable not found.Please build chip-cert in connectedhomeip by ninja -C out/host") + + +def gen_test_certs(vendor_id: int, + product_id: int, + output: str): + + CD_PATH = CHIP_ROOT + "/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem" + CD_KEY_PATH = CHIP_ROOT + "/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem" + PAA_PATH = CHIP_ROOT + "/credentials/test/attestation/Chip-Test-PAA-NoVID-Cert.pem" + PAA_KEY_PATH = CHIP_ROOT + "/credentials/test/attestation/Chip-Test-PAA-NoVID-Key.pem" + + attestation_certs = namedtuple("attestation_certs", ["dac_cert", "dac_key", "pai_cert"]) + + log.info("Generating new Certification Declaration using chip-cert...") + + # generate Certification Declaration + cmd = [chip_cert_exe, "gen-cd", + "--key", CD_KEY_PATH, + "--cert", CD_PATH, + "--out", output + "_" + str(vendor_id) + "_" + str(product_id) + "/CD.der", + "--format-version", "1", + "--vendor-id", hex(vendor_id), + "--product-id", hex(product_id), + "--device-type-id", "0", + "--certificate-id", "FFFFFFFFFFFFFFFFFFF", + "--security-level", "0", + "--security-info", "0", + "--certification-type", "0", + "--version-number", "0xFFFF", + ] + subprocess.run(cmd) + + new_certificates = {"PAI_CERT": output + "_" + str(vendor_id) + "_" + str(product_id) + "/PAI_cert", + "PAI_KEY": output + "_" + str(vendor_id) + "_" + str(product_id)+"/PAI_key", + "DAC_CERT": output + "_" + str(vendor_id) + "_" + str(product_id) + "/DAC_cert", + "DAC_KEY": output + "_" + str(vendor_id) + "_" + str(product_id) + "/DAC_key" + } + + log.info("Generating new PAI and DAC certificates using chip-cert...") + + # generate PAI + cmd = [chip_cert_exe, "gen-att-cert", + "-t", "i", + "-c", "device", + "-V", hex(vendor_id), + "-C", PAA_PATH, + "-K", PAA_KEY_PATH, + "-o", new_certificates["PAI_CERT"] + ".pem", + "-O", new_certificates["PAI_KEY"] + ".pem", + "-l", str(10000), + ] + subprocess.run(cmd) + + # generate DAC + cmd = [chip_cert_exe, "gen-att-cert", + "-t", "d", + "-c", "device", + "-V", hex(vendor_id), + "-P", hex(product_id), + "-C", new_certificates["PAI_CERT"] + ".pem", + "-K", new_certificates["PAI_KEY"] + ".pem", + "-o", new_certificates["DAC_CERT"] + ".pem", + "-O", new_certificates["DAC_KEY"] + ".pem", + "-l", str(10000), + ] + subprocess.run(cmd) + + # convert to .der files + for cert_k, cert_v in new_certificates.items(): + action_type = "convert-cert" if cert_k.find("CERT") != -1 else "convert-key" + log.info(cert_v + ".der") + cmd = [chip_cert_exe, action_type, + cert_v + ".pem", + cert_v + ".der", + "--x509-der", + ] + subprocess.run(cmd) + + return attestation_certs(new_certificates["DAC_CERT"] + ".der", + new_certificates["DAC_KEY"] + ".der", + new_certificates["PAI_CERT"] + ".der") + + +def get_args(): + def any_base_int(s): return int(s, 0) + parser = argparse.ArgumentParser(description="ESP32 Attestation generation tool") + parser.add_argument("-o", "--output", type=str, required=False, + help="Output path to store attestation certificates", default="certs/attestation") + parser.add_argument('--vendor-id', type=any_base_int, help="Vendor id") + parser.add_argument('--product-id', type=any_base_int, help="Product id") + return parser.parse_args() + + +def set_up_out_dirs(args): + os.makedirs(args.output + "_" + str(args.vendor_id) + "_" + str(args.product_id), exist_ok=True) + + +def main(): + args = get_args() + set_up_out_dirs(args) + certs = gen_test_certs(args.vendor_id, args.product_id, args.output) + + +if __name__ == "__main__": + main()