Skip to content

Commit

Permalink
[crypto] Introduce PSAKeyAllocator
Browse files Browse the repository at this point in the history
- Moved the PSA key definitions from CHIPCryptoPALPSA.h file to
the newly created PSAKeyAllocator.

- The new PSAKeyAllocator class allows for the allocation of keys
in secure storage. Users can create their own PSAKeyAllocator
implementation and set it to be used by the Matter stack.

- If the custom implementation is not provided the default one is
used and it works as the legacy solution and the mechanism is
about stored keys in the PSA ITS storage.

Signed-off-by: Arkadiusz Balys <[email protected]>
  • Loading branch information
ArekBalysNordic committed Jan 31, 2025
1 parent b887e30 commit efcd0cc
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 84 deletions.
1 change: 1 addition & 0 deletions src/crypto/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ if (chip_crypto == "openssl") {
"CHIPCryptoPALPSA.h",
"CHIPCryptoPALmbedTLS.h",
"CHIPCryptoPALmbedTLSCert.cpp",
"PSAKeyAllocator.h",
]
public_deps = [ ":public_headers" ]

Expand Down
82 changes: 1 addition & 81 deletions src/crypto/CHIPCryptoPALPSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,94 +27,14 @@
#pragma once

#include "CHIPCryptoPAL.h"
#include <lib/core/DataModelTypes.h>
#include "PSAKeyAllocator.h"
#include <lib/support/SafePointerCast.h>

#include <psa/crypto.h>

namespace chip {
namespace Crypto {

/**
* @def CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE
*
* @brief
* Base of the PSA key identifier range used by Matter.
*
* Cryptographic keys stored in the PSA Internal Trusted Storage must have
* a user-assigned identifer from the range PSA_KEY_ID_USER_MIN to
* PSA_KEY_ID_USER_MAX. This option allows to override the base used to derive
* key identifiers used by Matter to avoid overlapping with other firmware
* components that also use PSA crypto API. The default value was selected
* not to interfere with OpenThread's default base that is 0x20000.
*
* Note that volatile keys like ephemeral keys used for ECDH have identifiers
* auto-assigned by the PSA backend.
*/
#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

/**
* @def CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END
*
* @brief
* End of the PSA key identifier range used by Matter.
*
* This setting establishes the maximum limit for the key range specific to Matter, in order to
* prevent any overlap with other firmware components that also employ the PSA crypto API.
*/
#ifndef CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END
#define CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END 0x3FFFF
#endif // CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END

static_assert(PSA_KEY_ID_USER_MIN <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE && CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END <= PSA_KEY_ID_USER_MAX,
"Matter specific PSA key range doesn't fit within PSA allowed range");

// Each ICD client requires storing two keys- AES and HMAC
static constexpr uint32_t kMaxICDClientKeys = 2 * CHIP_CONFIG_CRYPTO_PSA_ICD_MAX_CLIENTS;

static_assert(kMaxICDClientKeys >= CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC * CHIP_CONFIG_MAX_FABRICS,
"Number of allocated ICD key slots is lower than maximum number of supported ICD clients");

/**
* @brief Defines subranges of the PSA key identifier space used by Matter.
*/
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
DACPrivKey = Operational + kMaxValidFabricIndex + 1,
ICDKeyRangeStart = DACPrivKey + 1,
Maximum = ICDKeyRangeStart + kMaxICDClientKeys,
};

static_assert(to_underlying(KeyIdBase::Minimum) >= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE &&
to_underlying(KeyIdBase::Maximum) <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END,
"PSA key ID base out of allowed range");

/**
* @brief Finds first free persistent Key slot ID within range.
*
* @param[out] keyId Key ID handler to which free ID will be set.
* @param[in] start Starting ID in search range.
* @param[in] range Search range.
*
* @retval CHIP_NO_ERROR On success.
* @retval CHIP_ERROR_INTERNAL On PSA crypto API error.
* @retval CHIP_ERROR_NOT_FOUND On no free Key ID within range.
* @retval CHIP_ERROR_INVALID_ARGUMENT On search arguments out of PSA allowed range.
*/
CHIP_ERROR FindFreeKeySlotInRange(psa_key_id_t & keyId, psa_key_id_t start, uint32_t range);

/**
* @brief Calculates PSA key ID for Node Operational Certificate private key for the given fabric.
*/
constexpr psa_key_id_t MakeOperationalKeyId(FabricIndex fabricIndex)
{
return to_underlying(KeyIdBase::Operational) + static_cast<psa_key_id_t>(fabricIndex);
}

/**
* @brief Concrete P256 keypair context used by PSA crypto backend.
*/
Expand Down
216 changes: 216 additions & 0 deletions src/crypto/PSAKeyAllocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright (c) 2025 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.
*/

#pragma once

#if CHIP_HAVE_CONFIG_H
#include <crypto/CryptoBuildConfig.h>
#endif // CHIP_HAVE_CONFIG_H

#include <lib/core/DataModelTypes.h>

namespace chip {
namespace Crypto {

static_assert(PSA_KEY_ID_USER_MIN <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE && CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END <= PSA_KEY_ID_USER_MAX,
"Matter specific PSA key range doesn't fit within PSA allowed range");

// Each ICD client requires storing two keys- AES and HMAC
static constexpr uint32_t kMaxICDClientKeys = 2 * CHIP_CONFIG_CRYPTO_PSA_ICD_MAX_CLIENTS;

static_assert(kMaxICDClientKeys >= CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC * CHIP_CONFIG_MAX_FABRICS,
"Number of allocated ICD key slots is lower than maximum number of supported ICD clients");

/**
* @brief Defines subranges of the PSA key identifier space used by Matter.
*/
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
DACPrivKey = Operational + kMaxValidFabricIndex + 1,
ICDKeyRangeStart = DACPrivKey + 1,
Maximum = ICDKeyRangeStart + kMaxICDClientKeys,
};

static_assert(to_underlying(KeyIdBase::Minimum) >= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE &&
to_underlying(KeyIdBase::Maximum) <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END,
"PSA key ID base out of allowed range");

/**
* @brief Finds first free persistent Key slot ID within range.
*
* @param[out] keyId Key ID handler to which free ID will be set.
* @param[in] start Starting ID in search range.
* @param[in] range Search range.
*
* @retval CHIP_NO_ERROR On success.
* @retval CHIP_ERROR_INTERNAL On PSA crypto API error.
* @retval CHIP_ERROR_NOT_FOUND On no free Key ID within range.
* @retval CHIP_ERROR_INVALID_ARGUMENT On search arguments out of PSA allowed range.
*/
CHIP_ERROR FindFreeKeySlotInRange(psa_key_id_t & keyId, psa_key_id_t start, uint32_t range);

/**
* @brief Calculates PSA key ID for Node Operational Certificate private key for the given fabric.
*/
constexpr psa_key_id_t MakeOperationalKeyId(FabricIndex fabricIndex)
{
return to_underlying(KeyIdBase::Operational) + static_cast<psa_key_id_t>(fabricIndex);
}

/**
* @brief Interface for PSA key allocation.
*
* The PSA Key Allocator interface provides an abstraction that allows the application to
* allocate PSA keys in a secure environment. This class uses a concept that isolates the
* application from the actual key material. The secure location may vary depending on the
* cryptographic hardware used. Using this class a platform can implement this interface to
* allocate keys in the specific secure location.
*
* In some cases key attributes must be redefined to match the specific requirements of the
* secure location and the cryptographic hardware.
*
* This class keeps the static instance and uses it in the all the places where the key
* should be persisted. You can add the usage of this class anywhere in the code by calling the
* GetPSAKeyAllocator() function.
*
* If the static instance is not set, the default implementation is used.
*
* To change the static instance of the PSAKeyAllocator, you can call the SetPSAKeyAllocator function.
*/
class PSAKeyAllocator
{
public:
/**
* @brief Destructor for PSAKeyAllocator.
*/
virtual ~PSAKeyAllocator() = default;

/**
* @brief Get the Device Attestation Key (DAC) ID.
*
* @return psa_key_id_t The DAC key ID.
*/
virtual psa_key_id_t GetDacKeyId() = 0;

/**
* @brief Get the Node Operational Certificate key ID for a given fabric index.
*
* @param fabricIndex The fabric index for which the operational key ID is requested.
* @return psa_key_id_t The operational key ID.
*/
virtual psa_key_id_t GetOpKeyId(FabricIndex fabricIndex) = 0;

/**
* @brief Allocate a new Intermittently Connected Devices (ICD) key ID.
*
* This method is used to allocate both AES-CCM and HMAC (SHA-256) keys independently.
* The caller is responsible for storing the key ID in non-volatile memory
* and setting the appropriate key type.
*
* @return psa_key_id_t The newly allocated ICD key ID.
*/
virtual psa_key_id_t AllocateICDKeyId() = 0;

/**
* @brief Update the key attributes before storing the key.
*
* In some cases the key attributes must be redefined to match the specific requirements of the
* secure location and the cryptographic hardware. This method allows the platform to update the
* key attributes before storing the key.
*
* Read the current key attributes to determine the key type, algorithm, and usage flags. Update
* the key attributes as needed.
*
* @param attrs Reference to the key attributes structure to be updated.
*/
virtual void UpdateKeyAttributes(psa_key_attributes_t & attrs) = 0;

// Allow setting and getting the static instance of the PSAKeyAllocator by external functions
friend PSAKeyAllocator & GetPSAKeyAllocator();
friend void SetPSAKeyAllocator(PSAKeyAllocator * keyAllocator);

private:
static PSAKeyAllocator * sInstance;
};

/**
* @brief Default implementation of PSAKeyAllocator.
*
* This default implementation allocates key IDs according to the KeyIdBase enum.
* The operational key ID is calculated as the base operational key ID plus the fabric index.
* The DAC key ID is calculated as the base DAC key ID.
* The ICD key ID is allocated from the range starting from the ICDKeyRangeStart.
* The key attributes are not updated.
*/
class DefaultPSAKeyAllocator : public PSAKeyAllocator
{
public:
// implementations of the PSAKeyAllocator interface
psa_key_id_t GetDacKeyId() override { return to_underlying(KeyIdBase::DACPrivKey); }
psa_key_id_t GetOpKeyId(FabricIndex fabricIndex) override { return MakeOperationalKeyId(fabricIndex); }
psa_key_id_t AllocateICDKeyId() override
{
psa_key_id_t newKeyId = PSA_KEY_ID_NULL;
if (CHIP_NO_ERROR !=
Crypto::FindFreeKeySlotInRange(newKeyId, to_underlying(KeyIdBase::ICDKeyRangeStart), kMaxICDClientKeys))
{
newKeyId = PSA_KEY_ID_NULL;
}
return newKeyId;
}
void UpdateKeyAttributes(psa_key_attributes_t & attrs) override
{
// Do nothing
}
};

/**
* @brief Static function to get the instance of PSAKeyAllocator.
*
* If the static instance is not set, the default implementation is returned.
*
* @return PSAKeyAllocator reference to the instance of PSAKeyAllocator.
*/
inline PSAKeyAllocator & GetPSAKeyAllocator()
{
if (!PSAKeyAllocator::sInstance)
{
static DefaultPSAKeyAllocator defaultAllocator;
return defaultAllocator;
}
return *PSAKeyAllocator::sInstance;
}

/**
* @brief Set the static implementation of the PSAKeyAllocator.
*
* Providing nullptr as an argument will revert to the default implementation.
*
* @param keyAllocator Pointer to the PSAKeyAllocator instance to be set.
*/
inline void SetPSAKeyAllocator(PSAKeyAllocator * keyAllocator)
{
PSAKeyAllocator::sInstance = keyAllocator;
}

// Initialize the PSAKeyAllocator instance
inline PSAKeyAllocator * PSAKeyAllocator::sInstance = nullptr;

} // namespace Crypto
} // namespace chip
5 changes: 4 additions & 1 deletion src/crypto/PSAOperationalKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ namespace Crypto {

PSAOperationalKeystore::PersistentP256Keypair::PersistentP256Keypair(FabricIndex fabricIndex)
{
ToPsaContext(mKeypair).key_id = MakeOperationalKeyId(fabricIndex);
VerifyOrReturn(IsValidFabricIndex(fabricIndex));
ToPsaContext(mKeypair).key_id = GetPSAKeyAllocator().GetOpKeyId(fabricIndex);
mInitialized = true;
}

Expand Down Expand Up @@ -70,6 +71,7 @@ CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Generate()
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_id(&attributes, GetKeyId());
GetPSAKeyAllocator().UpdateKeyAttributes(attributes);

status = psa_generate_key(&attributes, &keyId);
VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL);
Expand Down Expand Up @@ -153,6 +155,7 @@ CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Deserialize(P256Serial
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_id(&attributes, GetKeyId());
GetPSAKeyAllocator().UpdateKeyAttributes(attributes);

status = psa_import_key(&attributes, input.ConstBytes() + mPublicKey.Length(), kP256_PrivateKey_Length, &keyId);
VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL);
Expand Down
9 changes: 7 additions & 2 deletions src/crypto/PSASessionKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class KeyAttributesBase
psa_set_key_algorithm(&mAttrs, algorithm);
psa_set_key_usage_flags(&mAttrs, usageFlags);
psa_set_key_bits(&mAttrs, bits);
GetPSAKeyAllocator().UpdateKeyAttributes(mAttrs);
}

~KeyAttributesBase() { psa_reset_key_attributes(&mAttrs); }
Expand Down Expand Up @@ -189,7 +190,7 @@ void PSASessionKeystore::DestroyKey(HkdfKeyHandle & key)
#if CHIP_CONFIG_ENABLE_ICD_CIP
CHIP_ERROR PSASessionKeystore::PersistICDKey(Symmetric128BitsKeyHandle & key)
{
CHIP_ERROR err;
CHIP_ERROR err = CHIP_NO_ERROR;
psa_key_id_t newKeyId = PSA_KEY_ID_NULL;
psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT;

Expand All @@ -202,9 +203,13 @@ CHIP_ERROR PSASessionKeystore::PersistICDKey(Symmetric128BitsKeyHandle & key)
return CHIP_NO_ERROR;
}

SuccessOrExit(err = Crypto::FindFreeKeySlotInRange(newKeyId, to_underlying(KeyIdBase::ICDKeyRangeStart), kMaxICDClientKeys));
newKeyId = GetPSAKeyAllocator().AllocateICDKeyId();
VerifyOrExit(PSA_KEY_ID_NULL != newKeyId, err = CHIP_ERROR_INTERNAL);

psa_set_key_lifetime(&attrs, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_id(&attrs, newKeyId);
GetPSAKeyAllocator().UpdateKeyAttributes(attrs);

VerifyOrExit(psa_copy_key(key.As<psa_key_id_t>(), &attrs, &newKeyId) == PSA_SUCCESS, err = CHIP_ERROR_INTERNAL);

exit:
Expand Down
Loading

0 comments on commit efcd0cc

Please sign in to comment.