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 :
39 : struct GroupInfo
40 : {
41 : static constexpr size_t kGroupNameMax = CHIP_CONFIG_MAX_GROUP_NAME_LENGTH;
42 : enum class Flags : uint8_t
43 : {
44 : kHasAuxiliaryACL = 0b00000001,
45 : kMcastAddrPolicy = 0b00000010,
46 : };
47 :
48 : // Identifies group within the scope of the given Fabric
49 : GroupId group_id = kUndefinedGroupId;
50 : // Lastest group name written for a given GroupId on any Endpoint via the Groups cluster
51 : char name[kGroupNameMax + 1] = { 0 };
52 : uint8_t flags = 0;
53 : uint16_t count = 0;
54 :
55 78 : GroupInfo() { SetName(nullptr); }
56 6 : GroupInfo(const GroupInfo & other) { Copy(other); }
57 1595 : GroupInfo(const char * groupName) { SetName(groupName); }
58 : GroupInfo(const CharSpan & groupName) { SetName(groupName); }
59 1435 : GroupInfo(GroupId id, const char * groupName, uint8_t groupFlags = 0) : group_id(id), flags(groupFlags)
60 : {
61 1435 : SetName(groupName);
62 1435 : }
63 31 : GroupInfo(GroupId id, const CharSpan & groupName, uint8_t groupFlags = 0) : group_id(id), flags(groupFlags)
64 : {
65 31 : SetName(groupName);
66 31 : }
67 3784 : void SetName(const char * groupName)
68 : {
69 3784 : if (nullptr == groupName)
70 : {
71 2911 : name[0] = 0;
72 : }
73 : else
74 : {
75 873 : Platform::CopyString(name, groupName);
76 : }
77 3784 : }
78 3348 : void SetName(const CharSpan & groupName)
79 : {
80 3348 : if (nullptr == groupName.data())
81 : {
82 3318 : name[0] = 0;
83 : }
84 : else
85 : {
86 30 : Platform::CopyString(name, groupName);
87 : }
88 3348 : }
89 676 : void Copy(const GroupInfo & other)
90 : {
91 676 : if (this != &other)
92 : {
93 676 : group_id = other.group_id;
94 676 : flags = other.flags;
95 676 : SetName(other.name);
96 : }
97 676 : }
98 32 : bool operator==(const GroupInfo & other) const
99 : {
100 32 : return (this->group_id == other.group_id) && !strncmp(this->name, other.name, kGroupNameMax);
101 : }
102 1 : GroupInfo & operator=(const GroupInfo & other)
103 : {
104 1 : Copy(other);
105 1 : return *this;
106 : }
107 : };
108 :
109 : struct GroupKey
110 : {
111 66 : GroupKey() = default;
112 858 : GroupKey(GroupId group, KeysetId keyset) : group_id(group), keyset_id(keyset) {}
113 : // Identifies group within the scope of the given Fabric
114 : GroupId group_id = kUndefinedGroupId;
115 : // Set of group keys that generate operational group keys for use with this group
116 : KeysetId keyset_id = 0;
117 28 : bool operator==(const GroupKey & other) const
118 : {
119 28 : return this->group_id == other.group_id && this->keyset_id == other.keyset_id;
120 : }
121 : };
122 :
123 : struct GroupEndpoint
124 : {
125 934 : GroupEndpoint() = default;
126 1608 : GroupEndpoint(GroupId group, EndpointId endpoint) : group_id(group), endpoint_id(endpoint) {}
127 : // Identifies group within the scope of the given Fabric
128 : GroupId group_id = kUndefinedGroupId;
129 : // Endpoint on the Node to which messages to this group may be forwarded
130 : EndpointId endpoint_id = kInvalidEndpointId;
131 :
132 : bool operator==(const GroupEndpoint & other) const
133 : {
134 : return this->group_id == other.group_id && this->endpoint_id == other.endpoint_id;
135 : }
136 : };
137 :
138 : struct GroupSession
139 : {
140 9 : GroupSession() = default;
141 : GroupId group_id = kUndefinedGroupId;
142 : FabricIndex fabric_index;
143 : SecurityPolicy security_policy;
144 : Crypto::SymmetricKeyContext * keyContext = nullptr;
145 : };
146 :
147 : // An EpochKey is a single key usable to determine an operational group key
148 : struct EpochKey
149 : {
150 : static constexpr size_t kLengthBytes = Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES;
151 : // Validity start time in microseconds since 2000-01-01T00:00:00 UTC ("the Epoch")
152 : uint64_t start_time;
153 : // Actual key bits. Depending on context, it may be a raw epoch key (as seen within `SetKeySet` calls)
154 : // or it may be the derived operational group key (as seen in any other usage).
155 : uint8_t key[kLengthBytes];
156 :
157 357 : void Clear()
158 : {
159 357 : start_time = 0;
160 357 : Crypto::ClearSecretData(&key[0], sizeof(key));
161 357 : }
162 : };
163 :
164 : // A operational group key set, usable by many GroupState mappings
165 : struct KeySet
166 : {
167 : static constexpr size_t kEpochKeysMax = 3;
168 :
169 119 : KeySet() = default;
170 210 : KeySet(uint16_t id, SecurityPolicy policy_id, uint8_t num_keys) : keyset_id(id), policy(policy_id), num_keys_used(num_keys)
171 210 : {}
172 :
173 : // The actual keys for the group key set
174 : EpochKey epoch_keys[kEpochKeysMax];
175 : // Logical id provided by the Administrator that configured the entry
176 : uint16_t keyset_id = 0;
177 : // Security policy to use for groups that use this keyset
178 : SecurityPolicy policy = SecurityPolicy::kCacheAndSync;
179 : // Number of keys present
180 : uint8_t num_keys_used = 0;
181 :
182 : bool operator==(const KeySet & other) const
183 : {
184 : VerifyOrReturnError(this->policy == other.policy && this->num_keys_used == other.num_keys_used, false);
185 : return !memcmp(this->epoch_keys, other.epoch_keys, this->num_keys_used * sizeof(EpochKey));
186 : }
187 :
188 92 : void ClearKeys()
189 : {
190 368 : for (size_t key_idx = 0; key_idx < kEpochKeysMax; ++key_idx)
191 : {
192 276 : epoch_keys[key_idx].Clear();
193 : }
194 92 : }
195 : };
196 :
197 : /**
198 : * Interface to listen for changes in the Group info.
199 : */
200 : class GroupListener
201 : {
202 : public:
203 69 : virtual ~GroupListener() = default;
204 : /**
205 : * Callback invoked when a new group is added.
206 : *
207 : * @param[in] new_group GroupInfo structure of the new group.
208 : */
209 : virtual void OnGroupAdded(FabricIndex fabric_index, const GroupInfo & new_group) = 0;
210 : /**
211 : * Callback invoked when an existing group is removed.
212 : *
213 : * @param[in] old_group GroupInfo structure of the removed group.
214 : */
215 : virtual void OnGroupRemoved(FabricIndex fabric_index, const GroupInfo & old_group) = 0;
216 : };
217 :
218 : using GroupInfoIterator = CommonIterator<GroupInfo>;
219 : using GroupKeyIterator = CommonIterator<GroupKey>;
220 : using EndpointIterator = CommonIterator<GroupEndpoint>;
221 : using KeySetIterator = CommonIterator<KeySet>;
222 : using GroupSessionIterator = CommonIterator<GroupSession>;
223 :
224 85 : GroupDataProvider(uint16_t maxGroupsPerFabric = CHIP_CONFIG_MAX_GROUPS_PER_FABRIC,
225 85 : uint16_t maxGroupKeysPerFabric = CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC) :
226 85 : mMaxGroupsPerFabric(maxGroupsPerFabric),
227 85 : mMaxGroupKeysPerFabric(maxGroupKeysPerFabric)
228 85 : {}
229 :
230 85 : virtual ~GroupDataProvider() = default;
231 :
232 : // Not copyable
233 : GroupDataProvider(const GroupDataProvider &) = delete;
234 : GroupDataProvider & operator=(const GroupDataProvider &) = delete;
235 :
236 30 : uint16_t GetMaxGroupsPerFabric() const { return mMaxGroupsPerFabric; }
237 0 : uint16_t GetMaxGroupKeysPerFabric() const { return mMaxGroupKeysPerFabric; }
238 :
239 : /**
240 : * Initialize the GroupDataProvider, including possibly any persistent
241 : * data store initialization done by the implementation. Must be called once
242 : * before any other API succeeds.
243 : *
244 : * @retval #CHIP_ERROR_INCORRECT_STATE if called when already initialized.
245 : * @retval #CHIP_NO_ERROR on success
246 : */
247 : virtual CHIP_ERROR Init() = 0;
248 : virtual void Finish() = 0;
249 :
250 : //
251 : // Group Table
252 : //
253 :
254 : // By id
255 : virtual CHIP_ERROR SetGroupInfo(FabricIndex fabric_index, const GroupInfo & info) = 0;
256 : virtual CHIP_ERROR GetGroupInfo(FabricIndex fabric_index, GroupId group_id, GroupInfo & info) = 0;
257 : virtual CHIP_ERROR RemoveGroupInfo(FabricIndex fabric_index, GroupId group_id) = 0;
258 : // By index
259 : virtual CHIP_ERROR SetGroupInfoAt(FabricIndex fabric_index, size_t index, const GroupInfo & info) = 0;
260 : virtual CHIP_ERROR GetGroupInfoAt(FabricIndex fabric_index, size_t index, GroupInfo & info) = 0;
261 : virtual CHIP_ERROR RemoveGroupInfoAt(FabricIndex fabric_index, size_t index) = 0;
262 : // Endpoints
263 : virtual bool HasEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
264 : virtual CHIP_ERROR AddEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
265 : virtual CHIP_ERROR RemoveEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id) = 0;
266 : virtual CHIP_ERROR RemoveEndpoint(FabricIndex fabric_index, EndpointId endpoint_id) = 0;
267 : virtual CHIP_ERROR RemoveEndpoints(FabricIndex fabric_index, GroupId group_id) = 0;
268 : // Iterators
269 : /**
270 : * Creates an iterator that may be used to obtain the list of groups associated with the given fabric.
271 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
272 : * Modifying the group table during the iteration is currently not supported, and may yield unexpected behaviour.
273 : * @retval An instance of EndpointIterator on success
274 : * @retval nullptr if no iterator instances are available.
275 : */
276 : virtual GroupInfoIterator * IterateGroupInfo(FabricIndex fabric_index) = 0;
277 : /**
278 : * Creates an iterator that may be used to obtain the list of (group, endpoint) pairs associated with the given fabric.
279 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
280 : * Modifying the group table during the iteration is currently not supported, and may yield unexpected behaviour.
281 : * If you wish to iterate only the endpoints of a particular group id you can provide the optional `group_id` to do so.
282 : * @retval An instance of EndpointIterator on success
283 : * @retval nullptr if no iterator instances are available.
284 : */
285 : virtual EndpointIterator * IterateEndpoints(FabricIndex fabric_index, std::optional<GroupId> group_id = std::nullopt) = 0;
286 :
287 : //
288 : // Group-Key map
289 : //
290 :
291 : virtual CHIP_ERROR SetGroupKey(FabricIndex fabric_index, GroupId group_id, KeysetId keyset_id) = 0;
292 : virtual CHIP_ERROR SetGroupKeyAt(FabricIndex fabric_index, size_t index, const GroupKey & info) = 0;
293 : virtual CHIP_ERROR GetGroupKey(FabricIndex fabric_index, GroupId group_id, KeysetId & keyset_id) = 0;
294 : virtual CHIP_ERROR GetGroupKeyAt(FabricIndex fabric_index, size_t index, GroupKey & info) = 0;
295 : virtual CHIP_ERROR RemoveGroupKeyAt(FabricIndex fabric_index, size_t index) = 0;
296 : virtual CHIP_ERROR RemoveGroupKeys(FabricIndex fabric_index) = 0;
297 :
298 : /**
299 : * Creates an iterator that may be used to obtain the list of (group, keyset) pairs associated with the given fabric.
300 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
301 : * Modifying the keyset mappings during the iteration is currently not supported, and may yield unexpected behaviour.
302 : * @retval An instance of GroupKeyIterator on success
303 : * @retval nullptr if no iterator instances are available.
304 : */
305 : virtual GroupKeyIterator * IterateGroupKeys(FabricIndex fabric_index) = 0;
306 :
307 : //
308 : // Key Sets
309 : //
310 :
311 : virtual CHIP_ERROR SetKeySet(FabricIndex fabric_index, const ByteSpan & compressed_fabric_id, const KeySet & keys) = 0;
312 : virtual CHIP_ERROR GetKeySet(FabricIndex fabric_index, KeysetId keyset_id, KeySet & keys) = 0;
313 : virtual CHIP_ERROR RemoveKeySet(FabricIndex fabric_index, KeysetId keyset_id) = 0;
314 :
315 : /**
316 : * @brief Obtain the actual operational Identity Protection Key (IPK) keyset for a given
317 : * fabric. These keys are used by the CASE protocol, and do not participate in
318 : * any direct traffic encryption. Since the identity protection operational keyset
319 : * is used in multiple key derivations and procedures, it cannot be hidden behind a
320 : * SymmetricKeyContext, and must be obtainable by value.
321 : *
322 : * @param fabric_index - Fabric index for which to get the IPK operational keyset
323 : * @param out_keyset - Reference to a KeySet where the IPK keys will be stored on success
324 : * @return CHIP_NO_ERROR on success, CHIP_ERROR_NOT_FOUND if the IPK keyset is somehow unavailable
325 : * or another CHIP_ERROR value if an internal storage error occurs.
326 : */
327 : virtual CHIP_ERROR GetIpkKeySet(FabricIndex fabric_index, KeySet & out_keyset) = 0;
328 :
329 : /**
330 : * Creates an iterator that may be used to obtain the list of key sets associated with the given fabric.
331 : * In order to release the allocated memory, the Release() method must be called after the iteration is finished.
332 : * Modifying the key sets table during the iteration is currently not supported, and may yield unexpected behaviour.
333 : *
334 : * @retval An instance of KeySetIterator on success
335 : * @retval nullptr if no iterator instances are available.
336 : */
337 : virtual KeySetIterator * IterateKeySets(FabricIndex fabric_index) = 0;
338 :
339 : // Fabrics
340 : virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0;
341 :
342 : // Decryption
343 : virtual GroupSessionIterator * IterateGroupSessions(uint16_t session_id) = 0;
344 : virtual Crypto::SymmetricKeyContext * GetKeyContext(FabricIndex fabric_index, GroupId group_id) = 0;
345 :
346 : // Listener
347 2 : void SetListener(GroupListener * listener) { mListener = listener; };
348 : void RemoveListener() { mListener = nullptr; };
349 :
350 : // Groupcast MaxMembershipCount
351 : virtual uint16_t getMaxMembershipCount() = 0;
352 :
353 : protected:
354 259 : void GroupAdded(FabricIndex fabric_index, const GroupInfo & new_group)
355 : {
356 259 : if (mListener)
357 : {
358 38 : mListener->OnGroupAdded(fabric_index, new_group);
359 : }
360 259 : }
361 39 : void GroupRemoved(FabricIndex fabric_index, const GroupInfo & old_group)
362 : {
363 39 : if (mListener)
364 : {
365 32 : mListener->OnGroupRemoved(fabric_index, old_group);
366 : }
367 39 : }
368 : const uint16_t mMaxGroupsPerFabric;
369 : const uint16_t mMaxGroupKeysPerFabric;
370 : GroupListener * mListener = nullptr;
371 : };
372 :
373 : /**
374 : * @brief Utility Set the IPK Epoch key on a GroupDataProvider assuming a single IPK
375 : *
376 : * This utility replaces having to call `GroupDataProvider::SetKeySet` for the simple situation of a
377 : * single IPK for a fabric, if a single epoch key is used. Start time will be set to 0 ("was always valid")
378 : *
379 : * @param provider - pointer to GroupDataProvider on which to set the IPK
380 : * @param fabric_index - fabric index within the GroupDataProvider for which to set the IPK
381 : * @param ipk_epoch_span - Span containing the IPK epoch key
382 : * @param compressed_fabric_id - Compressed fabric ID associated with the fabric, for key derivation
383 : * @return CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT on any bad argument, other CHIP_ERROR values
384 : * from implementation on other errors
385 : */
386 1 : inline CHIP_ERROR SetSingleIpkEpochKey(GroupDataProvider * provider, FabricIndex fabric_index, const ByteSpan & ipk_epoch_span,
387 : const ByteSpan & compressed_fabric_id)
388 : {
389 : GroupDataProvider::KeySet ipkKeySet(GroupDataProvider::kIdentityProtectionKeySetId,
390 1 : GroupDataProvider::SecurityPolicy::kTrustFirst, 1);
391 :
392 1 : VerifyOrReturnError(provider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
393 1 : VerifyOrReturnError(ipk_epoch_span.size() == sizeof(ipkKeySet.epoch_keys[0].key), CHIP_ERROR_INVALID_ARGUMENT);
394 1 : VerifyOrReturnError(compressed_fabric_id.size() == sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT);
395 :
396 1 : ipkKeySet.epoch_keys[0].start_time = 0;
397 1 : memcpy(&ipkKeySet.epoch_keys[0].key, ipk_epoch_span.data(), ipk_epoch_span.size());
398 :
399 : // Set a single IPK, validate key derivation follows spec
400 1 : return provider->SetKeySet(fabric_index, compressed_fabric_id, ipkKeySet);
401 : }
402 :
403 : /**
404 : * Instance getter for the global GroupDataProvider.
405 : *
406 : * Callers have to externally synchronize usage of this function.
407 : *
408 : * @return The global Group Data Provider
409 : */
410 : GroupDataProvider * GetGroupDataProvider();
411 :
412 : /**
413 : * Instance setter for the global GroupDataProvider.
414 : *
415 : * Callers have to externally synchronize usage of this function.
416 : *
417 : * The `provider` can be set to nullptr if the owner is done with it fully.
418 : *
419 : * @param[in] provider pointer to the Group Data Provider global isntance to use
420 : */
421 : void SetGroupDataProvider(GroupDataProvider * provider);
422 :
423 : } // namespace Credentials
424 : } // namespace chip
|