blob: 73358f77ea4af0006f047a3748c4aa9a6c602d33 [file] [log] [blame]
/*
* Copyright (c) 2022 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if CONFIG_ENABLE_AMEBA_CRYPTO
#include <crypto/CHIPCryptoPAL.h>
#include <crypto/CHIPCryptoPALmbedTLS.h>
#include <crypto/OperationalKeystore.h>
#include <lib/core/CHIPError.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/TLV.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/SafeInt.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/support/SafePointerCast.h>
#include <chip_porting.h>
#include <platform/Ameba/crypto/AmebaPersistentStorageOperationalKeystore.h>
#include <mbedtls/ecp.h>
#include <mbedtls/x509_csr.h>
namespace chip {
using namespace chip::Crypto;
static inline mbedtls_ecp_keypair * to_keypair(P256KeypairContext * context)
{
return SafePointerCast<mbedtls_ecp_keypair *>(context);
}
static int CryptoRNG(void * ctxt, uint8_t * out_buffer, size_t out_length)
{
return (chip::Crypto::DRBG_get_bytes(out_buffer, out_length) == CHIP_NO_ERROR) ? 0 : 1;
}
CHIP_ERROR AmebaP256Keypair::Initialize(Crypto::ECPKeyTarget key_target)
{
CHIP_ERROR error = CHIP_NO_ERROR;
int result = 0;
Clear();
mbedtls_ecp_keypair * keypair = to_keypair(&mKeypair);
VerifyOrExit(matter_get_publickey(Uint8::to_uchar(mPublicKey), mPublicKey.Length()) != 0,
error = CHIP_ERROR_INVALID_PUBLIC_KEY);
keypair = nullptr;
mInitialized = true;
exit:
if (keypair != nullptr)
{
keypair = nullptr;
}
_log_mbedTLS_error(result);
return error;
}
void AmebaP256Keypair::Clear()
{
if (mInitialized)
{
mbedtls_ecp_keypair * keypair = to_keypair(&mKeypair);
mbedtls_ecp_keypair_free(keypair);
mInitialized = false;
}
}
AmebaP256Keypair::~AmebaP256Keypair()
{
Clear();
}
namespace {
// Tags for our operational keypair storage.
constexpr TLV::Tag kOpKeyVersionTag = TLV::ContextTag(0);
constexpr TLV::Tag kOpKeyDataTag = TLV::ContextTag(1);
// If this version grows beyond UINT16_MAX, adjust OpKeypairTLVMaxSize
// accordingly.
constexpr uint16_t kOpKeyVersion = 1;
constexpr size_t OpKeyTLVMaxSize()
{
// Version and serialized key
return TLV::EstimateStructOverhead(sizeof(uint16_t), Crypto::P256SerializedKeypair::Capacity());
}
/** 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
* in storage.
*/
CHIP_ERROR StoreOperationalKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage, P256Keypair * keypair)
{
VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (storage != nullptr) && (keypair != nullptr),
CHIP_ERROR_INVALID_ARGUMENT);
// Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit.
Crypto::SensitiveDataBuffer<OpKeyTLVMaxSize()> buf;
TLV::TLVWriter writer;
writer.Init(buf.Bytes(), buf.Capacity());
TLV::TLVType outerType;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
ReturnErrorOnFailure(writer.Put(kOpKeyVersionTag, kOpKeyVersion));
{
// P256SerializedKeypair has RAII secret clearing
Crypto::P256SerializedKeypair serializedOpKey;
size_t len = serializedOpKey.Length() == 0 ? serializedOpKey.Capacity() : serializedOpKey.Length();
int result = matter_serialize(serializedOpKey.Bytes(), len);
if (result != 0)
{
return CHIP_ERROR_INTERNAL;
}
ReturnErrorOnFailure(writer.Put(kOpKeyDataTag, ByteSpan(serializedOpKey.Bytes(), len)));
}
ReturnErrorOnFailure(writer.EndContainer(outerType));
const auto opKeyLength = writer.GetLengthWritten();
VerifyOrReturnError(CanCastTo<uint16_t>(opKeyLength), CHIP_ERROR_BUFFER_TOO_SMALL);
ReturnErrorOnFailure(storage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.ConstBytes(),
static_cast<uint16_t>(opKeyLength)));
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
* in storage.
*/
CHIP_ERROR SignWithStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage, const ByteSpan & message,
P256ECDSASignature & outSignature)
{
VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (storage != nullptr), CHIP_ERROR_INVALID_ARGUMENT);
// Use RAII scoping for the transient keypair, to make sure it doesn't get leaked on any error paths.
// Key is put in heap since signature is a costly stack operation and P256Keypair is
// a costly class depending on the backend.
auto transientOperationalKeypair = Platform::MakeUnique<P256Keypair>();
if (!transientOperationalKeypair)
{
return CHIP_ERROR_NO_MEMORY;
}
// 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)
{
return CHIP_ERROR_INVALID_FABRIC_INDEX;
}
// Load-up key material
// WARNING: This makes use of the raw key bits
int result = matter_deserialize(serializedOpKey.Bytes(), serializedOpKey.Length());
if (result != 0)
{
return CHIP_ERROR_INTERNAL;
}
// Scope 2: Sign message with the keypair
result = matter_ecdsa_sign_msg(message.data(), message.size(), outSignature.Bytes());
if (result != 0)
{
return CHIP_ERROR_INTERNAL;
}
VerifyOrReturnError(outSignature.SetLength(kP256_ECDSA_Signature_Length_Raw) == CHIP_NO_ERROR, err = CHIP_ERROR_INTERNAL);
return err;
}
} // namespace
bool AmebaPersistentStorageOperationalKeystore::HasOpKeypairForFabric(FabricIndex fabricIndex) const
{
VerifyOrReturnError(mStorage != nullptr, false);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), false);
// If there was a pending keypair, then there's really a usable key
if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex) && (mPendingKeypair != nullptr))
{
return true;
}
// TODO(#16958): need to actually read the key to know if it's there due to platforms not
// properly enforcing CHIP_ERROR_BUFFER_TOO_SMALL behavior needed by
// PersistentStorageDelegate. Very unfortunate, needs fixing ASAP.
// Use a SensitiveDataBuffer to get RAII secret data clearing on scope exit.
Crypto::SensitiveDataBuffer<OpKeyTLVMaxSize()> buf;
uint16_t keySize = static_cast<uint16_t>(buf.Capacity());
CHIP_ERROR err =
mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), keySize);
return (err == CHIP_NO_ERROR);
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::NewOpKeypairForFabric(FabricIndex fabricIndex,
MutableByteSpan & outCertificateSigningRequest)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
// If a key is pending, we cannot generate for a different fabric index until we commit or revert.
if ((mPendingFabricIndex != kUndefinedFabricIndex) && (fabricIndex != mPendingFabricIndex))
{
return CHIP_ERROR_INVALID_FABRIC_INDEX;
}
VerifyOrReturnError(outCertificateSigningRequest.size() >= Crypto::kMIN_CSR_Buffer_Size, CHIP_ERROR_BUFFER_TOO_SMALL);
// Replace previous pending keypair, if any was previously allocated
ResetPendingKey();
mPendingKeypair = Platform::New<P256Keypair>();
VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_NO_MEMORY);
size_t TempLength = outCertificateSigningRequest.size();
size_t csrLength = matter_gen_new_csr(outCertificateSigningRequest.data(), TempLength);
if (csrLength <= 0)
{
ResetPendingKey();
return CHIP_ERROR_INTERNAL;
}
outCertificateSigningRequest.reduce_size(csrLength);
mPendingFabricIndex = fabricIndex;
return CHIP_NO_ERROR;
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::ActivateOpKeypairForFabric(FabricIndex fabricIndex,
const Crypto::P256PublicKey & nocPublicKey)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
// Validate public key being activated matches last generated pending keypair
mPendingKeypair = Platform::New<AmebaP256Keypair>();
VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_NO_MEMORY);
mPendingKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA);
VerifyOrReturnError(mPendingKeypair->Pubkey().Matches(nocPublicKey), CHIP_ERROR_INVALID_PUBLIC_KEY);
mIsPendingKeypairActive = true;
return CHIP_NO_ERROR;
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::CommitOpKeypairForFabric(FabricIndex fabricIndex)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(mIsPendingKeypairActive == true, CHIP_ERROR_INCORRECT_STATE);
// Try to store persistent key. On failure, leave everything pending as-is
CHIP_ERROR err = StoreOperationalKey(fabricIndex, mStorage, mPendingKeypair);
ReturnErrorOnFailure(err);
// If we got here, we succeeded and can reset the pending key: next `SignWithOpKeypair` will use the stored key.
ResetPendingKey();
return CHIP_NO_ERROR;
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::ExportOpKeypairForFabric(FabricIndex fabricIndex,
Crypto::P256SerializedKeypair & outKeypair)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
return ExportStoredOpKey(fabricIndex, mStorage, outKeypair);
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
// Remove pending state if matching
if ((mPendingKeypair != nullptr) && (fabricIndex == mPendingFabricIndex))
{
RevertPendingKeypair();
}
CHIP_ERROR err = mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName());
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
err = CHIP_ERROR_INVALID_FABRIC_INDEX;
}
return err;
}
void AmebaPersistentStorageOperationalKeystore::RevertPendingKeypair()
{
VerifyOrReturn(mStorage != nullptr);
// Just reset the pending key, we never stored anything
ResetPendingKey();
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message,
Crypto::P256ECDSASignature & outSignature) const
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex))
{
VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INTERNAL);
// We have an override key: sign with it!
CHIP_ERROR err = CHIP_NO_ERROR;
if (matter_ecdsa_sign_msg(message.data(), message.size(), outSignature.Bytes()) != 0)
{
return CHIP_ERROR_INTERNAL;
}
VerifyOrReturnError(outSignature.SetLength(kP256_ECDSA_Signature_Length_Raw) == CHIP_NO_ERROR, err = CHIP_ERROR_INTERNAL);
return err;
}
return SignWithStoredOpKey(fabricIndex, mStorage, message, outSignature);
}
Crypto::P256Keypair * AmebaPersistentStorageOperationalKeystore::AllocateEphemeralKeypairForCASE()
{
// DO NOT CUT AND PASTE without considering the ReleaseEphemeralKeypair().
// If allocating a derived class, then `ReleaseEphemeralKeypair` MUST
// de-allocate the derived class after up-casting the base class pointer.
return Platform::New<Crypto::P256Keypair>();
}
void AmebaPersistentStorageOperationalKeystore::ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair)
{
// DO NOT CUT AND PASTE without considering the AllocateEphemeralKeypairForCASE().
// This must delete the same concrete class as allocated in `AllocateEphemeralKeypairForCASE`
Platform::Delete<Crypto::P256Keypair>(keypair);
}
CHIP_ERROR AmebaPersistentStorageOperationalKeystore::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
#endif /* CONFIG_ENABLE_AMEBA_CRYPTO */