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