blob: dca9fd21972341994f268d574e32dd3506712bac [file] [log] [blame]
/**
*
* Copyright (c) 2023 Project CHIP Authors
*
* 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.
*/
#pragma once
#include <crypto/CHIPCryptoPAL.h>
#include <crypto/SessionKeystore.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#include <lib/core/ClusterEnums.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/PersistentData.h>
#include <stddef.h>
namespace chip {
namespace Crypto {
using SymmetricKeystore = SessionKeystore;
}
} // namespace chip
namespace chip {
static constexpr size_t MaxICDMonitoringEntrySize()
{
// All the fields added together
return TLV::EstimateStructOverhead(sizeof(NodeId) /*checkInNodeID*/, sizeof(uint64_t) /*monitoredSubject*/,
sizeof(Crypto::Symmetric128BitsKeyByteArray) /*aes_key_handle*/,
sizeof(Crypto::Symmetric128BitsKeyByteArray) /*hmac_key_handle*/,
sizeof(uint8_t) /*client_type*/) *
// Provide 50% extra space to make a firmware upgrade that starts storing
// more data followed by a downgrade work easily and reliably.
// The 50% number is chosen fairly randomly; storage increases larger than that are
// possible but need to be staged carefully.
3 / 2;
}
inline constexpr size_t kICDMonitoringBufferSize = MaxICDMonitoringEntrySize();
struct ICDMonitoringEntry : public PersistentData<kICDMonitoringBufferSize>
{
ICDMonitoringEntry(FabricIndex fabric = kUndefinedFabricIndex, NodeId nodeId = kUndefinedNodeId)
{
this->fabricIndex = fabric;
this->checkInNodeID = nodeId;
this->monitoredSubject = nodeId;
}
ICDMonitoringEntry(Crypto::SymmetricKeystore * keyStore, FabricIndex fabric = kUndefinedFabricIndex,
NodeId nodeId = kUndefinedNodeId)
{
this->fabricIndex = fabric;
this->checkInNodeID = nodeId;
this->monitoredSubject = nodeId;
this->symmetricKeystore = keyStore;
}
CHIP_ERROR UpdateKey(StorageKeyName & key) override;
CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override;
CHIP_ERROR Deserialize(TLV::TLVReader & reader) override;
void Clear() override;
/**
* @brief Set the Key object
* This method will create a new keyHandle. The key handle might contain either
* the raw key or a keyID depending on which Crypto implementation is used.
* To avoid leaking keys, API consumers must either call the DeleteKey method
* or save the entry within the ICDMonitoring Table before this object goes out of scope.
*
* A new entry object should be used for each key when adding entries to the ICDMonitoring
* table.
*
* @param keyData A byte span containing the raw key
* @return CHIP_ERROR CHIP_NO_ERROR success
* CHIP_ERROR_INVALID_ARGUMENT wrong size of the raw key
* CHIP_ERROR_INTERNAL No KeyStore for the entry or Key Handle already present
* CHIP_ERROR_XXX Crypto API related failure
*/
CHIP_ERROR SetKey(ByteSpan keyData);
CHIP_ERROR DeleteKey(void);
inline bool IsValid()
{
return (symmetricKeystore != nullptr && keyHandleValid && fabricIndex != kUndefinedFabricIndex &&
checkInNodeID != kUndefinedNodeId);
}
ICDMonitoringEntry & operator=(const ICDMonitoringEntry & icdMonitoringEntry);
/**
* @brief Implement the key verification needed by the ICDManagement Server.
* Since for some key implementations we cannot retrieve the key from the AES128KeyHandle
* we must implement a way to deduce whether the verification key
* received is the same or at least works as the same way as the one stored.
*
* This method will produce a random number and then encrypt it with the keyData.
* It will then decrypt it with the key stored in the entry. If the resulting decrypted
* challenge matches the randomly generated number, then we can safely assume that both key are interchangeable.
* This method cannot guarantee a perfect match since the probability of two keys generating the same output in AES128 is
* not 0 but 1/2^128 which is small enough for our purposes.
*
* @param keyData
* @return bool True if the key is equivalent to the one stored, otherwise false
*/
bool IsKeyEquivalent(ByteSpan keyData);
chip::FabricIndex fabricIndex = kUndefinedFabricIndex;
chip::NodeId checkInNodeID = kUndefinedNodeId;
uint64_t monitoredSubject = static_cast<uint64_t>(0);
app::Clusters::IcdManagement::ClientTypeEnum clientType = app::Clusters::IcdManagement::ClientTypeEnum::kPermanent;
Crypto::Aes128KeyHandle aesKeyHandle = Crypto::Aes128KeyHandle();
Crypto::Hmac128KeyHandle hmacKeyHandle = Crypto::Hmac128KeyHandle();
bool keyHandleValid = false;
uint16_t index = 0;
Crypto::SymmetricKeystore * symmetricKeystore = nullptr;
};
/**
* @brief ICDMonitoringTable exists to manage the persistence of entries in the IcdManagement Cluster.
* To access persisted data with the ICDMonitoringTable class, instantiate an instance of this class
* and call the LoadFromStorage function.
*
* This class can only manage one fabric at a time. The flow is load a fabric, execute necessary operations,
* save it if there are any changes and load another fabric.
*
* Issue to refactor the class to use one entry for the entire table
* https://github.com/project-chip/connectedhomeip/issues/24288
*/
struct ICDMonitoringTable
{
ICDMonitoringTable(PersistentStorageDelegate & storage, FabricIndex fabric, uint16_t limit,
Crypto::SymmetricKeystore * symmetricKeystore) :
mStorage(&storage),
mFabric(fabric), mLimit(limit), mSymmetricKeystore(symmetricKeystore)
{}
/**
* @brief Returns the MonitoringRegistrationStruct entry at the given position.
* @param index Zero-based position within the RegisteredClients table.
* @param entry On success, contains the MonitoringRegistrationStruct matching the given index.
* @return CHIP_NO_ERROR on success,
* CHIP_ERROR_NOT_FOUND if index is greater than the index of the last entry on the table.
*/
CHIP_ERROR Get(uint16_t index, ICDMonitoringEntry & entry) const;
/**
* @brief Stores the MonitoringRegistrationStruct entry at the given position,
* overwriting any existing entry.
* @param index Zero-based position within the RegisteredClients table.
* @param entry On success, contains the MonitoringRegistrationStruct matching the given index.
* @return CHIP_NO_ERROR on success
*/
CHIP_ERROR Set(uint16_t index, const ICDMonitoringEntry & entry);
/**
* @brief Search the registered clients for an entry on the fabric whose checkInNodeID matches the given id.
* @param id NodeId to match.
* @param entry On success, contains the MonitoringRegistrationStruct matching the given node ID.
* If found, entry.index contains the position of the entry in the table.
* If CHIP_ERROR_NOT_FOUND is returned, entry.index contains the total number of entries in the table.
* @return CHIP_NO_ERROR if found, CHIP_ERROR_NOT_FOUND if no checkInNodeID matches the provided id.
*/
CHIP_ERROR Find(NodeId id, ICDMonitoringEntry & entry);
/**
* @brief Removes the MonitoringRegistrationStruct entry at the given position,
* shifting down the upper entries.
* @param index Zero-based position within the RegisteredClients table.
* @return CHIP_NO_ERROR on success
*/
CHIP_ERROR Remove(uint16_t index);
/**
* @brief Removes all the entries for the current fabricIndex.
* @return CHIP_NO_ERROR on success
*/
CHIP_ERROR RemoveAll();
/**
* @brief Check if the table is empty
* @return True when there is no entry in the table. False if there is at least one
*/
bool IsEmpty();
/**
* @return Maximum number of entries allowed in the RegisteredClients table.
*/
uint16_t Limit() const;
private:
PersistentStorageDelegate * mStorage;
FabricIndex mFabric;
uint16_t mLimit = 0;
Crypto::SymmetricKeystore * mSymmetricKeystore = nullptr;
};
} // namespace chip