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 151214 : PeerMessageCounter() : mStatus(Status::NotSynced) {} 39 151214 : ~PeerMessageCounter() { Reset(); } 40 : 41 18442 : void Reset() 42 : { 43 18442 : switch (mStatus) 44 : { 45 17323 : case Status::NotSynced: 46 17323 : break; 47 0 : case Status::SyncInProcess: 48 0 : mSyncInProcess.~SyncInProcess(); 49 0 : break; 50 1119 : case Status::Synced: 51 1119 : mSynced.~Synced(); 52 1119 : break; 53 : } 54 18442 : mStatus = Status::NotSynced; 55 18442 : } 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 9391 : CHIP_ERROR VerifyEncryptedUnicast(uint32_t counter) const 139 : { 140 9391 : if (mStatus != Status::Synced) 141 : { 142 0 : return CHIP_ERROR_INCORRECT_STATE; 143 : } 144 : 145 9391 : Position pos = ClassifyWithoutRollover(counter); 146 9391 : 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 9383 : void CommitEncryptedUnicast(uint32_t counter) { CommitWithoutRollover(counter); } 157 : 158 96 : CHIP_ERROR VerifyUnencrypted(uint32_t counter) 159 : { 160 96 : switch (mStatus) 161 : { 162 41 : case Status::NotSynced: { 163 : // Trust and set the counter when not synced 164 41 : SetCounter(counter); 165 41 : 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 96 : void CommitUnencrypted(uint32_t counter) { CommitWithRollover(counter); } 186 : 187 1337 : void SetCounter(uint32_t value) 188 : { 189 1337 : Reset(); 190 1337 : mStatus = Status::Synced; 191 1337 : new (&mSynced) Synced(); 192 1337 : mSynced.mMaxCounter = value; 193 1337 : mSynced.mWindow.reset(); 194 1337 : } 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 18774 : Position ClassifyWithoutRollover(uint32_t counter) const 212 : { 213 18774 : if (counter > mSynced.mMaxCounter) 214 : { 215 18764 : 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 157 : Position ClassifyWithRollover(uint32_t counter) const 237 : { 238 157 : uint32_t counterIncrease = counter - mSynced.mMaxCounter; 239 157 : constexpr uint32_t futureCounterWindow = (static_cast<uint32_t>(1 << 31)) - 1; 240 : 241 157 : if (counterIncrease >= 1 && counterIncrease <= futureCounterWindow) 242 : { 243 114 : return Position::FutureCounter; 244 : } 245 : 246 43 : 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 53 : Position ClassifyNonFutureCounter(uint32_t counter) const 254 : { 255 53 : if (counter == mSynced.mMaxCounter) 256 : { 257 48 : 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 9394 : CHIP_ERROR VerifyPositionEncrypted(Position position, uint32_t counter) const 274 : { 275 9394 : switch (position) 276 : { 277 9384 : case Position::FutureCounter: 278 9384 : 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 99 : void CommitWithRollover(uint32_t counter) 322 : { 323 99 : Position pos = ClassifyWithRollover(counter); 324 99 : CommitWithPosition(pos, counter); 325 99 : } 326 : 327 9383 : void CommitWithoutRollover(uint32_t counter) 328 : { 329 9383 : Position pos = ClassifyWithoutRollover(counter); 330 9383 : CommitWithPosition(pos, counter); 331 9383 : } 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 9482 : void CommitWithPosition(Position position, uint32_t counter) 338 : { 339 9482 : 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 42 : case Position::MaxCounter: { 347 : // Nothing to do 348 42 : break; 349 : } 350 9439 : default: { 351 : // Since we are committing, this becomes a new max-counter value. 352 9439 : uint32_t shift = counter - mSynced.mMaxCounter; 353 9439 : mSynced.mMaxCounter = counter; 354 9439 : if (shift > CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE) 355 : { 356 424 : mSynced.mWindow.reset(); 357 : } 358 : else 359 : { 360 9015 : mSynced.mWindow <<= shift; 361 9015 : mSynced.mWindow.set(shift - 1); 362 : } 363 9439 : break; 364 : } 365 : } 366 9482 : } 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