Skip to content

Commit 61724ee

Browse files
[crypto] Extend the OperationalKeystore API to allow migration (project-chip#31279)
- Extended the OperationalKeystore API by mechanism for migration of operational keys stored in one Operational Keystore implementation to another. - Extended the OperationalKeystore API by exporting keypair for Fabric. - Added Unit tests to PersistentStorageOpKeyStore and PSAOpKeystore regarding the new OperationalKeystore for migration and exporting OpKeys. Added first unit tests, created export method
1 parent 560a46b commit 61724ee

7 files changed

+488
-50
lines changed

src/crypto/OperationalKeystore.h

+40
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,46 @@ class OperationalKeystore
119119
*/
120120
virtual CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) = 0;
121121

122+
/**
123+
* @brief Try to read out the permanently committed operational keypair and save it to the buffer.
124+
*
125+
* @param fabricIndex - FabricIndex from which the keypair will be exported
126+
* @param outKeypair - a reference to P256SerializedKeypair object to store the exported key.
127+
* @retval CHIP_ERROR on success.
128+
* @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the key cannot be exported due to security restrictions.
129+
* @retval CHIP_ERROR_NOT_IMPLEMENTED if the exporting is not implemented for the cryptography backend.
130+
* @retval CHIP_ERROR_INVALID_FABRIC_INDEX if provided wrong value of `fabricIndex`.
131+
* @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if there is no keypair found for `fabricIndex`.
132+
* @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outKeyPair` buffer is too small to store the read out keypair.
133+
* @retval other CHIP_ERROR value on internal storage or cryptography backend errors.
134+
*/
135+
virtual CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair)
136+
{
137+
return CHIP_ERROR_NOT_IMPLEMENTED;
138+
};
139+
140+
/**
141+
* @brief Migrate the operational keypair from another Operational keystore (`operationalKeystore`) implementation to this one.
142+
*
143+
* This method assumes that the operational key for given `fabricIndex` exists in the `operationalKeystore` storage or it has
144+
* been already migrated to the new Operational Storage. If a key for the given `fabricIndex` does not exist in any of those
145+
* keystores, then the method retuns CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND.
146+
*
147+
* @param fabricIndex - FabricIndex for which to migrate the operational key
148+
* @param operationalKeystore - a reference to the operationalKeystore implementation that may contain saved operational key
149+
* for Fabric
150+
* @retval CHIP_ERROR on success
151+
* @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the key cannot be exported due to security restrictions.
152+
* @retval CHIP_ERROR_NOT_IMPLEMENTED if the exporting is not implemented for the cryptography backend.
153+
* @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no keypair found for `fabricIndex`.
154+
* @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if there is no keypair found for `fabricIndex`.
155+
* @retval other CHIP_ERROR value on internal storage or crypto engine errors.
156+
*/
157+
virtual CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const
158+
{
159+
return CHIP_ERROR_NOT_IMPLEMENTED;
160+
};
161+
122162
/**
123163
* @brief Permanently remove the keypair associated with a fabric
124164
*

src/crypto/PSAOperationalKeystore.cpp

+90
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
#include "PSAOperationalKeystore.h"
19+
#include "PersistentStorageOperationalKeystore.h"
1920

2021
#include <lib/support/CHIPMem.h>
2122

@@ -135,6 +136,35 @@ CHIP_ERROR PSAOperationalKeystore::NewOpKeypairForFabric(FabricIndex fabricIndex
135136
return CHIP_NO_ERROR;
136137
}
137138

139+
CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Deserialize(P256SerializedKeypair & input)
140+
{
141+
CHIP_ERROR error = CHIP_NO_ERROR;
142+
psa_status_t status = PSA_SUCCESS;
143+
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
144+
psa_key_id_t keyId = 0;
145+
VerifyOrReturnError(input.Length() == mPublicKey.Length() + kP256_PrivateKey_Length, CHIP_ERROR_INVALID_ARGUMENT);
146+
147+
Destroy();
148+
149+
// Type based on ECC with the elliptic curve SECP256r1 -> PSA_ECC_FAMILY_SECP_R1
150+
psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
151+
psa_set_key_bits(&attributes, kP256_PrivateKey_Length * 8);
152+
psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
153+
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
154+
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
155+
psa_set_key_id(&attributes, GetKeyId());
156+
157+
status = psa_import_key(&attributes, input.ConstBytes() + mPublicKey.Length(), kP256_PrivateKey_Length, &keyId);
158+
VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL);
159+
160+
memcpy(mPublicKey.Bytes(), input.ConstBytes(), mPublicKey.Length());
161+
162+
exit:
163+
psa_reset_key_attributes(&attributes);
164+
165+
return error;
166+
}
167+
138168
CHIP_ERROR PSAOperationalKeystore::ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey)
139169
{
140170
VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && mPendingFabricIndex == fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
@@ -154,6 +184,40 @@ CHIP_ERROR PSAOperationalKeystore::CommitOpKeypairForFabric(FabricIndex fabricIn
154184
return CHIP_NO_ERROR;
155185
}
156186

187+
CHIP_ERROR PSAOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair)
188+
{
189+
// Currently exporting the key is forbidden in PSAOperationalKeystore because the PSA_KEY_USAGE_EXPORT usage flag is not set, so
190+
// there is no need to compile the code for the device, but there should be an implementation for test purposes to verify if
191+
// the psa_export_key returns an error.
192+
#if CHIP_CONFIG_TEST
193+
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
194+
VerifyOrReturnError(HasOpKeypairForFabric(fabricIndex), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
195+
196+
size_t outSize = 0;
197+
psa_status_t status =
198+
psa_export_key(PersistentP256Keypair(fabricIndex).GetKeyId(), outKeypair.Bytes(), outKeypair.Capacity(), &outSize);
199+
200+
if (status == PSA_ERROR_BUFFER_TOO_SMALL)
201+
{
202+
return CHIP_ERROR_BUFFER_TOO_SMALL;
203+
}
204+
else if (status == PSA_ERROR_NOT_PERMITTED)
205+
{
206+
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
207+
}
208+
else if (status != PSA_SUCCESS)
209+
{
210+
return CHIP_ERROR_INTERNAL;
211+
}
212+
213+
outKeypair.SetLength(outSize);
214+
215+
return CHIP_NO_ERROR;
216+
#else
217+
return CHIP_ERROR_NOT_IMPLEMENTED;
218+
#endif
219+
}
220+
157221
CHIP_ERROR PSAOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex)
158222
{
159223
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
@@ -209,5 +273,31 @@ void PSAOperationalKeystore::ReleasePendingKeypair()
209273
mIsPendingKeypairActive = false;
210274
}
211275

276+
CHIP_ERROR PSAOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex,
277+
OperationalKeystore & operationalKeystore) const
278+
{
279+
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
280+
281+
P256SerializedKeypair serializedKeypair;
282+
283+
// Do not allow overwriting the existing key and just remove it from the previous Operational Keystore if needed.
284+
if (!HasOpKeypairForFabric(fabricIndex))
285+
{
286+
ReturnErrorOnFailure(operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair));
287+
288+
PersistentP256Keypair keypair(fabricIndex);
289+
ReturnErrorOnFailure(keypair.Deserialize(serializedKeypair));
290+
291+
// Migrated key is not useful anymore, remove it from the previous keystore.
292+
ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex));
293+
}
294+
else if (operationalKeystore.HasOpKeypairForFabric(fabricIndex))
295+
{
296+
ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex));
297+
}
298+
299+
return CHIP_NO_ERROR;
300+
}
301+
212302
} // namespace Crypto
213303
} // namespace chip

src/crypto/PSAOperationalKeystore.h

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <crypto/CHIPCryptoPALPSA.h>
2121
#include <crypto/OperationalKeystore.h>
22+
#include <lib/core/CHIPPersistentStorageDelegate.h>
2223

2324
namespace chip {
2425
namespace Crypto {
@@ -31,7 +32,9 @@ class PSAOperationalKeystore final : public OperationalKeystore
3132
CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override;
3233
CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override;
3334
CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override;
35+
CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override;
3436
CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override;
37+
CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const;
3538
void RevertPendingKeypair() override;
3639
CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message,
3740
Crypto::P256ECDSASignature & outSignature) const override;
@@ -53,13 +56,15 @@ class PSAOperationalKeystore final : public OperationalKeystore
5356
bool Exists() const;
5457
CHIP_ERROR Generate();
5558
CHIP_ERROR Destroy();
59+
CHIP_ERROR Deserialize(P256SerializedKeypair & input);
5660
};
5761

5862
void ReleasePendingKeypair();
5963

6064
PersistentP256Keypair * mPendingKeypair = nullptr;
6165
FabricIndex mPendingFabricIndex = kUndefinedFabricIndex;
6266
bool mIsPendingKeypairActive = false;
67+
PersistentStorageDelegate * mStorage = nullptr;
6368
};
6469

6570
} // namespace Crypto

src/crypto/PersistentStorageOperationalKeystore.cpp

+93-47
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,52 @@ CHIP_ERROR StoreOperationalKey(FabricIndex fabricIndex, PersistentStorageDelegat
8686
return CHIP_NO_ERROR;
8787
}
8888

89+
CHIP_ERROR ExportStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage,
90+
Crypto::P256SerializedKeypair & serializedOpKey)
91+
{
92+
VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
93+
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
94+
95+
// Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit.
96+
Crypto::SensitiveDataBuffer<OpKeyTLVMaxSize()> buf;
97+
98+
// Load up the operational key structure from storage
99+
uint16_t size = static_cast<uint16_t>(buf.Capacity());
100+
ReturnErrorOnFailure(
101+
storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size));
102+
103+
buf.SetLength(static_cast<size_t>(size));
104+
105+
// Read-out the operational key TLV entry.
106+
TLV::ContiguousBufferTLVReader reader;
107+
reader.Init(buf.Bytes(), buf.Length());
108+
109+
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
110+
TLV::TLVType containerType;
111+
ReturnErrorOnFailure(reader.EnterContainer(containerType));
112+
113+
ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag));
114+
uint16_t opKeyVersion;
115+
ReturnErrorOnFailure(reader.Get(opKeyVersion));
116+
VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH);
117+
118+
ReturnErrorOnFailure(reader.Next(kOpKeyDataTag));
119+
{
120+
ByteSpan keyData;
121+
ReturnErrorOnFailure(reader.GetByteView(keyData));
122+
123+
// Unfortunately, we have to copy the data into a P256SerializedKeypair.
124+
VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
125+
126+
ReturnErrorOnFailure(reader.ExitContainer(containerType));
127+
128+
memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size());
129+
serializedOpKey.SetLength(keyData.size());
130+
}
131+
132+
return CHIP_NO_ERROR;
133+
}
134+
89135
/** WARNING: This can leave the operational key on the stack somewhere, since many of the platform
90136
* APIs use stack buffers and do not sanitize! This implementation is for example purposes
91137
* only of the API and it is recommended to avoid directly accessing raw private key bits
@@ -106,56 +152,17 @@ CHIP_ERROR SignWithStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegat
106152
}
107153

108154
// Scope 1: Load up the keypair data from storage
155+
P256SerializedKeypair serializedOpKey;
156+
CHIP_ERROR err = ExportStoredOpKey(fabricIndex, storage, serializedOpKey);
157+
if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err)
109158
{
110-
// Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit.
111-
Crypto::SensitiveDataBuffer<OpKeyTLVMaxSize()> buf;
112-
113-
// Load up the operational key structure from storage
114-
uint16_t size = static_cast<uint16_t>(buf.Capacity());
115-
CHIP_ERROR err =
116-
storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size);
117-
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
118-
{
119-
err = CHIP_ERROR_INVALID_FABRIC_INDEX;
120-
}
121-
ReturnErrorOnFailure(err);
122-
buf.SetLength(static_cast<size_t>(size));
123-
124-
// Read-out the operational key TLV entry.
125-
TLV::ContiguousBufferTLVReader reader;
126-
reader.Init(buf.Bytes(), buf.Length());
127-
128-
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
129-
TLV::TLVType containerType;
130-
ReturnErrorOnFailure(reader.EnterContainer(containerType));
131-
132-
ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag));
133-
uint16_t opKeyVersion;
134-
ReturnErrorOnFailure(reader.Get(opKeyVersion));
135-
VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH);
136-
137-
ReturnErrorOnFailure(reader.Next(kOpKeyDataTag));
138-
{
139-
ByteSpan keyData;
140-
Crypto::P256SerializedKeypair serializedOpKey;
141-
ReturnErrorOnFailure(reader.GetByteView(keyData));
142-
143-
// Unfortunately, we have to copy the data into a P256SerializedKeypair.
144-
VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
145-
146-
// Before doing anything with the key, validate format further.
147-
ReturnErrorOnFailure(reader.ExitContainer(containerType));
148-
ReturnErrorOnFailure(reader.VerifyEndOfContainer());
149-
150-
memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size());
151-
serializedOpKey.SetLength(keyData.size());
152-
153-
// Load-up key material
154-
// WARNING: This makes use of the raw key bits
155-
ReturnErrorOnFailure(transientOperationalKeypair->Deserialize(serializedOpKey));
156-
}
159+
return CHIP_ERROR_INVALID_FABRIC_INDEX;
157160
}
158161

162+
// Load-up key material
163+
// WARNING: This makes use of the raw key bits
164+
ReturnErrorOnFailure(transientOperationalKeypair->Deserialize(serializedOpKey));
165+
159166
// Scope 2: Sign message with the keypair
160167
return transientOperationalKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature);
161168
}
@@ -251,6 +258,13 @@ CHIP_ERROR PersistentStorageOperationalKeystore::CommitOpKeypairForFabric(Fabric
251258
return CHIP_NO_ERROR;
252259
}
253260

261+
CHIP_ERROR PersistentStorageOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex,
262+
Crypto::P256SerializedKeypair & outKeypair)
263+
{
264+
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
265+
return ExportStoredOpKey(fabricIndex, mStorage, outKeypair);
266+
}
267+
254268
CHIP_ERROR PersistentStorageOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex)
255269
{
256270
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
@@ -310,4 +324,36 @@ void PersistentStorageOperationalKeystore::ReleaseEphemeralKeypair(Crypto::P256K
310324
Platform::Delete<Crypto::P256Keypair>(keypair);
311325
}
312326

327+
CHIP_ERROR PersistentStorageOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex,
328+
OperationalKeystore & operationalKeystore) const
329+
{
330+
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
331+
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
332+
333+
P256SerializedKeypair serializedKeypair;
334+
335+
// Do not allow overwriting the existing key and just remove it from the previous Operational Keystore if needed.
336+
if (!HasOpKeypairForFabric(fabricIndex))
337+
{
338+
ReturnErrorOnFailure(operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair));
339+
340+
auto operationalKeypair = Platform::MakeUnique<P256Keypair>();
341+
if (!operationalKeypair)
342+
{
343+
return CHIP_ERROR_NO_MEMORY;
344+
}
345+
346+
ReturnErrorOnFailure(operationalKeypair->Deserialize(serializedKeypair));
347+
ReturnErrorOnFailure(StoreOperationalKey(fabricIndex, mStorage, operationalKeypair.get()));
348+
349+
ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex));
350+
}
351+
else if (operationalKeystore.HasOpKeypairForFabric(fabricIndex))
352+
{
353+
ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex));
354+
}
355+
356+
return CHIP_NO_ERROR;
357+
}
358+
313359
} // namespace chip

src/crypto/PersistentStorageOperationalKeystore.h

+2
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@ class PersistentStorageOperationalKeystore : public Crypto::OperationalKeystore
8181
CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override;
8282
CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override;
8383
CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override;
84+
CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override;
8485
CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override;
8586
void RevertPendingKeypair() override;
8687
CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message,
8788
Crypto::P256ECDSASignature & outSignature) const override;
8889
Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override;
8990
void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) override;
91+
CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const override;
9092

9193
protected:
9294
void ResetPendingKey()

0 commit comments

Comments
 (0)