| /* |
| * |
| * Copyright (c) 2021-2022 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. |
| */ |
| |
| /** |
| * @brief Defines a table of fabrics that have provisioned the device. |
| */ |
| |
| #include "FabricTable.h" |
| |
| #include <lib/core/CHIPEncoding.h> |
| #include <lib/support/BufferWriter.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CHIPMemString.h> |
| #include <lib/support/DefaultStorageKeyAllocator.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/ScopedBuffer.h> |
| |
| namespace chip { |
| using namespace Credentials; |
| using namespace Crypto; |
| |
| using CertChainElement = chip::Credentials::OperationalCertificateStore::CertChainElement; |
| |
| namespace { |
| |
| static_assert(kMinValidFabricIndex <= CHIP_CONFIG_MAX_FABRICS, "Must support some fabrics."); |
| static_assert(CHIP_CONFIG_MAX_FABRICS <= kMaxValidFabricIndex, "Max fabric count out of range."); |
| |
| // Tags for our metadata storage. |
| constexpr TLV::Tag kVendorIdTag = TLV::ContextTag(0); |
| constexpr TLV::Tag kFabricLabelTag = TLV::ContextTag(1); |
| |
| // Tags for our index list storage. |
| constexpr TLV::Tag kNextAvailableFabricIndexTag = TLV::ContextTag(0); |
| constexpr TLV::Tag kFabricIndicesTag = TLV::ContextTag(1); |
| |
| // Tags for commit marker storage |
| constexpr TLV::Tag kMarkerFabricIndexTag = TLV::ContextTag(0); |
| constexpr TLV::Tag kMarkerIsAdditionTag = TLV::ContextTag(1); |
| |
| constexpr size_t CommitMarkerContextTLVMaxSize() |
| { |
| // Add 2x uncommitted uint64_t to leave space for backwards/forwards |
| // versioning for this critical feature that runs at boot. |
| return TLV::EstimateStructOverhead(sizeof(FabricIndex), sizeof(bool), sizeof(uint64_t), sizeof(uint64_t)); |
| } |
| |
| constexpr size_t IndexInfoTLVMaxSize() |
| { |
| // We have a single next-available index and an array of anonymous-tagged |
| // fabric indices. |
| // |
| // The max size of the list is (1 byte control + bytes for actual value) |
| // times max number of list items, plus one byte for the list terminator. |
| return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1); |
| } |
| |
| } // anonymous namespace |
| |
| CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams) |
| { |
| ReturnErrorOnFailure(initParams.AreValid()); |
| |
| Reset(); |
| |
| mNodeId = initParams.nodeId; |
| mFabricId = initParams.fabricId; |
| mFabricIndex = initParams.fabricIndex; |
| mCompressedFabricId = initParams.compressedFabricId; |
| mRootPublicKey = initParams.rootPublicKey; |
| mVendorId = static_cast<VendorId>(initParams.vendorId); |
| |
| // Deal with externally injected keys |
| if (initParams.operationalKeypair != nullptr) |
| { |
| if (initParams.hasExternallyOwnedKeypair) |
| { |
| ReturnErrorOnFailure(SetExternallyOwnedOperationalKeypair(initParams.operationalKeypair)); |
| } |
| else |
| { |
| ReturnErrorOnFailure(SetOperationalKeypair(initParams.operationalKeypair)); |
| } |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricInfo::operator=(FabricInfo && other) |
| { |
| Reset(); |
| |
| mNodeId = other.mNodeId; |
| mFabricId = other.mFabricId; |
| mFabricIndex = other.mFabricIndex; |
| mCompressedFabricId = other.mCompressedFabricId; |
| mRootPublicKey = other.mRootPublicKey; |
| mVendorId = other.mVendorId; |
| |
| SetFabricLabel(other.GetFabricLabel()); |
| |
| // Transfer ownership of operational keypair (if it was nullptr, it stays that way). |
| mOperationalKey = other.mOperationalKey; |
| mHasExternallyOwnedOperationalKey = other.mHasExternallyOwnedOperationalKey; |
| other.mOperationalKey = nullptr; |
| other.mHasExternallyOwnedOperationalKey = false; |
| |
| other.Reset(); |
| } |
| |
| CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) const |
| { |
| { |
| uint8_t buf[MetadataTLVMaxSize()]; |
| TLV::TLVWriter writer; |
| writer.Init(buf); |
| |
| TLV::TLVType outerType; |
| ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
| |
| ReturnErrorOnFailure(writer.Put(kVendorIdTag, mVendorId)); |
| |
| ReturnErrorOnFailure(writer.PutString(kFabricLabelTag, CharSpan::fromCharString(mFabricLabel))); |
| |
| ReturnErrorOnFailure(writer.EndContainer(outerType)); |
| |
| const auto metadataLength = writer.GetLengthWritten(); |
| VerifyOrReturnError(CanCastTo<uint16_t>(metadataLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
| ReturnErrorOnFailure(storage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf, |
| static_cast<uint16_t>(metadataLength))); |
| } |
| |
| // NOTE: Operational Key is never saved to storage here. See OperationalKeystore interface for how it is accessed |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage, FabricIndex newFabricIndex, const ByteSpan & rcac, |
| const ByteSpan & noc) |
| { |
| mFabricIndex = newFabricIndex; |
| |
| // Regenerate operational metadata from NOC/RCAC |
| { |
| ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &mNodeId, &mFabricId)); |
| |
| P256PublicKeySpan rootPubKeySpan; |
| ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(rcac, rootPubKeySpan)); |
| mRootPublicKey = rootPubKeySpan; |
| |
| uint8_t compressedFabricIdBuf[sizeof(uint64_t)]; |
| MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf); |
| ReturnErrorOnFailure(GenerateCompressedFabricId(mRootPublicKey, mFabricId, compressedFabricIdSpan)); |
| |
| // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() |
| // returns a binary buffer and is agnostic of usage of the output as an integer type. |
| mCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf); |
| } |
| |
| // Load other storable metadata (label, vendorId, etc) |
| { |
| uint8_t buf[MetadataTLVMaxSize()]; |
| uint16_t size = sizeof(buf); |
| ReturnErrorOnFailure( |
| storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf, size)); |
| TLV::ContiguousBufferTLVReader reader; |
| reader.Init(buf, size); |
| |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
| TLV::TLVType containerType; |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| ReturnErrorOnFailure(reader.Next(kVendorIdTag)); |
| ReturnErrorOnFailure(reader.Get(mVendorId)); |
| |
| ReturnErrorOnFailure(reader.Next(kFabricLabelTag)); |
| CharSpan label; |
| ReturnErrorOnFailure(reader.Get(label)); |
| |
| VerifyOrReturnError(label.size() <= kFabricLabelMaxLengthInBytes, CHIP_ERROR_BUFFER_TOO_SMALL); |
| Platform::CopyString(mFabricLabel, label); |
| |
| ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
| ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
| } |
| |
| // NOTE: Operational Key is never loaded here. See OperationalKeystore interface for how it is accessed |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricInfo::SetFabricLabel(const CharSpan & fabricLabel) |
| { |
| Platform::CopyString(mFabricLabel, fabricLabel); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::DeleteMetadataFromStorage(FabricIndex fabricIndex) |
| { |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
| VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| CHIP_ERROR deleteErr = mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricMetadata(fabricIndex).KeyName()); |
| |
| if (deleteErr != CHIP_NO_ERROR) |
| { |
| if (deleteErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
| { |
| ChipLogError(FabricProvisioning, "Warning: metadata not found during delete of fabric 0x%x", |
| static_cast<unsigned>(fabricIndex)); |
| } |
| else |
| { |
| ChipLogError(FabricProvisioning, "Error deleting metadata for fabric fabric 0x%x: %" CHIP_ERROR_FORMAT, |
| static_cast<unsigned>(fabricIndex), deleteErr.Format()); |
| } |
| } |
| |
| return deleteErr; |
| } |
| |
| CHIP_ERROR FabricInfo::SetOperationalKeypair(const P256Keypair * keyPair) |
| { |
| VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| P256SerializedKeypair serialized; |
| ReturnErrorOnFailure(keyPair->Serialize(serialized)); |
| |
| if (mHasExternallyOwnedOperationalKey) |
| { |
| // Drop it, so we will allocate an internally owned one. |
| mOperationalKey = nullptr; |
| mHasExternallyOwnedOperationalKey = false; |
| } |
| |
| if (mOperationalKey == nullptr) |
| { |
| mOperationalKey = chip::Platform::New<P256Keypair>(); |
| } |
| VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY); |
| return mOperationalKey->Deserialize(serialized); |
| } |
| |
| CHIP_ERROR FabricInfo::SetExternallyOwnedOperationalKeypair(P256Keypair * keyPair) |
| { |
| VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| if (!mHasExternallyOwnedOperationalKey && mOperationalKey != nullptr) |
| { |
| chip::Platform::Delete(mOperationalKey); |
| mOperationalKey = nullptr; |
| } |
| |
| mHasExternallyOwnedOperationalKey = true; |
| mOperationalKey = keyPair; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, |
| FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy, |
| CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, |
| NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey, |
| Crypto::P256PublicKey & outRootPubkey) |
| { |
| Credentials::ValidationContext validContext; |
| |
| // Note that we do NOT set a time in the validation context. This will |
| // cause the certificate chain NotBefore / NotAfter time validation logic |
| // to report CertificateValidityResult::kTimeUnknown. |
| // |
| // The default CHIPCert policy passes NotBefore / NotAfter validation for |
| // this case where time is unknown. If an override policy is passed, it |
| // will be up to the passed policy to decide how to handle this. |
| // |
| // In the FabricTable::AddNewFabric and FabricTable::UpdateFabric calls, |
| // the passed policy always passes for all questions of time validity. The |
| // rationale is that installed certificates should be valid at the time of |
| // installation by definition. If they are not and the commissionee and |
| // commissioner disagree enough on current time, CASE will fail and our |
| // fail-safe timer will expire. |
| // |
| // This then is ultimately how we validate that NotBefore / NotAfter in |
| // newly installed certificates is workable. |
| validContext.Reset(); |
| validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); |
| validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); |
| validContext.mValidityPolicy = policy; |
| |
| ChipLogProgress(FabricProvisioning, "Validating NOC chain"); |
| CHIP_ERROR err = FabricTable::VerifyCredentials(noc, icac, rcac, validContext, outCompressedFabricId, outFabricId, outNodeId, |
| outNocPubkey, &outRootPubkey); |
| if (err != CHIP_NO_ERROR && err != CHIP_ERROR_WRONG_NODE_ID) |
| { |
| err = CHIP_ERROR_UNSUPPORTED_CERT_FORMAT; |
| } |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed NOC chain validation: %" CHIP_ERROR_FORMAT, err.Format()); |
| } |
| ReturnErrorOnFailure(err); |
| |
| // Validate fabric ID match for cases like UpdateNOC. |
| if (existingFabricId != kUndefinedFabricId) |
| { |
| VerifyOrReturnError(existingFabricId == outFabricId, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
| } |
| |
| ChipLogProgress(FabricProvisioning, "NOC chain validation successful"); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricInfo::SignWithOpKeypair(ByteSpan message, P256ECDSASignature & outSignature) const |
| { |
| VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| |
| return mOperationalKey->ECDSA_sign_msg(message.data(), message.size(), outSignature); |
| } |
| |
| CHIP_ERROR FabricInfo::FetchRootPubkey(Crypto::P256PublicKey & outPublicKey) const |
| { |
| VerifyOrReturnError(IsInitialized(), CHIP_ERROR_KEY_NOT_FOUND); |
| outPublicKey = mRootPublicKey; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::VerifyCredentials(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, |
| ValidationContext & context, CompressedFabricId & outCompressedFabricId, |
| FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey, |
| Crypto::P256PublicKey * outRootPublicKey) const |
| { |
| uint8_t rootCertBuf[kMaxCHIPCertLength]; |
| MutableByteSpan rootCertSpan{ rootCertBuf }; |
| ReturnErrorOnFailure(FetchRootCert(fabricIndex, rootCertSpan)); |
| return VerifyCredentials(noc, icac, rootCertSpan, context, outCompressedFabricId, outFabricId, outNodeId, outNocPubkey, |
| outRootPublicKey); |
| } |
| |
| CHIP_ERROR FabricTable::VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, |
| ValidationContext & context, CompressedFabricId & outCompressedFabricId, |
| FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey, |
| Crypto::P256PublicKey * outRootPublicKey) |
| { |
| // TODO - Optimize credentials verification logic |
| // The certificate chain construction and verification is a compute and memory intensive operation. |
| // It can be optimized by not loading certificate (i.e. rcac) that's local and implicitly trusted. |
| // The FindValidCert() algorithm will need updates to achieve this refactor. |
| constexpr uint8_t kMaxNumCertsInOpCreds = 3; |
| |
| ChipCertificateSet certificates; |
| ReturnErrorOnFailure(certificates.Init(kMaxNumCertsInOpCreds)); |
| |
| ReturnErrorOnFailure(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor))); |
| |
| if (!icac.empty()) |
| { |
| ReturnErrorOnFailure(certificates.LoadCert(icac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash))); |
| } |
| |
| ReturnErrorOnFailure(certificates.LoadCert(noc, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash))); |
| |
| const ChipDN & nocSubjectDN = certificates.GetLastCert()[0].mSubjectDN; |
| const CertificateKeyId & nocSubjectKeyId = certificates.GetLastCert()[0].mSubjectKeyId; |
| |
| const ChipCertificateData * resultCert = nullptr; |
| // FindValidCert() checks the certificate set constructed by loading noc, icac and rcac. |
| // It confirms that the certs link correctly (noc -> icac -> rcac), and have been correctly signed. |
| ReturnErrorOnFailure(certificates.FindValidCert(nocSubjectDN, nocSubjectKeyId, context, &resultCert)); |
| |
| ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(certificates.GetLastCert()[0], &outNodeId, &outFabricId)); |
| |
| CHIP_ERROR err; |
| FabricId icacFabricId = kUndefinedFabricId; |
| if (!icac.empty()) |
| { |
| err = ExtractFabricIdFromCert(certificates.GetCertSet()[1], &icacFabricId); |
| if (err == CHIP_NO_ERROR) |
| { |
| ReturnErrorCodeIf(icacFabricId != outFabricId, CHIP_ERROR_FABRIC_MISMATCH_ON_ICA); |
| } |
| // FabricId is optional field in ICAC and "not found" code is not treated as error. |
| else if (err != CHIP_ERROR_NOT_FOUND) |
| { |
| return err; |
| } |
| } |
| |
| FabricId rcacFabricId = kUndefinedFabricId; |
| err = ExtractFabricIdFromCert(certificates.GetCertSet()[0], &rcacFabricId); |
| if (err == CHIP_NO_ERROR) |
| { |
| ReturnErrorCodeIf(rcacFabricId != outFabricId, CHIP_ERROR_WRONG_CERT_DN); |
| } |
| // FabricId is optional field in RCAC and "not found" code is not treated as error. |
| else if (err != CHIP_ERROR_NOT_FOUND) |
| { |
| return err; |
| } |
| |
| // Extract compressed fabric ID and root public key |
| { |
| uint8_t compressedFabricIdBuf[sizeof(uint64_t)]; |
| MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf); |
| P256PublicKey rootPubkey(certificates.GetCertSet()[0].mPublicKey); |
| |
| ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubkey, outFabricId, compressedFabricIdSpan)); |
| |
| // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() |
| // returns a binary buffer and is agnostic of usage of the output as an integer type. |
| outCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf); |
| |
| if (outRootPublicKey != nullptr) |
| { |
| *outRootPublicKey = rootPubkey; |
| } |
| } |
| |
| outNocPubkey = certificates.GetLastCert()->mPublicKey; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| const FabricInfo * FabricTable::FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const |
| { |
| return FindFabricCommon(rootPubKey, fabricId); |
| } |
| |
| const FabricInfo * FabricTable::FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const |
| { |
| return FindFabricCommon(rootPubKey, fabricId, nodeId); |
| } |
| |
| const FabricInfo * FabricTable::FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const |
| { |
| P256PublicKey candidatePubKey; |
| |
| // Try to match pending fabric first if available |
| if (HasPendingFabricUpdate()) |
| { |
| bool pubKeyAvailable = (mPendingFabric.FetchRootPubkey(candidatePubKey) == CHIP_NO_ERROR); |
| auto matchingNodeId = (nodeId == kUndefinedNodeId) ? mPendingFabric.GetNodeId() : nodeId; |
| if (pubKeyAvailable && rootPubKey.Matches(candidatePubKey) && fabricId == mPendingFabric.GetFabricId() && |
| matchingNodeId == mPendingFabric.GetNodeId()) |
| { |
| return &mPendingFabric; |
| } |
| } |
| |
| for (auto & fabric : mStates) |
| { |
| auto matchingNodeId = (nodeId == kUndefinedNodeId) ? fabric.GetNodeId() : nodeId; |
| |
| if (!fabric.IsInitialized()) |
| { |
| continue; |
| } |
| if (fabric.FetchRootPubkey(candidatePubKey) != CHIP_NO_ERROR) |
| { |
| continue; |
| } |
| if (rootPubKey.Matches(candidatePubKey) && fabricId == fabric.GetFabricId() && matchingNodeId == fabric.GetNodeId()) |
| { |
| return &fabric; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| FabricInfo * FabricTable::GetMutableFabricByIndex(FabricIndex fabricIndex) |
| { |
| // Try to match pending fabric first if available |
| if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex)) |
| { |
| return &mPendingFabric; |
| } |
| |
| for (auto & fabric : mStates) |
| { |
| if (!fabric.IsInitialized()) |
| { |
| continue; |
| } |
| |
| if (fabric.GetFabricIndex() == fabricIndex) |
| { |
| return &fabric; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| const FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex) const |
| { |
| // Try to match pending fabric first if available |
| if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex)) |
| { |
| return &mPendingFabric; |
| } |
| |
| for (const auto & fabric : mStates) |
| { |
| if (!fabric.IsInitialized()) |
| { |
| continue; |
| } |
| |
| if (fabric.GetFabricIndex() == fabricIndex) |
| { |
| return &fabric; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| const FabricInfo * FabricTable::FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const |
| { |
| // Try to match pending fabric first if available |
| if (HasPendingFabricUpdate() && (mPendingFabric.GetCompressedFabricId() == compressedFabricId)) |
| { |
| return &mPendingFabric; |
| } |
| |
| for (auto & fabric : mStates) |
| { |
| if (!fabric.IsInitialized()) |
| { |
| continue; |
| } |
| |
| if (compressedFabricId == fabric.GetPeerId().GetCompressedFabricId()) |
| { |
| return &fabric; |
| } |
| } |
| return nullptr; |
| } |
| |
| CHIP_ERROR FabricTable::FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kRcac, outCert); |
| } |
| |
| CHIP_ERROR FabricTable::FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending)) |
| { |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| if (mStateFlags.Has(StateFlags::kIsAddPending)) |
| { |
| // The root certificate is already associated with a pending fabric, so |
| // does not exist for purposes of this API. |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| return FetchRootCert(mFabricIndexWithPendingState, outCert); |
| } |
| |
| CHIP_ERROR FabricTable::FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| CHIP_ERROR err = mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kIcac, outCert); |
| if (err == CHIP_ERROR_NOT_FOUND) |
| { |
| if (mOpCertStore->HasCertificateForFabric(fabricIndex, CertChainElement::kNoc)) |
| { |
| // Didn't find ICAC, but have NOC: return empty for ICAC since not present in chain, but chain exists |
| outCert.reduce_size(0); |
| return CHIP_NO_ERROR; |
| } |
| } |
| |
| // For all other cases, delegate to operational cert store for results |
| return err; |
| } |
| |
| CHIP_ERROR FabricTable::FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kNoc, outCert); |
| } |
| |
| CHIP_ERROR FabricTable::FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const |
| { |
| const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
| ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
| return fabricInfo->FetchRootPubkey(outPublicKey); |
| } |
| |
| CHIP_ERROR FabricTable::FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const |
| { |
| uint8_t nocBuf[Credentials::kMaxCHIPCertLength]; |
| MutableByteSpan nocSpan{ nocBuf }; |
| ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan)); |
| ReturnErrorOnFailure(ExtractCATsFromOpCert(nocSpan, cats)); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::StoreFabricMetadata(const FabricInfo * fabricInfo) const |
| { |
| VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrDie(fabricInfo != nullptr); |
| |
| FabricIndex fabricIndex = fabricInfo->GetFabricIndex(); |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INTERNAL); |
| |
| // TODO: Refactor not to internally rely directly on storage |
| ReturnErrorOnFailure(fabricInfo->CommitToStorage(mStorage)); |
| |
| ChipLogProgress(FabricProvisioning, "Metadata for Fabric 0x%x persisted to storage.", static_cast<unsigned>(fabricIndex)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex) |
| { |
| VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(!fabric->IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
| |
| uint8_t nocBuf[kMaxCHIPCertLength]; |
| MutableByteSpan nocSpan{ nocBuf }; |
| uint8_t rcacBuf[kMaxCHIPCertLength]; |
| MutableByteSpan rcacSpan{ rcacBuf }; |
| |
| CHIP_ERROR err = FetchNOCCert(newFabricIndex, nocSpan); |
| if (err == CHIP_NO_ERROR) |
| { |
| err = FetchRootCert(newFabricIndex, rcacSpan); |
| } |
| |
| // TODO(#19935): Sweep-away fabrics without RCAC/NOC by deleting everything and marking fabric gone. |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| err = fabric->LoadFromStorage(mStorage, newFabricIndex, rcacSpan, nocSpan); |
| } |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to load Fabric (0x%x): %" CHIP_ERROR_FORMAT, static_cast<unsigned>(newFabricIndex), |
| err.Format()); |
| fabric->Reset(); |
| return err; |
| } |
| |
| ChipLogProgress(FabricProvisioning, |
| "Fabric index 0x%x was retrieved from storage. Compressed FabricId 0x" ChipLogFormatX64 |
| ", FabricId 0x" ChipLogFormatX64 ", NodeId 0x" ChipLogFormatX64 ", VendorId 0x%04X", |
| static_cast<unsigned>(fabric->GetFabricIndex()), ChipLogValueX64(fabric->GetCompressedFabricId()), |
| ChipLogValueX64(fabric->GetFabricId()), ChipLogValueX64(fabric->GetNodeId()), |
| to_underlying(fabric->GetVendorId())); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, |
| const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) |
| { |
| VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| CHIP_ERROR err = CHIP_ERROR_INTERNAL; |
| |
| Crypto::P256Keypair injectedOpKey; |
| Crypto::P256SerializedKeypair injectedOpKeysSerialized; |
| |
| Crypto::P256Keypair * opKey = nullptr; |
| if (!opKeySpan.empty()) |
| { |
| VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); |
| SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); |
| SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); |
| opKey = &injectedOpKey; |
| } |
| |
| SuccessOrExit(err = AddNewPendingTrustedRootCert(rootCert)); |
| SuccessOrExit(err = AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, |
| /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); |
| SuccessOrExit(err = CommitPendingFabricData()); |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| RevertPendingFabricData(); |
| } |
| return err; |
| } |
| |
| /* |
| * A validation policy we can pass into VerifyCredentials to extract the |
| * latest NotBefore time in the certificate chain without having to load the |
| * certificates into memory again, and one which will pass validation for all |
| * questions of NotBefore / NotAfter validity. |
| * |
| * The rationale is that installed certificates should be valid at the time of |
| * installation by definition. If they are not and the commissionee and |
| * commissioner disagree enough on current time, CASE will fail and our |
| * fail-safe timer will expire. |
| * |
| * This then is ultimately how we validate that NotBefore / NotAfter in |
| * newly installed certificates is workable. |
| */ |
| class NotBeforeCollector : public Credentials::CertificateValidityPolicy |
| { |
| public: |
| NotBeforeCollector() : mLatestNotBefore(0) {} |
| CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, |
| CertificateValidityResult result) override |
| { |
| if (cert->mNotBeforeTime > mLatestNotBefore.count()) |
| { |
| mLatestNotBefore = System::Clock::Seconds32(cert->mNotBeforeTime); |
| } |
| return CHIP_NO_ERROR; |
| } |
| System::Clock::Seconds32 mLatestNotBefore; |
| }; |
| |
| CHIP_ERROR FabricTable::NotifyFabricUpdated(FabricIndex fabricIndex) |
| { |
| FabricTable::Delegate * delegate = mDelegateListRoot; |
| while (delegate) |
| { |
| // It is possible that delegate will remove itself from the list in the callback |
| // so we grab the next delegate in the list now. |
| FabricTable::Delegate * nextDelegate = delegate->next; |
| delegate->OnFabricUpdated(*this, fabricIndex); |
| delegate = nextDelegate; |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::NotifyFabricCommitted(FabricIndex fabricIndex) |
| { |
| FabricTable::Delegate * delegate = mDelegateListRoot; |
| while (delegate) |
| { |
| // It is possible that delegate will remove itself from the list in the callback |
| // so we grab the next delegate in the list now. |
| FabricTable::Delegate * nextDelegate = delegate->next; |
| delegate->OnFabricCommitted(*this, fabricIndex); |
| delegate = nextDelegate; |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR |
| FabricTable::AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey, |
| bool isExistingOpKeyExternallyOwned, uint16_t vendorId) |
| { |
| // All parameters pre-validated before we get here |
| |
| FabricInfo::InitParams newFabricInfo; |
| FabricInfo * fabricEntry = nullptr; |
| FabricId fabricIdToValidate = kUndefinedFabricId; |
| CharSpan fabricLabel(""); |
| |
| if (isAddition) |
| { |
| // Initialization for Adding a fabric |
| |
| // Find an available slot. |
| for (auto & fabric : mStates) |
| { |
| if (fabric.IsInitialized()) |
| { |
| continue; |
| } |
| fabricEntry = &fabric; |
| break; |
| } |
| |
| VerifyOrReturnError(fabricEntry != nullptr, CHIP_ERROR_NO_MEMORY); |
| |
| newFabricInfo.vendorId = static_cast<VendorId>(vendorId); |
| newFabricInfo.fabricIndex = fabricIndex; |
| } |
| else |
| { |
| // Initialization for Upating fabric: setting up a shadow fabricInfo |
| const FabricInfo * existingFabric = FindFabricWithIndex(fabricIndex); |
| VerifyOrReturnError(existingFabric != nullptr, CHIP_ERROR_INTERNAL); |
| |
| mPendingFabric.Reset(); |
| fabricEntry = &mPendingFabric; |
| |
| newFabricInfo.vendorId = existingFabric->GetVendorId(); |
| newFabricInfo.fabricIndex = fabricIndex; |
| |
| fabricIdToValidate = existingFabric->GetFabricId(); |
| fabricLabel = existingFabric->GetFabricLabel(); |
| } |
| |
| // Make sure to not modify any of our state until ValidateIncomingNOCChain passes. |
| NotBeforeCollector notBeforeCollector; |
| P256PublicKey nocPubKey; |
| |
| // Validate the cert chain prior to adding |
| { |
| Platform::ScopedMemoryBuffer<uint8_t> nocBuf; |
| Platform::ScopedMemoryBuffer<uint8_t> icacBuf; |
| Platform::ScopedMemoryBuffer<uint8_t> rcacBuf; |
| |
| ReturnErrorCodeIf(!nocBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
| ReturnErrorCodeIf(!icacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
| ReturnErrorCodeIf(!rcacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
| |
| MutableByteSpan nocSpan{ nocBuf.Get(), kMaxCHIPCertLength }; |
| MutableByteSpan icacSpan{ icacBuf.Get(), kMaxCHIPCertLength }; |
| MutableByteSpan rcacSpan{ rcacBuf.Get(), kMaxCHIPCertLength }; |
| |
| ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan)); |
| ReturnErrorOnFailure(FetchICACert(fabricIndex, icacSpan)); |
| ReturnErrorOnFailure(FetchRootCert(fabricIndex, rcacSpan)); |
| |
| ReturnErrorOnFailure(ValidateIncomingNOCChain(nocSpan, icacSpan, rcacSpan, fabricIdToValidate, ¬BeforeCollector, |
| newFabricInfo.compressedFabricId, newFabricInfo.fabricId, |
| newFabricInfo.nodeId, nocPubKey, newFabricInfo.rootPublicKey)); |
| } |
| |
| if (existingOpKey != nullptr) |
| { |
| // Verify that public key in NOC matches public key of the provided keypair. |
| // When operational key is not injected (e.g. when mOperationalKeystore != nullptr) |
| // the check is done by the keystore in `ActivateOpKeypairForFabric`. |
| VerifyOrReturnError(existingOpKey->Pubkey().Matches(nocPubKey), CHIP_ERROR_INVALID_PUBLIC_KEY); |
| |
| newFabricInfo.operationalKeypair = existingOpKey; |
| newFabricInfo.hasExternallyOwnedKeypair = isExistingOpKeyExternallyOwned; |
| } |
| else if (mOperationalKeystore != nullptr) |
| { |
| // If a keystore exists, we activate the operational key now, which also validates if it was previously installed |
| if (mOperationalKeystore->HasPendingOpKeypair()) |
| { |
| ReturnErrorOnFailure(mOperationalKeystore->ActivateOpKeypairForFabric(fabricIndex, nocPubKey)); |
| } |
| else |
| { |
| VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| } |
| else |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| // Update local copy of fabric data. For add it's a new entry, for update, it's `mPendingFabric` shadow entry. |
| ReturnErrorOnFailure(fabricEntry->Init(newFabricInfo)); |
| |
| // Set the label, matching add/update semantics of empty/existing. |
| fabricEntry->SetFabricLabel(fabricLabel); |
| |
| if (isAddition) |
| { |
| ChipLogProgress(FabricProvisioning, "Added new fabric at index: 0x%x", |
| static_cast<unsigned>(fabricEntry->GetFabricIndex())); |
| ChipLogProgress(FabricProvisioning, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64, |
| ChipLogValueX64(fabricEntry->GetCompressedFabricId()), ChipLogValueX64(fabricEntry->GetNodeId())); |
| } |
| else |
| { |
| ChipLogProgress(FabricProvisioning, "Updated fabric at index: 0x%x, Node ID: 0x" ChipLogFormatX64, |
| static_cast<unsigned>(fabricEntry->GetFabricIndex()), ChipLogValueX64(fabricEntry->GetNodeId())); |
| } |
| |
| // Failure to update pending Last Known Good Time is non-fatal. If Last |
| // Known Good Time is incorrect and this causes the commissioner's |
| // certificates to appear invalid, the certificate validity policy will |
| // determine what to do. And if the validity policy considers this fatal |
| // this will prevent CASE and cause the pending fabric and Last Known Good |
| // Time to be reverted. |
| CHIP_ERROR lkgtErr = mLastKnownGoodTime.UpdatePendingLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore); |
| if (lkgtErr != CHIP_NO_ERROR) |
| { |
| // Log but this is not sticky... |
| ChipLogError(FabricProvisioning, "Failed to update pending Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format()); |
| } |
| |
| // Must be the last thing before we return, as this is undone later on error handling within Delete. |
| if (isAddition) |
| { |
| mFabricCount++; |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex) |
| { |
| VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| { |
| FabricTable::Delegate * delegate = mDelegateListRoot; |
| while (delegate) |
| { |
| // It is possible that delegate will remove itself from the list in FabricWillBeRemoved, |
| // so we grab the next delegate in the list now. |
| FabricTable::Delegate * nextDelegate = delegate->next; |
| delegate->FabricWillBeRemoved(*this, fabricIndex); |
| delegate = nextDelegate; |
| } |
| } |
| |
| FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
| if (fabricInfo == &mPendingFabric) |
| { |
| // Asked to Delete while pending an update: reset the pending state and |
| // get back to the underlying fabric data for existing fabric. |
| RevertPendingFabricData(); |
| fabricInfo = GetMutableFabricByIndex(fabricIndex); |
| } |
| |
| bool fabricIsInitialized = fabricInfo != nullptr && fabricInfo->IsInitialized(); |
| CHIP_ERROR metadataErr = DeleteMetadataFromStorage(fabricIndex); // Delete from storage regardless |
| |
| CHIP_ERROR opKeyErr = CHIP_NO_ERROR; |
| if (mOperationalKeystore != nullptr) |
| { |
| opKeyErr = mOperationalKeystore->RemoveOpKeypairForFabric(fabricIndex); |
| // Not having found data is not an error, we may just have gotten here |
| // on a fail-safe expiry after `RevertPendingFabricData`. |
| if (opKeyErr == CHIP_ERROR_INVALID_FABRIC_INDEX) |
| { |
| opKeyErr = CHIP_NO_ERROR; |
| } |
| } |
| |
| CHIP_ERROR opCertsErr = CHIP_NO_ERROR; |
| if (mOpCertStore != nullptr) |
| { |
| opCertsErr = mOpCertStore->RemoveOpCertsForFabric(fabricIndex); |
| // Not having found data is not an error, we may just have gotten here |
| // on a fail-safe expiry after `RevertPendingFabricData`. |
| if (opCertsErr == CHIP_ERROR_INVALID_FABRIC_INDEX) |
| { |
| opCertsErr = CHIP_NO_ERROR; |
| } |
| } |
| |
| if (!fabricIsInitialized) |
| { |
| // Make sure to return the error our API promises, not whatever storage |
| // chose to return. |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| // Since fabricIsInitialized was true, fabric is not null. |
| fabricInfo->Reset(); |
| |
| if (!mNextAvailableFabricIndex.HasValue()) |
| { |
| // We must have been in a situation where CHIP_CONFIG_MAX_FABRICS is 254 |
| // and our fabric table was full, so there was no valid next index. We |
| // have a single available index now, though; use it as |
| // mNextAvailableFabricIndex. |
| mNextAvailableFabricIndex.SetValue(fabricIndex); |
| } |
| // If StoreFabricIndexInfo fails here, that's probably OK. When we try to |
| // read things from storage later we will realize there is nothing for this |
| // index. |
| StoreFabricIndexInfo(); |
| |
| // If we ever start moving the FabricInfo entries around in the array on |
| // delete, we should update DeleteAllFabrics to handle that. |
| if (mFabricCount == 0) |
| { |
| ChipLogError(FabricProvisioning, "Trying to delete a fabric, but the current fabric count is already 0"); |
| } |
| else |
| { |
| mFabricCount--; |
| ChipLogProgress(FabricProvisioning, "Fabric (0x%x) deleted.", static_cast<unsigned>(fabricIndex)); |
| } |
| |
| if (mDelegateListRoot != nullptr) |
| { |
| FabricTable::Delegate * delegate = mDelegateListRoot; |
| while (delegate) |
| { |
| // It is possible that delegate will remove itself from the list in OnFabricRemoved, |
| // so we grab the next delegate in the list now. |
| FabricTable::Delegate * nextDelegate = delegate->next; |
| delegate->OnFabricRemoved(*this, fabricIndex); |
| delegate = nextDelegate; |
| } |
| } |
| |
| // Only return error after trying really hard to remove everything we could |
| ReturnErrorOnFailure(metadataErr); |
| ReturnErrorOnFailure(opKeyErr); |
| ReturnErrorOnFailure(opCertsErr); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricTable::DeleteAllFabrics() |
| { |
| static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
| |
| RevertPendingFabricData(); |
| |
| for (auto & fabric : *this) |
| { |
| Delete(fabric.GetFabricIndex()); |
| } |
| } |
| |
| CHIP_ERROR FabricTable::Init(const FabricTable::InitParams & initParams) |
| { |
| VerifyOrReturnError(initParams.storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(initParams.opCertStore != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| mStorage = initParams.storage; |
| mOperationalKeystore = initParams.operationalKeystore; |
| mOpCertStore = initParams.opCertStore; |
| |
| ChipLogDetail(FabricProvisioning, "Initializing FabricTable from persistent storage"); |
| |
| // Load the current fabrics from the storage. |
| static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
| |
| mFabricCount = 0; |
| for (auto & fabric : mStates) |
| { |
| fabric.Reset(); |
| } |
| mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); |
| |
| // Init failure of Last Known Good Time is non-fatal. If Last Known Good |
| // Time is unknown during incoming certificate validation for CASE and |
| // current time is also unknown, the certificate validity policy will see |
| // this condition and can act appropriately. |
| mLastKnownGoodTime.Init(mStorage); |
| |
| uint8_t buf[IndexInfoTLVMaxSize()]; |
| uint16_t size = sizeof(buf); |
| CHIP_ERROR err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf, size); |
| if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
| { |
| // No fabrics yet. Nothing to be done here. |
| } |
| else |
| { |
| ReturnErrorOnFailure(err); |
| TLV::ContiguousBufferTLVReader reader; |
| reader.Init(buf, size); |
| |
| // TODO: A safer way would be to just clean-up the entire fabric table on this situation... |
| err = ReadFabricInfo(reader); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Error loading fabric table: %" CHIP_ERROR_FORMAT ", we are in a bad state!", |
| err.Format()); |
| } |
| |
| ReturnErrorOnFailure(err); |
| } |
| |
| CommitMarker commitMarker; |
| err = GetCommitMarker(commitMarker); |
| if (err == CHIP_NO_ERROR) |
| { |
| // Found a commit marker! We need to possibly delete a loaded fabric |
| ChipLogError(FabricProvisioning, "Found a FabricTable aborted commit for index 0x%x (isAddition: %d), removing!", |
| static_cast<unsigned>(commitMarker.fabricIndex), static_cast<int>(commitMarker.isAddition)); |
| |
| mDeletedFabricIndexFromInit = commitMarker.fabricIndex; |
| |
| // Can't do better on error. We just have to hope for the best. |
| (void) Delete(commitMarker.fabricIndex); |
| } |
| else if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
| { |
| // Got an error, but somehow value is not missing altogether: inconsistent state but touch nothing. |
| ChipLogError(FabricProvisioning, "Error loading Table commit marker: %" CHIP_ERROR_FORMAT ", hope for the best!", |
| err.Format()); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricTable::Forget(FabricIndex fabricIndex) |
| { |
| ChipLogProgress(FabricProvisioning, "Forgetting fabric 0x%x", static_cast<unsigned>(fabricIndex)); |
| |
| auto * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
| VerifyOrReturn(fabricInfo != nullptr); |
| |
| RevertPendingFabricData(); |
| fabricInfo->Reset(); |
| } |
| |
| void FabricTable::Shutdown() |
| { |
| VerifyOrReturn(mStorage != nullptr); |
| ChipLogProgress(FabricProvisioning, "Shutting down FabricTable"); |
| |
| // Remove all links to every delegate |
| FabricTable::Delegate * delegate = mDelegateListRoot; |
| while (delegate) |
| { |
| FabricTable::Delegate * temp = delegate->next; |
| delegate->next = nullptr; |
| delegate = temp; |
| } |
| |
| RevertPendingFabricData(); |
| for (FabricInfo & fabricInfo : mStates) |
| { |
| // Clear-out any FabricInfo-owned operational keys and make sure any further |
| // direct lookups fail. |
| fabricInfo.Reset(); |
| } |
| |
| mStorage = nullptr; |
| } |
| |
| FabricIndex FabricTable::GetDeletedFabricFromCommitMarker() |
| { |
| FabricIndex retVal = mDeletedFabricIndexFromInit; |
| |
| // Reset for next read |
| mDeletedFabricIndexFromInit = kUndefinedFabricIndex; |
| |
| return retVal; |
| } |
| |
| CHIP_ERROR FabricTable::AddFabricDelegate(FabricTable::Delegate * delegate) |
| { |
| VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| for (FabricTable::Delegate * iter = mDelegateListRoot; iter != nullptr; iter = iter->next) |
| { |
| if (iter == delegate) |
| { |
| return CHIP_NO_ERROR; |
| } |
| } |
| delegate->next = mDelegateListRoot; |
| mDelegateListRoot = delegate; |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricTable::RemoveFabricDelegate(FabricTable::Delegate * delegateToRemove) |
| { |
| VerifyOrReturn(delegateToRemove != nullptr); |
| |
| if (delegateToRemove == mDelegateListRoot) |
| { |
| // Removing head of the list, keep link to next, may |
| // be nullptr. |
| mDelegateListRoot = mDelegateListRoot->next; |
| } |
| else |
| { |
| // Removing some other item: check if next, and |
| // remove the link, keeping its neighbour. |
| FabricTable::Delegate * currentNode = mDelegateListRoot; |
| |
| while (currentNode) |
| { |
| if (currentNode->next == delegateToRemove) |
| { |
| FabricTable::Delegate * temp = delegateToRemove->next; |
| currentNode->next = temp; |
| delegateToRemove->next = nullptr; |
| return; |
| } |
| |
| currentNode = currentNode->next; |
| } |
| } |
| } |
| |
| CHIP_ERROR FabricTable::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| // Find our latest NotBefore time for any installed certificate. |
| System::Clock::Seconds32 latestNotBefore = System::Clock::Seconds32(0); |
| for (auto & fabric : mStates) |
| { |
| if (!fabric.IsInitialized()) |
| { |
| continue; |
| } |
| { |
| uint8_t rcacBuf[kMaxCHIPCertLength]; |
| MutableByteSpan rcacSpan{ rcacBuf }; |
| SuccessOrExit(err = FetchRootCert(fabric.GetFabricIndex(), rcacSpan)); |
| chip::System::Clock::Seconds32 rcacNotBefore; |
| SuccessOrExit(err = Credentials::ExtractNotBeforeFromChipCert(rcacSpan, rcacNotBefore)); |
| latestNotBefore = rcacNotBefore > latestNotBefore ? rcacNotBefore : latestNotBefore; |
| } |
| { |
| uint8_t icacBuf[kMaxCHIPCertLength]; |
| MutableByteSpan icacSpan{ icacBuf }; |
| SuccessOrExit(err = FetchICACert(fabric.GetFabricIndex(), icacSpan)); |
| if (!icacSpan.empty()) |
| { |
| chip::System::Clock::Seconds32 icacNotBefore; |
| ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(icacSpan, icacNotBefore)); |
| latestNotBefore = icacNotBefore > latestNotBefore ? icacNotBefore : latestNotBefore; |
| } |
| } |
| { |
| uint8_t nocBuf[kMaxCHIPCertLength]; |
| MutableByteSpan nocSpan{ nocBuf }; |
| SuccessOrExit(err = FetchNOCCert(fabric.GetFabricIndex(), nocSpan)); |
| chip::System::Clock::Seconds32 nocNotBefore; |
| ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(nocSpan, nocNotBefore)); |
| latestNotBefore = nocNotBefore > latestNotBefore ? nocNotBefore : latestNotBefore; |
| } |
| } |
| // Pass this to the LastKnownGoodTime object so it can make determination |
| // of the legality of our new proposed time. |
| SuccessOrExit(err = mLastKnownGoodTime.SetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, latestNotBefore)); |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to update Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); |
| } |
| return err; |
| } |
| |
| namespace { |
| // Increment a fabric index in a way that ensures that it stays in the valid |
| // range [kMinValidFabricIndex, kMaxValidFabricIndex]. |
| FabricIndex NextFabricIndex(FabricIndex fabricIndex) |
| { |
| if (fabricIndex == kMaxValidFabricIndex) |
| { |
| return kMinValidFabricIndex; |
| } |
| |
| return static_cast<FabricIndex>(fabricIndex + 1); |
| } |
| } // anonymous namespace |
| |
| void FabricTable::UpdateNextAvailableFabricIndex() |
| { |
| // Only called when mNextAvailableFabricIndex.HasValue() |
| for (FabricIndex candidate = NextFabricIndex(mNextAvailableFabricIndex.Value()); candidate != mNextAvailableFabricIndex.Value(); |
| candidate = NextFabricIndex(candidate)) |
| { |
| if (!FindFabricWithIndex(candidate)) |
| { |
| mNextAvailableFabricIndex.SetValue(candidate); |
| return; |
| } |
| } |
| |
| mNextAvailableFabricIndex.ClearValue(); |
| } |
| |
| CHIP_ERROR FabricTable::StoreFabricIndexInfo() const |
| { |
| uint8_t buf[IndexInfoTLVMaxSize()]; |
| TLV::TLVWriter writer; |
| writer.Init(buf); |
| |
| TLV::TLVType outerType; |
| ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
| |
| if (mNextAvailableFabricIndex.HasValue()) |
| { |
| writer.Put(kNextAvailableFabricIndexTag, mNextAvailableFabricIndex.Value()); |
| } |
| else |
| { |
| writer.PutNull(kNextAvailableFabricIndexTag); |
| } |
| |
| TLV::TLVType innerContainerType; |
| ReturnErrorOnFailure(writer.StartContainer(kFabricIndicesTag, TLV::kTLVType_Array, innerContainerType)); |
| // Only enumerate the fabrics that are initialized. |
| for (const auto & fabric : *this) |
| { |
| writer.Put(TLV::AnonymousTag(), fabric.GetFabricIndex()); |
| } |
| ReturnErrorOnFailure(writer.EndContainer(innerContainerType)); |
| ReturnErrorOnFailure(writer.EndContainer(outerType)); |
| |
| const auto indexInfoLength = writer.GetLengthWritten(); |
| VerifyOrReturnError(CanCastTo<uint16_t>(indexInfoLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
| |
| ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf, |
| static_cast<uint16_t>(indexInfoLength))); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricTable::EnsureNextAvailableFabricIndexUpdated() |
| { |
| if (!mNextAvailableFabricIndex.HasValue() && mFabricCount < kMaxValidFabricIndex) |
| { |
| // We must have a fabric index available here. This situation could |
| // happen if we fail to store fabric index info when deleting a |
| // fabric. |
| mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); |
| if (FindFabricWithIndex(kMinValidFabricIndex)) |
| { |
| UpdateNextAvailableFabricIndex(); |
| } |
| } |
| } |
| |
| CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader) |
| { |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
| TLV::TLVType containerType; |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| ReturnErrorOnFailure(reader.Next(kNextAvailableFabricIndexTag)); |
| if (reader.GetType() == TLV::kTLVType_Null) |
| { |
| mNextAvailableFabricIndex.ClearValue(); |
| } |
| else |
| { |
| ReturnErrorOnFailure(reader.Get(mNextAvailableFabricIndex.Emplace())); |
| } |
| |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, kFabricIndicesTag)); |
| TLV::TLVType arrayType; |
| ReturnErrorOnFailure(reader.EnterContainer(arrayType)); |
| |
| CHIP_ERROR err; |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| if (mFabricCount >= ArraySize(mStates)) |
| { |
| // We have nowhere to deserialize this fabric info into. |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| auto & fabric = mStates[mFabricCount]; |
| FabricIndex currentFabricIndex = kUndefinedFabricIndex; |
| ReturnErrorOnFailure(reader.Get(currentFabricIndex)); |
| |
| err = LoadFromStorage(&fabric, currentFabricIndex); |
| if (err == CHIP_NO_ERROR) |
| { |
| ++mFabricCount; |
| } |
| else |
| { |
| // This could happen if we failed to store our fabric index info |
| // after we deleted the fabric from storage. Just ignore this |
| // fabric index and keep going. |
| } |
| } |
| |
| if (err != CHIP_END_OF_TLV) |
| { |
| return err; |
| } |
| |
| ReturnErrorOnFailure(reader.ExitContainer(arrayType)); |
| |
| ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
| ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
| |
| EnsureNextAvailableFabricIndexUpdated(); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| Crypto::P256Keypair * FabricTable::AllocateEphemeralKeypairForCASE() |
| { |
| if (mOperationalKeystore != nullptr) |
| { |
| return mOperationalKeystore->AllocateEphemeralKeypairForCASE(); |
| } |
| |
| return Platform::New<Crypto::P256Keypair>(); |
| } |
| |
| void FabricTable::ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) |
| { |
| if (mOperationalKeystore != nullptr) |
| { |
| mOperationalKeystore->ReleaseEphemeralKeypair(keypair); |
| } |
| else |
| { |
| Platform::Delete<Crypto::P256Keypair>(keypair); |
| } |
| } |
| |
| CHIP_ERROR FabricTable::StoreCommitMarker(const CommitMarker & commitMarker) |
| { |
| uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()]; |
| TLV::TLVWriter writer; |
| writer.Init(tlvBuf); |
| |
| TLV::TLVType outerType; |
| ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
| ReturnErrorOnFailure(writer.Put(kMarkerFabricIndexTag, commitMarker.fabricIndex)); |
| ReturnErrorOnFailure(writer.Put(kMarkerIsAdditionTag, commitMarker.isAddition)); |
| ReturnErrorOnFailure(writer.EndContainer(outerType)); |
| |
| const auto markerContextTLVLength = writer.GetLengthWritten(); |
| VerifyOrReturnError(CanCastTo<uint16_t>(markerContextTLVLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
| |
| return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName(), tlvBuf, |
| static_cast<uint16_t>(markerContextTLVLength)); |
| } |
| |
| CHIP_ERROR FabricTable::GetCommitMarker(CommitMarker & outCommitMarker) |
| { |
| uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()]; |
| uint16_t tlvSize = sizeof(tlvBuf); |
| ReturnErrorOnFailure( |
| mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName(), tlvBuf, tlvSize)); |
| |
| // If buffer was too small, we won't reach here. |
| TLV::ContiguousBufferTLVReader reader; |
| reader.Init(tlvBuf, tlvSize); |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
| |
| TLV::TLVType containerType; |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| ReturnErrorOnFailure(reader.Next(kMarkerFabricIndexTag)); |
| ReturnErrorOnFailure(reader.Get(outCommitMarker.fabricIndex)); |
| |
| ReturnErrorOnFailure(reader.Next(kMarkerIsAdditionTag)); |
| ReturnErrorOnFailure(reader.Get(outCommitMarker.isAddition)); |
| |
| // Don't try to exit container: we got all we needed. This allows us to |
| // avoid erroring-out on newer versions. |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void FabricTable::ClearCommitMarker() |
| { |
| mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName()); |
| } |
| |
| bool FabricTable::HasOperationalKeyForFabric(FabricIndex fabricIndex) const |
| { |
| const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
| VerifyOrReturnError(fabricInfo != nullptr, false); |
| |
| if (fabricInfo->HasOperationalKey()) |
| { |
| // Legacy case of manually injected keys: delegate to FabricInfo directly |
| return true; |
| } |
| if (mOperationalKeystore != nullptr) |
| { |
| return mOperationalKeystore->HasOpKeypairForFabric(fabricIndex); |
| } |
| |
| return false; |
| } |
| |
| CHIP_ERROR FabricTable::SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, P256ECDSASignature & outSignature) const |
| { |
| const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
| VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| |
| if (fabricInfo->HasOperationalKey()) |
| { |
| // Legacy case of manually injected FabricInfo: delegate to FabricInfo directly |
| return fabricInfo->SignWithOpKeypair(message, outSignature); |
| } |
| if (mOperationalKeystore != nullptr) |
| { |
| return mOperationalKeystore->SignWithOpKeypair(fabricIndex, message, outSignature); |
| } |
| |
| return CHIP_ERROR_KEY_NOT_FOUND; |
| } |
| |
| bool FabricTable::HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const |
| { |
| // We can only manage commissionable pending fail-safe state if we have a keystore |
| bool hasOpKeyPending = mStateFlags.Has(StateFlags::kIsOperationalKeyPending); |
| |
| if (hasOpKeyPending) |
| { |
| // We kept track of whether the last `AllocatePendingOperationalKey` for was for an update, |
| // so give it back out here. |
| outIsPendingKeyForUpdateNoc = mStateFlags.Has(StateFlags::kIsPendingKeyForUpdateNoc); |
| } |
| |
| return hasOpKeyPending; |
| } |
| |
| bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex) |
| { |
| bool isLegal = (mFabricIndexWithPendingState == kUndefinedFabricIndex) || (mFabricIndexWithPendingState == fabricIndex); |
| |
| if (isLegal) |
| { |
| mFabricIndexWithPendingState = fabricIndex; |
| } |
| return isLegal; |
| } |
| |
| CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr) |
| { |
| // We can only manage commissionable pending fail-safe state if we have a keystore |
| VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| // We can only allocate a pending key if no pending state (NOC, ICAC) already present, |
| // since there can only be one pending state per fail-safe. |
| VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent), CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(outputCsr.size() >= Crypto::kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL); |
| |
| EnsureNextAvailableFabricIndexUpdated(); |
| FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
| |
| if (fabricIndex.HasValue()) |
| { |
| // Check we not are trying to do an update but also change the root: forbidden |
| ReturnErrorCodeIf(mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
| |
| // Fabric update case (e.g. UpdateNOC): we already know the fabric index |
| fabricIndexToUse = fabricIndex.Value(); |
| mStateFlags.Set(StateFlags::kIsPendingKeyForUpdateNoc); |
| } |
| else if (mNextAvailableFabricIndex.HasValue()) |
| { |
| // Fabric addition case (e.g. AddNOC): we need to allocate for the next pending fabric index |
| fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
| mStateFlags.Clear(StateFlags::kIsPendingKeyForUpdateNoc); |
| } |
| else |
| { |
| // Fabric addition, but adding NOC would fail on table full: let's not allocate a key |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
| VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
| ReturnErrorOnFailure(mOperationalKeystore->NewOpKeypairForFabric(mFabricIndexWithPendingState, outputCsr)); |
| mStateFlags.Set(StateFlags::kIsOperationalKeyPending); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::AddNewPendingTrustedRootCert(const ByteSpan & rcac) |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| // We should not already have pending NOC chain elements when we get here |
| ReturnErrorCodeIf( |
| mStateFlags.HasAny(StateFlags::kIsTrustedRootPending, StateFlags::kIsUpdatePending, StateFlags::kIsAddPending), |
| CHIP_ERROR_INCORRECT_STATE); |
| |
| EnsureNextAvailableFabricIndexUpdated(); |
| FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
| |
| if (mNextAvailableFabricIndex.HasValue()) |
| { |
| fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
| } |
| else |
| { |
| // Fabric addition, but adding root would fail on table full: let's not allocate a fabric |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
| VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
| ReturnErrorOnFailure(mOpCertStore->AddNewTrustedRootCertForFabric(fabricIndexToUse, rcac)); |
| |
| mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
| mStateFlags.Set(StateFlags::kIsTrustedRootPending); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::FindExistingFabricByNocChaining(FabricIndex pendingFabricIndex, const ByteSpan & noc, |
| FabricIndex & outMatchingFabricIndex) const |
| { |
| // Check whether we already have a matching fabric from a cert chain perspective. |
| // To do so we have to extract the FabricID from the NOC and the root public key from the RCAC. |
| // We assume the RCAC is currently readable from OperationalCertificateStore, whether pending |
| // or persisted. |
| FabricId fabricId; |
| { |
| NodeId unused; |
| ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &unused, &fabricId)); |
| } |
| |
| // Try to find the root public key from the current existing fabric |
| Crypto::P256PublicKey candidateRootKey; |
| { |
| uint8_t tempRcac[kMaxCHIPCertLength]; |
| MutableByteSpan tempRcacSpan{ tempRcac }; |
| Credentials::P256PublicKeySpan publicKeySpan; |
| ReturnErrorOnFailure(FetchRootCert(pendingFabricIndex, tempRcacSpan)); |
| ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(tempRcacSpan, publicKeySpan)); |
| candidateRootKey = publicKeySpan; |
| } |
| |
| for (auto & existingFabric : *this) |
| { |
| if (existingFabric.GetFabricId() == fabricId) |
| { |
| P256PublicKey existingRootKey; |
| ReturnErrorOnFailure(FetchRootPubkey(existingFabric.GetFabricIndex(), existingRootKey)); |
| |
| if (existingRootKey.Matches(candidateRootKey)) |
| { |
| outMatchingFabricIndex = existingFabric.GetFabricIndex(); |
| return CHIP_NO_ERROR; |
| } |
| } |
| } |
| |
| // Did not find: set outMatchingFabricIndex to kUndefinedFabricIndex |
| outMatchingFabricIndex = kUndefinedFabricIndex; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId, |
| Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, |
| FabricIndex * outNewFabricIndex) |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(outNewFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
| |
| // We should already have a pending root when we get here |
| VerifyOrReturnError(mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
| // We should not have pending update when we get here |
| VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsUpdatePending), CHIP_ERROR_INCORRECT_STATE); |
| |
| EnsureNextAvailableFabricIndexUpdated(); |
| FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
| if (mNextAvailableFabricIndex.HasValue()) |
| { |
| fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
| } |
| else |
| { |
| // Fabric addition, but adding fabric would fail on table full: let's not allocate a fabric |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| // Internal consistency check that mNextAvailableFabricIndex is indeed properly updated... |
| // TODO: Centralize this a bit. |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
| |
| if (existingOpKey == nullptr) |
| { |
| // If existing operational key not provided, we need to have a keystore present. |
| // It should already have an operational key pending. |
| VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| // Make sure we have an operational key, pending or not |
| VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndexToUse) || |
| mOperationalKeystore->HasPendingOpKeypair(), |
| CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| |
| // Check for new fabric colliding with an existing fabric |
| if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored)) |
| { |
| FabricIndex collidingFabricIndex = kUndefinedFabricIndex; |
| ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndexToUse, noc, collidingFabricIndex)); |
| ReturnErrorCodeIf(collidingFabricIndex != kUndefinedFabricIndex, CHIP_ERROR_FABRIC_EXISTS); |
| } |
| |
| // We don't have a collision, handle the temp insert of NOC/ICAC |
| ReturnErrorOnFailure(mOpCertStore->AddNewOpCertsForFabric(fabricIndexToUse, noc, icac)); |
| VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
| |
| CHIP_ERROR err = |
| AddOrUpdateInner(fabricIndexToUse, /* isAddition = */ true, existingOpKey, isExistingOpKeyExternallyOwned, vendorId); |
| if (err != CHIP_NO_ERROR) |
| { |
| // Revert partial state added on error |
| RevertPendingOpCertsExceptRoot(); |
| return err; |
| } |
| |
| mStateFlags.Set(StateFlags::kIsAddPending); |
| mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
| |
| // Notify that NOC was added (at least transiently) |
| *outNewFabricIndex = fabricIndexToUse; |
| NotifyFabricUpdated(fabricIndexToUse); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, |
| Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned) |
| { |
| VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (existingOpKey == nullptr) |
| { |
| // If existing operational key not provided, we need to have a keystore present. |
| // It should already have an operational key pending. |
| VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| // Make sure we have an operational key, pending or not |
| VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex) || mOperationalKeystore->HasPendingOpKeypair(), |
| CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| |
| // We should should not have a pending root when we get here, since we can't update root on update |
| VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
| |
| // We should not have pending add when we get here, due to internal interlocks |
| VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsAddPending), CHIP_ERROR_INCORRECT_STATE); |
| |
| // Make sure we are updating at least an existing FabricIndex |
| const auto * fabricInfo = FindFabricWithIndex(fabricIndex); |
| ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
| |
| // Check for an existing fabric matching RCAC and FabricID. We must find a correct |
| // existing fabric that chains to same root. We assume the stored root is correct. |
| if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored)) |
| { |
| FabricIndex collidingFabricIndex = kUndefinedFabricIndex; |
| ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndex, noc, collidingFabricIndex)); |
| ReturnErrorCodeIf(collidingFabricIndex != fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); |
| } |
| |
| // Handle the temp insert of NOC/ICAC |
| ReturnErrorOnFailure(mOpCertStore->UpdateOpCertsForFabric(fabricIndex, noc, icac)); |
| VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndex), CHIP_ERROR_INCORRECT_STATE); |
| |
| CHIP_ERROR err = AddOrUpdateInner(fabricIndex, /* isAddition = */ false, existingOpKey, isExistingOpKeyExternallyOwned, |
| fabricInfo->GetVendorId()); |
| if (err != CHIP_NO_ERROR) |
| { |
| // Revert partial state added on error |
| // TODO: Figure-out if there is a better way. We need to make sure we are not inconsistent on elements |
| // other than the opcerts. |
| RevertPendingOpCertsExceptRoot(); |
| return err; |
| } |
| |
| mStateFlags.Set(StateFlags::kIsUpdatePending); |
| mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
| |
| // Notify that NOC was updated (at least transiently) |
| NotifyFabricUpdated(fabricIndex); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::CommitPendingFabricData() |
| { |
| VerifyOrReturnError((mStorage != nullptr) && (mOpCertStore != nullptr), CHIP_ERROR_INCORRECT_STATE); |
| |
| bool haveNewTrustedRoot = mStateFlags.Has(StateFlags::kIsTrustedRootPending); |
| bool isAdding = mStateFlags.Has(StateFlags::kIsAddPending); |
| bool isUpdating = mStateFlags.Has(StateFlags::kIsUpdatePending); |
| bool hasPending = mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent); |
| bool onlyHaveNewTrustedRoot = hasPending && haveNewTrustedRoot && !(isAdding || isUpdating); |
| bool hasInvalidInternalState = hasPending && (!IsValidFabricIndex(mFabricIndexWithPendingState) || !(isAdding || isUpdating)); |
| |
| FabricIndex fabricIndexBeingCommitted = mFabricIndexWithPendingState; |
| |
| // Proceed with Update/Add pre-flight checks |
| if (hasPending && !hasInvalidInternalState) |
| { |
| if ((isAdding && isUpdating) || (isAdding && !haveNewTrustedRoot)) |
| { |
| ChipLogError(FabricProvisioning, "Found inconsistent interlocks during commit %u/%u/%u!", |
| static_cast<unsigned>(isAdding), static_cast<unsigned>(isUpdating), |
| static_cast<unsigned>(haveNewTrustedRoot)); |
| hasInvalidInternalState = true; |
| } |
| } |
| |
| // Make sure we actually have a pending fabric |
| FabricInfo * pendingFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
| |
| if (isUpdating && hasPending && !hasInvalidInternalState) |
| { |
| if (!mPendingFabric.IsInitialized() || (mPendingFabric.GetFabricIndex() != fabricIndexBeingCommitted) || |
| (pendingFabricEntry == nullptr)) |
| { |
| ChipLogError(FabricProvisioning, "Missing pending fabric on update during commit!"); |
| hasInvalidInternalState = true; |
| } |
| } |
| |
| if (isAdding && hasPending && !hasInvalidInternalState) |
| { |
| bool opCertStoreHasRoot = mOpCertStore->HasCertificateForFabric(fabricIndexBeingCommitted, CertChainElement::kRcac); |
| if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending) || !opCertStoreHasRoot) |
| { |
| ChipLogError(FabricProvisioning, "Missing trusted root for fabric add during commit!"); |
| hasInvalidInternalState = true; |
| } |
| } |
| |
| if ((isAdding || isUpdating) && hasPending && !hasInvalidInternalState) |
| { |
| if (!HasOperationalKeyForFabric(fabricIndexBeingCommitted)) |
| { |
| ChipLogError(FabricProvisioning, "Could not find an operational key during commit!"); |
| hasInvalidInternalState = true; |
| } |
| } |
| |
| // If there was nothing pending, we are either in a completely OK state, or weird internally inconsistent |
| // state. In either case, let's clear all pending state anyway, in case it was partially stale! |
| if (!hasPending || onlyHaveNewTrustedRoot || hasInvalidInternalState) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| if (onlyHaveNewTrustedRoot) |
| { |
| ChipLogError(FabricProvisioning, |
| "Failed to commit: tried to commit with only a new trusted root cert. No data committed."); |
| err = CHIP_ERROR_INCORRECT_STATE; |
| } |
| else if (hasInvalidInternalState) |
| { |
| ChipLogError(FabricProvisioning, "Failed to commit: internally inconsistent state!"); |
| err = CHIP_ERROR_INTERNAL; |
| } |
| else |
| { |
| // There was nothing pending and no error... |
| } |
| |
| // Clear all pending state anyway, in case it was partially stale! |
| { |
| mStateFlags.ClearAll(); |
| mFabricIndexWithPendingState = kUndefinedFabricIndex; |
| mPendingFabric.Reset(); |
| mOpCertStore->RevertPendingOpCerts(); |
| if (mOperationalKeystore != nullptr) |
| { |
| mOperationalKeystore->RevertPendingKeypair(); |
| } |
| } |
| |
| return err; |
| } |
| |
| // ==== Start of actual commit transaction after pre-flight checks ==== |
| CHIP_ERROR stickyError = StoreCommitMarker(CommitMarker{ fabricIndexBeingCommitted, isAdding }); |
| bool failedCommitMarker = (stickyError != CHIP_NO_ERROR); |
| if (failedCommitMarker) |
| { |
| ChipLogError(FabricProvisioning, "Failed to store commit marker, may be inconsistent if reboot happens during fail-safe!"); |
| } |
| |
| { |
| // This scope block is to illustrate the complete commit transaction |
| // state. We can see it contains a LARGE number of items... |
| |
| // Atomically assume data no longer pending, since we are committing it. Do so here |
| // so that FindFabricBy* will return real data and never pending. |
| mStateFlags.Clear(StateFlags::kIsPendingFabricDataPresent); |
| |
| if (isUpdating) |
| { |
| // This will get the non-pending fabric |
| FabricInfo * existingFabricToUpdate = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
| |
| // Multiple interlocks validated the below, so it's fatal if we are somehow incoherent here |
| VerifyOrDie((existingFabricToUpdate != nullptr) && (existingFabricToUpdate != &mPendingFabric)); |
| |
| // Commit the pending entry to local in-memory fabric metadata, which |
| // also moves operational keys if not backed by OperationalKeystore |
| *existingFabricToUpdate = std::move(mPendingFabric); |
| } |
| |
| // Store pending metadata first |
| FabricInfo * liveFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
| VerifyOrDie(liveFabricEntry != nullptr); |
| |
| CHIP_ERROR metadataErr = StoreFabricMetadata(liveFabricEntry); |
| if (metadataErr != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to commit pending fabric metadata: %" CHIP_ERROR_FORMAT, metadataErr.Format()); |
| } |
| stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : metadataErr; |
| |
| // We can only manage commissionable pending fail-safe state if we have a keystore |
| CHIP_ERROR keyErr = CHIP_NO_ERROR; |
| if ((mOperationalKeystore != nullptr) && mOperationalKeystore->HasOpKeypairForFabric(fabricIndexBeingCommitted) && |
| mOperationalKeystore->HasPendingOpKeypair()) |
| { |
| keyErr = mOperationalKeystore->CommitOpKeypairForFabric(fabricIndexBeingCommitted); |
| if (keyErr != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to commit pending operational keypair %" CHIP_ERROR_FORMAT, |
| keyErr.Format()); |
| mOperationalKeystore->RevertPendingKeypair(); |
| } |
| } |
| stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : keyErr; |
| |
| // For testing only, early return (NEVER OCCURS OTHERWISE) during the commit |
| // so that clean-ups using the commit marker can be tested. |
| #if CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| { |
| if (mStateFlags.Has(StateFlags::kAbortCommitForTest)) |
| { |
| // Clear state so that shutdown doesn't attempt clean-up |
| mStateFlags.ClearAll(); |
| mFabricIndexWithPendingState = kUndefinedFabricIndex; |
| mPendingFabric.Reset(); |
| |
| ChipLogError(FabricProvisioning, "Aborting commit in middle of transaction for testing."); |
| return CHIP_ERROR_INTERNAL; |
| } |
| } |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| // Commit operational certs |
| CHIP_ERROR opCertErr = mOpCertStore->CommitOpCertsForFabric(fabricIndexBeingCommitted); |
| if (opCertErr != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to commit pending operational certificates %" CHIP_ERROR_FORMAT, |
| opCertErr.Format()); |
| mOpCertStore->RevertPendingOpCerts(); |
| } |
| stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : opCertErr; |
| |
| // Failure to commit Last Known Good Time is non-fatal. If Last Known |
| // Good Time is incorrect and this causes incoming certificates to |
| // appear invalid, the certificate validity policy will see this |
| // condition and can act appropriately. |
| CHIP_ERROR lkgtErr = mLastKnownGoodTime.CommitPendingLastKnownGoodChipEpochTime(); |
| if (lkgtErr != CHIP_NO_ERROR) |
| { |
| // Log but this is not sticky... |
| ChipLogError(FabricProvisioning, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format()); |
| } |
| |
| // If an Add occurred, let's update the fabric index |
| CHIP_ERROR fabricIndexErr = CHIP_NO_ERROR; |
| if (mStateFlags.Has(StateFlags::kIsAddPending)) |
| { |
| UpdateNextAvailableFabricIndex(); |
| fabricIndexErr = StoreFabricIndexInfo(); |
| if (fabricIndexErr != CHIP_NO_ERROR) |
| { |
| ChipLogError(FabricProvisioning, "Failed to commit pending fabric indices: %" CHIP_ERROR_FORMAT, |
| fabricIndexErr.Format()); |
| } |
| } |
| stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : fabricIndexErr; |
| } |
| |
| // Commit must have same side-effect as reverting all pending data |
| mStateFlags.ClearAll(); |
| mFabricIndexWithPendingState = kUndefinedFabricIndex; |
| mPendingFabric.Reset(); |
| |
| if (stickyError != CHIP_NO_ERROR) |
| { |
| // Blow-away everything if we got past any storage, even on Update: system state is broken |
| // TODO: Develop a way to properly revert in the future, but this is very difficult |
| Delete(fabricIndexBeingCommitted); |
| |
| RevertPendingFabricData(); |
| } |
| else |
| { |
| NotifyFabricCommitted(fabricIndexBeingCommitted); |
| } |
| |
| // Clear commit marker no matter what: if we got here, there was no reboot and previous clean-ups |
| // did their job. |
| ClearCommitMarker(); |
| |
| return stickyError; |
| } |
| |
| void FabricTable::RevertPendingFabricData() |
| { |
| // Will clear pending UpdateNoc/AddNOC |
| RevertPendingOpCertsExceptRoot(); |
| |
| if (mOperationalKeystore != nullptr) |
| { |
| mOperationalKeystore->RevertPendingKeypair(); |
| } |
| |
| // Clear everything else |
| if (mOpCertStore != nullptr) |
| { |
| mOpCertStore->RevertPendingOpCerts(); |
| } |
| |
| mLastKnownGoodTime.RevertPendingLastKnownGoodChipEpochTime(); |
| |
| mStateFlags.ClearAll(); |
| mFabricIndexWithPendingState = kUndefinedFabricIndex; |
| } |
| |
| void FabricTable::RevertPendingOpCertsExceptRoot() |
| { |
| mPendingFabric.Reset(); |
| |
| if (mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent)) |
| { |
| ChipLogError(FabricProvisioning, "Reverting pending fabric data for fabric 0x%x", |
| static_cast<unsigned>(mFabricIndexWithPendingState)); |
| } |
| |
| if (mOpCertStore != nullptr) |
| { |
| mOpCertStore->RevertPendingOpCertsExceptRoot(); |
| } |
| |
| if (mStateFlags.Has(StateFlags::kIsAddPending)) |
| { |
| // If we have a pending add, let's make sure to kill the pending fabric metadata and return it to viable state. |
| Delete(mFabricIndexWithPendingState); |
| } |
| |
| mStateFlags.Clear(StateFlags::kIsAddPending); |
| mStateFlags.Clear(StateFlags::kIsUpdatePending); |
| if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending)) |
| { |
| mFabricIndexWithPendingState = kUndefinedFabricIndex; |
| } |
| } |
| |
| CHIP_ERROR FabricTable::SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel) |
| { |
| VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
| |
| ReturnErrorCodeIf(fabricLabel.size() > kFabricLabelMaxLengthInBytes, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
| bool fabricIsInitialized = (fabricInfo != nullptr) && fabricInfo->IsInitialized(); |
| VerifyOrReturnError(fabricIsInitialized, CHIP_ERROR_INVALID_FABRIC_INDEX); |
| |
| // Update fabric table current in-memory entry, whether pending or not |
| ReturnErrorOnFailure(fabricInfo->SetFabricLabel(fabricLabel)); |
| |
| if (!mStateFlags.HasAny(StateFlags::kIsAddPending, StateFlags::kIsUpdatePending) && (fabricInfo != &mPendingFabric)) |
| { |
| // Nothing is pending, we have to store immediately. |
| ReturnErrorOnFailure(StoreFabricMetadata(fabricInfo)); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR FabricTable::GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel) |
| { |
| const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
| VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
| |
| outFabricLabel = fabricInfo->GetFabricLabel(); |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace chip |