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