[nexus] implement 1.4 Thread Administration Sharing test (1_4_CS_TC_3) (#12832)

This commit implements the Thread 1.4 Credential Sharing (CS) TC-3
Nexus test, focusing on Thread Administration Sharing using ePSKc.
The test case covers mDNS discovery, ePSKc generation/validation,
and DTLS secure transport for TMF message exchange.

Detailed changes:
- Implement full 26-step Nexus test in tests/nexus/test_1_4_CS_TC_3.cpp
  covering the following procedures:
  - mDNS discovery of meshcop and meshcop-e service instances.
  - Validation of State Bitmap (sb) and other TXT record fields.
  - Generation and Verhoeff-based validation of One-Time Passcodes.
  - DTLS handshakes using correct and incorrect ePSKc values.
  - MGMT_ACTIVE_GET and MGMT_PENDING_GET request/response exchanges
    over the secure DTLS session.
  - Testing of ephemeral key expiration and max connection attempts.
- Add Python-based packet verification script in
  tests/nexus/verify_1_4_CS_TC_3.py using pktverify to ensure
  protocol compliance.
- Register the 1_4_CS_TC_3 test in tests/nexus/CMakeLists.txt.
- Add 1_4_CS_TC_3 to the default test list in run_nexus_tests.sh.
This commit is contained in:
Jonathan Hui
2026-04-06 17:39:31 -05:00
committed by GitHub
parent 5439f80c7b
commit f0cc2809cc
4 changed files with 1312 additions and 0 deletions
+1
View File
@@ -293,6 +293,7 @@ ot_nexus_test(1_4_TREL_TC_4 "cert;nexus")
ot_nexus_test(1_4_TREL_TC_5 "cert;nexus")
ot_nexus_test(1_4_TREL_TC_6 "cert;nexus")
ot_nexus_test(1_4_DNS_TC_1 "cert;nexus")
ot_nexus_test(1_4_CS_TC_3 "cert;nexus")
# Misc tests
ot_nexus_test(border_admitter "core;nexus")
+1
View File
@@ -227,6 +227,7 @@ DEFAULT_TESTS=(
"1_4_TREL_TC_5"
"1_4_TREL_TC_6"
"1_4_DNS_TC_1"
"1_4_CS_TC_3"
)
# Use provided arguments or the default test list
+917
View File
@@ -0,0 +1,917 @@
/*
* Copyright (c) 2026, 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.
*/
#include <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
#include "thread/uri_paths.hpp"
#include "utils/verhoeff_checksum.hpp"
namespace ot {
namespace Nexus {
/**
* Time to advance for a node to form a network and become leader, in milliseconds.
*/
static constexpr uint32_t kFormNetworkTime = 50 * Time::kOneSecondInMsec;
/**
* Time to advance for the DUT to connect to the network, in milliseconds.
*/
static constexpr uint32_t kAttachTime = 10 * Time::kOneSecondInMsec;
/**
* Time to advance for mDNS discovery, in milliseconds.
*/
static constexpr uint32_t kMdnsDiscoveryTime = 5 * Time::kOneSecondInMsec;
/**
* Time to advance for DTLS connection, in milliseconds.
*/
static constexpr uint32_t kConnectTimeout = 3 * Time::kOneSecondInMsec;
/**
* Time to advance for TMF message exchange, in milliseconds.
*/
static constexpr uint32_t kTmfExchangeTime = 1 * Time::kOneSecondInMsec;
/**
* Max ePSKc connection attempts.
*/
static constexpr uint8_t kMaxEpskcConnAttempts = 10;
/**
* The filename for the DTLS key log.
*/
static const char kKeyLogFilename[] = "/tmp/test_1_4_CS_TC_3.keys";
/**
* The master secret length in bytes.
*/
static constexpr uint8_t kMasterSecretLength = 48;
/**
* The random buffer length in bytes.
*/
static constexpr uint8_t kRandomBufferLength = MeshCoP::SecureTransport::kSecureTransportRandomBufferSize;
/**
* Infrastructure interface index.
*/
static constexpr uint32_t kInfraIfIndex = 1;
/**
* The UDP port for ePSKc DTLS session.
*/
static constexpr uint16_t kEpskcPort = 1234;
/**
* Time to wait for DTLS retransmissions to finish (in milliseconds).
*/
static constexpr uint32_t kDtlsRetransmissionWaitTime = 60 * Time::kOneSecondInMsec;
/**
* Time to wait for DUT state to be cleaned up after DTLS session (in milliseconds).
*/
static constexpr uint32_t kDutStateCleanTime = 100 * Time::kOneSecondInMsec;
using EphemeralKeyManager = MeshCoP::BorderAgent::EphemeralKeyManager;
using Manager = MeshCoP::BorderAgent::Manager;
static void HandleMbedtlsExportKeys(void *aContext,
mbedtls_ssl_key_export_type aType,
const unsigned char *aMasterSecret,
size_t aMasterSecretLen,
const unsigned char aClientRandom[kRandomBufferLength],
const unsigned char aServerRandom[kRandomBufferLength],
mbedtls_tls_prf_types aTlsPrfType)
{
FILE *file = fopen(kKeyLogFilename, "a");
OT_UNUSED_VARIABLE(aContext);
OT_UNUSED_VARIABLE(aMasterSecretLen);
OT_UNUSED_VARIABLE(aServerRandom);
OT_UNUSED_VARIABLE(aTlsPrfType);
if (file != nullptr)
{
if (aType == MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET)
{
fprintf(file, "CLIENT_RANDOM ");
for (int i = 0; i < kRandomBufferLength; i++)
{
fprintf(file, "%02x", aClientRandom[i]);
}
fprintf(file, " ");
for (int i = 0; i < kMasterSecretLength; i++)
{
fprintf(file, "%02x", aMasterSecret[i]);
}
fprintf(file, "\n");
}
fclose(file);
}
}
typedef Dns::Name::Buffer DnsName;
struct BrowseOutcome
{
DnsName mServiceInstance;
DnsName mHostName;
uint16_t mPort;
Ip6::Address mAddress;
};
static Array<BrowseOutcome, 5> sBrowseOutcomes;
static void HandleBrowseCallback(otInstance *aInstance, const Dns::Multicast::Core::BrowseResult *aResult)
{
BrowseOutcome *outcome;
OT_UNUSED_VARIABLE(aInstance);
outcome = sBrowseOutcomes.PushBack();
VerifyOrQuit(outcome != nullptr);
SuccessOrQuit(StringCopy(outcome->mServiceInstance, aResult->mServiceInstance));
}
static void HandleSrvCallback(otInstance *aInstance, const Dns::Multicast::Core::SrvResult *aResult)
{
OT_UNUSED_VARIABLE(aInstance);
for (BrowseOutcome &outcome : sBrowseOutcomes)
{
if (StringMatch(outcome.mServiceInstance, aResult->mServiceInstance))
{
outcome.mPort = aResult->mPort;
SuccessOrQuit(StringCopy(outcome.mHostName, aResult->mHostName));
break;
}
}
}
static void HandleAddressCallback(otInstance *aInstance, const Dns::Multicast::Core::AddressResult *aResult)
{
OT_UNUSED_VARIABLE(aInstance);
for (BrowseOutcome &outcome : sBrowseOutcomes)
{
if (StringMatch(outcome.mHostName, aResult->mHostName))
{
if (aResult->mAddressesLength > 0)
{
outcome.mAddress = AsCoreType(&aResult->mAddresses[0].mAddress);
}
break;
}
}
}
static void DiscoverMeshcopEService(Node &aCommNode, Core &aNexus, Ip6::Address &aAddress, uint16_t &aPort)
{
sBrowseOutcomes.Clear();
{
Dns::Multicast::Core::Browser browser;
ClearAllBytes(browser);
browser.mServiceType = "_meshcop-e._udp";
browser.mInfraIfIndex = kInfraIfIndex;
browser.mCallback = HandleBrowseCallback;
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StartBrowser(browser));
aNexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StopBrowser(browser));
}
VerifyOrQuit(sBrowseOutcomes.GetLength() == 1);
{
Dns::Multicast::Core::SrvResolver srvResolver;
ClearAllBytes(srvResolver);
srvResolver.mServiceInstance = sBrowseOutcomes[0].mServiceInstance;
srvResolver.mServiceType = "_meshcop-e._udp";
srvResolver.mInfraIfIndex = kInfraIfIndex;
srvResolver.mCallback = HandleSrvCallback;
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StartSrvResolver(srvResolver));
aNexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StopSrvResolver(srvResolver));
}
{
Dns::Multicast::Core::AddressResolver addrResolver;
ClearAllBytes(addrResolver);
addrResolver.mHostName = sBrowseOutcomes[0].mHostName;
addrResolver.mInfraIfIndex = kInfraIfIndex;
addrResolver.mCallback = HandleAddressCallback;
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StartIp6AddressResolver(addrResolver));
aNexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(aCommNode.Get<Dns::Multicast::Core>().StopIp6AddressResolver(addrResolver));
}
aPort = sBrowseOutcomes[0].mPort;
aAddress = sBrowseOutcomes[0].mAddress;
}
void Test1_4_CS_TC_3(void)
{
/**
* 12.3. [1.4] [CERT] [COMPONENT] BR component creates numeric passcode and Candidate connects
*
* 12.3.1. Purpose
* This test is for automatically testing BR component DUTs. Instead of a test operator typing the passcode, the
* DUT generates a passcode and shares it via the Thread Harness API to the Harness so that it can execute the
* automated test. No UI, GUI or display is needed on the BR DUT in this case.
* In detail, the purpose is to verify that the Border Router DUT component:
* - correctly implements the Thread Credential Sharing feature by supporting the Thread Administration Sharing
* procedure
* - is able to generate a numeric Thread Administration One-Time Passcode consisting of 9 digits
* - allows an external Candidate to connect using the ePSKc that is based on the passcode
* - is able to facilitate the Candidates operations
* - enables access to a Candidate using the ePSKc, only once.
*
* 12.3.2. Topology
* - BR_1 Border Router Thread Component DUT
* - Router_1 Thread Router reference device and Leader
* - Comm_1 IPv6 host reference device on the AIL that implements a Commissioner Candidate role.
*
* Spec Reference | V1.4 Section
* --------------------------|-----------------
* Thread Credential Sharing | 8.4.1.2.3, 8.4.9
*/
Core nexus;
Node &router1 = nexus.CreateNode();
Node &br1 = nexus.CreateNode();
Node &comm1 = nexus.CreateNode();
router1.SetName("Router_1");
br1.SetName("BR_1");
comm1.SetName("Comm_1");
router1.Get<Manager>().SetEnabled(false);
comm1.Get<Manager>().SetEnabled(false);
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
// Remove any existing key log file.
remove(kKeyLogFilename);
#if OPENTHREAD_CONFIG_MBEDTLS_PROVIDES_SSL_KEY_EXPORT
static_cast<MeshCoP::SecureTransport &>(comm1.Get<Tmf::SecureAgent>())
.SetKeylogCallback(HandleMbedtlsExportKeys, &comm1.Get<Tmf::SecureAgent>());
#endif
SuccessOrQuit(br1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
comm1.Get<ThreadNetif>().Up();
// Wait for the interfaces to be ready and addresses to be configured.
nexus.AdvanceTime(Time::kOneSecondInMsec);
Log("---------------------------------------------------------------------------------------");
Log("Step 1: Enable and form network.");
/**
* Step 1
* - Device: Router_1, BR_1 (DUT)
* - Description (CS-12.3): Enable and form network. The DUT is configured with the Network Key of the test
* Thread Network, so that it can connect to it.
* - Pass Criteria: The DUT MUST connect to Router_1s network.
*/
router1.Form();
nexus.AdvanceTime(kFormNetworkTime);
br1.Join(router1);
nexus.AdvanceTime(kAttachTime);
VerifyOrQuit(br1.Get<Mle::Mle>().IsAttached());
SuccessOrQuit(comm1.Get<Mac::Mac>().SetPanChannel(router1.Get<Mac::Mac>().GetPanChannel()));
comm1.Get<Mac::Mac>().SetPanId(router1.Get<Mac::Mac>().GetPanId());
comm1.Get<ThreadNetif>().Up();
br1.Get<Manager>().SetEnabled(true);
nexus.AdvanceTime(Time::kOneSecondInMsec);
Log("---------------------------------------------------------------------------------------");
Log("Step 2: Comm_1 discovers BR DUT service.");
/**
* Step 2
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to do mDNS Qtype=PTR discovery query for DNS-SD service type
* “_meshcop._udp.local”, to find the BR DUT service. ... Harness then verifies data in the SRV record and TXT
* record as detailed to the right.
* - Pass Criteria:
* - The DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop._udp.local” in the
* Answer section.
* - Comm_1 MUST be able to obtain the TXT record as detailed on the left.
* - In the TXT record:
* - ePSKc Support flag (Bit 11) in State Bitmap (sb) MUST be 1 (ePSKc supported)
* - vn field MUST be present and contain >= 1 characters.
* - mn field MUST be present and contain >= 1 characters.
* - Comm_1 MUST be able to obtain the SRV record as detailed on the left. Harness logs and stores the port
* number in the SRV record for later use.
*/
// Border Agent starts automatically when attached.
VerifyOrQuit(br1.Get<Manager>().IsEnabled());
{
Dns::Multicast::Core::Browser browser;
ClearAllBytes(browser);
browser.mServiceType = "_meshcop._udp";
browser.mInfraIfIndex = kInfraIfIndex;
browser.mCallback = HandleBrowseCallback;
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StartBrowser(browser));
nexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StopBrowser(browser));
}
VerifyOrQuit(sBrowseOutcomes.GetLength() == 1);
Log("---------------------------------------------------------------------------------------");
Log("Step 3: BR_1 (DUT) produces passcode.");
/**
* Step 3
* - Device: BR_1 (DUT)
* - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration
* One-Time Passcode.
* - Pass Criteria: Passcode is successfully retrieved.
*/
EphemeralKeyManager::Tap tap1;
SuccessOrQuit(tap1.GenerateRandom());
Log("Passcode 1: %s", tap1.mTap);
nexus.AddTestVar("Passcode1", tap1.mTap);
Log("---------------------------------------------------------------------------------------");
Log("Step 4: Harness validates the passcode.");
/**
* Step 4
* - Device: Harness
* - Description (CS-12.3): Harness validates the passcode. Harness calculates the ePSKc as follows: each
* character from the passcode string is taken as an ePSKc octet.
* - Pass Criteria:
* - Harness validates the following, using its passcode validation function:
* - The passcode MUST contain only digits 0-9.
* - The passcode MUST be 9 digits long.
* - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on
* the first 8 digits.
*/
SuccessOrQuit(tap1.Validate());
Log("---------------------------------------------------------------------------------------");
Log("Step 5: Comm_1 discovers meshcop-e service.");
/**
* Step 5
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to discover a DNS-SD service type via mDNS, Qtype=PTR, name
* “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs it.
* - Pass Criteria:
* - The DUT MUST respond (mDNS) with a DNS-SD message that contains one PTR record of type
* “_meshcop-e._udp.local” in the Answer section.
* - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the
* “meshcop-e” service as detailed on the left.
*/
SuccessOrQuit(br1.Get<EphemeralKeyManager>().Start(tap1.mTap, 0, kEpskcPort));
uint16_t epskcPort;
Ip6::Address br1Addr;
DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort);
Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort);
Log("---------------------------------------------------------------------------------------");
Log("Step 6: Comm_1 connects with incorrect ePSKc.");
/**
* Step 6
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step 5.
* For the DTLS PSK, it uses as ePSKc an incorrect ePSKc which is calculated as follows: 1. Seven octets
* (b[0]…b[6]) copied from the correct ePSKc of step 4. 2. One octet (b[7]) as a random digit that differs from
* the correct ePSKc of step 4. 3. One octet (b[8]) calculated as the correct Verhoeff check digit over the
* previous 8 numeric digits. ... Harness then waits for 3 seconds.
* - Pass Criteria:
* - The DTLS handshake/connection to the DUT MUST NOT succeed: the DUT MUST reject attempt to establish a DTLS
* session.
*/
{
char incorrectTap[EphemeralKeyManager::Tap::kLength + 1];
char checksum;
uint8_t i = 7;
memcpy(incorrectTap, tap1.mTap, EphemeralKeyManager::Tap::kLength + 1);
incorrectTap[i]++;
if (incorrectTap[i] > '9')
{
incorrectTap[i] = '0';
}
incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = '\0';
SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(incorrectTap, checksum));
incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = checksum;
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SetPsk(reinterpret_cast<const uint8_t *>(incorrectTap),
EphemeralKeyManager::Tap::kLength));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(!comm1.Get<Tmf::SecureAgent>().IsConnected());
comm1.Get<Tmf::SecureAgent>().Close();
// Wait longer to ensure all retransmissions are finished.
nexus.AdvanceTime(kDtlsRetransmissionWaitTime);
}
Log("---------------------------------------------------------------------------------------");
Log("Step 7: Comm_1 connects with correct ePSKc.");
/**
* Step 7
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in
* step 5. For the DTLS PSK, it now uses the correct ePSKc as calculated in step 4.
* - Pass Criteria: The DTLS handshake/connection to the DUT MUST succeed.
*/
{
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SetPsk(reinterpret_cast<const uint8_t *>(tap1.mTap),
EphemeralKeyManager::Tap::kLength));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(comm1.Get<Tmf::SecureAgent>().IsConnected());
}
Log("---------------------------------------------------------------------------------------");
Log("Step 8: Comm_1 sends MGMT_ACTIVE_GET.req.");
/**
* Step 8
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to send TMF message MGMT_ACTIVE_GET.req to the DUT over the
* secure DTLS session. The Get TLV is not included in the request.
* - Pass Criteria:
* - Comm_1 MUST receive the response TMF message MGMT_ACTIVE_GET.rsp over the secure DTLS session.
* - Response payload MUST contain Network Key TLV, ...
*/
{
Coap::Message *message;
message = comm1.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriActiveGet);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SendMessage(*message));
}
nexus.AdvanceTime(kTmfExchangeTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 9: Comm_1 sends MGMT_PENDING_GET.req.");
/**
* Step 9
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to send MGMT_PENDING_GET.req to the DUT over the secure DTLS
* session. The Get TLV is not included in the request.
* - Pass Criteria:
* - Comm_1 MUST receive the TMF response message MGMT_PENDING_GET.rsp.
* - Response CoAP payload MUST be empty.
*/
{
Coap::Message *message;
message = comm1.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriPendingGet);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SendMessage(*message));
}
nexus.AdvanceTime(kTmfExchangeTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 10: Comm_1 closes the DTLS connection.");
/**
* Step 10
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to close the Commissioner session, i.e. close the DTLS
* connection. Then, wait for 3 seconds.
* - Pass Criteria: N/A
*/
comm1.Get<Tmf::SecureAgent>().Close();
nexus.AdvanceTime(3 * Time::kOneSecondInMsec);
Log("---------------------------------------------------------------------------------------");
Log("Step 11: Comm_1 connects again (should fail).");
/**
* Step 11
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in
* step 5. For the DTLS PSK, it uses the correct ePSKc as determined in step 4. ...
* - Pass Criteria:
* - The DTLS handshake/connection to the DUT MUST NOT succeed: the DUT MUST reject the DTLS connection attempt.
*/
{
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(!comm1.Get<Tmf::SecureAgent>().IsConnected());
comm1.Get<Tmf::SecureAgent>().Close();
// Wait much longer to ensure all retransmissions are finished and the DUT state is clean.
// MbedTLS retransmissions can last for a while.
nexus.AdvanceTime(kDutStateCleanTime);
}
Log("---------------------------------------------------------------------------------------");
Log("Step 12: Wait 3s and clear mDNS cache.");
/**
* Step 12
* - Device: Harness
* - Description (CS-12.3): Harness waits for 3 seconds. Harness clears its mDNS discovery cache.
* - Pass Criteria: N/A
*/
nexus.AdvanceTime(3 * Time::kOneSecondInMsec);
// Nexus mDNS cache is handled by AdvanceTime usually or explicitly.
// In this test, we just clear our browse outcomes.
sBrowseOutcomes.Clear();
Log("---------------------------------------------------------------------------------------");
Log("Step 13: Comm_1 discovers meshcop-e service (should fail).");
/**
* Step 13
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name
* “_meshcop-e._udp.local”.
* - Pass Criteria: The DUT MUST NOT respond with an mDNS message that contains a PTR record of type
* “_meshcop-e._udp.local” in the Answer section.
*/
{
Dns::Multicast::Core::Browser browser;
ClearAllBytes(browser);
browser.mServiceType = "_meshcop-e._udp";
browser.mInfraIfIndex = kInfraIfIndex;
browser.mCallback = HandleBrowseCallback;
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StartBrowser(browser));
nexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StopBrowser(browser));
}
VerifyOrQuit(sBrowseOutcomes.GetLength() == 0);
Log("---------------------------------------------------------------------------------------");
Log("Step 14: BR_1 (DUT) produces new passcode.");
/**
* Step 14
* - Device: BR_1 (DUT)
* - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration
* One-Time Passcode.
* - Pass Criteria: Passcode is successfully retrieved.
*/
EphemeralKeyManager::Tap tap2;
SuccessOrQuit(tap2.GenerateRandom());
Log("Passcode 2: %s", tap2.mTap);
nexus.AddTestVar("Passcode2", tap2.mTap);
Log("---------------------------------------------------------------------------------------");
Log("Step 15: Harness validates the new passcode.");
/**
* Step 15
* - Device: Harness
* - Description (CS-12.3): Harness validates the passcode. From the passcode, the Harness calculates the ePSKc
* using the procedure of step 4.
* - Pass Criteria:
* - Harness validates the following, using its passcode validation function:
* - The passcode MUST differ from the passcode previously obtained in step 4
* - The passcode MUST contain only digits 0-9
* - The passcode MUST be 9 digits long
* - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on
* the first 8 digits.
*/
SuccessOrQuit(tap2.Validate());
VerifyOrQuit(!StringMatch(tap2.mTap, tap1.mTap));
Log("---------------------------------------------------------------------------------------");
Log("Step 16: Comm_1 discovers meshcop-e service.");
/**
* Step 16
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name
* “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs these.
* - Pass Criteria:
* - The DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop-e._udp.local” in
* the Answer section.
* - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the
* “meshcop-e” service as detailed on the left.
*/
SuccessOrQuit(br1.Get<EphemeralKeyManager>().Start(tap2.mTap, 0, kEpskcPort + 1));
DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort);
Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort);
Log("---------------------------------------------------------------------------------------");
Log("Step 17: Comm_1 connects with correct ePSKc.");
/**
* Step 17
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step
* 16. For the DTLS PSK, it uses the correct ePSKc as calculated in step 15.
* - Pass Criteria: The DTLS handshake/connection to the DUT MUST succeed.
*/
{
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SetPsk(reinterpret_cast<const uint8_t *>(tap2.mTap),
EphemeralKeyManager::Tap::kLength));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(comm1.Get<Tmf::SecureAgent>().IsConnected());
}
Log("---------------------------------------------------------------------------------------");
Log("Step 18: Comm_1 sends MGMT_ACTIVE_GET.req.");
/**
* Step 18
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs the device to send TMF message MGMT_ACTIVE_GET.req to the DUT over
* the secure DTLS session. The Get TLV is not included in the request.
* - Pass Criteria:
* - Comm_1 MUST receive the response TMF message MGMT_ACTIVE_GET.rsp over the secure DTLS session.
* - Response payload MUST contain Network Key TLV, ...
*/
{
Coap::Message *message;
message = comm1.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriActiveGet);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SendMessage(*message));
}
nexus.AdvanceTime(kTmfExchangeTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 19: Comm_1 closes the DTLS connection.");
/**
* Step 19
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to close the DTLS connection.
* - Pass Criteria: N/A
*/
comm1.Get<Tmf::SecureAgent>().Close();
nexus.AdvanceTime(3 * Time::kOneSecondInMsec);
Log("---------------------------------------------------------------------------------------");
Log("Step 20: BR_1 (DUT) produces new passcode.");
/**
* Step 20
* - Device: BR_1 (DUT)
* - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration
* One-Time Passcode.
* - Pass Criteria: Passcode is successfully retrieved.
*/
EphemeralKeyManager::Tap tap3;
SuccessOrQuit(tap3.GenerateRandom());
Log("Passcode 3: %s", tap3.mTap);
nexus.AddTestVar("Passcode3", tap3.mTap);
Log("---------------------------------------------------------------------------------------");
Log("Step 21: Harness validates the new passcode.");
/**
* Step 21
* - Device: Harness
* - Description (CS-12.3): Harness validates the passcode. From the passcode, the Harness calculates the ePSKc
* again using the procedure of step 4.
* - Pass Criteria:
* - Harness validates the following, equal to step 4, using its passcode validation function:
* - The passcode MUST contain only digits 0-9.
* - The passcode MUST be 9 digits long.
* - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on
* the first 8 digits.
* - Harness also validates that the passcode MUST be different from the passcodes obtained before in steps 4
* and 15.
*/
SuccessOrQuit(tap3.Validate());
VerifyOrQuit(!StringMatch(tap3.mTap, tap1.mTap));
VerifyOrQuit(!StringMatch(tap3.mTap, tap2.mTap));
Log("---------------------------------------------------------------------------------------");
Log("Step 22: Comm_1 discovers meshcop-e service.");
/**
* Step 22
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name
* “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs these.
* - Pass Criteria:
* - DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop-e._udp.local” in the
* Answer section.
* - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the
* “meshcop-e” service as detailed on the left.
*/
SuccessOrQuit(br1.Get<EphemeralKeyManager>().Start(tap3.mTap, 0, kEpskcPort + 2));
DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort);
Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort);
Log("---------------------------------------------------------------------------------------");
Log("Step 23: Comm_1 connects with incorrect ePSKc 10 times.");
/**
* Step 23
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step
* 22. For the DTLS PSK, it uses an incorrect ePSKc as calculated using the procedure of step 6 applied to the
* passcode found in step 21. This connection attempt is done in total MAX_EPSKC_CONN_ATTEMPTS (10) times. Each
* time, the pass criteria are checked. Between attempts, the Harness pauses for 2 seconds.
* - Pass Criteria:
* - The DTLS handshake/connection to DUT MUST NOT succeed.
* - In all cases, the DUT MUST NOT send ICMPv6 Port Unreachable error - this way of failing is not allowed here.
* The DUT MUST respond with DTLS messages.
*/
{
char incorrectTap[EphemeralKeyManager::Tap::kLength + 1];
char checksum;
uint8_t i = 7;
memcpy(incorrectTap, tap3.mTap, EphemeralKeyManager::Tap::kLength + 1);
incorrectTap[i]++;
if (incorrectTap[i] > '9')
{
incorrectTap[i] = '0';
}
incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = '\0';
SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(incorrectTap, checksum));
incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = checksum;
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SetPsk(reinterpret_cast<const uint8_t *>(incorrectTap),
EphemeralKeyManager::Tap::kLength));
for (uint8_t attempt = 1; attempt <= kMaxEpskcConnAttempts; attempt++)
{
Log("Attempt %u with incorrect ePSKc.", attempt);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(!comm1.Get<Tmf::SecureAgent>().IsConnected());
comm1.Get<Tmf::SecureAgent>().Close();
nexus.AdvanceTime(2 * Time::kOneSecondInMsec);
}
}
Log("---------------------------------------------------------------------------------------");
Log("Step 24: Comm_1 connects with correct ePSKc (should fail).");
/**
* Step 24
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in
* step 22. For the DTLS PSK, it uses the correct ePSKc as calculated in step 21. But the connection still fails
* due to the maximum number of attempts passed.
* - Pass Criteria:
* - The DTLS handshake/connection to DUT MUST NOT succeed.
* - Note: the reason that the correct ePSKc does not succeed here is that the maximum number of failed
* connection attempts has been reached. This is being tested here.
*/
{
Ip6::SockAddr br1SockAddr;
br1SockAddr.SetAddress(br1Addr);
br1SockAddr.SetPort(epskcPort);
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().SetPsk(reinterpret_cast<const uint8_t *>(tap3.mTap),
EphemeralKeyManager::Tap::kLength));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Open(Ip6::kNetifBackbone));
SuccessOrQuit(comm1.Get<Tmf::SecureAgent>().Connect(br1SockAddr));
nexus.AdvanceTime(kConnectTimeout);
VerifyOrQuit(!comm1.Get<Tmf::SecureAgent>().IsConnected());
comm1.Get<Tmf::SecureAgent>().Close();
}
Log("---------------------------------------------------------------------------------------");
Log("Step 25: Comm_1 clears its mDNS discovery cache.");
/**
* Step 25
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs device to clear its mDNS discovery cache.
* - Pass Criteria: N/A
*/
sBrowseOutcomes.Clear();
Log("---------------------------------------------------------------------------------------");
Log("Step 26: Comm_1 discovers meshcop-e service (should fail).");
/**
* Step 26
* - Device: Comm_1
* - Description (CS-12.3): Harness instructs the device to discover a service type via mDNS, Qtype=PTR, name
* “_meshcop-e._udp.local”.
* - Pass Criteria: The DUT MUST NOT respond with an mDNS message that contains a PTR record of type
* “_meshcop-e._udp.local” in the Answer section.
*/
{
Dns::Multicast::Core::Browser browser;
ClearAllBytes(browser);
browser.mServiceType = "_meshcop-e._udp";
browser.mInfraIfIndex = kInfraIfIndex;
browser.mCallback = HandleBrowseCallback;
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StartBrowser(browser));
nexus.AdvanceTime(kMdnsDiscoveryTime);
SuccessOrQuit(comm1.Get<Dns::Multicast::Core>().StopBrowser(browser));
}
VerifyOrQuit(sBrowseOutcomes.GetLength() == 0);
nexus.SaveTestInfo("test_1_4_CS_TC_3.json");
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::Test1_4_CS_TC_3();
printf("All tests passed\n");
return 0;
}
+393
View File
@@ -0,0 +1,393 @@
#!/usr/bin/env python3
#
# Copyright (c) 2026, 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.
#
import sys
import os
import struct
# Add the current directory to sys.path to find verify_utils
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(CUR_DIR)
import verify_utils
from pktverify import consts
from pktverify.null_field import nullField
# Constants
EPSKC_PORT = 1234
ALERT_DESC_CLOSE_NOTIFY = 0
ALERT_LEVEL_FATAL = 2
ALERT_DESC_DECRYPT_ERROR = 51
MESHCOP_SERVICE = '_meshcop._udp.local'
MESHCOP_E_SERVICE = '_meshcop-e._udp.local'
def verify(pv):
pkts = pv.pkts
pv.summary.show()
COMM_1_LL = 'fe80::ff:fe00:2'
BR_1_LL = 'fe80::ff:fe00:1'
BR_1_INFRA = 'fe80::ff:fe00:1'
def get_txt_entries(p):
entries = {}
txt_list = []
# Try to get all values if it's a list
layer = None
if hasattr(p, 'mdns') and p.mdns:
layer = p.mdns
elif hasattr(p, 'dns') and p.dns:
layer = p.dns
if layer:
txt_list = layer.txt
if not isinstance(txt_list, list):
txt_list = [txt_list]
for txt in txt_list:
if isinstance(txt, nullField.__class__):
continue
# txt is now a Bytes object (hex representation)
# Find the '=' character (0x3d)
try:
eq_idx = txt.index(0x3d)
key = bytes(txt[:eq_idx]).decode()
value = txt[eq_idx + 1:]
entries[key] = value
except ValueError:
entries[bytes(txt).decode()] = None
return entries
# Step 1: Enable and form network.
print("Step 1: Enable and form network.")
# Step 2: Comm_1 discovers BR DUT service.
print("Step 2: Comm_1 discovers BR DUT service.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_SERVICE in p.mdns.qry.name).\
must_next()
mdns_resp = pkts.filter_ipv6_src(BR_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_SERVICE in p.mdns.resp.name).\
must_next()
# In the TXT record:
# - ePSKc Support flag (Bit 11) in State Bitmap (sb) MUST be 1 (ePSKc supported)
# - vn field MUST be present and contain >= 1 characters.
# - mn field MUST be present and contain >= 1 characters.
txts = get_txt_entries(mdns_resp)
assert 'sb' in txts, "sb field missing in TXT record"
sb = struct.unpack('>I', txts['sb'])[0]
assert (sb >> 11) & 1 == 1, f"ePSKc support flag (bit 11) in sb is not 1 (sb={sb:08x})"
assert 'vn' in txts and len(txts['vn']) >= 1, "vn field missing or empty in TXT record"
assert 'mn' in txts and len(txts['mn']) >= 1, "mn field missing or empty in TXT record"
# Step 3 & 4: Passcode generation and validation.
print("Step 3 & 4: Passcode generation and validation.")
# (Validated by C++ test)
# Step 5: Comm_1 discovers meshcop-e service.
print("Step 5: Comm_1 discovers meshcop-e service.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.qry.name).\
must_next()
mdns_resp = pkts.filter_ipv6_src(BR_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.resp.name).\
must_next()
port_step5 = mdns_resp.mdns.srv.port[0]
print(f"Step 5 Discovered port: {port_step5}")
# Step 6: Comm_1 connects with incorrect ePSKc.
print("Step 6: Comm_1 connects with incorrect ePSKc.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
filter(lambda p: p.dtls.handshake.cookie is not nullField).\
must_next()
pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step5).\
filter(lambda p: consts.CONTENT_ALERT in p.dtls.record.content_type).\
must_next()
# Step 7: Comm_1 connects with correct ePSKc.
print("Step 7: Comm_1 connects with correct ePSKc.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
filter(lambda p: p.dtls.handshake.cookie is not nullField).\
must_next()
pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step5).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_FINISHED in p.dtls.handshake.type).\
must_next()
# Step 8: Comm_1 sends MGMT_ACTIVE_GET.req.
print("Step 8: Comm_1 sends MGMT_ACTIVE_GET.req.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: p.coap.opt.uri_path == ['c', 'ag']).\
must_next()
active_get_rsp = pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step5).\
filter(lambda p: p.coap.type == 2).\
must_next()
# Response payload MUST contain Network Key, Ext PAN ID, Network Name, PAN ID, PSKc, Security Policy,
# Active Timestamp, Mesh-Local Prefix, Channel, Channel Mask.
# Note: pktverify uses 'master_key' for Network Key TLV.
active_get_rsp.must_verify(lambda p: all(
p.coap.tlv.has(field) for field in [
'network_key',
'ext_pan_id',
'network_name',
'pan_id',
'pskc',
'security_policy',
'active_timestamp',
'mesh_local_prefix',
'channel',
'channel_mask',
]))
# Step 9: Comm_1 sends MGMT_PENDING_GET.req.
print("Step 9: Comm_1 sends MGMT_PENDING_GET.req.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: p.coap.opt.uri_path == ['c', 'pg']).\
must_next()
pending_get_rsp = pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step5).\
filter(lambda p: p.coap.type == 2).\
must_next()
# Response CoAP payload MUST be empty.
assert pending_get_rsp.coap.payload is nullField, "MGMT_PENDING_GET response payload is not empty"
# Step 10: Comm_1 closes the DTLS connection.
print("Step 10: Comm_1 closes the DTLS connection.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: consts.CONTENT_ALERT in p.dtls.record.content_type).\
must_next()
# Step 11: Comm_1 connects again (should fail).
print("Step 11: Comm_1 connects again (should fail).")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step5).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
must_next()
# The DUT may either send an ALERT or simply not respond (since ephemeral key is stopped).
# We check if there's an ALERT, but don't fail if there isn't one as long as connection doesn't succeed.
pkts.copy().filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step5).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_FINISHED in p.dtls.handshake.type).\
must_not_next()
# Step 13: Comm_1 discovers meshcop-e service (should fail).
print("Step 13: Comm_1 discovers meshcop-e service (should fail).")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.qry.name).\
must_next()
# Step 16: Comm_1 discovers meshcop-e service.
print("Step 16: Comm_1 discovers meshcop-e service.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.qry.name).\
must_next()
mdns_resp = pkts.filter_ipv6_src(BR_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.resp.name).\
must_next()
port_step16 = mdns_resp.mdns.srv.port[0]
print(f"Step 16 Discovered port: {port_step16}")
# Step 17: Comm_1 connects with correct ePSKc.
print("Step 17: Comm_1 connects with correct ePSKc.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step16).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
filter(lambda p: p.dtls.handshake.cookie is not nullField).\
must_next()
pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step16).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_FINISHED in p.dtls.handshake.type).\
must_next()
# Step 18: Comm_1 sends MGMT_ACTIVE_GET.req.
print("Step 18: Comm_1 sends MGMT_ACTIVE_GET.req.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step16).\
filter(lambda p: p.coap.opt.uri_path == ['c', 'ag']).\
must_next()
active_get_rsp = pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step16).\
filter(lambda p: p.coap.type == 2).\
must_next()
active_get_rsp.must_verify(lambda p: all(
p.coap.tlv.has(field) for field in [
'network_key',
'ext_pan_id',
'network_name',
'pan_id',
'pskc',
'security_policy',
'active_timestamp',
'mesh_local_prefix',
'channel',
'channel_mask',
]))
# Step 19: Comm_1 closes the DTLS connection.
print("Step 19: Comm_1 closes the DTLS connection.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step16).\
filter(lambda p: consts.CONTENT_ALERT in p.dtls.record.content_type).\
must_next()
# Step 22: Comm_1 discovers meshcop-e service.
print("Step 22: Comm_1 discovers meshcop-e service.")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.qry.name).\
must_next()
mdns_resp = pkts.filter_ipv6_src(BR_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.resp.name).\
must_next()
port_step22 = mdns_resp.mdns.srv.port[0]
print(f"Step 22 Discovered port: {port_step22}")
# Step 23: Comm_1 connects with incorrect ePSKc 10 times.
print("Step 23: Comm_1 connects with incorrect ePSKc 10 times.")
for i in range(10):
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step22).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
filter(lambda p: p.dtls.handshake.cookie is not nullField).\
must_next()
pkts.filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step22).\
filter(lambda p: consts.CONTENT_ALERT in p.dtls.record.content_type).\
must_next()
# Step 24: Comm_1 connects with correct ePSKc (should fail).
print("Step 24: Comm_1 connects with correct ePSKc (should fail).")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst(BR_1_INFRA).\
filter(lambda p: p.udp.dstport == port_step22).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_CLIENT_HELLO in p.dtls.handshake.type).\
must_next()
# Similar to Step 11, the DUT may either send an ALERT or not respond.
pkts.copy().filter_ipv6_src(BR_1_INFRA).\
filter_ipv6_dst(COMM_1_LL).\
filter(lambda p: p.udp.srcport == port_step22).\
filter(lambda p: consts.CONTENT_HANDSHAKE in p.dtls.record.content_type).\
filter(lambda p: consts.HANDSHAKE_FINISHED in p.dtls.handshake.type).\
must_not_next()
# Step 26: Comm_1 discovers meshcop-e service (should fail).
print("Step 26: Comm_1 discovers meshcop-e service (should fail).")
pkts.filter_ipv6_src(COMM_1_LL).\
filter_ipv6_dst('ff02::fb').\
filter(lambda p: p.udp.dstport == 5353).\
filter(lambda p: MESHCOP_E_SERVICE in p.mdns.qry.name).\
must_next()
if __name__ == '__main__':
# Add keylog file to Wireshark preferences if it exists.
keylog_file = '/tmp/test_1_4_CS_TC_3.keys'
if os.path.exists(keylog_file):
consts.WIRESHARK_OVERRIDE_PREFS['tls.keylog_file'] = keylog_file
for port in [1234, 1235, 1236]:
consts.WIRESHARK_DECODE_AS_ENTRIES[f'udp.port=={port}'] = 'dtls'
consts.WIRESHARK_DECODE_AS_ENTRIES[f'dtls.port=={port}'] = 'coap'
verify_utils.run_main(verify)