mirror of
https://github.com/espressif/openthread.git
synced 2026-08-09 12:17:47 +00:00
Updates the TCAT class public methods for doing Commissioner authorization checks and clarifies the code, with minor updates to PSKc cases handling. Unit tests are added for checking Commissioner authorization. To do these checks, a new test class UnitTester is added which has access to private members of the TcatAgent class. Validation/mock functions are added in the test code to keep the unit tests readable. Also reverts the CommCert4 fix that was made in #12151. For more background information see JIRA BHC-766.
This commit is contained in:
@@ -251,58 +251,50 @@ uint8_t TcatAgent::CheckAuthorizationRequirements(CommandClassFlags aFlagsRequir
|
||||
{
|
||||
if (aFlagsRequired & flag)
|
||||
{
|
||||
bool authorized = false;
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case kPskdFlag:
|
||||
if (mPskdVerified)
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
authorized = mPskdVerified;
|
||||
break;
|
||||
|
||||
case kNetworkNameFlag:
|
||||
if (aDatasetInfo != nullptr && mCommissionerHasNetworkName &&
|
||||
aDatasetInfo->IsPresent<Dataset::kNetworkName>() &&
|
||||
(aDatasetInfo->Get<Dataset::kNetworkName>() == mCommissionerNetworkName))
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
authorized = mCommissionerHasNetworkName && aDatasetInfo != nullptr &&
|
||||
aDatasetInfo->IsPresent<Dataset::kNetworkName>() &&
|
||||
aDatasetInfo->Get<Dataset::kNetworkName>() == mCommissionerNetworkName;
|
||||
break;
|
||||
|
||||
case kExtendedPanIdFlag:
|
||||
if (aDatasetInfo != nullptr && mCommissionerHasExtendedPanId &&
|
||||
aDatasetInfo->IsPresent<Dataset::kExtendedPanId>() &&
|
||||
(aDatasetInfo->Get<Dataset::kExtendedPanId>() == mCommissionerExtendedPanId))
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
authorized = mCommissionerHasExtendedPanId && aDatasetInfo != nullptr &&
|
||||
aDatasetInfo->IsPresent<Dataset::kExtendedPanId>() &&
|
||||
aDatasetInfo->Get<Dataset::kExtendedPanId>() == mCommissionerExtendedPanId;
|
||||
break;
|
||||
|
||||
case kThreadDomainFlag:
|
||||
|
||||
if (mCommissionerHasDomainName)
|
||||
{
|
||||
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4)
|
||||
if (Get<MeshCoP::NetworkIdentity>().GetDomainName() == mCommissionerDomainName)
|
||||
authorized = Get<MeshCoP::NetworkIdentity>().GetDomainName() == mCommissionerDomainName;
|
||||
#else
|
||||
if (StringMatch(mCommissionerDomainName.GetAsCString(), NetworkIdentity::kDefaultDomainName))
|
||||
authorized =
|
||||
StringMatch(mCommissionerDomainName.GetAsCString(), NetworkIdentity::kDefaultDomainName);
|
||||
#endif
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kPskcFlag:
|
||||
if (mPskcVerified)
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
authorized = mPskcVerified;
|
||||
break;
|
||||
|
||||
default:
|
||||
LogCrit("Error in access flags. Unexpected flag %d", flag);
|
||||
OT_ASSERT(false); // Should not get here
|
||||
// A requirement for an unknown/future flag will always fail (see spec).
|
||||
break;
|
||||
}
|
||||
|
||||
if (authorized)
|
||||
{
|
||||
res |= flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,40 +302,36 @@ uint8_t TcatAgent::CheckAuthorizationRequirements(CommandClassFlags aFlagsRequir
|
||||
return res;
|
||||
}
|
||||
|
||||
bool TcatAgent::CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,
|
||||
CommandClassFlags aDeviceCommandClassFlags,
|
||||
Dataset *aDataset) const
|
||||
bool TcatAgent::IsCommandClassAuthorizedWithFlags(CommandClassFlags aCommissionerCommandClassFlags,
|
||||
CommandClassFlags aDeviceCommandClassFlags,
|
||||
const Dataset *aCommSuppliedDataset) const
|
||||
{
|
||||
bool authorized = false;
|
||||
uint8_t deviceRequirementMet;
|
||||
uint8_t commissionerRequirementMet;
|
||||
Dataset::Info datasetInfo;
|
||||
Error datasetError = kErrorNone;
|
||||
bool authorized = false;
|
||||
uint8_t deviceRequirementMet;
|
||||
uint8_t commissionerRequirementMet;
|
||||
Dataset::Info datasetInfo;
|
||||
Dataset::Info *datasetInfoPtr = nullptr;
|
||||
|
||||
VerifyOrExit(IsConnected());
|
||||
|
||||
if (aDataset == nullptr)
|
||||
datasetInfo.Clear();
|
||||
if (aCommSuppliedDataset != nullptr)
|
||||
{
|
||||
datasetError = Get<ActiveDatasetManager>().Read(datasetInfo);
|
||||
aCommSuppliedDataset->ConvertTo(datasetInfo);
|
||||
datasetInfoPtr = &datasetInfo;
|
||||
}
|
||||
else
|
||||
else if (Get<ActiveDatasetManager>().Read(datasetInfo) == kErrorNone)
|
||||
{
|
||||
aDataset->ConvertTo(datasetInfo);
|
||||
datasetInfoPtr = &datasetInfo;
|
||||
}
|
||||
|
||||
if (datasetError == kErrorNone)
|
||||
{
|
||||
deviceRequirementMet = CheckAuthorizationRequirements(aDeviceCommandClassFlags, &datasetInfo);
|
||||
commissionerRequirementMet = CheckAuthorizationRequirements(aCommissionerCommandClassFlags, &datasetInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceRequirementMet = CheckAuthorizationRequirements(aDeviceCommandClassFlags, nullptr);
|
||||
commissionerRequirementMet = CheckAuthorizationRequirements(aCommissionerCommandClassFlags, nullptr);
|
||||
}
|
||||
deviceRequirementMet = CheckAuthorizationRequirements(aDeviceCommandClassFlags, datasetInfoPtr);
|
||||
commissionerRequirementMet = CheckAuthorizationRequirements(aCommissionerCommandClassFlags, datasetInfoPtr);
|
||||
|
||||
if (aDataset != nullptr) // For set active operational dataset TLV the PSKc check is always successful
|
||||
if (aCommSuppliedDataset != nullptr)
|
||||
{
|
||||
// If Commissioner supplies a dataset (only for Set Active Operational Dataset TLV commands),
|
||||
// then the PSKc check is always considered successful. Table 13-9, bit 5.
|
||||
deviceRequirementMet |= kPskcFlag;
|
||||
commissionerRequirementMet |= (aCommissionerCommandClassFlags & kPskcFlag);
|
||||
}
|
||||
@@ -359,6 +347,8 @@ bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const
|
||||
{
|
||||
bool authorized = false;
|
||||
|
||||
VerifyOrExit(IsConnected());
|
||||
|
||||
switch (aCommandClass)
|
||||
{
|
||||
case kGeneral:
|
||||
@@ -366,33 +356,40 @@ bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const
|
||||
break;
|
||||
|
||||
case kCommissioning:
|
||||
authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags,
|
||||
mDeviceAuthorizationField.mCommissioningFlags, nullptr);
|
||||
authorized = IsCommandClassAuthorizedWithFlags(mCommissionerAuthorizationField.mCommissioningFlags,
|
||||
mDeviceAuthorizationField.mCommissioningFlags, nullptr);
|
||||
break;
|
||||
|
||||
case kExtraction:
|
||||
authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mExtractionFlags,
|
||||
mDeviceAuthorizationField.mExtractionFlags, nullptr);
|
||||
authorized = IsCommandClassAuthorizedWithFlags(mCommissionerAuthorizationField.mExtractionFlags,
|
||||
mDeviceAuthorizationField.mExtractionFlags, nullptr);
|
||||
break;
|
||||
|
||||
case kDecommissioning:
|
||||
authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mDecommissioningFlags,
|
||||
mDeviceAuthorizationField.mDecommissioningFlags, nullptr);
|
||||
authorized = IsCommandClassAuthorizedWithFlags(mCommissionerAuthorizationField.mDecommissioningFlags,
|
||||
mDeviceAuthorizationField.mDecommissioningFlags, nullptr);
|
||||
break;
|
||||
|
||||
case kApplication:
|
||||
authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags,
|
||||
mDeviceAuthorizationField.mApplicationFlags, nullptr);
|
||||
authorized = IsCommandClassAuthorizedWithFlags(mCommissionerAuthorizationField.mApplicationFlags,
|
||||
mDeviceAuthorizationField.mApplicationFlags, nullptr);
|
||||
break;
|
||||
|
||||
case kInvalid:
|
||||
authorized = false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit:
|
||||
return authorized;
|
||||
}
|
||||
|
||||
bool TcatAgent::IsSetActiveDatasetAuthorized(const Dataset *aDataset) const
|
||||
{
|
||||
return !mIsCommissioned &&
|
||||
IsCommandClassAuthorizedWithFlags(mCommissionerAuthorizationField.mCommissioningFlags,
|
||||
mDeviceAuthorizationField.mCommissioningFlags, aDataset);
|
||||
}
|
||||
|
||||
Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage)
|
||||
{
|
||||
Error error;
|
||||
@@ -587,12 +584,7 @@ Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessa
|
||||
SuccessOrExit(error = dataset.ValidateTlvs());
|
||||
VerifyOrExit(dataset.ContainsTlv(Tlv::kNetworkKey), error = kErrorInvalidArgs);
|
||||
|
||||
if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags,
|
||||
mDeviceAuthorizationField.mCommissioningFlags, &dataset))
|
||||
{
|
||||
error = kErrorRejected;
|
||||
ExitNow();
|
||||
}
|
||||
VerifyOrExit(IsSetActiveDatasetAuthorized(&dataset), error = kErrorRejected);
|
||||
|
||||
SuccessOrExit(error = Get<Ble::BleSecure>().GetPeerCertificateDer(buf, &bufLen, bufLen));
|
||||
Get<Settings>().SaveTcatCommissionerCertificate(buf, static_cast<uint16_t>(bufLen));
|
||||
@@ -645,12 +637,7 @@ Error TcatAgent::HandleGetDiagnosticTlvs(const Message &aIncomingMessage,
|
||||
uint16_t initialLength;
|
||||
uint16_t length;
|
||||
|
||||
if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags,
|
||||
mDeviceAuthorizationField.mCommissioningFlags, nullptr))
|
||||
{
|
||||
error = kErrorRejected;
|
||||
ExitNow();
|
||||
}
|
||||
VerifyOrExit(IsCommandClassAuthorized(kCommissioning), error = kErrorRejected);
|
||||
|
||||
offsetRange.Init(aOffset, aLength);
|
||||
initialLength = aOutgoingMessage.GetLength();
|
||||
@@ -1196,7 +1183,7 @@ void SerializeTcatAdvertisementTlv(uint8_t *aBuffer,
|
||||
aOffset += aLength;
|
||||
}
|
||||
|
||||
Error TcatAgent::GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData)
|
||||
Error TcatAgent::GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData) const
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
DeviceTypeAndStatus tas;
|
||||
|
||||
@@ -63,9 +63,12 @@ class BleSecure;
|
||||
|
||||
namespace MeshCoP {
|
||||
|
||||
class UnitTester;
|
||||
|
||||
class TcatAgent : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
friend class Ble::BleSecure;
|
||||
friend class UnitTester;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -377,15 +380,29 @@ public:
|
||||
bool IsConnected(void) const { return mState == kStateConnected; }
|
||||
|
||||
/**
|
||||
* Indicates whether or not a TCAT command class is authorized for use.
|
||||
* Indicates if a TCAT command class is currently authorized for use by the active TCAT Commissioner.
|
||||
*
|
||||
* @note For Set Active Operational Dataset commands, authorization must be checked separately
|
||||
* using #IsSetActiveDatasetAuthorized() because it uses different rules.
|
||||
*
|
||||
* @param[in] aCommandClass Command class to subject to authorization check.
|
||||
*
|
||||
* @retval TRUE The command class is authorized for use by the present (if any) TCAT commissioner.
|
||||
* @retval FALSE The command class is not authorized for use.
|
||||
* @retval TRUE The command class is authorized for use by the present TCAT commissioner (if any).
|
||||
* @retval FALSE The command class is currently not authorized for use.
|
||||
*/
|
||||
bool IsCommandClassAuthorized(CommandClass aCommandClass) const;
|
||||
|
||||
/**
|
||||
* Indicates if Set Active Dataset commands in the Commissioning command class are currently
|
||||
* authorized for use by the active TCAT Commissioner for setting the @p aDataset.
|
||||
*
|
||||
* @param aDataset The specific Active Operational Dataset intended to be written.
|
||||
*
|
||||
* @retval TRUE The command for writing @p aDataset is currently authorized.
|
||||
* @retval FALSE The command for writing @p aDataset is currently not authorized.
|
||||
*/
|
||||
bool IsSetActiveDatasetAuthorized(const Dataset *aDataset) const;
|
||||
|
||||
/**
|
||||
* Gets TCAT advertisement data from the TCAT agent.
|
||||
*
|
||||
@@ -395,7 +412,7 @@ public:
|
||||
* @retval kErrorNone Successfully retrieved the TCAT advertisement data.
|
||||
* @retval kErrorInvalidArgs The vendor data could not be retrieved, or aAdvertisementData is null.
|
||||
*/
|
||||
Error GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData);
|
||||
Error GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData) const;
|
||||
|
||||
/**
|
||||
* @brief Gets the Install Code Verify Status of the current TCAT Commissioner session.
|
||||
@@ -468,10 +485,10 @@ private:
|
||||
size_t aBufLen);
|
||||
Error CalculateHash(uint64_t aChallenge, const char *aBuf, size_t aBufLen, Crypto::HmacSha256::Hash &aHash);
|
||||
|
||||
bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,
|
||||
CommandClassFlags aDeviceCommandClassFlags,
|
||||
Dataset *aDataset) const;
|
||||
uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aDatasetInfo) const;
|
||||
bool IsCommandClassAuthorizedWithFlags(CommandClassFlags aCommissionerCommandClassFlags,
|
||||
CommandClassFlags aDeviceCommandClassFlags,
|
||||
const Dataset *aCommSuppliedDataset) const;
|
||||
uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aActiveDatasetInfo) const;
|
||||
|
||||
static constexpr uint16_t kPingPayloadMaxLength = 512;
|
||||
static constexpr uint16_t kProvisioningUrlMaxLength = OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH;
|
||||
|
||||
+640
-25
@@ -35,6 +35,8 @@
|
||||
|
||||
#include <openthread/ble_secure.h>
|
||||
|
||||
#include "cli/cli_dataset.hpp"
|
||||
|
||||
#define OT_TCAT_X509_CERT \
|
||||
"-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIB6TCCAZCgAwIBAgICNekwCgYIKoZIzj0EAwIwcTEmMCQGA1UEAwwdVGhyZWFk\n" \
|
||||
@@ -73,8 +75,118 @@
|
||||
"eVFOwC8bd//D99KiHAIgU84kwFHIyDvFqu6y+u1hFqBGsiuTmKwZ2PHhVe/xK1k=\n" \
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
namespace ot {
|
||||
#define COMM_NETWORK_NAME "OpenThread-c64e"
|
||||
#define COMM_XPAN_ID {0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xfe}
|
||||
#define COMM_XPAN_ID_ALT {0xef, 0x13, 0x98, 0xc2, 0xfd, 0x50, 0x4b, 0x67}
|
||||
|
||||
namespace ot {
|
||||
namespace MeshCoP {
|
||||
|
||||
static constexpr char kPskdVendor[] = "J01NM3";
|
||||
static constexpr char kUrl[] = "dummy_url";
|
||||
static constexpr char kDomainName[] = "DefaultDomain";
|
||||
static constexpr char kNetworkName[] = COMM_NETWORK_NAME;
|
||||
static constexpr char kWrongName[] = "WrongName";
|
||||
static const uint8_t kExtPanId[8] = COMM_XPAN_ID;
|
||||
static const uint8_t kExtPanIdAlt[8] = COMM_XPAN_ID_ALT;
|
||||
static constexpr uint16_t kConnectionId = 0;
|
||||
static constexpr int kCertificateThreadVersion = 2;
|
||||
static constexpr int kCertificateAuthorizationField = 3;
|
||||
|
||||
static constexpr otTcatVendorInfo vendorInfo = {.mProvisioningUrl = kUrl, .mPskdString = kPskdVendor};
|
||||
|
||||
// TCAT command class bits for expressing any combination of classes in tests
|
||||
static constexpr uint16_t kClassNone = 0;
|
||||
static constexpr uint16_t kClassGeneral = 1 << TcatAgent::kGeneral;
|
||||
static constexpr uint16_t kClassCommissioning = 1 << TcatAgent::kCommissioning;
|
||||
static constexpr uint16_t kClassExtraction = 1 << TcatAgent::kExtraction;
|
||||
static constexpr uint16_t kClassDecommissioning = 1 << TcatAgent::kDecommissioning;
|
||||
static constexpr uint16_t kClassApplication = 1 << TcatAgent::kApplication;
|
||||
|
||||
// TCAT authorization fields
|
||||
static const uint8_t kDeviceCert1AuthField[5] = {0x20, 0x01, 0x01, 0x01, 0x01};
|
||||
static const uint8_t kDeviceCert2AuthField[5] = {0x20, 0x02, 0x03, 0x04, 0x24};
|
||||
static const uint8_t kCommCert1AuthField[5] = {0x21, 0x01, 0x01, 0x01, 0x01};
|
||||
static const uint8_t kCommCert2AuthField[5] = {0x21, 0x1F, 0x3F, 0x3F, 0x3F};
|
||||
static const uint8_t kCommCert4AuthField[5] = {0x21, 0x21, 0x05, 0x09, 0x11};
|
||||
static const uint8_t kCommCert5AuthField[5] = {0x21, 0x03, 0x02, 0x83, 0x41};
|
||||
|
||||
static const otOperationalDataset kFullDataset = {
|
||||
.mActiveTimestamp =
|
||||
{
|
||||
.mSeconds = 1,
|
||||
.mTicks = 0,
|
||||
.mAuthoritative = false,
|
||||
},
|
||||
.mNetworkKey =
|
||||
{
|
||||
.m8 = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
||||
},
|
||||
.mNetworkName = {COMM_NETWORK_NAME},
|
||||
.mExtendedPanId =
|
||||
{
|
||||
.m8 = COMM_XPAN_ID,
|
||||
},
|
||||
.mMeshLocalPrefix =
|
||||
{
|
||||
.m8 = {0xfd, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
||||
},
|
||||
.mPanId = 0x1234,
|
||||
.mChannel = 11,
|
||||
.mPskc =
|
||||
{
|
||||
.m8 = {0xc2, 0x3a, 0x76, 0xe9, 0x8f, 0x1a, 0x64, 0x83, 0x63, 0x9b, 0x1a, 0xc1, 0x27, 0x1e, 0x2e, 0x27},
|
||||
},
|
||||
.mSecurityPolicy =
|
||||
{
|
||||
.mRotationTime = 672,
|
||||
.mObtainNetworkKeyEnabled = true,
|
||||
.mNativeCommissioningEnabled = true,
|
||||
.mRoutersEnabled = true,
|
||||
.mExternalCommissioningEnabled = true,
|
||||
},
|
||||
.mChannelMask = 0x07fff800,
|
||||
.mComponents =
|
||||
{
|
||||
.mIsActiveTimestampPresent = true,
|
||||
.mIsNetworkKeyPresent = true,
|
||||
.mIsNetworkNamePresent = true,
|
||||
.mIsExtendedPanIdPresent = true,
|
||||
.mIsMeshLocalPrefixPresent = true,
|
||||
.mIsPanIdPresent = true,
|
||||
.mIsChannelPresent = true,
|
||||
.mIsPskcPresent = true,
|
||||
.mIsSecurityPolicyPresent = true,
|
||||
.mIsChannelMaskPresent = true,
|
||||
},
|
||||
};
|
||||
|
||||
static const otOperationalDataset kPartialDataset = {
|
||||
.mNetworkKey =
|
||||
{
|
||||
.m8 = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
||||
},
|
||||
.mComponents =
|
||||
{
|
||||
.mIsActiveTimestampPresent = false,
|
||||
.mIsNetworkKeyPresent = true,
|
||||
.mIsNetworkNamePresent = false,
|
||||
.mIsExtendedPanIdPresent = false,
|
||||
.mIsMeshLocalPrefixPresent = false,
|
||||
.mIsPanIdPresent = false,
|
||||
.mIsChannelPresent = false,
|
||||
.mIsPskcPresent = false,
|
||||
.mIsSecurityPolicyPresent = false,
|
||||
.mIsChannelMaskPresent = false,
|
||||
},
|
||||
};
|
||||
|
||||
static Dataset::Info sFullDataset, sPartialDataset;
|
||||
static NetworkName sCommNetworkName, sCommDomainName;
|
||||
static ExtendedPanId sCommExtPanId;
|
||||
static TcatAgent::CertificateAuthorizationField sCommAuth, sDeviceAuth;
|
||||
|
||||
// Helper class to test BLE connection state.
|
||||
class TestBleSecure
|
||||
{
|
||||
public:
|
||||
@@ -105,32 +217,68 @@ static void HandleBleSecureConnect(otInstance *aInstance, bool aConnected, bool
|
||||
static_cast<TestBleSecure *>(aContext)->HandleBleSecureConnect(aConnected, aBleConnectionOpen);
|
||||
}
|
||||
|
||||
void TestTcat(void)
|
||||
// test helper to validate that only classes set '1' in aCommandClassesBitmap are authorized and others not.
|
||||
static bool CommandClassesAuthorized(const TcatAgent *aAgent, const uint16_t aCommandClassesBitmap)
|
||||
{
|
||||
const char kPskdVendor[] = "J01NM3";
|
||||
const char kUrl[] = "dummy_url";
|
||||
constexpr uint16_t kConnectionId = 0;
|
||||
const int kCertificateThreadVersion = 2;
|
||||
const int kCertificateAuthorizationField = 3;
|
||||
const uint8_t expectedTcatAuthField[5] = {0x20, 0x01, 0x01, 0x01, 0x01};
|
||||
uint8_t attributeBuffer[8];
|
||||
size_t attributeLen;
|
||||
bool validationResult = true;
|
||||
|
||||
TestBleSecure ble;
|
||||
Instance *instance = testInitInstance();
|
||||
static_assert(TcatAgent::kInvalid < 16, "kInvalid must be less than 16 to fit in uint16_t");
|
||||
for (uint16_t i = TcatAgent::kGeneral; i <= TcatAgent::kInvalid; i++)
|
||||
{
|
||||
const bool isAuthorizedByAgent = aAgent->IsCommandClassAuthorized(static_cast<TcatAgent::CommandClass>(i));
|
||||
const bool isAuthorizationExpected = (aCommandClassesBitmap & (1 << i)) != 0;
|
||||
if (isAuthorizedByAgent != isAuthorizationExpected)
|
||||
{
|
||||
printf("Expected command class %d authorization '%d', but TCAT Agent reports '%d'\n", i,
|
||||
isAuthorizationExpected, isAuthorizedByAgent);
|
||||
validationResult = false;
|
||||
}
|
||||
}
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
otTcatVendorInfo vendorInfo = {.mProvisioningUrl = kUrl, .mPskdString = kPskdVendor};
|
||||
// test helper to validate if Set Active Dataset commands are authorized or not, given the dataset to write.
|
||||
static bool SetActiveDatasetAuthorized(const TcatAgent *aAgent, const Dataset::Info &aDatasetInfo)
|
||||
{
|
||||
Dataset dataset;
|
||||
|
||||
// Convert high-level Dataset::Info into TLVs representation required by IsSetActiveDatasetAuthorized()
|
||||
VerifyOrQuit(dataset.WriteTlvsFrom(aDatasetInfo) == kErrorNone);
|
||||
return aAgent->IsSetActiveDatasetAuthorized(&dataset);
|
||||
}
|
||||
|
||||
static Instance *TestInitInstanceTcat(void)
|
||||
{
|
||||
Instance *instance = testInitInstance();
|
||||
|
||||
otBleSecureSetCertificate(instance, reinterpret_cast<const uint8_t *>(OT_TCAT_X509_CERT), sizeof(OT_TCAT_X509_CERT),
|
||||
reinterpret_cast<const uint8_t *>(OT_TCAT_PRIV_KEY), sizeof(OT_TCAT_PRIV_KEY));
|
||||
|
||||
otBleSecureSetCaCertificateChain(instance, reinterpret_cast<const uint8_t *>(OT_TCAT_TRUSTED_ROOT_CERTIFICATE),
|
||||
sizeof(OT_TCAT_TRUSTED_ROOT_CERTIFICATE));
|
||||
|
||||
otBleSecureSetSslAuthMode(instance, true);
|
||||
|
||||
// Validate BLE secure and Tcat start APIs
|
||||
SuccessOrQuit(otBleSecureSetTcatVendorInfo(instance, &vendorInfo));
|
||||
|
||||
// reset default data items used across tests
|
||||
sFullDataset = AsCoreType(&kFullDataset);
|
||||
sPartialDataset = AsCoreType(&kPartialDataset);
|
||||
sCommNetworkName.Set(kNetworkName);
|
||||
sCommDomainName.Set(kDomainName);
|
||||
memcpy(&sCommExtPanId, &kExtPanId, sizeof(sCommExtPanId));
|
||||
memcpy(&sCommAuth, &kCommCert1AuthField, sizeof(sCommAuth));
|
||||
memcpy(&sDeviceAuth, &kDeviceCert1AuthField, sizeof(sDeviceAuth));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void TestTcatConnectionAndCertAttributes(void)
|
||||
{
|
||||
uint8_t attributeBuffer[8];
|
||||
size_t attributeLen;
|
||||
TestBleSecure ble;
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
|
||||
// Validate BLE secure and Tcat start APIs
|
||||
VerifyOrQuit(otBleSecureTcatStart(instance, nullptr) == kErrorInvalidState);
|
||||
SuccessOrQuit(otBleSecureStart(instance, HandleBleSecureConnect, nullptr, true, &ble));
|
||||
VerifyOrQuit(otBleSecureStart(instance, HandleBleSecureConnect, nullptr, true, nullptr) == kErrorAlready);
|
||||
@@ -158,29 +306,29 @@ void TestTcat(void)
|
||||
otBleSecureDisconnect(instance);
|
||||
VerifyOrQuit(!ble.IsConnected() && !ble.IsBleConnectionOpen());
|
||||
|
||||
// Validate TLS connection can be started only when peer is connected
|
||||
// Validate TLS connection can be started (as client) only when peer is BLE-connected
|
||||
otPlatBleGapOnConnected(instance, kConnectionId);
|
||||
SuccessOrQuit(otBleSecureConnect(instance));
|
||||
VerifyOrQuit(otBleSecureIsConnectionActive(instance));
|
||||
|
||||
// Once in TLS client connecting state, the below cert eval functions are available.
|
||||
// Test that the Thread-specific attributes can be decoded properly.
|
||||
// Test that the Thread-specific attributes from own certificate can be decoded properly.
|
||||
attributeLen = 1;
|
||||
SuccessOrQuit(otBleSecureGetThreadAttributeFromOwnCertificate(instance, kCertificateThreadVersion,
|
||||
&attributeBuffer[0], &attributeLen));
|
||||
VerifyOrQuit(attributeLen == 1 && attributeBuffer[0] >= kThreadVersion1p4);
|
||||
|
||||
static_assert(5 == sizeof(expectedTcatAuthField), "expectedTcatAuthField size incorrect for test");
|
||||
static_assert(5 == sizeof(kDeviceCert1AuthField), "expectedTcatAuthField size incorrect for test");
|
||||
attributeLen = 5;
|
||||
SuccessOrQuit(otBleSecureGetThreadAttributeFromOwnCertificate(instance, kCertificateAuthorizationField,
|
||||
&attributeBuffer[0], &attributeLen));
|
||||
VerifyOrQuit(attributeLen == 5 && memcmp(&expectedTcatAuthField, &attributeBuffer, attributeLen) == 0);
|
||||
VerifyOrQuit(attributeLen == 5 && memcmp(&kDeviceCert1AuthField, &attributeBuffer, attributeLen) == 0);
|
||||
|
||||
// Validate TLS connection can be started only when peer is connected
|
||||
// Validate TLS client connection can be started only when peer is BLE-connected
|
||||
otBleSecureDisconnect(instance);
|
||||
VerifyOrQuit(otBleSecureConnect(instance) == kErrorInvalidState);
|
||||
|
||||
// Validate Tcat state changes after stopping BLE secure
|
||||
// Validate Tcat agent state changes after stopping BLE secure
|
||||
VerifyOrQuit(otBleSecureIsTcatAgentStarted(instance));
|
||||
otBleSecureStop(instance);
|
||||
VerifyOrQuit(!otBleSecureIsTcatAgentStarted(instance));
|
||||
@@ -188,6 +336,467 @@ void TestTcat(void)
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
class UnitTester
|
||||
{
|
||||
private:
|
||||
// Mock action: TCAT Commissioner connects with authorization aCommAuth while device has aDeviceAuth.
|
||||
static void MockCommissionerConnected(TcatAgent *aAgent,
|
||||
const TcatAgent::CertificateAuthorizationField aCommAuth,
|
||||
const TcatAgent::CertificateAuthorizationField aDeviceAuth,
|
||||
bool aIsCommissionedAtStart)
|
||||
{
|
||||
aAgent->mState = TcatAgent::kStateConnected;
|
||||
aAgent->mCommissionerAuthorizationField = aCommAuth;
|
||||
aAgent->mDeviceAuthorizationField = aDeviceAuth;
|
||||
aAgent->mPskcVerified = false;
|
||||
aAgent->mPskdVerified = false;
|
||||
aAgent->mCommissionerHasExtendedPanId = false;
|
||||
aAgent->mCommissionerHasNetworkName = false;
|
||||
aAgent->mCommissionerHasDomainName = false;
|
||||
aAgent->mIsCommissioned = aIsCommissionedAtStart;
|
||||
}
|
||||
|
||||
// Mock condition: commissioner has or has not the given Extended Pan ID in its certificate.
|
||||
static void MockExtPanId(TcatAgent *aAgent, bool aCommHasExtPanId, const ExtendedPanId *aExtPanId)
|
||||
{
|
||||
aAgent->mCommissionerHasExtendedPanId = aCommHasExtPanId;
|
||||
aAgent->mCommissionerExtendedPanId = *aExtPanId;
|
||||
}
|
||||
|
||||
// Mock condition: commissioner has or has not the given Network Name in its certificate.
|
||||
static void MockNetworkName(TcatAgent *aAgent, bool aCommHasNetworkName, const NetworkName *aNetworkName)
|
||||
{
|
||||
aAgent->mCommissionerHasNetworkName = aCommHasNetworkName;
|
||||
aAgent->mCommissionerNetworkName = *aNetworkName;
|
||||
}
|
||||
|
||||
// Mock condition: commissioner has or has not the given Domain Name in its certificate.
|
||||
static void MockDomainName(TcatAgent *aAgent, bool aCommHasDomainName, const NetworkName *aDomainName)
|
||||
{
|
||||
aAgent->mCommissionerHasDomainName = aCommHasDomainName;
|
||||
aAgent->mCommissionerDomainName = *aDomainName;
|
||||
}
|
||||
|
||||
public:
|
||||
static void TestTcatCommissioner1Auth(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
|
||||
// validate no Commissioner authorizations if not connected
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassNone));
|
||||
|
||||
// Mock TCAT Commissioner 1 connects to the agent - verify it has access to all classes
|
||||
// ====================================================================================
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Write a partial Active Dataset and verify that Commissioner can still overwrite this with another dataset
|
||||
// if needed.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sPartialDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(instance->Get<ActiveDatasetManager>().IsPartiallyComplete());
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// Write a full Active Dataset and verify that Commissioner can still overwrite this.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsPartiallyComplete());
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// And back to partial dataset.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sPartialDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(instance->Get<ActiveDatasetManager>().IsPartiallyComplete());
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// provide PSKc proof-of-possession - verify access is same as before
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(instance->Get<ActiveDatasetManager>().IsPartiallyComplete());
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner2Auth(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// Mock TCAT Commissioner 2 connects to the agent - verify it only has access to class General by default.
|
||||
// CommCert2 contains Network Name and Extended PAN ID in this initial test, but not the (also-required)
|
||||
// Thread Domain Name.
|
||||
// =======================================================================================================
|
||||
memcpy(&sCommAuth, &kCommCert2AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
|
||||
// Verify that Set Active Dataset can't be used yet, despite a matching XPAN ID and Network Name for the
|
||||
// dataset that the Commissioner wants to write.
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// provide PSKd proof-of-possession - this is required for all 4 command classes, but not sufficient yet.
|
||||
// So verify there's no change.
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// Commissioner cert now has a matching Domain Name - does not unlock any new classes, because
|
||||
// Network Name and XPAN ID can't match, due to Device being uncommissioned. Writing Active Dataset works now.
|
||||
MockDomainName(agent, true, &sCommDomainName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// Writing a partial dataset does not work: misses the required Network Name and XPAN ID
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Commissioner now has no XPAN ID anymore in cert - verify this prevents Set Active Dataset.
|
||||
// It's a misconfig in the Commissioner's cert.
|
||||
MockExtPanId(agent, false, &sCommExtPanId);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Commissioner now has correct XPAN ID in cert, but not matching the dataset it wants to write.
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
sFullDataset.mExtendedPanId.m8[2]++; // modify bits in dataset to be written.
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Commissioner now attempts to write a dataset with XPAN ID matching to that in cert.
|
||||
sFullDataset.mExtendedPanId = sCommExtPanId;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: Active Dataset is now configured (device is commissioned), but XPAN ID in Dataset
|
||||
// doesn't match the XPAN ID in the Commissioner's cert; and PSKc proof is not given yet,
|
||||
// so most classes remain unavailable.
|
||||
sFullDataset.mExtendedPanId.m8[2]++; // modify bits in dataset to be written.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(instance->Get<ActiveDatasetManager>().IsCommissioned());
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// XPAN ID now matches again; class Commissioning authorization (0x1F) is restored. It doesn't require
|
||||
// PSKc proof.
|
||||
sFullDataset.mExtendedPanId = sCommExtPanId;
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
// Active Dataset can be overwritten because Device was uncommissioned at session start.
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Now PSKc proof is given, unlocking more command classes
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Commissioner connects again - this time, the Device is already commissioned at the start of the session.
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, true);
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
MockDomainName(agent, true, &sCommDomainName);
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// PSKd proof does not authorize Set Active Dataset - because device is already commissioned.
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner4Auth(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// Mock TCAT Commissioner 4 connects to the Device - verify it only has access to class General by default.
|
||||
// The Device is commissioned already at start of the TCAT Link.
|
||||
// =======================================================================================================
|
||||
memcpy(&sCommAuth, &kCommCert4AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, true);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// PSKc proof - satisfies 0x21. Set Active Dataset is not allowed: Device already commissioned.
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// Matching network name now in Commissioner cert - satisfies 0x05
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: matching XPAN ID present in Comm cert - satisfies 0x09
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: Thread Domain name in cert matches - Application class added.
|
||||
MockDomainName(agent, true, &sCommDomainName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: PSKc proof not given - Commissioning class is revoked
|
||||
agent->mPskcVerified = false;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassExtraction | kClassDecommissioning |
|
||||
kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: network name present, but mismatch - Extraction revoked
|
||||
NetworkName wrongNetworkName;
|
||||
wrongNetworkName.Set("WrongName");
|
||||
MockNetworkName(agent, true, &wrongNetworkName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: XPAN ID present, but mismatch - Decommissioning revoked
|
||||
sFullDataset.mExtendedPanId.m8[4]++; // change bits to force a mismatch
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: XPAN ID not present in dataset - same as before
|
||||
sFullDataset.mExtendedPanId = sCommExtPanId; // restore changes bits of above
|
||||
sFullDataset.mComponents.mIsExtendedPanIdPresent = false;
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: Network Name not present in dataset - same as before
|
||||
sFullDataset.mComponents.mIsNetworkNamePresent = false;
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// New situation: Device is decommissioned (by some other Commissioner). Then, this Commissioner
|
||||
// connects again and does PSKc proof. Set Active Dataset access should now be allowed.
|
||||
instance->Get<ActiveDatasetManager>().Clear();
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner5Auth(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// Mock TCAT Commissioner 5 connects to the agent - it requires checks that are unknown to the Device
|
||||
// ==================================================================================================
|
||||
memcpy(&sCommAuth, &kCommCert5AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, true);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// PSKd proof is given: it won't enable Extraction, because Extraction access flag bit 0 = 0.
|
||||
// Also it won't enable Decommissioning, because this class has an unknown flag bit 7 set i.e. the Commissioner
|
||||
// is configured to require a method that the TCAT Device doesn't know about. Application class is also not
|
||||
// enabled, since it requires a check with unknown flag bit 6. Device will enable Commissioning class.
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Connect again and test the situation that Device was uncommissioned at connection start.
|
||||
// Commissioning is now enabled with PSKd proof.
|
||||
instance->Get<ActiveDatasetManager>().Clear();
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner1AuthWithDeviceRequirements(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// test different auth info: for TCAT Device 2 which has specific authorization requirements per class.
|
||||
memcpy(&sDeviceAuth, &kDeviceCert2AuthField, sizeof(sDeviceAuth));
|
||||
|
||||
// Mock TCAT Commissioner 1 connects to the agent - verify
|
||||
// ====================================================================================
|
||||
memcpy(&sCommAuth, &kCommCert1AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassExtraction));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// PSKd proof
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// New situation: Device is commissioned and Commissioner has matching Network Name; and connects.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, true);
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassExtraction | kClassDecommissioning |
|
||||
kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// PSKc proof
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassExtraction | kClassDecommissioning |
|
||||
kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// If Network Name does not match
|
||||
NetworkName wrongNetworkName;
|
||||
wrongNetworkName.Set(kWrongName);
|
||||
MockNetworkName(agent, true, &wrongNetworkName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassExtraction | kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
agent->mPskdVerified = true;
|
||||
agent->mPskcVerified = false;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassApplication));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner2AuthWithDeviceRequirements(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// test different auth info: for TCAT Device 2 which has specific authorization requirements per class.
|
||||
memcpy(&sDeviceAuth, &kDeviceCert2AuthField, sizeof(sDeviceAuth));
|
||||
|
||||
// Mock TCAT Commissioner 2 connects to the agent
|
||||
// ==============================================
|
||||
memcpy(&sCommAuth, &kCommCert2AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, false);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// PSKd proof
|
||||
agent->mPskdVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Network Name match
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// XPAN ID match
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Domain Name match
|
||||
MockDomainName(agent, true, &sCommDomainName);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// PSKc proof
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning | kClassApplication));
|
||||
VerifyOrQuit(SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sPartialDataset));
|
||||
|
||||
// Try write a full dataset with differing XPAN ID
|
||||
sFullDataset.mExtendedPanId.m8[2]++;
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
static void TestTcatCommissioner4AuthWithExistingPartialDataset(void)
|
||||
{
|
||||
Instance *instance = TestInitInstanceTcat();
|
||||
TcatAgent *agent = &instance->Get<TcatAgent>();
|
||||
|
||||
// TCAT device was commissioned earlier on with a partial dataset.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sPartialDataset);
|
||||
|
||||
// Mock TCAT Commissioner 4 connects to the Device.
|
||||
// static const uint8_t kCommCert4AuthField[5] = {0x21, 0x21, 0x05, 0x09, 0x11};
|
||||
memcpy(&sCommAuth, &kCommCert4AuthField, sizeof(sCommAuth));
|
||||
MockCommissionerConnected(agent, sCommAuth, sDeviceAuth, true);
|
||||
MockNetworkName(agent, true, &sCommNetworkName);
|
||||
MockExtPanId(agent, true, &sCommExtPanId);
|
||||
|
||||
// It wants access to Extraction class (0x05) based on matching Network Name, but it's denied.
|
||||
// Decommissioning (0x09) based on matching XPAN ID is also denied.
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// PSKc proof - satisfies 0x21. Set Active Dataset is not allowed: Device already commissioned.
|
||||
agent->mPskcVerified = true;
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
// As a sanity check, redo the test assuming that a (matching) full dataset was initially in the Device.
|
||||
// Now, Extraction and Commissioning classes do work because of matching elements in the Commcert.
|
||||
instance->Get<ActiveDatasetManager>().SaveLocal(sFullDataset);
|
||||
VerifyOrQuit(CommandClassesAuthorized(agent, kClassGeneral | kClassCommissioning | kClassExtraction |
|
||||
kClassDecommissioning));
|
||||
VerifyOrQuit(!SetActiveDatasetAuthorized(agent, sFullDataset));
|
||||
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
}; // class UnitTester
|
||||
|
||||
} // namespace MeshCoP
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
|
||||
@@ -195,11 +804,17 @@ void TestTcat(void)
|
||||
int main(void)
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
|
||||
ot::TestTcat();
|
||||
ot::MeshCoP::TestTcatConnectionAndCertAttributes();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner1Auth();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner2Auth();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner4Auth();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner5Auth();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner1AuthWithDeviceRequirements();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner2AuthWithDeviceRequirements();
|
||||
ot::MeshCoP::UnitTester::TestTcatCommissioner4AuthWithExistingPartialDataset();
|
||||
printf("All tests passed\n");
|
||||
#else
|
||||
printf("Tcat is not enabled\n");
|
||||
return -1;
|
||||
printf("TCAT feature is not enabled\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICITCCAcigAwIBAgIDDhqGMAoGCCqGSM49BAMCMGYxHTAbBgNVBAMMFFRDQVQg
|
||||
MIICIzCCAcigAwIBAgIDDhqGMAoGCCqGSM49BAMCMGYxHTAbBgNVBAMMFFRDQVQg
|
||||
RXhhbXBsZSBDQSAnY2EnMRQwEgYDVQQKDAtFeGFtcGxlIEluYzEVMBMGA1UEBwwM
|
||||
RXhhbXBsZSBDaXR5MQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwHhcNMjUxMTE0
|
||||
MTEyNzMxWhcNMjUxMTI4MTEyNzMxWjA6MR8wHQYDVQQDDBZUQ0FUIEV4YW1wbGUg
|
||||
RXhhbXBsZSBDaXR5MQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwHhcNMjUxMTIz
|
||||
MjIwNDU4WhcNMjUxMjA3MjIwNDU4WjA6MR8wHQYDVQQDDBZUQ0FUIEV4YW1wbGUg
|
||||
Q29tbUNlcnQ0MRcwFQYDVQQFEw4zNTIzLTE1NDMtMDAwNDBZMBMGByqGSM49AgEG
|
||||
CCqGSM49AwEHA0IABICIKe55HN+sUzyr9Jckp0GAssP0gx4wyOYkQ7pBWBFQcEUN
|
||||
QiNtBw2d4qn/zMV2W91HC2eGmMpUhyXqvCOY1/OjgZAwgY0wHwYDVR0jBBgwFoAU
|
||||
4EynoSw9eDKZEVPkums2IWLAJCowGgYJKwYBBAGC3yoBBA0WC090aGVyRG9tYWlu
|
||||
MBQGCSsGAQQBgt8qAwQHBAUhIQUpETAfBgkrBgEEAYLfKgQEEgwQT3RoZXJUaHJl
|
||||
YWQtYzY0ZTAXBgkrBgEEAYLfKgUECgQI7xOYwv1QS2gwCgYIKoZIzj0EAwIDRwAw
|
||||
RAIgKRqYGDm7jn9Aaowxp+ocXJwhgkzwZY2NkXhidUa1g4UCIEZyk09JC6rtyea1
|
||||
apso/bE3OJs+WWrM57RnvntjwE26
|
||||
MBQGCSsGAQQBgt8qAwQHBAUhIQUJETAfBgkrBgEEAYLfKgQEEgwQT3RoZXJUaHJl
|
||||
YWQtYzY0ZTAXBgkrBgEEAYLfKgUECgQI7xOYwv1QS2gwCgYIKoZIzj0EAwIDSQAw
|
||||
RgIhAJdvb/ETZqSrpZTYQ2GzTLJGVBNe018CT+dlwSdeHjQaAiEAqLgbc9r6PV+j
|
||||
91r1jtaSP+StXf2ZAkylcDg+tEGoojk=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -10,6 +10,6 @@ authorityKeyIdentifier = keyid
|
||||
# See https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
|
||||
#
|
||||
1.3.6.1.4.1.44970.1 = ASN1:IA5STRING:OtherDomain
|
||||
1.3.6.1.4.1.44970.3 = DER:04:05:21:21:05:29:11
|
||||
1.3.6.1.4.1.44970.3 = DER:04:05:21:21:05:09:11
|
||||
1.3.6.1.4.1.44970.4 = ASN1:UTF8STRING:OtherThread-c64e
|
||||
1.3.6.1.4.1.44970.5 = DER:04:08:ef:13:98:c2:fd:50:4b:68
|
||||
|
||||
Reference in New Issue
Block a user