From 7bed931ba144b36cc9e62618ab50d75e36822e0a Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Wed, 20 Dec 2023 16:19:07 +0100 Subject: [PATCH] [nrf noup] Move DAC priv key from Factory Data to PSA ITS 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'. --- config/nrfconnect/chip-module/Kconfig | 17 +- src/crypto/CHIPCryptoPALPSA.h | 3 +- src/platform/nrfconnect/CHIPPlatformConfig.h | 4 + src/platform/nrfconnect/FactoryDataParser.c | 11 +- src/platform/nrfconnect/FactoryDataParser.h | 1 + .../nrfconnect/FactoryDataProvider.cpp | 149 ++++++++++++++++-- src/platform/nrfconnect/FactoryDataProvider.h | 10 ++ 7 files changed, 173 insertions(+), 22 deletions(-) diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index 6afd11cca2..b664bfa867 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -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 @@ -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" @@ -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 diff --git a/src/crypto/CHIPCryptoPALPSA.h b/src/crypto/CHIPCryptoPALPSA.h index 1a64c1f879..0f71d5e835 100644 --- a/src/crypto/CHIPCryptoPALPSA.h +++ b/src/crypto/CHIPCryptoPALPSA.h @@ -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, diff --git a/src/platform/nrfconnect/CHIPPlatformConfig.h b/src/platform/nrfconnect/CHIPPlatformConfig.h index c8d67115f9..c1d8a3e665 100644 --- a/src/platform/nrfconnect/CHIPPlatformConfig.h +++ b/src/platform/nrfconnect/CHIPPlatformConfig.h @@ -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 diff --git a/src/platform/nrfconnect/FactoryDataParser.c b/src/platform/nrfconnect/FactoryDataParser.c index 610c78ab3e..29b8be9a95 100644 --- a/src/platform/nrfconnect/FactoryDataParser.c +++ b/src/platform/nrfconnect/FactoryDataParser.c @@ -17,12 +17,13 @@ #include "FactoryDataParser.h" +#include #include #include #include -#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) { @@ -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); @@ -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) { diff --git a/src/platform/nrfconnect/FactoryDataParser.h b/src/platform/nrfconnect/FactoryDataParser.h index 54be7b80b8..4fe5f2aa4a 100644 --- a/src/platform/nrfconnect/FactoryDataParser.h +++ b/src/platform/nrfconnect/FactoryDataParser.h @@ -66,6 +66,7 @@ struct FactoryData bool discriminatorPresent; bool productFinishPresent; bool primaryColorPresent; + size_t dacPrivateKeyOffset; }; /** diff --git a/src/platform/nrfconnect/FactoryDataProvider.cpp b/src/platform/nrfconnect/FactoryDataProvider.cpp index caa1ad434f..11bb33fc1a 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.cpp +++ b/src/platform/nrfconnect/FactoryDataProvider.cpp @@ -24,7 +24,15 @@ #include #endif -#include +#include + +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#include +#include + +static const struct device * const kFlashDev = DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_flash_controller)); +#endif namespace chip { namespace { @@ -59,7 +67,33 @@ CHIP_ERROR FactoryDataProvider::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(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) @@ -73,30 +107,104 @@ CHIP_ERROR FactoryDataProvider::Init() return error; } - error = mFlashFactoryData.GetFactoryDataPartition(factoryData, factoryDataSize); + return error; +} - if (error != CHIP_NO_ERROR) +#ifdef CONFIG_CHIP_CRYPTO_PSA +template +CHIP_ERROR FactoryDataProvider::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(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(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 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(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 CHIP_ERROR FactoryDataProvider::GetCertificationDeclaration(MutableByteSpan & outBuffer) @@ -160,8 +268,18 @@ CHIP_ERROR FactoryDataProvider::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(mFactoryData.dac_cert.data), mFactoryData.dac_cert.len }; chip::Crypto::P256PublicKey dacPublicKey; @@ -171,6 +289,7 @@ CHIP_ERROR FactoryDataProvider::SignWithDeviceAttestationKey(c LoadKeypairFromRaw(ByteSpan(reinterpret_cast(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); } diff --git a/src/platform/nrfconnect/FactoryDataProvider.h b/src/platform/nrfconnect/FactoryDataProvider.h index 15dae3ab7e..576a49b177 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.h +++ b/src/platform/nrfconnect/FactoryDataProvider.h @@ -21,6 +21,10 @@ #include #include +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#endif + #include #include #include @@ -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; @@ -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