Line data Source code
1 : /*
2 : * Copyright (c) 2021 Project CHIP Authors
3 : *
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : */
16 :
17 : /**
18 : * @file
19 : * This file defines the CHIP message counters of remote nodes.
20 : *
21 : */
22 : #pragma once
23 :
24 : #include <array>
25 : #include <bitset>
26 :
27 : #include <lib/support/Span.h>
28 :
29 : namespace chip {
30 : namespace Transport {
31 :
32 : class PeerMessageCounter
33 : {
34 : public:
35 : static constexpr size_t kChallengeSize = 8;
36 : static constexpr uint32_t kInitialSyncValue = 0;
37 :
38 153723 : PeerMessageCounter() : mStatus(Status::NotSynced) {}
39 153723 : ~PeerMessageCounter() { Reset(); }
40 :
41 20741 : void Reset()
42 : {
43 20741 : switch (mStatus)
44 : {
45 19832 : case Status::NotSynced:
46 19832 : break;
47 0 : case Status::SyncInProcess:
48 0 : mSyncInProcess.~SyncInProcess();
49 0 : break;
50 909 : case Status::Synced:
51 909 : mSynced.~Synced();
52 909 : break;
53 : }
54 20741 : mStatus = Status::NotSynced;
55 20741 : }
56 :
57 0 : bool IsSynchronizing() { return mStatus == Status::SyncInProcess; }
58 0 : bool IsSynchronized() { return mStatus == Status::Synced; }
59 :
60 0 : void SyncStarting(FixedByteSpan<kChallengeSize> challenge)
61 : {
62 0 : VerifyOrDie(mStatus == Status::NotSynced);
63 0 : mStatus = Status::SyncInProcess;
64 0 : new (&mSyncInProcess) SyncInProcess();
65 0 : ::memcpy(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize);
66 0 : }
67 :
68 0 : void SyncFailed() { Reset(); }
69 :
70 0 : CHIP_ERROR VerifyChallenge(uint32_t counter, FixedByteSpan<kChallengeSize> challenge)
71 : {
72 0 : if (mStatus != Status::SyncInProcess)
73 : {
74 0 : return CHIP_ERROR_INCORRECT_STATE;
75 : }
76 0 : if (::memcmp(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize) != 0)
77 : {
78 0 : return CHIP_ERROR_INVALID_ARGUMENT;
79 : }
80 :
81 0 : mSyncInProcess.~SyncInProcess();
82 0 : mStatus = Status::Synced;
83 0 : new (&mSynced) Synced();
84 0 : mSynced.mMaxCounter = counter;
85 0 : mSynced.mWindow.reset(); // reset all bits, accept all packets in the window
86 0 : return CHIP_NO_ERROR;
87 : }
88 :
89 : /**
90 : * @brief Implementation of spec 4.5.4.2
91 : *
92 : * For encrypted messages of Group Session Type, any arriving message with a counter in the range
93 : * [(max_message_counter + 1) to (max_message_counter + 2^31 - 1)] (modulo 2^32) SHALL be considered
94 : * new, and cause the max_message_counter value to be updated. Messages with counters from
95 : * [(max_message_counter - 2^31) to (max_message_counter - MSG_COUNTER_WINDOW_SIZE - 1)] (modulo 2^
96 : * 32) SHALL be considered duplicate. Message counters within the range of the bitmap SHALL be
97 : * considered duplicate if the corresponding bit offset is set to true.
98 : *
99 : */
100 3 : CHIP_ERROR VerifyGroup(uint32_t counter) const
101 : {
102 3 : if (mStatus != Status::Synced)
103 : {
104 0 : return CHIP_ERROR_INCORRECT_STATE;
105 : }
106 :
107 3 : Position pos = ClassifyWithRollover(counter);
108 3 : return VerifyPositionEncrypted(pos, counter);
109 : }
110 :
111 4 : CHIP_ERROR VerifyOrTrustFirstGroup(uint32_t counter)
112 : {
113 4 : switch (mStatus)
114 : {
115 1 : case Status::NotSynced: {
116 : // Trust and set the counter when not synced
117 1 : SetCounter(counter);
118 1 : return CHIP_NO_ERROR;
119 : }
120 3 : case Status::Synced: {
121 3 : return VerifyGroup(counter);
122 : }
123 0 : default:
124 0 : VerifyOrDie(false);
125 : return CHIP_ERROR_INTERNAL;
126 : }
127 : }
128 :
129 : /**
130 : * @brief
131 : * With the group counter verified and the packet MIC also verified by the secure key, we can trust the packet and adjust
132 : * counter states.
133 : *
134 : * @pre counter has been verified via VerifyGroup or VerifyOrTrustFirstGroup
135 : */
136 3 : void CommitGroup(uint32_t counter) { CommitWithRollover(counter); }
137 :
138 9966 : CHIP_ERROR VerifyEncryptedUnicast(uint32_t counter) const
139 : {
140 9966 : if (mStatus != Status::Synced)
141 : {
142 0 : return CHIP_ERROR_INCORRECT_STATE;
143 : }
144 :
145 9966 : Position pos = ClassifyWithoutRollover(counter);
146 9966 : return VerifyPositionEncrypted(pos, counter);
147 : }
148 :
149 : /**
150 : * @brief
151 : * With the counter verified and the packet MIC also verified by the secure key, we can trust the packet and adjust
152 : * counter states.
153 : *
154 : * @pre counter has been verified via VerifyEncryptedUnicast
155 : */
156 9958 : void CommitEncryptedUnicast(uint32_t counter) { CommitWithoutRollover(counter); }
157 :
158 98 : CHIP_ERROR VerifyUnencrypted(uint32_t counter)
159 : {
160 98 : switch (mStatus)
161 : {
162 43 : case Status::NotSynced: {
163 : // Trust and set the counter when not synced
164 43 : SetCounter(counter);
165 43 : return CHIP_NO_ERROR;
166 : }
167 55 : case Status::Synced: {
168 55 : Position pos = ClassifyWithRollover(counter);
169 55 : return VerifyPositionUnencrypted(pos, counter);
170 : }
171 0 : default: {
172 0 : VerifyOrDie(false);
173 : return CHIP_ERROR_INTERNAL;
174 : }
175 : }
176 : }
177 :
178 : /**
179 : * @brief
180 : * With the unencrypted counter verified we can trust the packet and adjust
181 : * counter states.
182 : *
183 : * @pre counter has been verified via VerifyUnencrypted
184 : */
185 98 : void CommitUnencrypted(uint32_t counter) { CommitWithRollover(counter); }
186 :
187 1515 : void SetCounter(uint32_t value)
188 : {
189 1515 : Reset();
190 1515 : mStatus = Status::Synced;
191 1515 : new (&mSynced) Synced();
192 1515 : mSynced.mMaxCounter = value;
193 1515 : mSynced.mWindow.reset();
194 1515 : }
195 :
196 : uint32_t GetCounter() const { return mSynced.mMaxCounter; }
197 :
198 : private:
199 : // Counter position indicator with respect to our current
200 : // mSynced.mMaxCounter.
201 : enum class Position
202 : {
203 : BeforeWindow,
204 : InWindow,
205 : MaxCounter,
206 : FutureCounter,
207 : };
208 :
209 : // Classify an incoming counter value's position. Must be used only if
210 : // mStatus is Status::Synced.
211 19924 : Position ClassifyWithoutRollover(uint32_t counter) const
212 : {
213 19924 : if (counter > mSynced.mMaxCounter)
214 : {
215 19914 : return Position::FutureCounter;
216 : }
217 :
218 10 : return ClassifyNonFutureCounter(counter);
219 : }
220 :
221 : /**
222 : * Classify an incoming counter value's position for the cases when counters
223 : * are allowed to roll over. Must be used only if mStatus is
224 : * Status::Synced.
225 : *
226 : * This can be used as the basis for implementing section 4.5.4.2 in the
227 : * spec:
228 : *
229 : * For encrypted messages of Group Session Type, any arriving message with a counter in the range
230 : * [(max_message_counter + 1) to (max_message_counter + 2^31 - 1)] (modulo 2^32) SHALL be considered
231 : * new, and cause the max_message_counter value to be updated. Messages with counters from
232 : * [(max_message_counter - 2^31) to (max_message_counter - MSG_COUNTER_WINDOW_SIZE - 1)] (modulo 2^
233 : * 32) SHALL be considered duplicate. Message counters within the range of the bitmap SHALL be
234 : * considered duplicate if the corresponding bit offset is set to true.
235 : */
236 159 : Position ClassifyWithRollover(uint32_t counter) const
237 : {
238 159 : uint32_t counterIncrease = counter - mSynced.mMaxCounter;
239 159 : constexpr uint32_t futureCounterWindow = (static_cast<uint32_t>(1 << 31)) - 1;
240 :
241 159 : if (counterIncrease >= 1 && counterIncrease <= futureCounterWindow)
242 : {
243 114 : return Position::FutureCounter;
244 : }
245 :
246 45 : return ClassifyNonFutureCounter(counter);
247 : }
248 :
249 : /**
250 : * Classify a counter that's known to not be future counter. This works
251 : * identically whether we are doing rollover or not.
252 : */
253 55 : Position ClassifyNonFutureCounter(uint32_t counter) const
254 : {
255 55 : if (counter == mSynced.mMaxCounter)
256 : {
257 50 : return Position::MaxCounter;
258 : }
259 :
260 5 : uint32_t offset = mSynced.mMaxCounter - counter;
261 5 : if (offset <= CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
262 : {
263 3 : return Position::InWindow;
264 : }
265 :
266 2 : return Position::BeforeWindow;
267 : }
268 :
269 : /**
270 : * Given an encrypted (group or unicast) counter position and the counter
271 : * value, verify whether we should accept it.
272 : */
273 9969 : CHIP_ERROR VerifyPositionEncrypted(Position position, uint32_t counter) const
274 : {
275 9969 : switch (position)
276 : {
277 9959 : case Position::FutureCounter:
278 9959 : return CHIP_NO_ERROR;
279 2 : case Position::InWindow: {
280 2 : uint32_t offset = mSynced.mMaxCounter - counter;
281 2 : if (mSynced.mWindow.test(offset - 1))
282 : {
283 1 : return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
284 : }
285 1 : return CHIP_NO_ERROR;
286 : }
287 8 : default: {
288 : // Equal to max counter, or before window.
289 8 : return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
290 : }
291 : }
292 : }
293 :
294 : /**
295 : * Given an unencrypted counter position and value, verify whether we should
296 : * accept it.
297 : */
298 55 : CHIP_ERROR VerifyPositionUnencrypted(Position position, uint32_t counter) const
299 : {
300 55 : switch (position)
301 : {
302 0 : case Position::MaxCounter:
303 0 : return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
304 0 : case Position::InWindow: {
305 0 : uint32_t offset = mSynced.mMaxCounter - counter;
306 0 : if (mSynced.mWindow.test(offset - 1))
307 : {
308 0 : return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
309 : }
310 0 : return CHIP_NO_ERROR;
311 : }
312 55 : default: {
313 : // Future counter or before window; all of these are accepted. The
314 : // before-window case is accepted because the peer may have reset
315 : // and is using a new randomized initial value.
316 55 : return CHIP_NO_ERROR;
317 : }
318 : }
319 : }
320 :
321 101 : void CommitWithRollover(uint32_t counter)
322 : {
323 101 : Position pos = ClassifyWithRollover(counter);
324 101 : CommitWithPosition(pos, counter);
325 101 : }
326 :
327 9958 : void CommitWithoutRollover(uint32_t counter)
328 : {
329 9958 : Position pos = ClassifyWithoutRollover(counter);
330 9958 : CommitWithPosition(pos, counter);
331 9958 : }
332 :
333 : /**
334 : * Commit a counter value that is known to be at the given position with
335 : * respect to our max counter.
336 : */
337 10059 : void CommitWithPosition(Position position, uint32_t counter)
338 : {
339 10059 : switch (position)
340 : {
341 1 : case Position::InWindow: {
342 1 : uint32_t offset = mSynced.mMaxCounter - counter;
343 1 : mSynced.mWindow.set(offset - 1);
344 1 : break;
345 : }
346 44 : case Position::MaxCounter: {
347 : // Nothing to do
348 44 : break;
349 : }
350 10014 : default: {
351 : // Since we are committing, this becomes a new max-counter value.
352 10014 : uint32_t shift = counter - mSynced.mMaxCounter;
353 10014 : mSynced.mMaxCounter = counter;
354 10014 : if (shift > CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
355 : {
356 448 : mSynced.mWindow.reset();
357 : }
358 : else
359 : {
360 9566 : mSynced.mWindow <<= shift;
361 9566 : mSynced.mWindow.set(shift - 1);
362 : }
363 10014 : break;
364 : }
365 : }
366 10059 : }
367 :
368 : enum class Status
369 : {
370 : NotSynced, // No state associated
371 : SyncInProcess, // mSyncInProcess will be active
372 : Synced, // mSynced will be active
373 : } mStatus;
374 :
375 : struct SyncInProcess
376 : {
377 : std::array<uint8_t, kChallengeSize> mChallenge;
378 : };
379 :
380 : struct Synced
381 : {
382 : /*
383 : * Past <-- --> Future
384 : * MaxCounter - 1
385 : * |
386 : * v
387 : * | <-- mWindow -->|
388 : * |[n]| ... |[0]|
389 : */
390 : uint32_t mMaxCounter = 0; // The most recent counter we have seen
391 : std::bitset<CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE> mWindow;
392 : };
393 :
394 : // We should use std::variant here when migrated to C++17
395 : union
396 : {
397 : SyncInProcess mSyncInProcess;
398 : Synced mSynced;
399 : };
400 : };
401 :
402 : } // namespace Transport
403 : } // namespace chip
|