Skip to content

Commit 06edeee

Browse files
[crypto] Introduce PSAKeyAllocator (#37332)
* [crypto] Introduce PSAKeyAllocator - 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 <arkadiusz.balys@nordicsemi.no> * [crypto] Added a unit test for PSAKeyAllocator The unit test: - Verifies the defaultKeyAllocator instance, key allocation and if attributes are ont changed. - Creates a new testing key allocator and verifies if it works as expected and differently than the default ones. - Switches back to the defaultKeyAllocator instance and checks if code works properly. Signed-off-by: Arkadiusz Balys <arkadiusz.balys@nordicsemi.no> --------- Signed-off-by: Arkadiusz Balys <arkadiusz.balys@nordicsemi.no>
1 parent 48adf65 commit 06edeee

7 files changed

+371
-84
lines changed

src/crypto/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ if (chip_crypto == "openssl") {
141141
"CHIPCryptoPALPSA.h",
142142
"CHIPCryptoPALmbedTLS.h",
143143
"CHIPCryptoPALmbedTLSCert.cpp",
144+
"PSAKeyAllocator.h",
144145
]
145146
public_deps = [ ":public_headers" ]
146147

src/crypto/CHIPCryptoPALPSA.h

+1-81
Original file line numberDiff line numberDiff line change
@@ -27,94 +27,14 @@
2727
#pragma once
2828

2929
#include "CHIPCryptoPAL.h"
30-
#include <lib/core/DataModelTypes.h>
30+
#include "PSAKeyAllocator.h"
3131
#include <lib/support/SafePointerCast.h>
3232

3333
#include <psa/crypto.h>
3434

3535
namespace chip {
3636
namespace Crypto {
3737

38-
/**
39-
* @def CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE
40-
*
41-
* @brief
42-
* Base of the PSA key identifier range used by Matter.
43-
*
44-
* Cryptographic keys stored in the PSA Internal Trusted Storage must have
45-
* a user-assigned identifer from the range PSA_KEY_ID_USER_MIN to
46-
* PSA_KEY_ID_USER_MAX. This option allows to override the base used to derive
47-
* key identifiers used by Matter to avoid overlapping with other firmware
48-
* components that also use PSA crypto API. The default value was selected
49-
* not to interfere with OpenThread's default base that is 0x20000.
50-
*
51-
* Note that volatile keys like ephemeral keys used for ECDH have identifiers
52-
* auto-assigned by the PSA backend.
53-
*/
54-
#ifndef CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE
55-
#define CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE 0x30000
56-
#endif // CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE
57-
58-
/**
59-
* @def CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END
60-
*
61-
* @brief
62-
* End of the PSA key identifier range used by Matter.
63-
*
64-
* This setting establishes the maximum limit for the key range specific to Matter, in order to
65-
* prevent any overlap with other firmware components that also employ the PSA crypto API.
66-
*/
67-
#ifndef CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END
68-
#define CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END 0x3FFFF
69-
#endif // CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END
70-
71-
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,
72-
"Matter specific PSA key range doesn't fit within PSA allowed range");
73-
74-
// Each ICD client requires storing two keys- AES and HMAC
75-
static constexpr uint32_t kMaxICDClientKeys = 2 * CHIP_CONFIG_CRYPTO_PSA_ICD_MAX_CLIENTS;
76-
77-
static_assert(kMaxICDClientKeys >= CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC * CHIP_CONFIG_MAX_FABRICS,
78-
"Number of allocated ICD key slots is lower than maximum number of supported ICD clients");
79-
80-
/**
81-
* @brief Defines subranges of the PSA key identifier space used by Matter.
82-
*/
83-
enum class KeyIdBase : psa_key_id_t
84-
{
85-
Minimum = CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE,
86-
Operational = Minimum, ///< Base of the PSA key ID range for Node Operational Certificate private keys
87-
DACPrivKey = Operational + kMaxValidFabricIndex + 1,
88-
ICDKeyRangeStart = DACPrivKey + 1,
89-
Maximum = ICDKeyRangeStart + kMaxICDClientKeys,
90-
};
91-
92-
static_assert(to_underlying(KeyIdBase::Minimum) >= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE &&
93-
to_underlying(KeyIdBase::Maximum) <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END,
94-
"PSA key ID base out of allowed range");
95-
96-
/**
97-
* @brief Finds first free persistent Key slot ID within range.
98-
*
99-
* @param[out] keyId Key ID handler to which free ID will be set.
100-
* @param[in] start Starting ID in search range.
101-
* @param[in] range Search range.
102-
*
103-
* @retval CHIP_NO_ERROR On success.
104-
* @retval CHIP_ERROR_INTERNAL On PSA crypto API error.
105-
* @retval CHIP_ERROR_NOT_FOUND On no free Key ID within range.
106-
* @retval CHIP_ERROR_INVALID_ARGUMENT On search arguments out of PSA allowed range.
107-
*/
108-
CHIP_ERROR FindFreeKeySlotInRange(psa_key_id_t & keyId, psa_key_id_t start, uint32_t range);
109-
110-
/**
111-
* @brief Calculates PSA key ID for Node Operational Certificate private key for the given fabric.
112-
*/
113-
constexpr psa_key_id_t MakeOperationalKeyId(FabricIndex fabricIndex)
114-
{
115-
return to_underlying(KeyIdBase::Operational) + static_cast<psa_key_id_t>(fabricIndex);
116-
}
117-
11838
/**
11939
* @brief Concrete P256 keypair context used by PSA crypto backend.
12040
*/

src/crypto/PSAKeyAllocator.h

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright (c) 2025 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#if CHIP_HAVE_CONFIG_H
21+
#include <crypto/CryptoBuildConfig.h>
22+
#endif // CHIP_HAVE_CONFIG_H
23+
24+
#include <lib/core/CHIPError.h>
25+
#include <lib/core/DataModelTypes.h>
26+
#include <lib/support/TypeTraits.h>
27+
28+
namespace chip {
29+
namespace Crypto {
30+
31+
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,
32+
"Matter specific PSA key range doesn't fit within PSA allowed range");
33+
34+
// Each ICD client requires storing two keys- AES and HMAC
35+
static constexpr uint32_t kMaxICDClientKeys = 2 * CHIP_CONFIG_CRYPTO_PSA_ICD_MAX_CLIENTS;
36+
37+
static_assert(kMaxICDClientKeys >= CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC * CHIP_CONFIG_MAX_FABRICS,
38+
"Number of allocated ICD key slots is lower than maximum number of supported ICD clients");
39+
40+
/**
41+
* @brief Defines subranges of the PSA key identifier space used by Matter.
42+
*/
43+
enum class KeyIdBase : psa_key_id_t
44+
{
45+
Minimum = CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE,
46+
Operational = Minimum, ///< Base of the PSA key ID range for Node Operational Certificate private keys
47+
DACPrivKey = Operational + kMaxValidFabricIndex + 1,
48+
ICDKeyRangeStart = DACPrivKey + 1,
49+
Maximum = ICDKeyRangeStart + kMaxICDClientKeys,
50+
};
51+
52+
static_assert(to_underlying(KeyIdBase::Minimum) >= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE &&
53+
to_underlying(KeyIdBase::Maximum) <= CHIP_CONFIG_CRYPTO_PSA_KEY_ID_END,
54+
"PSA key ID base out of allowed range");
55+
56+
/**
57+
* @brief Finds first free persistent Key slot ID within range.
58+
*
59+
* @param[out] keyId Key ID handler to which free ID will be set.
60+
* @param[in] start Starting ID in search range.
61+
* @param[in] range Search range.
62+
*
63+
* @retval CHIP_NO_ERROR On success.
64+
* @retval CHIP_ERROR_INTERNAL On PSA crypto API error.
65+
* @retval CHIP_ERROR_NOT_FOUND On no free Key ID within range.
66+
* @retval CHIP_ERROR_INVALID_ARGUMENT On search arguments out of PSA allowed range.
67+
*/
68+
CHIP_ERROR FindFreeKeySlotInRange(psa_key_id_t & keyId, psa_key_id_t start, uint32_t range);
69+
70+
/**
71+
* @brief Calculates PSA key ID for Node Operational Certificate private key for the given fabric.
72+
*/
73+
constexpr psa_key_id_t MakeOperationalKeyId(FabricIndex fabricIndex)
74+
{
75+
return to_underlying(KeyIdBase::Operational) + static_cast<psa_key_id_t>(fabricIndex);
76+
}
77+
78+
/**
79+
* @brief Interface for PSA key allocation.
80+
*
81+
* The PSA Key Allocator interface provides an abstraction that allows the application to
82+
* allocate PSA keys in a secure environment. This class uses a concept that isolates the
83+
* application from the actual key material. The secure location may vary depending on the
84+
* cryptographic hardware used. Using this class a platform can implement this interface to
85+
* allocate keys in the specific secure location.
86+
*
87+
* In some cases key attributes must be redefined to match the specific requirements of the
88+
* secure location and the cryptographic hardware.
89+
*
90+
* This class keeps the static instance and uses it in the all the places where the key
91+
* should be persisted. You can add the usage of this class anywhere in the code by calling the
92+
* GetPSAKeyAllocator() function.
93+
*
94+
* If the static instance is not set, the default implementation is used.
95+
*
96+
* To change the static instance of the PSAKeyAllocator, you can call the SetPSAKeyAllocator function.
97+
*/
98+
class PSAKeyAllocator
99+
{
100+
public:
101+
/**
102+
* @brief Destructor for PSAKeyAllocator.
103+
*/
104+
virtual ~PSAKeyAllocator() = default;
105+
106+
/**
107+
* @brief Get the Device Attestation Key (DAC) ID.
108+
*
109+
* @return psa_key_id_t The DAC key ID.
110+
*/
111+
virtual psa_key_id_t GetDacKeyId() = 0;
112+
113+
/**
114+
* @brief Get the Node Operational Certificate key ID for a given fabric index.
115+
*
116+
* @param fabricIndex The fabric index for which the operational key ID is requested.
117+
* @return psa_key_id_t The operational key ID.
118+
*/
119+
virtual psa_key_id_t GetOpKeyId(FabricIndex fabricIndex) = 0;
120+
121+
/**
122+
* @brief Allocate a new Intermittently Connected Devices (ICD) key ID.
123+
*
124+
* This method is used to allocate both AES-CCM and HMAC (SHA-256) keys independently.
125+
* The caller is responsible for storing the key ID in non-volatile memory
126+
* and setting the appropriate key type.
127+
*
128+
* @return psa_key_id_t The newly allocated ICD key ID.
129+
*/
130+
virtual psa_key_id_t AllocateICDKeyId() = 0;
131+
132+
/**
133+
* @brief Update the key attributes before storing the key.
134+
*
135+
* In some cases the key attributes must be redefined to match the specific requirements of the
136+
* secure location and the cryptographic hardware. This method allows the platform to update the
137+
* key attributes before storing the key.
138+
*
139+
* Read the current key attributes to determine the key type, algorithm, and usage flags. Update
140+
* the key attributes as needed.
141+
*
142+
* @param attrs Reference to the key attributes structure to be updated.
143+
*/
144+
virtual void UpdateKeyAttributes(psa_key_attributes_t & attrs) = 0;
145+
146+
// Allow setting and getting the static instance of the PSAKeyAllocator by external functions
147+
friend PSAKeyAllocator & GetPSAKeyAllocator();
148+
friend void SetPSAKeyAllocator(PSAKeyAllocator * keyAllocator);
149+
150+
private:
151+
static PSAKeyAllocator * sInstance;
152+
};
153+
154+
/**
155+
* @brief Default implementation of PSAKeyAllocator.
156+
*
157+
* This default implementation allocates key IDs according to the KeyIdBase enum.
158+
* The operational key ID is calculated as the base operational key ID plus the fabric index.
159+
* The DAC key ID is calculated as the base DAC key ID.
160+
* The ICD key ID is allocated from the range starting from the ICDKeyRangeStart.
161+
* The key attributes are not updated.
162+
*/
163+
class DefaultPSAKeyAllocator : public PSAKeyAllocator
164+
{
165+
public:
166+
// implementations of the PSAKeyAllocator interface
167+
psa_key_id_t GetDacKeyId() override { return to_underlying(KeyIdBase::DACPrivKey); }
168+
psa_key_id_t GetOpKeyId(FabricIndex fabricIndex) override { return MakeOperationalKeyId(fabricIndex); }
169+
psa_key_id_t AllocateICDKeyId() override
170+
{
171+
psa_key_id_t newKeyId = PSA_KEY_ID_NULL;
172+
if (CHIP_NO_ERROR !=
173+
Crypto::FindFreeKeySlotInRange(newKeyId, to_underlying(KeyIdBase::ICDKeyRangeStart), kMaxICDClientKeys))
174+
{
175+
newKeyId = PSA_KEY_ID_NULL;
176+
}
177+
return newKeyId;
178+
}
179+
void UpdateKeyAttributes(psa_key_attributes_t & attrs) override
180+
{
181+
// Do nothing
182+
}
183+
};
184+
185+
/**
186+
* @brief Static function to get the instance of PSAKeyAllocator.
187+
*
188+
* If the static instance is not set, the default implementation is returned.
189+
*
190+
* @return PSAKeyAllocator reference to the instance of PSAKeyAllocator.
191+
*/
192+
inline PSAKeyAllocator & GetPSAKeyAllocator()
193+
{
194+
if (!PSAKeyAllocator::sInstance)
195+
{
196+
static DefaultPSAKeyAllocator defaultAllocator;
197+
return defaultAllocator;
198+
}
199+
return *PSAKeyAllocator::sInstance;
200+
}
201+
202+
/**
203+
* @brief Set the static implementation of the PSAKeyAllocator.
204+
*
205+
* Providing nullptr as an argument will revert to the default implementation.
206+
*
207+
* @param keyAllocator Pointer to the PSAKeyAllocator instance to be set.
208+
*/
209+
inline void SetPSAKeyAllocator(PSAKeyAllocator * keyAllocator)
210+
{
211+
PSAKeyAllocator::sInstance = keyAllocator;
212+
}
213+
214+
// Initialize the static global PSAKeyAllocator instance
215+
// To avoid the need for an additional source file, we initialize the static instance here using the 'inline' keyword.
216+
// This is possible due to a C++17 feature, which is available starting from the 201606L standard.
217+
// The functionality is verified in the TestPSAOpKeyStore/ TestKeyAllocation test case.
218+
inline PSAKeyAllocator * PSAKeyAllocator::sInstance = nullptr;
219+
220+
} // namespace Crypto
221+
} // namespace chip

src/crypto/PSAOperationalKeystore.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ namespace Crypto {
2727

2828
PSAOperationalKeystore::PersistentP256Keypair::PersistentP256Keypair(FabricIndex fabricIndex)
2929
{
30-
ToPsaContext(mKeypair).key_id = MakeOperationalKeyId(fabricIndex);
30+
VerifyOrReturn(IsValidFabricIndex(fabricIndex));
31+
ToPsaContext(mKeypair).key_id = GetPSAKeyAllocator().GetOpKeyId(fabricIndex);
3132
mInitialized = true;
3233
}
3334

@@ -70,6 +71,7 @@ CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Generate()
7071
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
7172
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
7273
psa_set_key_id(&attributes, GetKeyId());
74+
GetPSAKeyAllocator().UpdateKeyAttributes(attributes);
7375

7476
status = psa_generate_key(&attributes, &keyId);
7577
VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL);
@@ -153,6 +155,7 @@ CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Deserialize(P256Serial
153155
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
154156
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
155157
psa_set_key_id(&attributes, GetKeyId());
158+
GetPSAKeyAllocator().UpdateKeyAttributes(attributes);
156159

157160
status = psa_import_key(&attributes, input.ConstBytes() + mPublicKey.Length(), kP256_PrivateKey_Length, &keyId);
158161
VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL);

src/crypto/PSASessionKeystore.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class KeyAttributesBase
3333
psa_set_key_algorithm(&mAttrs, algorithm);
3434
psa_set_key_usage_flags(&mAttrs, usageFlags);
3535
psa_set_key_bits(&mAttrs, bits);
36+
GetPSAKeyAllocator().UpdateKeyAttributes(mAttrs);
3637
}
3738

3839
~KeyAttributesBase() { psa_reset_key_attributes(&mAttrs); }
@@ -189,7 +190,7 @@ void PSASessionKeystore::DestroyKey(HkdfKeyHandle & key)
189190
#if CHIP_CONFIG_ENABLE_ICD_CIP
190191
CHIP_ERROR PSASessionKeystore::PersistICDKey(Symmetric128BitsKeyHandle & key)
191192
{
192-
CHIP_ERROR err;
193+
CHIP_ERROR err = CHIP_NO_ERROR;
193194
psa_key_id_t newKeyId = PSA_KEY_ID_NULL;
194195
psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT;
195196

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

205-
SuccessOrExit(err = Crypto::FindFreeKeySlotInRange(newKeyId, to_underlying(KeyIdBase::ICDKeyRangeStart), kMaxICDClientKeys));
206+
newKeyId = GetPSAKeyAllocator().AllocateICDKeyId();
207+
VerifyOrExit(PSA_KEY_ID_NULL != newKeyId, err = CHIP_ERROR_INTERNAL);
208+
206209
psa_set_key_lifetime(&attrs, PSA_KEY_LIFETIME_PERSISTENT);
207210
psa_set_key_id(&attrs, newKeyId);
211+
GetPSAKeyAllocator().UpdateKeyAttributes(attrs);
212+
208213
VerifyOrExit(psa_copy_key(key.As<psa_key_id_t>(), &attrs, &newKeyId) == PSA_SUCCESS, err = CHIP_ERROR_INTERNAL);
209214

210215
exit:

0 commit comments

Comments
 (0)