mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-27 19:13:13 +00:00
example: Add dynamic passcode commissionable data provider for light-switch app
This commit is contained in:
@@ -201,6 +201,19 @@ available for you to run your application's code.
|
||||
|
||||
Applications that do not require BLE post commissioning, can disable it using app_ble_disable() once commissioning is complete. It is not done explicitly because of a known issue with esp32c3 and will be fixed with the next IDF release (v4.4.2).
|
||||
|
||||
## 4. Dynamic Passcode
|
||||
|
||||
If the device features a screen capable of displaying the pairing QR Code, it is advisable to utilize a dynamic passcode for this purpose as the static passcode shall conform to more stringent rules. To enable the use of a dynamic passcode in the example, please ensure that the following configuration options are activated.
|
||||
|
||||
```
|
||||
CONFIG_CUSTOM_COMMISSIONABLE_DATA_PROVIDER=y
|
||||
CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER=y
|
||||
```
|
||||
After implementing these configurations, the device will generate a new, random passcode every time it reboots, if it is not yet commissioned. To obtain the commissioning QR Code, enter `matter onboardingcodes ble qrcode` in the device console, and then initiate the pairing process.
|
||||
```
|
||||
./chip-tool pairing code-wifi 1 <ssid> <password> <qrcode>
|
||||
```
|
||||
|
||||
## A2 Appendix FAQs
|
||||
|
||||
### A2.1 Binding Failed
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
set(SRC_DIRS_LIST ".")
|
||||
|
||||
if (CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER)
|
||||
list(APPEND SRC_DIRS_LIST "custom_provider")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRC_DIRS ${SRC_DIRS_LIST}
|
||||
PRIV_INCLUDE_DIRS ".")
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
menu "Example Configuration"
|
||||
visible if CUSTOM_COMMISSIONABLE_DATA_PROVIDER
|
||||
|
||||
config DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
bool "Enable Dynamic Passcode Commissionable Data Provider"
|
||||
depends on CUSTOM_COMMISSIONABLE_DATA_PROVIDER
|
||||
default y
|
||||
|
||||
config DYNAMIC_PASSCODE_PROVIDER_DISCRIMINATOR
|
||||
int "Discriminator in Dynamic Passcode Commissionable Data Provider"
|
||||
depends on DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
default 3840
|
||||
range 0 4095
|
||||
help
|
||||
Fixed discriminator in custom dynamic passcode commissionable data provider
|
||||
|
||||
config DYNAMIC_PASSCODE_PROVIDER_ITERATIONS
|
||||
int "Iterations in Dynamic Passcode Commissionable Data Provider"
|
||||
depends on DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
default 10000
|
||||
range 1000 100000
|
||||
help
|
||||
Fixed iterations in custom dynamic passcode commissionable data provider
|
||||
|
||||
config DYNAMIC_PASSCODE_PROVIDER_SALT_BASE64
|
||||
string "Base64-Encoded Salt in Dynamic Passcode Commissionable Data Provider"
|
||||
depends on DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
default "0NHS09TV1tfY2drb3N3e36ChoqOkpaanqKmqq6ytrq8="
|
||||
help
|
||||
Fixed salt in custom dynamic passcode commissionable data provider. It should be a Base64-Encoded string.
|
||||
|
||||
endmenu
|
||||
@@ -13,12 +13,16 @@
|
||||
#include <esp_matter.h>
|
||||
#include <esp_matter_console.h>
|
||||
#include <esp_matter_ota.h>
|
||||
#include <esp_matter_providers.h>
|
||||
|
||||
#include <app_priv.h>
|
||||
#include <app_reset.h>
|
||||
#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
|
||||
#include <platform/ESP32/OpenthreadLauncher.h>
|
||||
#endif
|
||||
#if CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
#include <custom_provider/dynamic_commissionable_data_provider.h>
|
||||
#endif
|
||||
|
||||
static const char *TAG = "app_main";
|
||||
uint16_t switch_endpoint_id = 0;
|
||||
@@ -27,6 +31,10 @@ using namespace esp_matter;
|
||||
using namespace esp_matter::attribute;
|
||||
using namespace esp_matter::endpoint;
|
||||
|
||||
#if CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
dynamic_commissionable_data_provider g_dynamic_passcode_provider;
|
||||
#endif
|
||||
|
||||
static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg)
|
||||
{
|
||||
switch (event->Type) {
|
||||
@@ -125,6 +133,11 @@ extern "C" void app_main()
|
||||
set_openthread_platform_config(&config);
|
||||
#endif
|
||||
|
||||
#if CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER
|
||||
/* This should be called before esp_matter::start() */
|
||||
esp_matter::set_custom_commissionable_data_provider(&g_dynamic_passcode_provider);
|
||||
#endif
|
||||
|
||||
/* Matter start */
|
||||
err = esp_matter::start(app_event_cb);
|
||||
if (err != ESP_OK) {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2023 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 <crypto/CHIPCryptoPAL.h>
|
||||
#include <custom_provider/dynamic_commissionable_data_provider.h>
|
||||
#include <esp_log.h>
|
||||
#include <lib/support/Base64.h>
|
||||
#include <platform/ESP32/ESP32Config.h>
|
||||
#include <setup_payload/SetupPayload.h>
|
||||
|
||||
using namespace ::chip::DeviceLayer::Internal;
|
||||
using namespace ::chip;
|
||||
|
||||
constexpr char *TAG = "custom_provider";
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GetSetupDiscriminator(uint16_t &setupDiscriminator)
|
||||
{
|
||||
setupDiscriminator = CONFIG_DYNAMIC_PASSCODE_PROVIDER_DISCRIMINATOR;
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GetSpake2pIterationCount(uint32_t &iterationCount)
|
||||
{
|
||||
iterationCount = CONFIG_DYNAMIC_PASSCODE_PROVIDER_ITERATIONS;
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
|
||||
static bool is_valid_base64_str(const char *str)
|
||||
{
|
||||
const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
size_t len = strlen(str);
|
||||
if (len % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
size_t padding_len = 0;
|
||||
if (str[len - 1] == '=') {
|
||||
padding_len++;
|
||||
if (str[len - 2] == '=') {
|
||||
padding_len++;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < len - padding_len; ++i) {
|
||||
if (strchr(base64_chars, str[i]) == NULL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GetSpake2pSalt(MutableByteSpan &saltBuf)
|
||||
{
|
||||
const char *saltB64 = CONFIG_DYNAMIC_PASSCODE_PROVIDER_SALT_BASE64;
|
||||
ReturnErrorCodeIf(!is_valid_base64_str(saltB64), CHIP_ERROR_INVALID_ARGUMENT);
|
||||
size_t saltB64Len = strlen(saltB64);
|
||||
uint8_t salt[chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length];
|
||||
size_t saltLen = chip::Base64Decode32(saltB64, saltB64Len, salt);
|
||||
ReturnErrorCodeIf(saltLen < chip::Crypto::kSpake2p_Min_PBKDF_Salt_Length, CHIP_ERROR_INVALID_ARGUMENT);
|
||||
ReturnErrorCodeIf(saltLen > saltBuf.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
|
||||
|
||||
memcpy(saltBuf.data(), salt, saltLen);
|
||||
saltBuf.reduce_size(saltLen);
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GetSpake2pVerifier(MutableByteSpan &verifierBuf, size_t &verifierLen)
|
||||
{
|
||||
uint32_t setupPasscode = 0;
|
||||
uint32_t iterationCount = 0;
|
||||
uint8_t salt[Crypto::kSpake2p_Max_PBKDF_Salt_Length] = {0};
|
||||
chip::MutableByteSpan saltSpan(salt, Crypto::kSpake2p_Max_PBKDF_Salt_Length);
|
||||
ReturnErrorOnFailure(GetSetupPasscode(setupPasscode));
|
||||
ReturnErrorOnFailure(GetSpake2pIterationCount(iterationCount));
|
||||
ReturnErrorOnFailure(GetSpake2pSalt(saltSpan));
|
||||
chip::Crypto::Spake2pVerifier verifier;
|
||||
ReturnErrorOnFailure(verifier.Generate(iterationCount, saltSpan, setupPasscode));
|
||||
ReturnErrorOnFailure(verifier.Serialize(verifierBuf));
|
||||
verifierLen = verifierBuf.size();
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GetSetupPasscode(uint32_t &setupPasscode)
|
||||
{
|
||||
if (mSetupPasscode == 0) {
|
||||
ReturnErrorOnFailure(GenerateRandomPasscode(mSetupPasscode));
|
||||
}
|
||||
setupPasscode = mSetupPasscode;
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
|
||||
CHIP_ERROR dynamic_commissionable_data_provider::GenerateRandomPasscode(uint32_t &passcode)
|
||||
{
|
||||
ReturnErrorOnFailure(chip::Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&passcode), sizeof(passcode)));
|
||||
// Passcode MUST be 1 to 99999998
|
||||
passcode = (passcode % chip::kSetupPINCodeMaximumValue) + 1;
|
||||
if (!chip::SetupPayload::IsValidSetupPIN(passcode)) {
|
||||
// if the generated passcode is invalid (11111111, 22222222, 33333333, 44444444, 55555555, 66666666,
|
||||
// 77777777, 88888888, 12345678, 87654321), increase it by 1 to make it valid.
|
||||
passcode = passcode + 1;
|
||||
}
|
||||
return CHIP_NO_ERROR;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2023 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 <crypto/CHIPCryptoPAL.h>
|
||||
#include <platform/CommissionableDataProvider.h>
|
||||
|
||||
using chip::MutableByteSpan;
|
||||
using chip::DeviceLayer::CommissionableDataProvider;
|
||||
|
||||
class dynamic_commissionable_data_provider : public CommissionableDataProvider {
|
||||
public:
|
||||
dynamic_commissionable_data_provider()
|
||||
: CommissionableDataProvider() {}
|
||||
|
||||
// Members functions that implement the CommissionableDataProvider
|
||||
CHIP_ERROR GetSetupDiscriminator(uint16_t &setupDiscriminator) override;
|
||||
CHIP_ERROR SetSetupDiscriminator(uint16_t setupDiscriminator) override { return CHIP_ERROR_NOT_IMPLEMENTED; }
|
||||
CHIP_ERROR GetSpake2pIterationCount(uint32_t &iterationCount) override;
|
||||
CHIP_ERROR GetSpake2pSalt(MutableByteSpan &saltBuf) override;
|
||||
CHIP_ERROR GetSpake2pVerifier(MutableByteSpan &verifierBuf, size_t &verifierLen) override;
|
||||
CHIP_ERROR GetSetupPasscode(uint32_t &setupPasscode) override;
|
||||
CHIP_ERROR SetSetupPasscode(uint32_t setupPasscode) override { return CHIP_ERROR_NOT_IMPLEMENTED; }
|
||||
private:
|
||||
CHIP_ERROR GenerateRandomPasscode(uint32_t &passcode);
|
||||
uint32_t mSetupPasscode = 0;
|
||||
};
|
||||
@@ -41,11 +41,6 @@ CHIP_NVS_MAP = {
|
||||
'encoding': 'string',
|
||||
'value': None,
|
||||
},
|
||||
'verifier': {
|
||||
'type': 'data',
|
||||
'encoding': 'string',
|
||||
'value': None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+48
-26
@@ -99,17 +99,23 @@ def generate_passcodes(args):
|
||||
salt_len_max = 32
|
||||
with open(OUT_FILE['pin_csv'], 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["Index", "PIN Code", "Iteration Count", "Salt", "Verifier"])
|
||||
if args.enable_dynamic_passcode:
|
||||
writer.writerow(["Index", "Iteration Count", "Salt"])
|
||||
else:
|
||||
writer.writerow(["Index", "PIN Code", "Iteration Count", "Salt", "Verifier"])
|
||||
for i in range(0, args.count):
|
||||
if args.passcode:
|
||||
passcode = args.passcode
|
||||
else:
|
||||
passcode = random.randint(1, 99999998)
|
||||
if passcode in INVALID_PASSCODES:
|
||||
passcode -= 1
|
||||
salt = os.urandom(salt_len_max)
|
||||
verifier = generate_verifier(passcode, salt, iter_count_max)
|
||||
writer.writerow([i, passcode, iter_count_max, base64.b64encode(salt).decode('utf-8'), base64.b64encode(verifier).decode('utf-8')])
|
||||
if args.enable_dynamic_passcode:
|
||||
writer.writerow([i, iter_count_max, base64.b64encode(salt).decode('utf-8')])
|
||||
else:
|
||||
if args.passcode:
|
||||
passcode = args.passcode
|
||||
else:
|
||||
passcode = random.randint(1, 99999998)
|
||||
if passcode in INVALID_PASSCODES:
|
||||
passcode -= 1
|
||||
verifier = generate_verifier(passcode, salt, iter_count_max)
|
||||
writer.writerow([i, passcode, iter_count_max, base64.b64encode(salt).decode('utf-8'), base64.b64encode(verifier).decode('utf-8')])
|
||||
|
||||
|
||||
def generate_discriminators(args):
|
||||
@@ -187,7 +193,7 @@ def generate_pai(args, ca_key, ca_cert, out_key, out_cert):
|
||||
logging.info('Generated PAI private key: {}'.format(out_key))
|
||||
|
||||
|
||||
def generate_dac(iteration, args, discriminator, passcode, ca_key, ca_cert):
|
||||
def generate_dac(iteration, args, ca_key, ca_cert):
|
||||
out_key_pem = os.sep.join([OUT_DIR['top'], UUIDs[iteration], 'internal', 'DAC_key.pem'])
|
||||
out_private_key_der = out_key_pem.replace('key.pem', 'key.der')
|
||||
out_cert_pem = out_key_pem.replace('key.pem', 'cert.pem')
|
||||
@@ -338,13 +344,13 @@ def write_per_device_unique_data(args):
|
||||
chip_factory_update('discriminator', row['Discriminator'])
|
||||
chip_factory_update('iteration-count', row['Iteration Count'])
|
||||
chip_factory_update('salt', row['Salt'])
|
||||
chip_factory_update('verifier', row['Verifier'])
|
||||
if not args.enable_dynamic_passcode:
|
||||
chip_factory_update('verifier', row['Verifier'])
|
||||
if args.paa or args.pai:
|
||||
if args.dac_key is not None and args.dac_cert is not None:
|
||||
dacs = use_dac_from_args(args)
|
||||
else:
|
||||
dacs = generate_dac(int(row['Index']), args, int(row['Discriminator']),
|
||||
int(row['PIN Code']), PAI['key_pem'], PAI['cert_pem'])
|
||||
dacs = generate_dac(int(row['Index']), args, PAI['key_pem'], PAI['cert_pem'])
|
||||
|
||||
if not args.dac_in_secure_cert:
|
||||
chip_factory_update('dac-cert', os.path.abspath(dacs[0]))
|
||||
@@ -394,7 +400,8 @@ def write_per_device_unique_data(args):
|
||||
append_chip_mcsv_row(mcsv_row_data)
|
||||
|
||||
# Generate onboarding data
|
||||
generate_onboarding_data(args, int(row['Index']), int(chip_factory_get_val('discriminator')), int(row['PIN Code']))
|
||||
if not args.enable_dynamic_passcode:
|
||||
generate_onboarding_data(args, int(row['Index']), int(chip_factory_get_val('discriminator')), int(row['PIN Code']))
|
||||
if args.paa or args.pai:
|
||||
logging.info("Generated CSV of Common Name and DAC: {}".format(OUT_FILE['cn_dac_csv']))
|
||||
|
||||
@@ -435,22 +442,29 @@ def generate_summary(args):
|
||||
summary_csv_data = ''
|
||||
with open(master_csv, 'r') as mcsvf:
|
||||
summary_lines = mcsvf.read().splitlines()
|
||||
summary_csv_data += summary_lines[0] + ',pincode,qrcode,manualcode\n'
|
||||
summary_csv_data += summary_lines[0]
|
||||
if not args.enable_dynamic_passcode:
|
||||
summary_csv_data += ',pincode,qrcode,manualcode\n'
|
||||
else:
|
||||
summary_csv_data += '\n'
|
||||
with open(OUT_FILE['pin_disc_csv'], 'r') as pdcsvf:
|
||||
pin_disc_dict = csv.DictReader(pdcsvf)
|
||||
for row in pin_disc_dict:
|
||||
pincode = row['PIN Code']
|
||||
discriminator = row['Discriminator']
|
||||
payloads = SetupPayload(int(discriminator), int(pincode), 1 << args.discovery_mode, CommissioningFlow(args.commissioning_flow),
|
||||
args.vendor_id, args.product_id)
|
||||
qrcode = payloads.generate_qrcode()
|
||||
manualcode = payloads.generate_manualcode()
|
||||
# ToDo: remove this if qrcode tool can handle the standard manual code format
|
||||
if args.commissioning_flow == CommissioningFlow.Standard:
|
||||
manualcode = manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:]
|
||||
if not args.enable_dynamic_passcode:
|
||||
pincode = row['PIN Code']
|
||||
discriminator = row['Discriminator']
|
||||
payloads = SetupPayload(int(discriminator), int(pincode), 1 << args.discovery_mode, CommissioningFlow(args.commissioning_flow),
|
||||
args.vendor_id, args.product_id)
|
||||
qrcode = payloads.generate_qrcode()
|
||||
manualcode = payloads.generate_manualcode()
|
||||
# ToDo: remove this if qrcode tool can handle the standard manual code format
|
||||
if args.commissioning_flow == CommissioningFlow.Standard:
|
||||
manualcode = manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:]
|
||||
else:
|
||||
manualcode = '"' + manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:11] + '\n' + manualcode[11:15] + '-' + manualcode[15:18] + '-' + manualcode[18:20] + '-' + manualcode[20:21] + '"'
|
||||
summary_csv_data += summary_lines[1 + int(row['Index'])] + ',' + pincode + ',' + qrcode + ',' + manualcode + '\n'
|
||||
else:
|
||||
manualcode = '"' + manualcode[:4] + '-' + manualcode[4:7] + '-' + manualcode[7:11] + '\n' + manualcode[11:15] + '-' + manualcode[15:18] + '-' + manualcode[18:20] + '-' + manualcode[20:21] + '"'
|
||||
summary_csv_data += summary_lines[1 + int(row['Index'])] + ',' + pincode + ',' + qrcode + ',' + manualcode + '\n'
|
||||
summary_csv_data += summary_lines[1 + int(row['Index'])] + '\n'
|
||||
|
||||
with open(summary_csv, 'w') as scsvf:
|
||||
scsvf.write(summary_csv_data)
|
||||
@@ -528,6 +542,11 @@ def get_args():
|
||||
g_commissioning.add_argument('-dm', '--discovery-mode', type=any_base_int, default=1,
|
||||
help='Commissionable device discovery networking technology. \
|
||||
0:WiFi-SoftAP, 1:BLE, 2:On-network. Default is BLE.', choices=[0, 1, 2])
|
||||
g_commissioning.add_argument('--enable-dynamic-passcode', action="store_true", required=False,
|
||||
help='Enable dynamic passcode. If enabling this option, the generated binaries will \
|
||||
not include the spake2p verifier. so this option should work with a custom \
|
||||
CommissionableDataProvider which can generate random passcode and \
|
||||
corresponding verifier')
|
||||
|
||||
g_dac = parser.add_argument_group('Device attestation credential options')
|
||||
g_dac.add_argument('--dac-in-secure-cert', action="store_true", required=False,
|
||||
@@ -623,6 +642,9 @@ def add_optional_KVs(args):
|
||||
chip_factory_append('dac-pub-key', 'file', 'binary', None)
|
||||
chip_factory_append('pai-cert', 'file', 'binary', None)
|
||||
|
||||
if not args.enable_dynamic_passcode:
|
||||
chip_factory_append('verifier', 'data', 'string', None)
|
||||
|
||||
# Add certificate declaration
|
||||
if args.cert_dclrn:
|
||||
chip_factory_append('cert-dclrn','file','binary', os.path.relpath(args.cert_dclrn))
|
||||
|
||||
Reference in New Issue
Block a user