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