examples: Manufacturing test application

- Reads the DAC and PAI certificate from the secure cert partition and
  dump the relevant details on the console.
- Signs the garbage using DAC key and verifies it with the key parsed
  from DAC.
- Performs DAC->PAI->PAA chain validation
- Reads and print the status of flash encryption and secure boot
This commit is contained in:
Shubham Patil
2023-02-17 17:18:57 +05:30
parent 6c697136fd
commit 4c0a1fc7b8
10 changed files with 435 additions and 0 deletions
+5
View File
@@ -224,6 +224,11 @@ build_esp_matter_examples_non_pytest_idf_v5_1:
IDF_VERSION: "ea5e0ff298e6257b31d8e0c81435e6d3937f04c7"
script:
- *build_external_platform_example
# mfg_test_app needs an secure boot signing key, generating one here
- cd ${ESP_MATTER_PATH}/examples/mfg_test_app
- openssl genrsa -out secure_boot_signing_key.pem 3072
- cd ${ESP_MATTER_PATH}
- rm ./examples/*/dependencies.lock
- pip install -r tools/ci/requirements-build.txt
+6
View File
@@ -65,3 +65,9 @@ examples/all_device_types_app:
- if: IDF_TARGET in ["esp32", "esp32c3", "esp32h2"]
temporary: true
reason: the other targets are not tested yet
examples/mfg_test_app:
enable:
- if: IDF_TARGET in ["esp32c3", "esp32c2", "esp32c6", "esp32h2"]
temporary: true
reason: the other targets are not tested yet
+28
View File
@@ -0,0 +1,28 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
if(NOT DEFINED ENV{ESP_MATTER_PATH})
message(FATAL_ERROR "Please set ESP_MATTER_PATH to the path of esp-matter repo")
endif(NOT DEFINED ENV{ESP_MATTER_PATH})
set(PROJECT_VER "v1.0")
set(PROJECT_VER_NUMBER 1)
set(ESP_MATTER_PATH $ENV{ESP_MATTER_PATH})
set(MATTER_SDK_PATH ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip)
# This should be done before using the IDF_TARGET variable.
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
"${MATTER_SDK_PATH}/config/esp32/components"
"${ESP_MATTER_PATH}/components")
project(mfg_test_app)
idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++14;-Os;-DCHIP_HAVE_CONFIG_H" APPEND)
idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND)
# For RISCV chips, project_include.cmake sets -Wno-format, but does not clear various
# flags that depend on -Wformat
idf_build_set_property(COMPILE_OPTIONS "-Wno-format-nonliteral;-Wno-format-security;-Wformat=0" APPEND)
+38
View File
@@ -0,0 +1,38 @@
## Matter Manufacturing Test Application
The Matter Manufacturing Test Application is a tool used to verify the successful pre-provisioning of a Matter module.
The purpose of this application is to ensure that the module is only running signed firmware and that all necessary
security features are enabled and configured properly.
It performs the tests listed below:
- Reads DAC and PAI certificate from the secure cert partition and dumps few details:
- VID and PID
- Public Key
- Subject and Authority Key Identifier
- Signs the sample dataset with DAC private key (using DAC provider APIs) and verifies the signature using public key in DAC.
- Validates the certificate chain DAC -> PAI -> PAA. This example by default uses the Espressif's
[PAA](main/paa_cert.der).
- Makes sure that all the security bits like flash encryption and secure boot are enabled and they are configured in
*release mode* and corresponding efuses are burned.
### What is expected from the customers opting for Matter pre-provisioning service
If a customer opts for the Matter pre-provisioning service, they must provide a signed firmware binary of this firmware
to test the pre-provisioning of their module.
The secure boot signing key used for signing this firmware must match the key that was used to sign the bootloader
provided for pre-provisioning purposes.
#### Steps to build the firmware
- Copy the key for signing the firmware in the project directory with name `secure_boot_signing_key.pem`.
- Configure Device Vendor Id and Device Product Id using `idf.py menuconfig`,
- Menu path: `(Top) -> Component config -> CHIP Device Layer -> Device Identification Options`
- Config options: `CONFIG_DEVICE_VENDOR_ID` and `CONFIG_DEVICE_PRODUCT_ID`
- Build the firmware and provide the signed application binary (`build/mfg_test_app.bin`) to Espressif.
- Please use esp-idf [v5.1](https://github.com/espressif/esp-idf/tree/v5.1).
@@ -0,0 +1,9 @@
set(PRIV_REQUIRES_LIST esp_matter bootloader_support)
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
EMBED_FILES "paa_cert.der")
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 14)
target_compile_options(${COMPONENT_LIB} PRIVATE "-DCHIP_HAVE_CONFIG_H")
+308
View File
@@ -0,0 +1,308 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <esp_flash_encrypt.h>
#include <esp_log.h>
#include <esp_matter_providers.h>
#include <esp_random.h>
#include <esp_secure_boot.h>
#include <nvs_flash.h>
#include <crypto/CHIPCryptoPAL.h>
#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
#include <credentials/CHIPCert.h>
#include <lib/support/Span.h>
#include <lib/core/CHIPError.h>
#include <lib/support/logging/CHIPLogging.h>
using namespace chip;
using namespace chip::Crypto;
using namespace chip::Credentials;
namespace {
const char *TAG = "MFG-TEST-APP";
uint8_t s_dac_cert_buffer[kMaxDERCertLength]; // 600 bytes
uint8_t s_pai_cert_buffer[kMaxDERCertLength]; // 600 bytes
uint8_t s_paa_cert_buffer[kMaxDERCertLength]; // 600 bytes
extern const uint8_t paa_cert_start[] asm("_binary_paa_cert_der_start");
extern const uint8_t paa_cert_end[] asm("_binary_paa_cert_der_end");
MutableByteSpan paa_span;
MutableByteSpan pai_span;
MutableByteSpan dac_span;
uint8_t s_garbage_buffer[128];
CHIP_ERROR read_certs_in_spans()
{
// DAC Provider implementation
DeviceAttestationCredentialsProvider * dac_provider = GetDeviceAttestationCredentialsProvider();
VerifyOrReturnError(dac_provider, CHIP_ERROR_INTERNAL, ESP_LOGE(TAG, "ERROR: Failed to get the DAC provider impl"));
// Read DAC
dac_span = MutableByteSpan(s_dac_cert_buffer);
CHIP_ERROR err = dac_provider->GetDeviceAttestationCert(dac_span);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to read the DAC, %" CHIP_ERROR_FORMAT, err.Format()));
// Read PAI
pai_span = MutableByteSpan(s_pai_cert_buffer);
err = dac_provider->GetProductAttestationIntermediateCert(pai_span);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to read the PAI Certificate %" CHIP_ERROR_FORMAT, err.Format()));
// Read PAA
uint16_t paa_len = paa_cert_end - paa_cert_start;
memcpy(s_paa_cert_buffer, paa_cert_start, paa_len);
paa_span = MutableByteSpan(s_paa_cert_buffer, paa_len);
return CHIP_NO_ERROR;
}
CHIP_ERROR dump_cert_details(const char *type, ByteSpan cert_span)
{
ESP_LOGI(TAG, "---------- %s ----------", type);
// Get VID, PID from the certificate
AttestationCertVidPid vidpid;
CHIP_ERROR err = ExtractVIDPIDFromX509Cert(cert_span, vidpid);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to extract VID and PID, error: %" CHIP_ERROR_FORMAT, err.Format()));
if (vidpid.mVendorId.HasValue()) {
ESP_LOGI(TAG, "Vendor ID: 0x%04X", vidpid.mVendorId.Value());
assert(CONFIG_DEVICE_VENDOR_ID == vidpid.mVendorId.Value());
} else if (strncmp(type, "PAA", sizeof("PAA")) != 0) {
ESP_LOGE(TAG, "ERROR: Vendor ID: Unspecified");
}
if (vidpid.mProductId.HasValue()) {
ESP_LOGI(TAG, "Product ID: 0x%04X", vidpid.mProductId.Value());
assert(CONFIG_DEVICE_PRODUCT_ID == vidpid.mProductId.Value());
} else if ((strncmp(type, "PAA", sizeof("PAA")) != 0) && (strncmp(type, "PAI", sizeof("PAI")) != 0)) {
ESP_LOGE(TAG, "ERROR: Product ID: Unspecified");
}
// Get Public key from the certificate
P256PublicKey pubkey;
err = ExtractPubkeyFromX509Cert(cert_span, pubkey);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to extract public key, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Print public key
ESP_LOGI(TAG, "Public Key encoded as hex string:");
for (uint8_t i = 0; i < pubkey.Length(); i++) {
printf("%02x", pubkey.ConstBytes()[i]);
}
printf("\n\n");
// Get AKID from the certificate
uint8_t akid_buffer[64];
MutableByteSpan akid_span(akid_buffer);
err = ExtractAKIDFromX509Cert(cert_span, akid_span);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to extract AKID, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Print AKID
ESP_LOGI(TAG, "X509v3 Authority Key Identifier:");
printf("%02x", akid_span.data()[0]);
for (uint8_t i = 1; i < akid_span.size(); i++) {
printf(":%02X", akid_span.data()[i]);
}
printf("\n\n");
// Get SKID from the certificate
uint8_t skid_buffer[64];
MutableByteSpan skid_span(skid_buffer);
err = ExtractSKIDFromX509Cert(cert_span, skid_span);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to extract SKID, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Print SKID
ESP_LOGI(TAG, "X509v3 Subject Key Identifier:");
printf("%02x", skid_span.data()[0]);
for (uint8_t i = 1; i < skid_span.size(); i++) {
printf(":%02X", skid_span.data()[i]);
}
printf("\n\n");
ESP_LOGI(TAG, "------------------------------");
return CHIP_NO_ERROR;
}
CHIP_ERROR test_dac(ByteSpan dac)
{
// DAC Provider implementation
DeviceAttestationCredentialsProvider * dac_provider = GetDeviceAttestationCredentialsProvider();
VerifyOrReturnError(dac_provider, CHIP_ERROR_INTERNAL, ESP_LOGE(TAG, "ERROR: Failed to get the DAC provider impl"));
// Get Public key from the certificate
P256PublicKey pubkey;
CHIP_ERROR err = ExtractPubkeyFromX509Cert(dac, pubkey);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to get DAC public key, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Garbage
esp_fill_random(s_garbage_buffer, sizeof(s_garbage_buffer));
ByteSpan mts_span(s_garbage_buffer);
// signature
P256ECDSASignature signature;
MutableByteSpan signature_span{ signature.Bytes(), signature.Capacity() };
// Generate attestation signature
err = dac_provider->SignWithDeviceAttestationKey(mts_span, signature_span);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to sign the message with DAC key, error: %" CHIP_ERROR_FORMAT, err.Format()));
ESP_LOGI(TAG, "Message signed with DAC key: OK");
ESP_LOG_BUFFER_HEX(TAG, signature_span.data(), signature_span.size());
P256ECDSASignature signature_to_verify;
ReturnErrorOnFailure(signature_to_verify.SetLength(signature_span.size()));
memcpy(signature_to_verify.Bytes(), signature_span.data(), signature_span.size());
err = pubkey.ECDSA_validate_msg_signature(s_garbage_buffer, sizeof(s_garbage_buffer), signature_to_verify);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, ESP_LOGE(TAG, "ERROR: Failed to validate signature, error: %" CHIP_ERROR_FORMAT, err.Format()));
ESP_LOGI(TAG, "Signature Verification: OK");
return CHIP_NO_ERROR;
}
AttestationVerificationResult MapError(CertificateChainValidationResult certificateChainValidationResult)
{
switch (certificateChainValidationResult)
{
case CertificateChainValidationResult::kRootFormatInvalid:
return AttestationVerificationResult::kPaaFormatInvalid;
case CertificateChainValidationResult::kRootArgumentInvalid:
return AttestationVerificationResult::kPaaArgumentInvalid;
case CertificateChainValidationResult::kICAFormatInvalid:
return AttestationVerificationResult::kPaiFormatInvalid;
case CertificateChainValidationResult::kICAArgumentInvalid:
return AttestationVerificationResult::kPaiArgumentInvalid;
case CertificateChainValidationResult::kLeafFormatInvalid:
return AttestationVerificationResult::kDacFormatInvalid;
case CertificateChainValidationResult::kLeafArgumentInvalid:
return AttestationVerificationResult::kDacArgumentInvalid;
case CertificateChainValidationResult::kChainInvalid:
return AttestationVerificationResult::kDacSignatureInvalid;
case CertificateChainValidationResult::kNoMemory:
return AttestationVerificationResult::kNoMemory;
case CertificateChainValidationResult::kInternalFrameworkError:
return AttestationVerificationResult::kInternalError;
default:
return AttestationVerificationResult::kInternalError;
}
}
bool test_cert_chain(ByteSpan paa, ByteSpan pai, ByteSpan dac)
{
AttestationVerificationResult err = AttestationVerificationResult::kSuccess;
// Validate Proper Certificate Format
VerifyOrExit(VerifyAttestationCertificateFormat(paa, AttestationCertType::kPAA) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kPaaFormatInvalid);
VerifyOrExit(VerifyAttestationCertificateFormat(pai, AttestationCertType::kPAI) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kPaiFormatInvalid);
VerifyOrExit(VerifyAttestationCertificateFormat(dac, AttestationCertType::kDAC) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kDacFormatInvalid);
// Verify that VID and PID in the certificates match.
{
AttestationCertVidPid dacVidPid;
AttestationCertVidPid paiVidPid;
AttestationCertVidPid paaVidPid;
VerifyOrExit(ExtractVIDPIDFromX509Cert(dac, dacVidPid) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kDacFormatInvalid);
VerifyOrExit(ExtractVIDPIDFromX509Cert(pai, paiVidPid) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kPaiFormatInvalid);
VerifyOrExit(ExtractVIDPIDFromX509Cert(paa, paaVidPid) == CHIP_NO_ERROR,
err = AttestationVerificationResult::kPaaFormatInvalid);
VerifyOrExit(dacVidPid.mVendorId.HasValue() && dacVidPid.mVendorId == paiVidPid.mVendorId,
err = AttestationVerificationResult::kDacVendorIdMismatch);
if (paaVidPid.mVendorId.HasValue())
{
VerifyOrExit(dacVidPid.mVendorId == paaVidPid.mVendorId,
err = AttestationVerificationResult::kPaiVendorIdMismatch);
}
if (paiVidPid.mProductId.HasValue())
{
VerifyOrExit(dacVidPid.mProductId == paiVidPid.mProductId,
err = AttestationVerificationResult::kDacProductIdMismatch);
}
VerifyOrExit(!paaVidPid.mProductId.HasValue(), err = AttestationVerificationResult::kPaaFormatInvalid);
}
// Validate certificate chain.
CertificateChainValidationResult chainValidationResult;
VerifyOrExit(ValidateCertificateChain(paa.data(), paa.size(), pai.data(), pai.size(), dac.data(), dac.size(),
chainValidationResult) == CHIP_NO_ERROR,
err = MapError(chainValidationResult));
exit:
if (err != AttestationVerificationResult::kSuccess) {
ESP_LOGE(TAG, "ERROR: Certificates Chain Validation Failed with Error Code: %d\n", static_cast<int>(err));
ESP_LOGE(TAG, "Find the error enum here:https://github.com/project-chip/connectedhomeip/blob/c5216d1/src/credentials/attestation_verifier/DeviceAttestationVerifier.h#L30");
return false;
} else {
ESP_LOGI(TAG, "DAC->PAI->PAA Certificate chain validation: OK");
}
return true;
}
void test_security_bits()
{
if (esp_flash_encryption_cfg_verify_release_mode()) {
ESP_LOGI(TAG, "Flash encryption in release mode: OK");
}
if (esp_secure_boot_cfg_verify_release_mode()) {
ESP_LOGI(TAG, "Secure Boot in release mode: OK");
}
}
}
extern "C" void app_main()
{
// Setup providers based on configuration options
esp_matter::setup_providers();
// read PAA, PAI, and DAC in spans
CHIP_ERROR err = read_certs_in_spans();
VerifyOrReturn(err == CHIP_NO_ERROR, ESP_LOGE(TAG, "ERROR: Reading certificates failed, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Dump PAI details
err = dump_cert_details("PAI", pai_span);
VerifyOrReturn(err == CHIP_NO_ERROR, ESP_LOGE(TAG, "ERROR: Failed to dump PAI certificate details, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Dump DAC details
err = dump_cert_details("DAC", dac_span);
VerifyOrReturn(err == CHIP_NO_ERROR, ESP_LOGE(TAG, "ERROR: Failed to dump DAC certificate details, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Sign the message with DAC key and verify with public key in DAC certificate
err = test_dac(dac_span);
VerifyOrReturn(err == CHIP_NO_ERROR, ESP_LOGE(TAG, "ERROR: Failed to Sign and Verify using DAC keypair, error: %" CHIP_ERROR_FORMAT, err.Format()));
// Test DAC -> PAI -> PAA chain validation
bool status = test_cert_chain(paa_span, pai_span, dac_span);
VerifyOrReturn(status, ESP_LOGE(TAG, "ERROR: Failed to validate attestation cert chain (DAC -> PAI -> PAA)"));
test_security_bits();
}
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
esp_secure_cert, 0x3F, ,0xd000, 0x2000, encrypted
nvs, data, nvs, 0x10000, 0xC000,
nvs_keys, data, nvs_keys,, 0x1000, encrypted
phy_init, data, phy, , 0x1000,
ota_0, app, ota_0, 0x20000, 0x100000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
3 esp_secure_cert, 0x3F, ,0xd000, 0x2000, encrypted
4 nvs, data, nvs, 0x10000, 0xC000,
5 nvs_keys, data, nvs_keys,, 0x1000, encrypted
6 phy_init, data, phy, , 0x1000,
7 ota_0, app, ota_0, 0x20000, 0x100000,
+32
View File
@@ -0,0 +1,32 @@
# Default to 921600 baud when flashing and monitoring device
CONFIG_ESPTOOLPY_BAUD_921600B=y
CONFIG_ESPTOOLPY_BAUD=921600
CONFIG_ESPTOOLPY_COMPRESSED=y
CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y
CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
#enable lwip ipv6 autoconfig
CONFIG_LWIP_IPV6_AUTOCONFIG=y
# Use a custom partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0xC000
#enable lwIP route hooks
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
# Disable DS Peripheral
CONFIG_ESP_SECURE_CERT_DS_PERIPHERAL=n
# Read attestation data from secure cert partition
CONFIG_SEC_CERT_DAC_PROVIDER=y
# Enable flash encryption and secure boot in release mode
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_SIGNING_KEY="secure_boot_signing_key.pem"
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
CONFIG_DEVICE_VENDOR_ID=0x131B
@@ -0,0 +1,2 @@
# Enable DS Peripheral
CONFIG_ESP_SECURE_CERT_DS_PERIPHERAL=y