diff --git a/src/crypto/OperationalKeystore.h b/src/crypto/OperationalKeystore.h index 8ecc461f49..46f60727d8 100644 --- a/src/crypto/OperationalKeystore.h +++ b/src/crypto/OperationalKeystore.h @@ -119,6 +119,47 @@ class OperationalKeystore */ virtual CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) = 0; + /** + * @brief Try to read out the permanently committed 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 cryptography backend. + * @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 cryptography backend errors. + */ + virtual CHIP_ERROR ExportOpKeypairForFabric(FabricIndex fabricIndex, Crypto::P256SerializedKeypair & outKeypair) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + }; + + /** + * @brief Migrate the operational keypair from another Operational keystore (`operationalKeystore`) implementation 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 cryptography backend. + * @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 + { + return CHIP_ERROR_NOT_IMPLEMENTED; + }; + /** * @brief Permanently remove the keypair associated with a fabric * diff --git a/src/crypto/PSAOperationalKeystore.cpp b/src/crypto/PSAOperationalKeystore.cpp index 980994696e..b6ba44e0aa 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 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 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..2bacf7599a 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 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..9ed39c5c38 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 c3fc35a453..1efbfbd46e 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.