mirror of
https://github.com/espressif/openthread.git
synced 2026-07-22 03:54:07 +00:00
[security] verify received Enh-ACK integrity (#5393)
With Thread 1.2, it is possible to receive an Enh-ACK with security enabled. This commit: - Move receive AES CCM related code to Frame class. - Verify received Enh-ACK in Mac layer. If MIC check failed, the transmitted frame will be marked as OT_ERROR_NO_ACK.
This commit is contained in:
+101
-24
@@ -1369,6 +1369,15 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError
|
||||
|
||||
mBroadcastTransmitCount = 0;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
|
||||
// Verify Enh-ACK integrity by checking its MIC
|
||||
if ((aError == OT_ERROR_NONE) && (aAckFrame != nullptr) &&
|
||||
(ProcessEnhAckSecurity(aFrame, *aAckFrame) != OT_ERROR_NONE))
|
||||
{
|
||||
aError = OT_ERROR_NO_ACK;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Determine next action based on current operation.
|
||||
@@ -1537,14 +1546,10 @@ otError Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Ne
|
||||
uint8_t securityLevel;
|
||||
uint8_t keyIdMode;
|
||||
uint32_t frameCounter;
|
||||
uint8_t nonce[Crypto::AesCcm::kNonceSize];
|
||||
uint8_t tag[Frame::kMaxMicSize];
|
||||
uint8_t tagLength;
|
||||
uint8_t keyid;
|
||||
uint32_t keySequence = 0;
|
||||
const Key * macKey;
|
||||
const ExtAddress *extAddress;
|
||||
Crypto::AesCcm aesCcm;
|
||||
|
||||
VerifyOrExit(aFrame.GetSecurityEnabled(), error = OT_ERROR_NONE);
|
||||
|
||||
@@ -1621,26 +1626,7 @@ otError Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Ne
|
||||
OT_UNREACHABLE_CODE(break);
|
||||
}
|
||||
|
||||
Crypto::AesCcm::GenerateNonce(*extAddress, frameCounter, securityLevel, nonce);
|
||||
tagLength = aFrame.GetFooterLength() - Frame::kFcsSize;
|
||||
|
||||
aesCcm.SetKey(*macKey);
|
||||
|
||||
aesCcm.Init(aFrame.GetHeaderLength(), aFrame.GetPayloadLength(), tagLength, nonce, sizeof(nonce));
|
||||
aesCcm.Header(aFrame.GetHeader(), aFrame.GetHeaderLength());
|
||||
|
||||
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
aesCcm.Payload(aFrame.GetPayload(), aFrame.GetPayload(), aFrame.GetPayloadLength(), Crypto::AesCcm::kDecrypt);
|
||||
#else
|
||||
// For fuzz tests, execute AES but do not alter the payload
|
||||
uint8_t fuzz[OT_RADIO_FRAME_MAX_SIZE];
|
||||
aesCcm.Payload(fuzz, aFrame.GetPayload(), aFrame.GetPayloadLength(), Crypto::AesCcm::kDecrypt);
|
||||
#endif
|
||||
aesCcm.Finalize(tag);
|
||||
|
||||
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
VerifyOrExit(memcmp(tag, aFrame.GetFooter(), tagLength) == 0, OT_NOOP);
|
||||
#endif
|
||||
SuccessOrExit(aFrame.ProcessReceiveAesCcm(*extAddress, *macKey));
|
||||
|
||||
if ((keyIdMode == Frame::kKeyIdMode1) && aNeighbor->IsStateValid())
|
||||
{
|
||||
@@ -1664,6 +1650,97 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
|
||||
otError Mac::ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame)
|
||||
{
|
||||
otError error = OT_ERROR_SECURITY;
|
||||
uint8_t securityLevel;
|
||||
uint8_t txKeyId;
|
||||
uint8_t ackKeyId;
|
||||
uint8_t keyIdMode;
|
||||
Address srcAddr;
|
||||
Neighbor * neighbor;
|
||||
KeyManager &keyManager = Get<KeyManager>();
|
||||
const Key * macKey;
|
||||
|
||||
VerifyOrExit(aAckFrame.GetSecurityEnabled(), error = OT_ERROR_NONE);
|
||||
VerifyOrExit(aAckFrame.IsVersion2015(), OT_NOOP);
|
||||
|
||||
IgnoreError(aAckFrame.GetSecurityLevel(securityLevel));
|
||||
VerifyOrExit(securityLevel == Frame::kSecEncMic32, OT_NOOP);
|
||||
|
||||
IgnoreError(aAckFrame.GetKeyIdMode(keyIdMode));
|
||||
VerifyOrExit(keyIdMode == Frame::kKeyIdMode1, error = OT_ERROR_NONE);
|
||||
|
||||
IgnoreError(aTxFrame.GetKeyId(txKeyId));
|
||||
IgnoreError(aAckFrame.GetKeyId(ackKeyId));
|
||||
|
||||
VerifyOrExit(txKeyId == ackKeyId, OT_NOOP);
|
||||
|
||||
IgnoreError(aAckFrame.GetSrcAddr(srcAddr));
|
||||
|
||||
if (srcAddr.IsShort())
|
||||
{
|
||||
neighbor = Get<NeighborTable>().FindNeighbor(srcAddr);
|
||||
|
||||
if (neighbor != nullptr)
|
||||
{
|
||||
srcAddr.SetExtended(neighbor->GetExtAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if (!srcAddr.IsExtended())
|
||||
{
|
||||
// Get Enh-ACK source address from transmitted frame destination address
|
||||
IgnoreError(aTxFrame.GetDstAddr(srcAddr));
|
||||
|
||||
if (srcAddr.IsShort())
|
||||
{
|
||||
neighbor = Get<NeighborTable>().FindNeighbor(srcAddr);
|
||||
|
||||
if (neighbor != nullptr)
|
||||
{
|
||||
srcAddr.SetExtended(neighbor->GetExtAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrExit(srcAddr.IsExtended(), OT_NOOP);
|
||||
|
||||
ackKeyId--;
|
||||
|
||||
if (ackKeyId == (keyManager.GetCurrentKeySequence() & 0x7f))
|
||||
{
|
||||
macKey = &mSubMac.GetCurrentMacKey();
|
||||
}
|
||||
else if (ackKeyId == ((keyManager.GetCurrentKeySequence() - 1) & 0x7f))
|
||||
{
|
||||
macKey = &mSubMac.GetPreviousMacKey();
|
||||
}
|
||||
else if (ackKeyId == ((keyManager.GetCurrentKeySequence() + 1) & 0x7f))
|
||||
{
|
||||
macKey = &mSubMac.GetNextMacKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (aAckFrame.ProcessReceiveAesCcm(srcAddr.GetExtended(), *macKey) == OT_ERROR_NONE)
|
||||
{
|
||||
error = OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (error != OT_ERROR_NONE)
|
||||
{
|
||||
otLogInfoMac("Frame tx attempt failed, error: Enh-ACK security check fail");
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
|
||||
|
||||
void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError)
|
||||
{
|
||||
Address srcaddr;
|
||||
|
||||
@@ -815,6 +815,9 @@ private:
|
||||
|
||||
otError ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor);
|
||||
void ProcessTransmitSecurity(TxFrame &aFrame);
|
||||
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
|
||||
otError ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame);
|
||||
#endif
|
||||
void UpdateIdleMode(void);
|
||||
void StartOperation(Operation aOperation);
|
||||
void FinishOperation(void);
|
||||
|
||||
@@ -1154,6 +1154,54 @@ exit:
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
|
||||
|
||||
otError RxFrame::ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey)
|
||||
{
|
||||
#if OPENTHREAD_RADIO
|
||||
OT_UNUSED_VARIABLE(aExtAddress);
|
||||
OT_UNUSED_VARIABLE(aMacKey);
|
||||
|
||||
return OT_ERROR_NONE;
|
||||
#else
|
||||
otError error = OT_ERROR_SECURITY;
|
||||
uint32_t frameCounter = 0;
|
||||
uint8_t securityLevel;
|
||||
uint8_t nonce[Crypto::AesCcm::kNonceSize];
|
||||
uint8_t tag[kMaxMicSize];
|
||||
uint8_t tagLength;
|
||||
Crypto::AesCcm aesCcm;
|
||||
|
||||
VerifyOrExit(GetSecurityEnabled(), error = OT_ERROR_NONE);
|
||||
|
||||
SuccessOrExit(GetSecurityLevel(securityLevel));
|
||||
SuccessOrExit(GetFrameCounter(frameCounter));
|
||||
|
||||
Crypto::AesCcm::GenerateNonce(aExtAddress, frameCounter, securityLevel, nonce);
|
||||
|
||||
aesCcm.SetKey(aMacKey);
|
||||
tagLength = GetFooterLength() - Frame::kFcsSize;
|
||||
|
||||
aesCcm.Init(GetHeaderLength(), GetPayloadLength(), tagLength, nonce, sizeof(nonce));
|
||||
aesCcm.Header(GetHeader(), GetHeaderLength());
|
||||
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
aesCcm.Payload(GetPayload(), GetPayload(), GetPayloadLength(), Crypto::AesCcm::kDecrypt);
|
||||
#else
|
||||
// For fuzz tests, execute AES but do not alter the payload
|
||||
uint8_t fuzz[OT_RADIO_FRAME_MAX_SIZE];
|
||||
aesCcm.Payload(fuzz, GetPayload(), GetPayloadLength(), Crypto::AesCcm::kDecrypt);
|
||||
#endif
|
||||
aesCcm.Finalize(tag);
|
||||
|
||||
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
VerifyOrExit(memcmp(tag, GetFooter(), tagLength) == 0, OT_NOOP);
|
||||
#endif
|
||||
|
||||
error = OT_ERROR_NONE;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
#endif // OPENTHREAD_RADIO
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
|
||||
|
||||
@@ -1074,6 +1074,19 @@ public:
|
||||
*/
|
||||
const uint64_t &GetTimestamp(void) const { return mInfo.mRxInfo.mTimestamp; }
|
||||
|
||||
/**
|
||||
* This method performs AES CCM on the frame which is received.
|
||||
*
|
||||
* @param[in] aExtAddress A reference to the extended address, which will be used to generate nonce
|
||||
* for AES CCM computation.
|
||||
* @param[in] aMacKey A reference to the MAC key to decrypt the received frame.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Process of received frame AES CCM succeeded.
|
||||
* @retval OT_ERROR_SECURITY Received frame MIC check failed.
|
||||
*
|
||||
*/
|
||||
otError ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey);
|
||||
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
/**
|
||||
* This method gets the offset to network time.
|
||||
|
||||
Reference in New Issue
Block a user