From 4d6def38a510d55cc169391dccaf0a9b139b96d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Bida?= Date: Tue, 21 Jan 2025 17:43:19 +0100 Subject: [PATCH] [tcat] implement extraction of active dataset and commissioner cert (#10991) Commit adds implementation of: - 0x40 Tcat tlv extraction of active dataset, - 0x25 Tcat tlv extraction of commissioner certificate. Includes also refactoring of `BleCommand` adds new method `process_response`. This simplifies: - `GetPskdHash` - `GetRandomNumberChallenge` --- include/openthread/ble_secure.h | 20 ++ include/openthread/instance.h | 2 +- include/openthread/platform/settings.h | 1 + src/core/api/ble_secure_api.cpp | 10 + src/core/common/settings.cpp | 17 +- src/core/common/settings.hpp | 30 ++- src/core/meshcop/secure_transport.cpp | 31 +++ src/core/meshcop/secure_transport.hpp | 13 ++ src/core/meshcop/tcat_agent.cpp | 79 ++++++- src/core/meshcop/tcat_agent.hpp | 15 +- src/core/radio/ble_secure.hpp | 2 +- src/core/utils/flash.hpp | 6 +- tests/scripts/expect/cli-tcat.exp | 17 ++ tools/tcat_ble_client/cli/base_commands.py | 247 +++++++++++---------- tools/tcat_ble_client/cli/cli.py | 10 +- tools/tcat_ble_client/tlv/tcat_tlv.py | 2 + 16 files changed, 370 insertions(+), 132 deletions(-) diff --git a/include/openthread/ble_secure.h b/include/openthread/ble_secure.h index 5b5c315af43..88fee32b707 100644 --- a/include/openthread/ble_secure.h +++ b/include/openthread/ble_secure.h @@ -171,6 +171,26 @@ void otBleSecureSetPsk(otInstance *aInstance, */ otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, unsigned char *aPeerCert, size_t *aCertLength); +/** + * Returns the DER encoded peer x509 certificate. + * + * @note Requires the build-time feature `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` to + * be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aPeerCert A pointer to the DER encoded certificate + * buffer. + * @param[in,out] aCertLength On input, the size the max size of @p + * aPeerCert. On output, the length of the + * DER encoded peer certificate. + * + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_INVALID_ARGS @p aInstance or @p aCertLength is invalid. + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NO_BUFS Can't allocate memory for certificate. + */ +otError otBleSecureGetPeerCertificateDer(otInstance *aInstance, unsigned char *aPeerCert, size_t *aCertLength); + /** * Returns an attribute value identified by its OID from the subject * of the peer x509 certificate. The peer OID is provided in binary format. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index ed1a4ec8cea..55a1ba7ddc9 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (471) +#define OPENTHREAD_API_VERSION (472) /** * @addtogroup api-instance diff --git a/include/openthread/platform/settings.h b/include/openthread/platform/settings.h index fe0af92997a..baa3c0afa78 100644 --- a/include/openthread/platform/settings.h +++ b/include/openthread/platform/settings.h @@ -72,6 +72,7 @@ enum OT_SETTINGS_KEY_BR_ULA_PREFIX = 0x000f, ///< BR ULA prefix. OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES = 0x0010, ///< BR local on-link prefixes. OT_SETTINGS_KEY_BORDER_AGENT_ID = 0x0011, ///< Unique Border Agent/Router ID. + OT_SETTINGS_KEY_TCAT_COMMR_CERT = 0x0012, ///< TCAT Commissioner certificate // Deprecated and reserved key values: // diff --git a/src/core/api/ble_secure_api.cpp b/src/core/api/ble_secure_api.cpp index 622b488f617..cc737ffd5af 100644 --- a/src/core/api/ble_secure_api.cpp +++ b/src/core/api/ble_secure_api.cpp @@ -87,7 +87,9 @@ otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, unsigned char { Error error; + VerifyOrExit(aPeerCert != nullptr, error = kErrorInvalidArgs); VerifyOrExit(aCertLength != nullptr, error = kErrorInvalidArgs); + error = AsCoreType(aInstance).Get().GetPeerCertificateBase64(aPeerCert, aCertLength, *aCertLength); exit: @@ -96,6 +98,14 @@ otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, unsigned char #endif #if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +otError otBleSecureGetPeerCertificateDer(otInstance *aInstance, unsigned char *aPeerCert, size_t *aCertLength) +{ + AssertPointerIsNotNull(aPeerCert); + AssertPointerIsNotNull(aCertLength); + + return AsCoreType(aInstance).Get().GetPeerCertificateDer(aPeerCert, aCertLength, *aCertLength); +} + otError otBleSecureGetPeerSubjectAttributeByOid(otInstance *aInstance, const char *aOid, size_t aOidLength, diff --git a/src/core/common/settings.cpp b/src/core/common/settings.cpp index f9368d40cd0..99f3391288e 100644 --- a/src/core/common/settings.cpp +++ b/src/core/common/settings.cpp @@ -168,7 +168,8 @@ const char *SettingsBase::KeyToString(Key aKey) "", // (14) Removed (previously NAT64 prefix) "BrUlaPrefix", // (15) kKeyBrUlaPrefix "BrOnLinkPrefixes", // (16) kKeyBrOnLinkPrefixes - "BorderAgentId" // (17) kKeyBorderAgentId + "BorderAgentId", // (17) kKeyBorderAgentId + "TcatCommrCert" // (18) kKeyTcatCommrCert }; struct EnumCheck @@ -192,9 +193,10 @@ const char *SettingsBase::KeyToString(Key aKey) ValidateNextEnum(kKeyBrUlaPrefix); ValidateNextEnum(kKeyBrOnLinkPrefixes); ValidateNextEnum(kKeyBorderAgentId); + ValidateNextEnum(kKeyTcatCommrCert); }; - static_assert(kLastKey == kKeyBorderAgentId, "kLastKey is not valid"); + static_assert(kLastKey == kKeyTcatCommrCert, "kLastKey is not valid"); OT_ASSERT(aKey <= kLastKey); @@ -263,6 +265,17 @@ void Settings::DeleteOperationalDataset(MeshCoP::Dataset::Type aType) OT_ASSERT(error != kErrorNotImplemented); } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +void Settings::SaveTcatCommissionerCertificate(uint8_t *aCert, uint16_t aCertLen) +{ + Error error = Get().Set(kKeyTcatCommrCert, aCert, aCertLen); + + Log(kActionSave, error, kKeyTcatCommrCert); + + SuccessOrAssert(error); +} +#endif + #if OPENTHREAD_FTD Error Settings::AddChildInfo(const ChildInfo &aChildInfo) { diff --git a/src/core/common/settings.hpp b/src/core/common/settings.hpp index a289fc045e6..5d1d6a2151e 100644 --- a/src/core/common/settings.hpp +++ b/src/core/common/settings.hpp @@ -119,9 +119,10 @@ class SettingsBase : public InstanceLocator kKeyBrUlaPrefix = OT_SETTINGS_KEY_BR_ULA_PREFIX, kKeyBrOnLinkPrefixes = OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES, kKeyBorderAgentId = OT_SETTINGS_KEY_BORDER_AGENT_ID, + kKeyTcatCommrCert = OT_SETTINGS_KEY_TCAT_COMMR_CERT, }; - static constexpr Key kLastKey = kKeyBorderAgentId; ///< The last (numerically) enumerator value in `Key`. + static constexpr Key kLastKey = kKeyTcatCommrCert; ///< The last (numerically) enumerator value in `Key`. static_assert(static_cast(kLastKey) < static_cast(OT_SETTINGS_KEY_VENDOR_RESERVED_MIN), "Core settings keys overlap with vendor reserved keys"); @@ -802,6 +803,33 @@ class Settings : public SettingsBase, private NonCopyable */ void DeleteOperationalDataset(MeshCoP::Dataset::Type aType); +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + /** + * Stores the Tcat Commissioner certificate. + * + * @param[in] aCert The DER-encoded X509 end-entity certificate to store. + * @param[in] aCertLen Certificate length. + */ + void SaveTcatCommissionerCertificate(uint8_t *aCert, uint16_t aCertLen); + + /** + * Reads the Tcat Commissioner certificate. + * + * @param[out] aCert Buffer to store the DER-encoded X509 end-entity certificate + * of the TCAT Commissioner. + * @param[in,out] aCertLen On input, the max size of @p aCert. On output, the length of + * the DER encoded peer certificate. + * + * @retval kErrorNone Successfully read the Dataset. + * @retval kErrorNotFound No corresponding value in the setting store. + * @retval kErrorNoBufs Buffer has not enough space to store the data. + */ + Error ReadTcatCommissionerCertificate(uint8_t *aCert, uint16_t &aCertLen) + { + return Get().Get(kKeyTcatCommrCert, aCert, &aCertLen); + } +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + /** * Reads a specified settings entry. * diff --git a/src/core/meshcop/secure_transport.cpp b/src/core/meshcop/secure_transport.cpp index 234228cab08..3ac1c89d0d2 100644 --- a/src/core/meshcop/secure_transport.cpp +++ b/src/core/meshcop/secure_transport.cpp @@ -1230,6 +1230,37 @@ Error SecureTransport::Extension::GetPeerCertificateBase64(unsigned char *aPeerC #endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) #if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +Error SecureTransport::Extension::GetPeerCertificateDer(uint8_t *aPeerCert, size_t *aCertLength, size_t aCertBufferSize) +{ + Error error = kErrorNone; + SecureSession *session = mSecureTransport.mSessions.GetHead(); + + VerifyOrExit(session->IsConnected(), error = kErrorInvalidState); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03010000) + VerifyOrExit(session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->raw.len < aCertBufferSize, + error = kErrorNoBufs); + + *aCertLength = session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->raw.len; + memcpy(aPeerCert, session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->raw.p, *aCertLength); + +#else + VerifyOrExit( + session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len) < + aCertBufferSize, + error = kErrorNoBufs); + + *aCertLength = + session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len); + memcpy(aPeerCert, + session->mSsl.MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(peer_cert)->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p), + *aCertLength); +#endif + +exit: + return error; +} + Error SecureTransport::Extension::GetPeerSubjectAttributeByOid(const char *aOid, size_t aOidLength, uint8_t *aAttributeBuffer, diff --git a/src/core/meshcop/secure_transport.hpp b/src/core/meshcop/secure_transport.hpp index 0c07e43fef8..cc71403b723 100644 --- a/src/core/meshcop/secure_transport.hpp +++ b/src/core/meshcop/secure_transport.hpp @@ -435,6 +435,19 @@ class SecureTransport : private NonCopyable #endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) #if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + /** + * Returns the DER encoded peer x509 certificate. + * + * @param[out] aPeerCert A pointer to the DER encoded certificate buffer. + * @param[out] aCertLength The length of the DER encoded peer certificate. + * @param[in] aCertBufferSize The buffer size of aPeerCert. + * + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNone Successfully get the peer certificate. + * @retval kErrorNoBufs Can't allocate memory for certificate. + */ + Error GetPeerCertificateDer(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize); + /** * Returns an attribute value identified by its OID from the subject * of the peer x509 certificate. The peer OID is provided in binary format. diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index 8d40e22739e..c98850aa5eb 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -404,6 +404,10 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length); break; + case kTlvGetActiveOperationalDataset: + error = HandleGetActiveOperationalDataset(aOutgoingMessage, response); + break; + case kTlvStartThreadInterface: error = HandleStartThreadInterface(); break; @@ -454,6 +458,9 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg case kTlvRequestPskdHash: error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response); break; + case kTlvGetCommissionerCertificate: + error = HandleGetCommissionerCertificate(aOutgoingMessage, response); + break; default: error = kErrorInvalidCommand; } @@ -509,33 +516,96 @@ Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessa Dataset dataset; OffsetRange offsetRange; Error error; + uint8_t buf[kCommissionerCertMaxLength]; + size_t bufLen = sizeof(buf); offsetRange.Init(aOffset, aLength); SuccessOrExit(error = dataset.SetFrom(aIncomingMessage, offsetRange)); SuccessOrExit(error = dataset.ValidateTlvs()); - if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags, - mDeviceAuthorizationField.mApplicationFlags, &dataset)) + if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags, &dataset)) { error = kErrorRejected; ExitNow(); } + SuccessOrExit(error = Get().GetPeerCertificateDer(buf, &bufLen, bufLen)); + Get().SaveTcatCommissionerCertificate(buf, static_cast(bufLen)); + Get().SaveLocal(dataset); exit: return error; } +Error TcatAgent::HandleGetActiveOperationalDataset(Message &aOutgoingMessage, bool &aResponse) +{ + Error error = kErrorNone; + Dataset dataset; + Dataset::Tlvs datasetTlvs; + + if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags, &dataset)) + { + error = kErrorRejected; + ExitNow(); + } + + SuccessOrExit(error = Get().Read(datasetTlvs)); + SuccessOrExit( + error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, datasetTlvs.mTlvs, datasetTlvs.mLength)); + aResponse = true; + +exit: + return error; +} + +Error TcatAgent::HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse) +{ + Error error = kErrorNone; + Dataset dataset; + uint8_t buf[kCommissionerCertMaxLength]; + uint16_t bufLen = sizeof(buf); + + if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags, &dataset)) + { + error = kErrorRejected; + ExitNow(); + } + + VerifyOrExit(kErrorNone == Get().ReadTcatCommissionerCertificate(buf, bufLen), + error = kErrorInvalidState); + SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, buf, bufLen)); + aResponse = true; + +exit: + return error; +} + Error TcatAgent::HandleDecomission(void) { - Error error = kErrorNone; + Error error = kErrorNone; + unsigned char buf[kCommissionerCertMaxLength]; + size_t bufLen = sizeof(buf); + Dataset dataset; + + if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mDecommissioningFlags, + mDeviceAuthorizationField.mDecommissioningFlags, &dataset)) + { + error = kErrorRejected; + ExitNow(); + } + + SuccessOrExit(error = Get().GetPeerCertificateDer(buf, &bufLen, bufLen)); + Get().SaveTcatCommissionerCertificate(buf, static_cast(bufLen)); IgnoreReturnValue(otThreadSetEnabled(&GetInstance(), false)); Get().Clear(); Get().Clear(); - error = Get().ErasePersistentInfo(); + IgnoreReturnValue(Get().ErasePersistentInfo()); #if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE { @@ -545,6 +615,7 @@ Error TcatAgent::HandleDecomission(void) } #endif +exit: return error; } diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index 9b87daf4611..3781a714da4 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -339,6 +339,7 @@ class TcatAgent : public InstanceLocator, private NonCopyable Error HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage); Error HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength); + Error HandleGetActiveOperationalDataset(Message &aOutgoingMessage, bool &aResponse); Error HandleDecomission(void); Error HandlePing(const Message &aIncomingMessage, Message &aOutgoingMessage, @@ -359,6 +360,7 @@ class TcatAgent : public InstanceLocator, private NonCopyable uint16_t aLength, bool &aResponse); Error HandleStartThreadInterface(void); + Error HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse); Error VerifyHash(const Message &aIncomingMessage, uint16_t aOffset, @@ -374,12 +376,13 @@ class TcatAgent : public InstanceLocator, private NonCopyable bool CanProcessTlv(uint8_t aTlvType) const; CommandClass GetCommandClass(uint8_t aTlvType) const; - static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; - static constexpr uint16_t kPingPayloadMaxLength = 512; - static constexpr uint16_t kProvisioningUrlMaxLength = 64; - 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 kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; + static constexpr uint16_t kPingPayloadMaxLength = 512; + static constexpr uint16_t kProvisioningUrlMaxLength = 64; + 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; JoinerPskd mJoinerPskd; const VendorInfo *mVendorInfo; diff --git a/src/core/radio/ble_secure.hpp b/src/core/radio/ble_secure.hpp index 8ff54d772c7..6d48482fb24 100644 --- a/src/core/radio/ble_secure.hpp +++ b/src/core/radio/ble_secure.hpp @@ -271,7 +271,7 @@ class BleSecure : public InstanceLocator, public MeshCoP::Tls::Extension, privat /** * @brief Gets the Install Code Verify Status during the current session. * - * @return TRUE The install code was correctly verfied. + * @return TRUE The install code was correctly verified. * @return FALSE The install code was not verified. */ bool GetInstallCodeVerifyStatus(void) const { return mTcatAgent.GetInstallCodeVerifyStatus(); } diff --git a/src/core/utils/flash.hpp b/src/core/utils/flash.hpp index d9feaffd34b..1104de42157 100644 --- a/src/core/utils/flash.hpp +++ b/src/core/utils/flash.hpp @@ -200,7 +200,11 @@ class Flash : public InstanceLocator } private: - static constexpr uint16_t kMaxDataSize = 255; +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + static constexpr uint16_t kMaxDataSize = 1024; +#else + static constexpr uint16_t kMaxDataSize = 256; +#endif uint8_t mData[kMaxDataSize]; } OT_TOOL_PACKED_END; diff --git a/tests/scripts/expect/cli-tcat.exp b/tests/scripts/expect/cli-tcat.exp index e30926a0601..8296ee809dc 100755 --- a/tests/scripts/expect/cli-tcat.exp +++ b/tests/scripts/expect/cli-tcat.exp @@ -37,10 +37,27 @@ send "network_name\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x06" +send "get_dataset\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x04" + +send "get_comm_cert\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x06" + send "commission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" +send "get_dataset\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t106" +expect_line "\tVALUE:\t0x0e080000000000010000000300001235060004001fffe00208ef1398c2fd504b670708fd35344133d1d73e0510fda7c771a27202e232ecd04cf934f476030f4f70656e5468726561642d633634650102c64e04105e9b9b360f80b88be2603fb0135c8d650c0402a0f7f8" + +send "get_comm_cert\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tVALUE:\t0x308201d53082017ba00302010202030e1a83300a06082a8648ce3d04030230713126302406035504030c1d5468726561642043657274696669636174696f6e20446576696365434131193017060355040a0c105468726561642047726f757020496e633112301006035504070c0953616e2052616d6f6e310b300906035504080c024341310b3009060355040613025553301e170d3234303530373039333934355a170d3234303532313039333934355a303a311f301d06035504030c1654434154204578616d706c6520436f6d6d4365727431311730150603550405130e333532332d313534332d303030313059301306072a8648ce3d020106082a8648ce3d030107034200041d9abcbe167cd7a244860a9eeb364a8a830315baca1242659d17d224475f5d96124bde30505287cc41dc018dcf53820f4c69c5e69bac504e35ac96b69fcbc2efa3393037301f0603551d230418301680145fab1b296888a1d4b431a88661e7e76659edf819301406092b0601040182df2a03040704052101010101300a06082a8648ce3d0403020348003045022077847ce3845f78f26b6f9b24ca06104705139e9e50e8dacfd6954edd6ca041bc022100d2d68718da4682313f6f890b5b6edd77c1fe0ff1f990d6eaf49966f5cb74efc6" + send "thread start\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" diff --git a/tools/tcat_ble_client/cli/base_commands.py b/tools/tcat_ble_client/cli/base_commands.py index 6dc74078c0c..0286dc52243 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -44,6 +44,8 @@ import hmac import binascii +CHALLENGE_SIZE = 8 + class HelpCommand(Command): @@ -85,11 +87,15 @@ async def execute_default(self, args, context): if not response: return tlv_response = TLV.from_bytes(response) + self.process_response(tlv_response, context) return CommandResultTLV(tlv_response) except DataNotPrepared as err: print('Command failed', err) return CommandResultNone() + def process_response(self, tlv_response, context): + pass + class HelloCommand(BleCommand): @@ -129,6 +135,51 @@ def prepare_data(self, args, context): return TLV(TcatTLVType.DECOMMISSION.value, bytes()).to_bytes() +class DisconnectCommand(Command): + + def get_help_string(self) -> str: + return 'Disconnect client from TCAT device' + + async def execute_default(self, args, context): + if 'ble_sstream' not in context or context['ble_sstream'] is None: + print("TCAT Device not connected.") + return CommandResultNone() + await context['ble_sstream'].close() + return CommandResultNone() + + +class ExtractDatasetCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Getting active dataset.' + + def get_help_string(self) -> str: + return 'Get active dataset from device.' + + def prepare_data(self, args, context): + return TLV(TcatTLVType.GET_ACTIVE_DATASET.value, bytes()).to_bytes() + + def process_response(self, tlv_response, context): + if tlv_response.type == TcatTLVType.RESPONSE_W_PAYLOAD.value: + dataset = ThreadDataset() + dataset.set_from_bytes(tlv_response.value) + dataset.print_content() + else: + print('Dataset extraction error.') + + +class GetCommissionerCertificate(BleCommand): + + def get_log_string(self) -> str: + return 'Getting commissioner certificate.' + + def get_help_string(self) -> str: + return 'Get commissioner certificate from device.' + + def prepare_data(self, args, context): + return TLV(TcatTLVType.GET_COMMISSIONER_CERTIFICATE.value, bytes()).to_bytes() + + class GetDeviceIdCommand(BleCommand): def get_log_string(self) -> str: @@ -177,83 +228,38 @@ def prepare_data(self, args, context): return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes() -class PresentHash(BleCommand): +class GetPskdHash(BleCommand): def get_log_string(self) -> str: - return 'Presenting hash.' + return 'Retrieving peer PSKd hash.' def get_help_string(self) -> str: - return 'Present calculated hash.' + return 'Get calculated PSKd hash.' def prepare_data(self, args, context): - type = args[0] - code = None - tlv_type = None - if type == "pskd": - code = bytes(args[1], 'utf-8') - tlv_type = TcatTLVType.PRESENT_PSKD_HASH.value - elif type == "pskc": - code = bytes.fromhex(args[1]) - tlv_type = TcatTLVType.PRESENT_PSKC_HASH.value - elif type == "install": - code = bytes(args[1], 'utf-8') - tlv_type = TcatTLVType.PRESENT_INSTALL_CODE_HASH.value - else: - raise DataNotPrepared("Hash code name incorrect.") bless: BleStreamSecure = context['ble_sstream'] if bless.peer_public_key is None: raise DataNotPrepared("Peer certificate not present.") - if bless.peer_challenge is None: - raise DataNotPrepared("Peer challenge not present.") + challenge = token_bytes(CHALLENGE_SIZE) + pskd = bytes(args[0], 'utf-8') - hash = hmac.new(code, digestmod=sha256) - hash.update(bless.peer_challenge) - hash.update(bless.peer_public_key) + data = TLV(TcatTLVType.GET_PSKD_HASH.value, challenge).to_bytes() - data = TLV(tlv_type, hash.digest()).to_bytes() + hash = hmac.new(pskd, digestmod=sha256) + hash.update(challenge) + hash.update(bless.peer_public_key) + self.digest = hash.digest() return data - -class GetPskdHash(Command): - - def get_log_string(self) -> str: - return 'Retrieving peer PSKd hash.' - - def get_help_string(self) -> str: - return 'Get calculated PSKd hash.' - - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] - - print(self.get_log_string()) - try: - if bless.peer_public_key is None: - print("Peer certificate not present.") - return - challenge_size = 8 - challenge = token_bytes(challenge_size) - pskd = bytes(args[0], 'utf-8') - data = TLV(TcatTLVType.GET_PSKD_HASH.value, challenge).to_bytes() - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - if tlv_response.value != None: - hash = hmac.new(pskd, digestmod=sha256) - hash.update(challenge) - hash.update(bless.peer_public_key) - digest = hash.digest() - if digest == tlv_response.value: - print('Requested hash is valid.') - else: - print('Requested hash is NOT valid.') - return CommandResultTLV(tlv_response) - except DataNotPrepared as err: - print('Command failed', err) + def process_response(self, tlv_response, context): + if tlv_response.value == self.digest: + print('Requested hash is valid.') + else: + print('Requested hash is NOT valid.') -class GetRandomNumberChallenge(Command): +class GetRandomNumberChallenge(BleCommand): def get_log_string(self) -> str: return 'Retrieving random challenge.' @@ -261,25 +267,17 @@ def get_log_string(self) -> str: def get_help_string(self) -> str: return 'Get the device random number challenge.' - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] + def prepare_data(self, args, context): + return TLV(TcatTLVType.GET_RANDOM_NUMBER_CHALLENGE.value, bytes()).to_bytes() - print(self.get_log_string()) - try: - data = TLV(TcatTLVType.GET_RANDOM_NUMBER_CHALLENGE.value, bytes()).to_bytes() - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - if tlv_response.value != None: - if len(tlv_response.value) == 8: - bless.peer_challenge = tlv_response.value - else: - print('Challenge format invalid.') - return CommandResultNone() - return CommandResultTLV(tlv_response) - except DataNotPrepared as err: - print('Command failed', err) + def process_response(self, tlv_response, context): + bless: BleStreamSecure = context['ble_sstream'] + if tlv_response.value != None: + if len(tlv_response.value) == CHALLENGE_SIZE: + bless.peer_challenge = tlv_response.value + else: + print('Challenge format invalid.') + return CommandResultNone() class PingCommand(Command): @@ -295,7 +293,7 @@ async def execute_default(self, args, context): payload_size = int(args[0]) if payload_size > max_payload: print(f'Payload size too large. Maximum supported value is {max_payload}') - return + return CommandResultNone() to_send = token_bytes(payload_size) data = TLV(TcatTLVType.PING.value, to_send).to_bytes() elapsed_time = time() @@ -313,41 +311,42 @@ async def execute_default(self, args, context): return CommandResultTLV(tlv_response) -class ThreadStartCommand(BleCommand): - - def get_log_string(self) -> str: - return 'Enabling Thread...' - - def get_help_string(self) -> str: - return 'Enable thread interface.' - - def prepare_data(self, args, context): - return TLV(TcatTLVType.THREAD_START.value, bytes()).to_bytes() - - -class ThreadStopCommand(BleCommand): +class PresentHash(BleCommand): def get_log_string(self) -> str: - return 'Disabling Thread...' + return 'Presenting hash.' def get_help_string(self) -> str: - return 'Disable thread interface.' + return 'Present calculated hash.' def prepare_data(self, args, context): - return TLV(TcatTLVType.THREAD_STOP.value, bytes()).to_bytes() - - -class ThreadStateCommand(Command): + type = args[0] + code = None + tlv_type = None + if type == "pskd": + code = bytes(args[1], 'utf-8') + tlv_type = TcatTLVType.PRESENT_PSKD_HASH.value + elif type == "pskc": + code = bytes.fromhex(args[1]) + tlv_type = TcatTLVType.PRESENT_PSKC_HASH.value + elif type == "install": + code = bytes(args[1], 'utf-8') + tlv_type = TcatTLVType.PRESENT_INSTALL_CODE_HASH.value + else: + raise DataNotPrepared("Hash code name incorrect.") + bless: BleStreamSecure = context['ble_sstream'] + if bless.peer_public_key is None: + raise DataNotPrepared("Peer certificate not present.") - def __init__(self): - self._subcommands = {'start': ThreadStartCommand(), 'stop': ThreadStopCommand()} + if bless.peer_challenge is None: + raise DataNotPrepared("Peer challenge not present.") - def get_help_string(self) -> str: - return 'Manipulate state of the Thread interface of the connected device.' + hash = hmac.new(code, digestmod=sha256) + hash.update(bless.peer_challenge) + hash.update(bless.peer_public_key) - async def execute_default(self, args, context): - print('Invalid usage. Provide a subcommand.') - return CommandResultNone() + data = TLV(tlv_type, hash.digest()).to_bytes() + return data class ScanCommand(Command): @@ -387,14 +386,38 @@ async def execute_default(self, args, context): return CommandResultNone() -class DisconnectCommand(Command): +class ThreadStartCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Enabling Thread...' def get_help_string(self) -> str: - return 'Disconnect client from TCAT device' + return 'Enable thread interface.' + + def prepare_data(self, args, context): + return TLV(TcatTLVType.THREAD_START.value, bytes()).to_bytes() + + +class ThreadStopCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Disabling Thread...' + + def get_help_string(self) -> str: + return 'Disable thread interface.' + + def prepare_data(self, args, context): + return TLV(TcatTLVType.THREAD_STOP.value, bytes()).to_bytes() + + +class ThreadStateCommand(Command): + + def __init__(self): + self._subcommands = {'start': ThreadStartCommand(), 'stop': ThreadStopCommand()} + + def get_help_string(self) -> str: + return 'Manipulate state of the Thread interface of the connected device.' async def execute_default(self, args, context): - if 'ble_sstream' not in context or context['ble_sstream'] is None: - print("TCAT Device not connected.") - return CommandResultNone() - await context['ble_sstream'].close() + print('Invalid usage. Provide a subcommand.') return CommandResultNone() diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 6f397f6a28c..7137890cfe0 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -30,9 +30,9 @@ from argparse import ArgumentParser from ble.ble_stream_secure import BleStreamSecure from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, - GetDeviceIdCommand, GetPskdHash, GetExtPanIDCommand, GetNetworkNameCommand, - GetProvisioningUrlCommand, PingCommand, GetRandomNumberChallenge, ThreadStateCommand, - ScanCommand, PresentHash) + ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetPskdHash, + GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand, + GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash) from .tlv_commands import TlvCommand from cli.dataset_commands import (DatasetCommand) from dataset.dataset import ThreadDataset @@ -57,12 +57,14 @@ def __init__(self, 'network_name': GetNetworkNameCommand(), 'ping': PingCommand(), 'dataset': DatasetCommand(), + 'get_dataset': ExtractDatasetCommand(), 'thread': ThreadStateCommand(), 'scan': ScanCommand(), 'random_challenge': GetRandomNumberChallenge(), 'present_hash': PresentHash(), 'peer_pskd_hash': GetPskdHash(), - 'tlv': TlvCommand() + 'tlv': TlvCommand(), + 'get_comm_cert': GetCommissionerCertificate(), } self._context = { 'ble_sstream': ble_sstream, diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index 564db91bb09..8a001d51e91 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -43,6 +43,8 @@ class TcatTLVType(Enum): GET_RANDOM_NUMBER_CHALLENGE = 0x13 GET_PSKD_HASH = 0x14 ACTIVE_DATASET = 0x20 + GET_COMMISSIONER_CERTIFICATE = 0x25 + GET_ACTIVE_DATASET = 0x40 DECOMMISSION = 0x60 APPLICATION = 0x82 THREAD_START = 0x27