Skip to content

Commit

Permalink
[nrf noup] Move DAC priv key from Factory Data to PSA ITS
Browse files Browse the repository at this point in the history
We need a mechanism to move the DAC private key from the
factory data set to PSA ITS NVM storage during the first
boot of the device.
Then the DAC private key must be removed from the factory
data set and protected by overwriting.

In this commit:
- Added a method to FactoryDataProvider for moving and
removing DAC from the factory data set.
- Aligned the SignWithDeviceAttestationKey method to work with
stored DAC priv key in ITS NVM instead of raw data.
- Added a Matter config to determine base address of ITS storage
for Matter purposes.
- x509 MBedTLS support seems to be not needed anymore
we can disable it and save ~20kB of FLASH.
- Prevent the DAC private key from removal during the
factory reset - for now, disable
the CHIP_FACTORY_RESET_ERASE_NVS config by default.
- Remove the DAC private key from the factory data set
only if the CONFIG_CHIP_CRYPTO_PSA_MIGRATE_DAC_PRIV_KEY
config is set to 'y'.
  • Loading branch information
ArekBalysNordic committed Jan 4, 2024
1 parent 5f7a9f6 commit 50a62b4
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 22 deletions.
17 changes: 13 additions & 4 deletions config/nrfconnect/chip-module/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,6 @@ config CHIP_FACTORY_DATA
bool "Factory data provider"
select ZCBOR
imply FPROTECT
imply MBEDTLS_X509_LIBRARY if CHIP_CRYPTO_PSA
imply MBEDTLS_X509_CRT_PARSE_C if CHIP_CRYPTO_PSA
imply MBEDTLS_PK_PARSE_C if CHIP_CRYPTO_PSA
imply MBEDTLS_TLS_LIBRARY if CHIP_CRYPTO_PSA
help
Enables the default nRF Connect factory data provider implementation that
supports reading the factory data encoded in the CBOR format from the
Expand Down Expand Up @@ -250,6 +246,7 @@ endif # CHIP_FACTORY_DATA_BUILD
config CHIP_FACTORY_RESET_ERASE_NVS
bool
default y
depends on !CHIP_CRYPTO_PSA_MIGRATE_DAC_PRIV_KEY

config CHIP_LOG_SIZE_OPTIMIZATION
bool "Disable some detailed logs to decrease flash usage"
Expand Down Expand Up @@ -284,4 +281,16 @@ config CHIP_ENABLE_READ_CLIENT
This config can be disabled for device types that do not require Read Client functionality.
Disabling this config can save flash and RAM space.

config CHIP_CRYPTO_PSA_MIGRATE_DAC_PRIV_KEY
bool "Migrate DAC private key from factory data to PSA ITS"
depends on CHIP_CRYPTO_PSA
depends on CHIP_FACTORY_DATA
select EXPERIMENTAL
help
Move DAC private key from the factory data set to the PSA ITS secure storage
and remove it. After the first boot of the device the DAC private key will be moved
to the PSA ITS secure storage and will not be available in the factory data anymore.
It will be overwritten in the factory data set by zeros.


endif # CHIP
3 changes: 2 additions & 1 deletion src/crypto/CHIPCryptoPALPSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ enum class KeyIdBase : psa_key_id_t
{
Minimum = CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE,
Operational = Minimum, ///< Base of the PSA key ID range for Node Operational Certificate private keys
Maximum = Operational + kMaxValidFabricIndex,
DACPrivKey = Operational + kMaxValidFabricIndex + 1,
Maximum = DACPrivKey,
};

static_assert(to_underlying(KeyIdBase::Minimum) >= PSA_KEY_ID_USER_MIN && to_underlying(KeyIdBase::Maximum) <= PSA_KEY_ID_USER_MAX,
Expand Down
4 changes: 4 additions & 0 deletions src/platform/nrfconnect/CHIPPlatformConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
#endif // CHIP_CONFIG_SHA256_CONTEXT_ALIGN
#endif // CONFIG_CHIP_CRYPTO_PSA

#ifndef CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE
#define CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE 0x30000
#endif // CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE

// ==================== General Configuration Overrides ====================

#ifndef CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS
Expand Down
11 changes: 9 additions & 2 deletions src/platform/nrfconnect/FactoryDataParser.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@

#include "FactoryDataParser.h"

#include <zcbor_common.h>
#include <zcbor_decode.h>

#include <ctype.h>
#include <string.h>

#define MAX_FACTORY_DATA_NESTING_LEVEL 4
#define MAX_FACTORY_DATA_NESTING_LEVEL 2

static inline bool uint16_decode(zcbor_state_t * states, uint16_t * value)
{
Expand Down Expand Up @@ -123,6 +124,11 @@ bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, voi

bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData)
{
if (!buffer || !factoryData || bufferSize == 0)
{
return false;
}

memset(factoryData, 0, sizeof(*factoryData));
ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL, buffer, bufferSize, 1);

Expand Down Expand Up @@ -209,7 +215,8 @@ bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData
}
else if (strncmp("dac_key", (const char *) currentString.value, currentString.len) == 0)
{
res = res && zcbor_bstr_decode(states, (struct zcbor_string *) &factoryData->dac_priv_key);
res = res && zcbor_bstr_decode(states, (struct zcbor_string *) &factoryData->dac_priv_key);
factoryData->dacPrivateKeyOffset = (size_t) ((uint8_t *) factoryData->dac_priv_key.data - buffer);
}
else if (strncmp("pai_cert", (const char *) currentString.value, currentString.len) == 0)
{
Expand Down
1 change: 1 addition & 0 deletions src/platform/nrfconnect/FactoryDataParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ struct FactoryData
bool discriminatorPresent;
bool productFinishPresent;
bool primaryColorPresent;
size_t dacPrivateKeyOffset;
};

/**
Expand Down
149 changes: 134 additions & 15 deletions src/platform/nrfconnect/FactoryDataProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@
#include <platform/Zephyr/ZephyrConfig.h>
#endif

#include <zephyr/logging/log.h>
#include <lib/support/logging/CHIPLogging.h>

#ifdef CONFIG_CHIP_CRYPTO_PSA
#include <lib/support/ScopedBuffer.h>
#include <psa/crypto.h>
#include <zephyr/drivers/flash.h>

static const struct device * const kFlashDev = DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_flash_controller));
#endif

namespace chip {
namespace {
Expand Down Expand Up @@ -59,7 +67,33 @@ CHIP_ERROR FactoryDataProvider<FlashFactoryData>::Init()
uint8_t * factoryData = nullptr;
size_t factoryDataSize;

CHIP_ERROR error = mFlashFactoryData.ProtectFactoryDataPartitionAgainstWrite();
CHIP_ERROR error = mFlashFactoryData.GetFactoryDataPartition(factoryData, factoryDataSize);

if (error != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Failed to read factory data partition");
return error;
}

if (!ParseFactoryData(factoryData, static_cast<uint16_t>(factoryDataSize), &mFactoryData))
{
ChipLogError(DeviceLayer, "Failed to parse factory data");
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}

// Check if factory data version is correct
if (mFactoryData.version != CONFIG_CHIP_FACTORY_DATA_VERSION)
{
ChipLogError(DeviceLayer, "Factory data version mismatch. Flash version: %d vs code version: %d", mFactoryData.version,
CONFIG_CHIP_FACTORY_DATA_VERSION);
return CHIP_ERROR_VERSION_MISMATCH;
}

#ifdef CONFIG_CHIP_CRYPTO_PSA
VerifyOrDie(MoveDACPrivateKeyToSecureStorage(factoryData, factoryDataSize) == CHIP_NO_ERROR);
#endif

error = mFlashFactoryData.ProtectFactoryDataPartitionAgainstWrite();

// Protection against write for external storage is not supported.
if (error == CHIP_ERROR_NOT_IMPLEMENTED)
Expand All @@ -73,30 +107,104 @@ CHIP_ERROR FactoryDataProvider<FlashFactoryData>::Init()
return error;
}

error = mFlashFactoryData.GetFactoryDataPartition(factoryData, factoryDataSize);
return error;
}

if (error != CHIP_NO_ERROR)
#ifdef CONFIG_CHIP_CRYPTO_PSA
template <class FlashFactoryData>
CHIP_ERROR FactoryDataProvider<FlashFactoryData>::MoveDACPrivateKeyToSecureStorage(uint8_t * factoryDataPartition,
size_t factoryDataSize)
{

if (!factoryDataPartition || factoryDataSize == 0)
{
ChipLogError(DeviceLayer, "Failed to read factory data partition");
return error;
return CHIP_ERROR_INVALID_ARGUMENT;
}

if (!ParseFactoryData(factoryData, static_cast<uint16_t>(factoryDataSize), &mFactoryData))
if (mFactoryData.dac_priv_key.len != kDACPrivateKeyLength)
{
ChipLogError(DeviceLayer, "Failed to parse factory data");
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
return CHIP_ERROR_INVALID_LIST_LENGTH;
}

// Check if factory data version is correct
if (mFactoryData.version != CONFIG_CHIP_FACTORY_DATA_VERSION)
uint8_t clearedDACPrivKey[kDACPrivateKeyLength];
memset(clearedDACPrivKey, 0x00, sizeof(clearedDACPrivKey));

// Check if factory data contains DAC private key
if (memcmp(mFactoryData.dac_priv_key.data, clearedDACPrivKey, kDACPrivateKeyLength) != 0)
{
ChipLogError(DeviceLayer, "Factory data version mismatch. Flash version: %d vs code version: %d", mFactoryData.version,
CONFIG_CHIP_FACTORY_DATA_VERSION);
return CHIP_ERROR_VERSION_MISMATCH;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
// If there is the new DAC private key present in the factory data set and also there is
// the existing one in ITS NVM storage, then skip saving it again.
if (psa_get_key_attributes(mDACPrivKeyId, &attributes) != PSA_SUCCESS)
{
ChipLogProgress(DeviceLayer, "Found DAC Private Key in factory data set. Copying to secure storage...");

// Remove the key if any exists and can be corrupted.
psa_destroy_key(mDACPrivKeyId);

psa_reset_key_attributes(&attributes);
psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&attributes, kDACPrivateKeyLength * 8);
psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
#ifdef CONFIG_CHIP_CRYPTO_PSA_MIGRATE_DAC_PRIV_KEY
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_id(&attributes, mDACPrivKeyId);
#else
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE);
#endif
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);

VerifyOrReturnError(psa_import_key(&attributes, reinterpret_cast<uint8_t *>(mFactoryData.dac_priv_key.data),
kDACPrivateKeyLength, &mDACPrivKeyId) == PSA_SUCCESS,
CHIP_ERROR_INTERNAL);
}

#ifdef CONFIG_CHIP_CRYPTO_PSA_MIGRATE_DAC_PRIV_KEY
// Check once again if the saved key has attributes set before removing it from the factory data set.
VerifyOrReturnError(psa_get_key_attributes(mDACPrivKeyId, &attributes) == PSA_SUCCESS, CHIP_ERROR_INTERNAL);

// Get the actual block size.
const flash_parameters * flashParameters = flash_get_parameters(kFlashDev);
VerifyOrReturnError(flashParameters, CHIP_ERROR_INTERNAL);

// To write zeros directly to the Flash memory without erasing whole page the start address must be aligned to the
// write_block_size value (alignedDacPrivKeyOffset), then we need align the required buffer size to the write_block_size as
// well (requiredFlashSpaceSize) to meet Flash write requirements, and we need to calculate how many bytes of the factory
// data set must be copied to the additional buffer space created after the alignments (bytesToLeftBefore, and
// bytesToLeftAfter)
size_t alignedDacPrivKeyOffset = ROUND_DOWN(mFactoryData.dacPrivateKeyOffset, flashParameters->write_block_size);
size_t bytesToLeftBefore = mFactoryData.dacPrivateKeyOffset % flashParameters->write_block_size;
size_t requiredFlashSpaceSize = ROUND_UP(kDACPrivateKeyLength + bytesToLeftBefore, flashParameters->write_block_size);
size_t bytesToLeftAfter = requiredFlashSpaceSize - bytesToLeftBefore - kDACPrivateKeyLength;

// Allocate the memory buffer for removing DAC private key.
chip::Platform::ScopedMemoryBuffer<uint8_t> removedPrivKeyBuffer;
VerifyOrReturnError(removedPrivKeyBuffer.Calloc(requiredFlashSpaceSize), CHIP_ERROR_NO_MEMORY);

// Copy the existing parts of the aligned memory space to before and after the DAC private key space.
memcpy(removedPrivKeyBuffer.Get(), factoryDataPartition + alignedDacPrivKeyOffset, bytesToLeftBefore);
memcpy(removedPrivKeyBuffer.Get() + bytesToLeftBefore + kDACPrivateKeyLength,
factoryDataPartition + mFactoryData.dacPrivateKeyOffset + kDACPrivateKeyLength, bytesToLeftAfter);

// Write aligned buffer directly to the Flash without erasing.
VerifyOrReturnError(0 ==
flash_write(kFlashDev, kFactoryDataPartitionAddress + alignedDacPrivKeyOffset,
removedPrivKeyBuffer.Get(), requiredFlashSpaceSize),
CHIP_ERROR_INTERNAL);

// Parse the factory data again and verify if the procedure finished successfully
VerifyOrReturnError(ParseFactoryData(factoryDataPartition, static_cast<uint16_t>(factoryDataSize), &mFactoryData),
CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);

// Verify if the factory data does not contain the DAC private key anymore.
VerifyOrReturnError(memcmp(mFactoryData.dac_priv_key.data, clearedDACPrivKey, kDACPrivateKeyLength) == 0,
CHIP_ERROR_INTERNAL);
#endif
}

return CHIP_NO_ERROR;
}
#endif

template <class FlashFactoryData>
CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetCertificationDeclaration(MutableByteSpan & outBuffer)
Expand Down Expand Up @@ -160,8 +268,18 @@ CHIP_ERROR FactoryDataProvider<FlashFactoryData>::SignWithDeviceAttestationKey(c

VerifyOrReturnError(outSignBuffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
ReturnErrorCodeIf(!mFactoryData.dac_cert.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
ReturnErrorCodeIf(!mFactoryData.dac_priv_key.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);

#ifdef CONFIG_CHIP_CRYPTO_PSA
size_t outputLen = 0;

psa_status_t err = psa_sign_message(mDACPrivKeyId, PSA_ALG_ECDSA(PSA_ALG_SHA_256), messageToSign.data(), messageToSign.size(),
signature.Bytes(), signature.Capacity(), &outputLen);

VerifyOrReturnError(!err, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(outputLen == chip::Crypto::kP256_ECDSA_Signature_Length_Raw, CHIP_ERROR_INTERNAL);
ReturnErrorOnFailure(signature.SetLength(outputLen));
#else
ReturnErrorCodeIf(!mFactoryData.dac_priv_key.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
// Extract public key from DAC cert.
ByteSpan dacCertSpan{ reinterpret_cast<uint8_t *>(mFactoryData.dac_cert.data), mFactoryData.dac_cert.len };
chip::Crypto::P256PublicKey dacPublicKey;
Expand All @@ -171,6 +289,7 @@ CHIP_ERROR FactoryDataProvider<FlashFactoryData>::SignWithDeviceAttestationKey(c
LoadKeypairFromRaw(ByteSpan(reinterpret_cast<uint8_t *>(mFactoryData.dac_priv_key.data), mFactoryData.dac_priv_key.len),
ByteSpan(dacPublicKey.Bytes(), dacPublicKey.Length()), keypair));
ReturnErrorOnFailure(keypair.ECDSA_sign_msg(messageToSign.data(), messageToSign.size(), signature));
#endif

return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, outSignBuffer);
}
Expand Down
10 changes: 10 additions & 0 deletions src/platform/nrfconnect/FactoryDataProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include <platform/CommissionableDataProvider.h>
#include <platform/DeviceInstanceInfoProvider.h>

#ifdef CONFIG_CHIP_CRYPTO_PSA
#include <crypto/CHIPCryptoPALPSA.h>
#endif

#include <fprotect.h>
#include <pm_config.h>
#include <system/SystemError.h>
Expand Down Expand Up @@ -109,6 +113,9 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia
{
public:
CHIP_ERROR Init();
#ifdef CONFIG_CHIP_CRYPTO_PSA
CHIP_ERROR MoveDACPrivateKeyToSecureStorage(uint8_t * factoryDataPartition, size_t factoryDataSize);
#endif

// ===== Members functions that implement the DeviceAttestationCredentialsProvider
CHIP_ERROR GetCertificationDeclaration(MutableByteSpan & outBuffer) override;
Expand Down Expand Up @@ -175,6 +182,9 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia

struct FactoryData mFactoryData;
FlashFactoryData mFlashFactoryData;
#ifdef CONFIG_CHIP_CRYPTO_PSA
psa_key_id_t mDACPrivKeyId = to_underlying(chip::Crypto::KeyIdBase::DACPrivKey);
#endif
};

} // namespace DeviceLayer
Expand Down

0 comments on commit 50a62b4

Please sign in to comment.