blob: 7563972ed66cd94dc2e2fe87ebd6f4c0c5eb07e7 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements Thread security material generation.
*/
#include "key_manager.hpp"
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/timer.hpp"
#include "crypto/hkdf_sha256.hpp"
#include "crypto/storage.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
namespace ot {
RegisterLogModule("KeyManager");
const uint8_t KeyManager::kThreadString[] = {
'T', 'h', 'r', 'e', 'a', 'd',
};
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
const uint8_t KeyManager::kHkdfExtractSaltString[] = {'T', 'h', 'r', 'e', 'a', 'd', 'S', 'e', 'q', 'u', 'e', 'n',
'c', 'e', 'M', 'a', 's', 't', 'e', 'r', 'K', 'e', 'y'};
const uint8_t KeyManager::kTrelInfoString[] = {'T', 'h', 'r', 'e', 'a', 'd', 'O', 'v', 'e',
'r', 'I', 'n', 'f', 'r', 'a', 'K', 'e', 'y'};
#endif
void SecurityPolicy::SetToDefault(void)
{
mRotationTime = kDefaultKeyRotationTime;
SetToDefaultFlags();
}
void SecurityPolicy::SetToDefaultFlags(void)
{
mObtainNetworkKeyEnabled = true;
mNativeCommissioningEnabled = true;
mRoutersEnabled = true;
mExternalCommissioningEnabled = true;
mCommercialCommissioningEnabled = false;
mAutonomousEnrollmentEnabled = false;
mNetworkKeyProvisioningEnabled = false;
mTobleLinkEnabled = true;
mNonCcmRoutersEnabled = false;
mVersionThresholdForRouting = 0;
}
void SecurityPolicy::SetFlags(const uint8_t *aFlags, uint8_t aFlagsLength)
{
OT_ASSERT(aFlagsLength > 0);
SetToDefaultFlags();
mObtainNetworkKeyEnabled = aFlags[0] & kObtainNetworkKeyMask;
mNativeCommissioningEnabled = aFlags[0] & kNativeCommissioningMask;
mRoutersEnabled = aFlags[0] & kRoutersMask;
mExternalCommissioningEnabled = aFlags[0] & kExternalCommissioningMask;
mCommercialCommissioningEnabled = (aFlags[0] & kCommercialCommissioningMask) == 0;
mAutonomousEnrollmentEnabled = (aFlags[0] & kAutonomousEnrollmentMask) == 0;
mNetworkKeyProvisioningEnabled = (aFlags[0] & kNetworkKeyProvisioningMask) == 0;
VerifyOrExit(aFlagsLength > sizeof(aFlags[0]));
mTobleLinkEnabled = aFlags[1] & kTobleLinkMask;
mNonCcmRoutersEnabled = (aFlags[1] & kNonCcmRoutersMask) == 0;
mVersionThresholdForRouting = aFlags[1] & kVersionThresholdForRoutingMask;
exit:
return;
}
void SecurityPolicy::GetFlags(uint8_t *aFlags, uint8_t aFlagsLength) const
{
OT_ASSERT(aFlagsLength > 0);
memset(aFlags, 0, aFlagsLength);
if (mObtainNetworkKeyEnabled)
{
aFlags[0] |= kObtainNetworkKeyMask;
}
if (mNativeCommissioningEnabled)
{
aFlags[0] |= kNativeCommissioningMask;
}
if (mRoutersEnabled)
{
aFlags[0] |= kRoutersMask;
}
if (mExternalCommissioningEnabled)
{
aFlags[0] |= kExternalCommissioningMask;
}
if (!mCommercialCommissioningEnabled)
{
aFlags[0] |= kCommercialCommissioningMask;
}
if (!mAutonomousEnrollmentEnabled)
{
aFlags[0] |= kAutonomousEnrollmentMask;
}
if (!mNetworkKeyProvisioningEnabled)
{
aFlags[0] |= kNetworkKeyProvisioningMask;
}
VerifyOrExit(aFlagsLength > sizeof(aFlags[0]));
if (mTobleLinkEnabled)
{
aFlags[1] |= kTobleLinkMask;
}
if (!mNonCcmRoutersEnabled)
{
aFlags[1] |= kNonCcmRoutersMask;
}
aFlags[1] |= kReservedMask;
aFlags[1] |= mVersionThresholdForRouting;
exit:
return;
}
KeyManager::KeyManager(Instance &aInstance)
: InstanceLocator(aInstance)
, mKeySequence(0)
, mMleFrameCounter(0)
, mStoredMacFrameCounter(0)
, mStoredMleFrameCounter(0)
, mHoursSinceKeyRotation(0)
, mKeySwitchGuardTime(kDefaultKeySwitchGuardTime)
, mKeySwitchGuardEnabled(false)
, mKeyRotationTimer(aInstance, KeyManager::HandleKeyRotationTimer)
, mKekFrameCounter(0)
, mIsPskcSet(false)
{
otPlatCryptoInit();
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
{
NetworkKey networkKey;
mNetworkKeyRef = Crypto::Storage::kInvalidKeyRef;
mPskcRef = Crypto::Storage::kInvalidKeyRef;
IgnoreError(networkKey.GenerateRandom());
StoreNetworkKey(networkKey, /* aOverWriteExisting */ false);
}
#else
IgnoreError(mNetworkKey.GenerateRandom());
mPskc.Clear();
#endif
mMacFrameCounters.Reset();
}
void KeyManager::Start(void)
{
mKeySwitchGuardEnabled = false;
StartKeyRotationTimer();
}
void KeyManager::Stop(void)
{
mKeyRotationTimer.Stop();
}
void KeyManager::SetPskc(const Pskc &aPskc)
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
if (Crypto::Storage::IsKeyRefValid(mPskcRef))
{
Pskc pskc;
GetPskc(pskc);
VerifyOrExit(aPskc != pskc, Get<Notifier>().SignalIfFirst(kEventPskcChanged));
}
StorePskc(aPskc);
Get<Notifier>().Signal(kEventPskcChanged);
#else
SuccessOrExit(Get<Notifier>().Update(mPskc, aPskc, kEventPskcChanged));
#endif
exit:
mIsPskcSet = true;
}
void KeyManager::ResetFrameCounters(void)
{
Router *parent;
// reset parent frame counters
parent = &Get<Mle::MleRouter>().GetParent();
parent->SetKeySequence(0);
parent->GetLinkFrameCounters().Reset();
parent->SetLinkAckFrameCounter(0);
parent->SetMleFrameCounter(0);
#if OPENTHREAD_FTD
// reset router frame counters
for (Router &router : Get<RouterTable>().Iterate())
{
router.SetKeySequence(0);
router.GetLinkFrameCounters().Reset();
router.SetLinkAckFrameCounter(0);
router.SetMleFrameCounter(0);
}
// reset child frame counters
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
{
child.SetKeySequence(0);
child.GetLinkFrameCounters().Reset();
child.SetLinkAckFrameCounter(0);
child.SetMleFrameCounter(0);
}
#endif
}
void KeyManager::SetNetworkKey(const NetworkKey &aNetworkKey)
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
if (Crypto::Storage::IsKeyRefValid(mNetworkKeyRef))
{
NetworkKey networkKey;
GetNetworkKey(networkKey);
VerifyOrExit(networkKey != aNetworkKey, Get<Notifier>().SignalIfFirst(kEventNetworkKeyChanged));
}
StoreNetworkKey(aNetworkKey, /* aOverWriteExisting */ true);
Get<Notifier>().Signal(kEventNetworkKeyChanged);
#else
SuccessOrExit(Get<Notifier>().Update(mNetworkKey, aNetworkKey, kEventNetworkKeyChanged));
#endif
Get<Notifier>().Signal(kEventThreadKeySeqCounterChanged);
mKeySequence = 0;
UpdateKeyMaterial();
ResetFrameCounters();
exit:
return;
}
void KeyManager::ComputeKeys(uint32_t aKeySequence, HashKeys &aHashKeys)
{
Crypto::HmacSha256 hmac;
uint8_t keySequenceBytes[sizeof(uint32_t)];
Crypto::Key cryptoKey;
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
cryptoKey.SetAsKeyRef(mNetworkKeyRef);
#else
cryptoKey.Set(mNetworkKey.m8, NetworkKey::kSize);
#endif
hmac.Start(cryptoKey);
Encoding::BigEndian::WriteUint32(aKeySequence, keySequenceBytes);
hmac.Update(keySequenceBytes);
hmac.Update(kThreadString);
hmac.Finish(aHashKeys.mHash);
}
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void KeyManager::ComputeTrelKey(uint32_t aKeySequence, Mac::Key &aKey)
{
Crypto::HkdfSha256 hkdf;
uint8_t salt[sizeof(uint32_t) + sizeof(kHkdfExtractSaltString)];
Crypto::Key cryptoKey;
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
cryptoKey.SetAsKeyRef(mNetworkKeyRef);
#else
cryptoKey.Set(mNetworkKey.m8, NetworkKey::kSize);
#endif
Encoding::BigEndian::WriteUint32(aKeySequence, salt);
memcpy(salt + sizeof(uint32_t), kHkdfExtractSaltString, sizeof(kHkdfExtractSaltString));
hkdf.Extract(salt, sizeof(salt), cryptoKey);
hkdf.Expand(kTrelInfoString, sizeof(kTrelInfoString), aKey.m8, Mac::Key::kSize);
}
#endif
void KeyManager::UpdateKeyMaterial(void)
{
HashKeys hashKeys;
ComputeKeys(mKeySequence, hashKeys);
mMleKey.SetFrom(hashKeys.GetMleKey());
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
{
Mac::KeyMaterial curKey;
Mac::KeyMaterial prevKey;
Mac::KeyMaterial nextKey;
curKey.SetFrom(hashKeys.GetMacKey(), kExportableMacKeys);
ComputeKeys(mKeySequence - 1, hashKeys);
prevKey.SetFrom(hashKeys.GetMacKey(), kExportableMacKeys);
ComputeKeys(mKeySequence + 1, hashKeys);
nextKey.SetFrom(hashKeys.GetMacKey(), kExportableMacKeys);
Get<Mac::SubMac>().SetMacKey(Mac::Frame::kKeyIdMode1, (mKeySequence & 0x7f) + 1, prevKey, curKey, nextKey);
}
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
{
Mac::Key key;
ComputeTrelKey(mKeySequence, key);
mTrelKey.SetFrom(key);
}
#endif
}
void KeyManager::SetCurrentKeySequence(uint32_t aKeySequence)
{
VerifyOrExit(aKeySequence != mKeySequence, Get<Notifier>().SignalIfFirst(kEventThreadKeySeqCounterChanged));
if ((aKeySequence == (mKeySequence + 1)) && mKeyRotationTimer.IsRunning())
{
if (mKeySwitchGuardEnabled)
{
// Check if the guard timer has expired if key rotation is requested.
VerifyOrExit(mHoursSinceKeyRotation >= mKeySwitchGuardTime);
StartKeyRotationTimer();
}
mKeySwitchGuardEnabled = true;
}
mKeySequence = aKeySequence;
UpdateKeyMaterial();
SetAllMacFrameCounters(0);
mMleFrameCounter = 0;
Get<Notifier>().Signal(kEventThreadKeySeqCounterChanged);
exit:
return;
}
const Mle::KeyMaterial &KeyManager::GetTemporaryMleKey(uint32_t aKeySequence)
{
HashKeys hashKeys;
ComputeKeys(aKeySequence, hashKeys);
mTemporaryMleKey.SetFrom(hashKeys.GetMleKey());
return mTemporaryMleKey;
}
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
const Mac::KeyMaterial &KeyManager::GetTemporaryTrelMacKey(uint32_t aKeySequence)
{
Mac::Key key;
ComputeTrelKey(aKeySequence, key);
mTemporaryTrelKey.SetFrom(key);
return mTemporaryTrelKey;
}
#endif
void KeyManager::SetAllMacFrameCounters(uint32_t aMacFrameCounter)
{
mMacFrameCounters.SetAll(aMacFrameCounter);
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
Get<Mac::SubMac>().SetFrameCounter(aMacFrameCounter);
#endif
}
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
void KeyManager::MacFrameCounterUsed(uint32_t aMacFrameCounter)
{
// This is callback from `SubMac` to indicate that a frame
// counter value is used for tx. We ensure to handle it
// even if it is called out of order.
VerifyOrExit(mMacFrameCounters.Get154() <= aMacFrameCounter);
mMacFrameCounters.Set154(aMacFrameCounter + 1);
if (mMacFrameCounters.Get154() >= mStoredMacFrameCounter)
{
IgnoreError(Get<Mle::MleRouter>().Store());
}
exit:
return;
}
#else
void KeyManager::MacFrameCounterUsed(uint32_t)
{
}
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void KeyManager::IncrementTrelMacFrameCounter(void)
{
mMacFrameCounters.IncrementTrel();
if (mMacFrameCounters.GetTrel() >= mStoredMacFrameCounter)
{
IgnoreError(Get<Mle::MleRouter>().Store());
}
}
#endif
void KeyManager::IncrementMleFrameCounter(void)
{
mMleFrameCounter++;
if (mMleFrameCounter >= mStoredMleFrameCounter)
{
IgnoreError(Get<Mle::MleRouter>().Store());
}
}
void KeyManager::SetKek(const Kek &aKek)
{
mKek.SetFrom(aKek, /* aIsExportable */ true);
mKekFrameCounter = 0;
}
void KeyManager::SetSecurityPolicy(const SecurityPolicy &aSecurityPolicy)
{
if (aSecurityPolicy.mRotationTime < SecurityPolicy::kMinKeyRotationTime)
{
LogNote("Key Rotation Time too small: %d", aSecurityPolicy.mRotationTime);
ExitNow();
}
IgnoreError(Get<Notifier>().Update(mSecurityPolicy, aSecurityPolicy, kEventSecurityPolicyChanged));
exit:
return;
}
void KeyManager::StartKeyRotationTimer(void)
{
mHoursSinceKeyRotation = 0;
mKeyRotationTimer.Start(kOneHourIntervalInMsec);
}
void KeyManager::HandleKeyRotationTimer(Timer &aTimer)
{
aTimer.Get<KeyManager>().HandleKeyRotationTimer();
}
void KeyManager::HandleKeyRotationTimer(void)
{
mHoursSinceKeyRotation++;
// Order of operations below is important. We should restart the timer (from
// last fire time for one hour interval) before potentially calling
// `SetCurrentKeySequence()`. `SetCurrentKeySequence()` uses the fact that
// timer is running to decide to check for the guard time and to reset the
// rotation timer (and the `mHoursSinceKeyRotation`) if it updates the key
// sequence.
mKeyRotationTimer.StartAt(mKeyRotationTimer.GetFireTime(), kOneHourIntervalInMsec);
if (mHoursSinceKeyRotation >= mSecurityPolicy.mRotationTime)
{
SetCurrentKeySequence(mKeySequence + 1);
}
}
void KeyManager::GetNetworkKey(NetworkKey &aNetworkKey) const
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
if (Crypto::Storage::IsKeyRefValid(mNetworkKeyRef))
{
size_t keyLen;
SuccessOrAssert(Crypto::Storage::ExportKey(mNetworkKeyRef, aNetworkKey.m8, NetworkKey::kSize, keyLen));
OT_ASSERT(keyLen == NetworkKey::kSize);
}
else
{
aNetworkKey.Clear();
}
#else
aNetworkKey = mNetworkKey;
#endif
}
void KeyManager::GetPskc(Pskc &aPskc) const
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
if (Crypto::Storage::IsKeyRefValid(mPskcRef))
{
size_t keyLen;
SuccessOrAssert(Crypto::Storage::ExportKey(mPskcRef, aPskc.m8, Pskc::kSize, keyLen));
OT_ASSERT(keyLen == Pskc::kSize);
}
else
{
aPskc.Clear();
}
#else
aPskc = mPskc;
#endif
}
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
void KeyManager::StoreNetworkKey(const NetworkKey &aNetworkKey, bool aOverWriteExisting)
{
NetworkKeyRef keyRef;
keyRef = Crypto::Storage::kNetworkKeyRef;
if (!aOverWriteExisting)
{
// Check if there is already a network key stored in ITS. If
// stored, and we are not overwriting the existing key,
// return without doing anything.
if (Crypto::Storage::HasKey(keyRef))
{
ExitNow();
}
}
Crypto::Storage::DestroyKey(keyRef);
SuccessOrAssert(Crypto::Storage::ImportKey(keyRef, Crypto::Storage::kKeyTypeHmac,
Crypto::Storage::kKeyAlgorithmHmacSha256,
Crypto::Storage::kUsageSignHash | Crypto::Storage::kUsageExport,
Crypto::Storage::kTypePersistent, aNetworkKey.m8, NetworkKey::kSize));
exit:
if (mNetworkKeyRef != keyRef)
{
Crypto::Storage::DestroyKey(mNetworkKeyRef);
}
mNetworkKeyRef = keyRef;
}
void KeyManager::StorePskc(const Pskc &aPskc)
{
PskcRef keyRef = Crypto::Storage::kPskcRef;
Crypto::Storage::DestroyKey(keyRef);
SuccessOrAssert(Crypto::Storage::ImportKey(keyRef, Crypto::Storage::kKeyTypeRaw,
Crypto::Storage::kKeyAlgorithmVendor, Crypto::Storage::kUsageExport,
Crypto::Storage::kTypePersistent, aPskc.m8, Pskc::kSize));
if (mPskcRef != keyRef)
{
Crypto::Storage::DestroyKey(mPskcRef);
}
mPskcRef = keyRef;
}
void KeyManager::SetPskcRef(PskcRef aKeyRef)
{
VerifyOrExit(mPskcRef != aKeyRef, Get<Notifier>().SignalIfFirst(kEventPskcChanged));
Crypto::Storage::DestroyKey(mPskcRef);
mPskcRef = aKeyRef;
Get<Notifier>().Signal(kEventPskcChanged);
exit:
mIsPskcSet = true;
}
void KeyManager::SetNetworkKeyRef(otNetworkKeyRef aKeyRef)
{
VerifyOrExit(mNetworkKeyRef != aKeyRef, Get<Notifier>().SignalIfFirst(kEventNetworkKeyChanged));
Crypto::Storage::DestroyKey(mNetworkKeyRef);
mNetworkKeyRef = aKeyRef;
Get<Notifier>().Signal(kEventNetworkKeyChanged);
Get<Notifier>().Signal(kEventThreadKeySeqCounterChanged);
mKeySequence = 0;
UpdateKeyMaterial();
ResetFrameCounters();
exit:
return;
}
#endif // OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
} // namespace ot