Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021-2022 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #pragma once
18 :
19 : #include <optional>
20 : #include <stdint.h>
21 : #include <sys/types.h>
22 :
23 : #include <app/util/basic-types.h>
24 : #include <crypto/CHIPCryptoPAL.h>
25 : #include <lib/core/CHIPError.h>
26 : #include <lib/core/ClusterEnums.h>
27 : #include <lib/support/CHIPMemString.h>
28 : #include <lib/support/CommonIterator.h>
29 :
30 : namespace chip {
31 : namespace Credentials {
32 :
33 : class GroupDataProvider
34 : {
35 : public:
36 : using SecurityPolicy = app::Clusters::GroupKeyManagement::GroupKeySecurityPolicyEnum;
37 : static constexpr KeysetId kIdentityProtectionKeySetId = 0;
38 : static constexpr size_t kMaxListeners = 2;
39 :
40 : struct GroupInfo
41 : {
42 : static constexpr size_t kGroupNameMax = CHIP_CONFIG_MAX_GROUP_NAME_LENGTH;
43 : enum class Flags : uint8_t
44 : {
45 : kHasAuxiliaryACL = 0b00000001,
46 : kMcastAddrPolicy = 0b00000010,
47 : };
48 : static constexpr uint8_t kFlagsDefault = to_underlying(GroupInfo::Flags::kMcastAddrPolicy);
49 :
50 : // Identifies group within the scope of the given Fabric
51 : GroupId group_id = kUndefinedGroupId;
52 : // Lastest group name written for a given GroupId on any Endpoint via the Groups cluster
53 : char name[kGroupNameMax + 1] = { 0 };
54 : uint8_t flags = kFlagsDefault;
55 : uint16_t count = 0;
56 :
57 2939 : GroupInfo() { SetName(nullptr); }
58 6 : GroupInfo(const GroupInfo & other) { Copy(other); }
59 2362 : GroupInfo(const char * groupName) { SetName(groupName); }
60 : GroupInfo(const CharSpan & groupName) { SetName(groupName); }
61 5838 : GroupInfo(GroupId id, const char * groupName, uint8_t groupFlags = kFlagsDefault) : group_id(id), flags(groupFlags)
62 : {
63 5838 : SetName(groupName);
64 5838 : }
65 31 : GroupInfo(GroupId id, const CharSpan & groupName, uint8_t groupFlags = kFlagsDefault) : group_id(id), flags(groupFlags)
66 : {
67 31 : SetName(groupName);
68 31 : }
69 16030 : void SetName(const char * groupName)
70 : {
71 16030 : if (nullptr == groupName)
72 : {
73 10942 : name[0] = 0;
74 : }
75 : else
76 : {
77 5088 : Platform::CopyString(name, groupName);
78 : }
79 16030 : }
80 8927 : void SetName(const CharSpan & groupName)
81 : {
82 8927 : if (nullptr == groupName.data())
83 : {
84 8897 : name[0] = 0;
85 : }
86 : else
87 : {
88 30 : Platform::CopyString(name, groupName);
89 : }
90 8927 : }
91 4883 : void Copy(const GroupInfo & other)
92 : {
93 4883 : if (this != &other)
94 : {
95 4883 : group_id = other.group_id;
96 4883 : flags = other.flags;
97 4883 : SetName(other.name);
98 : }
99 4883 : }
100 1122 : bool HasAuxiliaryACL() const { return (flags & static_cast<uint8_t>(Flags::kHasAuxiliaryACL)); }
101 4023 : bool UsePerGroupAddress() const { return (flags & static_cast<uint8_t>(Flags::kMcastAddrPolicy)); }
102 :
103 32 : bool operator==(const GroupInfo & other) const
104 : {
105 32 : return (this->group_id == other.group_id) && !strncmp(this->name, other.name, kGroupNameMax);
106 : }
107 1 : GroupInfo & operator=(const GroupInfo & other)
108 : {
109 1 : Copy(other);
110 1 : return *this;
111 : }
112 : };
113 :
114 : struct GroupKey
115 : {
116 68 : GroupKey() = default;
117 1045 : GroupKey(GroupId group, KeysetId keyset) : group_id(group), keyset_id(keyset) {}
118 : // Identifies group within the scope of the given Fabric
119 : GroupId group_id = kUndefinedGroupId;
120 : // Set of group keys that generate operational group keys for use with this group
121 : KeysetId keyset_id = 0;
122 28 : bool operator==(const GroupKey & other) const
123 : {
124 28 : return this->group_id == other.group_id && this->keyset_id == other.keyset_id;
125 : }
126 : };
127 :
128 : struct GroupEndpoint
129 : {
130 1425 : GroupEndpoint() = default;
131 2205 : GroupEndpoint(GroupId group, EndpointId endpoint) : group_id(group), endpoint_id(endpoint) {}
132 : // Identifies group within the scope of the given Fabric
133 : GroupId group_id = kUndefinedGroupId;
134 : // Endpoint on the Node to which messages to this group may be forwarded
135 : EndpointId endpoint_id = kInvalidEndpointId;
136 :
137 : bool operator==(const GroupEndpoint & other) const
138 : {
139 : return this->group_id == other.group_id && this->endpoint_id == other.endpoint_id;
140 : }
141 : };
142 :
143 : struct GroupSession
144 : {
145 10 : GroupSession() = default;
146 : GroupId group_id = kUndefinedGroupId;
147 : FabricIndex fabric_index;
148 : SecurityPolicy security_policy;
149 : Crypto::SymmetricKeyContext * keyContext = nullptr;
150 : };
151 :
152 : // An EpochKey is a single key usable to determine an operational group key
153 : struct EpochKey
154 : {
155 : static constexpr size_t kLengthBytes = Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES;
156 : // Validity start time in microseconds since 2000-01-01T00:00:00 UTC ("the Epoch")
157 : uint64_t start_time;
158 : // Actual key bits. Depending on context, it may be a raw epoch key (as seen within `SetKeySet` calls)
159 : // or it may be the derived operational group key (as seen in any other usage).
160 : uint8_t key[kLengthBytes];
161 :
162 1773 : void Clear()
163 : {
164 1773 : start_time = 0;
165 1773 : Crypto::ClearSecretData(&key[0], sizeof(key));
166 1773 : }
167 : };
168 :
169 : // A operational group key set, usable by many GroupState mappings
170 : struct KeySet
171 : {
172 : static constexpr size_t kEpochKeysMax = 3;
173 :
174 187 : KeySet() { ClearKeys(); }
175 210 : KeySet(uint16_t id, SecurityPolicy policy_id, uint8_t num_keys) : keyset_id(id), policy(policy_id), num_keys_used(num_keys)
176 : {
177 210 : ClearKeys();
178 210 : }
179 :
180 : // The actual keys for the group key set
181 : EpochKey epoch_keys[kEpochKeysMax];
182 : // Logical id provided by the Administrator that configured the entry
183 : uint16_t keyset_id = 0;
184 : // Security policy to use for groups that use this keyset
185 : SecurityPolicy policy = SecurityPolicy::kCacheAndSync;
186 : // Number of keys present
187 : uint8_t num_keys_used = 0;
188 :
189 : bool operator==(const KeySet & other) const
190 : {
191 : VerifyOrReturnError(this->policy == other.policy && this->num_keys_used == other.num_keys_used, false);
192 : return !memcmp(this->epoch_keys, other.epoch_keys, this->num_keys_used * sizeof(EpochKey));
193 : }
194 :
195 564 : void ClearKeys()
196 : {
197 2256 : for (size_t key_idx = 0; key_idx < kEpochKeysMax; ++key_idx)
198 : {
199 1692 : epoch_keys[key_idx].Clear();
200 : }
201 564 : }
202 : };
203 :
204 : /**
205 : * Interface to listen for changes in the Group info.
206 : */
207 : class GroupListener
208 : {
209 : public:
210 173 : virtual ~GroupListener() = default;
211 : /**
212 : * Callback invoked when a new group is added.
213 : *
214 : * @param[in] new_group GroupInfo structure of the new group.
215 : */
216 : virtual void OnGroupAdded(FabricIndex fabric_index, const GroupInfo & new_group) = 0;
217 : /**
218 : * Callback invoked when an existing group is removed.
219 : *
220 : * @param[in] old_group GroupInfo structure of the removed group.
221 : */
222 : virtual void OnGroupRemoved(FabricIndex fabric_index, const GroupInfo & old_group) = 0;
223 : /**
224 : * Callback invoked when an existing group is modified.
225 : * The modifications may be any of the following:
226 : * - Endpoints List modified
227 : * - KeySetID modified
228 : * - Flags modified (kHasAuxiliaryACL or kMcastAddrPolicy)
229 : *
230 : * Note that this callback is not invoked when the group is added or removed.
231 : * Those events are handled by the OnGroupAdded and OnGroupRemoved callbacks respectively.
232 : *
233 : * @param[in] modified_group_id ID of the modified group.
234 : */
235 84 : virtual void OnGroupModified(FabricIndex fabric_index, const GroupId & modified_group_id){};
236 : };
237 :
238 : using GroupInfoIterator = CommonIterator<GroupInfo>;
239 : using GroupKeyIterator = CommonIterator<GroupKey>;
240 : using EndpointIterator = CommonIterator<GroupEndpoint>;
241 : using KeySetIterator = CommonIterator<KeySet>;
242 : using GroupSessionIterator = CommonIterator<GroupSession>;
243 :
244 97 : GroupDataProvider(uint16_t maxGroupsPerFabric, uint16_t maxGroupKeysPerFabric) :
245 97 : mMaxGroupsPerFabric(maxGroupsPerFabric), mMaxGroupKeysPerFabric(maxGroupKeysPerFabric)
246 97 : {}
247 :
248 : enum class GroupCleanupPolicy
249 : {
250 : kDeleteGroupIfEmpty, // Default behavior for legacy Groups
251 : kKeepGroupIfEmpty // Required for Groupcast Sender feature
252 : };
253 :
254 97 : virtual ~GroupDataProvider() = default;
255 :
256 : // Not copyable
257 : GroupDataProvider(const GroupDataProvider &) = delete;
258 : GroupDataProvider & operator=(const GroupDataProvider &) = delete;
259 :
260 : // TODO(#72056): Once groupcast is enabled by default, this should just return mMaxGroupsPerFabric. See GroupDataProviderImpl()
261 : // constructor.
262 730 : uint16_t GetMaxGroupsPerFabric() const
263 : {
264 730 : return static_cast<uint16_t>(IsGroupcastEnabled() ? (getMaxMembershipCount() / 2) : mMaxGroupsPerFabric);
265 : }
266 8 : uint16_t GetMaxGroupKeysPerFabric() const { return mMaxGroupKeysPerFabric; }
267 :
268 : /**
269 : * Initialize the GroupDataProvider, including possibly any persistent
270 : * data store initialization done by the implementation. Must be called once
271 : * before any other API succeeds.
272 : *
273 : * @retval #CHIP_ERROR_INCORRECT_STATE if called when already initialized.
274 : * @retval #CHIP_NO_ERROR on success
275 : */
276 : virtual CHIP_ERROR Init() = 0;
277 : virtual void Finish() = 0;
278 :
279 : //
280 : // Group Table
281 : //
282 :
283 : // By id
284 : virtual CHIP_ERROR SetGroupInfo(FabricIndex fabric_index, const GroupInfo & info) = 0;
285 : virtual CHIP_ERROR GetGroupInfo(FabricIndex fabric_index, GroupId group_id, GroupInfo & info) = 0;
286 : virtual CHIP_ERROR RemoveGroupInfo(FabricIndex fabric_index, GroupId group_id) = 0;
287 : // By index
288 : virtual CHIP_ERROR SetGroupInfoAt(FabricIndex fabric_index, size_t index, const GroupInfo & info) = 0;
289 : virtual CHIP_ERROR GetGroupInfoAt(FabricIndex fabric_index, size_t index, GroupInfo & info) = 0;
290 : virtual CHIP_ERROR RemoveGroupInfoAt(FabricIndex fabric_index, size_t index) = 0;
291 : // Endpoints
292 : virtual bool HasEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
293 : virtual CHIP_ERROR AddEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
294 : virtual CHIP_ERROR RemoveEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id,
295 : GroupCleanupPolicy cleanupPolicy) = 0;
296 : virtual CHIP_ERROR RemoveEndpointAllGroups(FabricIndex fabric_index, EndpointId endpoint_id,
297 : GroupCleanupPolicy cleanupPolicy) = 0;
298 : virtual CHIP_ERROR RemoveEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
299 : virtual CHIP_ERROR RemoveEndpoint(FabricIndex fabric_index, EndpointId endpoint_id) = 0;
300 : virtual CHIP_ERROR RemoveEndpoints(FabricIndex fabric_index, GroupId group_id) = 0;
301 : // Iterators
302 : /**
303 : * Creates an iterator that may be used to obtain the list of groups associated with the given fabric.
304 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
305 : * Modifying the group table during the iteration is currently not supported, and may yield unexpected behaviour.
306 : * @retval An instance of EndpointIterator on success
307 : * @retval nullptr if no iterator instances are available.
308 : */
309 : virtual GroupInfoIterator * IterateGroupInfo(FabricIndex fabric_index) = 0;
310 : /**
311 : * Creates an iterator that may be used to obtain the list of (group, endpoint) pairs associated with the given fabric.
312 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
313 : * Modifying the group table during the iteration is currently not supported, and may yield unexpected behaviour.
314 : * If you wish to iterate only the endpoints of a particular group id you can provide the optional `group_id` to do so.
315 : * @retval An instance of EndpointIterator on success
316 : * @retval nullptr if no iterator instances are available.
317 : */
318 : virtual EndpointIterator * IterateEndpoints(FabricIndex fabric_index, std::optional<GroupId> group_id = std::nullopt) = 0;
319 :
320 : //
321 : // Group-Key map
322 : //
323 :
324 : virtual CHIP_ERROR SetGroupKey(FabricIndex fabric_index, GroupId group_id, KeysetId keyset_id) = 0;
325 : virtual CHIP_ERROR SetGroupKeyAt(FabricIndex fabric_index, size_t index, const GroupKey & info) = 0;
326 : virtual CHIP_ERROR GetGroupKey(FabricIndex fabric_index, GroupId group_id, KeysetId & keyset_id) = 0;
327 : virtual CHIP_ERROR GetGroupKeyAt(FabricIndex fabric_index, size_t index, GroupKey & info) = 0;
328 : virtual CHIP_ERROR RemoveGroupKeyAt(FabricIndex fabric_index, size_t index) = 0;
329 : virtual CHIP_ERROR RemoveGroupKeys(FabricIndex fabric_index) = 0;
330 :
331 : /**
332 : * Creates an iterator that may be used to obtain the list of (group, keyset) pairs associated with the given fabric.
333 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
334 : * Modifying the keyset mappings during the iteration is currently not supported, and may yield unexpected behaviour.
335 : * @retval An instance of GroupKeyIterator on success
336 : * @retval nullptr if no iterator instances are available.
337 : */
338 : virtual GroupKeyIterator * IterateGroupKeys(FabricIndex fabric_index) = 0;
339 :
340 : //
341 : // Key Sets
342 : //
343 :
344 : virtual CHIP_ERROR SetKeySet(FabricIndex fabric_index, const ByteSpan & compressed_fabric_id, const KeySet & keys) = 0;
345 : virtual CHIP_ERROR GetKeySet(FabricIndex fabric_index, KeysetId keyset_id, KeySet & keys) = 0;
346 : virtual CHIP_ERROR RemoveKeySet(FabricIndex fabric_index, KeysetId keyset_id) = 0;
347 :
348 : /**
349 : * @brief Obtain the actual operational Identity Protection Key (IPK) keyset for a given
350 : * fabric. These keys are used by the CASE protocol, and do not participate in
351 : * any direct traffic encryption. Since the identity protection operational keyset
352 : * is used in multiple key derivations and procedures, it cannot be hidden behind a
353 : * SymmetricKeyContext, and must be obtainable by value.
354 : *
355 : * @param fabric_index - Fabric index for which to get the IPK operational keyset
356 : * @param out_keyset - Reference to a KeySet where the IPK keys will be stored on success
357 : * @return CHIP_NO_ERROR on success, CHIP_ERROR_NOT_FOUND if the IPK keyset is somehow unavailable
358 : * or another CHIP_ERROR value if an internal storage error occurs.
359 : */
360 : virtual CHIP_ERROR GetIpkKeySet(FabricIndex fabric_index, KeySet & out_keyset) = 0;
361 :
362 : /**
363 : * Creates an iterator that may be used to obtain the list of key sets associated with the given fabric.
364 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
365 : * Modifying the key sets table during the iteration is currently not supported, and may yield unexpected behaviour.
366 : *
367 : * @retval An instance of KeySetIterator on success
368 : * @retval nullptr if no iterator instances are available.
369 : */
370 : virtual KeySetIterator * IterateKeySets(FabricIndex fabric_index) = 0;
371 :
372 : // Fabrics
373 : virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0;
374 :
375 : // Decryption
376 : virtual GroupSessionIterator * IterateGroupSessions(uint16_t session_id) = 0;
377 : virtual Crypto::SymmetricKeyContext * GetKeyContext(FabricIndex fabric_index, GroupId group_id) = 0;
378 :
379 : // Listener
380 35 : void SetListener(GroupListener * listener)
381 : {
382 53 : for (size_t i = 0; listener && (i < kMaxListeners); ++i)
383 : {
384 52 : if (nullptr == mListeners[i])
385 : {
386 34 : mListeners[i] = listener;
387 34 : return;
388 : }
389 : }
390 : }
391 33 : void RemoveListener(GroupListener * listener)
392 : {
393 51 : for (size_t i = 0; listener && (i < kMaxListeners); ++i)
394 : {
395 50 : if (listener == mListeners[i])
396 : {
397 32 : mListeners[i] = nullptr;
398 32 : return;
399 : }
400 : }
401 : }
402 :
403 17 : void SetGroupcastEnabled(bool groupcastVal) { mGroupcastEnabled = groupcastVal; }
404 :
405 2220 : bool IsGroupcastEnabled() const { return mGroupcastEnabled; }
406 :
407 : // Groupcast
408 : virtual uint16_t getMaxMembershipCount() const = 0;
409 : virtual uint16_t getMaxMcastAddrCount() const = 0;
410 :
411 : /**
412 : * @brief Check if a notification is needed for Auxiliary ACL changes and reset the flag.
413 : *
414 : * This method returns true if the last set of operations (e.g., SetGroupInfo, RemoveGroupInfo)
415 : * somehow changed the Auxiliary ACL status of a group. Calling this method resets the internal flag.
416 : *
417 : * @return true if a notification is needed, false otherwise.
418 : */
419 : virtual bool ConsumeAuxAclNotificationNeeded() = 0;
420 :
421 : protected:
422 314 : void GroupAdded(FabricIndex fabric_index, const GroupInfo & new_group)
423 : {
424 942 : for (auto * listener : mListeners)
425 : {
426 628 : if (listener != nullptr)
427 : {
428 146 : listener->OnGroupAdded(fabric_index, new_group);
429 : }
430 : }
431 314 : }
432 67 : void GroupRemoved(FabricIndex fabric_index, const GroupInfo & old_group)
433 : {
434 201 : for (auto * listener : mListeners)
435 : {
436 134 : if (listener != nullptr)
437 : {
438 68 : listener->OnGroupRemoved(fabric_index, old_group);
439 : }
440 : }
441 67 : }
442 1638 : void GroupModified(FabricIndex fabric_index, const GroupId & modified_group_id)
443 : {
444 4914 : for (auto * listener : mListeners)
445 : {
446 3276 : if (listener != nullptr)
447 : {
448 2280 : listener->OnGroupModified(fabric_index, modified_group_id);
449 : }
450 : }
451 1638 : }
452 : const uint16_t mMaxGroupsPerFabric;
453 : const uint16_t mMaxGroupKeysPerFabric;
454 : GroupListener * mListeners[kMaxListeners] = { nullptr };
455 : bool mGroupcastEnabled = false;
456 : };
457 :
458 : /**
459 : * @brief Utility Set the IPK Epoch key on a GroupDataProvider assuming a single IPK
460 : *
461 : * This utility replaces having to call `GroupDataProvider::SetKeySet` for the simple situation of a
462 : * single IPK for a fabric, if a single epoch key is used. Start time will be set to 0 ("was always valid")
463 : *
464 : * @param provider - pointer to GroupDataProvider on which to set the IPK
465 : * @param fabric_index - fabric index within the GroupDataProvider for which to set the IPK
466 : * @param ipk_epoch_span - Span containing the IPK epoch key
467 : * @param compressed_fabric_id - Compressed fabric ID associated with the fabric, for key derivation
468 : * @return CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT on any bad argument, other CHIP_ERROR values
469 : * from implementation on other errors
470 : */
471 1 : inline CHIP_ERROR SetSingleIpkEpochKey(GroupDataProvider * provider, FabricIndex fabric_index, const ByteSpan & ipk_epoch_span,
472 : const ByteSpan & compressed_fabric_id)
473 : {
474 : GroupDataProvider::KeySet ipkKeySet(GroupDataProvider::kIdentityProtectionKeySetId,
475 1 : GroupDataProvider::SecurityPolicy::kTrustFirst, 1);
476 :
477 1 : VerifyOrReturnError(provider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
478 1 : VerifyOrReturnError(ipk_epoch_span.size() == sizeof(ipkKeySet.epoch_keys[0].key), CHIP_ERROR_INVALID_ARGUMENT);
479 1 : VerifyOrReturnError(compressed_fabric_id.size() == sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT);
480 :
481 1 : ipkKeySet.epoch_keys[0].start_time = 0;
482 1 : memcpy(&ipkKeySet.epoch_keys[0].key, ipk_epoch_span.data(), ipk_epoch_span.size());
483 :
484 : // Set a single IPK, validate key derivation follows spec
485 1 : return provider->SetKeySet(fabric_index, compressed_fabric_id, ipkKeySet);
486 : }
487 :
488 : /**
489 : * Instance getter for the global GroupDataProvider.
490 : *
491 : * Callers have to externally synchronize usage of this function.
492 : *
493 : * @return The global Group Data Provider
494 : */
495 : GroupDataProvider * GetGroupDataProvider();
496 :
497 : /**
498 : * Instance setter for the global GroupDataProvider.
499 : *
500 : * Callers have to externally synchronize usage of this function.
501 : *
502 : * The `provider` can be set to nullptr if the owner is done with it fully.
503 : *
504 : * @param[in] provider pointer to the Group Data Provider global isntance to use
505 : */
506 : void SetGroupDataProvider(GroupDataProvider * provider);
507 :
508 : } // namespace Credentials
509 : } // namespace chip
|