Skip to content

Commit

Permalink
[crypto] Extend the OperationalKeystore API to allow migration (#31279)
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
  • Loading branch information
ArekBalysNordic authored Feb 8, 2024
1 parent 560a46b commit 61724ee
Show file tree
Hide file tree
Showing 7 changed files with 488 additions and 50 deletions.
40 changes: 40 additions & 0 deletions src/crypto/OperationalKeystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ 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 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
*
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 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
140 changes: 93 additions & 47 deletions src/crypto/PersistentStorageOperationalKeystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,52 @@ 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);

ReturnErrorOnFailure(reader.ExitContainer(containerType));

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 +152,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 +258,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 +324,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 61724ee

Please sign in to comment.