diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index 7d364fe32..394c80c2d 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -60,8 +60,10 @@ TcatAgent::TcatAgent(Instance &aInstance) , mTimerSetsToActive(false) , mActiveOrStandbyTimer(aInstance) , mTcatActiveDurationMs(0) + , mHashVerificationAttempts(1) { ClearCommissionerState(); + mLastHashVerificationTimestamp = Get().GetUptimeInSeconds(); } void TcatAgent::ClearCommissionerState(void) @@ -496,10 +498,6 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response); break; - case kTlvRequestPskdHash: - error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response); - break; - case kTlvGetCommissionerCertificate: error = HandleGetCommissionerCertificate(aOutgoingMessage, response); break; @@ -883,33 +881,6 @@ exit: 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, uint16_t aOffset, uint16_t aLength, @@ -918,14 +889,26 @@ Error TcatAgent::VerifyHash(const Message &aIncomingMessage, { Error error = kErrorNone; Crypto::HmacSha256::Hash hash; + UptimeSec currentTime = Get().GetUptimeInSeconds(); + uint32_t newAttempts = (currentTime - mLastHashVerificationTimestamp) / kHashVerificationAttemptTime; + uint32_t totalAttempts = newAttempts + mHashVerificationAttempts; VerifyOrExit(aLength == Crypto::HmacSha256::Hash::kSize, 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(Min(totalAttempts, kHashVerificationMaxAttempts)); + + VerifyOrExit(mHashVerificationAttempts > 0, error = kErrorBusy); + mLastHashVerificationTimestamp = currentTime; + mHashVerificationAttempts--; + SuccessOrExit(error = CalculateHash(mRandomChallenge, reinterpret_cast(aBuf), aBufLen, hash)); DumpDebg("Hash", &hash, sizeof(hash)); VerifyOrExit(aIncomingMessage.Compare(aOffset, hash), error = kErrorSecurity); + mHashVerificationAttempts++; exit: return error; diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index bc444bdd1..905f467d3 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -38,6 +38,10 @@ #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#if !OPENTHREAD_CONFIG_UPTIME_ENABLE +#error "OPENTHREAD_CONFIG_UPTIME_ENABLE is required for TCAT agent" +#endif + #include #include #include @@ -48,6 +52,7 @@ #include "common/log.hpp" #include "common/message.hpp" #include "common/non_copyable.hpp" +#include "common/uptime.hpp" #include "mac/mac_types.hpp" #include "meshcop/dataset.hpp" #include "meshcop/meshcop.hpp" @@ -172,7 +177,6 @@ public: kTlvPresentPskcHash = 0x11, ///< TCAT commissioner rights elevation request TLV using PSKc hash kTlvPresentInstallCodeHash = 0x12, ///< TCAT commissioner rights elevation request TLV using install code kTlvRequestRandomNumChallenge = 0x13, ///< TCAT random number challenge query TLV - kTlvRequestPskdHash = 0x14, ///< TCAT PSKd hash request TLV // Command Class Commissioning kTlvSetActiveOperationalDataset = 0x20, ///< TCAT active operational dataset TLV @@ -463,11 +467,6 @@ private: Error HandlePresentPskcHash(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 HandleRequestPskdHash(const Message &aIncomingMessage, - Message &aOutgoingMessage, - uint16_t aOffset, - uint16_t aLength, - bool &aResponse); Error HandleStartThreadInterface(void); Error HandleStopThreadInterface(void); Error HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse); @@ -490,16 +489,18 @@ private: const Dataset *aCommSuppliedDataset) const; uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aActiveDatasetInfo) const; - static constexpr uint16_t kPingPayloadMaxLength = 512; - 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 kTcatMaxDeviceIdSize = OT_TCAT_MAX_DEVICEID_SIZE; - static constexpr uint16_t kInstallCodeMaxSize = 255; - static constexpr uint16_t kCommissionerCertMaxLength = 1024; - 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 kApplicationLayerMaxCount = OT_TCAT_APPLICATION_LAYER_MAX_COUNT; - static constexpr uint16_t kTcatTmfEnableDefaultSec = OT_TCAT_ENABLE_MAX; + static constexpr uint16_t kPingPayloadMaxLength = 512; + 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 kTcatMaxDeviceIdSize = OT_TCAT_MAX_DEVICEID_SIZE; + static constexpr uint16_t kInstallCodeMaxSize = 255; + static constexpr uint16_t kCommissionerCertMaxLength = 1024; + 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 kApplicationLayerMaxCount = OT_TCAT_APPLICATION_LAYER_MAX_COUNT; + static constexpr uint16_t kTcatTmfEnableDefaultSec = OT_TCAT_ENABLE_MAX; + static constexpr uint32_t kHashVerificationAttemptTime = 5; + static constexpr uint8_t kHashVerificationMaxAttempts = 10; const VendorInfo *mVendorInfo; Callback mJoinCallback; @@ -524,6 +525,8 @@ private: using ExpireTimer = TimerMilliIn; ExpireTimer mActiveOrStandbyTimer; uint32_t mTcatActiveDurationMs; + UptimeSec mLastHashVerificationTimestamp; + uint8_t mHashVerificationAttempts; }; DeclareTmfHandler(TcatAgent, kUriTcatEnable); diff --git a/tests/scripts/expect/cli-tcat-hashes.exp b/tests/scripts/expect/cli-tcat-hashes.exp index 631235f37..5f7722c08 100755 --- a/tests/scripts/expect/cli-tcat-hashes.exp +++ b/tests/scripts/expect/cli-tcat-hashes.exp @@ -31,6 +31,9 @@ source "tests/scripts/expect/_common.exp" 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 send "commission\n" @@ -41,11 +44,6 @@ send "random_challenge\n" expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" 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" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x07" @@ -74,6 +72,27 @@ send "present_hash pskc aaaa\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" 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 switch_node 1 diff --git a/tools/tcat_ble_client/cli/base_commands.py b/tools/tcat_ble_client/cli/base_commands.py index 1954e2b7a..c466d7a64 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -324,41 +324,6 @@ class GetNetworkNameCommand(BleCommand): 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): def get_log_string(self) -> str: diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 9de33dec8..25fc6a0a3 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -33,7 +33,7 @@ import shlex from typing import Optional from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, - ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetPskdHash, + ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand, GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash, DiagnosticTlvsCommand, GetApplicationLayersCommand, SendVendorData, @@ -74,7 +74,6 @@ class CLI: 'simulation': SimulationCommand(), 'random_challenge': GetRandomNumberChallenge(), 'present_hash': PresentHash(), - 'peer_pskd_hash': GetPskdHash(), 'tlv': TlvCommand(), 'get_comm_cert': GetCommissionerCertificate(), 'diagnostic_tlvs': DiagnosticTlvsCommand() diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index 8a3e2ae7b..6cde9b05c 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -42,7 +42,6 @@ class TcatTLVType(Enum): PRESENT_PSKC_HASH = 0x11 PRESENT_INSTALL_CODE_HASH = 0x12 GET_RANDOM_NUMBER_CHALLENGE = 0x13 - GET_PSKD_HASH = 0x14 ACTIVE_DATASET = 0x20 GET_COMMISSIONER_CERTIFICATE = 0x25 GET_ACTIVE_DATASET = 0x40