Skip to content

Commit

Permalink
[crypto] Extend the OperationalKeystore API to allow migration
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
ArekBalysNordic committed Jan 22, 2024
1 parent a461207 commit f14fe2b
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 50 deletions.
35 changes: 35 additions & 0 deletions src/crypto/OperationalKeystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
90 changes: 90 additions & 0 deletions src/crypto/PSAOperationalKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

#include "PSAOperationalKeystore.h"
#include "PersistentStorageOperationalKeystore.h"

#include <lib/support/CHIPMem.h>

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
5 changes: 5 additions & 0 deletions src/crypto/PSAOperationalKeystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <crypto/CHIPCryptoPALPSA.h>
#include <crypto/OperationalKeystore.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>

namespace chip {
namespace Crypto {
Expand All @@ -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;
Expand All @@ -53,13 +56,15 @@ class PSAOperationalKeystore final : public OperationalKeystore
bool Exists() const;
CHIP_ERROR Generate();
CHIP_ERROR Destroy();
CHIP_ERROR Deserialize(P256SerializedKeypair & input);
};

void ReleasePendingKeypair();

PersistentP256Keypair * mPendingKeypair = nullptr;
FabricIndex mPendingFabricIndex = kUndefinedFabricIndex;
bool mIsPendingKeypairActive = false;
PersistentStorageDelegate * mStorage = nullptr;
};

} // namespace Crypto
Expand Down
142 changes: 95 additions & 47 deletions src/crypto/PersistentStorageOperationalKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpKeyTLVMaxSize()> buf;

// Load up the operational key structure from storage
uint16_t size = static_cast<uint16_t>(buf.Capacity());
ReturnErrorOnFailure(
storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), size));

buf.SetLength(static_cast<size_t>(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
Expand All @@ -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<OpKeyTLVMaxSize()> buf;

// Load up the operational key structure from storage
uint16_t size = static_cast<uint16_t>(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_t>(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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -310,4 +326,36 @@ void PersistentStorageOperationalKeystore::ReleaseEphemeralKeypair(Crypto::P256K
Platform::Delete<Crypto::P256Keypair>(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<P256Keypair>();
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
2 changes: 2 additions & 0 deletions src/crypto/PersistentStorageOperationalKeystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit f14fe2b

Please sign in to comment.