Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021-2022 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : /**
19 : * @brief Defines a table of fabrics that have provisioned the device.
20 : */
21 :
22 : #pragma once
23 :
24 : #include <algorithm>
25 :
26 : #include <app/util/basic-types.h>
27 : #include <credentials/CHIPCert.h>
28 : #include <credentials/CHIPCertificateSet.h>
29 : #include <credentials/CertificateValidityPolicy.h>
30 : #include <credentials/LastKnownGoodTime.h>
31 : #include <credentials/OperationalCertificateStore.h>
32 : #include <crypto/CHIPCryptoPAL.h>
33 : #include <crypto/OperationalKeystore.h>
34 : #include <lib/core/CHIPEncoding.h>
35 : #include <lib/core/CHIPPersistentStorageDelegate.h>
36 : #include <lib/core/CHIPSafeCasts.h>
37 : #include <lib/core/Optional.h>
38 : #include <lib/core/ScopedNodeId.h>
39 : #include <lib/core/TLV.h>
40 : #include <lib/support/BitFlags.h>
41 : #include <lib/support/CHIPMem.h>
42 : #include <lib/support/DLLUtil.h>
43 : #include <lib/support/Span.h>
44 :
45 : namespace chip {
46 :
47 : static constexpr uint8_t kFabricLabelMaxLengthInBytes = 32;
48 :
49 : static_assert(kUndefinedFabricIndex < chip::kMinValidFabricIndex, "Undefined fabric index should not be valid");
50 :
51 : /**
52 : * Provides access to the core metadata for a given fabric to which a node is joined.
53 : *
54 : * This metadata includes:
55 : *
56 : * - FabricIndex within the local set of fabrics
57 : * - Operational Identity
58 : * - NodeId
59 : * - Fabric Id
60 : * - Public key of operational root CA (to avoid keeping/reloading RCAC (Root CA Certificate) too often)
61 : * - Pre-computed "Compressed Fabric ID" used for discovery
62 : * - Operational public key (if externally injected as opposed to present in an OperationalKeystore)
63 : * - Fabric Label
64 : * - VendorID allocated at fabric joining by commissioner
65 : *
66 : * NOTE: All the setters of this class are private and only accessible by FabricTable, the
67 : * friend class that owns these. The reason is that there are data dependencies between
68 : * fabrics that require FabricTable to be the single entrypoint for all mutations, rather
69 : * than directly on a FabricInfo instance.
70 : */
71 : class DLL_EXPORT FabricInfo
72 : {
73 : public:
74 0 : FabricInfo() { Reset(); }
75 0 : ~FabricInfo() { Reset(); }
76 :
77 : // Non-copyable
78 : FabricInfo(FabricInfo const &) = delete;
79 : void operator=(FabricInfo const &) = delete;
80 :
81 : // Returns a span into our internal storage.
82 0 : CharSpan GetFabricLabel() const { return CharSpan(mFabricLabel, strnlen(mFabricLabel, kFabricLabelMaxLengthInBytes)); }
83 : CHIP_ERROR SetFabricLabel(const CharSpan & fabricLabel);
84 :
85 1317 : NodeId GetNodeId() const { return mNodeId; }
86 : ScopedNodeId GetScopedNodeId() const { return ScopedNodeId(mNodeId, mFabricIndex); }
87 0 : ScopedNodeId GetScopedNodeIdForNode(const NodeId node) const { return ScopedNodeId(node, mFabricIndex); }
88 :
89 : // TODO(#15049): Refactor/rename PeerId to OperationalId or OpId throughout source
90 0 : PeerId GetPeerId() const { return PeerId(mCompressedFabricId, mNodeId); }
91 : PeerId GetPeerIdForNode(const NodeId node) const { return PeerId(mCompressedFabricId, node); }
92 :
93 37 : FabricId GetFabricId() const { return mFabricId; }
94 37650 : FabricIndex GetFabricIndex() const { return mFabricIndex; }
95 :
96 21276 : CompressedFabricId GetCompressedFabricId() const { return mCompressedFabricId; }
97 0 : CHIP_ERROR GetCompressedFabricIdBytes(MutableByteSpan & compressedFabricId) const
98 : {
99 0 : VerifyOrReturnError(compressedFabricId.size() == sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT);
100 0 : Encoding::BigEndian::Put64(compressedFabricId.data(), GetCompressedFabricId());
101 0 : return CHIP_NO_ERROR;
102 : }
103 :
104 : CHIP_ERROR FetchRootPubkey(Crypto::P256PublicKey & outPublicKey) const;
105 :
106 0 : VendorId GetVendorId() const { return mVendorId; }
107 :
108 82182 : bool IsInitialized() const { return (mFabricIndex != kUndefinedFabricIndex) && IsOperationalNodeId(mNodeId); }
109 :
110 757 : bool HasOperationalKey() const { return mOperationalKey != nullptr; }
111 :
112 0 : bool ShouldAdvertiseIdentity() const { return mShouldAdvertiseIdentity; }
113 :
114 : friend class FabricTable;
115 :
116 : private:
117 : struct InitParams
118 : {
119 : CompressedFabricId compressedFabricId = kUndefinedCompressedFabricId;
120 : NodeId nodeId = kUndefinedNodeId;
121 : FabricIndex fabricIndex = kUndefinedFabricIndex;
122 : Crypto::P256Keypair * operationalKeypair = nullptr;
123 : FabricId fabricId = kUndefinedFabricId;
124 : Crypto::P256PublicKey rootPublicKey;
125 : VendorId vendorId = VendorId::NotSpecified; /**< Vendor ID for commissioner of fabric */
126 : bool hasExternallyOwnedKeypair = false;
127 : bool advertiseIdentity = false;
128 :
129 732 : CHIP_ERROR AreValid() const
130 : {
131 732 : VerifyOrReturnError((fabricId != kUndefinedFabricId) && (fabricIndex != kUndefinedFabricIndex),
132 : CHIP_ERROR_INVALID_ARGUMENT);
133 732 : VerifyOrReturnError(IsOperationalNodeId(nodeId), CHIP_ERROR_INVALID_ARGUMENT);
134 : // We don't check the root public key validity or the compressed fabric ID, since in the
135 : // very small usage that exists in private use, the rest should be OK.
136 732 : return CHIP_NO_ERROR;
137 : }
138 : };
139 :
140 : // Move assignment operator to support setting from pending on fabric table commit
141 : void operator=(FabricInfo && other);
142 :
143 : /**
144 : * @brief Initialize a FabricInfo object's metadata given init parameters.
145 : *
146 : * Note that certificates are never owned by this object and are assumed pre-validated
147 : *
148 : * @param initParams Init parameters to use to initialize the given fabric.
149 : * @return CHIP_NO_ERROR on success or another internal CHIP_ERROR_* value on failure
150 : */
151 : CHIP_ERROR Init(const InitParams & initParams);
152 :
153 : /**
154 : * Sets the P256Keypair used for this fabric. This will make a copy of the keypair
155 : * via the P256Keypair::Serialize and P256Keypair::Deserialize methods.
156 : *
157 : * The keyPair argument is safe to deallocate once this method returns.
158 : *
159 : * If your P256Keypair does not support serialization, use the
160 : * `SetExternallyOwnedOperationalKeypair` method instead.
161 : */
162 : CHIP_ERROR SetOperationalKeypair(const Crypto::P256Keypair * keyPair);
163 :
164 : /**
165 : * Sets the P256Keypair used for this fabric, delegating ownership of the
166 : * key to the caller. The P256Keypair provided here must be freed later by
167 : * the caller of this method if it was allocated dynamically.
168 : *
169 : * This should be used if your P256Keypair does not support serialization
170 : * and deserialization (e.g. your private key is held in a secure element
171 : * and cannot be accessed directly), or if you back your operational
172 : * private keys by external implementation of the cryptographic interfaces.
173 : *
174 : * To have the ownership of the key managed for you, use
175 : * SetOperationalKeypair instead.
176 : */
177 : CHIP_ERROR SetExternallyOwnedOperationalKeypair(Crypto::P256Keypair * keyPair);
178 :
179 : /**
180 : * @brief Sign a message with the fabric's operational private key. This ONLY
181 : * works if `SetOperationalKeypair` or `SetExternallyOwnedOperationalKeypair`
182 : * had been called and is an API that is present ONLY to be called by FabricTable.
183 : *
184 : * @param message - message to sign
185 : * @param outSignature - buffer to hold the signature
186 : * @return CHIP_NO_ERROR on success or another CHIP_ERROR on crypto internal errors
187 : */
188 : CHIP_ERROR SignWithOpKeypair(ByteSpan message, Crypto::P256ECDSASignature & outSignature) const;
189 :
190 : /**
191 : * Reset the state to a completely uninitialized status.
192 : */
193 0 : void Reset()
194 : {
195 0 : mNodeId = kUndefinedNodeId;
196 0 : mFabricId = kUndefinedFabricId;
197 0 : mFabricIndex = kUndefinedFabricIndex;
198 0 : mCompressedFabricId = kUndefinedCompressedFabricId;
199 :
200 0 : mVendorId = VendorId::NotSpecified;
201 0 : mFabricLabel[0] = '\0';
202 :
203 0 : if (!mHasExternallyOwnedOperationalKey && mOperationalKey != nullptr)
204 : {
205 0 : chip::Platform::Delete(mOperationalKey);
206 : }
207 0 : mOperationalKey = nullptr;
208 0 : mHasExternallyOwnedOperationalKey = false;
209 0 : mShouldAdvertiseIdentity = true;
210 :
211 0 : mFabricIndex = kUndefinedFabricIndex;
212 0 : mNodeId = kUndefinedNodeId;
213 0 : }
214 :
215 4 : void SetShouldAdvertiseIdentity(bool advertiseIdentity) { mShouldAdvertiseIdentity = advertiseIdentity; }
216 :
217 : static constexpr size_t MetadataTLVMaxSize()
218 : {
219 : return TLV::EstimateStructOverhead(sizeof(uint16_t), kFabricLabelMaxLengthInBytes);
220 : }
221 :
222 : static constexpr size_t OpKeyTLVMaxSize()
223 : {
224 : return TLV::EstimateStructOverhead(sizeof(uint16_t), Crypto::P256SerializedKeypair::Capacity());
225 : }
226 :
227 : NodeId mNodeId = kUndefinedNodeId;
228 : FabricId mFabricId = kUndefinedFabricId;
229 : // We cache the compressed fabric id since it's used so often and costly to get.
230 : CompressedFabricId mCompressedFabricId = kUndefinedCompressedFabricId;
231 : // We cache the root public key since it's used so often and costly to get.
232 : Crypto::P256PublicKey mRootPublicKey;
233 :
234 : // mFabricLabel is 33 bytes, so ends on a 1 mod 4 byte boundary.
235 : char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' };
236 :
237 : // mFabricIndex, mVendorId, mHasExternallyOwnedOperationalKey,
238 : // mShouldAdvertiseIdentity are 5 bytes and do not include any padding if
239 : // they come after the 33-byte mFabricLabel, so end on a 2 mod 4 byte
240 : // boundary.
241 : FabricIndex mFabricIndex = kUndefinedFabricIndex;
242 : VendorId mVendorId = VendorId::NotSpecified;
243 : bool mHasExternallyOwnedOperationalKey = false;
244 : bool mShouldAdvertiseIdentity = true;
245 :
246 : // 2 bytes of padding here, since mOperationalKey needs to be void*-aligned,
247 : // so has to be at a 0 mod 4 byte location.
248 :
249 : mutable Crypto::P256Keypair * mOperationalKey = nullptr;
250 :
251 : CHIP_ERROR CommitToStorage(PersistentStorageDelegate * storage) const;
252 : CHIP_ERROR LoadFromStorage(PersistentStorageDelegate * storage, FabricIndex newFabricIndex, const ByteSpan & rcac,
253 : const ByteSpan & noc);
254 : };
255 :
256 : /**
257 : * Iterates over valid fabrics within a list
258 : */
259 :
260 : class ConstFabricIterator
261 : {
262 : public:
263 : using value_type = FabricInfo;
264 : using pointer = FabricInfo *;
265 : using reference = FabricInfo &;
266 :
267 1486 : ConstFabricIterator(const FabricInfo * start, const FabricInfo * pending, size_t index, size_t maxSize) :
268 1486 : mStart(start), mPending(pending), mIndex(index), mMaxSize(maxSize)
269 : {
270 1486 : if (mIndex >= maxSize)
271 : {
272 743 : mIndex = maxSize;
273 : }
274 743 : else if (!mStart[mIndex].IsInitialized())
275 : {
276 26 : Advance();
277 : }
278 1486 : }
279 : ConstFabricIterator(const ConstFabricIterator &) = default;
280 : ConstFabricIterator & operator=(const ConstFabricIterator &) = default;
281 :
282 1047 : ConstFabricIterator & operator++() { return Advance(); }
283 : ConstFabricIterator operator++(int)
284 : {
285 : ConstFabricIterator other(*this);
286 : Advance();
287 : return other;
288 : }
289 :
290 1073 : const FabricInfo & operator*() const
291 : {
292 1073 : VerifyOrDie(!IsAtEnd());
293 :
294 1073 : return *GetCurrent();
295 : }
296 0 : const FabricInfo * operator->() const
297 : {
298 0 : VerifyOrDie(!IsAtEnd());
299 :
300 0 : return GetCurrent();
301 : }
302 :
303 1790 : bool operator==(const ConstFabricIterator & other) const
304 : {
305 1790 : if (IsAtEnd())
306 : {
307 717 : return other.IsAtEnd();
308 : }
309 :
310 : // Pending entry does not participate in finding this.
311 1073 : return (mStart == other.mStart) && (mIndex == other.mIndex) && (mMaxSize == other.mMaxSize);
312 : }
313 1790 : bool operator!=(const ConstFabricIterator & other) const { return !(*this == other); }
314 :
315 15059 : bool IsAtEnd() const { return (mIndex == mMaxSize); }
316 :
317 : private:
318 : const FabricInfo * mStart;
319 : const FabricInfo * mPending; ///< Pointer to the shadow pending entry, nullptr if none
320 : size_t mIndex;
321 : size_t mMaxSize;
322 :
323 : // Helper to get either a given entry of the fabric table, or its pending shadow if
324 : // a fabric update is currently pending.
325 1073 : const FabricInfo * GetCurrent() const
326 : {
327 1073 : const auto * current = mStart + mIndex;
328 :
329 : // If we reached the pending entry, return that instead of the underlying entry from the mStates.
330 1073 : if ((mPending != nullptr) && mPending->IsInitialized() && (current->GetFabricIndex() == mPending->GetFabricIndex()))
331 : {
332 0 : current = mPending;
333 : }
334 :
335 1073 : return current;
336 : }
337 :
338 11479 : ConstFabricIterator & Advance()
339 : {
340 : do
341 : {
342 11479 : if (mIndex < mMaxSize)
343 : {
344 11479 : mIndex++;
345 : }
346 11479 : } while (!IsAtEnd() && !mStart[mIndex].IsInitialized());
347 :
348 1073 : return *this;
349 : }
350 : };
351 :
352 : class DLL_EXPORT FabricTable
353 : {
354 : public:
355 : struct DLL_EXPORT InitParams
356 : {
357 : // PersistentStorageDelegate for Fabric Info metadata storage and Fabric Table index (MANDATORY).
358 : PersistentStorageDelegate * storage = nullptr;
359 : // Operational Keystore to abstract access to key. Mandatory for commissionable devices (e.g.
360 : // chip::Server-based things) and recommended for controllers. With this set to false, FabricInfo
361 : // added as new fabrics need to have directly injected operational keys with FabricInfo::Set*OperationalKey.
362 : Crypto::OperationalKeystore * operationalKeystore = nullptr;
363 : // Operational Certificate store to hold the NOC/ICAC/RCAC chains (MANDATORY).
364 : Credentials::OperationalCertificateStore * opCertStore = nullptr;
365 : };
366 :
367 : class DLL_EXPORT Delegate
368 : {
369 : public:
370 483 : Delegate() {}
371 483 : virtual ~Delegate() {}
372 :
373 : /**
374 : * Gets called when a fabric is about to be deleted, such as on
375 : * FabricTable::Delete(). This allows actions to be taken that need the
376 : * fabric to still be around before we delete it.
377 : **/
378 14 : virtual void FabricWillBeRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) {}
379 :
380 : /**
381 : * Gets called when a fabric is deleted, such as on FabricTable::Delete().
382 : **/
383 0 : virtual void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) {}
384 :
385 : /**
386 : * Gets called when a fabric in Fabric Table is persisted to storage, by CommitPendingFabricData.
387 : **/
388 688 : virtual void OnFabricCommitted(const FabricTable & fabricTable, FabricIndex fabricIndex){};
389 :
390 : /**
391 : * Gets called when operational credentials are changed, which may not be persistent.
392 : *
393 : * Can be used to affect what is needed for UpdateNOC prior to commit.
394 : **/
395 688 : virtual void OnFabricUpdated(const FabricTable & fabricTable, FabricIndex fabricIndex){};
396 :
397 : // Intrusive list pointer for FabricTable to manage the entries.
398 : Delegate * next = nullptr;
399 : };
400 :
401 : public:
402 0 : FabricTable() = default;
403 0 : ~FabricTable() = default;
404 :
405 : // Non-copyable
406 : FabricTable(FabricTable const &) = delete;
407 : void operator=(FabricTable const &) = delete;
408 :
409 : enum class AdvertiseIdentity : uint8_t
410 : {
411 : Yes,
412 : No
413 : };
414 :
415 : /**
416 : * @brief Delete the fabric with given `fabricIndex`.
417 : *
418 : * @param fabricIndex - Index of fabric for deletion
419 : * @retval CHIP_NO_ERROR on success
420 : * @retval CHIP_ERROR_NOT_FOUND if there is no fabric for that index
421 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds
422 : * @retval other CHIP_ERROR on internal errors
423 : */
424 : CHIP_ERROR Delete(FabricIndex fabricIndex);
425 : void DeleteAllFabrics();
426 :
427 : // TODO this #if CONFIG_BUILD_FOR_HOST_UNIT_TEST is temporary. There is a change incoming soon
428 : // that will allow triggering NOC update directly.
429 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
430 : void SendUpdateFabricNotificationForTest(FabricIndex fabricIndex) { NotifyFabricUpdated(fabricIndex); }
431 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
432 :
433 : /**
434 : * Collection of methods to help find a matching FabricInfo instance given a set of query criteria
435 : *
436 : */
437 :
438 : /**
439 : * Finds a matching FabricInfo instance given a root public key and fabric ID that uniquely identifies the fabric in any scope.
440 : *
441 : * Returns nullptr if no matching instance is found.
442 : *
443 : */
444 : const FabricInfo * FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const;
445 :
446 : /**
447 : * Finds a matching FabricInfo instance given a locally-scoped fabric index.
448 : *
449 : * Returns nullptr if no matching instance is found.
450 : *
451 : */
452 : const FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex) const;
453 :
454 : /**
455 : * Finds a matching FabricInfo instance given a root public key, fabric ID AND a matching NodeId. This variant of find
456 : * is only to be used when it is possible to have colliding fabrics in the table that are on the same logical fabric
457 : * but may be associated with different node identities.
458 : *
459 : * Returns nullptr if no matching instance is found.
460 : *
461 : */
462 : const FabricInfo * FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const;
463 :
464 : /**
465 : * Finds a matching FabricInfo instance given a compressed fabric ID. If there are multiple
466 : * matching FabricInfo instances given the low but non-zero probability of collision, there is no guarantee
467 : * on which instance will be returned.
468 : *
469 : * Returns nullptr if no matching instance is found.
470 : */
471 : const FabricInfo * FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const;
472 :
473 : CHIP_ERROR Init(const FabricTable::InitParams & initParams);
474 : void Shutdown();
475 :
476 : /**
477 : * @brief If `Init()` caused a Delete due to partial commit, the fabric index at play is returned.
478 : *
479 : * Allows caller to schedule more clean-up. This is because at Init() time, none of the delegates
480 : * are registered yet, so no other modules would learn of the removal.
481 : *
482 : * The value is auto-reset to `kUndefinedFabricIndex` on being returned, so that subsequent
483 : * `GetDeletedFabricFromCommitMarker()` after one that has a fabric index to give will provide
484 : * `kUndefinedFabricIndex`.
485 : *
486 : * @return the fabric index of a just-deleted fabric, or kUndefinedFabricIndex if none were deleted.
487 : */
488 : FabricIndex GetDeletedFabricFromCommitMarker();
489 :
490 : /**
491 : * @brief Clear the commit marker when we are sure we have proceeded with any remaining clean-up
492 : */
493 : void ClearCommitMarker();
494 :
495 : // Forget a fabric in memory: doesn't delete any persistent state, just
496 : // reverts any pending state (blindly) and then resets the fabric table
497 : // entry.
498 : //
499 : // TODO: We have to determine if we should remove this call.
500 : void Forget(FabricIndex fabricIndex);
501 :
502 : CHIP_ERROR AddFabricDelegate(FabricTable::Delegate * delegate);
503 : void RemoveFabricDelegate(FabricTable::Delegate * delegate);
504 :
505 : /**
506 : * @brief Set the Fabric Label for the fabric referred by `fabricIndex`.
507 : *
508 : * If a fabric add/update is pending, only the pending version will be updated,
509 : * so that on fail-safe expiry, you would actually see the only fabric label if
510 : * Update fails. If the fabric label is set before UpdateNOC, then the change is immediate.
511 : *
512 : * @param fabricIndex - Fabric Index for which to set the label
513 : * @param fabricLabel - Label to set on the fabric
514 : * @retval CHIP_NO_ERROR on success
515 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to an fabric in the table
516 : * @retval CHIP_ERROR_INVALID_ARGUMENT on fabric label error (e.g. too large)
517 : * @retval other CHIP_ERROR on internal errors
518 : */
519 : CHIP_ERROR SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel);
520 :
521 : /**
522 : * @brief Get the Fabric Label for a given fabric
523 : *
524 : * NOTE: The outFabricLabel argument points to internal memory of the fabric info.
525 : * It may become invalid on the next FabricTable API call due to shadow
526 : * storage of data.
527 : *
528 : * @param fabricIndex - Fabric index for which to get the label
529 : * @param outFabricLabel - char span that will be set to the label value
530 : * @retval CHIP_NO_ERROR on success
531 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX on error
532 : * @retval other CHIP_ERROR on internal errors
533 : */
534 : CHIP_ERROR GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel);
535 :
536 : /**
537 : * Get the current Last Known Good Time.
538 : *
539 : * @param lastKnownGoodChipEpochTime (out) the current last known good time, if any is known
540 : * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR
541 : */
542 0 : CHIP_ERROR GetLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const
543 : {
544 0 : return mLastKnownGoodTime.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime);
545 : }
546 :
547 : /**
548 : * Validate that the passed Last Known Good Time is within bounds and then
549 : * store this and write back to storage. Legal values are those which are
550 : * not earlier than firmware build time or any of our stored certificates'
551 : * NotBefore times:
552 : *
553 : * 3.5.6.1. Last Known Good UTC Time
554 : *
555 : * A Node MAY adjust the Last Known Good UTC Time backwards if it
556 : * believes the current Last Known Good UTC Time is incorrect and it has
557 : * a good time value from a trusted source. The Node SHOULD NOT adjust
558 : * the Last Known Good UTC to a time before the later of:
559 : * • The build timestamp of its currently running software image
560 : * • The not-before timestamp of any of its operational certificates
561 : *
562 : * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch
563 : * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR
564 : */
565 : CHIP_ERROR SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime);
566 :
567 : /**
568 : * @return the number of fabrics currently accessible/usable/iterable.
569 : */
570 8 : uint8_t FabricCount() const { return mFabricCount; }
571 :
572 743 : ConstFabricIterator cbegin() const
573 : {
574 743 : const FabricInfo * pending = GetShadowPendingFabricEntry();
575 743 : return ConstFabricIterator(mStates, pending, 0, CHIP_CONFIG_MAX_FABRICS);
576 : }
577 743 : ConstFabricIterator cend() const
578 : {
579 743 : return ConstFabricIterator(mStates, nullptr, CHIP_CONFIG_MAX_FABRICS, CHIP_CONFIG_MAX_FABRICS);
580 : }
581 743 : ConstFabricIterator begin() const { return cbegin(); }
582 743 : ConstFabricIterator end() const { return cend(); }
583 :
584 : /**
585 : * @brief Get the RCAC (operational root certificate) associated with a fabric.
586 : *
587 : * If a root is pending for `fabricIndex` from `AddNewPendingTrustedRootCert`, it is returned.
588 : *
589 : * @param fabricIndex - Fabric for which to get the RCAC
590 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
591 : * @retval CHIP_NO_ERROR on success
592 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
593 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
594 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
595 : */
596 : CHIP_ERROR FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
597 :
598 : /**
599 : * @brief Get the pending root certificate which is not associated with a fabric, if there is one.
600 : *
601 : * If a root is pending from `AddNewPendingTrustedRootCert`, and there is no
602 : * fabric associated with the corresponding fabric index yet
603 : * (i.e. `AddNewPendingFabric*` has not been called yet) it is returned.
604 : *
605 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
606 : * @retval CHIP_NO_ERROR on success
607 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small.
608 : * @retval CHIP_ERROR_NOT_FOUND if there is no pending root certificate
609 : * that's not yet associated with a fabric.
610 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
611 : */
612 : CHIP_ERROR FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const;
613 :
614 : /**
615 : * @brief Get the ICAC (operational intermediate certificate) associated with a fabric.
616 : *
617 : * If a fabric is pending from add/update operation for the given `fabricIndex`, its
618 : * ICAC is returned.
619 : *
620 : * If an NOC exists, but the ICAC is not present in the chain, CHIP_NO_ERROR is
621 : * returned and `outCert` is resized to 0 length so that its `empty()` method returns true.
622 : *
623 : * @param fabricIndex - Fabric for which to get the ICAC
624 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
625 : * @retval CHIP_NO_ERROR on success, including if absent within an existing chain
626 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
627 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
628 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
629 : */
630 : CHIP_ERROR FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
631 :
632 : /**
633 : * @brief Get the NOC (Node Operational Certificate) associated with a fabric.
634 : *
635 : * If a fabric is pending from add/update operation for the given `fabricIndex`, its
636 : * NOC is returned.
637 : *
638 : * @param fabricIndex - Fabric for which to get the NOC
639 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
640 : * @retval CHIP_NO_ERROR on success
641 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
642 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
643 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
644 : */
645 : CHIP_ERROR FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
646 :
647 : /**
648 : * @brief Get the root public key by value for the given `fabricIndex`.
649 : *
650 : * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC)
651 : * @param outPublicKey - PublicKey instance to receive the public key contents
652 : * @retval CHIP_NO_ERROR on success
653 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
654 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value
655 : * @retval other CHIP_ERROR values on other invalid arguments or internal errors.
656 : */
657 : CHIP_ERROR FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const;
658 :
659 : /**
660 : * @brief Get the CASE Authenticated Tags from the NOC for the given `fabricIndex`.
661 : *
662 : * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC)
663 : * @param cats - CATValues struct to write the NOC CATs for the given fabric index
664 : * @retval CHIP_NO_ERROR on success
665 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value
666 : * @retval other CHIP_ERROR values on other invalid arguments or internal errors.
667 : */
668 : CHIP_ERROR FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const;
669 :
670 : /**
671 : * @brief Sign a message with a given fabric's operational keypair. This is used for
672 : * CASE and the only way the key should be used.
673 : *
674 : * This will use a pending key activated with `ActivatePendingOperationalKey` but
675 : * not yet persisted, if one is available for the fabric.
676 : *
677 : * @param fabricIndex - Fabric index whose operational key touse
678 : * @param message - Message to sign
679 : * @param outSignature - Signature object to receive the signature
680 : *
681 : * @retval CHIP_NO_ERROR on success
682 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if no active key is found for the given `fabricIndex` or if
683 : * `fabricIndex` is invalid.
684 : * @retval other CHIP_ERROR value on internal errors
685 : */
686 : CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, Crypto::P256ECDSASignature & outSignature) const;
687 :
688 : /**
689 : * @brief Create an ephemeral keypair for use in session establishment.
690 : *
691 : * WARNING: The return value MUST be released by `ReleaseEphemeralKeypair`. This is because
692 : * Matter CHIPMem.h does not properly support UniquePtr in a way that would
693 : * safely allow classes derived from Crypto::P256Keypair to be released properly.
694 : *
695 : * This delegates to the OperationalKeystore if one exists, otherwise it directly allocates a base
696 : * Crypto::P256Keypair instance
697 : *
698 : * @return a pointer to a dynamically P256Keypair (or derived class thereof), which may evaluate to nullptr
699 : * if running out of memory.
700 : */
701 : Crypto::P256Keypair * AllocateEphemeralKeypairForCASE();
702 :
703 : /**
704 : * @brief Release an ephemeral keypair previously created by `AllocateEphemeralKeypairForCASE()`
705 : */
706 : void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair);
707 :
708 : /**
709 : * This initializes a new keypair for the given fabric and generates a CSR for it,
710 : * so that it can be passed in a CSRResponse.
711 : *
712 : * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either
713 : * `ActivatePendingOperationalKey` is called. It is destroyed if
714 : * `RevertPendingFabricData` is called before `CommitPendingFabricData`.
715 : * If a pending keypair for the provided fabricIndex (if present) already existed, it is replaced by this call.
716 : *
717 : * Only one pending operational keypair is supported at a time.
718 : *
719 : * @param fabricIndex - Existing FabricIndex for which a new keypair must be made available. If it
720 : * doesn't have a value, the key will be marked pending for the next available
721 : * fabric index that would apply for `AddNewFabric`.
722 : * @param outputCsr - Buffer to contain the CSR. Must be at least `kMIN_CSR_Buffer_Size` large.
723 : *
724 : * @retval CHIP_NO_ERROR on success
725 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outputCsr` buffer is too small
726 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is already a pending keypair for another `fabricIndex` value
727 : * or if fabricIndex is an invalid value.
728 : * @retval other CHIP_ERROR value on internal errors
729 : */
730 : CHIP_ERROR AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr);
731 :
732 : /**
733 : * @brief Returns whether an operational key is pending (true if `AllocatePendingOperationalKey` was
734 : * previously successfully called, false otherwise).
735 : *
736 : * @param outIsPendingKeyForUpdateNoc this is set to true if the `AllocatePendingOperationalKey` had an
737 : * associated fabric index attached, indicating it's for UpdateNoc
738 : */
739 : bool HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const;
740 :
741 : /**
742 : * @brief Returns whether an operational key can be used to sign for given FabricIndex
743 : *
744 : * @param fabricIndex - Fabric index for which an operational key must be found
745 : * @return true if a pending fabric or committed fabric for fabricIndex has an operational key, false otherwise.
746 : */
747 : bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const;
748 :
749 : /**
750 : * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending.
751 : *
752 : * A newly-added fabric is pending if AddNOC has been previously called successfully but the
753 : * fabric is not yet fully committed by CommissioningComplete.
754 : *
755 : * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending.
756 : *
757 : * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending.
758 : */
759 : FabricIndex GetPendingNewFabricIndex() const;
760 :
761 : /**
762 : * @brief Returns the operational keystore. This is used for
763 : * CASE and the only way the keystore should be used.
764 : *
765 : * @return The operational keystore, nullptr otherwise.
766 : */
767 7 : const Crypto::OperationalKeystore * GetOperationalKeystore() { return mOperationalKeystore; }
768 :
769 : /**
770 : * @brief Add a pending trusted root certificate for the next fabric created with `AddNewPendingFabric*` methods.
771 : *
772 : * The root only becomes actually pending when the `AddNewPendingFabric*` is called afterwards. It is reverted
773 : * if `RevertPendingFabricData` is called.
774 : *
775 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
776 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
777 : * `UpdatePendingFabric` which would mean logical collision of an addition and an update.
778 : *
779 : * @param rcac - Root certificate in Matter Operational Certificate Encoding (TLV) format
780 : * @retval CHIP_NO_ERROR on success
781 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
782 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the root pending
783 : * @retval CHIP_ERROR_INVALID_ARGUMENT if the RCAC is too large (further checks are done on `AddNewPendingFabric*`)
784 : * @retval other CHIP_ERROR on internal errors.
785 : */
786 : CHIP_ERROR AddNewPendingTrustedRootCert(const ByteSpan & rcac);
787 :
788 : /**
789 : * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric
790 : *
791 : * Operational key is assumed to be pending or committed in the associated mOperationalKeystore.
792 : *
793 : * The fabric becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc.
794 : * The new fabric becomes permanent/persisted on successful `CommitPendingFabricData`. It disappears
795 : * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`.
796 : *
797 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
798 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
799 : * `UpdatePendingFabric*` which would mean logical collision of an addition and an update.
800 : *
801 : * If a pending key was present in the OperationalKeystore associated with this FabricTable,
802 : * it is activated on success.
803 : *
804 : *
805 : * @param noc - NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
806 : * @param icac - ICAC for the fabric. Can be empty if absent from the chain.
807 : * @param vendorId - VendorID to use for the new fabric
808 : * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr.
809 : *
810 : * @retval CHIP_NO_ERROR on success.
811 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order.
812 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending.
813 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
814 : * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present.
815 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
816 : */
817 0 : CHIP_ERROR AddNewPendingFabricWithOperationalKeystore(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
818 : FabricIndex * outNewFabricIndex,
819 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
820 : {
821 0 : return AddNewPendingFabricCommon(noc, icac, vendorId, nullptr, false, advertiseIdentity, outNewFabricIndex);
822 : };
823 :
824 : /**
825 : * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric
826 : *
827 : * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or
828 : * owned externally if `isExistingOpKeyExternallyOwned` is true).
829 : *
830 : * WARNING: Copying keypairs is unsafe and not recommended. Consider using
831 : * AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore
832 : * or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe
833 : * class derived from P256Keypair that avoids the true private key persisting in memory.
834 : *
835 : * For rest of semantics outside of operational key, @see AddNewPendingFabricWithOperationalKeystore
836 : *
837 : * @param noc - NOC for the fabric. Public key must match the `existingOpKey`'s public key
838 : * @param icac - ICAC for the fabric. Can be empty if absent from the chain.
839 : * @param vendorId - VendorID to use for the new fabric
840 : * @param existingOpKey - Existing operational key to ingest for use in the fabric. Cannot be nullptr.
841 : * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is
842 : * copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo.
843 : * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr.
844 : *
845 : * @retval CHIP_NO_ERROR on success.
846 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order.
847 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending.
848 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
849 : * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present.
850 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
851 : */
852 685 : CHIP_ERROR AddNewPendingFabricWithProvidedOpKey(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
853 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
854 : FabricIndex * outNewFabricIndex,
855 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
856 : {
857 685 : return AddNewPendingFabricCommon(noc, icac, vendorId, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity,
858 685 : outNewFabricIndex);
859 : };
860 :
861 : /**
862 : * @brief Use an NOC and optional ICAC to update an existing fabric
863 : *
864 : * Operational key is assumed to be pending or committed in the associated mOperationalKeystore.
865 : *
866 : * The new NOC chain becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc.
867 : * The RCAC remains as before. For this method call to succeed, NOC chain must chain back to the existing RCAC.
868 : * The update fabric becomes permanent/persisted on successful `CommitPendingFabricData`. Changes revert
869 : * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`. FabricId CANNOT be updated, but
870 : * CAT tags and Node ID in NOC can change between previous and new NOC for a given FabricId.
871 : *
872 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
873 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
874 : * `AddNewPending*` which would mean logical collision of an addition and an update.
875 : *
876 : * If a pending key was present in the OperationalKeystore associated with this FabricTable,
877 : * it is activated on success.
878 : *
879 : * @param fabricIndex - fabricIndex of the existing fabric to update
880 : * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
881 : * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain.
882 : *
883 : * @retval CHIP_NO_ERROR on success
884 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric
885 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
886 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates
887 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
888 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
889 : */
890 0 : CHIP_ERROR UpdatePendingFabricWithOperationalKeystore(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
891 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
892 : {
893 0 : return UpdatePendingFabricCommon(fabricIndex, noc, icac, nullptr, false, advertiseIdentity);
894 : }
895 :
896 : /**
897 : * @brief Use an NOC and optional ICAC to update an existing fabric
898 : *
899 : * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or
900 : * owned externally if `isExistingOpKeyExternallyOwned` is true).
901 : *
902 : * WARNING: Copying keypairs is unsafe and not recommended. Consider using
903 : * AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore
904 : * or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe
905 : * class derived from P256Keypair that avoids the true private key persisting in memory.
906 : *
907 : * For rest of semantics outside of operational key, @see UpdatePendingFabricWithOperationalKeystore
908 : *
909 : * @param fabricIndex - fabricIndex of the existing fabric to update
910 : * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
911 : * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain.
912 : * @param existingOpKey - Existing operational key to ingest for use in the fabric with new NOC. Cannot be nullptr.
913 : * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is
914 : * copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo.
915 : *
916 : * @retval CHIP_NO_ERROR on success
917 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric
918 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
919 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates
920 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
921 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
922 : */
923 :
924 0 : CHIP_ERROR UpdatePendingFabricWithProvidedOpKey(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
925 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
926 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
927 : {
928 0 : return UpdatePendingFabricCommon(fabricIndex, noc, icac, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity);
929 : }
930 :
931 : /**
932 : * @brief Commit any pending temporary FabricTable state. This is used mostly for affecting
933 : * CommissioningComplete.
934 : *
935 : * On success, any pending information is committed such that after a restart, it would
936 : * be found to be the same in persistent storage.
937 : *
938 : * If no changes were pending and state is internally consistent, this appears as a no-op and returns
939 : * CHIP_NO_ERROR.
940 : *
941 : * If there is any internally inconsistent state, this methods acts the same as RevertPendingFabricData(),
942 : * and all state is lost.
943 : *
944 : * In rare circumstances, and depending on the storage backend for opcerts and operational keys,
945 : * an inconsistent state could be left, such as if restarting during storage writes of
946 : * CommitPendingFabricData(). If this happens, the next FabricTable::Init() will attempt
947 : * to clean-up the pieces.
948 : *
949 : * @return CHIP_NO_ERROR on success or any other CHIP_ERROR value on internal errors
950 : */
951 : CHIP_ERROR CommitPendingFabricData();
952 :
953 : /**
954 : * @brief Revert any pending state.
955 : *
956 : * This is used to handle fail-safe expiry of partially configured fabrics, or to recover
957 : * from situations where partial state was written and configuration cannot continue properly.
958 : *
959 : * All pending certificates and operational keys and pending fabric metadata are cleared.
960 : */
961 : void RevertPendingFabricData();
962 :
963 : /**
964 : * @brief Revert only the pending NOC/ICAC and pending added fabric, not RCAC. Used for error handling
965 : * during commissioning.
966 : */
967 : void RevertPendingOpCertsExceptRoot();
968 :
969 : // Verifies credentials, using the root certificate of the provided fabric index.
970 : CHIP_ERROR VerifyCredentials(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
971 : Credentials::ValidationContext & context, CompressedFabricId & outCompressedFabricId,
972 : FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
973 : Crypto::P256PublicKey * outRootPublicKey = nullptr) const;
974 :
975 : // Verifies credentials, using the provided root certificate.
976 : static CHIP_ERROR VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
977 : Credentials::ValidationContext & context, CompressedFabricId & outCompressedFabricId,
978 : FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
979 : Crypto::P256PublicKey * outRootPublicKey = nullptr);
980 : /**
981 : * @brief Enables FabricInfo instances to collide and reference the same logical fabric (i.e Root Public Key + FabricId).
982 : *
983 : * *WARNING* This is ONLY to be used when creating multiple controllers on the same fabric OR for test.
984 : *
985 : */
986 0 : void PermitCollidingFabrics() { mStateFlags.Set(StateFlags::kAreCollidingFabricsIgnored); }
987 :
988 : // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will
989 : // get copied (directly) into the fabric table.
990 : CHIP_ERROR AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
991 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
992 :
993 : // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will
994 : // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending.
995 : CHIP_ERROR AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
996 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
997 :
998 : // Same as AddNewFabricForTest, but ignore if we are colliding with same <Root Public Key, Fabric Id>, so
999 : // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form.
1000 : CHIP_ERROR AddNewFabricForTestIgnoringCollisions(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
1001 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
1002 : {
1003 : PermitCollidingFabrics();
1004 : CHIP_ERROR err = AddNewFabricForTest(rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
1005 : mStateFlags.Clear(StateFlags::kAreCollidingFabricsIgnored);
1006 : return err;
1007 : }
1008 :
1009 : // For test only. See definition of `StateFlags::kAbortCommitForTest`.
1010 : void SetForceAbortCommitForTest(bool abortCommitForTest)
1011 : {
1012 : (void) abortCommitForTest;
1013 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
1014 : if (abortCommitForTest)
1015 : {
1016 : mStateFlags.Set(StateFlags::kAbortCommitForTest);
1017 : }
1018 : else
1019 : {
1020 : mStateFlags.Clear(StateFlags::kAbortCommitForTest);
1021 : }
1022 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1023 : }
1024 :
1025 : /**
1026 : * Get the fabric index that will be used for the next fabric that will be
1027 : * added. Returns error if no more fabrics can be added, otherwise writes
1028 : * the fabric index that will be used for the next addition into the
1029 : * outparam.
1030 : */
1031 : CHIP_ERROR PeekFabricIndexForNextAddition(FabricIndex & outIndex);
1032 :
1033 : /**
1034 : * Set the fabric index that will be used fo the next fabric added.
1035 : *
1036 : * Returns an error if the |fabricIndex| is already in use.
1037 : */
1038 : CHIP_ERROR SetFabricIndexForNextAddition(FabricIndex fabricIndex);
1039 :
1040 : /**
1041 : * @brief Set the advertising behavior for the fabric identified by `fabricIndex`.
1042 : *
1043 : * It is the caller's responsibility to actually restart DNS-SD advertising
1044 : * as needed after updating this state.
1045 : *
1046 : * @param fabricIndex - Fabric Index for which to set the label
1047 : * @param advertiseIdentity - whether the identity for this fabric should be advertised.
1048 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to a fabric in the table
1049 : */
1050 : CHIP_ERROR SetShouldAdvertiseIdentity(FabricIndex fabricIndex, AdvertiseIdentity advertiseIdentity);
1051 :
1052 : private:
1053 : enum class StateFlags : uint16_t
1054 : {
1055 : // If true, we are in the process of a fail-safe and there was at least one
1056 : // operation that caused partial data in the fabric table.
1057 : kIsPendingFabricDataPresent = (1u << 0),
1058 : kIsTrustedRootPending = (1u << 1),
1059 : kIsUpdatePending = (1u << 2),
1060 : kIsAddPending = (1u << 3),
1061 :
1062 : // Only true when `AllocatePendingOperationalKey` has been called
1063 : kIsOperationalKeyPending = (1u << 4),
1064 : // True if `AllocatePendingOperationalKey` was for an existing fabric
1065 : kIsPendingKeyForUpdateNoc = (1u << 5),
1066 :
1067 : // True if we allow more than one fabric with same root and fabricId in the fabric table
1068 : // for test purposes. This disables a collision check.
1069 : kAreCollidingFabricsIgnored = (1u << 6),
1070 :
1071 : // If set to true (only possible on test builds), will cause `CommitPendingFabricData()` to early
1072 : // return during commit, skipping clean-ups, so that we can validate commit marker fabric removal.
1073 : kAbortCommitForTest = (1u << 7),
1074 : };
1075 :
1076 : // Stored to indicate a commit is in progress, so that it can be cleaned-up on next boot
1077 : // if stopped in the middle.
1078 : struct CommitMarker
1079 : {
1080 : CommitMarker() = default;
1081 721 : CommitMarker(FabricIndex fabricIndex_, bool isAddition_)
1082 721 : {
1083 721 : this->fabricIndex = fabricIndex_;
1084 721 : this->isAddition = isAddition_;
1085 721 : }
1086 : FabricIndex fabricIndex = kUndefinedFabricIndex;
1087 : bool isAddition = false;
1088 : };
1089 :
1090 : /**
1091 : * @brief Get a mutable FabricInfo entry from the table by FabricIndex.
1092 : *
1093 : * NOTE: This is private for use within the FabricTable itself. All mutations have to go through the
1094 : * FabricTable public methods that take a FabricIndex so that there are no mutations about which
1095 : * the FabricTable is unaware, since this would break expectations regarding shadow/pending
1096 : * entries used during fail-safe.
1097 : *
1098 : * @param fabricIndex - fabric index for which to get a mutable FabricInfo entry
1099 : * @return the FabricInfo entry for the fabricIndex if found, or nullptr if not found
1100 : */
1101 : FabricInfo * GetMutableFabricByIndex(FabricIndex fabricIndex);
1102 :
1103 : // Load a FabricInfo metatada item from storage for a given new fabric index. Returns internal error on failure.
1104 : CHIP_ERROR LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex);
1105 :
1106 : // Store a given fabric metadata directly/immediately. Used by internal operations.
1107 : CHIP_ERROR StoreFabricMetadata(const FabricInfo * fabricInfo) const;
1108 :
1109 : // Tries to set `mFabricIndexWithPendingState` and returns false if there's a clash.
1110 : bool SetPendingDataFabricIndex(FabricIndex fabricIndex);
1111 :
1112 : // Core validation logic for fabric additions/updates
1113 : CHIP_ERROR AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey,
1114 : bool isExistingOpKeyExternallyOwned, uint16_t vendorId, AdvertiseIdentity advertiseIdentity);
1115 :
1116 : // Common code for fabric addition, for either OperationalKeystore or injected key scenarios.
1117 : CHIP_ERROR AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
1118 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
1119 : AdvertiseIdentity advertiseIdentity, FabricIndex * outNewFabricIndex);
1120 :
1121 : // Common code for fabric updates, for either OperationalKeystore or injected key scenarios.
1122 : CHIP_ERROR UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
1123 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
1124 : AdvertiseIdentity advertiseIdentity);
1125 :
1126 : // Common code for looking up a fabric given a root public key, a fabric ID and an optional node id scoped to that fabric.
1127 : const FabricInfo * FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId,
1128 : NodeId nodeId = kUndefinedNodeId) const;
1129 :
1130 : /**
1131 : * UpdateNextAvailableFabricIndex should only be called when
1132 : * mNextAvailableFabricIndex has a value and that value stops being
1133 : * available. It will set mNextAvailableFabricIndex to the next available
1134 : * value, or no value if there is none available.
1135 : */
1136 : void UpdateNextAvailableFabricIndex();
1137 :
1138 : /**
1139 : * Ensure that we have a valid next available fabric index, if that's at all possible. This covers
1140 : * some FabricIndex allocation corner cases. After this is called, the only way we can fail to have
1141 : * a next available fabric index is if our fabric table is max-sized (254 entries) and full.
1142 : */
1143 : void EnsureNextAvailableFabricIndexUpdated();
1144 :
1145 : /**
1146 : * Store our current fabric index state: what our next available index is
1147 : * and what indices we're using right now.
1148 : */
1149 : CHIP_ERROR StoreFabricIndexInfo() const;
1150 :
1151 : /**
1152 : * @brief Delete all metadata from storage for the given fabric
1153 : *
1154 : * @param fabricIndex FabricIndex for which to delete the metadadata
1155 : * @return CHIP_NO_ERROR on success or another CHIP_ERROR on failure
1156 : */
1157 : CHIP_ERROR DeleteMetadataFromStorage(FabricIndex fabricIndex);
1158 :
1159 : /**
1160 : * @brief Determine if a collision (undesired on AddNOC, necessary on UpdateNOC) exists
1161 : * between the FabricID in the given noc, and the RCAC found for `currentFabricIndex`
1162 : * in the op cert store, against an existing fabric in the FabricTable (which could be pending)
1163 : *
1164 : * @param currentFabricIndex - pending fabricIndex for which we are trying to Add/Update a NOC
1165 : * @param noc - NOC cert received that contains FabricID whose collision we care to validate
1166 : * @param outMatchingFabricIndex - set to the FabricIndex matching the collision or kUndefinedFabricIndex on no collision found
1167 : * @return CHIP_NO_ERROR on successful update of outMatchingFabricIndex or other CHIP_ERROR on internal errors
1168 : */
1169 : CHIP_ERROR FindExistingFabricByNocChaining(FabricIndex currentFabricIndex, const ByteSpan & noc,
1170 : FabricIndex & outMatchingFabricIndex) const;
1171 :
1172 : /**
1173 : * @brief Get the shadow FabricInfo entry that is pending for updates, if an
1174 : * update is in progress.
1175 : *
1176 : * @return a pointer to the shadow pending fabric or nullptr if none is active.
1177 : */
1178 743 : const FabricInfo * GetShadowPendingFabricEntry() const { return HasPendingFabricUpdate() ? &mPendingFabric : nullptr; }
1179 :
1180 : // Returns true if we have a shadow entry pending for a fabric update.
1181 24353 : bool HasPendingFabricUpdate() const
1182 : {
1183 24353 : return mPendingFabric.IsInitialized() &&
1184 24353 : mStateFlags.HasAll(StateFlags::kIsPendingFabricDataPresent, StateFlags::kIsUpdatePending);
1185 : }
1186 :
1187 : // Validate an NOC chain at time of adding/updating a fabric (uses VerifyCredentials with additional checks).
1188 : // The `existingFabricId` is passed for UpdateNOC, and must match the Fabric, to make sure that we are
1189 : // not trying to change FabricID with UpdateNOC. If set to kUndefinedFabricId, we are doing AddNOC and
1190 : // we don't need to check match to pre-existing fabric.
1191 : static CHIP_ERROR ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
1192 : FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy,
1193 : CompressedFabricId & outCompressedFabricId, FabricId & outFabricId,
1194 : NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
1195 : Crypto::P256PublicKey & outRootPubkey);
1196 :
1197 : /**
1198 : * Read our fabric index info from the given TLV reader and set up the
1199 : * fabric table accordingly.
1200 : */
1201 : CHIP_ERROR ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader);
1202 :
1203 : CHIP_ERROR NotifyFabricUpdated(FabricIndex fabricIndex);
1204 : CHIP_ERROR NotifyFabricCommitted(FabricIndex fabricIndex);
1205 :
1206 : // Commit management clean-up APIs
1207 : CHIP_ERROR StoreCommitMarker(const CommitMarker & commitMarker);
1208 : CHIP_ERROR GetCommitMarker(CommitMarker & outCommitMarker);
1209 :
1210 : FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS];
1211 : // Used for UpdateNOC pending fabric updates
1212 : FabricInfo mPendingFabric;
1213 : PersistentStorageDelegate * mStorage = nullptr;
1214 : Crypto::OperationalKeystore * mOperationalKeystore = nullptr;
1215 : Credentials::OperationalCertificateStore * mOpCertStore = nullptr;
1216 :
1217 : // FabricTable::Delegate link to first node, since FabricTable::Delegate is a form
1218 : // of intrusive linked-list item.
1219 : FabricTable::Delegate * mDelegateListRoot = nullptr;
1220 :
1221 : // When mStateFlags.Has(kIsPendingFabricDataPresent) is true, this holds the index of the fabric
1222 : // for which there is currently pending data.
1223 : FabricIndex mFabricIndexWithPendingState = kUndefinedFabricIndex;
1224 :
1225 : // For when a revert occurs during init, so that more clean-up can be scheduled by caller.
1226 : FabricIndex mDeletedFabricIndexFromInit = kUndefinedFabricIndex;
1227 :
1228 : LastKnownGoodTime mLastKnownGoodTime;
1229 :
1230 : // We may not have an mNextAvailableFabricIndex if our table is as large as
1231 : // it can go and is full.
1232 : Optional<FabricIndex> mNextAvailableFabricIndex;
1233 : uint8_t mFabricCount = 0;
1234 :
1235 : BitFlags<StateFlags> mStateFlags;
1236 : };
1237 :
1238 : } // namespace chip
|