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 1309 : 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 36393 : FabricIndex GetFabricIndex() const { return mFabricIndex; }
95 :
96 20546 : 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 79586 : bool IsInitialized() const { return (mFabricIndex != kUndefinedFabricIndex) && IsOperationalNodeId(mNodeId); }
109 :
110 741 : 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 715 : CHIP_ERROR AreValid() const
130 : {
131 715 : VerifyOrReturnError((fabricId != kUndefinedFabricId) && (fabricIndex != kUndefinedFabricIndex),
132 : CHIP_ERROR_INVALID_ARGUMENT);
133 715 : 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 715 : 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 1446 : ConstFabricIterator(const FabricInfo * start, const FabricInfo * pending, size_t index, size_t maxSize) :
268 1446 : mStart(start), mPending(pending), mIndex(index), mMaxSize(maxSize)
269 : {
270 1446 : if (mIndex >= maxSize)
271 : {
272 723 : mIndex = maxSize;
273 : }
274 723 : else if (!mStart[mIndex].IsInitialized())
275 : {
276 23 : Advance();
277 : }
278 1446 : }
279 : ConstFabricIterator(const ConstFabricIterator &) = default;
280 : ConstFabricIterator & operator=(const ConstFabricIterator &) = default;
281 :
282 1020 : ConstFabricIterator & operator++() { return Advance(); }
283 : ConstFabricIterator operator++(int)
284 : {
285 : ConstFabricIterator other(*this);
286 : Advance();
287 : return other;
288 : }
289 :
290 1046 : const FabricInfo & operator*() const
291 : {
292 1046 : VerifyOrDie(!IsAtEnd());
293 :
294 1046 : return *GetCurrent();
295 : }
296 0 : const FabricInfo * operator->() const
297 : {
298 0 : VerifyOrDie(!IsAtEnd());
299 :
300 0 : return GetCurrent();
301 : }
302 :
303 1743 : bool operator==(const ConstFabricIterator & other) const
304 : {
305 1743 : if (IsAtEnd())
306 : {
307 697 : return other.IsAtEnd();
308 : }
309 :
310 : // Pending entry does not participate in finding this.
311 1046 : return (mStart == other.mStart) && (mIndex == other.mIndex) && (mMaxSize == other.mMaxSize);
312 : }
313 1743 : bool operator!=(const ConstFabricIterator & other) const { return !(*this == other); }
314 :
315 14645 : 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 1046 : const FabricInfo * GetCurrent() const
326 : {
327 1046 : const auto * current = mStart + mIndex;
328 :
329 : // If we reached the pending entry, return that instead of the underlying entry from the mStates.
330 1046 : if ((mPending != nullptr) && mPending->IsInitialized() && (current->GetFabricIndex() == mPending->GetFabricIndex()))
331 : {
332 0 : current = mPending;
333 : }
334 :
335 1046 : return current;
336 : }
337 :
338 11159 : ConstFabricIterator & Advance()
339 : {
340 : do
341 : {
342 11159 : if (mIndex < mMaxSize)
343 : {
344 11159 : mIndex++;
345 : }
346 11159 : } while (!IsAtEnd() && !mStart[mIndex].IsInitialized());
347 :
348 1043 : 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 466 : Delegate() {}
371 466 : 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 10 : 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 672 : 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 672 : 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 : // Returns CHIP_ERROR_NOT_FOUND if there is no fabric for that index.
416 : CHIP_ERROR Delete(FabricIndex fabricIndex);
417 : void DeleteAllFabrics();
418 :
419 : // TODO this #if CONFIG_BUILD_FOR_HOST_UNIT_TEST is temporary. There is a change incoming soon
420 : // that will allow triggering NOC update directly.
421 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
422 : void SendUpdateFabricNotificationForTest(FabricIndex fabricIndex) { NotifyFabricUpdated(fabricIndex); }
423 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
424 :
425 : /**
426 : * Collection of methods to help find a matching FabricInfo instance given a set of query criteria
427 : *
428 : */
429 :
430 : /**
431 : * Finds a matching FabricInfo instance given a root public key and fabric ID that uniquely identifies the fabric in any scope.
432 : *
433 : * Returns nullptr if no matching instance is found.
434 : *
435 : */
436 : const FabricInfo * FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const;
437 :
438 : /**
439 : * Finds a matching FabricInfo instance given a locally-scoped fabric index.
440 : *
441 : * Returns nullptr if no matching instance is found.
442 : *
443 : */
444 : const FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex) const;
445 :
446 : /**
447 : * Finds a matching FabricInfo instance given a root public key, fabric ID AND a matching NodeId. This variant of find
448 : * is only to be used when it is possible to have colliding fabrics in the table that are on the same logical fabric
449 : * but may be associated with different node identities.
450 : *
451 : * Returns nullptr if no matching instance is found.
452 : *
453 : */
454 : const FabricInfo * FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const;
455 :
456 : /**
457 : * Finds a matching FabricInfo instance given a compressed fabric ID. If there are multiple
458 : * matching FabricInfo instances given the low but non-zero probability of collision, there is no guarantee
459 : * on which instance will be returned.
460 : *
461 : * Returns nullptr if no matching instance is found.
462 : */
463 : const FabricInfo * FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const;
464 :
465 : CHIP_ERROR Init(const FabricTable::InitParams & initParams);
466 : void Shutdown();
467 :
468 : /**
469 : * @brief If `Init()` caused a Delete due to partial commit, the fabric index at play is returned.
470 : *
471 : * Allows caller to schedule more clean-up. This is because at Init() time, none of the delegates
472 : * are registered yet, so no other modules would learn of the removal.
473 : *
474 : * The value is auto-reset to `kUndefinedFabricIndex` on being returned, so that subsequent
475 : * `GetDeletedFabricFromCommitMarker()` after one that has a fabric index to give will provide
476 : * `kUndefinedFabricIndex`.
477 : *
478 : * @return the fabric index of a just-deleted fabric, or kUndefinedFabricIndex if none were deleted.
479 : */
480 : FabricIndex GetDeletedFabricFromCommitMarker();
481 :
482 : /**
483 : * @brief Clear the commit marker when we are sure we have proceeded with any remaining clean-up
484 : */
485 : void ClearCommitMarker();
486 :
487 : // Forget a fabric in memory: doesn't delete any persistent state, just
488 : // reverts any pending state (blindly) and then resets the fabric table
489 : // entry.
490 : //
491 : // TODO: We have to determine if we should remove this call.
492 : void Forget(FabricIndex fabricIndex);
493 :
494 : CHIP_ERROR AddFabricDelegate(FabricTable::Delegate * delegate);
495 : void RemoveFabricDelegate(FabricTable::Delegate * delegate);
496 :
497 : /**
498 : * @brief Set the Fabric Label for the fabric referred by `fabricIndex`.
499 : *
500 : * If a fabric add/update is pending, only the pending version will be updated,
501 : * so that on fail-safe expiry, you would actually see the only fabric label if
502 : * Update fails. If the fabric label is set before UpdateNOC, then the change is immediate.
503 : *
504 : * @param fabricIndex - Fabric Index for which to set the label
505 : * @param fabricLabel - Label to set on the fabric
506 : * @retval CHIP_NO_ERROR on success
507 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to an fabric in the table
508 : * @retval CHIP_ERROR_INVALID_ARGUMENT on fabric label error (e.g. too large)
509 : * @retval other CHIP_ERROR on internal errors
510 : */
511 : CHIP_ERROR SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel);
512 :
513 : /**
514 : * @brief Get the Fabric Label for a given fabric
515 : *
516 : * NOTE: The outFabricLabel argument points to internal memory of the fabric info.
517 : * It may become invalid on the next FabricTable API call due to shadow
518 : * storage of data.
519 : *
520 : * @param fabricIndex - Fabric index for which to get the label
521 : * @param outFabricLabel - char span that will be set to the label value
522 : * @retval CHIP_NO_ERROR on success
523 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX on error
524 : * @retval other CHIP_ERROR on internal errors
525 : */
526 : CHIP_ERROR GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel);
527 :
528 : /**
529 : * Get the current Last Known Good Time.
530 : *
531 : * @param lastKnownGoodChipEpochTime (out) the current last known good time, if any is known
532 : * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR
533 : */
534 0 : CHIP_ERROR GetLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const
535 : {
536 0 : return mLastKnownGoodTime.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime);
537 : }
538 :
539 : /**
540 : * Validate that the passed Last Known Good Time is within bounds and then
541 : * store this and write back to storage. Legal values are those which are
542 : * not earlier than firmware build time or any of our stored certificates'
543 : * NotBefore times:
544 : *
545 : * 3.5.6.1. Last Known Good UTC Time
546 : *
547 : * A Node MAY adjust the Last Known Good UTC Time backwards if it
548 : * believes the current Last Known Good UTC Time is incorrect and it has
549 : * a good time value from a trusted source. The Node SHOULD NOT adjust
550 : * the Last Known Good UTC to a time before the later of:
551 : * • The build timestamp of its currently running software image
552 : * • The not-before timestamp of any of its operational certificates
553 : *
554 : * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch
555 : * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR
556 : */
557 : CHIP_ERROR SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime);
558 :
559 : /**
560 : * @return the number of fabrics currently accessible/usable/iterable.
561 : */
562 8 : uint8_t FabricCount() const { return mFabricCount; }
563 :
564 723 : ConstFabricIterator cbegin() const
565 : {
566 723 : const FabricInfo * pending = GetShadowPendingFabricEntry();
567 723 : return ConstFabricIterator(mStates, pending, 0, CHIP_CONFIG_MAX_FABRICS);
568 : }
569 723 : ConstFabricIterator cend() const
570 : {
571 723 : return ConstFabricIterator(mStates, nullptr, CHIP_CONFIG_MAX_FABRICS, CHIP_CONFIG_MAX_FABRICS);
572 : }
573 723 : ConstFabricIterator begin() const { return cbegin(); }
574 723 : ConstFabricIterator end() const { return cend(); }
575 :
576 : /**
577 : * @brief Get the RCAC (operational root certificate) associated with a fabric.
578 : *
579 : * If a root is pending for `fabricIndex` from `AddNewPendingTrustedRootCert`, it is returned.
580 : *
581 : * @param fabricIndex - Fabric for which to get the RCAC
582 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
583 : * @retval CHIP_NO_ERROR on success
584 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
585 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
586 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
587 : */
588 : CHIP_ERROR FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
589 :
590 : /**
591 : * @brief Get the pending root certificate which is not associated with a fabric, if there is one.
592 : *
593 : * If a root is pending from `AddNewPendingTrustedRootCert`, and there is no
594 : * fabric associated with the corresponding fabric index yet
595 : * (i.e. `AddNewPendingFabric*` has not been called yet) it is returned.
596 : *
597 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
598 : * @retval CHIP_NO_ERROR on success
599 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small.
600 : * @retval CHIP_ERROR_NOT_FOUND if there is no pending root certificate
601 : * that's not yet associated with a fabric.
602 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
603 : */
604 : CHIP_ERROR FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const;
605 :
606 : /**
607 : * @brief Get the ICAC (operational intermediate certificate) associated with a fabric.
608 : *
609 : * If a fabric is pending from add/update operation for the given `fabricIndex`, its
610 : * ICAC is returned.
611 : *
612 : * If an NOC exists, but the ICAC is not present in the chain, CHIP_NO_ERROR is
613 : * returned and `outCert` is resized to 0 length so that its `empty()` method returns true.
614 : *
615 : * @param fabricIndex - Fabric for which to get the ICAC
616 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
617 : * @retval CHIP_NO_ERROR on success, including if absent within an existing chain
618 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
619 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
620 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
621 : */
622 : CHIP_ERROR FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
623 :
624 : /**
625 : * @brief Get the NOC (Node Operational Certificate) associated with a fabric.
626 : *
627 : * If a fabric is pending from add/update operation for the given `fabricIndex`, its
628 : * NOC is returned.
629 : *
630 : * @param fabricIndex - Fabric for which to get the NOC
631 : * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size.
632 : * @retval CHIP_NO_ERROR on success
633 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
634 : * @retval CHIP_ERROR_NOT_FOUND if not found/available
635 : * @retval other CHIP_ERROR values on invalid arguments or internal errors.
636 : */
637 : CHIP_ERROR FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const;
638 :
639 : /**
640 : * @brief Get the root public key by value for the given `fabricIndex`.
641 : *
642 : * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC)
643 : * @param outPublicKey - PublicKey instance to receive the public key contents
644 : * @retval CHIP_NO_ERROR on success
645 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small
646 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value
647 : * @retval other CHIP_ERROR values on other invalid arguments or internal errors.
648 : */
649 : CHIP_ERROR FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const;
650 :
651 : /**
652 : * @brief Get the CASE Authenticated Tags from the NOC for the given `fabricIndex`.
653 : *
654 : * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC)
655 : * @param cats - CATValues struct to write the NOC CATs for the given fabric index
656 : * @retval CHIP_NO_ERROR on success
657 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value
658 : * @retval other CHIP_ERROR values on other invalid arguments or internal errors.
659 : */
660 : CHIP_ERROR FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const;
661 :
662 : /**
663 : * @brief Sign a message with a given fabric's operational keypair. This is used for
664 : * CASE and the only way the key should be used.
665 : *
666 : * This will use a pending key activated with `ActivatePendingOperationalKey` but
667 : * not yet persisted, if one is available for the fabric.
668 : *
669 : * @param fabricIndex - Fabric index whose operational key touse
670 : * @param message - Message to sign
671 : * @param outSignature - Signature object to receive the signature
672 : *
673 : * @retval CHIP_NO_ERROR on success
674 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if no active key is found for the given `fabricIndex` or if
675 : * `fabricIndex` is invalid.
676 : * @retval other CHIP_ERROR value on internal errors
677 : */
678 : CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, Crypto::P256ECDSASignature & outSignature) const;
679 :
680 : /**
681 : * @brief Create an ephemeral keypair for use in session establishment.
682 : *
683 : * WARNING: The return value MUST be released by `ReleaseEphemeralKeypair`. This is because
684 : * Matter CHIPMem.h does not properly support UniquePtr in a way that would
685 : * safely allow classes derived from Crypto::P256Keypair to be released properly.
686 : *
687 : * This delegates to the OperationalKeystore if one exists, otherwise it directly allocates a base
688 : * Crypto::P256Keypair instance
689 : *
690 : * @return a pointer to a dynamically P256Keypair (or derived class thereof), which may evaluate to nullptr
691 : * if running out of memory.
692 : */
693 : Crypto::P256Keypair * AllocateEphemeralKeypairForCASE();
694 :
695 : /**
696 : * @brief Release an ephemeral keypair previously created by `AllocateEphemeralKeypairForCASE()`
697 : */
698 : void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair);
699 :
700 : /**
701 : * This initializes a new keypair for the given fabric and generates a CSR for it,
702 : * so that it can be passed in a CSRResponse.
703 : *
704 : * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either
705 : * `ActivatePendingOperationalKey` is called. It is destroyed if
706 : * `RevertPendingFabricData` is called before `CommitPendingFabricData`.
707 : * If a pending keypair for the provided fabricIndex (if present) already existed, it is replaced by this call.
708 : *
709 : * Only one pending operational keypair is supported at a time.
710 : *
711 : * @param fabricIndex - Existing FabricIndex for which a new keypair must be made available. If it
712 : * doesn't have a value, the key will be marked pending for the next available
713 : * fabric index that would apply for `AddNewFabric`.
714 : * @param outputCsr - Buffer to contain the CSR. Must be at least `kMIN_CSR_Buffer_Size` large.
715 : *
716 : * @retval CHIP_NO_ERROR on success
717 : * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outputCsr` buffer is too small
718 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is already a pending keypair for another `fabricIndex` value
719 : * or if fabricIndex is an invalid value.
720 : * @retval other CHIP_ERROR value on internal errors
721 : */
722 : CHIP_ERROR AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr);
723 :
724 : /**
725 : * @brief Returns whether an operational key is pending (true if `AllocatePendingOperationalKey` was
726 : * previously successfully called, false otherwise).
727 : *
728 : * @param outIsPendingKeyForUpdateNoc this is set to true if the `AllocatePendingOperationalKey` had an
729 : * associated fabric index attached, indicating it's for UpdateNoc
730 : */
731 : bool HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const;
732 :
733 : /**
734 : * @brief Returns whether an operational key can be used to sign for given FabricIndex
735 : *
736 : * @param fabricIndex - Fabric index for which an operational key must be found
737 : * @return true if a pending fabric or committed fabric for fabricIndex has an operational key, false otherwise.
738 : */
739 : bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const;
740 :
741 : /**
742 : * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending.
743 : *
744 : * A newly-added fabric is pending if AddNOC has been previously called successfully but the
745 : * fabric is not yet fully committed by CommissioningComplete.
746 : *
747 : * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending.
748 : *
749 : * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending.
750 : */
751 : FabricIndex GetPendingNewFabricIndex() const;
752 :
753 : /**
754 : * @brief Returns the operational keystore. This is used for
755 : * CASE and the only way the keystore should be used.
756 : *
757 : * @return The operational keystore, nullptr otherwise.
758 : */
759 7 : const Crypto::OperationalKeystore * GetOperationalKeystore() { return mOperationalKeystore; }
760 :
761 : /**
762 : * @brief Add a pending trusted root certificate for the next fabric created with `AddNewPendingFabric*` methods.
763 : *
764 : * The root only becomes actually pending when the `AddNewPendingFabric*` is called afterwards. It is reverted
765 : * if `RevertPendingFabricData` is called.
766 : *
767 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
768 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
769 : * `UpdatePendingFabric` which would mean logical collision of an addition and an update.
770 : *
771 : * @param rcac - Root certificate in Matter Operational Certificate Encoding (TLV) format
772 : * @retval CHIP_NO_ERROR on success
773 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
774 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the root pending
775 : * @retval CHIP_ERROR_INVALID_ARGUMENT if the RCAC is too large (further checks are done on `AddNewPendingFabric*`)
776 : * @retval other CHIP_ERROR on internal errors.
777 : */
778 : CHIP_ERROR AddNewPendingTrustedRootCert(const ByteSpan & rcac);
779 :
780 : /**
781 : * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric
782 : *
783 : * Operational key is assumed to be pending or committed in the associated mOperationalKeystore.
784 : *
785 : * The fabric becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc.
786 : * The new fabric becomes permanent/persisted on successful `CommitPendingFabricData`. It disappears
787 : * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`.
788 : *
789 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
790 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
791 : * `UpdatePendingFabric*` which would mean logical collision of an addition and an update.
792 : *
793 : * If a pending key was present in the OperationalKeystore associated with this FabricTable,
794 : * it is activated on success.
795 : *
796 : *
797 : * @param noc - NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
798 : * @param icac - ICAC for the fabric. Can be empty if absent from the chain.
799 : * @param vendorId - VendorID to use for the new fabric
800 : * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr.
801 : *
802 : * @retval CHIP_NO_ERROR on success.
803 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order.
804 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending.
805 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
806 : * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present.
807 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
808 : */
809 0 : CHIP_ERROR AddNewPendingFabricWithOperationalKeystore(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
810 : FabricIndex * outNewFabricIndex,
811 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
812 : {
813 0 : return AddNewPendingFabricCommon(noc, icac, vendorId, nullptr, false, advertiseIdentity, outNewFabricIndex);
814 : };
815 :
816 : /**
817 : * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric
818 : *
819 : * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or
820 : * owned externally if `isExistingOpKeyExternallyOwned` is true).
821 : *
822 : * WARNING: Copying keypairs is unsafe and not recommended. Consider using
823 : * AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore
824 : * or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe
825 : * class derived from P256Keypair that avoids the true private key persisting in memory.
826 : *
827 : * For rest of semantics outside of operational key, @see AddNewPendingFabricWithOperationalKeystore
828 : *
829 : * @param noc - NOC for the fabric. Public key must match the `existingOpKey`'s public key
830 : * @param icac - ICAC for the fabric. Can be empty if absent from the chain.
831 : * @param vendorId - VendorID to use for the new fabric
832 : * @param existingOpKey - Existing operational key to ingest for use in the fabric. Cannot be nullptr.
833 : * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is
834 : * copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo.
835 : * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr.
836 : *
837 : * @retval CHIP_NO_ERROR on success.
838 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order.
839 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending.
840 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
841 : * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present.
842 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
843 : */
844 669 : CHIP_ERROR AddNewPendingFabricWithProvidedOpKey(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
845 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
846 : FabricIndex * outNewFabricIndex,
847 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
848 : {
849 669 : return AddNewPendingFabricCommon(noc, icac, vendorId, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity,
850 669 : outNewFabricIndex);
851 : };
852 :
853 : /**
854 : * @brief Use an NOC and optional ICAC to update an existing fabric
855 : *
856 : * Operational key is assumed to be pending or committed in the associated mOperationalKeystore.
857 : *
858 : * The new NOC chain becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc.
859 : * The RCAC remains as before. For this method call to succeed, NOC chain must chain back to the existing RCAC.
860 : * The update fabric becomes permanent/persisted on successful `CommitPendingFabricData`. Changes revert
861 : * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`. FabricId CANNOT be updated, but
862 : * CAT tags and Node ID in NOC can change between previous and new NOC for a given FabricId.
863 : *
864 : * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions,
865 : * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after
866 : * `AddNewPending*` which would mean logical collision of an addition and an update.
867 : *
868 : * If a pending key was present in the OperationalKeystore associated with this FabricTable,
869 : * it is activated on success.
870 : *
871 : * @param fabricIndex - fabricIndex of the existing fabric to update
872 : * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
873 : * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain.
874 : *
875 : * @retval CHIP_NO_ERROR on success
876 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric
877 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
878 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates
879 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
880 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
881 : */
882 0 : CHIP_ERROR UpdatePendingFabricWithOperationalKeystore(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
883 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
884 : {
885 0 : return UpdatePendingFabricCommon(fabricIndex, noc, icac, nullptr, false, advertiseIdentity);
886 : }
887 :
888 : /**
889 : * @brief Use an NOC and optional ICAC to update an existing fabric
890 : *
891 : * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or
892 : * owned externally if `isExistingOpKeyExternallyOwned` is true).
893 : *
894 : * WARNING: Copying keypairs is unsafe and not recommended. Consider using
895 : * AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore
896 : * or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe
897 : * class derived from P256Keypair that avoids the true private key persisting in memory.
898 : *
899 : * For rest of semantics outside of operational key, @see UpdatePendingFabricWithOperationalKeystore
900 : *
901 : * @param fabricIndex - fabricIndex of the existing fabric to update
902 : * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore.
903 : * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain.
904 : * @param existingOpKey - Existing operational key to ingest for use in the fabric with new NOC. Cannot be nullptr.
905 : * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is
906 : * copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo.
907 : *
908 : * @retval CHIP_NO_ERROR on success
909 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric
910 : * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order
911 : * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates
912 : * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds.
913 : * @retval other CHIP_ERROR_* on internal errors or certificate validation errors.
914 : */
915 :
916 0 : CHIP_ERROR UpdatePendingFabricWithProvidedOpKey(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
917 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
918 : AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes)
919 : {
920 0 : return UpdatePendingFabricCommon(fabricIndex, noc, icac, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity);
921 : }
922 :
923 : /**
924 : * @brief Commit any pending temporary FabricTable state. This is used mostly for affecting
925 : * CommissioningComplete.
926 : *
927 : * On success, any pending information is committed such that after a restart, it would
928 : * be found to be the same in persistent storage.
929 : *
930 : * If no changes were pending and state is internally consistent, this appears as a no-op and returns
931 : * CHIP_NO_ERROR.
932 : *
933 : * If there is any internally inconsistent state, this methods acts the same as RevertPendingFabricData(),
934 : * and all state is lost.
935 : *
936 : * In rare circumstances, and depending on the storage backend for opcerts and operational keys,
937 : * an inconsistent state could be left, such as if restarting during storage writes of
938 : * CommitPendingFabricData(). If this happens, the next FabricTable::Init() will attempt
939 : * to clean-up the pieces.
940 : *
941 : * @return CHIP_NO_ERROR on success or any other CHIP_ERROR value on internal errors
942 : */
943 : CHIP_ERROR CommitPendingFabricData();
944 :
945 : /**
946 : * @brief Revert any pending state.
947 : *
948 : * This is used to handle fail-safe expiry of partially configured fabrics, or to recover
949 : * from situations where partial state was written and configuration cannot continue properly.
950 : *
951 : * All pending certificates and operational keys and pending fabric metadata are cleared.
952 : */
953 : void RevertPendingFabricData();
954 :
955 : /**
956 : * @brief Revert only the pending NOC/ICAC and pending added fabric, not RCAC. Used for error handling
957 : * during commissioning.
958 : */
959 : void RevertPendingOpCertsExceptRoot();
960 :
961 : // Verifies credentials, using the root certificate of the provided fabric index.
962 : CHIP_ERROR VerifyCredentials(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
963 : Credentials::ValidationContext & context, CompressedFabricId & outCompressedFabricId,
964 : FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
965 : Crypto::P256PublicKey * outRootPublicKey = nullptr) const;
966 :
967 : // Verifies credentials, using the provided root certificate.
968 : static CHIP_ERROR VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
969 : Credentials::ValidationContext & context, CompressedFabricId & outCompressedFabricId,
970 : FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
971 : Crypto::P256PublicKey * outRootPublicKey = nullptr);
972 : /**
973 : * @brief Enables FabricInfo instances to collide and reference the same logical fabric (i.e Root Public Key + FabricId).
974 : *
975 : * *WARNING* This is ONLY to be used when creating multiple controllers on the same fabric OR for test.
976 : *
977 : */
978 0 : void PermitCollidingFabrics() { mStateFlags.Set(StateFlags::kAreCollidingFabricsIgnored); }
979 :
980 : // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will
981 : // get copied (directly) into the fabric table.
982 : CHIP_ERROR AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
983 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
984 :
985 : // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will
986 : // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending.
987 : CHIP_ERROR AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
988 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
989 :
990 : // Same as AddNewFabricForTest, but ignore if we are colliding with same <Root Public Key, Fabric Id>, so
991 : // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form.
992 : CHIP_ERROR AddNewFabricForTestIgnoringCollisions(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
993 : const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
994 : {
995 : PermitCollidingFabrics();
996 : CHIP_ERROR err = AddNewFabricForTest(rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
997 : mStateFlags.Clear(StateFlags::kAreCollidingFabricsIgnored);
998 : return err;
999 : }
1000 :
1001 : // For test only. See definition of `StateFlags::kAbortCommitForTest`.
1002 : void SetForceAbortCommitForTest(bool abortCommitForTest)
1003 : {
1004 : (void) abortCommitForTest;
1005 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
1006 : if (abortCommitForTest)
1007 : {
1008 : mStateFlags.Set(StateFlags::kAbortCommitForTest);
1009 : }
1010 : else
1011 : {
1012 : mStateFlags.Clear(StateFlags::kAbortCommitForTest);
1013 : }
1014 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
1015 : }
1016 :
1017 : /**
1018 : * Get the fabric index that will be used for the next fabric that will be
1019 : * added. Returns error if no more fabrics can be added, otherwise writes
1020 : * the fabric index that will be used for the next addition into the
1021 : * outparam.
1022 : */
1023 : CHIP_ERROR PeekFabricIndexForNextAddition(FabricIndex & outIndex);
1024 :
1025 : /**
1026 : * Set the fabric index that will be used fo the next fabric added.
1027 : *
1028 : * Returns an error if the |fabricIndex| is already in use.
1029 : */
1030 : CHIP_ERROR SetFabricIndexForNextAddition(FabricIndex fabricIndex);
1031 :
1032 : /**
1033 : * @brief Set the advertising behavior for the fabric identified by `fabricIndex`.
1034 : *
1035 : * It is the caller's responsibility to actually restart DNS-SD advertising
1036 : * as needed after updating this state.
1037 : *
1038 : * @param fabricIndex - Fabric Index for which to set the label
1039 : * @param advertiseIdentity - whether the identity for this fabric should be advertised.
1040 : * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to a fabric in the table
1041 : */
1042 : CHIP_ERROR SetShouldAdvertiseIdentity(FabricIndex fabricIndex, AdvertiseIdentity advertiseIdentity);
1043 :
1044 : private:
1045 : enum class StateFlags : uint16_t
1046 : {
1047 : // If true, we are in the process of a fail-safe and there was at least one
1048 : // operation that caused partial data in the fabric table.
1049 : kIsPendingFabricDataPresent = (1u << 0),
1050 : kIsTrustedRootPending = (1u << 1),
1051 : kIsUpdatePending = (1u << 2),
1052 : kIsAddPending = (1u << 3),
1053 :
1054 : // Only true when `AllocatePendingOperationalKey` has been called
1055 : kIsOperationalKeyPending = (1u << 4),
1056 : // True if `AllocatePendingOperationalKey` was for an existing fabric
1057 : kIsPendingKeyForUpdateNoc = (1u << 5),
1058 :
1059 : // True if we allow more than one fabric with same root and fabricId in the fabric table
1060 : // for test purposes. This disables a collision check.
1061 : kAreCollidingFabricsIgnored = (1u << 6),
1062 :
1063 : // If set to true (only possible on test builds), will cause `CommitPendingFabricData()` to early
1064 : // return during commit, skipping clean-ups, so that we can validate commit marker fabric removal.
1065 : kAbortCommitForTest = (1u << 7),
1066 : };
1067 :
1068 : // Stored to indicate a commit is in progress, so that it can be cleaned-up on next boot
1069 : // if stopped in the middle.
1070 : struct CommitMarker
1071 : {
1072 : CommitMarker() = default;
1073 705 : CommitMarker(FabricIndex fabricIndex_, bool isAddition_)
1074 705 : {
1075 705 : this->fabricIndex = fabricIndex_;
1076 705 : this->isAddition = isAddition_;
1077 705 : }
1078 : FabricIndex fabricIndex = kUndefinedFabricIndex;
1079 : bool isAddition = false;
1080 : };
1081 :
1082 : /**
1083 : * @brief Get a mutable FabricInfo entry from the table by FabricIndex.
1084 : *
1085 : * NOTE: This is private for use within the FabricTable itself. All mutations have to go through the
1086 : * FabricTable public methods that take a FabricIndex so that there are no mutations about which
1087 : * the FabricTable is unaware, since this would break expectations regarding shadow/pending
1088 : * entries used during fail-safe.
1089 : *
1090 : * @param fabricIndex - fabric index for which to get a mutable FabricInfo entry
1091 : * @return the FabricInfo entry for the fabricIndex if found, or nullptr if not found
1092 : */
1093 : FabricInfo * GetMutableFabricByIndex(FabricIndex fabricIndex);
1094 :
1095 : // Load a FabricInfo metatada item from storage for a given new fabric index. Returns internal error on failure.
1096 : CHIP_ERROR LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex);
1097 :
1098 : // Store a given fabric metadata directly/immediately. Used by internal operations.
1099 : CHIP_ERROR StoreFabricMetadata(const FabricInfo * fabricInfo) const;
1100 :
1101 : // Tries to set `mFabricIndexWithPendingState` and returns false if there's a clash.
1102 : bool SetPendingDataFabricIndex(FabricIndex fabricIndex);
1103 :
1104 : // Core validation logic for fabric additions/updates
1105 : CHIP_ERROR AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey,
1106 : bool isExistingOpKeyExternallyOwned, uint16_t vendorId, AdvertiseIdentity advertiseIdentity);
1107 :
1108 : // Common code for fabric addition, for either OperationalKeystore or injected key scenarios.
1109 : CHIP_ERROR AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
1110 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
1111 : AdvertiseIdentity advertiseIdentity, FabricIndex * outNewFabricIndex);
1112 :
1113 : // Common code for fabric updates, for either OperationalKeystore or injected key scenarios.
1114 : CHIP_ERROR UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
1115 : Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
1116 : AdvertiseIdentity advertiseIdentity);
1117 :
1118 : // Common code for looking up a fabric given a root public key, a fabric ID and an optional node id scoped to that fabric.
1119 : const FabricInfo * FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId,
1120 : NodeId nodeId = kUndefinedNodeId) const;
1121 :
1122 : /**
1123 : * UpdateNextAvailableFabricIndex should only be called when
1124 : * mNextAvailableFabricIndex has a value and that value stops being
1125 : * available. It will set mNextAvailableFabricIndex to the next available
1126 : * value, or no value if there is none available.
1127 : */
1128 : void UpdateNextAvailableFabricIndex();
1129 :
1130 : /**
1131 : * Ensure that we have a valid next available fabric index, if that's at all possible. This covers
1132 : * some FabricIndex allocation corner cases. After this is called, the only way we can fail to have
1133 : * a next available fabric index is if our fabric table is max-sized (254 entries) and full.
1134 : */
1135 : void EnsureNextAvailableFabricIndexUpdated();
1136 :
1137 : /**
1138 : * Store our current fabric index state: what our next available index is
1139 : * and what indices we're using right now.
1140 : */
1141 : CHIP_ERROR StoreFabricIndexInfo() const;
1142 :
1143 : /**
1144 : * @brief Delete all metadata from storage for the given fabric
1145 : *
1146 : * @param fabricIndex FabricIndex for which to delete the metadadata
1147 : * @return CHIP_NO_ERROR on success or another CHIP_ERROR on failure
1148 : */
1149 : CHIP_ERROR DeleteMetadataFromStorage(FabricIndex fabricIndex);
1150 :
1151 : /**
1152 : * @brief Determine if a collision (undesired on AddNOC, necessary on UpdateNOC) exists
1153 : * between the FabricID in the given noc, and the RCAC found for `currentFabricIndex`
1154 : * in the op cert store, against an existing fabric in the FabricTable (which could be pending)
1155 : *
1156 : * @param currentFabricIndex - pending fabricIndex for which we are trying to Add/Update a NOC
1157 : * @param noc - NOC cert received that contains FabricID whose collision we care to validate
1158 : * @param outMatchingFabricIndex - set to the FabricIndex matching the collision or kUndefinedFabricIndex on no collision found
1159 : * @return CHIP_NO_ERROR on successful update of outMatchingFabricIndex or other CHIP_ERROR on internal errors
1160 : */
1161 : CHIP_ERROR FindExistingFabricByNocChaining(FabricIndex currentFabricIndex, const ByteSpan & noc,
1162 : FabricIndex & outMatchingFabricIndex) const;
1163 :
1164 : /**
1165 : * @brief Get the shadow FabricInfo entry that is pending for updates, if an
1166 : * update is in progress.
1167 : *
1168 : * @return a pointer to the shadow pending fabric or nullptr if none is active.
1169 : */
1170 723 : const FabricInfo * GetShadowPendingFabricEntry() const { return HasPendingFabricUpdate() ? &mPendingFabric : nullptr; }
1171 :
1172 : // Returns true if we have a shadow entry pending for a fabric update.
1173 23534 : bool HasPendingFabricUpdate() const
1174 : {
1175 23534 : return mPendingFabric.IsInitialized() &&
1176 23534 : mStateFlags.HasAll(StateFlags::kIsPendingFabricDataPresent, StateFlags::kIsUpdatePending);
1177 : }
1178 :
1179 : // Validate an NOC chain at time of adding/updating a fabric (uses VerifyCredentials with additional checks).
1180 : // The `existingFabricId` is passed for UpdateNOC, and must match the Fabric, to make sure that we are
1181 : // not trying to change FabricID with UpdateNOC. If set to kUndefinedFabricId, we are doing AddNOC and
1182 : // we don't need to check match to pre-existing fabric.
1183 : static CHIP_ERROR ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
1184 : FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy,
1185 : CompressedFabricId & outCompressedFabricId, FabricId & outFabricId,
1186 : NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
1187 : Crypto::P256PublicKey & outRootPubkey);
1188 :
1189 : /**
1190 : * Read our fabric index info from the given TLV reader and set up the
1191 : * fabric table accordingly.
1192 : */
1193 : CHIP_ERROR ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader);
1194 :
1195 : CHIP_ERROR NotifyFabricUpdated(FabricIndex fabricIndex);
1196 : CHIP_ERROR NotifyFabricCommitted(FabricIndex fabricIndex);
1197 :
1198 : // Commit management clean-up APIs
1199 : CHIP_ERROR StoreCommitMarker(const CommitMarker & commitMarker);
1200 : CHIP_ERROR GetCommitMarker(CommitMarker & outCommitMarker);
1201 :
1202 : FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS];
1203 : // Used for UpdateNOC pending fabric updates
1204 : FabricInfo mPendingFabric;
1205 : PersistentStorageDelegate * mStorage = nullptr;
1206 : Crypto::OperationalKeystore * mOperationalKeystore = nullptr;
1207 : Credentials::OperationalCertificateStore * mOpCertStore = nullptr;
1208 :
1209 : // FabricTable::Delegate link to first node, since FabricTable::Delegate is a form
1210 : // of intrusive linked-list item.
1211 : FabricTable::Delegate * mDelegateListRoot = nullptr;
1212 :
1213 : // When mStateFlags.Has(kIsPendingFabricDataPresent) is true, this holds the index of the fabric
1214 : // for which there is currently pending data.
1215 : FabricIndex mFabricIndexWithPendingState = kUndefinedFabricIndex;
1216 :
1217 : // For when a revert occurs during init, so that more clean-up can be scheduled by caller.
1218 : FabricIndex mDeletedFabricIndexFromInit = kUndefinedFabricIndex;
1219 :
1220 : LastKnownGoodTime mLastKnownGoodTime;
1221 :
1222 : // We may not have an mNextAvailableFabricIndex if our table is as large as
1223 : // it can go and is full.
1224 : Optional<FabricIndex> mNextAvailableFabricIndex;
1225 : uint8_t mFabricCount = 0;
1226 :
1227 : BitFlags<StateFlags> mStateFlags;
1228 : };
1229 :
1230 : } // namespace chip
|