example: Add dynamic passcode commissionable data provider for light-switch app

This commit is contained in:
WanqQixiang
2023-12-01 18:38:33 +08:00
parent d26a261266
commit f83d896e2a
8 changed files with 267 additions and 32 deletions
+13
View File
@@ -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
+7 -1
View File
@@ -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
View File
@@ -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;
};
-5
View File
@@ -41,11 +41,6 @@ CHIP_NVS_MAP = {
'encoding': 'string',
'value': None,
},
'verifier': {
'type': 'data',
'encoding': 'string',
'value': None,
},
}
}
+48 -26
View File
@@ -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))