/** * * 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 #include #include #include #include #include #include #include #include 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 { 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(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