From e2e7a78af50ee0a0b3e68143b8c4084ef944483d Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Wed, 6 May 2026 14:51:38 -0700 Subject: [PATCH] [mac] enforce KEK validation for Key ID Mode 0 frames (#13056) This commit adds validation to ensure that Key ID Mode 0 (implied KEK) secured frames are only accepted if a KEK is configured. If KEK is not configured, the frame is rejected. Specifically: - Added `mIsKekSet` boolean member variable to `KeyManager` to track KEK status. - Implemented `KeyManager::IsKekSet()` to check if a KEK is configured. - Enforced a guard in `Mac::ProcessReceiveSecurity()` under `kKeyIdMode0` to immediately reject incoming frames with `kErrorSecurity` when the KEK is not configured. - Added unit test `TestKeyManagerKek()` in `test_pskc.cpp` to verify that `IsKekSet()` transitions from `false` to `true` as expected. --- src/core/mac/mac.cpp | 1 + src/core/thread/key_manager.cpp | 3 +++ src/core/thread/key_manager.hpp | 9 +++++++++ tests/unit/test_pskc.cpp | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index a67e4fc2c..3c5accc2b 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1536,6 +1536,7 @@ Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neig switch (keyIdMode) { case Frame::kKeyIdMode0: + VerifyOrExit(keyManager.IsKekSet(), error = kErrorSecurity); macKey = &keyManager.GetKek(); extAddress = &aSrcAddr.GetExtended(); break; diff --git a/src/core/thread/key_manager.cpp b/src/core/thread/key_manager.cpp index d3f5e2ce7..e7b70dedf 100644 --- a/src/core/thread/key_manager.cpp +++ b/src/core/thread/key_manager.cpp @@ -175,6 +175,7 @@ KeyManager::KeyManager(Instance &aInstance) , mKeyRotationTimer(aInstance) , mKekFrameCounter(0) , mIsPskcSet(false) + , mIsKekSet(false) { otPlatCryptoInit(); @@ -512,6 +513,7 @@ void KeyManager::SetKek(const Kek &aKek) { mKek.SetFrom(aKek, /* aIsExportable */ true); mKekFrameCounter = 0; + mIsKekSet = true; } void KeyManager::SetSecurityPolicy(const SecurityPolicy &aSecurityPolicy) @@ -695,6 +697,7 @@ void KeyManager::DestroyTemporaryKeys(void) { mMleKey.Clear(); mKek.Clear(); + mIsKekSet = false; Get().ClearMacKeys(); } diff --git a/src/core/thread/key_manager.hpp b/src/core/thread/key_manager.hpp index 4bb3bd987..f3b4fe396 100644 --- a/src/core/thread/key_manager.hpp +++ b/src/core/thread/key_manager.hpp @@ -456,6 +456,14 @@ public: */ const KekKeyMaterial &GetKek(void) const { return mKek; } + /** + * Indicates whether or not the KEK is set. + * + * @retval TRUE If the KEK is set. + * @retval FALSE If the KEK is not set. + */ + bool IsKekSet(void) const { return mIsKekSet; } + /** * Retrieves the KEK as literal `Kek` key. * @@ -646,6 +654,7 @@ private: SecurityPolicy mSecurityPolicy; bool mIsPskcSet : 1; + bool mIsKekSet : 1; }; /** diff --git a/tests/unit/test_pskc.cpp b/tests/unit/test_pskc.cpp index b1a3877ff..c486b77df 100644 --- a/tests/unit/test_pskc.cpp +++ b/tests/unit/test_pskc.cpp @@ -29,6 +29,7 @@ #include "meshcop/commissioner.hpp" #include "meshcop/meshcop.hpp" +#include "thread/key_manager.hpp" #include "test_platform.h" #include "test_util.h" @@ -108,6 +109,22 @@ void TestExampleInSpec(void) testFreeInstance(instance); } +void TestKeyManagerKek(void) +{ + Instance *instance = testInitInstance(); + KeyManager &keyManager = instance->Get(); + + VerifyOrQuit(!keyManager.IsKekSet()); + + Kek kek; + memset(kek.m8, 0xaa, sizeof(kek.m8)); + keyManager.SetKek(kek); + + VerifyOrQuit(keyManager.IsKekSet()); + + testFreeInstance(instance); +} + } // namespace MeshCoP } // namespace ot @@ -119,6 +136,7 @@ int main(void) ot::MeshCoP::TestMinimumPassphrase(); ot::MeshCoP::TestMaximumPassphrase(); ot::MeshCoP::TestExampleInSpec(); + ot::MeshCoP::TestKeyManagerKek(); printf("All tests passed\n"); #else printf("PSKc generation is not supported on non-ftd build\n");