Skip to content

Commit

Permalink
[ESP32] FactoryDataProvider which implements CommissionableDataProvid…
Browse files Browse the repository at this point in the history
…er and DeviceAttestationCredentialsProvider (#17240)

* [ESP32] Commissionable data provider implementation

* Renamed CommissionableDataProviderImpl to
ESP32CommissionableDataProvider

* Fix OTA Requestor initialization order

* [ESP32] Factory data provider that implements CommissionableDataProvider
and DeviceAttestationCredentialsProvider

* Addressed review comments
  • Loading branch information
shubhamdp authored and pull[bot] committed Jan 12, 2024
1 parent 5a139bb commit 1535764
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ ColorControl
Comcast
Commandline
Commissionable
CommissionableDataProvider
commissionables
commissionee
CommissioningFlow
Expand Down Expand Up @@ -352,6 +353,7 @@ demangle
deployable
depottools
deps
der
desc
descheduled
detokenization
Expand All @@ -360,6 +362,7 @@ dev
devcontainer
devCtrl
DevelopmentCerts
DeviceAttestationCredentialsProvider
DeviceAttestationCredsExample
DeviceCaCerts
DeviceCert
Expand Down Expand Up @@ -845,6 +848,7 @@ mydir
MyPASSWORD
MySSID
NAMESERVER
NAMESPACE
namespacing
nano
natively
Expand Down Expand Up @@ -882,6 +886,7 @@ nullable
nullptr
NUM
NVM
NVS
nwk
NXP
objcopy
Expand Down
4 changes: 4 additions & 0 deletions config/esp32/components/chip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ if (CONFIG_CHIP_ENABLE_EXTERNAL_PLATFORM)
chip_gn_arg_append("chip_platform_target" "\"${CONFIG_CHIP_EXTERNAL_PLATFORM_TARGET}\"")
endif()

if (CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER)
chip_gn_arg_append("chip_use_transitional_commissionable_data_provider" "false")
endif()

set(args_gn_input "${CMAKE_CURRENT_BINARY_DIR}/args.gn.in")
file(GENERATE OUTPUT "${args_gn_input}" CONTENT "${chip_gn_args}")

Expand Down
10 changes: 10 additions & 0 deletions config/esp32/components/chip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,16 @@ menu "CHIP Device Layer"
bool "Enable Rotating Device Identifier Support"
default n

config ENABLE_ESP32_FACTORY_DATA_PROVIDER
bool "Use ESP32 Factory Data Provider"
default n
help
If this option is enabled, then ESP32 implementation of CommissionableDataProvider and
DeviceAttestationCredentialsProvider is used. Otherwise, the test-mode CommissionableDataProvider
and Example DeviceAttestationCredentialsProvider is used. ESP32 implementation reads factory data from
nvs partition. Factory partition can be configured using CONFIG_CHIP_FACTORY_NAMESPACE_PARTITION_LABEL
option, default is "nvs".

endmenu


Expand Down
44 changes: 44 additions & 0 deletions examples/lighting-app/esp32/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,50 @@ make sure the IDF_PATH has been exported(See the manual setup steps above).

$ idf.py -p /dev/tty.SLAB_USBtoUART monitor

## Using ESP32 Factory Data Provider

This application uses test-mode CommissionableDataProvider and Example
DeviceAttestationCredentialsProvider.

Enabled config option `CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER` to use ESP32
specific implementation of CommissionableDataProvider and
DeviceAttestationCredentialsProvider.

ESP32 implementation reads factory data from nvs partition, chip-factory data
must be flashed into the configure nvs partition. Factory partition can be
configured using CONFIG_CHIP_FACTORY_NAMESPACE_PARTITION_LABEL option, default
is "nvs".

`scripts/tools/generate_esp32_chip_factory_bin.py` script generates the
chip-factory NVS image `partition.bin`.

Below mentioned command generates the nvs image with test DAC with VID:0xFFF2
and PID:8001

```
cd third_party/connectedhomeip/scripts/tools
./generate_esp32_chip_factory_bin.py -d 3434 -p 99663300 \
--dac-cert ../../credentials/test/attestation/Chip-Test-DAC-FFF2-8001-0008-Cert.der \
--dac-key ../../credentials/test/attestation/Chip-Test-DAC-FFF2-8001-0008-Key.der \
--pai-cert ../../credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.der \
--cd ../../credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
cd -
```

This project uses VID:0xFFF1 and PID:0x8000, if you are planning to use the
above command as is please change the VID/PID using menuconfig options.

Use the following command to flash the NVS image. `0x9000` is default address
for `nvs` partition.

```
esptool.py -p <port> write_flash 0x9000 third_party/connectedhomeip/scripts/tools/partition.bin
```

NOTE: Please commission the device using above specified discriminator and
passcode

## Commissioning over BLE using chip-tool

- Please build the standalone chip-tool as described [here](../../chip-tool)
Expand Down
20 changes: 18 additions & 2 deletions examples/lighting-app/esp32/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
#include <platform/ESP32/NetworkCommissioningDriver.h>
#include <platform/ESP32/OTAImageProcessorImpl.h>

#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
#include <platform/ESP32/ESP32FactoryDataProvider.h>
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER

using namespace ::chip;
using namespace ::chip::Credentials;
using namespace ::chip::DeviceManager;
Expand All @@ -62,6 +66,10 @@ namespace {
app::Clusters::NetworkCommissioning::Instance
sWiFiNetworkCommissioningInstance(0 /* Endpoint Id */, &(NetworkCommissioning::ESPWiFiDriver::GetInstance()));
#endif

#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
ESP32FactoryDataProvider sFactoryDataProvider;
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
} // namespace

static void InitOTARequestor(void)
Expand All @@ -86,7 +94,12 @@ static void InitServer(intptr_t context)
chip::Server::GetInstance().Init(initParams);

// Initialize device attestation config
#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
SetDeviceAttestationCredentialsProvider(&sFactoryDataProvider);
#else
SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER

#if CHIP_DEVICE_CONFIG_ENABLE_WIFI
sWiFiNetworkCommissioningInstance.Init();
#endif
Expand All @@ -98,6 +111,7 @@ static void InitServer(intptr_t context)
chip::app::DnssdServer::Instance().StartServer();
}
#endif
InitOTARequestor();
}

extern "C" void app_main()
Expand All @@ -114,6 +128,10 @@ extern "C" void app_main()
ESP_LOGI(TAG, "chip-esp32-light-example starting");
ESP_LOGI(TAG, "==================================================");

#if CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER
SetCommissionableDataProvider(&sFactoryDataProvider);
#endif // CONFIG_ENABLE_ESP32_FACTORY_DATA_PROVIDER

#if CONFIG_ENABLE_CHIP_SHELL
chip::LaunchShell();
#endif
Expand All @@ -140,7 +158,5 @@ extern "C" void app_main()
#endif
AppLED.Init();

InitOTARequestor();

chip::DeviceLayer::PlatformMgr().ScheduleWork(InitServer, reinterpret_cast<intptr_t>(nullptr));
}
180 changes: 180 additions & 0 deletions scripts/tools/generate_esp32_chip_factory_bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import sys
import shutil
import logging
import argparse
import subprocess
import cryptography.x509
from types import SimpleNamespace

if os.getenv('IDF_PATH'):
sys.path.insert(0, os.path.join(os.getenv('IDF_PATH'),
'components',
'nvs_flash',
'nvs_partition_generator'))
import nvs_partition_gen
else:
log.error("Please set the IDF_PATH environment variable.")
exit(0)

INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444, 55555555,
66666666, 77777777, 88888888, 99999999, 12345678, 87654321]

TOOLS = {}


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)


def validate_args(args):
# 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)

logging.info('Discriminator:{} Passcode:{}'.format(args.discriminator, args.passcode))


def gen_spake2p_params(passcode):
iter_count_max = 10000
salt_len_max = 32

cmd = [
TOOLS['spake2p'], 'gen-verifier',
'--iteration-count', str(iter_count_max),
'--salt-len', str(salt_len_max),
'--pin-code', str(passcode),
'--out', '-',
]

output = subprocess.check_output(cmd)
output = output.decode('utf-8').splitlines()
return dict(zip(output[0].split(','), output[1].split(',')))


def gen_raw_ec_keypair_from_der(key_file, pubkey_raw_file, privkey_raw_file):
with open(key_file, 'rb') as f:
key_data = f.read()

logging.warning('Leaking of DAC private keys may lead to attestation chain revokation')
logging.warning('Please make sure the DAC private is key protected using a password')

# WARNING: Below line assumes that the DAC private key is not protected by a password,
# please be careful and use the password-protected key if reusing this code
key_der = cryptography.hazmat.primitives.serialization.load_der_private_key(key_data, None)

private_number_val = key_der.private_numbers().private_value
with open(privkey_raw_file, 'wb') as f:
f.write(private_number_val.to_bytes(32, byteorder='big'))

public_key_first_byte = 0x04
public_number_x = key_der.public_key().public_numbers().x
public_number_y = key_der.public_key().public_numbers().y
with open(pubkey_raw_file, '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_nvs_bin(args, spake2p_params):
dac_raw_privkey = 'dac_raw_privkey.bin'
dac_raw_pubkey = 'dac_raw_pubkey.bin'
gen_raw_ec_keypair_from_der(args.dac_key, dac_raw_pubkey, dac_raw_privkey)

csv_content = 'key,type,encoding,value\n'
csv_content += 'chip-factory,namespace,,\n'

csv_content += 'discriminator,data,u32,{}\n'.format(args.discriminator)
csv_content += 'iteration-count,data,u32,{}\n'.format(spake2p_params['Iteration Count'])
csv_content += 'salt,data,string,{}\n'.format(spake2p_params['Salt'])
csv_content += 'verifier,data,string,{}\n'.format(spake2p_params['Verifier'])

csv_content += 'dac-cert,file,binary,{}\n'.format(os.path.abspath(args.dac_cert))
csv_content += 'dac-key,file,binary,{}\n'.format(os.path.abspath(dac_raw_privkey))
csv_content += 'dac-pub-key,file,binary,{}\n'.format(os.path.abspath(dac_raw_pubkey))
csv_content += 'pai-cert,file,binary,{}\n'.format(os.path.abspath(args.pai_cert))
csv_content += 'cert-dclrn,file,binary,{}\n'.format(os.path.abspath(args.cd))

with open('nvs_partition.csv', 'w') as f:
f.write(csv_content)

nvs_args = SimpleNamespace(input='nvs_partition.csv',
output='partition.bin',
size=hex(args.size),
outdir=os.getcwd(),
version=2)

nvs_partition_gen.generate(nvs_args)

os.remove('nvs_partition.csv')
os.remove(dac_raw_privkey)
os.remove(dac_raw_pubkey)


def print_flashing_help():
logging.info('To flash the generated partition.bin, run the following command:')
logging.info('==============================================================')
logging.info('esptool.py -p <port> write_flash <addr> partition.bin')
logging.info('==============================================================')
logging.info('default \"nvs\" partition addr is 0x9000')


def main():
def any_base_int(s): return int(s, 0)

parser = argparse.ArgumentParser(description='Chip Factory NVS binary generator tool')

parser.add_argument('-p', '--passcode', type=any_base_int, required=True,
help='The discriminator for pairing, range: 0x01-0x5F5E0FE')
parser.add_argument('-d', '--discriminator', type=any_base_int, required=True,
help='The passcode for pairing, range: 0x00-0x0FFF')
parser.add_argument('--dac-cert', type=str, required=True,
help='The path to the DAC certificate in der format')
parser.add_argument('--dac-key', type=str, required=True,
help='The path to the DAC private key in der format')
parser.add_argument('--pai-cert', type=str, required=True,
help='The path to the PAI certificate in der format')
parser.add_argument('--cd', type=str, required=True,
help='The path to the certificate declaration der format')
parser.add_argument('-s', '--size', type=any_base_int, required=False, default=0x6000,
help='The size of the partition.bin, default: 0x6000')

args = parser.parse_args()
validate_args(args)
check_tools_exists()
spake2p_params = gen_spake2p_params(args.passcode)
generate_nvs_bin(args, spake2p_params)
print_flashing_help()


if __name__ == "__main__":
logging.basicConfig(format='[%(asctime)s] [%(levelname)7s] - %(message)s', level=logging.INFO)
main()
4 changes: 4 additions & 0 deletions src/platform/ESP32/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ static_library("ESP32") {
"DiagnosticDataProviderImpl.h",
"ESP32Config.cpp",
"ESP32Config.h",
"ESP32FactoryDataProvider.cpp",
"ESP32FactoryDataProvider.h",
"ESP32Utils.cpp",
"ESP32Utils.h",
"KeyValueStoreManagerImpl.cpp",
Expand All @@ -53,6 +55,8 @@ static_library("ESP32") {
"${chip_root}/src/setup_payload",
]

public = [ "${chip_root}/src/credentials/DeviceAttestationCredsProvider.h" ]

public_deps = [
"${chip_root}/src/crypto",
"${chip_root}/src/platform:platform_base",
Expand Down
5 changes: 5 additions & 0 deletions src/platform/ESP32/ESP32Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ const ESP32Config::Key ESP32Config::kConfigKey_SetupDiscriminator = { kConfig
const ESP32Config::Key ESP32Config::kConfigKey_Spake2pIterationCount = { kConfigNamespace_ChipFactory, "iteration-count" };
const ESP32Config::Key ESP32Config::kConfigKey_Spake2pSalt = { kConfigNamespace_ChipFactory, "salt" };
const ESP32Config::Key ESP32Config::kConfigKey_Spake2pVerifier = { kConfigNamespace_ChipFactory, "verifier" };
const ESP32Config::Key ESP32Config::kConfigKey_DACCert = { kConfigNamespace_ChipFactory, "dac-cert" };
const ESP32Config::Key ESP32Config::kConfigKey_DACPrivateKey = { kConfigNamespace_ChipFactory, "dac-key" };
const ESP32Config::Key ESP32Config::kConfigKey_DACPublicKey = { kConfigNamespace_ChipFactory, "dac-pub-key" };
const ESP32Config::Key ESP32Config::kConfigKey_PAICert = { kConfigNamespace_ChipFactory, "pai-cert" };
const ESP32Config::Key ESP32Config::kConfigKey_CertDeclaration = { kConfigNamespace_ChipFactory, "cert-dclrn" };

// Keys stored in the chip-config namespace
const ESP32Config::Key ESP32Config::kConfigKey_FabricId = { kConfigNamespace_ChipConfig, "fabric-id" };
Expand Down
Loading

0 comments on commit 1535764

Please sign in to comment.