[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:
Jintao Lin
2020-08-21 13:19:48 +08:00
committed by GitHub
parent 53245bd06c
commit 9eb960e3e2
4 changed files with 165 additions and 24 deletions
+101 -24
View File
@@ -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;
+3
View File
@@ -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);
+48
View File
@@ -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)
+13
View File
@@ -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.