mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
[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`
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
//
|
||||
|
||||
@@ -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<Ble::BleSecure>().GetPeerCertificateBase64(aPeerCert, aCertLength, *aCertLength);
|
||||
|
||||
exit:
|
||||
@@ -96,6 +98,14 @@ exit:
|
||||
#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<Ble::BleSecure>().GetPeerCertificateDer(aPeerCert, aCertLength, *aCertLength);
|
||||
}
|
||||
|
||||
otError otBleSecureGetPeerSubjectAttributeByOid(otInstance *aInstance,
|
||||
const char *aOid,
|
||||
size_t aOidLength,
|
||||
|
||||
@@ -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<SettingsDriver>().Set(kKeyTcatCommrCert, aCert, aCertLen);
|
||||
|
||||
Log(kActionSave, error, kKeyTcatCommrCert);
|
||||
|
||||
SuccessOrAssert(error);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
Error Settings::AddChildInfo(const ChildInfo &aChildInfo)
|
||||
{
|
||||
|
||||
@@ -119,9 +119,10 @@ public:
|
||||
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<uint16_t>(kLastKey) < static_cast<uint16_t>(OT_SETTINGS_KEY_VENDOR_RESERVED_MIN),
|
||||
"Core settings keys overlap with vendor reserved keys");
|
||||
@@ -802,6 +803,33 @@ public:
|
||||
*/
|
||||
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<SettingsDriver>().Get(kKeyTcatCommrCert, aCert, &aCertLen);
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
|
||||
|
||||
/**
|
||||
* Reads a specified settings entry.
|
||||
*
|
||||
|
||||
@@ -1230,6 +1230,37 @@ exit:
|
||||
#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,
|
||||
|
||||
@@ -435,6 +435,19 @@ public:
|
||||
#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.
|
||||
|
||||
@@ -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<Ble::BleSecure>().GetPeerCertificateDer(buf, &bufLen, bufLen));
|
||||
Get<Settings>().SaveTcatCommissionerCertificate(buf, static_cast<uint16_t>(bufLen));
|
||||
|
||||
Get<ActiveDatasetManager>().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<ActiveDatasetManager>().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<Settings>().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<Ble::BleSecure>().GetPeerCertificateDer(buf, &bufLen, bufLen));
|
||||
Get<Settings>().SaveTcatCommissionerCertificate(buf, static_cast<uint16_t>(bufLen));
|
||||
|
||||
IgnoreReturnValue(otThreadSetEnabled(&GetInstance(), false));
|
||||
Get<ActiveDatasetManager>().Clear();
|
||||
Get<PendingDatasetManager>().Clear();
|
||||
|
||||
error = Get<Instance>().ErasePersistentInfo();
|
||||
IgnoreReturnValue(Get<Instance>().ErasePersistentInfo());
|
||||
|
||||
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
|
||||
{
|
||||
@@ -545,6 +615,7 @@ Error TcatAgent::HandleDecomission(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@@ -339,6 +339,7 @@ private:
|
||||
|
||||
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 @@ private:
|
||||
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 @@ private:
|
||||
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;
|
||||
|
||||
@@ -271,7 +271,7 @@ public:
|
||||
/**
|
||||
* @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(); }
|
||||
|
||||
@@ -200,7 +200,11 @@ private:
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -44,6 +44,8 @@ from hashlib import sha256
|
||||
import hmac
|
||||
import binascii
|
||||
|
||||
CHALLENGE_SIZE = 8
|
||||
|
||||
|
||||
class HelpCommand(Command):
|
||||
|
||||
@@ -85,11 +87,15 @@ class BleCommand(Command):
|
||||
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 @@ class DecommissionCommand(BleCommand):
|
||||
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,6 +228,89 @@ class GetNetworkNameCommand(BleCommand):
|
||||
return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes()
|
||||
|
||||
|
||||
class GetPskdHash(BleCommand):
|
||||
|
||||
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):
|
||||
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):
|
||||
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:
|
||||
return 'Retrieving random challenge.'
|
||||
|
||||
def get_help_string(self) -> str:
|
||||
return 'Get the device random number challenge.'
|
||||
|
||||
def prepare_data(self, args, context):
|
||||
return TLV(TcatTLVType.GET_RANDOM_NUMBER_CHALLENGE.value, bytes()).to_bytes()
|
||||
|
||||
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):
|
||||
|
||||
def get_help_string(self) -> str:
|
||||
return 'Send echo request to TCAT device.'
|
||||
|
||||
async def execute_default(self, args, context):
|
||||
bless: BleStreamSecure = context['ble_sstream']
|
||||
payload_size = 10
|
||||
max_payload = 512
|
||||
if len(args) > 0:
|
||||
payload_size = int(args[0])
|
||||
if payload_size > max_payload:
|
||||
print(f'Payload size too large. Maximum supported value is {max_payload}')
|
||||
return CommandResultNone()
|
||||
to_send = token_bytes(payload_size)
|
||||
data = TLV(TcatTLVType.PING.value, to_send).to_bytes()
|
||||
elapsed_time = time()
|
||||
response = await bless.send_with_resp(data)
|
||||
elapsed_time = 1e3 * (time() - elapsed_time)
|
||||
if not response:
|
||||
return CommandResultNone()
|
||||
|
||||
tlv_response = TLV.from_bytes(response)
|
||||
if tlv_response.value != to_send:
|
||||
print("Received malformed response.")
|
||||
|
||||
print(f"Roundtrip time: {elapsed_time} ms")
|
||||
|
||||
return CommandResultTLV(tlv_response)
|
||||
|
||||
|
||||
class PresentHash(BleCommand):
|
||||
|
||||
def get_log_string(self) -> str:
|
||||
@@ -215,141 +349,6 @@ class PresentHash(BleCommand):
|
||||
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:
|
||||
return 'Send echo request to TCAT device.'
|
||||
|
||||
async def execute_default(self, args, context):
|
||||
bless: BleStreamSecure = context['ble_sstream']
|
||||
payload_size = 10
|
||||
max_payload = 512
|
||||
if len(args) > 0:
|
||||
payload_size = int(args[0])
|
||||
if payload_size > max_payload:
|
||||
print(f'Payload size too large. Maximum supported value is {max_payload}')
|
||||
return
|
||||
to_send = token_bytes(payload_size)
|
||||
data = TLV(TcatTLVType.PING.value, to_send).to_bytes()
|
||||
elapsed_time = time()
|
||||
response = await bless.send_with_resp(data)
|
||||
elapsed_time = 1e3 * (time() - elapsed_time)
|
||||
if not response:
|
||||
return CommandResultNone()
|
||||
|
||||
tlv_response = TLV.from_bytes(response)
|
||||
if tlv_response.value != to_send:
|
||||
print("Received malformed response.")
|
||||
|
||||
print(f"Roundtrip time: {elapsed_time} ms")
|
||||
|
||||
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):
|
||||
|
||||
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):
|
||||
print('Invalid usage. Provide a subcommand.')
|
||||
return CommandResultNone()
|
||||
|
||||
|
||||
class ScanCommand(Command):
|
||||
|
||||
def get_help_string(self) -> str:
|
||||
@@ -387,14 +386,38 @@ class ScanCommand(Command):
|
||||
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()
|
||||
|
||||
@@ -30,9 +30,9 @@ import shlex
|
||||
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 @@ class CLI:
|
||||
'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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user