[border-agent] add Thread Admin One-Time Passcodes (TAP) APIs (#12188)

This commit introduces new APIs to handle Thread Administration
One-Time Passcodes (TAP).

The new `otBorderAgentEphemeralKeyGenerateTap()` API generates a
cryptographically secure 9-character TAP string. This consists of
eight random numeric digits and a final check digit calculated using
the Verhoeff algorithm for error detection.

The corresponding `otBorderAgentEphemeralKeyValidateTap()` API
validates a given TAP string by checking its length, ensuring it
contains only digits, and verifying the Verhoeff checksum.

A new test is added to ensure the correctness of both the generation
and validation logic, covering success and failure scenarios.
This commit is contained in:
Abtin Keshavarzian
2025-12-09 12:20:04 -08:00
committed by GitHub
parent 0c592029ba
commit 075f4f7d0e
7 changed files with 232 additions and 1 deletions
+44
View File
@@ -423,6 +423,11 @@ otError otBorderAgentEvictActiveCommissioner(otInstance *aInstance);
*/
#define OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT (10 * 60 * 1000u)
/**
* The string length of Thread Administration One-Time Passcode (TAP).
*/
#define OT_BORDER_AGENT_EPHEMERAL_KEY_TAP_STRING_LENGTH 9
/**
* Represents Border Agent's Ephemeral Key Manager state.
*/
@@ -435,6 +440,14 @@ typedef enum otBorderAgentEphemeralKeyState
OT_BORDER_AGENT_STATE_ACCEPTED = 4, ///< Session is established and candidate is accepted as full commissioner.
} otBorderAgentEphemeralKeyState;
/**
* Represents a Thread Administration One-Time Passcode (TAP).
*/
typedef struct otBorderAgentEphemeralKeyTap
{
char mTap[OT_BORDER_AGENT_EPHEMERAL_KEY_TAP_STRING_LENGTH + 1]; ///< TAP string buffer (including `\0` character).
} otBorderAgentEphemeralKeyTap;
/**
* Gets the state of Border Agent's Ephemeral Key Manager.
*
@@ -563,6 +576,37 @@ void otBorderAgentEphemeralKeySetCallback(otInstance *aIns
*/
const char *otBorderAgentEphemeralKeyStateToString(otBorderAgentEphemeralKeyState aState);
/**
* Generates a cryptographically secure random Thread Administration One-Time Passcode (TAP) string.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE` and `OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE`.
*
* The TAP is a string of 9 characters, generated as a sequence of eight cryptographically secure random
* numeric digits [`0`-`9`] followed by a single check digit determined using the Verhoeff algorithm.
*
* @param[out] aTap A pointer to an `otBorderAgentEphemeralKeyTap` to output the generated TAP.
*
* @retval OT_ERROR_NONE Successfully generated a random TAP. @p aTap is updated.
* @retval OT_ERROR_FAILED Failed to generate a random TAP.
*/
otError otBorderAgentEphemeralKeyGenerateTap(otBorderAgentEphemeralKeyTap *aTap);
/**
* Validates a given Thread Administration One-Time Passcode (TAP) string.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE` and `OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE`.
*
* Validates that the TAP string has the proper length, contains digit characters [`0`-`9`], and validates the
* Verhoeff checksum.
*
* @param[in] aTap The `otBorderAgentEphemeralKeyTap` to validate.
*
* @retval OT_ERROR_NONE Successfully validated the @p aTap.
* @retval OT_ERROR_INVALID_ARGS The @p aTap string has an invalid length or contains non-digit characters.
* @retval OT_ERROR_FAILED Checksum validation failed.
*/
otError otBorderAgentEphemeralKeyValidateTap(const otBorderAgentEphemeralKeyTap *aTap);
/**
* @}
*/
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (558)
#define OPENTHREAD_API_VERSION (559)
/**
* @addtogroup api-instance
+14
View File
@@ -172,6 +172,20 @@ const char *otBorderAgentEphemeralKeyStateToString(otBorderAgentEphemeralKeyStat
return MeshCoP::BorderAgent::EphemeralKeyManager::StateToString(MapEnum(aState));
}
#if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
otError otBorderAgentEphemeralKeyGenerateTap(otBorderAgentEphemeralKeyTap *aTap)
{
return AsCoreType(aTap).GenerateRandom();
}
otError otBorderAgentEphemeralKeyValidateTap(const otBorderAgentEphemeralKeyTap *aTap)
{
return AsCoreType(aTap).Validate();
}
#endif
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
@@ -36,6 +36,7 @@
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
#include "instance/instance.hpp"
#include "utils/verhoeff_checksum.hpp"
namespace ot {
namespace MeshCoP {
@@ -428,6 +429,67 @@ const char *EphemeralKeyManager::DeactivationReasonToString(DeactivationReason a
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
//---------------------------------------------------------------------------------------------------------------------
// EphemeralKeyManager::Tap
#if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
Error EphemeralKeyManager::Tap::GenerateRandom(void)
{
Error error;
char checksum;
ClearAllBytes(mTap);
for (uint8_t index = 0; index < kLength - 1; index++)
{
SuccessOrExit(error = GenerateRandomDigit(mTap[index]));
}
IgnoreError(Utils::VerhoeffChecksum::Calculate(mTap, checksum));
mTap[kLength - 1] = checksum;
exit:
return error;
}
Error EphemeralKeyManager::Tap::GenerateRandomDigit(char &aChar)
{
static constexpr uint8_t kMaxValue = 250;
Error error;
uint8_t byte;
// To ensure uniform random distribution and avoid bias toward
// certain digit values, we ignore random `uint8` values of 250 or
// larger (i.e., values in the range [250-255]). This ensures the
// random `byte` is uniformly distributed in `[0-249]`, which,
// when `% 10`, gives us a uniform probability of `[0-9]` values.
do
{
SuccessOrExit(error = Random::Crypto::Fill(byte));
} while (byte >= kMaxValue);
aChar = '0' + (byte % 10);
exit:
return error;
}
Error EphemeralKeyManager::Tap::Validate(void) const
{
Error error;
VerifyOrExit(StringLength(mTap, kLength + 1) == kLength, error = kErrorInvalidArgs);
error = Utils::VerhoeffChecksum::Validate(mTap);
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
} // namespace BorderAgent
} // namespace MeshCoP
} // namespace ot
@@ -78,6 +78,43 @@ public:
kStateAccepted = OT_BORDER_AGENT_STATE_ACCEPTED, ///< Session connected and accepted as full commissioner.
};
#if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
/**
* Represents a Thread Administration One-Time Passcode (TAP).
*/
class Tap : public otBorderAgentEphemeralKeyTap
{
public:
static constexpr uint8_t kLength = OT_BORDER_AGENT_EPHEMERAL_KEY_TAP_STRING_LENGTH; ///< TAP string length.
/**
* Generates a cryptographically secure random Thread Administration One-Time Passcode (TAP) string.
*
* The TAP is a string of 9 characters, generated as a sequence of eight cryptographically secure random
* numeric digits [`0`-`9`] followed by a single check digit determined using the Verhoeff algorithm.
*
* @retval kErrorNone Successfully generated a random TAP.
* @retval kErrorFailed Failed to generate a random TAP.
*/
Error GenerateRandom(void);
/**
* Validates a given Thread Administration One-Time Passcode (TAP) string.
*
* Validates that the TAP string has the proper length, contains digit characters [`0`-`9`], and validates the
* Verhoeff checksum.
*
* @retval kErrorNone Successfully validated the TAP.
* @retval kErrorInvalidArgs The TAP string has an invalid length or contains non-digit characters.
* @retval kErrorFailed Checksum validation failed.
*/
Error Validate(void) const;
private:
Error GenerateRandomDigit(char &aChar);
};
#endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
/**
* Initializes the `EphemeralKeyManager`.
*
@@ -226,6 +263,9 @@ private:
} // namespace MeshCoP
DefineMapEnum(otBorderAgentEphemeralKeyState, MeshCoP::BorderAgent::EphemeralKeyManager::State);
#if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
DefineCoreType(otBorderAgentEphemeralKeyTap, MeshCoP::BorderAgent::EphemeralKeyManager::Tap);
#endif
} // namespace ot
@@ -135,6 +135,7 @@
#define OPENTHREAD_CONFIG_TREL_MANAGE_DNSSD_ENABLE 1
#define OPENTHREAD_CONFIG_TREL_USE_HEAP_ENABLE 1
#define OPENTHREAD_CONFIG_UPTIME_ENABLE 1
#define OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE 1
// CLI configs
#define OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH 800
+70
View File
@@ -757,6 +757,75 @@ void TestBorderAgentEphemeralKey(void)
VerifyOrQuit(node0.Get<Manager>().GetCounters().mEpskcInvalidArgsErrors == 2);
}
void TestBorderAgentEphemeralKeyTapGeneration(void)
{
static constexpr uint16_t kMaxRounds = 20;
using Tap = EphemeralKeyManager::Tap;
Core nexus;
Node &node = nexus.CreateNode();
Tap tap;
uint8_t index;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAgentEphemeralKeyTapGeneration");
nexus.AdvanceTime(0);
node.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(node.Get<Mle::Mle>().IsLeader());
for (uint16_t round = 0; round < kMaxRounds; round++)
{
SuccessOrQuit(tap.GenerateRandom());
SuccessOrQuit(tap.Validate());
Log("Generated random TAP: %s", tap.mTap);
// Tamper with one of the digits and ensure checksum fails.
index = round % Tap::kLength;
tap.mTap[index]++;
if (tap.mTap[index] > '9')
{
tap.mTap[index] = '0';
}
VerifyOrQuit(tap.Validate() == kErrorFailed);
}
// CHeck valid TAP strings (Thread spec (1.4.1d3) - section 8.4.9.7)
SuccessOrQuit(StringCopy(tap.mTap, "903723159"));
SuccessOrQuit(tap.Validate());
SuccessOrQuit(StringCopy(tap.mTap, "746351983"));
SuccessOrQuit(tap.Validate());
// Check invalid TAP strings.
SuccessOrQuit(StringCopy(tap.mTap, ""));
VerifyOrQuit(tap.Validate() == kErrorInvalidArgs);
SuccessOrQuit(StringCopy(tap.mTap, "1234"));
VerifyOrQuit(tap.Validate() == kErrorInvalidArgs);
SuccessOrQuit(StringCopy(tap.mTap, "12345678"));
VerifyOrQuit(tap.Validate() == kErrorInvalidArgs);
SuccessOrQuit(StringCopy(tap.mTap, "123456789"));
VerifyOrQuit(tap.Validate() == kErrorFailed);
SuccessOrQuit(StringCopy(tap.mTap, "a23456789"));
VerifyOrQuit(tap.Validate() == kErrorInvalidArgs);
SuccessOrQuit(StringCopy(tap.mTap, "12345678A"));
VerifyOrQuit(tap.Validate() == kErrorInvalidArgs);
}
EpskcEvent GetNewestEpskcEvent(Node &aNode)
{
const EpskcEvent *epskcEvent = nullptr;
@@ -1979,6 +2048,7 @@ int main(void)
{
ot::Nexus::TestBorderAgent();
ot::Nexus::TestBorderAgentEphemeralKey();
ot::Nexus::TestBorderAgentEphemeralKeyTapGeneration();
ot::Nexus::TestHistoryTrackerBorderAgentEpskcEvent();
ot::Nexus::TestBorderAgentTxtDataCallback();
ot::Nexus::TestBorderAgentServiceRegistration();