[tcat] implementation of TCAT general class commands (#10700)

Commit introduces implementation of missing general class commands:
- PresentPskdHash
- PresentPskcHash
- PresentInstallCodeHash
- RequestRandomNumChallenge
- RequestPskdHash

Also include minor fixes in Tcat python client and refactoring of expect
tests for tcat.
This commit is contained in:
Przemysław Bida
2024-10-02 11:10:16 -07:00
committed by GitHub
parent e120051e21
commit 213665cce0
21 changed files with 543 additions and 70 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
pip install bleak 'cryptography==43.0.0'
- name: Run RCP Mode
run: |
ulimit -c unlimited
+1 -1
View File
@@ -247,7 +247,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
pip install bleak 'cryptography==43.0.0'
- name: Run
run: |
ulimit -c unlimited
+1 -1
View File
@@ -298,7 +298,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
pip install bleak 'cryptography==43.0.0'
- name: Run RCP Mode
run: |
ulimit -c unlimited
+10
View File
@@ -407,6 +407,16 @@ otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint
*/
otError otBleSecureFlush(otInstance *aInstance);
/**
* Gets the Install Code Verify Status during the current session.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @retval TRUE The install code was correctly verified.
* @retval FALSE The install code was not verified.
*/
bool otBleSecureGetInstallCodeVerifyStatus(otInstance *aInstance);
/**
* @}
*/
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (451)
#define OPENTHREAD_API_VERSION (452)
/**
* @addtogroup api-instance
+4 -2
View File
@@ -92,8 +92,9 @@ namespace Cli {
otTcatAdvertisedDeviceId sAdvertisedDeviceIds[OT_TCAT_DEVICE_ID_MAX];
otTcatGeneralDeviceId sGeneralDeviceId;
const char kPskdVendor[] = "JJJJJJ";
const char kUrl[] = "dummy_url";
const char kPskdVendor[] = "JJJJJJ";
const char kInstallVendor[] = "InstallCode";
const char kUrl[] = "dummy_url";
static bool IsDeviceIdSet(void)
{
@@ -293,6 +294,7 @@ template <> otError Tcat::Process<Cmd("start")>(Arg aArgs[])
ClearAllBytes(mVendorInfo);
mVendorInfo.mPskdString = kPskdVendor;
mVendorInfo.mProvisioningUrl = kUrl;
mVendorInfo.mInstallCode = kInstallVendor;
if (IsDeviceIdSet())
{
+5
View File
@@ -186,4 +186,9 @@ otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint
otError otBleSecureFlush(otInstance *aInstance) { return AsCoreType(aInstance).Get<Ble::BleSecure>().Flush(); }
bool otBleSecureGetInstallCodeVerifyStatus(otInstance *aInstance)
{
return AsCoreType(aInstance).Get<Ble::BleSecure>().GetInstallCodeVerifyStatus();
}
#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
+8
View File
@@ -301,6 +301,14 @@ public:
* @param[in] aX509CaCertChainLength The length of chain.
*/
void SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLength);
/**
* Extracts public key from it's own certificate.
*
* @returns Public key from own certificate in form of entire ASN.1 field.
*/
const mbedtls_asn1_buf &GetOwnPublicKey(void) const { return mOwnCert.pk_raw; }
#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#if defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)
+168 -16
View File
@@ -32,6 +32,7 @@
*/
#include "tcat_agent.hpp"
#include "common/code_utils.hpp"
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
@@ -59,6 +60,10 @@ TcatAgent::TcatAgent(Instance &aInstance)
, mCommissionerHasNetworkName(false)
, mCommissionerHasDomainName(false)
, mCommissionerHasExtendedPanId(false)
, mRandomChallenge(0)
, mPskdVerified(false)
, mPskcVerified(false)
, mInstallCodeVerified(false)
{
mJoinerPskd.Clear();
mCurrentServiceName[0] = 0;
@@ -72,6 +77,7 @@ Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallb
VerifyOrExit(mVendorInfo != nullptr, error = kErrorFailed);
mAppDataReceiveCallback.Set(aAppDataReceiveCallback, aContext);
mJoinCallback.Set(aHandler, aContext);
mRandomChallenge = 0;
mCurrentApplicationProtocol = kApplicationProtocolNone;
mState = kStateEnabled;
@@ -87,6 +93,10 @@ void TcatAgent::Stop(void)
mState = kStateDisabled;
mAppDataReceiveCallback.Clear();
mJoinCallback.Clear();
mRandomChallenge = 0;
mPskdVerified = false;
mPskcVerified = false;
mInstallCodeVerified = false;
LogInfo("TCAT agent stopped");
}
@@ -169,6 +179,11 @@ void TcatAgent::Disconnected(void)
mState = kStateEnabled;
}
mRandomChallenge = 0;
mPskdVerified = false;
mPskcVerified = false;
mInstallCodeVerified = false;
LogInfo("TCAT agent disconnected");
}
@@ -190,14 +205,14 @@ bool TcatAgent::CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissio
additionalDeviceRequirementMet = true;
}
if (aDeviceCommandClassFlags & kPskdFlag)
if (!additionalDeviceRequirementMet && (aDeviceCommandClassFlags & kPskdFlag))
{
additionalDeviceRequirementMet = true;
additionalDeviceRequirementMet = mPskdVerified;
}
if (aDeviceCommandClassFlags & kPskcFlag)
if (!additionalDeviceRequirementMet && (aDeviceCommandClassFlags & kPskcFlag))
{
additionalDeviceRequirementMet = true;
additionalDeviceRequirementMet = mPskcVerified;
}
if (mCommissionerHasNetworkName || mCommissionerHasExtendedPanId)
@@ -424,7 +439,21 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg
case kTlvGetProvisioningURL:
error = HandleGetProvisioningUrl(aOutgoingMessage, response);
break;
case kTlvPresentPskdHash:
error = HandlePresentPskdHash(aIncomingMessage, offset, length);
break;
case kTlvPresentPskcHash:
error = HandlePresentPskcHash(aIncomingMessage, offset, length);
break;
case kTlvPresentInstallCodeHash:
error = HandlePresentInstallCodeHash(aIncomingMessage, offset, length);
break;
case kTlvRequestRandomNumChallenge:
error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response);
break;
case kTlvRequestPskdHash:
error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response);
break;
default:
error = kErrorInvalidCommand;
}
@@ -459,6 +488,10 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg
statusCode = kStatusUnsupported;
break;
case kErrorSecurity:
statusCode = kStatusHashError;
break;
default:
statusCode = kStatusGeneralError;
break;
@@ -519,7 +552,7 @@ Error TcatAgent::HandlePing(const Message &aIncomingMessage,
Message &aOutgoingMessage,
uint16_t aOffset,
uint16_t aLength,
bool &response)
bool &aResponse)
{
Error error = kErrorNone;
ot::ExtendedTlv extTlv;
@@ -540,13 +573,13 @@ Error TcatAgent::HandlePing(const Message &aIncomingMessage,
}
SuccessOrExit(error = aOutgoingMessage.AppendBytesFromMessage(aIncomingMessage, aOffset, aLength));
response = true;
aResponse = true;
exit:
return error;
}
Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &response)
Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &aResponse)
{
Error error = kErrorNone;
MeshCoP::NameData nameData = Get<MeshCoP::NetworkNameManager>().GetNetworkName().GetAsData();
@@ -558,13 +591,13 @@ Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &response)
SuccessOrExit(
error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, nameData.GetBuffer(), nameData.GetLength()));
response = true;
aResponse = true;
exit:
return error;
}
Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &response)
Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &aResponse)
{
const uint8_t *deviceId;
uint16_t length = 0;
@@ -587,13 +620,13 @@ Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &response)
SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, deviceId, length));
response = true;
aResponse = true;
exit:
return error;
}
Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &response)
Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &aResponse)
{
Error error;
@@ -601,13 +634,13 @@ Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &response)
SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload,
&Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId(), sizeof(ExtendedPanId)));
response = true;
aResponse = true;
exit:
return error;
}
Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response)
Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &aResponse)
{
Error error = kErrorNone;
uint16_t length;
@@ -617,13 +650,132 @@ Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &respo
length = StringLength(mVendorInfo->mProvisioningUrl, kProvisioningUrlMaxLength);
VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorInvalidState);
error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, length);
response = true;
SuccessOrExit(error =
Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, length));
aResponse = true;
exit:
return error;
}
Error TcatAgent::HandlePresentPskdHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength)
{
Error error = kErrorNone;
VerifyOrExit(mVendorInfo->mPskdString != nullptr, error = kErrorSecurity);
SuccessOrExit(error = VerifyHash(aIncomingMessage, aOffset, aLength, mVendorInfo->mPskdString,
StringLength(mVendorInfo->mPskdString, kMaxPskdLength)));
mPskdVerified = true;
exit:
return error;
}
Error TcatAgent::HandlePresentPskcHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength)
{
Error error = kErrorNone;
Dataset::Info datasetInfo;
Pskc pskc;
VerifyOrExit(Get<ActiveDatasetManager>().Read(datasetInfo) == kErrorNone, error = kErrorSecurity);
VerifyOrExit(datasetInfo.IsPresent<Dataset::kPskc>(), error = kErrorSecurity);
pskc = datasetInfo.Get<Dataset::kPskc>();
SuccessOrExit(error = VerifyHash(aIncomingMessage, aOffset, aLength, pskc.m8, Pskc::kSize));
mPskcVerified = true;
exit:
return error;
}
Error TcatAgent::HandlePresentInstallCodeHash(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength)
{
Error error = kErrorNone;
VerifyOrExit(mVendorInfo->mInstallCode != nullptr, error = kErrorSecurity);
SuccessOrExit(error = VerifyHash(aIncomingMessage, aOffset, aLength, mVendorInfo->mInstallCode,
StringLength(mVendorInfo->mInstallCode, kInstallCodeMaxSize)));
mInstallCodeVerified = true;
exit:
return error;
}
Error TcatAgent::HandleRequestRandomNumberChallenge(Message &aOutgoingMessage, bool &aResponse)
{
Error error = kErrorNone;
SuccessOrExit(error = Random::Crypto::Fill(mRandomChallenge));
SuccessOrExit(
error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, &mRandomChallenge, sizeof(mRandomChallenge)));
aResponse = true;
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(StringLength(mVendorInfo->mPskdString, kMaxPskdLength) != 0, error = kErrorFailed);
VerifyOrExit(aLength == sizeof(providedChallenge), error = kErrorParse);
SuccessOrExit(error = aIncomingMessage.Read(aOffset, &providedChallenge, aLength));
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,
const void *aBuf,
size_t aBufLen)
{
Error error = kErrorNone;
Crypto::HmacSha256::Hash hash;
VerifyOrExit(aLength == Crypto::HmacSha256::Hash::kSize, error = kErrorSecurity);
VerifyOrExit(mRandomChallenge != 0, error = kErrorSecurity);
CalculateHash(mRandomChallenge, reinterpret_cast<const char *>(aBuf), aBufLen, hash);
VerifyOrExit(aIncomingMessage.Compare(aOffset, hash), error = kErrorSecurity);
exit:
return error;
}
void TcatAgent::CalculateHash(uint64_t aChallenge, const char *aBuf, size_t aBufLen, Crypto::HmacSha256::Hash &aHash)
{
const mbedtls_asn1_buf &rawKey = Get<Ble::BleSecure>().GetOwnPublicKey();
Crypto::Key cryptoKey;
Crypto::HmacSha256 hmac;
cryptoKey.Set(reinterpret_cast<const uint8_t *>(aBuf), static_cast<uint16_t>(aBufLen));
hmac.Start(cryptoKey);
hmac.Update(aChallenge);
hmac.Update(rawKey.p, static_cast<uint16_t>(rawKey.len));
hmac.Finish(aHash);
}
Error TcatAgent::HandleStartThreadInterface(void)
{
Error error;
+39 -8
View File
@@ -325,6 +325,14 @@ public:
*/
Error GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData);
/**
* @brief Gets the Install Code Verify Status during the current session.
*
* @retval TRUE The install code was correctly verified.
* @retval FALSE The install code was not verified.
*/
bool GetInstallCodeVerifyStatus(void) const { return mInstallCodeVerified; }
private:
Error Connected(MeshCoP::SecureTransport &aTlsContext);
void Disconnected(void);
@@ -336,23 +344,42 @@ private:
Message &aOutgoingMessage,
uint16_t aOffset,
uint16_t aLength,
bool &response);
Error HandleGetNetworkName(Message &aOutgoingMessage, bool &response);
Error HandleGetDeviceId(Message &aOutgoingMessage, bool &response);
Error HandleGetExtPanId(Message &aOutgoingMessage, bool &response);
Error HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response);
bool &aResponse);
Error HandleGetNetworkName(Message &aOutgoingMessage, bool &aResponse);
Error HandleGetDeviceId(Message &aOutgoingMessage, bool &aResponse);
Error HandleGetExtPanId(Message &aOutgoingMessage, bool &aResponse);
Error HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &aResponse);
Error HandlePresentPskdHash(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 HandleRequestRandomNumberChallenge(Message &aOutgoingMessage, bool &aResponse);
Error HandleRequestPskdHash(const Message &aIncomingMessage,
Message &aOutgoingMessage,
uint16_t aOffset,
uint16_t aLength,
bool &aResponse);
Error HandleStartThreadInterface(void);
bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,
CommandClassFlags aDeviceCommandClassFlags,
Dataset *aDataset) const;
Error VerifyHash(const Message &aIncomingMessage,
uint16_t aOffset,
uint16_t aLength,
const void *aBuf,
size_t aBufLen);
void CalculateHash(uint64_t aChallenge, const char *aBuf, size_t aBufLen, Crypto::HmacSha256::Hash &aHash);
bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,
CommandClassFlags aDeviceCommandClassFlags,
Dataset *aDataset) const;
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;
JoinerPskd mJoinerPskd;
const VendorInfo *mVendorInfo;
@@ -369,6 +396,10 @@ private:
bool mCommissionerHasNetworkName : 1;
bool mCommissionerHasDomainName : 1;
bool mCommissionerHasExtendedPanId : 1;
uint64_t mRandomChallenge;
bool mPskdVerified : 1;
bool mPskcVerified : 1;
bool mInstallCodeVerified : 1;
friend class Ble::BleSecure;
};
+15
View File
@@ -334,6 +334,13 @@ public:
return mTls.GetThreadAttributeFromOwnCertificate(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength);
}
/**
* Extracts public key from it's own certificate.
*
* @returns Public key from own certificate in form of entire ASN.1 field.
*/
const mbedtls_asn1_buf &GetOwnPublicKey(void) const { return mTls.GetOwnPublicKey(); }
/**
* Sets the authentication mode for the BLE secure connection. It disables or enables the verification
* of peer certificate.
@@ -419,6 +426,14 @@ public:
*/
Error HandleBleMtuUpdate(uint16_t aMtu);
/**
* @brief Gets the Install Code Verify Status during the current session.
*
* @return TRUE The install code was correctly verfied.
* @return FALSE The install code was not verified.
*/
bool GetInstallCodeVerifyStatus(void) const { return mTcatAgent.GetInstallCodeVerifyStatus(); }
private:
enum BleState : uint8_t
{
+31
View File
@@ -266,4 +266,35 @@ proc fail {message} {
error $message
}
proc spawn_tcat_client_for_node {id} {
global tcat_ids
global spawn_id
switch_node $id
send "tcat start\n"
expect_line "Done"
spawn python "tools/tcat_ble_client/bbtc.py" --simulation $id --cert_path "tools/tcat_ble_client/auth"
expect_line "Done"
set tcat_ids($id) $spawn_id
return $spawn_id
}
proc switch_tcat_client_for_node {id} {
global tcat_ids
global spawn_id
send_user "\n# ${id}\n"
set spawn_id $tcat_ids($id)
}
proc dispose_tcat_client {id} {
switch_tcat_client_for_node $id
send "exit\n"
expect eof
}
set timeout 10
@@ -31,7 +31,6 @@ source "tests/scripts/expect/_common.exp"
spawn_node 1 "cli"
switch_node 1
send "tcat advid ianapen f378\n"
expect_line "Done"
@@ -89,3 +88,5 @@ expect_line "Done"
send "tcat devid\n"
expect_line "Done"
dispose_all
@@ -31,13 +31,8 @@ source "tests/scripts/expect/_common.exp"
spawn_node 1 "cli"
switch_node 1
send "tcat start\n"
expect_line "Done"
spawn_tcat_client_for_node 1
spawn python "tools/tcat_ble_client/bbtc.py" --simulation 1 --cert_path "tools/tcat_ble_client/auth"
set py_client "$spawn_id"
expect_line "Done"
send "commission\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
@@ -49,8 +44,7 @@ expect_line "\tVALUE:\t0x00"
send "decommission\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
send "exit\n"
expect eof
dispose_tcat_client 1
switch_node 1
send "tcat stop\n"
@@ -62,3 +56,5 @@ expect_line "Error 23: NotFound"
send "networkkey\n"
expect_line "00000000000000000000000000000000"
expect_line "Done"
dispose_all
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/expect -f
#
# Copyright (c) 2024, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
source "tests/scripts/expect/_common.exp"
spawn_node 1 "cli"
spawn_tcat_client_for_node 1
send "commission\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
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 JJJJJJ\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
send "present_hash pskd AAAA\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x07"
send "present_hash install InstallCode\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
send "present_hash install Code\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x07"
send "present_hash pskc 5e9b9b360f80b88be2603fb0135c8d65\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
send "present_hash pskc aaaa\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x07"
dispose_tcat_client 1
switch_node 1
send "tcat stop\n"
expect_line "Done"
send "networkkey\n"
expect_line "fda7c771a27202e232ecd04cf934f476"
expect_line "Done"
dispose_all
+4 -9
View File
@@ -31,13 +31,7 @@ source "tests/scripts/expect/_common.exp"
spawn_node 1 "cli"
switch_node 1
send "tcat start\n"
expect_line "Done"
spawn python "tools/tcat_ble_client/bbtc.py" --simulation 1 --cert_path "tools/tcat_ble_client/auth"
set py_client "$spawn_id"
expect_line "Done"
spawn_tcat_client_for_node 1
send "network_name\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
@@ -83,8 +77,7 @@ expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
expect_line "\tLEN:\t9"
expect_line "\tVALUE:\t0x64756d6d795f75726c"
send "exit\n"
expect eof
dispose_tcat_client 1
switch_node 1
send "tcat stop\n"
@@ -96,3 +89,5 @@ expect_line "Done"
wait_for "state" "leader"
expect_line "Done"
dispose_all
+2 -4
View File
@@ -87,12 +87,10 @@ async def main():
print('Setting up secure TLS channel..', end='')
try:
await ble_sstream.do_handshake()
print('\nDone')
ble_sstream.log_cert_identities()
print('Done')
except Exception as e:
print('\nFailed')
print('Failed')
logger.error(e)
ble_sstream.log_cert_identities()
quit_with_reason('TLS handshake failure')
ds = ThreadDataset()
@@ -32,6 +32,8 @@ import ssl
import sys
import logging
from cryptography.x509 import load_der_x509_certificate
from cryptography.hazmat.primitives.serialization import (Encoding, PublicFormat)
from tlv.tlv import TLV
from tlv.tcat_tlv import TcatTLVType
from time import time
@@ -49,6 +51,8 @@ class BleStreamSecure:
self.outgoing = ssl.MemoryBIO()
self.ssl_object = None
self.cert = ''
self.peer_challenge = None
self._peer_public_key = None
def load_cert(self, certfile='', keyfile='', cafile=''):
if certfile and keyfile:
@@ -102,6 +106,11 @@ class BleStreamSecure:
else:
print('TLS Connection timed out.')
return False
print('')
cert = self.ssl_object.getpeercert(True)
cert_obj = load_der_x509_certificate(cert)
self._peer_public_key = cert_obj.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
self.log_cert_identities()
return True
async def send(self, bytes):
@@ -142,8 +151,22 @@ class BleStreamSecure:
if self.ssl_object.session is not None:
logger.debug('sending Disconnect command TLV')
data = TLV(TcatTLVType.DISCONNECT.value, bytes()).to_bytes()
self.peer_challenge = None
self._peer_public_key = None
await self.send(data)
@property
def peer_public_key(self):
return self._peer_public_key
@property
def peer_challenge(self):
return self._peer_challenge
@peer_challenge.setter
def peer_challenge(self, value):
self._peer_challenge = value
def log_cert_identities(self):
# using the internal object of the ssl library is necessary to see the cert data in
# case of handshake failure - see https://sethmlarson.dev/experimental-python-3.10-apis-and-trust-stores
+131 -16
View File
@@ -40,6 +40,9 @@ from utils import select_device_by_user_input
from os import path
from time import time
from secrets import token_bytes
from hashlib import sha256
import hmac
import binascii
class HelpCommand(Command):
@@ -55,6 +58,10 @@ class HelpCommand(Command):
return CommandResultNone()
class DataNotPrepared(Exception):
pass
class BleCommand(Command):
@abstractmethod
@@ -62,19 +69,22 @@ class BleCommand(Command):
pass
@abstractmethod
def prepare_data(self, context):
def prepare_data(self, args, context):
pass
async def execute_default(self, args, context):
bless: BleStreamSecure = context['ble_sstream']
print(self.get_log_string())
data = self.prepare_data(context)
response = await bless.send_with_resp(data)
if not response:
return
tlv_response = TLV.from_bytes(response)
return CommandResultTLV(tlv_response)
try:
data = self.prepare_data(args, context)
response = await bless.send_with_resp(data)
if not response:
return
tlv_response = TLV.from_bytes(response)
return CommandResultTLV(tlv_response)
except DataNotPrepared as err:
print('Command failed', err)
class HelloCommand(BleCommand):
@@ -85,7 +95,7 @@ class HelloCommand(BleCommand):
def get_help_string(self) -> str:
return 'Send round trip "Hello world!" message.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.APPLICATION.value, bytes('Hello world!', 'ascii')).to_bytes()
@@ -97,7 +107,7 @@ class CommissionCommand(BleCommand):
def get_help_string(self) -> str:
return 'Update the connected device with current dataset.'
def prepare_data(self, context):
def prepare_data(self, args, context):
dataset: ThreadDataset = context['dataset']
dataset_bytes = dataset.to_bytes()
return TLV(TcatTLVType.ACTIVE_DATASET.value, dataset_bytes).to_bytes()
@@ -111,7 +121,7 @@ class DecommissionCommand(BleCommand):
def get_help_string(self) -> str:
return 'Stop Thread interface and decommission device from current network.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.DECOMMISSION.value, bytes()).to_bytes()
@@ -123,7 +133,7 @@ class GetDeviceIdCommand(BleCommand):
def get_help_string(self) -> str:
return 'Get unique identifier for the TCAT device.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.GET_DEVICE_ID.value, bytes()).to_bytes()
@@ -135,7 +145,7 @@ class GetExtPanIDCommand(BleCommand):
def get_help_string(self) -> str:
return 'Get extended PAN ID that is commissioned in the active dataset.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.GET_EXT_PAN_ID.value, bytes()).to_bytes()
@@ -147,7 +157,7 @@ class GetProvisioningUrlCommand(BleCommand):
def get_help_string(self) -> str:
return 'Get a URL for an application suited to commission the TCAT device.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.GET_PROVISIONING_URL.value, bytes()).to_bytes()
@@ -159,10 +169,115 @@ class GetNetworkNameCommand(BleCommand):
def get_help_string(self) -> str:
return 'Get the Thread network name that is commissioned in the active dataset.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes()
class PresentHash(BleCommand):
def get_log_string(self) -> str:
return 'Presenting hash.'
def get_help_string(self) -> str:
return 'Present calculated 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.")
hash = hmac.new(code, digestmod=sha256)
hash.update(bless.peer_challenge)
hash.update(bless.peer_public_key)
data = TLV(tlv_type, hash.digest()).to_bytes()
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)
class GetRandomNumberChallenge(Command):
def get_log_string(self) -> str:
return 'Retrieving random challenge.'
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']
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)
class PingCommand(Command):
def get_help_string(self) -> str:
@@ -202,7 +317,7 @@ class ThreadStartCommand(BleCommand):
def get_help_string(self) -> str:
return 'Enable thread interface.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.THREAD_START.value, bytes()).to_bytes()
@@ -214,7 +329,7 @@ class ThreadStopCommand(BleCommand):
def get_help_string(self) -> str:
return 'Disable thread interface.'
def prepare_data(self, context):
def prepare_data(self, args, context):
return TLV(TcatTLVType.THREAD_STOP.value, bytes()).to_bytes()
+5 -2
View File
@@ -30,8 +30,8 @@ import shlex
from argparse import ArgumentParser
from ble.ble_stream_secure import BleStreamSecure
from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, GetDeviceIdCommand,
GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand,
ThreadStateCommand, ScanCommand)
GetPskdHash, GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand,
PingCommand, GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash)
from cli.dataset_commands import (DatasetCommand)
from dataset.dataset import ThreadDataset
from typing import Optional
@@ -56,6 +56,9 @@ class CLI:
'dataset': DatasetCommand(),
'thread': ThreadStateCommand(),
'scan': ScanCommand(),
'random_challenge': GetRandomNumberChallenge(),
'present_hash': PresentHash(),
'peer_pskd_hash': GetPskdHash(),
}
self._context = {
'ble_sstream': ble_sstream,
+5
View File
@@ -37,6 +37,11 @@ class TcatTLVType(Enum):
GET_DEVICE_ID = 0x0B
GET_EXT_PAN_ID = 0x0C
GET_PROVISIONING_URL = 0x0D
PRESENT_PSKD_HASH = 0x10
PRESENT_PSKC_HASH = 0x11
PRESENT_INSTALL_CODE_HASH = 0x12
GET_RANDOM_NUMBER_CHALLENGE = 0x13
GET_PSKD_HASH = 0x14
ACTIVE_DATASET = 0x20
DECOMMISSION = 0x60
APPLICATION = 0x82