/src/connectedhomeip/src/credentials/FabricTable.cpp
Line | Count | Source |
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 | | #include "FabricTable.h" |
23 | | |
24 | | #include <lib/core/CHIPEncoding.h> |
25 | | #include <lib/support/BufferWriter.h> |
26 | | #include <lib/support/CHIPMem.h> |
27 | | #include <lib/support/CHIPMemString.h> |
28 | | #include <lib/support/DefaultStorageKeyAllocator.h> |
29 | | #include <lib/support/SafeInt.h> |
30 | | #include <lib/support/ScopedMemoryBuffer.h> |
31 | | #include <lib/support/TypeTraits.h> |
32 | | #include <lib/support/logging/CHIPLogging.h> |
33 | | #include <platform/LockTracker.h> |
34 | | #include <tracing/macros.h> |
35 | | |
36 | | namespace chip { |
37 | | using namespace Credentials; |
38 | | using namespace Crypto; |
39 | | |
40 | | using CertChainElement = chip::Credentials::OperationalCertificateStore::CertChainElement; |
41 | | |
42 | | namespace { |
43 | | |
44 | | static_assert(kMinValidFabricIndex <= CHIP_CONFIG_MAX_FABRICS, "Must support some fabrics."); |
45 | | static_assert(CHIP_CONFIG_MAX_FABRICS <= kMaxValidFabricIndex, "Max fabric count out of range."); |
46 | | |
47 | | // Tags for our metadata storage. |
48 | | constexpr TLV::Tag kVendorIdTag = TLV::ContextTag(0); |
49 | | constexpr TLV::Tag kFabricLabelTag = TLV::ContextTag(1); |
50 | | |
51 | | // Tags for our index list storage. |
52 | | constexpr TLV::Tag kNextAvailableFabricIndexTag = TLV::ContextTag(0); |
53 | | constexpr TLV::Tag kFabricIndicesTag = TLV::ContextTag(1); |
54 | | |
55 | | // Tags for commit marker storage |
56 | | constexpr TLV::Tag kMarkerFabricIndexTag = TLV::ContextTag(0); |
57 | | constexpr TLV::Tag kMarkerIsAdditionTag = TLV::ContextTag(1); |
58 | | |
59 | | constexpr size_t CommitMarkerContextTLVMaxSize() |
60 | 0 | { |
61 | 0 | // Add 2x uncommitted uint64_t to leave space for backwards/forwards |
62 | 0 | // versioning for this critical feature that runs at boot. |
63 | 0 | return TLV::EstimateStructOverhead(sizeof(FabricIndex), sizeof(bool), sizeof(uint64_t), sizeof(uint64_t)); |
64 | 0 | } |
65 | | |
66 | | constexpr size_t IndexInfoTLVMaxSize() |
67 | 0 | { |
68 | 0 | // We have a single next-available index and an array of anonymous-tagged |
69 | 0 | // fabric indices. |
70 | 0 | // |
71 | 0 | // The max size of the list is (1 byte control + bytes for actual value) |
72 | 0 | // times max number of list items, plus one byte for the list terminator. |
73 | 0 | return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1); |
74 | 0 | } |
75 | | |
76 | | CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, ByteSpan rootCert, ByteSpan icacCert, |
77 | | ByteSpan nocCert, ByteSpan opKeySpan, FabricIndex * outFabricIndex) |
78 | 0 | { |
79 | 0 | VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
80 | | |
81 | 0 | CHIP_ERROR err = CHIP_ERROR_INTERNAL; |
82 | |
|
83 | 0 | Crypto::P256Keypair injectedOpKey; |
84 | 0 | Crypto::P256SerializedKeypair injectedOpKeysSerialized; |
85 | |
|
86 | 0 | Crypto::P256Keypair * opKey = nullptr; |
87 | 0 | if (!opKeySpan.empty()) |
88 | 0 | { |
89 | 0 | VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); |
90 | | |
91 | 0 | memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); |
92 | 0 | SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); |
93 | 0 | SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); |
94 | 0 | opKey = &injectedOpKey; |
95 | 0 | } |
96 | | |
97 | 0 | SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert)); |
98 | 0 | SuccessOrExit(err = |
99 | 0 | fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, |
100 | 0 | /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); |
101 | 0 | if (!leavePending) |
102 | 0 | { |
103 | 0 | SuccessOrExit(err = fabricTable.CommitPendingFabricData()); |
104 | 0 | } |
105 | 0 | exit: |
106 | 0 | if (err != CHIP_NO_ERROR) |
107 | 0 | { |
108 | 0 | fabricTable.RevertPendingFabricData(); |
109 | 0 | } |
110 | 0 | return err; |
111 | 0 | } |
112 | | |
113 | | } // anonymous namespace |
114 | | |
115 | | CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams) |
116 | 0 | { |
117 | 0 | ReturnErrorOnFailure(initParams.AreValid()); |
118 | | |
119 | 0 | Reset(); |
120 | |
|
121 | 0 | mNodeId = initParams.nodeId; |
122 | 0 | mFabricId = initParams.fabricId; |
123 | 0 | mFabricIndex = initParams.fabricIndex; |
124 | 0 | mCompressedFabricId = initParams.compressedFabricId; |
125 | 0 | mRootPublicKey = initParams.rootPublicKey; |
126 | 0 | mVendorId = static_cast<VendorId>(initParams.vendorId); |
127 | 0 | mShouldAdvertiseIdentity = initParams.advertiseIdentity; |
128 | | |
129 | | // Deal with externally injected keys |
130 | 0 | if (initParams.operationalKeypair != nullptr) |
131 | 0 | { |
132 | 0 | if (initParams.hasExternallyOwnedKeypair) |
133 | 0 | { |
134 | 0 | ReturnErrorOnFailure(SetExternallyOwnedOperationalKeypair(initParams.operationalKeypair)); |
135 | 0 | } |
136 | 0 | else |
137 | 0 | { |
138 | 0 | ReturnErrorOnFailure(SetOperationalKeypair(initParams.operationalKeypair)); |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | 0 | return CHIP_NO_ERROR; |
143 | 0 | } |
144 | | |
145 | | void FabricInfo::operator=(FabricInfo && other) |
146 | 0 | { |
147 | 0 | Reset(); |
148 | |
|
149 | 0 | mNodeId = other.mNodeId; |
150 | 0 | mFabricId = other.mFabricId; |
151 | 0 | mFabricIndex = other.mFabricIndex; |
152 | 0 | mCompressedFabricId = other.mCompressedFabricId; |
153 | 0 | mRootPublicKey = other.mRootPublicKey; |
154 | 0 | mVendorId = other.mVendorId; |
155 | 0 | mShouldAdvertiseIdentity = other.mShouldAdvertiseIdentity; |
156 | |
|
157 | 0 | TEMPORARY_RETURN_IGNORED SetFabricLabel(other.GetFabricLabel()); |
158 | | |
159 | | // Transfer ownership of operational keypair (if it was nullptr, it stays that way). |
160 | 0 | mOperationalKey = other.mOperationalKey; |
161 | 0 | mHasExternallyOwnedOperationalKey = other.mHasExternallyOwnedOperationalKey; |
162 | 0 | other.mOperationalKey = nullptr; |
163 | 0 | other.mHasExternallyOwnedOperationalKey = false; |
164 | |
|
165 | 0 | other.Reset(); |
166 | 0 | } |
167 | | |
168 | | CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) const |
169 | 0 | { |
170 | 0 | { |
171 | 0 | uint8_t buf[MetadataTLVMaxSize()]; |
172 | 0 | TLV::TLVWriter writer; |
173 | 0 | writer.Init(buf); |
174 | |
|
175 | 0 | TLV::TLVType outerType; |
176 | 0 | ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
177 | | |
178 | 0 | ReturnErrorOnFailure(writer.Put(kVendorIdTag, mVendorId)); |
179 | | |
180 | 0 | ReturnErrorOnFailure(writer.PutString(kFabricLabelTag, CharSpan::fromCharString(mFabricLabel))); |
181 | | |
182 | 0 | ReturnErrorOnFailure(writer.EndContainer(outerType)); |
183 | | |
184 | 0 | const auto metadataLength = writer.GetLengthWritten(); |
185 | 0 | VerifyOrReturnError(CanCastTo<uint16_t>(metadataLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
186 | 0 | ReturnErrorOnFailure(storage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf, |
187 | 0 | static_cast<uint16_t>(metadataLength))); |
188 | 0 | } |
189 | | |
190 | | // NOTE: Operational Key is never saved to storage here. See OperationalKeystore interface for how it is accessed |
191 | | |
192 | 0 | return CHIP_NO_ERROR; |
193 | 0 | } |
194 | | |
195 | | CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage, FabricIndex newFabricIndex, const ByteSpan & rcac, |
196 | | const ByteSpan & noc) |
197 | 0 | { |
198 | 0 | mFabricIndex = newFabricIndex; |
199 | | |
200 | | // Regenerate operational metadata from NOC/RCAC |
201 | 0 | { |
202 | 0 | ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &mNodeId, &mFabricId)); |
203 | | |
204 | 0 | P256PublicKeySpan rootPubKeySpan; |
205 | 0 | ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(rcac, rootPubKeySpan)); |
206 | 0 | mRootPublicKey = rootPubKeySpan; |
207 | |
|
208 | 0 | uint8_t compressedFabricIdBuf[sizeof(uint64_t)]; |
209 | 0 | MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf); |
210 | 0 | ReturnErrorOnFailure(GenerateCompressedFabricId(mRootPublicKey, mFabricId, compressedFabricIdSpan)); |
211 | | |
212 | | // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() |
213 | | // returns a binary buffer and is agnostic of usage of the output as an integer type. |
214 | 0 | mCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf); |
215 | 0 | } |
216 | | |
217 | | // Load other storable metadata (label, vendorId, etc) |
218 | 0 | { |
219 | 0 | uint8_t buf[MetadataTLVMaxSize()]; |
220 | 0 | uint16_t size = sizeof(buf); |
221 | 0 | ReturnErrorOnFailure( |
222 | 0 | storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf, size)); |
223 | 0 | TLV::ContiguousBufferTLVReader reader; |
224 | 0 | reader.Init(buf, size); |
225 | |
|
226 | 0 | ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
227 | 0 | TLV::TLVType containerType; |
228 | 0 | ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
229 | | |
230 | 0 | ReturnErrorOnFailure(reader.Next(kVendorIdTag)); |
231 | 0 | ReturnErrorOnFailure(reader.Get(mVendorId)); |
232 | | |
233 | 0 | ReturnErrorOnFailure(reader.Next(kFabricLabelTag)); |
234 | 0 | CharSpan label; |
235 | 0 | ReturnErrorOnFailure(reader.Get(label)); |
236 | | |
237 | 0 | VerifyOrReturnError(label.size() <= kFabricLabelMaxLengthInBytes, CHIP_ERROR_BUFFER_TOO_SMALL); |
238 | 0 | Platform::CopyString(mFabricLabel, label); |
239 | |
|
240 | 0 | ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
241 | 0 | ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
242 | 0 | } |
243 | | |
244 | | // NOTE: Operational Key is never loaded here. See OperationalKeystore interface for how it is accessed |
245 | | |
246 | 0 | return CHIP_NO_ERROR; |
247 | 0 | } |
248 | | |
249 | | CHIP_ERROR FabricInfo::SetFabricLabel(const CharSpan & fabricLabel) |
250 | 0 | { |
251 | 0 | Platform::CopyString(mFabricLabel, fabricLabel); |
252 | |
|
253 | 0 | return CHIP_NO_ERROR; |
254 | 0 | } |
255 | | |
256 | | CHIP_ERROR FabricTable::DeleteMetadataFromStorage(FabricIndex fabricIndex) |
257 | 0 | { |
258 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
259 | 0 | VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
260 | | |
261 | 0 | CHIP_ERROR deleteErr = mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricMetadata(fabricIndex).KeyName()); |
262 | |
|
263 | 0 | if (deleteErr != CHIP_NO_ERROR) |
264 | 0 | { |
265 | 0 | if (deleteErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
266 | 0 | { |
267 | 0 | ChipLogError(FabricProvisioning, "Warning: metadata not found during delete of fabric 0x%x", |
268 | 0 | static_cast<unsigned>(fabricIndex)); |
269 | 0 | } |
270 | 0 | else |
271 | 0 | { |
272 | 0 | ChipLogError(FabricProvisioning, "Error deleting metadata for fabric fabric 0x%x: %" CHIP_ERROR_FORMAT, |
273 | 0 | static_cast<unsigned>(fabricIndex), deleteErr.Format()); |
274 | 0 | } |
275 | 0 | } |
276 | |
|
277 | 0 | return deleteErr; |
278 | 0 | } |
279 | | |
280 | | CHIP_ERROR FabricInfo::SetOperationalKeypair(const P256Keypair * keyPair) |
281 | 0 | { |
282 | 0 | VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
283 | | |
284 | 0 | P256SerializedKeypair serialized; |
285 | 0 | ReturnErrorOnFailure(keyPair->Serialize(serialized)); |
286 | | |
287 | 0 | if (mHasExternallyOwnedOperationalKey) |
288 | 0 | { |
289 | | // Drop it, so we will allocate an internally owned one. |
290 | 0 | mOperationalKey = nullptr; |
291 | 0 | mHasExternallyOwnedOperationalKey = false; |
292 | 0 | } |
293 | |
|
294 | 0 | if (mOperationalKey == nullptr) |
295 | 0 | { |
296 | 0 | mOperationalKey = chip::Platform::New<P256Keypair>(); |
297 | 0 | } |
298 | 0 | VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY); |
299 | 0 | return mOperationalKey->Deserialize(serialized); |
300 | 0 | } |
301 | | |
302 | | CHIP_ERROR FabricInfo::SetExternallyOwnedOperationalKeypair(P256Keypair * keyPair) |
303 | 0 | { |
304 | 0 | VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
305 | 0 | if (!mHasExternallyOwnedOperationalKey && mOperationalKey != nullptr) |
306 | 0 | { |
307 | 0 | chip::Platform::Delete(mOperationalKey); |
308 | 0 | mOperationalKey = nullptr; |
309 | 0 | } |
310 | |
|
311 | 0 | mHasExternallyOwnedOperationalKey = true; |
312 | 0 | mOperationalKey = keyPair; |
313 | 0 | return CHIP_NO_ERROR; |
314 | 0 | } |
315 | | |
316 | | CHIP_ERROR FabricTable::ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, |
317 | | FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy, |
318 | | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, |
319 | | NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey, |
320 | | Crypto::P256PublicKey & outRootPubkey) |
321 | 0 | { |
322 | 0 | MATTER_TRACE_SCOPE("ValidateIncomingNOCChain", "Fabric"); |
323 | 0 | Credentials::ValidationContext validContext; |
324 | | |
325 | | // Note that we do NOT set a time in the validation context. This will |
326 | | // cause the certificate chain NotBefore / NotAfter time validation logic |
327 | | // to report CertificateValidityResult::kTimeUnknown. |
328 | | // |
329 | | // The default CHIPCert policy passes NotBefore / NotAfter validation for |
330 | | // this case where time is unknown. If an override policy is passed, it |
331 | | // will be up to the passed policy to decide how to handle this. |
332 | | // |
333 | | // In the FabricTable::AddNewFabric and FabricTable::UpdateFabric calls, |
334 | | // the passed policy always passes for all questions of time validity. The |
335 | | // rationale is that installed certificates should be valid at the time of |
336 | | // installation by definition. If they are not and the commissionee and |
337 | | // commissioner disagree enough on current time, CASE will fail and our |
338 | | // fail-safe timer will expire. |
339 | | // |
340 | | // This then is ultimately how we validate that NotBefore / NotAfter in |
341 | | // newly installed certificates is workable. |
342 | 0 | validContext.Reset(); |
343 | 0 | validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); |
344 | 0 | validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); |
345 | 0 | validContext.mValidityPolicy = policy; |
346 | |
|
347 | 0 | ChipLogProgress(FabricProvisioning, "Validating NOC chain"); |
348 | 0 | CHIP_ERROR err = FabricTable::VerifyCredentials(noc, icac, rcac, validContext, outCompressedFabricId, outFabricId, outNodeId, |
349 | 0 | outNocPubkey, &outRootPubkey); |
350 | 0 | if (err != CHIP_NO_ERROR) |
351 | 0 | { |
352 | 0 | ChipLogError(FabricProvisioning, "Failed NOC chain validation, VerifyCredentials returned: %" CHIP_ERROR_FORMAT, |
353 | 0 | err.Format()); |
354 | |
|
355 | 0 | if (err != CHIP_ERROR_WRONG_NODE_ID) |
356 | 0 | { |
357 | 0 | err = CHIP_ERROR_UNSUPPORTED_CERT_FORMAT; |
358 | 0 | } |
359 | 0 | return err; |
360 | 0 | } |
361 | | |
362 | | // Validate fabric ID match for cases like UpdateNOC. |
363 | 0 | if (existingFabricId != kUndefinedFabricId) |
364 | 0 | { |
365 | 0 | VerifyOrReturnError(existingFabricId == outFabricId, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
366 | 0 | } |
367 | | |
368 | 0 | ChipLogProgress(FabricProvisioning, "NOC chain validation successful"); |
369 | 0 | return CHIP_NO_ERROR; |
370 | 0 | } |
371 | | |
372 | | CHIP_ERROR FabricInfo::SignWithOpKeypair(ByteSpan message, P256ECDSASignature & outSignature) const |
373 | 0 | { |
374 | 0 | MATTER_TRACE_SCOPE("SignWithOpKeypair", "Fabric"); |
375 | 0 | VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
376 | | |
377 | 0 | return mOperationalKey->ECDSA_sign_msg(message.data(), message.size(), outSignature); |
378 | 0 | } |
379 | | |
380 | | CHIP_ERROR FabricInfo::FetchRootPubkey(Crypto::P256PublicKey & outPublicKey) const |
381 | 0 | { |
382 | 0 | MATTER_TRACE_SCOPE("FetchRootPubKey", "Fabric"); |
383 | 0 | VerifyOrReturnError(IsInitialized(), CHIP_ERROR_KEY_NOT_FOUND); |
384 | 0 | outPublicKey = mRootPublicKey; |
385 | 0 | return CHIP_NO_ERROR; |
386 | 0 | } |
387 | | |
388 | | CHIP_ERROR FabricTable::VerifyCredentials(FabricIndex fabricIndex, ByteSpan noc, ByteSpan icac, ValidationContext & context, |
389 | | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, NodeId & outNodeId, |
390 | | Crypto::P256PublicKey & outNocPubkey, Crypto::P256PublicKey * outRootPublicKey) const |
391 | 0 | { |
392 | 0 | MATTER_TRACE_SCOPE("VerifyCredentials", "Fabric"); |
393 | 0 | assertChipStackLockedByCurrentThread(); |
394 | 0 | uint8_t rootCertBuf[kMaxCHIPCertLength]; |
395 | 0 | MutableByteSpan rootCertSpan{ rootCertBuf }; |
396 | 0 | ReturnErrorOnFailure(FetchRootCert(fabricIndex, rootCertSpan)); |
397 | 0 | return VerifyCredentials(noc, icac, rootCertSpan, context, outCompressedFabricId, outFabricId, outNodeId, outNocPubkey, |
398 | 0 | outRootPublicKey); |
399 | 0 | } |
400 | | |
401 | | CHIP_ERROR FabricTable::VerifyCredentials(ByteSpan noc, ByteSpan icac, ByteSpan rcac, ValidationContext & context, |
402 | | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, NodeId & outNodeId, |
403 | | Crypto::P256PublicKey & outNocPubkey, Crypto::P256PublicKey * outRootPublicKey) |
404 | 0 | { |
405 | | // TODO - Optimize credentials verification logic |
406 | | // The certificate chain construction and verification is a compute and memory intensive operation. |
407 | | // It can be optimized by not loading certificate (i.e. rcac) that's local and implicitly trusted. |
408 | | // The FindValidCert() algorithm will need updates to achieve this refactor. |
409 | 0 | constexpr uint8_t kMaxNumCertsInOpCreds = 3; |
410 | |
|
411 | 0 | ChipCertificateSet certificates; |
412 | 0 | ReturnErrorOnFailure(certificates.Init(kMaxNumCertsInOpCreds)); |
413 | | |
414 | 0 | ReturnErrorOnFailure(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor))); |
415 | | |
416 | 0 | if (!icac.empty()) |
417 | 0 | { |
418 | 0 | ReturnErrorOnFailure(certificates.LoadCert(icac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash))); |
419 | 0 | const ChipDN & icacSubjectDN = certificates.GetLastCert()[0].mSubjectDN; |
420 | 0 | CertType certType; |
421 | 0 | ReturnErrorOnFailure(icacSubjectDN.GetCertType(certType)); |
422 | 0 | VerifyOrReturnError(certType == CertType::kICA, CHIP_ERROR_WRONG_CERT_DN); |
423 | 0 | } |
424 | | |
425 | 0 | ReturnErrorOnFailure(certificates.LoadCert(noc, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash))); |
426 | | |
427 | 0 | const ChipDN & nocSubjectDN = certificates.GetLastCert()[0].mSubjectDN; |
428 | 0 | const CertificateKeyId & nocSubjectKeyId = certificates.GetLastCert()[0].mSubjectKeyId; |
429 | |
|
430 | 0 | const ChipCertificateData * resultCert = nullptr; |
431 | | // FindValidCert() checks the certificate set constructed by loading noc, icac and rcac. |
432 | | // It confirms that the certs link correctly (noc -> icac -> rcac), and have been correctly signed. |
433 | 0 | ReturnErrorOnFailure(certificates.FindValidCert(nocSubjectDN, nocSubjectKeyId, context, &resultCert)); |
434 | | |
435 | 0 | ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(certificates.GetLastCert()[0], &outNodeId, &outFabricId)); |
436 | | |
437 | 0 | CHIP_ERROR err; |
438 | 0 | FabricId icacFabricId = kUndefinedFabricId; |
439 | 0 | if (!icac.empty()) |
440 | 0 | { |
441 | 0 | err = ExtractFabricIdFromCert(certificates.GetCertSet()[1], &icacFabricId); |
442 | 0 | if (err == CHIP_NO_ERROR) |
443 | 0 | { |
444 | 0 | VerifyOrReturnError(icacFabricId == outFabricId, CHIP_ERROR_FABRIC_MISMATCH_ON_ICA); |
445 | 0 | } |
446 | | // FabricId is optional field in ICAC and "not found" code is not treated as error. |
447 | 0 | else if (err != CHIP_ERROR_NOT_FOUND) |
448 | 0 | { |
449 | 0 | return err; |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | 0 | FabricId rcacFabricId = kUndefinedFabricId; |
454 | 0 | err = ExtractFabricIdFromCert(certificates.GetCertSet()[0], &rcacFabricId); |
455 | 0 | if (err == CHIP_NO_ERROR) |
456 | 0 | { |
457 | 0 | VerifyOrReturnError(rcacFabricId == outFabricId, CHIP_ERROR_WRONG_CERT_DN); |
458 | 0 | } |
459 | | // FabricId is optional field in RCAC and "not found" code is not treated as error. |
460 | 0 | else if (err != CHIP_ERROR_NOT_FOUND) |
461 | 0 | { |
462 | 0 | return err; |
463 | 0 | } |
464 | | |
465 | | // Extract compressed fabric ID and root public key |
466 | 0 | { |
467 | 0 | uint8_t compressedFabricIdBuf[sizeof(uint64_t)]; |
468 | 0 | MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf); |
469 | 0 | P256PublicKey rootPubkey(certificates.GetCertSet()[0].mPublicKey); |
470 | |
|
471 | 0 | ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubkey, outFabricId, compressedFabricIdSpan)); |
472 | | |
473 | | // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() |
474 | | // returns a binary buffer and is agnostic of usage of the output as an integer type. |
475 | 0 | outCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf); |
476 | |
|
477 | 0 | if (outRootPublicKey != nullptr) |
478 | 0 | { |
479 | 0 | *outRootPublicKey = rootPubkey; |
480 | 0 | } |
481 | 0 | } |
482 | | |
483 | 0 | outNocPubkey = certificates.GetLastCert()->mPublicKey; |
484 | |
|
485 | 0 | return CHIP_NO_ERROR; |
486 | 0 | } |
487 | | |
488 | | const FabricInfo * FabricTable::FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const |
489 | 0 | { |
490 | 0 | return FindFabricCommon(rootPubKey, fabricId); |
491 | 0 | } |
492 | | |
493 | | const FabricInfo * FabricTable::FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const |
494 | 0 | { |
495 | 0 | return FindFabricCommon(rootPubKey, fabricId, nodeId); |
496 | 0 | } |
497 | | |
498 | | const FabricInfo * FabricTable::FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const |
499 | 0 | { |
500 | 0 | P256PublicKey candidatePubKey; |
501 | | |
502 | | // Try to match pending fabric first if available |
503 | 0 | if (HasPendingFabricUpdate()) |
504 | 0 | { |
505 | 0 | bool pubKeyAvailable = (mPendingFabric.FetchRootPubkey(candidatePubKey) == CHIP_NO_ERROR); |
506 | 0 | auto matchingNodeId = (nodeId == kUndefinedNodeId) ? mPendingFabric.GetNodeId() : nodeId; |
507 | 0 | if (pubKeyAvailable && rootPubKey.Matches(candidatePubKey) && fabricId == mPendingFabric.GetFabricId() && |
508 | 0 | matchingNodeId == mPendingFabric.GetNodeId()) |
509 | 0 | { |
510 | 0 | return &mPendingFabric; |
511 | 0 | } |
512 | 0 | } |
513 | | |
514 | 0 | for (auto & fabric : mStates) |
515 | 0 | { |
516 | 0 | auto matchingNodeId = (nodeId == kUndefinedNodeId) ? fabric.GetNodeId() : nodeId; |
517 | |
|
518 | 0 | if (!fabric.IsInitialized()) |
519 | 0 | { |
520 | 0 | continue; |
521 | 0 | } |
522 | 0 | if (fabric.FetchRootPubkey(candidatePubKey) != CHIP_NO_ERROR) |
523 | 0 | { |
524 | 0 | continue; |
525 | 0 | } |
526 | 0 | if (rootPubKey.Matches(candidatePubKey) && fabricId == fabric.GetFabricId() && matchingNodeId == fabric.GetNodeId()) |
527 | 0 | { |
528 | 0 | return &fabric; |
529 | 0 | } |
530 | 0 | } |
531 | | |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | | |
535 | | FabricInfo * FabricTable::GetMutableFabricByIndex(FabricIndex fabricIndex) |
536 | 0 | { |
537 | | // Try to match pending fabric first if available |
538 | 0 | if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex)) |
539 | 0 | { |
540 | 0 | return &mPendingFabric; |
541 | 0 | } |
542 | | |
543 | 0 | for (auto & fabric : mStates) |
544 | 0 | { |
545 | 0 | if (!fabric.IsInitialized()) |
546 | 0 | { |
547 | 0 | continue; |
548 | 0 | } |
549 | | |
550 | 0 | if (fabric.GetFabricIndex() == fabricIndex) |
551 | 0 | { |
552 | 0 | return &fabric; |
553 | 0 | } |
554 | 0 | } |
555 | | |
556 | 0 | return nullptr; |
557 | 0 | } |
558 | | |
559 | | const FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex) const |
560 | 0 | { |
561 | 0 | if (fabricIndex == kUndefinedFabricIndex) |
562 | 0 | { |
563 | 0 | return nullptr; |
564 | 0 | } |
565 | | |
566 | | // Try to match pending fabric first if available |
567 | 0 | if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex)) |
568 | 0 | { |
569 | 0 | return &mPendingFabric; |
570 | 0 | } |
571 | | |
572 | 0 | for (const auto & fabric : mStates) |
573 | 0 | { |
574 | 0 | if (!fabric.IsInitialized()) |
575 | 0 | { |
576 | 0 | continue; |
577 | 0 | } |
578 | | |
579 | 0 | if (fabric.GetFabricIndex() == fabricIndex) |
580 | 0 | { |
581 | 0 | return &fabric; |
582 | 0 | } |
583 | 0 | } |
584 | | |
585 | 0 | return nullptr; |
586 | 0 | } |
587 | | |
588 | | const FabricInfo * FabricTable::FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const |
589 | 0 | { |
590 | | // Try to match pending fabric first if available |
591 | 0 | if (HasPendingFabricUpdate() && (mPendingFabric.GetCompressedFabricId() == compressedFabricId)) |
592 | 0 | { |
593 | 0 | return &mPendingFabric; |
594 | 0 | } |
595 | | |
596 | 0 | for (auto & fabric : mStates) |
597 | 0 | { |
598 | 0 | if (!fabric.IsInitialized()) |
599 | 0 | { |
600 | 0 | continue; |
601 | 0 | } |
602 | | |
603 | 0 | if (compressedFabricId == fabric.GetPeerId().GetCompressedFabricId()) |
604 | 0 | { |
605 | 0 | return &fabric; |
606 | 0 | } |
607 | 0 | } |
608 | 0 | return nullptr; |
609 | 0 | } |
610 | | |
611 | | CHIP_ERROR FabricTable::FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
612 | 0 | { |
613 | 0 | MATTER_TRACE_SCOPE("FetchRootCert", "Fabric"); |
614 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
615 | 0 | return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kRcac, outCert); |
616 | 0 | } |
617 | | |
618 | | CHIP_ERROR FabricTable::FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const |
619 | 0 | { |
620 | 0 | MATTER_TRACE_SCOPE("FetchPendingNonFabricAssociatedRootCert", "Fabric"); |
621 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
622 | 0 | if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending)) |
623 | 0 | { |
624 | 0 | return CHIP_ERROR_NOT_FOUND; |
625 | 0 | } |
626 | | |
627 | 0 | if (mStateFlags.Has(StateFlags::kIsAddPending)) |
628 | 0 | { |
629 | | // The root certificate is already associated with a pending fabric, so |
630 | | // does not exist for purposes of this API. |
631 | 0 | return CHIP_ERROR_NOT_FOUND; |
632 | 0 | } |
633 | | |
634 | 0 | return FetchRootCert(mFabricIndexWithPendingState, outCert); |
635 | 0 | } |
636 | | |
637 | | CHIP_ERROR FabricTable::FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
638 | 0 | { |
639 | 0 | MATTER_TRACE_SCOPE("FetchICACert", "Fabric"); |
640 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
641 | | |
642 | 0 | CHIP_ERROR err = mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kIcac, outCert); |
643 | 0 | if (err == CHIP_ERROR_NOT_FOUND) |
644 | 0 | { |
645 | 0 | if (mOpCertStore->HasCertificateForFabric(fabricIndex, CertChainElement::kNoc)) |
646 | 0 | { |
647 | | // Didn't find ICAC, but have NOC: return empty for ICAC since not present in chain, but chain exists |
648 | 0 | outCert.reduce_size(0); |
649 | 0 | return CHIP_NO_ERROR; |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | | // For all other cases, delegate to operational cert store for results |
654 | 0 | return err; |
655 | 0 | } |
656 | | |
657 | | CHIP_ERROR FabricTable::FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const |
658 | 0 | { |
659 | 0 | MATTER_TRACE_SCOPE("FetchNOCCert", "Fabric"); |
660 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
661 | 0 | return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kNoc, outCert); |
662 | 0 | } |
663 | | |
664 | | CHIP_ERROR FabricTable::FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const |
665 | 0 | { |
666 | 0 | MATTER_TRACE_SCOPE("FetchRootPubkey", "Fabric"); |
667 | 0 | const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
668 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
669 | 0 | return fabricInfo->FetchRootPubkey(outPublicKey); |
670 | 0 | } |
671 | | |
672 | | CHIP_ERROR FabricTable::FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const |
673 | 0 | { |
674 | 0 | uint8_t nocBuf[Credentials::kMaxCHIPCertLength]; |
675 | 0 | MutableByteSpan nocSpan{ nocBuf }; |
676 | 0 | ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan)); |
677 | 0 | ReturnErrorOnFailure(ExtractCATsFromOpCert(nocSpan, cats)); |
678 | 0 | return CHIP_NO_ERROR; |
679 | 0 | } |
680 | | |
681 | | CHIP_ERROR FabricTable::StoreFabricMetadata(const FabricInfo * fabricInfo) const |
682 | 0 | { |
683 | 0 | VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
684 | 0 | VerifyOrDie(fabricInfo != nullptr); |
685 | | |
686 | 0 | FabricIndex fabricIndex = fabricInfo->GetFabricIndex(); |
687 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INTERNAL); |
688 | | |
689 | | // TODO: Refactor not to internally rely directly on storage |
690 | 0 | ReturnErrorOnFailure(fabricInfo->CommitToStorage(mStorage)); |
691 | | |
692 | 0 | ChipLogProgress(FabricProvisioning, "Metadata for Fabric 0x%x persisted to storage.", static_cast<unsigned>(fabricIndex)); |
693 | |
|
694 | 0 | return CHIP_NO_ERROR; |
695 | 0 | } |
696 | | |
697 | | CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex) |
698 | 0 | { |
699 | 0 | VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
700 | 0 | VerifyOrReturnError(!fabric->IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
701 | | |
702 | 0 | uint8_t nocBuf[kMaxCHIPCertLength]; |
703 | 0 | MutableByteSpan nocSpan{ nocBuf }; |
704 | 0 | uint8_t rcacBuf[kMaxCHIPCertLength]; |
705 | 0 | MutableByteSpan rcacSpan{ rcacBuf }; |
706 | |
|
707 | 0 | CHIP_ERROR err = FetchNOCCert(newFabricIndex, nocSpan); |
708 | 0 | if (err == CHIP_NO_ERROR) |
709 | 0 | { |
710 | 0 | err = FetchRootCert(newFabricIndex, rcacSpan); |
711 | 0 | } |
712 | | |
713 | | // TODO(#19935): Sweep-away fabrics without RCAC/NOC by deleting everything and marking fabric gone. |
714 | |
|
715 | 0 | if (err == CHIP_NO_ERROR) |
716 | 0 | { |
717 | 0 | err = fabric->LoadFromStorage(mStorage, newFabricIndex, rcacSpan, nocSpan); |
718 | 0 | } |
719 | |
|
720 | 0 | if (err != CHIP_NO_ERROR) |
721 | 0 | { |
722 | 0 | ChipLogError(FabricProvisioning, "Failed to load Fabric (0x%x): %" CHIP_ERROR_FORMAT, static_cast<unsigned>(newFabricIndex), |
723 | 0 | err.Format()); |
724 | 0 | fabric->Reset(); |
725 | 0 | return err; |
726 | 0 | } |
727 | | |
728 | 0 | ChipLogProgress(FabricProvisioning, |
729 | 0 | "Fabric index 0x%x was retrieved from storage. Compressed FabricId 0x" ChipLogFormatX64 |
730 | 0 | ", FabricId 0x" ChipLogFormatX64 ", NodeId 0x" ChipLogFormatX64 ", VendorId 0x%04X", |
731 | 0 | static_cast<unsigned>(fabric->GetFabricIndex()), ChipLogValueX64(fabric->GetCompressedFabricId()), |
732 | 0 | ChipLogValueX64(fabric->GetFabricId()), ChipLogValueX64(fabric->GetNodeId()), |
733 | 0 | to_underlying(fabric->GetVendorId())); |
734 | |
|
735 | 0 | return CHIP_NO_ERROR; |
736 | 0 | } |
737 | | |
738 | | CHIP_ERROR FabricTable::AddNewFabricForTest(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan opKeySpan, |
739 | | FabricIndex * outFabricIndex) |
740 | 0 | { |
741 | 0 | return AddNewFabricForTestInternal(*this, /*leavePending=*/false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); |
742 | 0 | } |
743 | | |
744 | | CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan opKeySpan, |
745 | | FabricIndex * outFabricIndex) |
746 | 0 | { |
747 | 0 | return AddNewFabricForTestInternal(*this, /*leavePending=*/true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); |
748 | 0 | } |
749 | | |
750 | | /* |
751 | | * A validation policy we can pass into VerifyCredentials to extract the |
752 | | * latest NotBefore time in the certificate chain without having to load the |
753 | | * certificates into memory again, and one which will pass validation for all |
754 | | * questions of NotBefore / NotAfter validity. |
755 | | * |
756 | | * The rationale is that installed certificates should be valid at the time of |
757 | | * installation by definition. If they are not and the commissionee and |
758 | | * commissioner disagree enough on current time, CASE will fail and our |
759 | | * fail-safe timer will expire. |
760 | | * |
761 | | * This then is ultimately how we validate that NotBefore / NotAfter in |
762 | | * newly installed certificates is workable. |
763 | | */ |
764 | | class NotBeforeCollector : public Credentials::CertificateValidityPolicy |
765 | | { |
766 | | public: |
767 | 0 | NotBeforeCollector() : mLatestNotBefore(0) {} |
768 | | CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, |
769 | | CertificateValidityResult result) override |
770 | 0 | { |
771 | 0 | if (cert->mNotBeforeTime > mLatestNotBefore.count()) |
772 | 0 | { |
773 | 0 | mLatestNotBefore = System::Clock::Seconds32(cert->mNotBeforeTime); |
774 | 0 | } |
775 | 0 | return CHIP_NO_ERROR; |
776 | 0 | } |
777 | | System::Clock::Seconds32 mLatestNotBefore; |
778 | | }; |
779 | | |
780 | | CHIP_ERROR FabricTable::NotifyFabricUpdated(FabricIndex fabricIndex) |
781 | 0 | { |
782 | 0 | MATTER_TRACE_SCOPE("NotifyFabricUpdated", "Fabric"); |
783 | 0 | FabricTable::Delegate * delegate = mDelegateListRoot; |
784 | 0 | while (delegate) |
785 | 0 | { |
786 | | // It is possible that delegate will remove itself from the list in the callback |
787 | | // so we grab the next delegate in the list now. |
788 | 0 | FabricTable::Delegate * nextDelegate = delegate->next; |
789 | 0 | delegate->OnFabricUpdated(*this, fabricIndex); |
790 | 0 | delegate = nextDelegate; |
791 | 0 | } |
792 | 0 | return CHIP_NO_ERROR; |
793 | 0 | } |
794 | | |
795 | | CHIP_ERROR FabricTable::NotifyFabricCommitted(FabricIndex fabricIndex) |
796 | 0 | { |
797 | 0 | MATTER_TRACE_SCOPE("NotifyFabricCommitted", "Fabric"); |
798 | |
|
799 | 0 | FabricTable::Delegate * delegate = mDelegateListRoot; |
800 | 0 | while (delegate) |
801 | 0 | { |
802 | | // It is possible that delegate will remove itself from the list in the callback |
803 | | // so we grab the next delegate in the list now. |
804 | 0 | FabricTable::Delegate * nextDelegate = delegate->next; |
805 | 0 | delegate->OnFabricCommitted(*this, fabricIndex); |
806 | 0 | delegate = nextDelegate; |
807 | 0 | } |
808 | 0 | return CHIP_NO_ERROR; |
809 | 0 | } |
810 | | |
811 | | CHIP_ERROR |
812 | | FabricTable::AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey, |
813 | | bool isExistingOpKeyExternallyOwned, uint16_t vendorId, AdvertiseIdentity advertiseIdentity) |
814 | 0 | { |
815 | | // All parameters pre-validated before we get here |
816 | |
|
817 | 0 | FabricInfo::InitParams newFabricInfo; |
818 | 0 | FabricInfo * fabricEntry = nullptr; |
819 | 0 | FabricId fabricIdToValidate = kUndefinedFabricId; |
820 | 0 | CharSpan fabricLabel; |
821 | |
|
822 | 0 | if (isAddition) |
823 | 0 | { |
824 | | // Initialization for Adding a fabric |
825 | | |
826 | | // Find an available slot. |
827 | 0 | for (auto & fabric : mStates) |
828 | 0 | { |
829 | 0 | if (fabric.IsInitialized()) |
830 | 0 | { |
831 | 0 | continue; |
832 | 0 | } |
833 | 0 | fabricEntry = &fabric; |
834 | 0 | break; |
835 | 0 | } |
836 | |
|
837 | 0 | VerifyOrReturnError(fabricEntry != nullptr, CHIP_ERROR_NO_MEMORY); |
838 | | |
839 | 0 | newFabricInfo.vendorId = static_cast<VendorId>(vendorId); |
840 | 0 | newFabricInfo.fabricIndex = fabricIndex; |
841 | 0 | } |
842 | 0 | else |
843 | 0 | { |
844 | | // Initialization for Updating fabric: setting up a shadow fabricInfo |
845 | 0 | const FabricInfo * existingFabric = FindFabricWithIndex(fabricIndex); |
846 | 0 | VerifyOrReturnError(existingFabric != nullptr, CHIP_ERROR_INTERNAL); |
847 | | |
848 | 0 | mPendingFabric.Reset(); |
849 | 0 | fabricEntry = &mPendingFabric; |
850 | |
|
851 | 0 | newFabricInfo.vendorId = existingFabric->GetVendorId(); |
852 | 0 | newFabricInfo.fabricIndex = fabricIndex; |
853 | |
|
854 | 0 | fabricIdToValidate = existingFabric->GetFabricId(); |
855 | 0 | fabricLabel = existingFabric->GetFabricLabel(); |
856 | 0 | } |
857 | | |
858 | | // Make sure to not modify any of our state until ValidateIncomingNOCChain passes. |
859 | 0 | NotBeforeCollector notBeforeCollector; |
860 | 0 | P256PublicKey nocPubKey; |
861 | | |
862 | | // Validate the cert chain prior to adding |
863 | 0 | { |
864 | 0 | Platform::ScopedMemoryBuffer<uint8_t> nocBuf; |
865 | 0 | Platform::ScopedMemoryBuffer<uint8_t> icacBuf; |
866 | 0 | Platform::ScopedMemoryBuffer<uint8_t> rcacBuf; |
867 | |
|
868 | 0 | VerifyOrReturnError(nocBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
869 | 0 | VerifyOrReturnError(icacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
870 | 0 | VerifyOrReturnError(rcacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); |
871 | | |
872 | 0 | MutableByteSpan nocSpan{ nocBuf.Get(), kMaxCHIPCertLength }; |
873 | 0 | MutableByteSpan icacSpan{ icacBuf.Get(), kMaxCHIPCertLength }; |
874 | 0 | MutableByteSpan rcacSpan{ rcacBuf.Get(), kMaxCHIPCertLength }; |
875 | |
|
876 | 0 | ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan)); |
877 | 0 | ReturnErrorOnFailure(FetchICACert(fabricIndex, icacSpan)); |
878 | 0 | ReturnErrorOnFailure(FetchRootCert(fabricIndex, rcacSpan)); |
879 | | |
880 | 0 | ReturnErrorOnFailure(ValidateIncomingNOCChain(nocSpan, icacSpan, rcacSpan, fabricIdToValidate, ¬BeforeCollector, |
881 | 0 | newFabricInfo.compressedFabricId, newFabricInfo.fabricId, |
882 | 0 | newFabricInfo.nodeId, nocPubKey, newFabricInfo.rootPublicKey)); |
883 | 0 | } |
884 | | |
885 | 0 | if (existingOpKey != nullptr) |
886 | 0 | { |
887 | | // Verify that public key in NOC matches public key of the provided keypair. |
888 | | // When operational key is not injected (e.g. when mOperationalKeystore != nullptr) |
889 | | // the check is done by the keystore in `ActivateOpKeypairForFabric`. |
890 | 0 | VerifyOrReturnError(existingOpKey->Pubkey().Matches(nocPubKey), CHIP_ERROR_INVALID_PUBLIC_KEY); |
891 | | |
892 | 0 | newFabricInfo.operationalKeypair = existingOpKey; |
893 | 0 | newFabricInfo.hasExternallyOwnedKeypair = isExistingOpKeyExternallyOwned; |
894 | 0 | } |
895 | 0 | else if (mOperationalKeystore != nullptr) |
896 | 0 | { |
897 | | // If a keystore exists, we activate the operational key now, which also validates if it was previously installed |
898 | 0 | if (mOperationalKeystore->HasPendingOpKeypair()) |
899 | 0 | { |
900 | 0 | ReturnErrorOnFailure(mOperationalKeystore->ActivateOpKeypairForFabric(fabricIndex, nocPubKey)); |
901 | 0 | } |
902 | 0 | else |
903 | 0 | { |
904 | 0 | VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex), CHIP_ERROR_KEY_NOT_FOUND); |
905 | 0 | } |
906 | 0 | } |
907 | 0 | else |
908 | 0 | { |
909 | 0 | return CHIP_ERROR_INCORRECT_STATE; |
910 | 0 | } |
911 | | |
912 | 0 | newFabricInfo.advertiseIdentity = (advertiseIdentity == AdvertiseIdentity::Yes); |
913 | | |
914 | | // Update local copy of fabric data. For add it's a new entry, for update, it's `mPendingFabric` shadow entry. |
915 | 0 | ReturnErrorOnFailure(fabricEntry->Init(newFabricInfo)); |
916 | | |
917 | | // Set the label, matching add/update semantics of empty/existing. |
918 | 0 | TEMPORARY_RETURN_IGNORED fabricEntry->SetFabricLabel(fabricLabel); |
919 | |
|
920 | 0 | if (isAddition) |
921 | 0 | { |
922 | 0 | ChipLogProgress(FabricProvisioning, "Added new fabric at index: 0x%x", |
923 | 0 | static_cast<unsigned>(fabricEntry->GetFabricIndex())); |
924 | 0 | ChipLogProgress(FabricProvisioning, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64, |
925 | 0 | ChipLogValueX64(fabricEntry->GetCompressedFabricId()), ChipLogValueX64(fabricEntry->GetNodeId())); |
926 | 0 | } |
927 | 0 | else |
928 | 0 | { |
929 | 0 | ChipLogProgress(FabricProvisioning, "Updated fabric at index: 0x%x, Node ID: 0x" ChipLogFormatX64, |
930 | 0 | static_cast<unsigned>(fabricEntry->GetFabricIndex()), ChipLogValueX64(fabricEntry->GetNodeId())); |
931 | 0 | } |
932 | | |
933 | | // Failure to update pending Last Known Good Time is non-fatal. If Last |
934 | | // Known Good Time is incorrect and this causes the commissioner's |
935 | | // certificates to appear invalid, the certificate validity policy will |
936 | | // determine what to do. And if the validity policy considers this fatal |
937 | | // this will prevent CASE and cause the pending fabric and Last Known Good |
938 | | // Time to be reverted. |
939 | 0 | CHIP_ERROR lkgtErr = mLastKnownGoodTime.UpdatePendingLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore); |
940 | 0 | if (lkgtErr != CHIP_NO_ERROR) |
941 | 0 | { |
942 | | // Log but this is not sticky... |
943 | 0 | ChipLogError(FabricProvisioning, "Failed to update pending Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format()); |
944 | 0 | } |
945 | | |
946 | | // Must be the last thing before we return, as this is undone later on error handling within Delete. |
947 | 0 | if (isAddition) |
948 | 0 | { |
949 | 0 | mFabricCount++; |
950 | 0 | } |
951 | |
|
952 | 0 | return CHIP_NO_ERROR; |
953 | 0 | } |
954 | | |
955 | | CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex) |
956 | 0 | { |
957 | 0 | MATTER_TRACE_SCOPE("Delete", "Fabric"); |
958 | 0 | VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
959 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT); |
960 | | |
961 | 0 | { |
962 | 0 | FabricTable::Delegate * delegate = mDelegateListRoot; |
963 | 0 | while (delegate) |
964 | 0 | { |
965 | | // It is possible that delegate will remove itself from the list in FabricWillBeRemoved, |
966 | | // so we grab the next delegate in the list now. |
967 | 0 | FabricTable::Delegate * nextDelegate = delegate->next; |
968 | 0 | delegate->FabricWillBeRemoved(*this, fabricIndex); |
969 | 0 | delegate = nextDelegate; |
970 | 0 | } |
971 | 0 | } |
972 | |
|
973 | 0 | FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
974 | 0 | if (fabricInfo == &mPendingFabric) |
975 | 0 | { |
976 | | // Asked to Delete while pending an update: reset the pending state and |
977 | | // get back to the underlying fabric data for existing fabric. |
978 | 0 | RevertPendingFabricData(); |
979 | 0 | fabricInfo = GetMutableFabricByIndex(fabricIndex); |
980 | 0 | } |
981 | |
|
982 | 0 | bool fabricIsInitialized = fabricInfo != nullptr && fabricInfo->IsInitialized(); |
983 | 0 | CHIP_ERROR metadataErr = DeleteMetadataFromStorage(fabricIndex); // Delete from storage regardless |
984 | |
|
985 | 0 | CHIP_ERROR opKeyErr = CHIP_NO_ERROR; |
986 | 0 | if (mOperationalKeystore != nullptr) |
987 | 0 | { |
988 | 0 | opKeyErr = mOperationalKeystore->RemoveOpKeypairForFabric(fabricIndex); |
989 | | // Not having found data is not an error, we may just have gotten here |
990 | | // on a fail-safe expiry after `RevertPendingFabricData`. |
991 | 0 | if (opKeyErr == CHIP_ERROR_INVALID_FABRIC_INDEX) |
992 | 0 | { |
993 | 0 | opKeyErr = CHIP_NO_ERROR; |
994 | 0 | } |
995 | 0 | } |
996 | |
|
997 | 0 | CHIP_ERROR opCertsErr = CHIP_NO_ERROR; |
998 | 0 | if (mOpCertStore != nullptr) |
999 | 0 | { |
1000 | 0 | opCertsErr = mOpCertStore->RemoveOpCertsForFabric(fabricIndex); |
1001 | | // Not having found data is not an error, we may just have gotten here |
1002 | | // on a fail-safe expiry after `RevertPendingFabricData`. |
1003 | 0 | if (opCertsErr == CHIP_ERROR_INVALID_FABRIC_INDEX) |
1004 | 0 | { |
1005 | 0 | opCertsErr = CHIP_NO_ERROR; |
1006 | 0 | } |
1007 | 0 | } |
1008 | |
|
1009 | 0 | if (fabricIsInitialized) |
1010 | 0 | { |
1011 | | // Since fabricIsInitialized was true, fabric is not null. |
1012 | 0 | fabricInfo->Reset(); |
1013 | |
|
1014 | 0 | if (!mNextAvailableFabricIndex.HasValue()) |
1015 | 0 | { |
1016 | | // We must have been in a situation where CHIP_CONFIG_MAX_FABRICS is 254 |
1017 | | // and our fabric table was full, so there was no valid next index. We |
1018 | | // have a single available index now, though; use it as |
1019 | | // mNextAvailableFabricIndex. |
1020 | 0 | mNextAvailableFabricIndex.SetValue(fabricIndex); |
1021 | 0 | } |
1022 | | // If StoreFabricIndexInfo fails here, that's probably OK. When we try to |
1023 | | // read things from storage later we will realize there is nothing for this |
1024 | | // index. |
1025 | 0 | TEMPORARY_RETURN_IGNORED StoreFabricIndexInfo(); |
1026 | | |
1027 | | // If we ever start moving the FabricInfo entries around in the array on |
1028 | | // delete, we should update DeleteAllFabrics to handle that. |
1029 | 0 | if (mFabricCount == 0) |
1030 | 0 | { |
1031 | 0 | ChipLogError(FabricProvisioning, "Trying to delete a fabric, but the current fabric count is already 0"); |
1032 | 0 | } |
1033 | 0 | else |
1034 | 0 | { |
1035 | 0 | mFabricCount--; |
1036 | 0 | ChipLogProgress(FabricProvisioning, "Fabric (0x%x) deleted.", static_cast<unsigned>(fabricIndex)); |
1037 | 0 | } |
1038 | 0 | } |
1039 | |
|
1040 | 0 | if (mDelegateListRoot != nullptr) |
1041 | 0 | { |
1042 | 0 | FabricTable::Delegate * delegate = mDelegateListRoot; |
1043 | 0 | while (delegate) |
1044 | 0 | { |
1045 | | // It is possible that delegate will remove itself from the list in OnFabricRemoved, |
1046 | | // so we grab the next delegate in the list now. |
1047 | 0 | FabricTable::Delegate * nextDelegate = delegate->next; |
1048 | 0 | delegate->OnFabricRemoved(*this, fabricIndex); |
1049 | 0 | delegate = nextDelegate; |
1050 | 0 | } |
1051 | 0 | } |
1052 | |
|
1053 | 0 | if (fabricIsInitialized) |
1054 | 0 | { |
1055 | | // Only return error after trying really hard to remove everything we could |
1056 | 0 | ReturnErrorOnFailure(metadataErr); |
1057 | 0 | ReturnErrorOnFailure(opKeyErr); |
1058 | 0 | ReturnErrorOnFailure(opCertsErr); |
1059 | | |
1060 | 0 | return CHIP_NO_ERROR; |
1061 | 0 | } |
1062 | | |
1063 | 0 | return CHIP_ERROR_NOT_FOUND; |
1064 | 0 | } |
1065 | | |
1066 | | void FabricTable::DeleteAllFabrics() |
1067 | 0 | { |
1068 | 0 | static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
1069 | |
|
1070 | 0 | RevertPendingFabricData(); |
1071 | |
|
1072 | 0 | for (auto & fabric : *this) |
1073 | 0 | { |
1074 | 0 | TEMPORARY_RETURN_IGNORED Delete(fabric.GetFabricIndex()); |
1075 | 0 | } |
1076 | 0 | } |
1077 | | |
1078 | | CHIP_ERROR FabricTable::Init(const FabricTable::InitParams & initParams) |
1079 | 0 | { |
1080 | 0 | VerifyOrReturnError(initParams.storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
1081 | 0 | VerifyOrReturnError(initParams.opCertStore != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
1082 | | |
1083 | 0 | mStorage = initParams.storage; |
1084 | 0 | mOperationalKeystore = initParams.operationalKeystore; |
1085 | 0 | mOpCertStore = initParams.opCertStore; |
1086 | |
|
1087 | 0 | ChipLogDetail(FabricProvisioning, "Initializing FabricTable from persistent storage"); |
1088 | | |
1089 | | // Load the current fabrics from the storage. |
1090 | 0 | static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
1091 | |
|
1092 | 0 | mFabricCount = 0; |
1093 | 0 | for (auto & fabric : mStates) |
1094 | 0 | { |
1095 | 0 | fabric.Reset(); |
1096 | 0 | } |
1097 | 0 | mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); |
1098 | | |
1099 | | // Init failure of Last Known Good Time is non-fatal. If Last Known Good |
1100 | | // Time is unknown during incoming certificate validation for CASE and |
1101 | | // current time is also unknown, the certificate validity policy will see |
1102 | | // this condition and can act appropriately. |
1103 | 0 | TEMPORARY_RETURN_IGNORED mLastKnownGoodTime.Init(mStorage); |
1104 | |
|
1105 | 0 | uint8_t buf[IndexInfoTLVMaxSize()]; |
1106 | 0 | uint16_t size = sizeof(buf); |
1107 | 0 | CHIP_ERROR err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf, size); |
1108 | 0 | if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
1109 | 0 | { |
1110 | | // No fabrics yet. Nothing to be done here. |
1111 | 0 | } |
1112 | 0 | else |
1113 | 0 | { |
1114 | 0 | ReturnErrorOnFailure(err); |
1115 | 0 | TLV::ContiguousBufferTLVReader reader; |
1116 | 0 | reader.Init(buf, size); |
1117 | | |
1118 | | // TODO: A safer way would be to just clean-up the entire fabric table on this situation... |
1119 | 0 | err = ReadFabricInfo(reader); |
1120 | 0 | if (err != CHIP_NO_ERROR) |
1121 | 0 | { |
1122 | 0 | ChipLogError(FabricProvisioning, "Error loading fabric table: %" CHIP_ERROR_FORMAT ", we are in a bad state!", |
1123 | 0 | err.Format()); |
1124 | 0 | } |
1125 | |
|
1126 | 0 | ReturnErrorOnFailure(err); |
1127 | 0 | } |
1128 | | |
1129 | 0 | CommitMarker commitMarker; |
1130 | 0 | err = GetCommitMarker(commitMarker); |
1131 | 0 | if (err == CHIP_NO_ERROR) |
1132 | 0 | { |
1133 | | // Found a commit marker! We need to possibly delete a loaded fabric |
1134 | 0 | ChipLogError(FabricProvisioning, "Found a FabricTable aborted commit for index 0x%x (isAddition: %d), removing!", |
1135 | 0 | static_cast<unsigned>(commitMarker.fabricIndex), static_cast<int>(commitMarker.isAddition)); |
1136 | |
|
1137 | 0 | mDeletedFabricIndexFromInit = commitMarker.fabricIndex; |
1138 | | |
1139 | | // Can't do better on error. We just have to hope for the best. |
1140 | 0 | (void) Delete(commitMarker.fabricIndex); |
1141 | 0 | } |
1142 | 0 | else if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
1143 | 0 | { |
1144 | | // Got an error, but somehow value is not missing altogether: inconsistent state but touch nothing. |
1145 | 0 | ChipLogError(FabricProvisioning, "Error loading Table commit marker: %" CHIP_ERROR_FORMAT ", hope for the best!", |
1146 | 0 | err.Format()); |
1147 | 0 | } |
1148 | |
|
1149 | 0 | return CHIP_NO_ERROR; |
1150 | 0 | } |
1151 | | |
1152 | | void FabricTable::Forget(FabricIndex fabricIndex) |
1153 | 0 | { |
1154 | 0 | ChipLogProgress(FabricProvisioning, "Forgetting fabric 0x%x", static_cast<unsigned>(fabricIndex)); |
1155 | |
|
1156 | 0 | auto * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
1157 | 0 | VerifyOrReturn(fabricInfo != nullptr); |
1158 | | |
1159 | 0 | RevertPendingFabricData(); |
1160 | 0 | fabricInfo->Reset(); |
1161 | 0 | } |
1162 | | |
1163 | | void FabricTable::Shutdown() |
1164 | 0 | { |
1165 | 0 | VerifyOrReturn(mStorage != nullptr); |
1166 | 0 | ChipLogProgress(FabricProvisioning, "Shutting down FabricTable"); |
1167 | | |
1168 | | // Remove all links to every delegate |
1169 | 0 | FabricTable::Delegate * delegate = mDelegateListRoot; |
1170 | 0 | while (delegate) |
1171 | 0 | { |
1172 | 0 | FabricTable::Delegate * temp = delegate->next; |
1173 | 0 | delegate->next = nullptr; |
1174 | 0 | delegate = temp; |
1175 | 0 | } |
1176 | |
|
1177 | 0 | RevertPendingFabricData(); |
1178 | 0 | for (FabricInfo & fabricInfo : mStates) |
1179 | 0 | { |
1180 | | // Clear-out any FabricInfo-owned operational keys and make sure any further |
1181 | | // direct lookups fail. |
1182 | 0 | fabricInfo.Reset(); |
1183 | 0 | } |
1184 | |
|
1185 | 0 | mStorage = nullptr; |
1186 | 0 | } |
1187 | | |
1188 | | FabricIndex FabricTable::GetDeletedFabricFromCommitMarker() |
1189 | 0 | { |
1190 | 0 | FabricIndex retVal = mDeletedFabricIndexFromInit; |
1191 | | |
1192 | | // Reset for next read |
1193 | 0 | mDeletedFabricIndexFromInit = kUndefinedFabricIndex; |
1194 | |
|
1195 | 0 | return retVal; |
1196 | 0 | } |
1197 | | |
1198 | | CHIP_ERROR FabricTable::AddFabricDelegate(FabricTable::Delegate * delegate) |
1199 | 0 | { |
1200 | 0 | VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
1201 | 0 | for (FabricTable::Delegate * iter = mDelegateListRoot; iter != nullptr; iter = iter->next) |
1202 | 0 | { |
1203 | 0 | if (iter == delegate) |
1204 | 0 | { |
1205 | 0 | return CHIP_NO_ERROR; |
1206 | 0 | } |
1207 | 0 | } |
1208 | 0 | delegate->next = mDelegateListRoot; |
1209 | 0 | mDelegateListRoot = delegate; |
1210 | 0 | return CHIP_NO_ERROR; |
1211 | 0 | } |
1212 | | |
1213 | | void FabricTable::RemoveFabricDelegate(FabricTable::Delegate * delegateToRemove) |
1214 | 0 | { |
1215 | 0 | VerifyOrReturn(delegateToRemove != nullptr); |
1216 | | |
1217 | 0 | if (delegateToRemove == mDelegateListRoot) |
1218 | 0 | { |
1219 | | // Removing head of the list, keep link to next, may |
1220 | | // be nullptr. |
1221 | 0 | mDelegateListRoot = mDelegateListRoot->next; |
1222 | 0 | } |
1223 | 0 | else |
1224 | 0 | { |
1225 | | // Removing some other item: check if next, and |
1226 | | // remove the link, keeping its neighbour. |
1227 | 0 | FabricTable::Delegate * currentNode = mDelegateListRoot; |
1228 | |
|
1229 | 0 | while (currentNode) |
1230 | 0 | { |
1231 | 0 | if (currentNode->next == delegateToRemove) |
1232 | 0 | { |
1233 | 0 | FabricTable::Delegate * temp = delegateToRemove->next; |
1234 | 0 | currentNode->next = temp; |
1235 | 0 | delegateToRemove->next = nullptr; |
1236 | 0 | return; |
1237 | 0 | } |
1238 | | |
1239 | 0 | currentNode = currentNode->next; |
1240 | 0 | } |
1241 | 0 | } |
1242 | 0 | } |
1243 | | |
1244 | | CHIP_ERROR FabricTable::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) |
1245 | 0 | { |
1246 | 0 | CHIP_ERROR err = CHIP_NO_ERROR; |
1247 | | // Find our latest NotBefore time for any installed certificate. |
1248 | 0 | System::Clock::Seconds32 latestNotBefore = System::Clock::Seconds32(0); |
1249 | 0 | for (auto & fabric : mStates) |
1250 | 0 | { |
1251 | 0 | if (!fabric.IsInitialized()) |
1252 | 0 | { |
1253 | 0 | continue; |
1254 | 0 | } |
1255 | 0 | { |
1256 | 0 | uint8_t rcacBuf[kMaxCHIPCertLength]; |
1257 | 0 | MutableByteSpan rcacSpan{ rcacBuf }; |
1258 | 0 | SuccessOrExit(err = FetchRootCert(fabric.GetFabricIndex(), rcacSpan)); |
1259 | 0 | chip::System::Clock::Seconds32 rcacNotBefore; |
1260 | 0 | SuccessOrExit(err = Credentials::ExtractNotBeforeFromChipCert(rcacSpan, rcacNotBefore)); |
1261 | 0 | latestNotBefore = rcacNotBefore > latestNotBefore ? rcacNotBefore : latestNotBefore; |
1262 | 0 | } |
1263 | 0 | { |
1264 | 0 | uint8_t icacBuf[kMaxCHIPCertLength]; |
1265 | 0 | MutableByteSpan icacSpan{ icacBuf }; |
1266 | 0 | SuccessOrExit(err = FetchICACert(fabric.GetFabricIndex(), icacSpan)); |
1267 | 0 | if (!icacSpan.empty()) |
1268 | 0 | { |
1269 | 0 | chip::System::Clock::Seconds32 icacNotBefore; |
1270 | 0 | ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(icacSpan, icacNotBefore)); |
1271 | 0 | latestNotBefore = icacNotBefore > latestNotBefore ? icacNotBefore : latestNotBefore; |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 | { |
1275 | 0 | uint8_t nocBuf[kMaxCHIPCertLength]; |
1276 | 0 | MutableByteSpan nocSpan{ nocBuf }; |
1277 | 0 | SuccessOrExit(err = FetchNOCCert(fabric.GetFabricIndex(), nocSpan)); |
1278 | 0 | chip::System::Clock::Seconds32 nocNotBefore; |
1279 | 0 | ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(nocSpan, nocNotBefore)); |
1280 | 0 | latestNotBefore = nocNotBefore > latestNotBefore ? nocNotBefore : latestNotBefore; |
1281 | 0 | } |
1282 | 0 | } |
1283 | | // Pass this to the LastKnownGoodTime object so it can make determination |
1284 | | // of the legality of our new proposed time. |
1285 | 0 | SuccessOrExit(err = mLastKnownGoodTime.SetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, latestNotBefore)); |
1286 | 0 | exit: |
1287 | 0 | if (err != CHIP_NO_ERROR) |
1288 | 0 | { |
1289 | 0 | ChipLogError(FabricProvisioning, "Failed to update Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); |
1290 | 0 | } |
1291 | 0 | return err; |
1292 | 0 | } |
1293 | | |
1294 | | namespace { |
1295 | | // Increment a fabric index in a way that ensures that it stays in the valid |
1296 | | // range [kMinValidFabricIndex, kMaxValidFabricIndex]. |
1297 | | FabricIndex NextFabricIndex(FabricIndex fabricIndex) |
1298 | 0 | { |
1299 | 0 | if (fabricIndex == kMaxValidFabricIndex) |
1300 | 0 | { |
1301 | 0 | return kMinValidFabricIndex; |
1302 | 0 | } |
1303 | | |
1304 | 0 | return static_cast<FabricIndex>(fabricIndex + 1); |
1305 | 0 | } |
1306 | | } // anonymous namespace |
1307 | | |
1308 | | void FabricTable::UpdateNextAvailableFabricIndex() |
1309 | 0 | { |
1310 | | // Only called when mNextAvailableFabricIndex.HasValue() |
1311 | 0 | for (FabricIndex candidate = NextFabricIndex(mNextAvailableFabricIndex.Value()); candidate != mNextAvailableFabricIndex.Value(); |
1312 | 0 | candidate = NextFabricIndex(candidate)) |
1313 | 0 | { |
1314 | 0 | if (!FindFabricWithIndex(candidate)) |
1315 | 0 | { |
1316 | 0 | mNextAvailableFabricIndex.SetValue(candidate); |
1317 | 0 | return; |
1318 | 0 | } |
1319 | 0 | } |
1320 | | |
1321 | 0 | mNextAvailableFabricIndex.ClearValue(); |
1322 | 0 | } |
1323 | | |
1324 | | CHIP_ERROR FabricTable::StoreFabricIndexInfo() const |
1325 | 0 | { |
1326 | 0 | uint8_t buf[IndexInfoTLVMaxSize()]; |
1327 | 0 | TLV::TLVWriter writer; |
1328 | 0 | writer.Init(buf); |
1329 | |
|
1330 | 0 | TLV::TLVType outerType; |
1331 | 0 | ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
1332 | | |
1333 | 0 | if (mNextAvailableFabricIndex.HasValue()) |
1334 | 0 | { |
1335 | 0 | TEMPORARY_RETURN_IGNORED writer.Put(kNextAvailableFabricIndexTag, mNextAvailableFabricIndex.Value()); |
1336 | 0 | } |
1337 | 0 | else |
1338 | 0 | { |
1339 | 0 | TEMPORARY_RETURN_IGNORED writer.PutNull(kNextAvailableFabricIndexTag); |
1340 | 0 | } |
1341 | |
|
1342 | 0 | TLV::TLVType innerContainerType; |
1343 | 0 | ReturnErrorOnFailure(writer.StartContainer(kFabricIndicesTag, TLV::kTLVType_Array, innerContainerType)); |
1344 | | // Only enumerate the fabrics that are initialized. |
1345 | 0 | for (const auto & fabric : *this) |
1346 | 0 | { |
1347 | 0 | TEMPORARY_RETURN_IGNORED writer.Put(TLV::AnonymousTag(), fabric.GetFabricIndex()); |
1348 | 0 | } |
1349 | 0 | ReturnErrorOnFailure(writer.EndContainer(innerContainerType)); |
1350 | 0 | ReturnErrorOnFailure(writer.EndContainer(outerType)); |
1351 | | |
1352 | 0 | const auto indexInfoLength = writer.GetLengthWritten(); |
1353 | 0 | VerifyOrReturnError(CanCastTo<uint16_t>(indexInfoLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
1354 | | |
1355 | 0 | ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf, |
1356 | 0 | static_cast<uint16_t>(indexInfoLength))); |
1357 | | |
1358 | 0 | return CHIP_NO_ERROR; |
1359 | 0 | } |
1360 | | |
1361 | | void FabricTable::EnsureNextAvailableFabricIndexUpdated() |
1362 | 0 | { |
1363 | 0 | if (!mNextAvailableFabricIndex.HasValue() && mFabricCount < kMaxValidFabricIndex) |
1364 | 0 | { |
1365 | | // We must have a fabric index available here. This situation could |
1366 | | // happen if we fail to store fabric index info when deleting a |
1367 | | // fabric. |
1368 | 0 | mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); |
1369 | 0 | if (FindFabricWithIndex(kMinValidFabricIndex)) |
1370 | 0 | { |
1371 | 0 | UpdateNextAvailableFabricIndex(); |
1372 | 0 | } |
1373 | 0 | } |
1374 | 0 | } |
1375 | | |
1376 | | CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader) |
1377 | 0 | { |
1378 | 0 | ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
1379 | 0 | TLV::TLVType containerType; |
1380 | 0 | ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
1381 | | |
1382 | 0 | ReturnErrorOnFailure(reader.Next(kNextAvailableFabricIndexTag)); |
1383 | 0 | if (reader.GetType() == TLV::kTLVType_Null) |
1384 | 0 | { |
1385 | 0 | mNextAvailableFabricIndex.ClearValue(); |
1386 | 0 | } |
1387 | 0 | else |
1388 | 0 | { |
1389 | 0 | ReturnErrorOnFailure(reader.Get(mNextAvailableFabricIndex.Emplace())); |
1390 | 0 | } |
1391 | | |
1392 | 0 | ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, kFabricIndicesTag)); |
1393 | 0 | TLV::TLVType arrayType; |
1394 | 0 | ReturnErrorOnFailure(reader.EnterContainer(arrayType)); |
1395 | | |
1396 | 0 | CHIP_ERROR err; |
1397 | 0 | while ((err = reader.Next()) == CHIP_NO_ERROR) |
1398 | 0 | { |
1399 | 0 | if (mFabricCount >= MATTER_ARRAY_SIZE(mStates)) |
1400 | 0 | { |
1401 | | // We have nowhere to deserialize this fabric info into. |
1402 | 0 | return CHIP_ERROR_NO_MEMORY; |
1403 | 0 | } |
1404 | | |
1405 | 0 | auto & fabric = mStates[mFabricCount]; |
1406 | 0 | FabricIndex currentFabricIndex = kUndefinedFabricIndex; |
1407 | 0 | ReturnErrorOnFailure(reader.Get(currentFabricIndex)); |
1408 | | |
1409 | 0 | err = LoadFromStorage(&fabric, currentFabricIndex); |
1410 | 0 | if (err == CHIP_NO_ERROR) |
1411 | 0 | { |
1412 | 0 | ++mFabricCount; |
1413 | 0 | } |
1414 | 0 | else |
1415 | 0 | { |
1416 | | // This could happen if we failed to store our fabric index info |
1417 | | // after we deleted the fabric from storage. Just ignore this |
1418 | | // fabric index and keep going. |
1419 | 0 | } |
1420 | 0 | } |
1421 | | |
1422 | 0 | if (err != CHIP_END_OF_TLV) |
1423 | 0 | { |
1424 | 0 | return err; |
1425 | 0 | } |
1426 | | |
1427 | 0 | ReturnErrorOnFailure(reader.ExitContainer(arrayType)); |
1428 | | |
1429 | 0 | ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
1430 | 0 | ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
1431 | | |
1432 | 0 | EnsureNextAvailableFabricIndexUpdated(); |
1433 | |
|
1434 | 0 | return CHIP_NO_ERROR; |
1435 | 0 | } |
1436 | | |
1437 | | Crypto::P256Keypair * FabricTable::AllocateEphemeralKeypairForCASE() |
1438 | 0 | { |
1439 | 0 | if (mOperationalKeystore != nullptr) |
1440 | 0 | { |
1441 | 0 | return mOperationalKeystore->AllocateEphemeralKeypairForCASE(); |
1442 | 0 | } |
1443 | | |
1444 | 0 | return Platform::New<Crypto::P256Keypair>(); |
1445 | 0 | } |
1446 | | |
1447 | | void FabricTable::ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) |
1448 | 0 | { |
1449 | 0 | if (mOperationalKeystore != nullptr) |
1450 | 0 | { |
1451 | 0 | mOperationalKeystore->ReleaseEphemeralKeypair(keypair); |
1452 | 0 | } |
1453 | 0 | else |
1454 | 0 | { |
1455 | 0 | Platform::Delete<Crypto::P256Keypair>(keypair); |
1456 | 0 | } |
1457 | 0 | } |
1458 | | |
1459 | | CHIP_ERROR FabricTable::StoreCommitMarker(const CommitMarker & commitMarker) |
1460 | 0 | { |
1461 | 0 | uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()]; |
1462 | 0 | TLV::TLVWriter writer; |
1463 | 0 | writer.Init(tlvBuf); |
1464 | |
|
1465 | 0 | TLV::TLVType outerType; |
1466 | 0 | ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); |
1467 | 0 | ReturnErrorOnFailure(writer.Put(kMarkerFabricIndexTag, commitMarker.fabricIndex)); |
1468 | 0 | ReturnErrorOnFailure(writer.Put(kMarkerIsAdditionTag, commitMarker.isAddition)); |
1469 | 0 | ReturnErrorOnFailure(writer.EndContainer(outerType)); |
1470 | | |
1471 | 0 | const auto markerContextTLVLength = writer.GetLengthWritten(); |
1472 | 0 | VerifyOrReturnError(CanCastTo<uint16_t>(markerContextTLVLength), CHIP_ERROR_BUFFER_TOO_SMALL); |
1473 | | |
1474 | 0 | return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricTableCommitMarkerKey().KeyName(), tlvBuf, |
1475 | 0 | static_cast<uint16_t>(markerContextTLVLength)); |
1476 | 0 | } |
1477 | | |
1478 | | CHIP_ERROR FabricTable::GetCommitMarker(CommitMarker & outCommitMarker) |
1479 | 0 | { |
1480 | 0 | uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()]; |
1481 | 0 | uint16_t tlvSize = sizeof(tlvBuf); |
1482 | 0 | ReturnErrorOnFailure( |
1483 | 0 | mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricTableCommitMarkerKey().KeyName(), tlvBuf, tlvSize)); |
1484 | | |
1485 | | // If buffer was too small, we won't reach here. |
1486 | 0 | TLV::ContiguousBufferTLVReader reader; |
1487 | 0 | reader.Init(tlvBuf, tlvSize); |
1488 | 0 | ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); |
1489 | | |
1490 | 0 | TLV::TLVType containerType; |
1491 | 0 | ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
1492 | | |
1493 | 0 | ReturnErrorOnFailure(reader.Next(kMarkerFabricIndexTag)); |
1494 | 0 | ReturnErrorOnFailure(reader.Get(outCommitMarker.fabricIndex)); |
1495 | | |
1496 | 0 | ReturnErrorOnFailure(reader.Next(kMarkerIsAdditionTag)); |
1497 | 0 | ReturnErrorOnFailure(reader.Get(outCommitMarker.isAddition)); |
1498 | | |
1499 | | // Don't try to exit container: we got all we needed. This allows us to |
1500 | | // avoid erroring-out on newer versions. |
1501 | | |
1502 | 0 | return CHIP_NO_ERROR; |
1503 | 0 | } |
1504 | | |
1505 | | void FabricTable::ClearCommitMarker() |
1506 | 0 | { |
1507 | 0 | TEMPORARY_RETURN_IGNORED mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricTableCommitMarkerKey().KeyName()); |
1508 | 0 | } |
1509 | | |
1510 | | bool FabricTable::HasOperationalKeyForFabric(FabricIndex fabricIndex) const |
1511 | 0 | { |
1512 | 0 | const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
1513 | 0 | VerifyOrReturnError(fabricInfo != nullptr, false); |
1514 | | |
1515 | 0 | if (fabricInfo->HasOperationalKey()) |
1516 | 0 | { |
1517 | | // Legacy case of manually injected keys: delegate to FabricInfo directly |
1518 | 0 | return true; |
1519 | 0 | } |
1520 | 0 | if (mOperationalKeystore != nullptr) |
1521 | 0 | { |
1522 | 0 | return mOperationalKeystore->HasOpKeypairForFabric(fabricIndex); |
1523 | 0 | } |
1524 | | |
1525 | 0 | return false; |
1526 | 0 | } |
1527 | | |
1528 | | CHIP_ERROR FabricTable::SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, P256ECDSASignature & outSignature) const |
1529 | 0 | { |
1530 | 0 | const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
1531 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
1532 | | |
1533 | 0 | if (fabricInfo->HasOperationalKey()) |
1534 | 0 | { |
1535 | | // Legacy case of manually injected FabricInfo: delegate to FabricInfo directly |
1536 | 0 | return fabricInfo->SignWithOpKeypair(message, outSignature); |
1537 | 0 | } |
1538 | 0 | if (mOperationalKeystore != nullptr) |
1539 | 0 | { |
1540 | 0 | return mOperationalKeystore->SignWithOpKeypair(fabricIndex, message, outSignature); |
1541 | 0 | } |
1542 | | |
1543 | 0 | return CHIP_ERROR_KEY_NOT_FOUND; |
1544 | 0 | } |
1545 | | |
1546 | | bool FabricTable::HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const |
1547 | 0 | { |
1548 | | // We can only manage commissionable pending fail-safe state if we have a keystore |
1549 | 0 | bool hasOpKeyPending = mStateFlags.Has(StateFlags::kIsOperationalKeyPending); |
1550 | |
|
1551 | 0 | if (hasOpKeyPending) |
1552 | 0 | { |
1553 | | // We kept track of whether the last `AllocatePendingOperationalKey` for was for an update, |
1554 | | // so give it back out here. |
1555 | 0 | outIsPendingKeyForUpdateNoc = mStateFlags.Has(StateFlags::kIsPendingKeyForUpdateNoc); |
1556 | 0 | } |
1557 | |
|
1558 | 0 | return hasOpKeyPending; |
1559 | 0 | } |
1560 | | |
1561 | | bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex) |
1562 | 0 | { |
1563 | 0 | bool isLegal = (mFabricIndexWithPendingState == kUndefinedFabricIndex) || (mFabricIndexWithPendingState == fabricIndex); |
1564 | |
|
1565 | 0 | if (isLegal) |
1566 | 0 | { |
1567 | 0 | mFabricIndexWithPendingState = fabricIndex; |
1568 | 0 | } |
1569 | 0 | return isLegal; |
1570 | 0 | } |
1571 | | |
1572 | | FabricIndex FabricTable::GetPendingNewFabricIndex() const |
1573 | 0 | { |
1574 | 0 | if (mStateFlags.Has(StateFlags::kIsAddPending)) |
1575 | 0 | { |
1576 | 0 | return mFabricIndexWithPendingState; |
1577 | 0 | } |
1578 | | |
1579 | 0 | return kUndefinedFabricIndex; |
1580 | 0 | } |
1581 | | |
1582 | | CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr) |
1583 | 0 | { |
1584 | | // We can only manage commissionable pending fail-safe state if we have a keystore |
1585 | 0 | VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
1586 | | |
1587 | | // We can only allocate a pending key if no pending state (NOC, ICAC) already present, |
1588 | | // since there can only be one pending state per fail-safe. |
1589 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent), CHIP_ERROR_INCORRECT_STATE); |
1590 | 0 | VerifyOrReturnError(outputCsr.size() >= Crypto::kMIN_CSR_Buffer_Size, CHIP_ERROR_BUFFER_TOO_SMALL); |
1591 | | |
1592 | 0 | EnsureNextAvailableFabricIndexUpdated(); |
1593 | 0 | FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
1594 | |
|
1595 | 0 | if (fabricIndex.HasValue()) |
1596 | 0 | { |
1597 | | // Check we not are trying to do an update but also change the root: forbidden |
1598 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
1599 | | |
1600 | | // Fabric update case (e.g. UpdateNOC): we already know the fabric index |
1601 | 0 | fabricIndexToUse = fabricIndex.Value(); |
1602 | 0 | mStateFlags.Set(StateFlags::kIsPendingKeyForUpdateNoc); |
1603 | 0 | } |
1604 | 0 | else if (mNextAvailableFabricIndex.HasValue()) |
1605 | 0 | { |
1606 | | // Fabric addition case (e.g. AddNOC): we need to allocate for the next pending fabric index |
1607 | 0 | fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
1608 | 0 | mStateFlags.Clear(StateFlags::kIsPendingKeyForUpdateNoc); |
1609 | 0 | } |
1610 | 0 | else |
1611 | 0 | { |
1612 | | // Fabric addition, but adding NOC would fail on table full: let's not allocate a key |
1613 | 0 | return CHIP_ERROR_NO_MEMORY; |
1614 | 0 | } |
1615 | | |
1616 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
1617 | 0 | VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
1618 | 0 | ReturnErrorOnFailure(mOperationalKeystore->NewOpKeypairForFabric(mFabricIndexWithPendingState, outputCsr)); |
1619 | 0 | mStateFlags.Set(StateFlags::kIsOperationalKeyPending); |
1620 | |
|
1621 | 0 | return CHIP_NO_ERROR; |
1622 | 0 | } |
1623 | | |
1624 | | CHIP_ERROR FabricTable::AddNewPendingTrustedRootCert(const ByteSpan & rcac) |
1625 | 0 | { |
1626 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
1627 | | |
1628 | | // We should not already have pending NOC chain elements when we get here |
1629 | 0 | VerifyOrReturnError( |
1630 | 0 | !mStateFlags.HasAny(StateFlags::kIsTrustedRootPending, StateFlags::kIsUpdatePending, StateFlags::kIsAddPending), |
1631 | 0 | CHIP_ERROR_INCORRECT_STATE); |
1632 | | |
1633 | 0 | EnsureNextAvailableFabricIndexUpdated(); |
1634 | 0 | FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
1635 | |
|
1636 | 0 | if (mNextAvailableFabricIndex.HasValue()) |
1637 | 0 | { |
1638 | 0 | fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
1639 | 0 | } |
1640 | 0 | else |
1641 | 0 | { |
1642 | | // Fabric addition, but adding root would fail on table full: let's not allocate a fabric |
1643 | 0 | return CHIP_ERROR_NO_MEMORY; |
1644 | 0 | } |
1645 | | |
1646 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
1647 | 0 | VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
1648 | 0 | ReturnErrorOnFailure(mOpCertStore->AddNewTrustedRootCertForFabric(fabricIndexToUse, rcac)); |
1649 | | |
1650 | 0 | mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
1651 | 0 | mStateFlags.Set(StateFlags::kIsTrustedRootPending); |
1652 | 0 | return CHIP_NO_ERROR; |
1653 | 0 | } |
1654 | | |
1655 | | CHIP_ERROR FabricTable::FindExistingFabricByNocChaining(FabricIndex pendingFabricIndex, const ByteSpan & noc, |
1656 | | FabricIndex & outMatchingFabricIndex) const |
1657 | 0 | { |
1658 | 0 | MATTER_TRACE_SCOPE("FindExistingFabricByNocChaining", "Fabric"); |
1659 | | // Check whether we already have a matching fabric from a cert chain perspective. |
1660 | | // To do so we have to extract the FabricID from the NOC and the root public key from the RCAC. |
1661 | | // We assume the RCAC is currently readable from OperationalCertificateStore, whether pending |
1662 | | // or persisted. |
1663 | 0 | FabricId fabricId; |
1664 | 0 | { |
1665 | 0 | NodeId unused; |
1666 | 0 | ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &unused, &fabricId)); |
1667 | 0 | } |
1668 | | |
1669 | | // Try to find the root public key from the current existing fabric |
1670 | 0 | Crypto::P256PublicKey candidateRootKey; |
1671 | 0 | { |
1672 | 0 | uint8_t tempRcac[kMaxCHIPCertLength]; |
1673 | 0 | MutableByteSpan tempRcacSpan{ tempRcac }; |
1674 | 0 | Credentials::P256PublicKeySpan publicKeySpan; |
1675 | 0 | ReturnErrorOnFailure(FetchRootCert(pendingFabricIndex, tempRcacSpan)); |
1676 | 0 | ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(tempRcacSpan, publicKeySpan)); |
1677 | 0 | candidateRootKey = publicKeySpan; |
1678 | 0 | } |
1679 | | |
1680 | 0 | for (auto & existingFabric : *this) |
1681 | 0 | { |
1682 | 0 | if (existingFabric.GetFabricId() == fabricId) |
1683 | 0 | { |
1684 | 0 | P256PublicKey existingRootKey; |
1685 | 0 | ReturnErrorOnFailure(FetchRootPubkey(existingFabric.GetFabricIndex(), existingRootKey)); |
1686 | | |
1687 | 0 | if (existingRootKey.Matches(candidateRootKey)) |
1688 | 0 | { |
1689 | 0 | outMatchingFabricIndex = existingFabric.GetFabricIndex(); |
1690 | 0 | return CHIP_NO_ERROR; |
1691 | 0 | } |
1692 | 0 | } |
1693 | 0 | } |
1694 | | |
1695 | | // Did not find: set outMatchingFabricIndex to kUndefinedFabricIndex |
1696 | 0 | outMatchingFabricIndex = kUndefinedFabricIndex; |
1697 | 0 | return CHIP_NO_ERROR; |
1698 | 0 | } |
1699 | | |
1700 | | CHIP_ERROR FabricTable::AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId, |
1701 | | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, |
1702 | | AdvertiseIdentity advertiseIdentity, FabricIndex * outNewFabricIndex) |
1703 | 0 | { |
1704 | 0 | MATTER_TRACE_SCOPE("AddNewPendingFabricCommon", "Fabric"); |
1705 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
1706 | 0 | VerifyOrReturnError(outNewFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
1707 | 0 | static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX"); |
1708 | | |
1709 | | // We should already have a pending root when we get here |
1710 | 0 | VerifyOrReturnError(mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
1711 | | // We should not have pending update when we get here |
1712 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsUpdatePending), CHIP_ERROR_INCORRECT_STATE); |
1713 | | |
1714 | 0 | EnsureNextAvailableFabricIndexUpdated(); |
1715 | 0 | FabricIndex fabricIndexToUse = kUndefinedFabricIndex; |
1716 | 0 | if (mNextAvailableFabricIndex.HasValue()) |
1717 | 0 | { |
1718 | 0 | fabricIndexToUse = mNextAvailableFabricIndex.Value(); |
1719 | 0 | } |
1720 | 0 | else |
1721 | 0 | { |
1722 | | // Fabric addition, but adding fabric would fail on table full: let's not allocate a fabric |
1723 | 0 | return CHIP_ERROR_NO_MEMORY; |
1724 | 0 | } |
1725 | | |
1726 | | // Internal consistency check that mNextAvailableFabricIndex is indeed properly updated... |
1727 | | // TODO: Centralize this a bit. |
1728 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX); |
1729 | | |
1730 | 0 | if (existingOpKey == nullptr) |
1731 | 0 | { |
1732 | | // If existing operational key not provided, we need to have a keystore present. |
1733 | | // It should already have an operational key pending. |
1734 | 0 | VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
1735 | | // Make sure we have an operational key, pending or not |
1736 | 0 | VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndexToUse) || |
1737 | 0 | mOperationalKeystore->HasPendingOpKeypair(), |
1738 | 0 | CHIP_ERROR_KEY_NOT_FOUND); |
1739 | 0 | } |
1740 | | |
1741 | | // Check for new fabric colliding with an existing fabric |
1742 | 0 | if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored)) |
1743 | 0 | { |
1744 | 0 | FabricIndex collidingFabricIndex = kUndefinedFabricIndex; |
1745 | 0 | ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndexToUse, noc, collidingFabricIndex)); |
1746 | 0 | VerifyOrReturnError(collidingFabricIndex == kUndefinedFabricIndex, CHIP_ERROR_FABRIC_EXISTS); |
1747 | 0 | } |
1748 | | |
1749 | | // We don't have a collision, handle the temp insert of NOC/ICAC |
1750 | 0 | ReturnErrorOnFailure(mOpCertStore->AddNewOpCertsForFabric(fabricIndexToUse, noc, icac)); |
1751 | 0 | VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE); |
1752 | | |
1753 | 0 | CHIP_ERROR err = AddOrUpdateInner(fabricIndexToUse, /* isAddition = */ true, existingOpKey, isExistingOpKeyExternallyOwned, |
1754 | 0 | vendorId, advertiseIdentity); |
1755 | 0 | if (err != CHIP_NO_ERROR) |
1756 | 0 | { |
1757 | | // Revert partial state added on error |
1758 | 0 | RevertPendingOpCertsExceptRoot(); |
1759 | 0 | return err; |
1760 | 0 | } |
1761 | | |
1762 | 0 | mStateFlags.Set(StateFlags::kIsAddPending); |
1763 | 0 | mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
1764 | | |
1765 | | // Notify that NOC was added (at least transiently) |
1766 | 0 | *outNewFabricIndex = fabricIndexToUse; |
1767 | 0 | TEMPORARY_RETURN_IGNORED NotifyFabricUpdated(fabricIndexToUse); |
1768 | |
|
1769 | 0 | return CHIP_NO_ERROR; |
1770 | 0 | } |
1771 | | |
1772 | | CHIP_ERROR FabricTable::UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, |
1773 | | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, |
1774 | | AdvertiseIdentity advertiseIdentity) |
1775 | 0 | { |
1776 | 0 | MATTER_TRACE_SCOPE("UpdatePendingFabricCommon", "Fabric"); |
1777 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
1778 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT); |
1779 | | |
1780 | 0 | if (existingOpKey == nullptr) |
1781 | 0 | { |
1782 | | // If existing operational key not provided, we need to have a keystore present. |
1783 | | // It should already have an operational key pending. |
1784 | 0 | VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
1785 | | // Make sure we have an operational key, pending or not |
1786 | 0 | VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex) || mOperationalKeystore->HasPendingOpKeypair(), |
1787 | 0 | CHIP_ERROR_KEY_NOT_FOUND); |
1788 | 0 | } |
1789 | | |
1790 | | // We should should not have a pending root when we get here, since we can't update root on update |
1791 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE); |
1792 | | |
1793 | | // We should not have pending add when we get here, due to internal interlocks |
1794 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsAddPending), CHIP_ERROR_INCORRECT_STATE); |
1795 | | |
1796 | | // Make sure we are updating at least an existing FabricIndex |
1797 | 0 | const auto * fabricInfo = FindFabricWithIndex(fabricIndex); |
1798 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
1799 | | |
1800 | | // Cannot have a VVSC already if ICAC is provided. |
1801 | 0 | if (!icac.empty()) |
1802 | 0 | { |
1803 | 0 | uint8_t vvscBuffer[kMaxCHIPCertLength]; |
1804 | 0 | MutableByteSpan vvscSpan{ vvscBuffer }; |
1805 | |
|
1806 | 0 | CHIP_ERROR err = mOpCertStore->GetVidVerificationElement( |
1807 | 0 | fabricIndex, OperationalCertificateStore::VidVerificationElement::kVvsc, vvscSpan); |
1808 | 0 | if (err == CHIP_NO_ERROR) |
1809 | 0 | { |
1810 | 0 | if (!vvscSpan.empty()) |
1811 | 0 | { |
1812 | 0 | ChipLogError( |
1813 | 0 | FabricProvisioning, |
1814 | 0 | "Received an UpdateNOC storage request with ICAC when VVSC already present. VVSC must be removed first."); |
1815 | 0 | return CHIP_ERROR_INCORRECT_STATE; |
1816 | 0 | } |
1817 | 0 | } |
1818 | 0 | else if (err != CHIP_ERROR_NOT_IMPLEMENTED) |
1819 | 0 | { |
1820 | 0 | return err; |
1821 | 0 | } |
1822 | 0 | } |
1823 | | |
1824 | | // Check for an existing fabric matching RCAC and FabricID. We must find a correct |
1825 | | // existing fabric that chains to same root. We assume the stored root is correct. |
1826 | 0 | if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored)) |
1827 | 0 | { |
1828 | 0 | FabricIndex collidingFabricIndex = kUndefinedFabricIndex; |
1829 | 0 | ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndex, noc, collidingFabricIndex)); |
1830 | 0 | VerifyOrReturnError(collidingFabricIndex == fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); |
1831 | 0 | } |
1832 | | |
1833 | | // Handle the temp insert of NOC/ICAC |
1834 | 0 | ReturnErrorOnFailure(mOpCertStore->UpdateOpCertsForFabric(fabricIndex, noc, icac)); |
1835 | 0 | VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndex), CHIP_ERROR_INCORRECT_STATE); |
1836 | | |
1837 | 0 | CHIP_ERROR err = AddOrUpdateInner(fabricIndex, /* isAddition = */ false, existingOpKey, isExistingOpKeyExternallyOwned, |
1838 | 0 | fabricInfo->GetVendorId(), advertiseIdentity); |
1839 | 0 | if (err != CHIP_NO_ERROR) |
1840 | 0 | { |
1841 | | // Revert partial state added on error |
1842 | | // TODO: Figure-out if there is a better way. We need to make sure we are not inconsistent on elements |
1843 | | // other than the opcerts. |
1844 | 0 | RevertPendingOpCertsExceptRoot(); |
1845 | 0 | return err; |
1846 | 0 | } |
1847 | | |
1848 | 0 | mStateFlags.Set(StateFlags::kIsUpdatePending); |
1849 | 0 | mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent); |
1850 | | |
1851 | | // Notify that NOC was updated (at least transiently) |
1852 | 0 | TEMPORARY_RETURN_IGNORED NotifyFabricUpdated(fabricIndex); |
1853 | |
|
1854 | 0 | return CHIP_NO_ERROR; |
1855 | 0 | } |
1856 | | |
1857 | | CHIP_ERROR FabricTable::CommitPendingFabricData() |
1858 | 0 | { |
1859 | 0 | VerifyOrReturnError((mStorage != nullptr) && (mOpCertStore != nullptr), CHIP_ERROR_INCORRECT_STATE); |
1860 | | |
1861 | 0 | bool haveNewTrustedRoot = mStateFlags.Has(StateFlags::kIsTrustedRootPending); |
1862 | 0 | bool isAdding = mStateFlags.Has(StateFlags::kIsAddPending); |
1863 | 0 | bool isUpdating = mStateFlags.Has(StateFlags::kIsUpdatePending); |
1864 | 0 | bool hasPending = mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent); |
1865 | 0 | bool onlyHaveNewTrustedRoot = hasPending && haveNewTrustedRoot && !(isAdding || isUpdating); |
1866 | 0 | bool hasInvalidInternalState = hasPending && (!IsValidFabricIndex(mFabricIndexWithPendingState) || !(isAdding || isUpdating)); |
1867 | |
|
1868 | 0 | FabricIndex fabricIndexBeingCommitted = mFabricIndexWithPendingState; |
1869 | | |
1870 | | // Proceed with Update/Add pre-flight checks |
1871 | 0 | if (hasPending && !hasInvalidInternalState) |
1872 | 0 | { |
1873 | 0 | if ((isAdding && isUpdating) || (isAdding && !haveNewTrustedRoot)) |
1874 | 0 | { |
1875 | 0 | ChipLogError(FabricProvisioning, "Found inconsistent interlocks during commit %u/%u/%u!", |
1876 | 0 | static_cast<unsigned>(isAdding), static_cast<unsigned>(isUpdating), |
1877 | 0 | static_cast<unsigned>(haveNewTrustedRoot)); |
1878 | 0 | hasInvalidInternalState = true; |
1879 | 0 | } |
1880 | 0 | } |
1881 | | |
1882 | | // Make sure we actually have a pending fabric |
1883 | 0 | FabricInfo * pendingFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
1884 | |
|
1885 | 0 | if (isUpdating && hasPending && !hasInvalidInternalState) |
1886 | 0 | { |
1887 | 0 | if (!mPendingFabric.IsInitialized() || (mPendingFabric.GetFabricIndex() != fabricIndexBeingCommitted) || |
1888 | 0 | (pendingFabricEntry == nullptr)) |
1889 | 0 | { |
1890 | 0 | ChipLogError(FabricProvisioning, "Missing pending fabric on update during commit!"); |
1891 | 0 | hasInvalidInternalState = true; |
1892 | 0 | } |
1893 | 0 | } |
1894 | |
|
1895 | 0 | if (isAdding && hasPending && !hasInvalidInternalState) |
1896 | 0 | { |
1897 | 0 | bool opCertStoreHasRoot = mOpCertStore->HasCertificateForFabric(fabricIndexBeingCommitted, CertChainElement::kRcac); |
1898 | 0 | if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending) || !opCertStoreHasRoot) |
1899 | 0 | { |
1900 | 0 | ChipLogError(FabricProvisioning, "Missing trusted root for fabric add during commit!"); |
1901 | 0 | hasInvalidInternalState = true; |
1902 | 0 | } |
1903 | 0 | } |
1904 | |
|
1905 | 0 | if ((isAdding || isUpdating) && hasPending && !hasInvalidInternalState) |
1906 | 0 | { |
1907 | 0 | if (!HasOperationalKeyForFabric(fabricIndexBeingCommitted)) |
1908 | 0 | { |
1909 | 0 | ChipLogError(FabricProvisioning, "Could not find an operational key during commit!"); |
1910 | 0 | hasInvalidInternalState = true; |
1911 | 0 | } |
1912 | 0 | } |
1913 | | |
1914 | | // If there was nothing pending, we are either in a completely OK state, or weird internally inconsistent |
1915 | | // state. In either case, let's clear all pending state anyway, in case it was partially stale! |
1916 | 0 | if (!hasPending || onlyHaveNewTrustedRoot || hasInvalidInternalState) |
1917 | 0 | { |
1918 | 0 | CHIP_ERROR err = CHIP_NO_ERROR; |
1919 | |
|
1920 | 0 | if (onlyHaveNewTrustedRoot) |
1921 | 0 | { |
1922 | 0 | ChipLogError(FabricProvisioning, |
1923 | 0 | "Failed to commit: tried to commit with only a new trusted root cert. No data committed."); |
1924 | 0 | err = CHIP_ERROR_INCORRECT_STATE; |
1925 | 0 | } |
1926 | 0 | else if (hasInvalidInternalState) |
1927 | 0 | { |
1928 | 0 | ChipLogError(FabricProvisioning, "Failed to commit: internally inconsistent state!"); |
1929 | 0 | err = CHIP_ERROR_INTERNAL; |
1930 | 0 | } |
1931 | 0 | else |
1932 | 0 | { |
1933 | | // There was nothing pending and no error... |
1934 | 0 | } |
1935 | | |
1936 | | // Clear all pending state anyway, in case it was partially stale! |
1937 | 0 | { |
1938 | 0 | mStateFlags.ClearAll(); |
1939 | 0 | mFabricIndexWithPendingState = kUndefinedFabricIndex; |
1940 | 0 | mPendingFabric.Reset(); |
1941 | 0 | mOpCertStore->RevertPendingOpCerts(); |
1942 | 0 | if (mOperationalKeystore != nullptr) |
1943 | 0 | { |
1944 | 0 | mOperationalKeystore->RevertPendingKeypair(); |
1945 | 0 | } |
1946 | 0 | } |
1947 | |
|
1948 | 0 | return err; |
1949 | 0 | } |
1950 | | |
1951 | | // ==== Start of actual commit transaction after pre-flight checks ==== |
1952 | 0 | CHIP_ERROR stickyError = StoreCommitMarker(CommitMarker{ fabricIndexBeingCommitted, isAdding }); |
1953 | 0 | bool failedCommitMarker = (stickyError != CHIP_NO_ERROR); |
1954 | 0 | if (failedCommitMarker) |
1955 | 0 | { |
1956 | 0 | ChipLogError(FabricProvisioning, "Failed to store commit marker, may be inconsistent if reboot happens during fail-safe!"); |
1957 | 0 | } |
1958 | |
|
1959 | 0 | { |
1960 | | // This scope block is to illustrate the complete commit transaction |
1961 | | // state. We can see it contains a LARGE number of items... |
1962 | | |
1963 | | // Atomically assume data no longer pending, since we are committing it. Do so here |
1964 | | // so that FindFabricBy* will return real data and never pending. |
1965 | 0 | mStateFlags.Clear(StateFlags::kIsPendingFabricDataPresent); |
1966 | |
|
1967 | 0 | if (isUpdating) |
1968 | 0 | { |
1969 | | // This will get the non-pending fabric |
1970 | 0 | FabricInfo * existingFabricToUpdate = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
1971 | | |
1972 | | // Multiple interlocks validated the below, so it's fatal if we are somehow incoherent here |
1973 | 0 | VerifyOrDie((existingFabricToUpdate != nullptr) && (existingFabricToUpdate != &mPendingFabric)); |
1974 | | |
1975 | | // Commit the pending entry to local in-memory fabric metadata, which |
1976 | | // also moves operational keys if not backed by OperationalKeystore |
1977 | 0 | *existingFabricToUpdate = std::move(mPendingFabric); |
1978 | 0 | } |
1979 | | |
1980 | | // Store pending metadata first |
1981 | 0 | FabricInfo * liveFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted); |
1982 | 0 | VerifyOrDie(liveFabricEntry != nullptr); |
1983 | | |
1984 | 0 | CHIP_ERROR metadataErr = StoreFabricMetadata(liveFabricEntry); |
1985 | 0 | if (metadataErr != CHIP_NO_ERROR) |
1986 | 0 | { |
1987 | 0 | ChipLogError(FabricProvisioning, "Failed to commit pending fabric metadata: %" CHIP_ERROR_FORMAT, metadataErr.Format()); |
1988 | 0 | } |
1989 | 0 | stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : metadataErr; |
1990 | | |
1991 | | // We can only manage commissionable pending fail-safe state if we have a keystore |
1992 | 0 | CHIP_ERROR keyErr = CHIP_NO_ERROR; |
1993 | 0 | if ((mOperationalKeystore != nullptr) && mOperationalKeystore->HasOpKeypairForFabric(fabricIndexBeingCommitted) && |
1994 | 0 | mOperationalKeystore->HasPendingOpKeypair()) |
1995 | 0 | { |
1996 | 0 | keyErr = mOperationalKeystore->CommitOpKeypairForFabric(fabricIndexBeingCommitted); |
1997 | 0 | if (keyErr != CHIP_NO_ERROR) |
1998 | 0 | { |
1999 | 0 | ChipLogError(FabricProvisioning, "Failed to commit pending operational keypair %" CHIP_ERROR_FORMAT, |
2000 | 0 | keyErr.Format()); |
2001 | 0 | mOperationalKeystore->RevertPendingKeypair(); |
2002 | 0 | } |
2003 | 0 | } |
2004 | 0 | stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : keyErr; |
2005 | | |
2006 | | // For testing only, early return (NEVER OCCURS OTHERWISE) during the commit |
2007 | | // so that clean-ups using the commit marker can be tested. |
2008 | 0 | #if CONFIG_BUILD_FOR_HOST_UNIT_TEST |
2009 | 0 | { |
2010 | 0 | if (mStateFlags.Has(StateFlags::kAbortCommitForTest)) |
2011 | 0 | { |
2012 | | // Clear state so that shutdown doesn't attempt clean-up |
2013 | 0 | mStateFlags.ClearAll(); |
2014 | 0 | mFabricIndexWithPendingState = kUndefinedFabricIndex; |
2015 | 0 | mPendingFabric.Reset(); |
2016 | |
|
2017 | 0 | ChipLogError(FabricProvisioning, "Aborting commit in middle of transaction for testing."); |
2018 | 0 | return CHIP_ERROR_INTERNAL; |
2019 | 0 | } |
2020 | 0 | } |
2021 | 0 | #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
2022 | | |
2023 | | // Commit operational certs |
2024 | 0 | CHIP_ERROR opCertErr = mOpCertStore->CommitOpCertsForFabric(fabricIndexBeingCommitted); |
2025 | 0 | if (opCertErr != CHIP_NO_ERROR) |
2026 | 0 | { |
2027 | 0 | ChipLogError(FabricProvisioning, "Failed to commit pending operational certificates %" CHIP_ERROR_FORMAT, |
2028 | 0 | opCertErr.Format()); |
2029 | 0 | mOpCertStore->RevertPendingOpCerts(); |
2030 | 0 | } |
2031 | 0 | stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : opCertErr; |
2032 | | |
2033 | | // Failure to commit Last Known Good Time is non-fatal. If Last Known |
2034 | | // Good Time is incorrect and this causes incoming certificates to |
2035 | | // appear invalid, the certificate validity policy will see this |
2036 | | // condition and can act appropriately. |
2037 | 0 | CHIP_ERROR lkgtErr = mLastKnownGoodTime.CommitPendingLastKnownGoodChipEpochTime(); |
2038 | 0 | if (lkgtErr != CHIP_NO_ERROR) |
2039 | 0 | { |
2040 | | // Log but this is not sticky... |
2041 | 0 | ChipLogError(FabricProvisioning, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format()); |
2042 | 0 | } |
2043 | | |
2044 | | // If an Add occurred, let's update the fabric index |
2045 | 0 | CHIP_ERROR fabricIndexErr = CHIP_NO_ERROR; |
2046 | 0 | if (mStateFlags.Has(StateFlags::kIsAddPending)) |
2047 | 0 | { |
2048 | 0 | UpdateNextAvailableFabricIndex(); |
2049 | 0 | fabricIndexErr = StoreFabricIndexInfo(); |
2050 | 0 | if (fabricIndexErr != CHIP_NO_ERROR) |
2051 | 0 | { |
2052 | 0 | ChipLogError(FabricProvisioning, "Failed to commit pending fabric indices: %" CHIP_ERROR_FORMAT, |
2053 | 0 | fabricIndexErr.Format()); |
2054 | 0 | } |
2055 | 0 | } |
2056 | 0 | stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : fabricIndexErr; |
2057 | 0 | } |
2058 | | |
2059 | | // Commit must have same side-effect as reverting all pending data |
2060 | 0 | mStateFlags.ClearAll(); |
2061 | 0 | mFabricIndexWithPendingState = kUndefinedFabricIndex; |
2062 | 0 | mPendingFabric.Reset(); |
2063 | |
|
2064 | 0 | if (stickyError != CHIP_NO_ERROR) |
2065 | 0 | { |
2066 | | // Blow-away everything if we got past any storage, even on Update: system state is broken |
2067 | | // TODO: Develop a way to properly revert in the future, but this is very difficult |
2068 | 0 | TEMPORARY_RETURN_IGNORED Delete(fabricIndexBeingCommitted); |
2069 | |
|
2070 | 0 | RevertPendingFabricData(); |
2071 | 0 | } |
2072 | 0 | else |
2073 | 0 | { |
2074 | 0 | TEMPORARY_RETURN_IGNORED NotifyFabricCommitted(fabricIndexBeingCommitted); |
2075 | 0 | } |
2076 | | |
2077 | | // Clear commit marker no matter what: if we got here, there was no reboot and previous clean-ups |
2078 | | // did their job. |
2079 | 0 | ClearCommitMarker(); |
2080 | |
|
2081 | 0 | return stickyError; |
2082 | 0 | } |
2083 | | |
2084 | | void FabricTable::RevertPendingFabricData() |
2085 | 0 | { |
2086 | 0 | MATTER_TRACE_SCOPE("RevertPendingFabricData", "Fabric"); |
2087 | | // Will clear pending UpdateNoc/AddNOC |
2088 | 0 | RevertPendingOpCertsExceptRoot(); |
2089 | |
|
2090 | 0 | if (mOperationalKeystore != nullptr) |
2091 | 0 | { |
2092 | 0 | mOperationalKeystore->RevertPendingKeypair(); |
2093 | 0 | } |
2094 | | |
2095 | | // Clear everything else |
2096 | 0 | if (mOpCertStore != nullptr) |
2097 | 0 | { |
2098 | 0 | mOpCertStore->RevertPendingOpCerts(); |
2099 | 0 | } |
2100 | |
|
2101 | 0 | TEMPORARY_RETURN_IGNORED mLastKnownGoodTime.RevertPendingLastKnownGoodChipEpochTime(); |
2102 | |
|
2103 | 0 | mStateFlags.ClearAll(); |
2104 | 0 | mFabricIndexWithPendingState = kUndefinedFabricIndex; |
2105 | 0 | } |
2106 | | |
2107 | | void FabricTable::RevertPendingOpCertsExceptRoot() |
2108 | 0 | { |
2109 | 0 | MATTER_TRACE_SCOPE("RevertPendingOpCertsExceptRoot", "Fabric"); |
2110 | 0 | mPendingFabric.Reset(); |
2111 | |
|
2112 | 0 | if (mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent)) |
2113 | 0 | { |
2114 | 0 | ChipLogError(FabricProvisioning, "Reverting pending fabric data for fabric 0x%x", |
2115 | 0 | static_cast<unsigned>(mFabricIndexWithPendingState)); |
2116 | 0 | } |
2117 | |
|
2118 | 0 | if (mOpCertStore != nullptr) |
2119 | 0 | { |
2120 | 0 | mOpCertStore->RevertPendingOpCertsExceptRoot(); |
2121 | 0 | } |
2122 | |
|
2123 | 0 | if (mStateFlags.Has(StateFlags::kIsAddPending)) |
2124 | 0 | { |
2125 | | // If we have a pending add, let's make sure to kill the pending fabric metadata and return it to viable state. |
2126 | 0 | TEMPORARY_RETURN_IGNORED Delete(mFabricIndexWithPendingState); |
2127 | 0 | } |
2128 | |
|
2129 | 0 | mStateFlags.Clear(StateFlags::kIsAddPending); |
2130 | 0 | mStateFlags.Clear(StateFlags::kIsUpdatePending); |
2131 | 0 | if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending)) |
2132 | 0 | { |
2133 | 0 | mFabricIndexWithPendingState = kUndefinedFabricIndex; |
2134 | 0 | } |
2135 | 0 | } |
2136 | | |
2137 | | CHIP_ERROR FabricTable::SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel) |
2138 | 0 | { |
2139 | 0 | VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); |
2140 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
2141 | | |
2142 | 0 | VerifyOrReturnError(fabricLabel.size() <= kFabricLabelMaxLengthInBytes, CHIP_ERROR_INVALID_ARGUMENT); |
2143 | | |
2144 | 0 | FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
2145 | 0 | bool fabricIsInitialized = (fabricInfo != nullptr) && fabricInfo->IsInitialized(); |
2146 | 0 | VerifyOrReturnError(fabricIsInitialized, CHIP_ERROR_INVALID_FABRIC_INDEX); |
2147 | | |
2148 | | // Update fabric table current in-memory entry, whether pending or not |
2149 | 0 | ReturnErrorOnFailure(fabricInfo->SetFabricLabel(fabricLabel)); |
2150 | | |
2151 | 0 | if (!mStateFlags.HasAny(StateFlags::kIsAddPending, StateFlags::kIsUpdatePending) && (fabricInfo != &mPendingFabric)) |
2152 | 0 | { |
2153 | | // Nothing is pending, we have to store immediately. |
2154 | 0 | ReturnErrorOnFailure(StoreFabricMetadata(fabricInfo)); |
2155 | 0 | } |
2156 | | |
2157 | 0 | return CHIP_NO_ERROR; |
2158 | 0 | } |
2159 | | |
2160 | | CHIP_ERROR FabricTable::GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel) |
2161 | 0 | { |
2162 | 0 | const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
2163 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); |
2164 | | |
2165 | 0 | outFabricLabel = fabricInfo->GetFabricLabel(); |
2166 | 0 | return CHIP_NO_ERROR; |
2167 | 0 | } |
2168 | | |
2169 | | CHIP_ERROR FabricTable::PeekFabricIndexForNextAddition(FabricIndex & outIndex) |
2170 | 0 | { |
2171 | 0 | EnsureNextAvailableFabricIndexUpdated(); |
2172 | 0 | if (!mNextAvailableFabricIndex.HasValue()) |
2173 | 0 | { |
2174 | 0 | return CHIP_ERROR_NO_MEMORY; |
2175 | 0 | } |
2176 | | |
2177 | 0 | FabricIndex index = mNextAvailableFabricIndex.Value(); |
2178 | 0 | VerifyOrReturnError(IsValidFabricIndex(index), CHIP_ERROR_INVALID_FABRIC_INDEX); |
2179 | | |
2180 | 0 | outIndex = index; |
2181 | 0 | return CHIP_NO_ERROR; |
2182 | 0 | } |
2183 | | |
2184 | | CHIP_ERROR FabricTable::SetFabricIndexForNextAddition(FabricIndex fabricIndex) |
2185 | 0 | { |
2186 | 0 | VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent), CHIP_ERROR_INCORRECT_STATE); |
2187 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
2188 | | |
2189 | 0 | const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); |
2190 | 0 | VerifyOrReturnError(fabricInfo == nullptr, CHIP_ERROR_FABRIC_EXISTS); |
2191 | | |
2192 | 0 | mNextAvailableFabricIndex.SetValue(fabricIndex); |
2193 | 0 | return CHIP_NO_ERROR; |
2194 | 0 | } |
2195 | | |
2196 | | CHIP_ERROR FabricTable::SetShouldAdvertiseIdentity(FabricIndex fabricIndex, AdvertiseIdentity advertiseIdentity) |
2197 | 0 | { |
2198 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); |
2199 | | |
2200 | 0 | FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
2201 | 0 | bool fabricIsInitialized = (fabricInfo != nullptr) && fabricInfo->IsInitialized(); |
2202 | 0 | VerifyOrReturnError(fabricIsInitialized, CHIP_ERROR_INVALID_FABRIC_INDEX); |
2203 | | |
2204 | 0 | fabricInfo->SetShouldAdvertiseIdentity(advertiseIdentity == AdvertiseIdentity::Yes); |
2205 | |
|
2206 | 0 | return CHIP_NO_ERROR; |
2207 | 0 | } |
2208 | | |
2209 | | CHIP_ERROR FabricTable::FetchVIDVerificationStatement(FabricIndex fabricIndex, MutableByteSpan & outVIDVerificationStatement) const |
2210 | 0 | { |
2211 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
2212 | 0 | return mOpCertStore->GetVidVerificationElement( |
2213 | 0 | fabricIndex, OperationalCertificateStore::VidVerificationElement::kVidVerificationStatement, outVIDVerificationStatement); |
2214 | 0 | } |
2215 | | |
2216 | | CHIP_ERROR FabricTable::FetchVVSC(FabricIndex fabricIndex, MutableByteSpan & outVVSC) const |
2217 | 0 | { |
2218 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE); |
2219 | | |
2220 | 0 | return mOpCertStore->GetVidVerificationElement(fabricIndex, OperationalCertificateStore::VidVerificationElement::kVvsc, |
2221 | 0 | outVVSC); |
2222 | 0 | return CHIP_NO_ERROR; |
2223 | 0 | } |
2224 | | |
2225 | | CHIP_ERROR FabricTable::SignVIDVerificationRequest(FabricIndex fabricIndex, ByteSpan clientChallenge, ByteSpan attestationChallenge, |
2226 | | SignVIDVerificationResponseData & outResponse) |
2227 | 0 | { |
2228 | 0 | FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
2229 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
2230 | | |
2231 | 0 | P256PublicKey rootPublicKey; |
2232 | 0 | ReturnErrorOnFailure(fabricInfo->FetchRootPubkey(rootPublicKey)); |
2233 | | |
2234 | | // Step 1: Generate FabricBindingMessage for given fabric. |
2235 | 0 | uint8_t fabricBindingMessageBuffer[kVendorFabricBindingMessageV1Size]; |
2236 | 0 | MutableByteSpan fabricBindingMessageSpan{ fabricBindingMessageBuffer }; |
2237 | |
|
2238 | 0 | ReturnErrorOnFailure( |
2239 | 0 | GenerateVendorFabricBindingMessage(FabricBindingVersion::kVersion1, rootPublicKey, fabricInfo->GetFabricId(), |
2240 | 0 | static_cast<uint16_t>(fabricInfo->GetVendorId()), fabricBindingMessageSpan)); |
2241 | 0 | VerifyOrReturnError(fabricBindingMessageSpan.size() == kVendorFabricBindingMessageV1Size, CHIP_ERROR_INTERNAL); |
2242 | | |
2243 | | // Step 2: Recover VIDVerificationStatement, if any. |
2244 | 0 | uint8_t vidVerificationStatementBuffer[kVendorIdVerificationStatementV1Size]; |
2245 | 0 | MutableByteSpan vidVerificationStatementSpan{ vidVerificationStatementBuffer }; |
2246 | |
|
2247 | 0 | ReturnErrorOnFailure(FetchVIDVerificationStatement(fabricIndex, vidVerificationStatementSpan)); |
2248 | | |
2249 | | // Step 3: Generate VidVerificationToBeSigned |
2250 | 0 | uint8_t vidVerificationTbsBuffer[kVendorIdVerificationTbsV1MaxSize]; |
2251 | 0 | MutableByteSpan vidVerificationTbsSpan{ vidVerificationTbsBuffer }; |
2252 | |
|
2253 | 0 | P256ECDSASignature signature; |
2254 | 0 | auto signatureBuffer = Platform::ScopedMemoryBufferWithSize<uint8_t>(); |
2255 | 0 | VerifyOrReturnError(signatureBuffer.Calloc(signature.Capacity()), CHIP_ERROR_BUFFER_TOO_SMALL); |
2256 | | |
2257 | 0 | ReturnErrorOnFailure(GenerateVendorIdVerificationToBeSigned(fabricIndex, clientChallenge, attestationChallenge, |
2258 | 0 | fabricBindingMessageSpan, vidVerificationStatementSpan, |
2259 | 0 | vidVerificationTbsSpan)); |
2260 | | |
2261 | | // Step 4: Sign the statement with the operational key. |
2262 | 0 | ReturnErrorOnFailure(SignWithOpKeypair(fabricIndex, vidVerificationTbsSpan, signature)); |
2263 | 0 | memcpy(signatureBuffer.Get(), signature.Bytes(), signature.Capacity()); |
2264 | |
|
2265 | 0 | outResponse.fabricIndex = fabricIndex; |
2266 | 0 | outResponse.fabricBindingVersion = fabricBindingMessageSpan[0]; |
2267 | 0 | outResponse.signature = std::move(signatureBuffer); |
2268 | |
|
2269 | 0 | return CHIP_NO_ERROR; |
2270 | 0 | } |
2271 | | |
2272 | | CHIP_ERROR FabricTable::SetVIDVerificationStatementElements(FabricIndex fabricIndex, Optional<uint16_t> vendorId, |
2273 | | Optional<ByteSpan> VIDVerificationStatement, Optional<ByteSpan> VVSC, |
2274 | | bool & outFabricTableWasChanged) |
2275 | 0 | { |
2276 | 0 | VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INTERNAL); |
2277 | 0 | VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT); |
2278 | | |
2279 | 0 | FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex); |
2280 | 0 | VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
2281 | | |
2282 | | // PendingNew fabric means AddNOC had been called. ShadowPending fabric is the overlay version |
2283 | | // that is used when UpdateNOC had been called. This is to detect either condition of not |
2284 | | // fully committed fabric. |
2285 | 0 | bool isTargetFabricPending = (GetPendingNewFabricIndex() == fabricIndex) || |
2286 | 0 | ((GetShadowPendingFabricEntry() != nullptr) && (GetShadowPendingFabricEntry()->GetFabricIndex() == fabricIndex)); |
2287 | |
|
2288 | 0 | outFabricTableWasChanged = false; |
2289 | | |
2290 | | // Start with VVSC first as it's the most likely to fail. |
2291 | 0 | if (VVSC.HasValue()) |
2292 | 0 | { |
2293 | 0 | if (mOpCertStore->HasCertificateForFabric(fabricIndex, OperationalCertificateStore::CertChainElement::kIcac)) |
2294 | 0 | { |
2295 | 0 | ChipLogError(FabricProvisioning, |
2296 | 0 | "Received SetVIDVerificationStatement storage request with VVSC when ICAC already present. ICAC must be " |
2297 | 0 | "removed first."); |
2298 | 0 | return CHIP_ERROR_INCORRECT_STATE; |
2299 | 0 | } |
2300 | | |
2301 | 0 | ReturnErrorOnFailure(mOpCertStore->UpdateVidVerificationSignerCertForFabric(fabricIndex, VVSC.Value())); |
2302 | 0 | } |
2303 | | |
2304 | 0 | if (VIDVerificationStatement.HasValue()) |
2305 | 0 | { |
2306 | 0 | bool wasVvsEqual = false; |
2307 | 0 | { |
2308 | | // This is in a scope to save stack space from getting too deep. |
2309 | 0 | uint8_t vidVerificationStatementBuffer[Crypto::kVendorIdVerificationStatementV1Size]; |
2310 | 0 | MutableByteSpan vidVerificationStatementSpan{ vidVerificationStatementBuffer }; |
2311 | 0 | ReturnErrorOnFailure(mOpCertStore->GetVidVerificationElement( |
2312 | 0 | fabricIndex, OperationalCertificateStore::VidVerificationElement::kVidVerificationStatement, |
2313 | 0 | vidVerificationStatementSpan)); |
2314 | 0 | wasVvsEqual = vidVerificationStatementSpan.data_equal(VIDVerificationStatement.Value()); |
2315 | 0 | } |
2316 | | |
2317 | 0 | if (!wasVvsEqual) |
2318 | 0 | { |
2319 | 0 | ReturnErrorOnFailure( |
2320 | 0 | mOpCertStore->UpdateVidVerificationStatementForFabric(fabricIndex, VIDVerificationStatement.Value())); |
2321 | 0 | outFabricTableWasChanged = true; |
2322 | 0 | } |
2323 | 0 | } |
2324 | | |
2325 | 0 | if (vendorId.HasValue()) |
2326 | 0 | { |
2327 | 0 | if (static_cast<uint16_t>(fabricInfo->GetVendorId()) != vendorId.Value()) |
2328 | 0 | { |
2329 | 0 | fabricInfo->SetVendorId(static_cast<VendorId>(vendorId.Value())); |
2330 | | // Immediately commit Vendor ID if not a pending fabric. Otherwise the commit occurs on CommissioningComplete. |
2331 | 0 | if (!isTargetFabricPending) |
2332 | 0 | { |
2333 | 0 | ReturnErrorOnFailure(StoreFabricMetadata(fabricInfo)); |
2334 | 0 | outFabricTableWasChanged = true; |
2335 | 0 | } |
2336 | 0 | } |
2337 | 0 | } |
2338 | | |
2339 | 0 | return CHIP_NO_ERROR; |
2340 | 0 | } |
2341 | | |
2342 | | } // namespace chip |