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