mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-28 03:23:07 +00:00
Merge branch 'task/mfg-tool' into 'main'
Manufacturing partition generator tool See merge request app-frameworks/esp-matter!96
This commit is contained in:
@@ -6,3 +6,5 @@ sdkconfig
|
||||
sdkconfig.old
|
||||
dependencies.lock
|
||||
managed_components/
|
||||
__pycache__/
|
||||
out/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 +0,0 @@
|
||||
3081e806092a864886f70d010702a081da3081d7020103310d300b0609608648016503040201304506092a864886f70d010701a0380436152400012501f1ff360205008018250334122c04135a494732303134315a423333303030312d32342405002406002507942624080018317c307a020103801462fa823359acfaa9963e1cfa140addf504f37160300b0609608648016503040201300a06082a8648ce3d04030204463044022043a63f2b943df33c38b3e02fcaa75fe3532aebbf5e63f5bbdbc0b1f01d3c4f6002204c1abf5f1807b81894b1576c47e4724e4d966c612ed3fa25c118c3f2b3f90369
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Executable
+657
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user