From f14fe2bf141d0baf4ceb4fe8b7f931d1ca0c39cd Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Mon, 8 Jan 2024 09:46:11 +0100 Subject: [PATCH] [crypto] Extend the OperationalKeystore API to allow migration - 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 Aligned NXP and Silabs platform to the recent OperationalKeystore changes. --- src/crypto/OperationalKeystore.h | 35 ++++ src/crypto/PSAOperationalKeystore.cpp | 90 ++++++++++ src/crypto/PSAOperationalKeystore.h | 5 + .../PersistentStorageOperationalKeystore.cpp | 142 ++++++++++----- .../PersistentStorageOperationalKeystore.h | 2 + src/crypto/tests/TestPSAOpKeyStore.cpp | 97 ++++++++++- .../tests/TestPersistentStorageOpKeyStore.cpp | 164 +++++++++++++++++- .../k32w1/K32W1PersistentStorageOpKeystore.h | 8 + .../efr32/Efr32PsaOperationalKeystore.h | 8 + .../secure_channel/tests/TestCASESession.cpp | 8 + 10 files changed, 509 insertions(+), 50 deletions(-) diff --git a/src/crypto/OperationalKeystore.h b/src/crypto/OperationalKeystore.h index 8ecc461f49b9dd..34b19d67bb0a21 100644 --- a/src/crypto/OperationalKeystore.h +++ b/src/crypto/OperationalKeystore.h @@ -119,6 +119,41 @@ 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 provided wrong value of `fabricIndex`. + * @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if there is no keypair found for `fabricIndex`. + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outKeyPair` 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 (`operationalKeystore`) to this one. + * + * This method assumes that the operational key for given `fabricIndex` exists in the `operationalKeystore` storage or it has + * been already migrated to the new Operational Storage. If a key for the given `fabricIndex` does not exist in any of those + * keystores, then the method retuns CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND. + * + * @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_PERSISTED_STORAGE_VALUE_NOT_FOUND 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 980994696e3047..d572e2e8cfb598 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,40 @@ CHIP_ERROR PSAOperationalKeystore::CommitOpKeypairForFabric(FabricIndex fabricIn return CHIP_NO_ERROR; } +CHIP_ERROR PSAOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) +{ + // Currently exporting the key is forbidden in PSAOperationalKeystore because the PSA_KEY_USAGE_EXPORT usage flag is not set, so + // there is no need to compile the code for the device, but there should be an implementation for the test purposes to verify if + // the psa_export_key returns an error. +#if CHIP_CONFIG_TEST + 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_UNSUPPORTED_CHIP_FEATURE; + } + else if (status != PSA_SUCCESS) + { + return CHIP_ERROR_INTERNAL; + } + + outKeypair.SetLength(outSize); + + return CHIP_NO_ERROR; +#else + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif +} + CHIP_ERROR PSAOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex) { VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); @@ -209,5 +273,31 @@ void PSAOperationalKeystore::ReleasePendingKeypair() mIsPendingKeypairActive = false; } +CHIP_ERROR PSAOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex, + OperationalKeystore & operationalKeystore) const +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + P256SerializedKeypair serializedKeypair; + + // Do not allow overwriting the existing key and just remove it from the previous Operational Keystore if needed. + if (!HasOpKeypairForFabric(fabricIndex)) + { + ReturnErrorOnFailure(operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair)); + + PersistentP256Keypair keypair(fabricIndex); + ReturnErrorOnFailure(keypair.Deserialize(serializedKeypair)); + + // Migrated key is not useful anymore, remove it from the previous keystore. + ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex)); + } + else if (operationalKeystore.HasOpKeypairForFabric(fabricIndex)) + { + ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex)); + } + + return CHIP_NO_ERROR; +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/PSAOperationalKeystore.h b/src/crypto/PSAOperationalKeystore.h index 89c3edc7fe7607..b61927b617cf03 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 80eee2d9742006..2bacf7599aac77 100644 --- a/src/crypto/PersistentStorageOperationalKeystore.cpp +++ b/src/crypto/PersistentStorageOperationalKeystore.cpp @@ -86,6 +86,54 @@ 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()); + ReturnErrorOnFailure( + storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size)); + + 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,56 +154,17 @@ CHIP_ERROR SignWithStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegat } // Scope 1: Load up the keypair data from storage + P256SerializedKeypair serializedOpKey; + CHIP_ERROR err = ExportStoredOpKey(fabricIndex, storage, serializedOpKey); + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err) { - // 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; - 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)); - } + return CHIP_ERROR_INVALID_FABRIC_INDEX; } + // 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 +260,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 +326,36 @@ void PersistentStorageOperationalKeystore::ReleaseEphemeralKeypair(Crypto::P256K Platform::Delete(keypair); } +CHIP_ERROR PersistentStorageOperationalKeystore::MigrateOpKeypairForFabric(FabricIndex fabricIndex, + OperationalKeystore & operationalKeystore) const +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + P256SerializedKeypair serializedKeypair; + + // Do not allow overwriting the existing key and just remove it from the previous Operational Keystore if needed. + if (!HasOpKeypairForFabric(fabricIndex)) + { + ReturnErrorOnFailure(operationalKeystore.ExportOpKeypairForFabric(fabricIndex, serializedKeypair)); + + 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)); + } + else if (operationalKeystore.HasOpKeypairForFabric(fabricIndex)) + { + ReturnErrorOnFailure(operationalKeystore.RemoveOpKeypairForFabric(fabricIndex)); + } + + return CHIP_NO_ERROR; +} + } // namespace chip diff --git a/src/crypto/PersistentStorageOperationalKeystore.h b/src/crypto/PersistentStorageOperationalKeystore.h index e69983461a7432..541a9d848e3d02 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 f638663e464d3f..9ed39c5c3831de 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,11 @@ 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_UNSUPPORTED_CHIP_FEATURE); + // After committing, signing works with the key that was pending P256ECDSASignature sig3; uint8_t message2[] = { 10, 11, 12, 13 }; @@ -172,11 +180,96 @@ void TestEphemeralKeys(nlTestSuite * inSuite, void * inContext) opKeyStore.ReleaseEphemeralKeypair(ephemeralKeypair); } +void TestMigrationKeys(nlTestSuite * inSuite, void * inContext) +{ + chip::TestPersistentStorageDelegate storage; + PSAOperationalKeystore psaOpKeyStore; + PersistentStorageOperationalKeystore persistentOpKeyStore; + constexpr 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); + + // Failure on the key migration, while the key does not exist in the any keystore. + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, storage.HasKey(DefaultStorageKeyAllocator::FabricOpKey(kFabricIndex).KeyName()) == false); + NL_TEST_ASSERT(inSuite, psaOpKeyStore.HasOpKeypairForFabric(kFabricIndex) == false); + NL_TEST_ASSERT(inSuite, + psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == + CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + 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, 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); + + // Verify that migration method returns success when there is no OpKey stored in the old keystore, but already exists in PSA + // ITS. + NL_TEST_ASSERT(inSuite, psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == CHIP_NO_ERROR); + + // The key already exists in ITS, but there is an another attempt to migrate the new key. + // The key should not be overwritten, but the key from the previous persistent keystore should be removed. + MutableByteSpan csrSpan2{ csrBuf }; + generateAndStore(kFabricIndex, csrSpan2, csrPublicKey1); + NL_TEST_ASSERT(inSuite, psaOpKeyStore.MigrateOpKeypairForFabric(kFabricIndex, persistentOpKeyStore) == CHIP_NO_ERROR); + 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 c3fc35a453aae7..1efbfbd46e208f 100644 --- a/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp +++ b/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp @@ -33,6 +33,101 @@ 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 + { + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Simulate not existing value while the fabric index is valid. + 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 +269,15 @@ 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_PERSISTED_STORAGE_VALUE_NOT_FOUND); + // After committing, signing works with the key that was pending P256ECDSASignature sig3; uint8_t message2[] = { 10, 11, 12, 13 }; @@ -216,11 +320,69 @@ void TestEphemeralKeys(nlTestSuite * inSuite, void * inContext) opKeyStore.Finish(); } +void TestMigrationKeys(nlTestSuite * inSuite, void * inContext) +{ + + chip::TestPersistentStorageDelegate storageDelegate; + TestOperationalKeystore testOperationalKeystore; + PersistentStorageOperationalKeystore opKeyStore; + FabricIndex kFabricIndex = 111; + std::string opKeyStorageKey = DefaultStorageKeyAllocator::FabricOpKey(kFabricIndex).KeyName(); + + 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 any of the Operational Keystores + NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == false); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + 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); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == true); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + // Verify the migration + P256SerializedKeypair serializedKeypair; + NL_TEST_ASSERT(inSuite, opKeyStore.ExportOpKeypairForFabric(kFabricIndex, serializedKeypair) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.ValidateKeypair(serializedKeypair)); + + // Verify that migration method returns success when there is no OpKey stored in the old keystore, but already exists in PSA + // ITS. + NL_TEST_ASSERT(inSuite, opKeyStore.MigrateOpKeypairForFabric(kFabricIndex, testOperationalKeystore) == CHIP_NO_ERROR); + + // The key already exists in ITS, but there is an another attempt to migrate the new key. + // The key should not be overwritten, but the key from the previous persistent keystore should be removed. + MutableByteSpan csrSpan2{ csrBuf }; + NL_TEST_ASSERT(inSuite, testOperationalKeystore.NewOpKeypairForFabric(kFabricIndex, csrSpan2) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.HasOpKeypairForFabric(kFabricIndex) == true); + NL_TEST_ASSERT(inSuite, opKeyStore.MigrateOpKeypairForFabric(kFabricIndex, testOperationalKeystore) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testOperationalKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + 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/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h index 6402c235527ff0..dd601eec5d4282 100644 --- a/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h +++ b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h @@ -142,6 +142,14 @@ class K32W1PersistentStorageOpKeystore : public Crypto::OperationalKeystore Crypto::P256ECDSASignature & outSignature) const override; Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override; void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) override; + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR MigrateOpKeypairForFabric(chip::FabricIndex, chip::Crypto::OperationalKeystore &) const override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } protected: void ResetPendingKey() diff --git a/src/platform/silabs/efr32/Efr32PsaOperationalKeystore.h b/src/platform/silabs/efr32/Efr32PsaOperationalKeystore.h index 868535b97a63dd..b7215a3aaf4f69 100644 --- a/src/platform/silabs/efr32/Efr32PsaOperationalKeystore.h +++ b/src/platform/silabs/efr32/Efr32PsaOperationalKeystore.h @@ -74,6 +74,14 @@ class Efr32PsaOperationalKeystore : public chip::Crypto::OperationalKeystore chip::Crypto::P256ECDSASignature & outSignature) const override; Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override; void ReleaseEphemeralKeypair(chip::Crypto::P256Keypair * keypair) override; + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR MigrateOpKeypairForFabric(chip::FabricIndex, chip::Crypto::OperationalKeystore &) const override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } protected: // The keymap maps PSA Crypto persistent key ID offsets against fabric IDs. diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index e72da8af1cbad7..13ddbcf015c528 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -183,6 +183,14 @@ class TestOperationalKeystore : public chip::Crypto::OperationalKeystore CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR MigrateOpKeypairForFabric(FabricIndex fabricIndex, OperationalKeystore & operationalKeystore) const override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } void RevertPendingKeypair() override {}