From 92cc1641264f6266970d25bccd9b345e43305577 Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Mon, 8 Jan 2024 09:46:11 +0100 Subject: [PATCH] [nrf fromtree] [crypto] Migrate Operational Keys from mbedTLS to PSA ITS - 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 --- src/app/server/Server.cpp | 21 +++ src/app/server/Server.h | 3 +- src/crypto/OperationalKeystore.h | 29 ++++ src/crypto/PSAOperationalKeystore.cpp | 84 ++++++++++ src/crypto/PSAOperationalKeystore.h | 5 + .../PersistentStorageOperationalKeystore.cpp | 145 +++++++++++------ .../PersistentStorageOperationalKeystore.h | 2 + src/crypto/tests/TestPSAOpKeyStore.cpp | 82 +++++++++- .../tests/TestPersistentStorageOpKeyStore.cpp | 146 +++++++++++++++++- src/include/platform/CHIPDeviceEvent.h | 12 ++ .../support/TestPersistentStorageDelegate.h | 1 + 11 files changed, 477 insertions(+), 53 deletions(-) diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index d6d04f3438..20c54be6a6 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -163,6 +163,27 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) SuccessOrExit(err); } +#ifdef CHIP_CRYPTO_PSA + { + PersistentStorageOperationalKeystore persistentStorageOperationalKeystore; + if (CHIP_NO_ERROR == persistentStorageOperationalKeystore.Init(mDeviceStorage)) + { + for (const FabricInfo & fabric : mFabrics) + { + if (CHIP_NO_ERROR != + mOperationalKeystore->MigrateOpKeypairForFabric(fabric.GetFabricIndex(), persistentStorageOperationalKeystore)) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kOperationalKeystoreMigrationFailed; + event.OperationalKeystoreMigrationFailed.fabricIndex = fabric.GetFabricIndex(); + // Post event and ignore the result to not block server Init flow + err = PlatformMgr().PostEvent(&event); + } + } + } + } +#endif + SuccessOrExit(err = mAccessControl.Init(initParams.accessDelegate, sDeviceTypeResolver)); Access::SetAccessControl(mAccessControl); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index b7430d704d..24a92aec36 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -42,9 +42,8 @@ #include #if CHIP_CRYPTO_PSA #include -#else -#include #endif +#include #include #include #include diff --git a/src/crypto/OperationalKeystore.h b/src/crypto/OperationalKeystore.h index 8ecc461f49..deea8b2543 100644 --- a/src/crypto/OperationalKeystore.h +++ b/src/crypto/OperationalKeystore.h @@ -119,6 +119,35 @@ class OperationalKeystore */ virtual CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) = 0; + /** + * @brief Try to read out the permanently commited operational keypair and save it to the buffer. + * + * @param fabricIndex - FabricIndex from which the keypair will be exported + * @param outKeypair - a reference to P256SerializedKeypair object to store the exported key. + * @retval CHIP_ERROR on success. + * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the key cannot be exported due to security restrictions. + * @retval CHIP_ERROR_NOT_IMPLEMENTED if the exporting is not implemented for the crypto engine. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no keypair found for `fabricIndex`. + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `keyPair` buffer is too small to store the read out keypair. + * @retval other CHIP_ERROR value on internal storage or crypto engine errors. + */ + virtual CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) = 0; + + /** + * @brief Migrate the operational keypair from the extern Operational keystore to this one. + * + * @param fabricIndex - FabricIndex for which to migrate the operational key + * @param operationalKeystore - a reference to the operationalKeystore implementation that may contain saved operational key + * for Fabric + * @retval CHIP_ERROR on success + * @retval CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the key cannot be exported due to security restrictions. + * @retval CHIP_ERROR_NOT_IMPLEMENTED if the exporting is not implemented for the crypto engine. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no keypair found for `fabricIndex`. + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `keyPair` buffer is too small to store the read out keypair. + * @retval other CHIP_ERROR value on internal storage or crypto engine errors. + */ + virtual CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const = 0; + /** * @brief Permanently remove the keypair associated with a fabric * diff --git a/src/crypto/PSAOperationalKeystore.cpp b/src/crypto/PSAOperationalKeystore.cpp index 980994696e..b3ac4140d9 100644 --- a/src/crypto/PSAOperationalKeystore.cpp +++ b/src/crypto/PSAOperationalKeystore.cpp @@ -16,6 +16,7 @@ */ #include "PSAOperationalKeystore.h" +#include "PersistentStorageOperationalKeystore.h" #include @@ -135,6 +136,35 @@ CHIP_ERROR PSAOperationalKeystore::NewOpKeypairForFabric(FabricIndex fabricIndex return CHIP_NO_ERROR; } +CHIP_ERROR PSAOperationalKeystore::PersistentP256Keypair::Deserialize(P256SerializedKeypair & input) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + psa_status_t status = PSA_SUCCESS; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t keyId = 0; + VerifyOrReturnError(input.Length() == mPublicKey.Length() + kP256_PrivateKey_Length, CHIP_ERROR_INVALID_ARGUMENT); + + Destroy(); + + // Type based on ECC with the elliptic curve SECP256r1 -> PSA_ECC_FAMILY_SECP_R1 + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, kP256_PrivateKey_Length * 8); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + 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()); + + status = psa_import_key(&attributes, input.ConstBytes() + mPublicKey.Length(), kP256_PrivateKey_Length, &keyId); + VerifyOrExit(status == PSA_SUCCESS, error = CHIP_ERROR_INTERNAL); + + memcpy(mPublicKey.Bytes(), input.ConstBytes(), mPublicKey.Length()); + +exit: + psa_reset_key_attributes(&attributes); + + return error; +} + CHIP_ERROR PSAOperationalKeystore::ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) { VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && mPendingFabricIndex == fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); @@ -154,6 +184,33 @@ CHIP_ERROR PSAOperationalKeystore::CommitOpKeypairForFabric(FabricIndex fabricIn return CHIP_NO_ERROR; } +CHIP_ERROR PSAOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(HasOpKeypairForFabric(fabricIndex), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + size_t outSize = 0; + psa_status_t status = + psa_export_key(PersistentP256Keypair(fabricIndex).GetKeyId(), outKeypair.Bytes(), outKeypair.Capacity(), &outSize); + + if (status == PSA_ERROR_BUFFER_TOO_SMALL) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + else if (status == PSA_ERROR_NOT_PERMITTED) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + else if (status != PSA_SUCCESS) + { + return CHIP_ERROR_INTERNAL; + } + + outKeypair.SetLength(outSize); + + return CHIP_NO_ERROR; +} + CHIP_ERROR PSAOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex) { VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); @@ -209,5 +266,32 @@ void PSAOperationalKeystore::ReleasePendingKeypair() mIsPendingKeypairActive = false; } +CHIP_ERROR PSAOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex, + OperationalKeystore & operationalKeystore) const +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(fabricIndex != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); + + P256SerializedKeypair serializedKeypair; + + CHIP_ERROR err = operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair); + + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND != err) + { + ReturnErrorOnFailure(err); + + // Do not allow overwriting the existing key and just remove it from KVS + if (!HasOpKeypairForFabric(fabricIndex)) + { + PersistentP256Keypair keypair(fabricIndex); + ReturnErrorOnFailure(keypair.Deserialize(serializedKeypair)); + } + + ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex)); + } + + return err; +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/PSAOperationalKeystore.h b/src/crypto/PSAOperationalKeystore.h index 89c3edc7fe..b61927b617 100644 --- a/src/crypto/PSAOperationalKeystore.h +++ b/src/crypto/PSAOperationalKeystore.h @@ -19,6 +19,7 @@ #include #include +#include namespace chip { namespace Crypto { @@ -31,7 +32,9 @@ class PSAOperationalKeystore final : public OperationalKeystore CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override; CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override; CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override; CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const; void RevertPendingKeypair() override; CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, Crypto::P256ECDSASignature & outSignature) const override; @@ -53,6 +56,7 @@ class PSAOperationalKeystore final : public OperationalKeystore bool Exists() const; CHIP_ERROR Generate(); CHIP_ERROR Destroy(); + CHIP_ERROR Deserialize(P256SerializedKeypair & input); }; void ReleasePendingKeypair(); @@ -60,6 +64,7 @@ class PSAOperationalKeystore final : public OperationalKeystore PersistentP256Keypair * mPendingKeypair = nullptr; FabricIndex mPendingFabricIndex = kUndefinedFabricIndex; bool mIsPendingKeypairActive = false; + PersistentStorageDelegate * mStorage = nullptr; }; } // namespace Crypto diff --git a/src/crypto/PersistentStorageOperationalKeystore.cpp b/src/crypto/PersistentStorageOperationalKeystore.cpp index 80eee2d974..c3a5ed6432 100644 --- a/src/crypto/PersistentStorageOperationalKeystore.cpp +++ b/src/crypto/PersistentStorageOperationalKeystore.cpp @@ -86,6 +86,57 @@ CHIP_ERROR StoreOperationalKey(FabricIndex fabricIndex, PersistentStorageDelegat return CHIP_NO_ERROR; } +CHIP_ERROR ExportStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage, + Crypto::P256SerializedKeypair & serializedOpKey) +{ + VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit. + Crypto::SensitiveDataBuffer buf; + + // Load up the operational key structure from storage + uint16_t size = static_cast(buf.Capacity()); + CHIP_ERROR err = storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = CHIP_ERROR_INVALID_FABRIC_INDEX; + } + ReturnErrorOnFailure(err); + buf.SetLength(static_cast(size)); + + // Read-out the operational key TLV entry. + TLV::ContiguousBufferTLVReader reader; + reader.Init(buf.Bytes(), buf.Length()); + + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + TLV::TLVType containerType; + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + + ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag)); + uint16_t opKeyVersion; + ReturnErrorOnFailure(reader.Get(opKeyVersion)); + VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH); + + ReturnErrorOnFailure(reader.Next(kOpKeyDataTag)); + { + ByteSpan keyData; + ReturnErrorOnFailure(reader.GetByteView(keyData)); + + // Unfortunately, we have to copy the data into a P256SerializedKeypair. + VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); + + // Before doing anything with the key, validate format further. + ReturnErrorOnFailure(reader.ExitContainer(containerType)); + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + + memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size()); + serializedOpKey.SetLength(keyData.size()); + } + + return CHIP_NO_ERROR; +} + /** WARNING: This can leave the operational key on the stack somewhere, since many of the platform * APIs use stack buffers and do not sanitize! This implementation is for example purposes * only of the API and it is recommended to avoid directly accessing raw private key bits @@ -106,55 +157,12 @@ CHIP_ERROR SignWithStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegat } // Scope 1: Load up the keypair data from storage - { - // Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit. - Crypto::SensitiveDataBuffer buf; - - // Load up the operational key structure from storage - uint16_t size = static_cast(buf.Capacity()); - CHIP_ERROR err = - storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - err = CHIP_ERROR_INVALID_FABRIC_INDEX; - } - ReturnErrorOnFailure(err); - buf.SetLength(static_cast(size)); - - // Read-out the operational key TLV entry. - TLV::ContiguousBufferTLVReader reader; - reader.Init(buf.Bytes(), buf.Length()); + P256SerializedKeypair serializedOpKey; + ReturnErrorOnFailure(ExportStoredOpKey(fabricIndex, storage, serializedOpKey)); - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); - TLV::TLVType containerType; - ReturnErrorOnFailure(reader.EnterContainer(containerType)); - - ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag)); - uint16_t opKeyVersion; - ReturnErrorOnFailure(reader.Get(opKeyVersion)); - VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH); - - ReturnErrorOnFailure(reader.Next(kOpKeyDataTag)); - { - ByteSpan keyData; - Crypto::P256SerializedKeypair serializedOpKey; - ReturnErrorOnFailure(reader.GetByteView(keyData)); - - // Unfortunately, we have to copy the data into a P256SerializedKeypair. - VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); - - // Before doing anything with the key, validate format further. - ReturnErrorOnFailure(reader.ExitContainer(containerType)); - ReturnErrorOnFailure(reader.VerifyEndOfContainer()); - - memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size()); - serializedOpKey.SetLength(keyData.size()); - - // Load-up key material - // WARNING: This makes use of the raw key bits - ReturnErrorOnFailure(transientOperationalKeypair->Deserialize(serializedOpKey)); - } - } + // Load-up key material + // WARNING: This makes use of the raw key bits + ReturnErrorOnFailure(transientOperationalKeypair->Deserialize(serializedOpKey)); // Scope 2: Sign message with the keypair return transientOperationalKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); @@ -251,6 +259,13 @@ CHIP_ERROR PersistentStorageOperationalKeystore::CommitOpKeypairForFabric(Fabric return CHIP_NO_ERROR; } +CHIP_ERROR PersistentStorageOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex, + Crypto::P256SerializedKeypair & outKeypair) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + return ExportStoredOpKey(fabricIndex, mStorage, outKeypair); +} + CHIP_ERROR PersistentStorageOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex) { VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -310,4 +325,38 @@ void PersistentStorageOperationalKeystore::ReleaseEphemeralKeypair(Crypto::P256K Platform::Delete(keypair); } +CHIP_ERROR PersistentStorageOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex, + OperationalKeystore & operationalKeystore) const +{ + VerifyOrReturnError(mStorage, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(fabricIndex != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); + + P256SerializedKeypair serializedKeypair; + + CHIP_ERROR err = operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair); + + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND != err) + { + ReturnErrorOnFailure(err); + + // Do not allow overwriting the existing key and just remove it from KVS + if (!HasOpKeypairForFabric(fabricIndex)) + { + auto operationalKeypair = Platform::MakeUnique(); + if (!operationalKeypair) + { + return CHIP_ERROR_NO_MEMORY; + } + + ReturnErrorOnFailure(operationalKeypair->Deserialize(serializedKeypair)); + ReturnErrorOnFailure(StoreOperationalKey(fabricIndex, mStorage, operationalKeypair.get())); + } + + ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex)); + } + + return err; +} + } // namespace chip diff --git a/src/crypto/PersistentStorageOperationalKeystore.h b/src/crypto/PersistentStorageOperationalKeystore.h index e69983461a..541a9d848e 100644 --- a/src/crypto/PersistentStorageOperationalKeystore.h +++ b/src/crypto/PersistentStorageOperationalKeystore.h @@ -81,12 +81,14 @@ class PersistentStorageOperationalKeystore : public Crypto::OperationalKeystore CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override; CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override; CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override; CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override; void RevertPendingKeypair() override; CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, Crypto::P256ECDSASignature & outSignature) const override; Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override; void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) override; + CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const override; protected: void ResetPendingKey() diff --git a/src/crypto/tests/TestPSAOpKeyStore.cpp b/src/crypto/tests/TestPSAOpKeyStore.cpp index f638663e46..9e12137b78 100644 --- a/src/crypto/tests/TestPSAOpKeyStore.cpp +++ b/src/crypto/tests/TestPSAOpKeyStore.cpp @@ -19,9 +19,12 @@ #include #include +#include #include #include +#include #include +#include #include #include #include @@ -139,6 +142,10 @@ void TestBasicLifeCycle(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == false); NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == true); + // Verify if the key is not exportable - the PSA_KEY_USAGE_EXPORT psa flag should not be set + P256SerializedKeypair serializedKeypair; + NL_TEST_ASSERT(inSuite, opKeystore.ExportOpKeypairForFabric(kFabricIndex, serializedKeypair) == CHIP_ERROR_NOT_IMPLEMENTED); + // After committing, signing works with the key that was pending P256ECDSASignature sig3; uint8_t message2[] = { 10, 11, 12, 13 }; @@ -172,11 +179,82 @@ void TestEphemeralKeys(nlTestSuite * inSuite, void * inContext) opKeyStore.ReleaseEphemeralKeypair(ephemeralKeypair); } +void TestMigrationKeys(nlTestSuite * inSuite, void * inContext) +{ + chip::TestPersistentStorageDelegate storage; + PSAOperationalKeystore psaOpKeyStore; + PersistentStorageOperationalKeystore persistentOpKeyStore; + FabricIndex kFabricIndex = 111; + + // Failure before Init of MoveOpKeysFromPersistentStorageToITS + NL_TEST_ASSERT(inSuite, + psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == CHIP_ERROR_INCORRECT_STATE); + + // Initialize both operational key stores + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.Init(&storage) == CHIP_NO_ERROR); + + // Failure on invalid Fabric indexes + NL_TEST_ASSERT(inSuite, + psaOpKeyStore.MigrateOpKeypairForFabric(kUndefinedFabricIndex, persistentOpKeyStore) == + CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, + psaOpKeyStore.MigrateOpKeypairForFabric(kMaxValidFabricIndex + 1, persistentOpKeyStore) == + CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Check what happens when we want to move a key of the specific Fabric, but the key does not exist. + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, + psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == CHIP_ERROR_INVALID_FABRIC_INDEX); + + auto generateAndStore = [&](FabricIndex index, MutableByteSpan & buf, P256PublicKey & pubKey) { + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.HasPendingOpKeypair() == false); + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.NewOpKeypairForFabric(kFabricIndex, buf) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, VerifyCertificateSigningRequest(buf.data(), buf.size(), pubKey) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, pubKey.Matches(pubKey)); + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.ActivateOpKeypairForFabric(kFabricIndex, pubKey) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.CommitOpKeypairForFabric(kFabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, persistentOpKeyStore.HasPendingOpKeypair() == false); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, storage.HasKey(DefaultStorageKeyAllocator::FabricOpKey(kFabricIndex).KeyName()) == true); + }; + + // Save a key to the old persistent storage + uint8_t csrBuf[kMIN_CSR_Buffer_Size]; + MutableByteSpan csrSpan{ csrBuf }; + P256PublicKey csrPublicKey1; + generateAndStore(kFabricIndex, csrSpan, csrPublicKey1); + + // Migrate key to PSA ITS + NL_TEST_ASSERT(inSuite, psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, psaOpKeyStore.HasOpKeypairForFabric(kFabricIndex) == true); + + // Verify the migrated keys + P256ECDSASignature sig1; + uint8_t message1[] = { 10, 11, 12, 13 }; + NL_TEST_ASSERT(inSuite, psaOpKeyStore.SignWithOpKeypair(kFabricIndex, ByteSpan{ message1 }, sig1) == CHIP_NO_ERROR); + + // To verify use the public key generated by the old persistent storage + NL_TEST_ASSERT(inSuite, csrPublicKey1.ECDSA_validate_msg_signature(message1, sizeof(message1), sig1) == CHIP_NO_ERROR); + + // After migration there should be no old keys anymore + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, storage.HasKey(DefaultStorageKeyAllocator::FabricOpKey(kFabricIndex).KeyName()) == false); + + // Finalize + persistentOpKeyStore.Finish(); +} + /** * Test Suite. It lists all the test functions. */ -static const nlTest sTests[] = { NL_TEST_DEF("Test Basic Lifecycle of PersistentStorageOperationalKeystore", TestBasicLifeCycle), - NL_TEST_DEF("Test ephemeral key management", TestEphemeralKeys), NL_TEST_SENTINEL() }; +static const nlTest sTests[] = { + NL_TEST_DEF("Test Basic Lifecycle of PersistentStorageOperationalKeystore", TestBasicLifeCycle), + NL_TEST_DEF("Test ephemeral key management", TestEphemeralKeys), + NL_TEST_DEF("Test keys migration to ITS", TestMigrationKeys), + NL_TEST_SENTINEL(), +}; /** * Set up the test suite. diff --git a/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp b/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp index c3fc35a453..47fddcdb76 100644 --- a/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp +++ b/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp @@ -33,6 +33,99 @@ using namespace chip::Crypto; namespace { +/** + * Implementation of OperationalKeystore for testing purposes. + * + * Not all methods of OperationalKeystore are implemented, only the currently needed. + * Currently this implementation supports only one fabric and one operational key. + * + * The Validate method can be used to validate the provided P256SerializedKeypair with the + * stored kP256SerializedKeypairRaw data. + */ +class TestOperationalKeystore final : public Crypto::OperationalKeystore +{ +public: + constexpr static uint8_t kP256SerializedKeypairRaw[] = { + 0x4, 0xd0, 0x99, 0xde, 0xd1, 0x15, 0xea, 0xcf, 0x8f, 0x13, 0xde, 0xaf, 0x74, 0x65, 0xf3, 0x10, 0x3a, 0x75, 0x94, 0x51, + 0x37, 0x3c, 0xc, 0x9a, 0x25, 0xc7, 0xad, 0xb4, 0x31, 0x39, 0x62, 0xec, 0x12, 0xa3, 0xdf, 0x28, 0x5f, 0x2c, 0x86, 0x47, + 0x2d, 0x1f, 0x5d, 0x45, 0x1d, 0x9f, 0xbc, 0xe8, 0x47, 0xf2, 0x1f, 0x40, 0x17, 0x61, 0x2b, 0x9a, 0x4e, 0x68, 0x9c, 0xe9, + 0x9e, 0xb7, 0x45, 0xdc, 0xcd, 0xb, 0x90, 0xd0, 0x24, 0xa5, 0x6d, 0x64, 0x97, 0x62, 0x75, 0x42, 0x91, 0x74, 0xfc, 0xfe, + 0xcb, 0x1, 0x6c, 0xc, 0x74, 0x6f, 0x39, 0x9f, 0x5, 0x96, 0x1b, 0xe6, 0x4a, 0x97, 0xe5, 0x84, 0x72 + }; + + bool HasOpKeypairForFabric(FabricIndex fabricIndex) const override { return fabricIndex != mUsedFabricIndex; }; + + CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override + { + // Only one Fabric is supported + if (mUsedFabricIndex != 0) + { + ChipLogError(Test, + "The TestOperationalKeystore has been initialized already, please use RemoveOpKeypairForFabric or remove " + "the object to store a new fabric"); + return CHIP_ERROR_INVALID_FABRIC_INDEX; + } + mUsedFabricIndex = fabricIndex; + (void) outCertificateSigningRequest; + + return CHIP_NO_ERROR; + } + + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override + { + // Simulate not existing value + if (fabricIndex != mUsedFabricIndex) + { + return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + + if (outKeypair.Capacity() != sizeof(kP256SerializedKeypairRaw)) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + memcpy(outKeypair.Bytes(), kP256SerializedKeypairRaw, outKeypair.Capacity()); + outKeypair.SetLength(sizeof(kP256SerializedKeypairRaw)); + + return CHIP_NO_ERROR; + } + + CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override + { + mUsedFabricIndex = 0; + return CHIP_NO_ERROR; + } + + bool ValidateKeypair(Crypto::P256SerializedKeypair & keypair) + { + return (keypair.Length() == sizeof(kP256SerializedKeypairRaw) && + memcmp(keypair.ConstBytes(), kP256SerializedKeypairRaw, keypair.Length()) == 0); + } + + // Not implemented methods, they are not used in any tests yet. + bool HasPendingOpKeypair() const override { return false; } + CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + void RevertPendingKeypair() override {} + CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override { return nullptr; } + void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) override {} + +private: + FabricIndex mUsedFabricIndex = 0; +}; + void TestBasicLifeCycle(nlTestSuite * inSuite, void * inContext) { TestPersistentStorageDelegate storageDelegate; @@ -174,6 +267,14 @@ void TestBasicLifeCycle(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == true); + // Exporting a key + P256SerializedKeypair serializedKeypair; + NL_TEST_ASSERT(inSuite, opKeystore.ExportOpKeypairForFabric(kFabricIndex, serializedKeypair) == CHIP_NO_ERROR); + + // Exporting a key from the bad fabric index + NL_TEST_ASSERT(inSuite, + opKeystore.ExportOpKeypairForFabric(kBadFabricIndex, serializedKeypair) == CHIP_ERROR_INVALID_FABRIC_INDEX); + // After committing, signing works with the key that was pending P256ECDSASignature sig3; uint8_t message2[] = { 10, 11, 12, 13 }; @@ -216,11 +317,54 @@ void TestEphemeralKeys(nlTestSuite * inSuite, void * inContext) opKeyStore.Finish(); } +void TestMigrationKeys(nlTestSuite * inSuite, void * inContext) +{ + + chip::TestPersistentStorageDelegate storageDelegate; + TestOperationalKeystore testOperationalKeystore; + PersistentStorageOperationalKeystore opKeyStore; + FabricIndex kFabricIndex = 111; + + opKeyStore.Init(&storageDelegate); + + // Failure on invalid Fabric indexes + NL_TEST_ASSERT(inSuite, + opKeyStore.MigrateOpKeypairForFabric(kUndefinedFabricIndex, testOperationalKeystore) == + CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, + opKeyStore.MigrateOpKeypairForFabric(kMaxValidFabricIndex + 1, testOperationalKeystore) == + CHIP_ERROR_INVALID_FABRIC_INDEX); + + // The key does not exists in the old Operational Keystore + NL_TEST_ASSERT(inSuite, + opKeyStore.MigrateOpKeypairForFabric(kFabricIndex, testOperationalKeystore) == + CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + // Create a key in the old Operational Keystore + uint8_t csrBuf[kMIN_CSR_Buffer_Size]; + MutableByteSpan csrSpan{ csrBuf }; + NL_TEST_ASSERT(inSuite, testOperationalKeystore.NewOpKeypairForFabric(kFabricIndex, csrSpan) == CHIP_NO_ERROR); + + // Migrate the key to the PersistentStorageOperationalKeystore + NL_TEST_ASSERT(inSuite, opKeyStore.MigrateOpKeypairForFabric(kFabricIndex, testOperationalKeystore) == CHIP_NO_ERROR); + std::string opKeyStorageKey = DefaultStorageKeyAllocator::FabricOpKey(kFabricIndex).KeyName(); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == true); + + // Verify the migration + P256SerializedKeypair serializedKeypair; + NL_TEST_ASSERT(inSuite, opKeyStore.ExportOpKeypairForFabric(kFabricIndex, serializedKeypair) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.ValidateKeypair(serializedKeypair)); + + opKeyStore.Finish(); +} + /** * Test Suite. It lists all the test functions. */ static const nlTest sTests[] = { NL_TEST_DEF("Test Basic Lifecycle of PersistentStorageOperationalKeystore", TestBasicLifeCycle), - NL_TEST_DEF("Test ephemeral key management", TestEphemeralKeys), NL_TEST_SENTINEL() }; + NL_TEST_DEF("Test ephemeral key management", TestEphemeralKeys), + NL_TEST_DEF("Test keys migration ", TestMigrationKeys), NL_TEST_SENTINEL() }; /** * Set up the test suite. diff --git a/src/include/platform/CHIPDeviceEvent.h b/src/include/platform/CHIPDeviceEvent.h index bf4c55ca43..44ff586064 100644 --- a/src/include/platform/CHIPDeviceEvent.h +++ b/src/include/platform/CHIPDeviceEvent.h @@ -239,6 +239,13 @@ enum PublicEventTypes * sending messages to other nodes. */ kServerReady, + + /** + * Signals that an operational key migration has not been migrated from one Operational Keystore + * to another. + * + */ + kOperationalKeystoreMigrationFailed, }; /** @@ -526,6 +533,11 @@ struct ChipDeviceEvent final { OtaState newState; } OtaStateChanged; + + struct + { + FabricIndex fabricIndex; + } OperationalKeystoreMigrationFailed; }; void Clear() { memset(this, 0, sizeof(*this)); } diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h index 8d3bfd63e1..a33366500a 100644 --- a/src/lib/support/TestPersistentStorageDelegate.h +++ b/src/lib/support/TestPersistentStorageDelegate.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include #include #include