Merge branch 'task/mfg-tool' into 'main'

Manufacturing partition generator tool

See merge request app-frameworks/esp-matter!96
This commit is contained in:
Hrishikesh Dhayagude
2022-05-09 13:36:11 +08:00
16 changed files with 920 additions and 580 deletions
+2
View File
@@ -6,3 +6,5 @@ sdkconfig
sdkconfig.old
dependencies.lock
managed_components/
__pycache__/
out/
+1 -1
View File
@@ -72,7 +72,7 @@ set(INCLUDE_DIRS_LIST "."
"${MATTER_SDK_PATH}/src"
"${ZAP_GENERATED_PATH}/../")
set(REQUIRES_LIST chip bt esp_matter_console esp_matter_factory)
set(REQUIRES_LIST chip bt esp_matter_console)
if ("${IDF_TARGET}" STREQUAL "esp32h2")
list(APPEND REQUIRES_LIST openthread esp_matter_openthread)
-12
View File
@@ -1,12 +0,0 @@
menu "ESP Matter"
config ESP_MATTER_USE_ESP_DAC_PROVIDER
bool "Use ESP Matter DAC Provider"
default n
help
Use the ESP Matter DAC provider instead of the example DAC provider.
ESP Matter DAC provider reads the attestation certificates and keys
from the factory partition. Before enabling please flash the factory
partition with the attestation certificates and keys.
endmenu
+21 -20
View File
@@ -15,7 +15,6 @@
#include <esp_log.h>
#include <esp_matter.h>
#include <esp_matter_core.h>
#include <esp_matter_factory.h>
#include <nvs.h>
#include <app/clusters/network-commissioning/network-commissioning.h>
@@ -23,28 +22,20 @@
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/ESP32/ESP32FactoryDataProvider.h>
#include <platform/ESP32/NetworkCommissioningDriver.h>
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
#include <esp_matter_openthread.h>
#endif
/* TODO: Remove the examples DAC provider once we have a concrete
* way to generate attestation credentials.
*/
#if CONFIG_ESP_MATTER_USE_ESP_DAC_PROVIDER
#include <esp_matter_dac.h>
using chip::Credentials::esp::esp_matter_dac_provider_get;
#else
#include <credentials/examples/DeviceAttestationCredsExample.h>
using chip::Credentials::Examples::GetExampleDACProvider;
#endif
using chip::CommandId;
using chip::DataVersion;
using chip::kInvalidCommandId;
using chip::kInvalidEndpointId;
using chip::Credentials::SetDeviceAttestationCredentialsProvider;
using chip::Credentials::Examples::GetExampleDACProvider;
using chip::DeviceLayer::ChipDeviceEvent;
using chip::DeviceLayer::ConfigurationMgr;
using chip::DeviceLayer::ConnectivityManager;
@@ -59,6 +50,13 @@ using chip::DeviceLayer::ThreadStackMgr;
static const char *TAG = "esp_matter_core";
namespace esp_matter {
namespace {
#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
chip::DeviceLayer::ESP32FactoryDataProvider factory_data_provider;
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
} // namespace
typedef struct _attribute {
int attribute_id;
int cluster_id;
@@ -644,11 +642,15 @@ static void esp_matter_chip_init_task(intptr_t context)
// initParams.appDelegate = &sCallbacks;
chip::Server::GetInstance().Init(initParams);
#if CONFIG_ESP_MATTER_USE_ESP_DAC_PROVIDER
SetDeviceAttestationCredentialsProvider(esp_matter_dac_provider_get());
#else
/* TODO: Remove the examples DAC provider once we have a concrete
* way to generate attestation credentials.
*/
#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
SetDeviceAttestationCredentialsProvider(&factory_data_provider);
#else // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
SetDeviceAttestationCredentialsProvider(GetExampleDACProvider());
#endif
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
// If Thread is Provisioned, publish the dns service
if (chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned() &&
@@ -673,6 +675,10 @@ static void esp_matter_chip_init_task(intptr_t context)
static esp_err_t chip_init(event_callback_t callback)
{
#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
SetCommissionableDataProvider(&factory_data_provider);
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
if (chip::Platform::MemoryInit() != CHIP_NO_ERROR) {
ESP_LOGE(TAG, "Failed to initialize CHIP memory pool");
return ESP_ERR_NO_MEM;
@@ -711,11 +717,6 @@ esp_err_t start(event_callback_t callback)
esp_err_t err = chip_init(callback);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing matter");
return err;
}
err = esp_matter_factory_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing factory");
}
return err;
}
-111
View File
@@ -1,111 +0,0 @@
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <esp_matter_dac.h>
#include <esp_matter_factory.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPError.h>
#include <lib/support/Span.h>
namespace chip {
namespace Credentials {
namespace esp {
namespace {
static constexpr uint32_t kDACPrivateKeySize = 32;
static constexpr uint32_t kDACPublicKeySize = 65;
// TODO: This should be moved to a method of P256Keypair
CHIP_ERROR LoadKeypairFromRaw(ByteSpan privateKey, ByteSpan publicKey, Crypto::P256Keypair & keypair)
{
Crypto::P256SerializedKeypair serializedKeypair;
ReturnErrorOnFailure(serializedKeypair.SetLength(privateKey.size() + publicKey.size()));
memcpy(serializedKeypair.Bytes(), publicKey.data(), publicKey.size());
memcpy(serializedKeypair.Bytes() + publicKey.size(), privateKey.data(), privateKey.size());
return keypair.Deserialize(serializedKeypair);
}
class ESPMatterDAC : public DeviceAttestationCredentialsProvider
{
public:
CHIP_ERROR GetCertificationDeclaration(MutableByteSpan & outBuffer) override
{
size_t certSize = outBuffer.size();
VerifyOrReturnError(esp_matter_factory_get_cert_declrn(outBuffer.data(), &certSize) == ESP_OK, CHIP_ERROR_READ_FAILED);
outBuffer.reduce_size(certSize);
return CHIP_NO_ERROR;
}
CHIP_ERROR GetFirmwareInformation(MutableByteSpan & out_firmware_info_buffer) override
{
// TODO: We need a real example FirmwareInformation to be populated.
out_firmware_info_buffer.reduce_size(0);
return CHIP_NO_ERROR;
}
CHIP_ERROR GetDeviceAttestationCert(MutableByteSpan & outBuffer) override
{
size_t dacCertSz = outBuffer.size();
VerifyOrReturnError(esp_matter_factory_get_dac_cert(outBuffer.data(), &dacCertSz) == ESP_OK, CHIP_ERROR_READ_FAILED);
outBuffer.reduce_size(dacCertSz);
return CHIP_NO_ERROR;
}
CHIP_ERROR GetProductAttestationIntermediateCert(MutableByteSpan & outBuffer) override
{
size_t paiCertSz = outBuffer.size();
VerifyOrReturnError(esp_matter_factory_get_pai_cert(outBuffer.data(), &paiCertSz) == ESP_OK, CHIP_ERROR_READ_FAILED);
outBuffer.reduce_size(paiCertSz);
return CHIP_NO_ERROR;
}
CHIP_ERROR SignWithDeviceAttestationKey(const ByteSpan & digestToSign, MutableByteSpan & outSignBuffer) override
{
Crypto::P256ECDSASignature signature;
Crypto::P256Keypair keypair;
VerifyOrReturnError(IsSpanUsable(outSignBuffer), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSpanUsable(digestToSign), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(outSignBuffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
uint8_t privKeyBuf[kDACPrivateKeySize];
uint8_t pubKeyBuf[kDACPublicKeySize];
size_t privKeyLen = sizeof(privKeyBuf);
size_t pubKeyLen = sizeof(pubKeyBuf);
VerifyOrReturnError(esp_matter_factory_get_dac_private_key(privKeyBuf, &privKeyLen) == ESP_OK, CHIP_ERROR_READ_FAILED);
VerifyOrReturnError(esp_matter_factory_get_dac_public_key(pubKeyBuf, &pubKeyLen) == ESP_OK, CHIP_ERROR_READ_FAILED);
// In a non-exemplary implementation, the public key is not needed here. It is used here merely because
// Crypto::P256Keypair is only (currently) constructable from raw keys if both private/public keys are present.
ReturnErrorOnFailure(LoadKeypairFromRaw(ByteSpan(privKeyBuf, privKeyLen), ByteSpan(pubKeyBuf, pubKeyLen), keypair));
ReturnErrorOnFailure(keypair.ECDSA_sign_hash(digestToSign.data(), digestToSign.size(), signature));
return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, outSignBuffer);
}
};
} // namespace
DeviceAttestationCredentialsProvider * esp_matter_dac_provider_get(void)
{
static ESPMatterDAC dacProvider;
return &dacProvider;
}
} // namespace esp
} // namespace Credentials
} // namespace chip
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <credentials/DeviceAttestationCredsProvider.h>
namespace chip {
namespace Credentials {
namespace esp {
/**
* @brief Get implementation of a ESP DAC provider to validate device
* attestation procedure.
*
* @returns a singleton DeviceAttestationCredentialsProvider
*/
DeviceAttestationCredentialsProvider * esp_matter_dac_provider_get(void);
} // namespace esp
} // namespace Credentials
} // namespace chip
@@ -1,3 +0,0 @@
idf_component_register(SRCS "esp_matter_factory.c"
INCLUDE_DIRS .
REQUIRES chip nvs_flash esp32_mbedtls)
-13
View File
@@ -1,13 +0,0 @@
menu "ESP Matter Factory"
config ESP_MATTER_FACTORY_PARTITION
string "Matter Factory Partition Name"
default "fctry"
help
Partition name where the matter factory is located.
config ESP_MATTER_FACTORY_NAMESPACE
string "Matter Factory Namespace Name"
default "chip_creds"
endmenu
@@ -1,123 +0,0 @@
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_matter_factory.h>
static const char *TAG = "esp_matter_factory";
static const char *NVS_KEY_DAC_CERT = "dac_cert";
static const char *NVS_KEY_DAC_PUBLIC_KEY = "dac_pubkey";
static const char *NVS_KEY_DAC_PRIVATE_KEY = "dac_privkey";
static const char *NVS_KEY_PAI_CERT = "pai_cert";
static const char *NVS_KEY_CERT_DCLRN = "cert_dclrn";
static bool initialized = false;
static nvs_handle_t factory_nvs_handle;
esp_err_t esp_matter_factory_init(void)
{
if (initialized) {
ESP_LOGW(TAG, "Matter Factory already initialized");
return ESP_OK;
}
esp_err_t err = nvs_flash_init_partition(CONFIG_ESP_MATTER_FACTORY_PARTITION);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialized partition:%s err:0x%x", CONFIG_ESP_MATTER_FACTORY_PARTITION, err);
return err;
}
err = nvs_open_from_partition(CONFIG_ESP_MATTER_FACTORY_PARTITION, CONFIG_ESP_MATTER_FACTORY_NAMESPACE,
NVS_READONLY, &factory_nvs_handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open partition:%s namespace:%s mode:NVS_READONLY err:%x",
CONFIG_ESP_MATTER_FACTORY_PARTITION, CONFIG_ESP_MATTER_FACTORY_NAMESPACE, err);
nvs_flash_deinit_partition(CONFIG_ESP_MATTER_FACTORY_PARTITION);
return err;
}
initialized = true;
return ESP_OK;
}
void esp_matter_factory_deinit(void)
{
if (initialized) {
nvs_close(factory_nvs_handle);
nvs_flash_deinit_partition(CONFIG_ESP_MATTER_FACTORY_PARTITION);
initialized = false;
}
}
/*
* Read value from the factory partition.
* @param [in] key Key to read
* @param [out] buf Buffer to store the value
* @param [in, out] len Length of the buf, if read is successful, it will be updated to the actual length of the value
*
* @return ESP_OK on success, error code otherwise
* @return ESP_ERR_INVALID_ARG if key is NULL or len is NULL
* @return ESP_ERR_INVALID_STATE if factory is not initialized
*/
static esp_err_t matter_factory_read_value(const char *key, uint8_t *buf, size_t *len)
{
assert(key);
if (buf == NULL || len == NULL)
{
return ESP_ERR_INVALID_ARG;
}
if (initialized == false)
{
ESP_LOGE(TAG, "Matter Factory not initialized");
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = nvs_get_blob(factory_nvs_handle, key, buf, len);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to read value key:%s err:%x", key, err);
}
return err;
}
esp_err_t esp_matter_factory_get_dac_cert(uint8_t *buf, size_t *len)
{
return matter_factory_read_value(NVS_KEY_DAC_CERT, buf, len);
}
esp_err_t esp_matter_factory_get_pai_cert(uint8_t *buf, size_t *len)
{
return matter_factory_read_value(NVS_KEY_PAI_CERT, buf, len);
}
esp_err_t esp_matter_factory_get_cert_declrn(uint8_t *buf, size_t *len)
{
return matter_factory_read_value(NVS_KEY_CERT_DCLRN, buf, len);
}
esp_err_t esp_matter_factory_get_dac_public_key(uint8_t *buf, size_t *len)
{
return matter_factory_read_value(NVS_KEY_DAC_PUBLIC_KEY, buf, len);
}
esp_err_t esp_matter_factory_get_dac_private_key(uint8_t *buf, size_t *len)
{
return matter_factory_read_value(NVS_KEY_DAC_PRIVATE_KEY, buf, len);
}
@@ -1,92 +0,0 @@
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize and open the matter factory partition.
*
* @return ESP_OK on success or if already initialized,
* appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_init(void);
/**
* @brief Deinitialize and close the matter factory partition.
*/
void esp_matter_factory_deinit(void);
/**
* @brief Get the device attestation certificate (DAC) from matter factory partition.
*
* @param[out] out_buf Pointer to the buffer to store the certificate.
* @param[in, out] buf_len The length of the buffer, and the length of the
* certificate after the function returns.
*
* @return ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_get_dac_cert(uint8_t *out_buf, size_t *len);
/**
* @brief Get the Device Attestation Public Key from matter factory partition.
*
* @param[out] out_buf Pointer to the buffer to store the public key.
* @param[in, out] buf_len The length of the buffer, and the length of the
* public key after the function returns.
*
* @return ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_get_dac_public_key(uint8_t *out_buf, size_t *len);
/**
* @brief Get the Device Attestation Private Key from matter factory partition.
*
* @param[out] out_buf Pointer to the buffer to store the private key.
* @param[in, out] buf_len The length of the buffer, and the length of the
*
* @return ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_get_dac_private_key(uint8_t *out_buf, size_t *len);
/**
* @brief Get the Product Attestation Intermediate Certificate (PAI) from matter factory partition.
*
* @param[out] out_buf Pointer to the buffer to store the certificate.
* @param[in, out] buf_len The length of the buffer, and the length of the
* certificate after the function returns.
*
* @return ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_get_pai_cert(uint8_t *out_buf, size_t *len);
/**
* @brief Get the Certificate Declaration from matter factory partition.
*
* @param[out] out_buf Pointer to the buffer to store the certificate.
* @param[in, out] buf_len The length of the buffer, and the length of the
* certificate after the function returns.
*
* @return ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_matter_factory_get_cert_declrn(uint8_t *out_buf, size_t *len);
#ifdef __cplusplus
}
#endif
-1
View File
@@ -1 +0,0 @@
3081e806092a864886f70d010702a081da3081d7020103310d300b0609608648016503040201304506092a864886f70d010701a0380436152400012501f1ff360205008018250334122c04135a494732303134315a423333303030312d32342405002406002507942624080018317c307a020103801462fa823359acfaa9963e1cfa140addf504f37160300b0609608648016503040201300a06082a8648ce3d04030204463044022043a63f2b943df33c38b3e02fcaa75fe3532aebbf5e63f5bbdbc0b1f01d3c4f6002204c1abf5f1807b81894b1576c47e4724e4d966c612ed3fa25c118c3f2b3f90369
-171
View File
@@ -1,171 +0,0 @@
#!/bin/sh
# Script to generate the DACs and matter factory partition
# TODO: Lot of stuff here is hardcoded, and could be done better
# by using the arguments to the script or even better, switch to python
CHIP_CERT_TOOL="../../connectedhomeip/connectedhomeip/out/host/chip-cert gen-att-cert"
NVS_GENERATOR="$IDF_PATH/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py"
PAA_CERT_SUBJECT="ESP Matter PAA 01"
PAI_CERT_SUBJECT="ESP Matter PAI 01"
DAC_CERT_SUBJECT="ESP Matter DAC 01"
CERT_VALID_FROM="2022-01-01 14:23:43"
CERT_LIFETIME=4294967295
VENDOR_ID=FFF1
PRODUCT_ID=8000
OUT_DIR=out/$VENDOR_ID-$PRODUCT_ID
NAME_PREFIX="$OUT_DIR/ESP-Matter"
mkdir -p $OUT_DIR
# PAA_CERT_NAME="$NAME_PREFIX-PAA-Cert.pem"
PAA_CERT_NAME="../../connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAA-FFF1-Cert.pem"
PAI_CERT_NAME="$NAME_PREFIX-PAI-Cert.pem"
DAC_CERT_NAME="$NAME_PREFIX-DAC-Cert.pem"
PAI_CERT_NAME_DER="$NAME_PREFIX-PAI-Cert.der"
DAC_CERT_NAME_DER="$NAME_PREFIX-DAC-Cert.der"
# PAA_PRIVATE_KEY_NAME="$NAME_PREFIX-PAA-Private-Key.pem"
PAA_PRIVATE_KEY_NAME="../../connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAA-FFF1-Key.pem"
PAI_PRIVATE_KEY_NAME="$NAME_PREFIX-PAI-Private-Key.pem"
DAC_PRIVATE_KEY_NAME="$NAME_PREFIX-DAC-Private-Key.pem"
DAC_PRIVATE_KEY_BIN="$NAME_PREFIX-DAC-Private-Key.bin"
DAC_PUBLIC_KEY_BIN="$NAME_PREFIX-DAC-Public-Key.bin"
# @brief Convert certificates in PEM format to DER format
#
# @param $1 - PEM Certificate File Name
# @param $2 - DER Certificate File Name
convert_pem_to_der()
{
openssl x509 -inform pem -in $1 -out $2 -outform DER > /dev/null 2>&1
}
generate_paa()
{
$CHIP_CERT_TOOL --type a \
--subject-cn "$PAA_CERT_SUBJECT" \
--valid-from "$CERT_VALID_FROM" \
--lifetime "$CERT_LIFETIME" \
--out-key "$PAA_PRIVATE_KEY_NAME" \
--out "$PAA_CERT_NAME"
if [ $? -ne 0 ]; then
echo "Failed to generate PAA certificate"
exit 1
fi
echo "Generated PAA certificate"
}
generate_pai()
{
$CHIP_CERT_TOOL --type i \
--subject-cn "$PAI_CERT_SUBJECT" \
--subject-vid "$VENDOR_ID" \
--valid-from "$CERT_VALID_FROM" \
--lifetime "$CERT_LIFETIME" \
--ca-key "$PAA_PRIVATE_KEY_NAME" \
--ca-cert "$PAA_CERT_NAME" \
--out-key "$PAI_PRIVATE_KEY_NAME" \
--out "$PAI_CERT_NAME"
if [ $? -ne 0 ]; then
echo "Failed to generate PAA certificate"
exit 1
fi
convert_pem_to_der $PAI_CERT_NAME $PAI_CERT_NAME_DER
echo "Generated PAI certificate"
}
generate_keypair_bin()
{
openssl ec -in $DAC_PRIVATE_KEY_NAME | openssl asn1parse | awk -F ":" '/OCTET STRING/ { print $4 }' \
| xxd -r -p > $DAC_PRIVATE_KEY_BIN
openssl ec -inform PEM -in $DAC_PRIVATE_KEY_NAME -text | grep -A 5 'pub:' | sed "1 d" \
| tr -d ':\n ' | xxd -r -p > $DAC_PUBLIC_KEY_BIN
}
generate_dac()
{
$CHIP_CERT_TOOL --type d \
--subject-cn "$DAC_CERT_SUBJECT" \
--subject-vid "$VENDOR_ID" \
--subject-pid "$PRODUCT_ID" \
--valid-from "$CERT_VALID_FROM" \
--lifetime "$CERT_LIFETIME" \
--ca-key "$PAI_PRIVATE_KEY_NAME" \
--ca-cert "$PAI_CERT_NAME" \
--out-key "$DAC_PRIVATE_KEY_NAME" \
--out "$DAC_CERT_NAME"
if [ $? -ne 0 ]; then
echo "Failed to generate PAA certificate"
exit 1
fi
generate_keypair_bin $DAC_PRIVATE_KEY_NAME $DAC_PRIVATE_KEY_BIN $DAC_PUBLIC_KEY_BIN
convert_pem_to_der $DAC_CERT_NAME $DAC_CERT_NAME_DER
echo "Generated DAC certificate, key pair"
}
populate_csv()
{
cat << EOF >> $OUT_DIR/matter_factory.csv
key,type,encoding,value
chip_creds,namespace,,
dac_cert,file,binary,$DAC_CERT_NAME_DER
pai_cert,file,binary,$PAI_CERT_NAME_DER
dac_pubkey,file,binary,$DAC_PUBLIC_KEY_BIN
dac_privkey,file,binary,$DAC_PRIVATE_KEY_BIN
cert_dclrn,file,hex2bin,cert_dclrn.bin
EOF
}
print_flash_help()
{
echo ""
echo "=============================================================="
echo "Generated matter factory partition:$1"
echo "Please flash the partition to the device using esptool.py"
echo "esptool.py -p <serial-port> write_flash $2 $1"
echo "=============================================================="
}
generate_matter_factory_partition()
{
populate_csv
if [ $? -ne 0 ]; then
echo "Failed to generate CSV file"
exit 1
fi
$NVS_GENERATOR generate $OUT_DIR/matter_factory.csv $OUT_DIR/matter_factory.bin 0x6000
if [ $? -ne 0 ]; then
echo "Failed to generate matter factory partition"
exit 1
fi
print_flash_help $OUT_DIR/matter_factory.bin 0x6000
}
main()
{
generate_pai
generate_dac
generate_matter_factory_partition
}
# Execution starts here
main
+162
View File
@@ -0,0 +1,162 @@
# Manufacturing Partition Generator Utility
## Dependencies
* [CHIP Certificate Tool](https://github.com/project-chip/connectedhomeip/tree/master/src/tools/chip-cert),
chip-cert provides command line interface (CLI) utility used for generating and manipulating CHIP certificates and CHIP private keys.
* [SPAKE2P Parameters Tool](https://github.com/project-chip/connectedhomeip/tree/master/src/tools/spake2p),
spake2p tool provides command line interface (CLI) utility used for generating spake parameters (PIN code and verifier) for device manufacturing provisioning.
* [QR Code Tool](https://github.com/project-chip/connectedhomeip/tree/master/src/qrcodetool),
qrcodetool generates onboarding QR code payload and manual pairing code.
* [Mass Manufacturing Utility](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#manufacturing-utility),
mfg_gen.py to create factory NVS partition images.
### Install python dependencies
```
cd path/to/esp-matter/tools/mfg_tool
python3 -m pip install -r requirements.txt
```
### [Build and setup tools in Matter SDK](https://github.com/project-chip/connectedhomeip/blob/master/docs/guides/BUILDING.md#build-for-the-host-os-linux-or-macos)
#### Build chip-cert and spake2p
```
cd path/to/esp-matter/connectedhomeip/connectedhomeip
source scripts/activate.sh
gn gen out/host
ninja -C out/host
```
Above commands will generate spake2p and chip-cert at `esp-matter/connectedhomeip/connectedhomeip/out/host`.
#### Build qrcodetool
```
cd path/to/esp-matter/connectedhomeip/connectedhomeip
ninja -C out/host src/qrcodetool
# This builds the qrcodetool at path out/host/obj/src/qrcodetool/bin
# Move this to out/host to keep all the tools at the same path
cp out/host/obj/src/qrcodetool/bin/qrcodetool out/host
```
#### Add the tools path to $PATH
```
export PATH="$PATH:path/to/esp-matter/connectedhomeip/connectedhomeip/out/host"
```
### mfg_gen.py
`mfg_gen.py` is present at path `$IDF_PATH/tools/mass_mfg/mfg_gen.py`
## Output files and directory structure
```
out
└── fff1_8000
├── 11fe2c53-9a38-445c-b58f-2ff0554cd981
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-onb_codes.csv
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-partition.bin
│   ├── 11fe2c53-9a38-445c-b58f-2ff0554cd981-qrcode.png
│   └── internal
│   ├── DAC_cert.der
│   ├── DAC_cert.pem
│   ├── DAC_key.pem
│   ├── DAC_private_key.bin
│   ├── DAC_public_key.bin
│   ├── PAI_cert.der
│   └── partition.csv
├── 14874525-30b5-4c66-a00e-30e4af5dfb20
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-onb_codes.csv
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-partition.bin
│   ├── 14874525-30b5-4c66-a00e-30e4af5dfb20-qrcode.png
│   └── internal
│   ├── DAC_cert.der
│   ├── DAC_cert.pem
│   ├── DAC_key.pem
│   ├── DAC_private_key.bin
│   ├── DAC_public_key.bin
│   ├── PAI_cert.der
│   └── partition.csv
└── staging
├── config.csv
├── master.csv
├── pai_cert.der
└── pin_disc.csv
```
Tool generates following output files:
- Partition Binary : `<uuid>-partition.bin`
- Onboarding codes : `<uuid>-onb_codes.csv`
- QR Code image : `<uuid>-qrcode.png`
Other intermediate files are stored in `internal/` directory:
- Partition CSV : `partition.csv`
- PAI Certificate : `PAI_cert.der`
- DAC Certificates : `DAC_cert.der`, `DAC_cert.pem`
- DAC Private Key : `DAC_private_key.bin`
- DAC Public Key : `DAC_public_key.bin`
Above files are stored at `out/<vid_pid>/<UUID>`. Each device is identified with an unique UUID.
Common intermediate files are stored at `out/<vid_pid>/staging`.
## Usage examples
`mfg_tool.py -h` lists the mandatory as well as optional arguments.
Below commands uses the test PAI signing certificate and key, test certificate declaration present in Matter SDK, Vendor ID: 0xFFF2, and Product ID: 0x8001.
### Generate a factory partition
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
```
### Generate 5 factory partitions [Optional argument : `-n`]
```
./mfg_tool.py -n 5 -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
```
### Generate factory partition using existing DAC certificate and private key [Optional arguments : `--dac-cert` and `--dac-key`]
```
./mfg_tool.py -cn "My Bulb" -v 0xFFF2 -p 0x8001 --pai \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--dac-key DAC_key.pem --dac-cert DAC_cert.pem
```
### Generate factory partitions using existing Passcode, Discriminator, and unique ID [Optional arguments : `--passcode`, `--discriminator`, and `--unique-id`]
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--passcode 20202021 --discriminator 3840 --unique-id d2f351f57bb9387445a5f92a601d1c14
```
* NOTE: Script generates only one factory partition if **DAC or Discriminator or Passcode or Unique-ID** is specified.
### Generate factory partitions with extra NVS key-values specified using csv and mcsv file [Optional arguments : `--csv` and `--mcsv`]
```
./mfg_tool.py -cn "My bulb" -v 0xFFF2 -p 0x8001 --pai \
-k path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
-c path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
-cd path/to/esp-matter/connectedhomeip/connectedhomeip/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der \
--csv extra_nvs_key_config.csv --mcsv extra_nvs_key_value.csv
```
Above command will generate `n` number of partitions. Where `n` is the rows in the mcsv file.
Output binary contains all the chip specific key/value and key/values specified using `--csv` and `--mcsv` option.
## Flashing the manufacturing binary
Please note that `mfg_tool.py` only generates manufacturing binary images which need to be flashed onto device using `esptool.py`.
`esptool.py` is present at path `$IDF_PATH/components/esptool_py/esptool/esptool.py`
* Flashing a binary image to the device
```
esptool.py -p <serial_port> write_flash <address> path/to/<uuid>-partition.bin
```
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
# Copyright 2022 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This file contains the CHIP specific key along with the data type and encoding format
"""
CHIP_KEY_MAP = {
'chip-factory': {
'discriminator': {
'type': 'data',
'encoding': 'u32',
},
'pin-code': {
'type': 'data',
'encoding': 'u32',
},
'iteration-count': {
'type': 'data',
'encoding': 'u32',
},
'salt': {
'type': 'data',
'encoding': 'string',
},
'verifier': {
'type': 'data',
'encoding': 'string',
},
'dac-cert': {
'type': 'file',
'encoding': 'binary',
},
'dac-key': {
'type': 'file',
'encoding': 'binary',
},
'dac-pub-key': {
'type': 'file',
'encoding': 'binary',
},
'pai-cert': {
'type': 'file',
'encoding': 'binary',
},
'cert-dclrn': {
'type': 'file',
'encoding': 'binary',
},
},
'chip-config': {
'unique-id': {
'type': 'data',
'encoding': 'string',
},
},
}
+657
View File
@@ -0,0 +1,657 @@
#!/usr/bin/env python3
# Copyright 2022 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Script to generate Matter factory NVS partition image, Onboarding codes, and QR codes.
"""
import os
import sys
import csv
import uuid
import shutil
import random
import logging
import binascii
import argparse
import pyqrcode
import subprocess
import cryptography.hazmat.backends
import cryptography.x509
from chip_nvs_keys import CHIP_KEY_MAP
if not os.getenv('IDF_PATH'):
logging.error("IDF_PATH environment variable is not set")
sys.exit(1)
# TODO: Handle count > 100 case. spake2p gen-verifier doesn't support count > 100
TOOLS = {
'spake2p' : None,
'chip-cert' : None,
'qrcodetool': None,
'mfg_gen' : None,
}
PAI = {
'cert_pem': None,
'cert_der': None,
'key_pem' : None,
'key_der' : None,
}
OUT_DIR = {
'top' : None,
'chip': None,
}
OUT_FILE = {
'config_csv' : None,
'chip_mcsv' : None,
'mcsv' : None,
'pin_csv': None,
'pin_disc_csv': None,
}
UNIQUE_ID_LEN = 16 # 16 bytes (128-bits)
INVALID_PASSCODES = [ 00000000, 11111111, 22222222, 33333333, 44444444, 55555555,
66666666, 77777777, 88888888, 99999999, 12345678, 87654321 ]
UUIDs = list()
def vid_pid_str(vid, pid):
return '_'.join([hex(vid)[2:], hex(pid)[2:]])
def disc_pin_str(discriminator, passcode):
return '_'.join([hex(discriminator)[2:], hex(passcode)[2:]])
def check_tools_exists():
TOOLS['spake2p'] = shutil.which('spake2p')
if TOOLS['spake2p'] is None:
logging.error('spake2p not found, please add spake2p path to PATH environment variable')
sys.exit(1)
TOOLS['chip-cert'] = shutil.which('chip-cert')
if TOOLS['chip-cert'] is None:
logging.error('chip-cert not found, please add chip-cert path to PATH environment variable')
sys.exit(1)
TOOLS['qrcodetool'] = shutil.which('qrcodetool')
if TOOLS['qrcodetool'] is None:
logging.error('qrcodetool not found, please add qrcodetool path to PATH environment variable')
sys.exit(1)
TOOLS['mfg_gen'] = os.sep.join([os.getenv('IDF_PATH'), 'tools', 'mass_mfg', 'mfg_gen.py'])
if not os.path.exists(TOOLS['mfg_gen']):
logging.error('mfg_gen.py not found, please make sure IDF_PATH environment variable is set correctly')
sys.exit(1)
logging.debug('Using following tools:')
logging.debug('spake2p: {}'.format(TOOLS['spake2p']))
logging.debug('chip-cert: {}'.format(TOOLS['chip-cert']))
logging.debug('qrcodetool: {}'.format(TOOLS['qrcodetool']))
logging.debug('mfg_gen: {}'.format(TOOLS['mfg_gen']))
def execute_cmd(cmd):
logging.debug('Executing Command: {}'.format(cmd))
status = subprocess.run(cmd, capture_output=True)
try:
status.check_returncode()
except subprocess.CalledProcessError as e:
if status.stderr:
logging.error('[stderr]: {}'.format(status.stderr.decode('utf-8').strip()))
logging.error('Command failed with error: {}'.format(e))
sys.exit(1)
def generate_passcodes(args):
iter_count_max = 10000
salt_len_max = 32
cmd = [
TOOLS['spake2p'], 'gen-verifier',
'--count', str(args.count),
'--iteration-count', str(iter_count_max),
'--salt-len', str(salt_len_max),
'--out', OUT_FILE['pin_csv'],
]
# If passcode is provided, use it
if (args.passcode):
cmd.extend(['--pin-code', str(args.passcode)])
execute_cmd(cmd)
def generate_discriminators(args):
discriminators = list()
# If discriminator is provided, use it
if args.discriminator:
discriminators.append(args.discriminator)
else:
for i in range(args.count):
discriminators.append(random.randint(0x0000, 0x0FFF))
return discriminators
# Append discriminator to each line of the passcode file
def append_discriminator(discriminator):
with open(OUT_FILE['pin_csv'], 'r') as fd:
lines = fd.readlines()
lines[0] = ','.join([lines[0].strip(), 'Discriminator'])
for i in range(1, len(lines)):
lines[i] = ','.join([lines[i].strip(), str(discriminator[i - 1])])
with open(OUT_FILE['pin_disc_csv'], 'w') as fd:
fd.write('\n'.join(lines) + '\n')
os.remove(OUT_FILE['pin_csv'])
# Generates the csv file containing chip specific keys and keys provided by user in csv file
def generate_config_csv(args):
logging.info("Generating Config CSV...")
csv_data = ''
for k, v in CHIP_KEY_MAP.items():
csv_data += k + ',' + 'namespace,' + '\n'
for k1, v1 in v.items():
csv_data += k1 + ',' + v1['type'] + ',' + v1['encoding'] + '\n'
# Read data from the user provided csv file
if args.csv:
with open(args.csv, 'r') as f:
csv_data += f.read()
with open(OUT_FILE['config_csv'], 'w') as f:
f.write(csv_data)
def write_chip_mcsv_header(args):
logging.info('Writing chip manifest CSV header...')
keys = list()
for _, v in CHIP_KEY_MAP.items():
for k1, _ in v.items():
keys.append(k1)
mcsv_header = ','.join(keys) + '\n'
with open(OUT_FILE['chip_mcsv'], 'w') as f:
f.write(mcsv_header)
def append_chip_mcsv_row(row_data, args):
logging.info('Appending chip master CSV row...')
with open(OUT_FILE['chip_mcsv'], 'a') as f:
f.write(row_data + '\n')
# Convert the certificate in PEM format to DER format
def convert_x509_cert_from_pem_to_der(pem_file, out_der_file):
with open(pem_file, 'rb') as f:
pem_data = f.read()
pem_cert = cryptography.x509.load_pem_x509_certificate(pem_data, cryptography.hazmat.backends.default_backend())
der_cert = pem_cert.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.DER)
with open(out_der_file, 'wb') as f:
f.write(der_cert)
# Generate the Public and Private key pair binaries
def generate_keypair_bin(pem_file, out_privkey_bin, out_pubkey_bin):
with open(pem_file, 'rb') as f:
pem_data = f.read()
key_pem = cryptography.hazmat.primitives.serialization.load_pem_private_key(pem_data, None)
private_number_val = key_pem.private_numbers().private_value
public_number_x = key_pem.public_key().public_numbers().x
public_number_y = key_pem.public_key().public_numbers().y
public_key_first_byte = 0x04
with open(out_privkey_bin, 'wb') as f:
f.write(private_number_val.to_bytes(32, byteorder='big'))
with open(out_pubkey_bin, 'wb') as f:
f.write(public_key_first_byte.to_bytes(1, byteorder='big'))
f.write(public_number_x.to_bytes(32, byteorder='big'))
f.write(public_number_y.to_bytes(32, byteorder='big'))
def generate_pai(args, ca_key, ca_cert, out_key, out_cert):
cmd = [
TOOLS['chip-cert'], 'gen-att-cert',
'--type', 'i',
'--subject-cn', '"{} PAI {}"'.format(args.subject_cn_prefix, '00'),
'--out-key', out_key,
'--out', out_cert,
]
if args.lifetime:
cmd.extend(['--lifetime', str(args.lifetime)])
if args.valid_from:
cmd.extend(['--valid-from', str(args.valid_from)])
cmd.extend([
'--subject-vid', hex(args.vendor_id)[2:],
'--subject-pid', hex(args.product_id)[2:],
'--ca-key', ca_key,
'--ca-cert', ca_cert,
])
execute_cmd(cmd)
logging.info('Generated PAI certificate: {}'.format(out_cert))
logging.info('Generated PAI private key: {}'.format(out_key))
def generate_dac(iteration, args, discriminator, passcode, ca_key, ca_cert):
out_key_pem = os.sep.join([OUT_DIR['top'], UUIDs[iteration], 'internal', 'DAC_key.pem'])
out_cert_pem = out_key_pem.replace('key.pem', 'cert.pem')
out_cert_der = out_key_pem.replace('key.pem', 'cert.der')
out_private_key_bin = out_key_pem.replace('key.pem', 'private_key.bin')
out_public_key_bin = out_key_pem.replace('key.pem', 'public_key.bin')
cmd = [
TOOLS['chip-cert'], 'gen-att-cert',
'--type', 'd',
'--subject-cn', '"{} DAC {}"'.format(args.subject_cn_prefix, iteration),
'--out-key', out_key_pem,
'--out', out_cert_pem,
]
if args.lifetime:
cmd.extend(['--lifetime', str(args.lifetime)])
if args.valid_from:
cmd.extend(['--valid-from', str(args.valid_from)])
cmd.extend(['--subject-vid', hex(args.vendor_id)[2:],
'--subject-pid', hex(args.product_id)[2:],
'--ca-key', ca_key,
'--ca-cert', ca_cert,
])
execute_cmd(cmd)
logging.info('Generated DAC certificate: {}'.format(out_cert_pem))
logging.info('Generated DAC private key: {}'.format(out_key_pem))
convert_x509_cert_from_pem_to_der(out_cert_pem, out_cert_der)
logging.info('Generated DAC certificate in DER format: {}'.format(out_cert_der))
generate_keypair_bin(out_key_pem, out_private_key_bin, out_public_key_bin)
logging.info('Generated DAC private key in binary format: {}'.format(out_private_key_bin))
logging.info('Generated DAC public key in binary format: {}'.format(out_public_key_bin))
return out_cert_der, out_private_key_bin, out_public_key_bin
def use_dac_from_args(args):
logging.info('Using DAC from command line arguments...')
logging.info('DAC Certificate: {}'.format(args.dac_cert))
logging.info('DAC Private Key: {}'.format(args.dac_key))
# There should be only one UUID in the UUIDs list if DAC is specified
out_cert_der = os.sep.join([OUT_DIR['top'], UUIDs[0], 'internal', 'DAC_cert.der'])
out_private_key_bin = out_cert_der.replace('cert.der', 'private_key.bin')
out_public_key_bin = out_cert_der.replace('cert.der', 'public_key.bin')
convert_x509_cert_from_pem_to_der(args.dac_cert, out_cert_der)
logging.info('Generated DAC certificate in DER format: {}'.format(out_cert_der))
generate_keypair_bin(args.dac_key, out_private_key_bin, out_public_key_bin)
logging.info('Generated DAC private key in binary format: {}'.format(out_private_key_bin))
logging.info('Generated DAC public key in binary format: {}'.format(out_public_key_bin))
return out_cert_der, out_private_key_bin, out_public_key_bin
def setup_out_dirs(vid, pid, count):
OUT_DIR['top'] = os.sep.join(['out', vid_pid_str(vid, pid)])
OUT_DIR['stage'] = os.sep.join(['out', vid_pid_str(vid, pid), 'staging'])
os.makedirs(OUT_DIR['top'], exist_ok=True)
os.makedirs(OUT_DIR['stage'], exist_ok=True)
OUT_FILE['config_csv'] = os.sep.join([OUT_DIR['stage'], 'config.csv'])
OUT_FILE['chip_mcsv'] = os.sep.join([OUT_DIR['stage'], 'chip_master.csv'])
OUT_FILE['mcsv'] = os.sep.join([OUT_DIR['stage'], 'master.csv'])
OUT_FILE['pin_csv'] = os.sep.join([OUT_DIR['stage'], 'pin.csv'])
OUT_FILE['pin_disc_csv'] = os.sep.join([OUT_DIR['stage'], 'pin_disc.csv'])
# Create directories to store the generated files
for i in range(count):
uuid_str = str(uuid.uuid4())
UUIDs.append(uuid_str)
os.makedirs(os.sep.join([OUT_DIR['top'], uuid_str, 'internal']), exist_ok=True)
def generate_passcodes_and_discriminators(args):
# Generate passcodes using spake2p tool
generate_passcodes(args)
# Randomly generate discriminators
discriminators = generate_discriminators(args)
# Append discriminators to passcodes file
append_discriminator(discriminators)
def setup_csv_files(args):
generate_config_csv(args)
write_chip_mcsv_header(args)
def setup_root_certs(args):
# If PAA is passed as input, then generate PAI certificate
if args.paa:
# output file names
PAI['cert_pem'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.pem'])
PAI['cert_der'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.der'])
PAI['key_pem'] = os.sep.join([OUT_DIR['stage'], 'pai_key.pem'])
generate_pai(args, args.key, args.cert, PAI['key_pem'], PAI['cert_pem'])
convert_x509_cert_from_pem_to_der(PAI['cert_pem'], PAI['cert_der'])
logging.info('Generated PAI certificate in DER format: {}'.format(PAI['cert_der']))
# If PAI is passed as input, generate DACs
elif args.pai:
PAI['cert_pem'] = args.cert
PAI['key_pem'] = args.key
PAI['cert_der'] = os.sep.join([OUT_DIR['stage'], 'pai_cert.der'])
convert_x509_cert_from_pem_to_der(PAI['cert_pem'], PAI['cert_der'])
logging.info('Generated PAI certificate in DER format: {}'.format(PAI['cert_der']))
# No need to verify else block as validate_args() already checks for this
# Generates DACs, add the required items to the master.csv file, and
# generates the onboarding data i.e. manual codes and QR codes and image
def generate_dacs_and_onb_data(args):
with open(OUT_FILE['pin_disc_csv'], 'r') as csvf:
pin_disc_dict = csv.DictReader(csvf)
# If any key is added to chip_nvs_keys.py make sure to maintain that order below
for row in pin_disc_dict:
row_items = list()
row_items.append(row['Discriminator'])
# TODO: remove PIN Code, spec says it should not be included in nvs storage
row_items.append(row['PIN Code'])
row_items.append(row['Iteration Count'])
row_items.append(row['Salt'])
row_items.append(row['Verifier'])
if args.dac_key is not None and args.dac_cert is not None:
dacs = use_dac_from_args(args)
else:
dacs = generate_dac(int(row['Index']), args, int(row['Discriminator']), int(row['PIN Code']), PAI['key_pem'], PAI['cert_pem'])
# This appends DAC cert, private key, and public key
row_items.extend([os.path.abspath(x) for x in dacs])
row_items.append(os.path.abspath(PAI['cert_der']))
row_items.append(os.path.abspath(args.cert_dclrn))
# Unique-id
row_items.append(binascii.b2a_hex(os.urandom(UNIQUE_ID_LEN)).decode('utf-8'))
mcsv_row_data = ','.join(row_items)
append_chip_mcsv_row(mcsv_row_data, args)
# Generate onboarding data
generate_onboarding_data(args, int(row['Index']), int(row['Discriminator']), int(row['PIN Code']))
def merge_chip_mcsv_and_user_mcsv(args):
logging.info('Merging chip master CSV and user master CSV...')
with open(OUT_FILE['chip_mcsv'], 'r') as f:
chip_mcsv_data = f.readlines()
# If user mcsv is present, merge it with chip mcsv
if args.mcsv:
logging.info('User manifest CSV is present. Merging...')
with open(args.mcsv, 'r') as f:
user_mcsv_data = f.readlines()
if (len(chip_mcsv_data) != len(user_mcsv_data)):
logging.error('Chip and user mcsv files have different number of rows')
sys.exit(1)
chip_mcsv_data = [','.join((c_line.strip(), u_line.strip())) + '\n' for c_line, u_line in zip(chip_mcsv_data, user_mcsv_data)]
with open(OUT_FILE['mcsv'], 'w') as f:
f.write(''.join(chip_mcsv_data))
os.remove(OUT_FILE['chip_mcsv'])
def organize_output_files(suffix):
for i in range(len(UUIDs)):
dest_path = os.sep.join([OUT_DIR['top'], UUIDs[i]])
internal_path = os.sep.join([dest_path, 'internal'])
replace = os.sep.join([OUT_DIR['top'], 'bin', '{}-{}.bin'.format(suffix, str(i + 1))])
replace_with = os.sep.join([dest_path, '{}-partition.bin'.format(UUIDs[i])])
os.rename(replace, replace_with)
replace = os.sep.join([OUT_DIR['top'], 'csv', '{}-{}.csv'.format(suffix, str(i + 1))])
replace_with = os.sep.join([internal_path, 'partition.csv'])
os.rename(replace, replace_with)
# Also copy the PAI certificate to the output directory
shutil.copy2(PAI['cert_der'], os.sep.join([internal_path, 'PAI_cert.der']))
logging.info('Generated output files at: {}'.format(os.sep.join([OUT_DIR['top'], UUIDs[i]])))
os.rmdir(os.sep.join([OUT_DIR['top'], 'bin']))
os.rmdir(os.sep.join([OUT_DIR['top'], 'csv']))
def generate_partitions(suffix, size):
cmd = [
TOOLS['mfg_gen'], 'generate',
OUT_FILE['config_csv'], OUT_FILE['mcsv'],
suffix, hex(size), '--outdir', OUT_DIR['top']
]
execute_cmd(cmd)
def get_setup_payload_data(args, discriminator, passcode):
data = 'version 0\n'
data += 'vendorID ' + str(args.vendor_id) + '\n'
data += 'productID ' + str(args.product_id) + '\n'
data += 'commissioningFlow ' + str(args.commissioning_flow) + '\n'
data += 'rendezVousInformation ' + str(1 << args.discovery_mode) + '\n'
data += 'discriminator ' + str(discriminator) + '\n'
data += 'setUpPINCode ' + str(passcode) + '\n'
return data
def get_chip_qrcode(file):
data = subprocess.check_output([TOOLS['qrcodetool'], 'generate-qr-code', '-f', file])
data = data.decode('utf-8').splitlines()
qrcodeline = data[-1].split(': ')
if qrcodeline[1] == 'QR Code':
return qrcodeline[-1]
else:
logging.error('Failed to generate QR code')
return None
def get_chip_manualcode(file):
data = subprocess.check_output([TOOLS['qrcodetool'], 'generate-manual-code', '-f', file])
data = data.decode('utf-8').splitlines()
manualcodeline = data[-1].split(': ')
if manualcodeline[1] == 'Manual Code':
return manualcodeline[-1]
else:
logging.error('Failed to generate manual code')
return None
def generate_onboarding_data(args, index, discriminator, passcode):
setup_payload_file = os.sep.join(['', 'tmp', 'setup_payload_{}.txt'.format(args.vendor_id, args.product_id)])
with open(setup_payload_file, 'w') as f:
f.write(get_setup_payload_data(args, discriminator, passcode))
chip_qrcode = get_chip_qrcode(setup_payload_file)
chip_manualcode = get_chip_manualcode(setup_payload_file)
logging.info('Generated QR code: ' + chip_qrcode)
logging.info('Generated manual code: ' + chip_manualcode)
# TODO: Append commissioning flow, discovery mode, passcode, discriminator as well?
csv_data = 'qrcode,manualcode\n'
csv_data += chip_qrcode + ',' + chip_manualcode + '\n'
onboarding_data_file = os.sep.join([OUT_DIR['top'], UUIDs[index], '{}-onb_codes.csv'.format(UUIDs[index])])
with open(onboarding_data_file, 'w') as f:
f.write(csv_data)
# Create QR code image as mentioned in the spec
qrcode_file = os.sep.join([OUT_DIR['top'], UUIDs[index], '{}-qrcode.png'.format(UUIDs[index])])
chip_qr = pyqrcode.create(chip_qrcode, version=2, error='M')
chip_qr.png(qrcode_file, scale=6)
os.remove(setup_payload_file)
logging.info('Generated onboarding data and QR Code')
def validate_args(args):
# csv and mcsv both should present or none
if (args.csv is not None) != (args.mcsv is not None):
logging.error("csv and mcsv should be both present or none")
sys.exit(1)
else:
# Read the number of lines in mcsv file
if args.mcsv is not None:
with open(args.mcsv, 'r') as f:
lines = sum(1 for line in f)
# Subtract 1 for the header line
args.count = lines - 1
# Validate the passcode
if args.passcode is not None:
if ((args.passcode < 0x0000001 and args.passcode > 0x5F5E0FE) or (args.passcode in INVALID_PASSCODES)):
logging.error('Invalid passcode' + str(args.passcode))
sys.exit(1)
# Validate the discriminator
if (args.discriminator is not None) and (args.discriminator not in range(0x0000, 0x0FFF)):
logging.error('Invalid discriminator ' + str(args.discriminator))
sys.exit(1)
if args.unique_id is not None:
if len(args.unique_id) < (UNIQUE_ID_LEN * 2):
logging.error('Unique ID is too short')
sys.exit(1)
if (len(args.unique_id) % 2) != 0:
logging.error('Unique ID is not even length')
sys.exit(1)
# Check for a valid hex string
try:
int(args.unique_id, 16)
except ValueError:
logging.error('Unique ID is not a valid hex string')
sys.exit(1)
# DAC key and DAC cert both should be present or none
if (args.dac_key is not None) != (args.dac_cert is not None):
logging.error("dac_key and dac_cert should be both present or none")
sys.exit(1)
else:
# Make sure PAI certificate is present if DAC is present
if (args.dac_key is not None) and (args.pai is False):
logging.error('Please provide PAI certificate along with DAC certificate and DAC key')
sys.exit(1)
# If unique_id/discriminator/passcode/DAC is present then we are restricting
# the number of partitions to 1
if (args.discriminator is not None
or args.passcode is not None
or args.unique_id is not None
or args.dac_key is not None):
if args.count > 1:
logging.error('Number of partitions should be 1 when unique_id or discriminator or passcode or DAC is present')
sys.exit(1)
# Validate the input certificate type, if DAC is not present
if args.dac_key is None and args.dac_cert is None:
if args.paa:
logging.info('Input Root certificate type PAA')
elif args.pai:
logging.info('Input Root certificate type PAI')
else:
logging.error('Either PAA or PAI certificate is required')
sys.exit(1)
# Check if Key and certificate are present
if args.key is None or args.cert is None:
logging.error('PAA key and certificate are required')
sys.exit(1)
logging.info('Number of manufacturing NVS images to generate: {}'.format(args.count))
def get_args():
def any_base_int(s): return int(s, 0)
parser = argparse.ArgumentParser(description='Manufacuring partition generator tool')
parser.add_argument('-n', '--count', type=any_base_int, default=1, help='The number of manufacturing partition binaries to generate. Default is 1. If --csv and --mcsv are present, the number of lines in the mcsv file is used.')
parser.add_argument('-s', '--size', type=any_base_int, default=0x6000, help='The size of manufacturing partition binaries to generate. Default is 0x6000.')
parser.add_argument('-cn', '--subject-cn-prefix', type=str, default='ESP32', help='The common name prefix of the subject of the generated certificate.')
parser.add_argument('-v', '--vendor-id', type=any_base_int, required=True, help='The vendor ID.')
parser.add_argument('-p', '--product-id', type=any_base_int, required=True, help='The product ID.')
input_cert_group = parser.add_mutually_exclusive_group(required=True)
input_cert_group.add_argument('--paa', action='store_true', help='Use input certificate as PAA certificate.')
input_cert_group.add_argument('--pai', action='store_true', help='Use input certificate as PAI certificate.')
# If DAC is present then PAI key is not required, so it is marked as not required here
# but, if DAC is not present then PAI key is required and that case is validated in validate_args()
parser.add_argument('-c', '--cert', type=str, required=True, help='The input certificate file in PEM format.')
parser.add_argument('-k', '--key', type=str, required=False, help='The input key file in PEM format.')
parser.add_argument('-cd', '--cert-dclrn', type=str, required=True, help='The certificate declaration file in DER format.')
parser.add_argument('--dac-cert', type=str, help='The input DAC certificate file in PEM format.')
parser.add_argument('--dac-key', type=str, help='The input DAC private key file in PEM format.');
parser.add_argument('-lt', '--lifetime', default=4294967295, type=any_base_int, help='Lifetime of the generated certificate. Default is 4294967295 if not specified, this indicate that certificate does not have well defined expiration date.')
parser.add_argument('-vf', '--valid-from', type=str, help='The start date for the certificate validity period in format <YYYY>-<MM>-<DD> [ <HH>:<MM>:<SS> ]. Default is current date.')
parser.add_argument('--passcode', type=any_base_int, help='The passcode for pairing. Randomly generated if not specified.')
parser.add_argument('--discriminator', type=any_base_int, help='The discriminator for pairing. Randomly generated if not specified.')
parser.add_argument('-cf', '--commissioning-flow', type=any_base_int, default=0, help='Device commissioning flow, 0:Standard, 1:User-Intent, 2:Custom. Default is 0.', choices=[0, 1, 2])
parser.add_argument('-dm', '--discovery-mode', type=any_base_int, default=1, help='Commissionable device discovery netowrking technology. 0:WiFi-SoftAP, 1:BLE, 2:On-network. Default is BLE.', choices=[0, 1, 2])
parser.add_argument('--csv', type=str, help='CSV file containing the partition schema for extra options. [REF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#csv-configuration-file]')
parser.add_argument('--mcsv', type=str, help='Master CSV file containig optional/extra values specified by the user. [REF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/mass_mfg.html#master-value-csv-file]')
parser.add_argument('-u', '--unique-id', type=str, help='Unique Identifier for the device. This should be at least 128-bit long. If not specified, a random unique id is generated.')
return parser.parse_args()
def main():
logging.basicConfig(format='[%(asctime)s] [%(levelname)7s] - %(message)s', level=logging.INFO)
args = get_args()
validate_args(args)
check_tools_exists()
setup_out_dirs(args.vendor_id, args.product_id, args.count)
generate_passcodes_and_discriminators(args)
setup_csv_files(args)
setup_root_certs(args)
generate_dacs_and_onb_data(args)
merge_chip_mcsv_and_user_mcsv(args)
generate_partitions('matter_partition', args.size)
organize_output_files('matter_partition')
if __name__ == "__main__":
main()
+6
View File
@@ -0,0 +1,6 @@
cryptography==36.0.2
cffi==1.15.0
future==0.18.2
pycparser==2.21
pypng==0.0.21
PyQRCode==1.2.1