mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
[tcat] implement rate limitation for TCAT TLVs 0x10, 0x11 and 0x12 and remove TLV 0x14 (#12211)
This commit implements rate limitation for the TCAT commands Present PSKd Hash TLV (0x10), Present PSKc Hash TLV (0x11) and Present Install-code Hash TLV (0x12) to prevent password guessing attacks. It also removes the TCAT command Request PSKd Hash TLV (0x14), to prevent offline password guessing attacks with a single Hash value retrieved from the device. Note: The commit does not remove the Request PSKd Hash TLV implementation in the Python commissioner such that the non-existence of the command TLV can still be tested.
This commit is contained in:
@@ -60,8 +60,10 @@ TcatAgent::TcatAgent(Instance &aInstance)
|
|||||||
, mTimerSetsToActive(false)
|
, mTimerSetsToActive(false)
|
||||||
, mActiveOrStandbyTimer(aInstance)
|
, mActiveOrStandbyTimer(aInstance)
|
||||||
, mTcatActiveDurationMs(0)
|
, mTcatActiveDurationMs(0)
|
||||||
|
, mHashVerificationAttempts(1)
|
||||||
{
|
{
|
||||||
ClearCommissionerState();
|
ClearCommissionerState();
|
||||||
|
mLastHashVerificationTimestamp = Get<UptimeTracker>().GetUptimeInSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TcatAgent::ClearCommissionerState(void)
|
void TcatAgent::ClearCommissionerState(void)
|
||||||
@@ -496,10 +498,6 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg
|
|||||||
error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response);
|
error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kTlvRequestPskdHash:
|
|
||||||
error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case kTlvGetCommissionerCertificate:
|
case kTlvGetCommissionerCertificate:
|
||||||
error = HandleGetCommissionerCertificate(aOutgoingMessage, response);
|
error = HandleGetCommissionerCertificate(aOutgoingMessage, response);
|
||||||
break;
|
break;
|
||||||
@@ -883,33 +881,6 @@ exit:
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error TcatAgent::HandleRequestPskdHash(const Message &aIncomingMessage,
|
|
||||||
Message &aOutgoingMessage,
|
|
||||||
uint16_t aOffset,
|
|
||||||
uint16_t aLength,
|
|
||||||
bool &aResponse)
|
|
||||||
{
|
|
||||||
Error error = kErrorNone;
|
|
||||||
uint64_t providedChallenge = 0;
|
|
||||||
Crypto::HmacSha256::Hash hash;
|
|
||||||
|
|
||||||
VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState);
|
|
||||||
VerifyOrExit(StringLength(mVendorInfo->mPskdString, kMaxPskdLength) != 0, error = kErrorFailed);
|
|
||||||
VerifyOrExit(aLength == sizeof(providedChallenge), error = kErrorParse);
|
|
||||||
|
|
||||||
SuccessOrExit(error = aIncomingMessage.Read(aOffset, &providedChallenge, aLength));
|
|
||||||
|
|
||||||
SuccessOrExit(error = CalculateHash(providedChallenge, mVendorInfo->mPskdString,
|
|
||||||
StringLength(mVendorInfo->mPskdString, kMaxPskdLength), hash));
|
|
||||||
|
|
||||||
SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, hash.GetBytes(),
|
|
||||||
Crypto::HmacSha256::Hash::kSize));
|
|
||||||
aResponse = true;
|
|
||||||
|
|
||||||
exit:
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error TcatAgent::VerifyHash(const Message &aIncomingMessage,
|
Error TcatAgent::VerifyHash(const Message &aIncomingMessage,
|
||||||
uint16_t aOffset,
|
uint16_t aOffset,
|
||||||
uint16_t aLength,
|
uint16_t aLength,
|
||||||
@@ -918,14 +889,26 @@ Error TcatAgent::VerifyHash(const Message &aIncomingMessage,
|
|||||||
{
|
{
|
||||||
Error error = kErrorNone;
|
Error error = kErrorNone;
|
||||||
Crypto::HmacSha256::Hash hash;
|
Crypto::HmacSha256::Hash hash;
|
||||||
|
UptimeSec currentTime = Get<UptimeTracker>().GetUptimeInSeconds();
|
||||||
|
uint32_t newAttempts = (currentTime - mLastHashVerificationTimestamp) / kHashVerificationAttemptTime;
|
||||||
|
uint32_t totalAttempts = newAttempts + mHashVerificationAttempts;
|
||||||
|
|
||||||
VerifyOrExit(aLength == Crypto::HmacSha256::Hash::kSize, error = kErrorSecurity);
|
VerifyOrExit(aLength == Crypto::HmacSha256::Hash::kSize, error = kErrorSecurity);
|
||||||
VerifyOrExit(mRandomChallenge != 0, error = kErrorSecurity);
|
VerifyOrExit(mRandomChallenge != 0, error = kErrorSecurity);
|
||||||
|
|
||||||
|
// In case uptime has overflowed (will never happen in practical functional operational life), up to
|
||||||
|
// kHashVerificationMaxAttempts additional attempts can be tolerated.
|
||||||
|
mHashVerificationAttempts = static_cast<uint8_t>(Min<uint32_t>(totalAttempts, kHashVerificationMaxAttempts));
|
||||||
|
|
||||||
|
VerifyOrExit(mHashVerificationAttempts > 0, error = kErrorBusy);
|
||||||
|
mLastHashVerificationTimestamp = currentTime;
|
||||||
|
mHashVerificationAttempts--;
|
||||||
|
|
||||||
SuccessOrExit(error = CalculateHash(mRandomChallenge, reinterpret_cast<const char *>(aBuf), aBufLen, hash));
|
SuccessOrExit(error = CalculateHash(mRandomChallenge, reinterpret_cast<const char *>(aBuf), aBufLen, hash));
|
||||||
DumpDebg("Hash", &hash, sizeof(hash));
|
DumpDebg("Hash", &hash, sizeof(hash));
|
||||||
|
|
||||||
VerifyOrExit(aIncomingMessage.Compare(aOffset, hash), error = kErrorSecurity);
|
VerifyOrExit(aIncomingMessage.Compare(aOffset, hash), error = kErrorSecurity);
|
||||||
|
mHashVerificationAttempts++;
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return error;
|
return error;
|
||||||
|
|||||||
@@ -38,6 +38,10 @@
|
|||||||
|
|
||||||
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
|
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
|
||||||
|
|
||||||
|
#if !OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||||
|
#error "OPENTHREAD_CONFIG_UPTIME_ENABLE is required for TCAT agent"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <openthread/netdiag.h>
|
#include <openthread/netdiag.h>
|
||||||
#include <openthread/tcat.h>
|
#include <openthread/tcat.h>
|
||||||
#include <openthread/platform/ble.h>
|
#include <openthread/platform/ble.h>
|
||||||
@@ -48,6 +52,7 @@
|
|||||||
#include "common/log.hpp"
|
#include "common/log.hpp"
|
||||||
#include "common/message.hpp"
|
#include "common/message.hpp"
|
||||||
#include "common/non_copyable.hpp"
|
#include "common/non_copyable.hpp"
|
||||||
|
#include "common/uptime.hpp"
|
||||||
#include "mac/mac_types.hpp"
|
#include "mac/mac_types.hpp"
|
||||||
#include "meshcop/dataset.hpp"
|
#include "meshcop/dataset.hpp"
|
||||||
#include "meshcop/meshcop.hpp"
|
#include "meshcop/meshcop.hpp"
|
||||||
@@ -172,7 +177,6 @@ public:
|
|||||||
kTlvPresentPskcHash = 0x11, ///< TCAT commissioner rights elevation request TLV using PSKc hash
|
kTlvPresentPskcHash = 0x11, ///< TCAT commissioner rights elevation request TLV using PSKc hash
|
||||||
kTlvPresentInstallCodeHash = 0x12, ///< TCAT commissioner rights elevation request TLV using install code
|
kTlvPresentInstallCodeHash = 0x12, ///< TCAT commissioner rights elevation request TLV using install code
|
||||||
kTlvRequestRandomNumChallenge = 0x13, ///< TCAT random number challenge query TLV
|
kTlvRequestRandomNumChallenge = 0x13, ///< TCAT random number challenge query TLV
|
||||||
kTlvRequestPskdHash = 0x14, ///< TCAT PSKd hash request TLV
|
|
||||||
|
|
||||||
// Command Class Commissioning
|
// Command Class Commissioning
|
||||||
kTlvSetActiveOperationalDataset = 0x20, ///< TCAT active operational dataset TLV
|
kTlvSetActiveOperationalDataset = 0x20, ///< TCAT active operational dataset TLV
|
||||||
@@ -463,11 +467,6 @@ private:
|
|||||||
Error HandlePresentPskcHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength);
|
Error HandlePresentPskcHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength);
|
||||||
Error HandlePresentInstallCodeHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength);
|
Error HandlePresentInstallCodeHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength);
|
||||||
Error HandleRequestRandomNumberChallenge(Message &aOutgoingMessage, bool &aResponse);
|
Error HandleRequestRandomNumberChallenge(Message &aOutgoingMessage, bool &aResponse);
|
||||||
Error HandleRequestPskdHash(const Message &aIncomingMessage,
|
|
||||||
Message &aOutgoingMessage,
|
|
||||||
uint16_t aOffset,
|
|
||||||
uint16_t aLength,
|
|
||||||
bool &aResponse);
|
|
||||||
Error HandleStartThreadInterface(void);
|
Error HandleStartThreadInterface(void);
|
||||||
Error HandleStopThreadInterface(void);
|
Error HandleStopThreadInterface(void);
|
||||||
Error HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse);
|
Error HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse);
|
||||||
@@ -490,16 +489,18 @@ private:
|
|||||||
const Dataset *aCommSuppliedDataset) const;
|
const Dataset *aCommSuppliedDataset) const;
|
||||||
uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aActiveDatasetInfo) const;
|
uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aActiveDatasetInfo) const;
|
||||||
|
|
||||||
static constexpr uint16_t kPingPayloadMaxLength = 512;
|
static constexpr uint16_t kPingPayloadMaxLength = 512;
|
||||||
static constexpr uint16_t kProvisioningUrlMaxLength = OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH;
|
static constexpr uint16_t kProvisioningUrlMaxLength = OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH;
|
||||||
static constexpr uint16_t kMaxPskdLength = OT_JOINER_MAX_PSKD_LENGTH;
|
static constexpr uint16_t kMaxPskdLength = OT_JOINER_MAX_PSKD_LENGTH;
|
||||||
static constexpr uint16_t kTcatMaxDeviceIdSize = OT_TCAT_MAX_DEVICEID_SIZE;
|
static constexpr uint16_t kTcatMaxDeviceIdSize = OT_TCAT_MAX_DEVICEID_SIZE;
|
||||||
static constexpr uint16_t kInstallCodeMaxSize = 255;
|
static constexpr uint16_t kInstallCodeMaxSize = 255;
|
||||||
static constexpr uint16_t kCommissionerCertMaxLength = 1024;
|
static constexpr uint16_t kCommissionerCertMaxLength = 1024;
|
||||||
static constexpr uint16_t kBufferReserve = 2048 / (Buffer::kSize - sizeof(otMessageBuffer)) + 1;
|
static constexpr uint16_t kBufferReserve = 2048 / (Buffer::kSize - sizeof(otMessageBuffer)) + 1;
|
||||||
static constexpr uint8_t kServiceNameMaxLength = OT_TCAT_SERVICE_NAME_MAX_LENGTH;
|
static constexpr uint8_t kServiceNameMaxLength = OT_TCAT_SERVICE_NAME_MAX_LENGTH;
|
||||||
static constexpr uint8_t kApplicationLayerMaxCount = OT_TCAT_APPLICATION_LAYER_MAX_COUNT;
|
static constexpr uint8_t kApplicationLayerMaxCount = OT_TCAT_APPLICATION_LAYER_MAX_COUNT;
|
||||||
static constexpr uint16_t kTcatTmfEnableDefaultSec = OT_TCAT_ENABLE_MAX;
|
static constexpr uint16_t kTcatTmfEnableDefaultSec = OT_TCAT_ENABLE_MAX;
|
||||||
|
static constexpr uint32_t kHashVerificationAttemptTime = 5;
|
||||||
|
static constexpr uint8_t kHashVerificationMaxAttempts = 10;
|
||||||
|
|
||||||
const VendorInfo *mVendorInfo;
|
const VendorInfo *mVendorInfo;
|
||||||
Callback<JoinCallback> mJoinCallback;
|
Callback<JoinCallback> mJoinCallback;
|
||||||
@@ -524,6 +525,8 @@ private:
|
|||||||
using ExpireTimer = TimerMilliIn<TcatAgent, &TcatAgent::HandleTimer>;
|
using ExpireTimer = TimerMilliIn<TcatAgent, &TcatAgent::HandleTimer>;
|
||||||
ExpireTimer mActiveOrStandbyTimer;
|
ExpireTimer mActiveOrStandbyTimer;
|
||||||
uint32_t mTcatActiveDurationMs;
|
uint32_t mTcatActiveDurationMs;
|
||||||
|
UptimeSec mLastHashVerificationTimestamp;
|
||||||
|
uint8_t mHashVerificationAttempts;
|
||||||
};
|
};
|
||||||
|
|
||||||
DeclareTmfHandler(TcatAgent, kUriTcatEnable);
|
DeclareTmfHandler(TcatAgent, kUriTcatEnable);
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ source "tests/scripts/expect/_common.exp"
|
|||||||
|
|
||||||
spawn_node 1 "cli"
|
spawn_node 1 "cli"
|
||||||
|
|
||||||
|
# Sleep > 50 seconds to ensure the maximum number of unsuccessful hash verification attempts (10) are permitted
|
||||||
|
sleep 51
|
||||||
|
|
||||||
spawn_tcat_client_for_node 1 tools/tcat_ble_client/auth-cert/CommCert2
|
spawn_tcat_client_for_node 1 tools/tcat_ble_client/auth-cert/CommCert2
|
||||||
|
|
||||||
send "commission\n"
|
send "commission\n"
|
||||||
@@ -41,11 +44,6 @@ send "random_challenge\n"
|
|||||||
expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
|
expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
|
||||||
expect_line "\tLEN:\t8"
|
expect_line "\tLEN:\t8"
|
||||||
|
|
||||||
send "peer_pskd_hash JJJJJJ\n"
|
|
||||||
expect_line "Requested hash is valid."
|
|
||||||
expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
|
|
||||||
expect_line "\tLEN:\t32"
|
|
||||||
|
|
||||||
send "present_hash pskd AAAA\n"
|
send "present_hash pskd AAAA\n"
|
||||||
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
||||||
expect_line "\tVALUE:\t0x07"
|
expect_line "\tVALUE:\t0x07"
|
||||||
@@ -74,6 +72,27 @@ send "present_hash pskc aaaa\n"
|
|||||||
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
||||||
expect_line "\tVALUE:\t0x07"
|
expect_line "\tVALUE:\t0x07"
|
||||||
|
|
||||||
|
# Sleep >> 5 seconds to ensure a few more hash verification attempts are allowed
|
||||||
|
sleep 20
|
||||||
|
|
||||||
|
# Try hash verification >10 times to enforce rate limitation
|
||||||
|
for {set i 0} {$i < 11} {incr i} {
|
||||||
|
send "present_hash pskd AAAA\n"
|
||||||
|
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
||||||
|
}
|
||||||
|
|
||||||
|
send "present_hash pskd AAAA\n"
|
||||||
|
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
||||||
|
expect_line "\tVALUE:\t0x05"
|
||||||
|
|
||||||
|
# Sleep >5 seconds to ensure one more hash verification attempt is allowed
|
||||||
|
sleep 5.5
|
||||||
|
|
||||||
|
send "present_hash pskd AAAA\n"
|
||||||
|
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
|
||||||
|
expect_line "\tVALUE:\t0x07"
|
||||||
|
|
||||||
|
|
||||||
dispose_tcat_client 1
|
dispose_tcat_client 1
|
||||||
|
|
||||||
switch_node 1
|
switch_node 1
|
||||||
|
|||||||
@@ -324,41 +324,6 @@ class GetNetworkNameCommand(BleCommand):
|
|||||||
return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes()
|
return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes()
|
||||||
|
|
||||||
|
|
||||||
class GetPskdHash(BleCommand):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.digest = None
|
|
||||||
|
|
||||||
def get_log_string(self) -> str:
|
|
||||||
return 'Retrieving peer PSKd hash.'
|
|
||||||
|
|
||||||
def get_help_string(self) -> str:
|
|
||||||
return 'Get calculated PSKd hash.'
|
|
||||||
|
|
||||||
def prepare_data(self, args, context) -> bytes:
|
|
||||||
bless: BleStreamSecure = context['ble_sstream']
|
|
||||||
if bless.peer_public_key is None:
|
|
||||||
raise DataNotPrepared("Peer certificate not present.")
|
|
||||||
|
|
||||||
challenge = token_bytes(CHALLENGE_SIZE)
|
|
||||||
pskd = bytes(args[0], 'utf-8')
|
|
||||||
|
|
||||||
data = TLV(TcatTLVType.GET_PSKD_HASH.value, challenge).to_bytes()
|
|
||||||
|
|
||||||
hash = hmac.new(pskd, digestmod=sha256)
|
|
||||||
hash.update(challenge)
|
|
||||||
hash.update(bless.peer_public_key)
|
|
||||||
self.digest = hash.digest()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def process_response(self, tlv_response, context) -> None:
|
|
||||||
if tlv_response.value == self.digest:
|
|
||||||
print('Requested hash is valid.')
|
|
||||||
else:
|
|
||||||
print('Requested hash is NOT valid.')
|
|
||||||
|
|
||||||
|
|
||||||
class GetRandomNumberChallenge(BleCommand):
|
class GetRandomNumberChallenge(BleCommand):
|
||||||
|
|
||||||
def get_log_string(self) -> str:
|
def get_log_string(self) -> str:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import shlex
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand,
|
from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand,
|
||||||
ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetPskdHash,
|
ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand,
|
||||||
GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand,
|
GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand,
|
||||||
GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash,
|
GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash,
|
||||||
DiagnosticTlvsCommand, GetApplicationLayersCommand, SendVendorData,
|
DiagnosticTlvsCommand, GetApplicationLayersCommand, SendVendorData,
|
||||||
@@ -74,7 +74,6 @@ class CLI:
|
|||||||
'simulation': SimulationCommand(),
|
'simulation': SimulationCommand(),
|
||||||
'random_challenge': GetRandomNumberChallenge(),
|
'random_challenge': GetRandomNumberChallenge(),
|
||||||
'present_hash': PresentHash(),
|
'present_hash': PresentHash(),
|
||||||
'peer_pskd_hash': GetPskdHash(),
|
|
||||||
'tlv': TlvCommand(),
|
'tlv': TlvCommand(),
|
||||||
'get_comm_cert': GetCommissionerCertificate(),
|
'get_comm_cert': GetCommissionerCertificate(),
|
||||||
'diagnostic_tlvs': DiagnosticTlvsCommand()
|
'diagnostic_tlvs': DiagnosticTlvsCommand()
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ class TcatTLVType(Enum):
|
|||||||
PRESENT_PSKC_HASH = 0x11
|
PRESENT_PSKC_HASH = 0x11
|
||||||
PRESENT_INSTALL_CODE_HASH = 0x12
|
PRESENT_INSTALL_CODE_HASH = 0x12
|
||||||
GET_RANDOM_NUMBER_CHALLENGE = 0x13
|
GET_RANDOM_NUMBER_CHALLENGE = 0x13
|
||||||
GET_PSKD_HASH = 0x14
|
|
||||||
ACTIVE_DATASET = 0x20
|
ACTIVE_DATASET = 0x20
|
||||||
GET_COMMISSIONER_CERTIFICATE = 0x25
|
GET_COMMISSIONER_CERTIFICATE = 0x25
|
||||||
GET_ACTIVE_DATASET = 0x40
|
GET_ACTIVE_DATASET = 0x40
|
||||||
|
|||||||
Reference in New Issue
Block a user