|  | /** | 
|  | * | 
|  | *    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. | 
|  | */ | 
|  |  | 
|  | #include "ICDMonitoringTable.h" | 
|  |  | 
|  | #include <crypto/RandUtils.h> | 
|  |  | 
|  | namespace chip { | 
|  |  | 
|  | enum class Fields : uint8_t | 
|  | { | 
|  | kCheckInNodeID    = 1, | 
|  | kMonitoredSubject = 2, | 
|  | kAesKeyHandle     = 3, | 
|  | kHmacKeyHandle    = 4, | 
|  | kClientType       = 5, | 
|  | }; | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringEntry::UpdateKey(StorageKeyName & skey) const | 
|  | { | 
|  | VerifyOrReturnError(kUndefinedFabricIndex != this->fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); | 
|  | skey = DefaultStorageKeyAllocator::ICDManagementTableEntry(this->fabricIndex, index); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringEntry::Serialize(TLV::TLVWriter & writer) const | 
|  | { | 
|  | TLV::TLVType outer; | 
|  | ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); | 
|  | ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Fields::kCheckInNodeID), checkInNodeID)); | 
|  | ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Fields::kMonitoredSubject), monitoredSubject)); | 
|  |  | 
|  | ByteSpan aesKeybuf(aesKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>()); | 
|  | ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Fields::kAesKeyHandle), aesKeybuf)); | 
|  |  | 
|  | ByteSpan hmacKeybuf(hmacKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>()); | 
|  | ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Fields::kHmacKeyHandle), hmacKeybuf)); | 
|  |  | 
|  | ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Fields::kClientType), clientType)); | 
|  |  | 
|  | ReturnErrorOnFailure(writer.EndContainer(outer)); | 
|  | ReturnErrorOnFailure(writer.Finalize()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringEntry::Deserialize(TLV::TLVReader & reader) | 
|  | { | 
|  |  | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  | TLV::TLVType outer; | 
|  |  | 
|  | ReturnErrorOnFailure(reader.Next(TLV::AnonymousTag())); | 
|  | VerifyOrReturnError(TLV::kTLVType_Structure == reader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE); | 
|  | ReturnErrorOnFailure(reader.EnterContainer(outer)); | 
|  | while ((err = reader.Next()) == CHIP_NO_ERROR) | 
|  | { | 
|  | if (TLV::IsContextTag(reader.GetTag())) | 
|  | { | 
|  | switch (TLV::TagNumFromTag(reader.GetTag())) | 
|  | { | 
|  | case to_underlying(Fields::kCheckInNodeID): | 
|  | ReturnErrorOnFailure(reader.Get(checkInNodeID)); | 
|  | break; | 
|  | case to_underlying(Fields::kMonitoredSubject): | 
|  | ReturnErrorOnFailure(reader.Get(monitoredSubject)); | 
|  | break; | 
|  | case to_underlying(Fields::kAesKeyHandle): { | 
|  | ByteSpan buf; | 
|  | ReturnErrorOnFailure(reader.Get(buf)); | 
|  | // Since we are storing either the raw key or a key ID, we must | 
|  | // simply copy the data as is in the keyHandle. | 
|  | // Calling SetKey here would create another keyHandle in storage and will cause | 
|  | // key leaks in some implementations. | 
|  | memcpy(aesKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), buf.data(), | 
|  | sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  | keyHandleValid = true; | 
|  | } | 
|  | break; | 
|  | case to_underlying(Fields::kHmacKeyHandle): { | 
|  | ByteSpan buf; | 
|  | CHIP_ERROR error = reader.Get(buf); | 
|  |  | 
|  | if (error != CHIP_NO_ERROR) | 
|  | { | 
|  | // If retreiving the hmac key handle failed, we need to set an invalid key handle | 
|  | // even if the AesKeyHandle is valid. | 
|  | keyHandleValid = false; | 
|  | return error; | 
|  | } | 
|  |  | 
|  | // Since we are storing either the raw key or a key ID, we must | 
|  | // simply copy the data as is in the keyHandle. | 
|  | // Calling SetKey here would create another keyHandle in storage and will cause | 
|  | // key leaks in some implementations. | 
|  | memcpy(hmacKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), buf.data(), | 
|  | sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  | } | 
|  | break; | 
|  | case to_underlying(Fields::kClientType): | 
|  | ReturnErrorOnFailure(reader.Get(clientType)); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | VerifyOrReturnError(err == CHIP_END_OF_TLV, err); | 
|  | ReturnErrorOnFailure(reader.ExitContainer(outer)); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | void ICDMonitoringEntry::Clear() | 
|  | { | 
|  | this->checkInNodeID    = kUndefinedNodeId; | 
|  | this->monitoredSubject = kUndefinedNodeId; | 
|  | this->keyHandleValid   = false; | 
|  | this->clientType       = app::Clusters::IcdManagement::ClientTypeEnum::kPermanent; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringEntry::SetKey(ByteSpan keyData) | 
|  | { | 
|  | VerifyOrReturnError(keyData.size() == sizeof(Crypto::Symmetric128BitsKeyByteArray), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(symmetricKeystore != nullptr, CHIP_ERROR_INTERNAL); | 
|  | VerifyOrReturnError(!keyHandleValid, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | Crypto::Symmetric128BitsKeyByteArray keyMaterial; | 
|  | memcpy(keyMaterial, keyData.data(), sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  |  | 
|  | ReturnErrorOnFailure(symmetricKeystore->CreateKey(keyMaterial, aesKeyHandle)); | 
|  | CHIP_ERROR error = symmetricKeystore->CreateKey(keyMaterial, hmacKeyHandle); | 
|  |  | 
|  | if (error == CHIP_NO_ERROR) | 
|  | { | 
|  | // If the creation of the HmacKeyHandle succeeds, both key handles are valid | 
|  | keyHandleValid = true; | 
|  | } | 
|  | else | 
|  | { | 
|  | // Creation of the HmacKeyHandle failed, we need to delete the AesKeyHandle to avoid a key leak | 
|  | symmetricKeystore->DestroyKey(this->aesKeyHandle); | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringEntry::DeleteKey() | 
|  | { | 
|  | VerifyOrReturnError(symmetricKeystore != nullptr, CHIP_ERROR_INTERNAL); | 
|  | symmetricKeystore->DestroyKey(this->aesKeyHandle); | 
|  | symmetricKeystore->DestroyKey(this->hmacKeyHandle); | 
|  | keyHandleValid = false; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | bool ICDMonitoringEntry::IsKeyEquivalent(ByteSpan keyData) | 
|  | { | 
|  | VerifyOrReturnValue(keyData.size() == Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, false); | 
|  | VerifyOrReturnValue(symmetricKeystore != nullptr, false); | 
|  | VerifyOrReturnValue(keyHandleValid, false); | 
|  |  | 
|  | ICDMonitoringEntry tempEntry(symmetricKeystore); | 
|  |  | 
|  | VerifyOrReturnValue(tempEntry.SetKey(keyData) == CHIP_NO_ERROR, false); | 
|  |  | 
|  | // Challenge | 
|  | uint8_t mic[Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES]  = { 0 }; | 
|  | uint8_t aead[Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES] = { 0 }; | 
|  |  | 
|  | CHIP_ERROR err; | 
|  |  | 
|  | uint64_t data = Crypto::GetRandU64(), validation, encrypted; | 
|  | validation    = data; | 
|  |  | 
|  | err = Crypto::AES_CCM_encrypt(reinterpret_cast<uint8_t *>(&data), sizeof(data), nullptr, 0, tempEntry.aesKeyHandle, aead, | 
|  | Crypto::CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES, reinterpret_cast<uint8_t *>(&encrypted), mic, | 
|  | Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES); | 
|  |  | 
|  | data = 0; | 
|  | if (err == CHIP_NO_ERROR) | 
|  | { | 
|  | err = Crypto::AES_CCM_decrypt(reinterpret_cast<uint8_t *>(&encrypted), sizeof(encrypted), nullptr, 0, mic, | 
|  | Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, aesKeyHandle, aead, | 
|  | Crypto::CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES, reinterpret_cast<uint8_t *>(&data)); | 
|  | } | 
|  | tempEntry.DeleteKey(); | 
|  |  | 
|  | if (err != CHIP_NO_ERROR) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return (data == validation) ? true : false; | 
|  | } | 
|  |  | 
|  | ICDMonitoringEntry & ICDMonitoringEntry::operator=(const ICDMonitoringEntry & icdMonitoringEntry) | 
|  | { | 
|  | if (this == &icdMonitoringEntry) | 
|  | { | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | fabricIndex       = icdMonitoringEntry.fabricIndex; | 
|  | checkInNodeID     = icdMonitoringEntry.checkInNodeID; | 
|  | monitoredSubject  = icdMonitoringEntry.monitoredSubject; | 
|  | clientType        = icdMonitoringEntry.clientType; | 
|  | index             = icdMonitoringEntry.index; | 
|  | keyHandleValid    = icdMonitoringEntry.keyHandleValid; | 
|  | symmetricKeystore = icdMonitoringEntry.symmetricKeystore; | 
|  | memcpy(aesKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | icdMonitoringEntry.aesKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  | memcpy(hmacKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | icdMonitoringEntry.hmacKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  |  | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringTable::Get(uint16_t index, ICDMonitoringEntry & entry) const | 
|  | { | 
|  | entry.fabricIndex = this->mFabric; | 
|  | entry.index       = index; | 
|  | ReturnErrorOnFailure(entry.Load(this->mStorage)); | 
|  | entry.fabricIndex = this->mFabric; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringTable::Find(NodeId id, ICDMonitoringEntry & entry) | 
|  | { | 
|  | uint16_t index; | 
|  | ICDMonitoringEntry tempEntry(mSymmetricKeystore); | 
|  |  | 
|  | for (index = 0; index < this->Limit(); index++) | 
|  | { | 
|  | if (this->Get(index, tempEntry) != CHIP_NO_ERROR) | 
|  | { | 
|  | break; | 
|  | } | 
|  | if (id == tempEntry.checkInNodeID) | 
|  | { | 
|  | entry = tempEntry; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | entry.index = index; | 
|  | return CHIP_ERROR_NOT_FOUND; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringTable::Set(uint16_t index, const ICDMonitoringEntry & entry) | 
|  | { | 
|  | VerifyOrReturnError(index < this->Limit(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(kUndefinedNodeId != entry.checkInNodeID, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(kUndefinedNodeId != entry.monitoredSubject, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(entry.keyHandleValid, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | ICDMonitoringEntry e(this->mFabric, index); | 
|  | e.checkInNodeID     = entry.checkInNodeID; | 
|  | e.monitoredSubject  = entry.monitoredSubject; | 
|  | e.clientType        = entry.clientType; | 
|  | e.index             = index; | 
|  | e.symmetricKeystore = entry.symmetricKeystore; | 
|  |  | 
|  | memcpy(e.aesKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | entry.aesKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>(), sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  | memcpy(e.hmacKeyHandle.AsMutable<Crypto::Symmetric128BitsKeyByteArray>(), | 
|  | entry.hmacKeyHandle.As<Crypto::Symmetric128BitsKeyByteArray>(), sizeof(Crypto::Symmetric128BitsKeyByteArray)); | 
|  |  | 
|  | ReturnErrorOnFailure(e.symmetricKeystore->PersistICDKey(e.aesKeyHandle)); | 
|  | CHIP_ERROR error = e.symmetricKeystore->PersistICDKey(e.hmacKeyHandle); | 
|  | if (error != CHIP_NO_ERROR) | 
|  | { | 
|  | // If setting the persistence of the HmacKeyHandle failed, we need to delete the AesKeyHandle to avoid a key leak | 
|  | e.symmetricKeystore->DestroyKey(e.aesKeyHandle); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | return e.Save(this->mStorage); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringTable::Remove(uint16_t index) | 
|  | { | 
|  | ICDMonitoringEntry entry(mSymmetricKeystore, this->mFabric); | 
|  |  | 
|  | // Retrieve entry and delete the keyHandle first as to not | 
|  | // cause any key leaks. | 
|  | this->Get(index, entry); | 
|  | ReturnErrorOnFailure(entry.DeleteKey()); | 
|  |  | 
|  | // Shift remaining entries down one position | 
|  | while (CHIP_NO_ERROR == this->Get(static_cast<uint16_t>(index + 1), entry)) | 
|  | { | 
|  | ReturnErrorOnFailure(this->Set(index++, entry)); | 
|  | } | 
|  |  | 
|  | // Remove last entry | 
|  | entry.fabricIndex = this->mFabric; | 
|  | entry.index       = index; | 
|  |  | 
|  | // entry.Delete() doesn't delete the key from the AES128KeyHandle | 
|  | return entry.Delete(this->mStorage); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ICDMonitoringTable::RemoveAll() | 
|  | { | 
|  | ICDMonitoringEntry entry(mSymmetricKeystore, this->mFabric); | 
|  | uint16_t index = 0; | 
|  | while (index < this->Limit()) | 
|  | { | 
|  | CHIP_ERROR err = this->Get(index++, entry); | 
|  | if (CHIP_ERROR_NOT_FOUND == err) | 
|  | { | 
|  | break; | 
|  | } | 
|  | ReturnErrorOnFailure(err); | 
|  | entry.fabricIndex = this->mFabric; | 
|  | ReturnErrorOnFailure(entry.DeleteKey()); | 
|  | ReturnErrorOnFailure(entry.Delete(this->mStorage)); | 
|  | } | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | bool ICDMonitoringTable::IsEmpty() | 
|  | { | 
|  | ICDMonitoringEntry entry(mSymmetricKeystore, this->mFabric); | 
|  | return (this->Get(0, entry) == CHIP_ERROR_NOT_FOUND); | 
|  | } | 
|  |  | 
|  | uint16_t ICDMonitoringTable::Limit() const | 
|  | { | 
|  | return mLimit; | 
|  | } | 
|  |  | 
|  | } // namespace chip |