[border-agent] mechanism to use ephemeral key (#9435)

This commit adds a new mechanism in `BorderAgent` to allow the use of
ephemeral key. New `otBorderAgentSetEphemeralKey` API is added to
allow user to set an ephemeral key. The ephemeral key is used
instead of PSKc from Operation Dataset for a given timeout duration.
New API `otBorderAgentClearEphemeralKey` allows users to cancel the
ephemeral key before its timeout expires. While the timeout interval
is in effect, the ephemeral key can be used only once by an external
commissioner to connect. Once the commissioner disconnects, the
ephemeral key is cleared, and Border Agent reverts to using PSKc.

This commit adds a callback mechanism to signal changes related to the
Border Agent's (BA) use of an ephemeral key. It is invoked when the
BA starts/stops using the key, or when parameters (e.g., port number)
change.

This commit also adds CLI command under `ba ephemeralkey` for the new
APIs along with test script validating the new APIs.
This commit is contained in:
Abtin Keshavarzian
2024-03-05 13:53:31 -08:00
committed by GitHub
parent 88b2b5c621
commit 44c39060cd
16 changed files with 787 additions and 11 deletions
@@ -40,6 +40,7 @@
#define OPENTHREAD_CONFIG_ASSERT_ENABLE 1
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1
@@ -41,6 +41,7 @@
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 0
@@ -41,6 +41,7 @@
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 0
#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 0
+134
View File
@@ -57,6 +57,30 @@ extern "C" {
*/
#define OT_BORDER_AGENT_ID_LENGTH (16)
/**
* Minimum length of the ephemeral key string.
*
*/
#define OT_BORDER_AGENT_MIN_EPHEMERAL_KEY_LENGTH (6)
/**
* Maximum length of the ephemeral key string.
*
*/
#define OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_LENGTH (32)
/**
* Default ephemeral key timeout interval in milliseconds.
*
*/
#define OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT (2 * 60 * 1000u)
/**
* Maximum ephemeral key timeout interval in milliseconds.
*
*/
#define OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT (10 * 60 * 1000u)
/**
* @struct otBorderAgentId
*
@@ -109,6 +133,8 @@ uint16_t otBorderAgentGetUdpPort(otInstance *aInstance);
/**
* Gets the randomly generated Border Agent ID.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE`.
*
* The ID is saved in persistent storage and survives reboots. The typical use case of the ID is to
* be published in the MeshCoP mDNS service as the `id` TXT value for the client to identify this
* Border Router/Agent device.
@@ -127,6 +153,8 @@ otError otBorderAgentGetId(otInstance *aInstance, otBorderAgentId *aId);
/**
* Sets the Border Agent ID.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE`.
*
* The Border Agent ID will be saved in persistent storage and survive reboots. It's required to
* set the ID only once after factory reset. If the ID has never been set by calling this function,
* a random ID will be generated and returned when `otBorderAgentGetId` is called.
@@ -142,6 +170,112 @@ otError otBorderAgentGetId(otInstance *aInstance, otBorderAgentId *aId);
*/
otError otBorderAgentSetId(otInstance *aInstance, const otBorderAgentId *aId);
/**
* Sets the ephemeral key for a given timeout duration.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
*
* The ephemeral key can be set when the Border Agent is already running and is not currently connected to any external
* commissioner (i.e., it is in `OT_BORDER_AGENT_STATE_STARTED` state). Otherwise `OT_ERROR_INVALID_STATE` is returned.
*
* The given @p aKeyString is directly used as the ephemeral PSK (excluding the trailing null `\0` character ).
* The @p aKeyString length must be between `OT_BORDER_AGENT_MIN_EPHEMERAL_KEY_LENGTH` and
* `OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_LENGTH`, inclusive.
*
* Setting the ephemeral key again before a previously set key has timed out will replace the previously set key and
* reset the timeout.
*
* While the timeout interval is in effect, the ephemeral key can be used only once by an external commissioner to
* connect. Once the commissioner disconnects, the ephemeral key is cleared, and the Border Agent reverts to using
* PSKc.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aKeyString The ephemeral key string (used as PSK excluding the trailing null `\0` character).
* @param[in] aTimeout The timeout duration in milliseconds to use the ephemeral key.
* If zero, the default `OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT` value will be used.
* If the given timeout value is larger than `OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT`, the
* max value `OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT` will be used instead.
* @param[in] aUdpPort The UDP port to use with ephemeral key. If zero, an ephemeral port will be used.
* `otBorderAgentGetUdpPort()` will return the current UDP port being used.
*
* @retval OT_ERROR_NONE Successfully set the ephemeral key.
* @retval OT_ERROR_INVALID_STATE Border Agent is not running or it is connected to an external commissioner.
* @retval OT_ERROR_INVALID_ARGS The given @p aKeyString is not valid (too short or too long).
* @retval OT_ERROR_FAILED Failed to set the key (e.g., could not bind to UDP port).
*
*/
otError otBorderAgentSetEphemeralKey(otInstance *aInstance,
const char *aKeyString,
uint32_t aTimeout,
uint16_t aUdpPort);
/**
* Cancels the ephemeral key that is in use.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
*
* Can be used to cancel a previously set ephemeral key before it times out. If the Border Agent is not running or
* there is no ephemeral key in use, calling this function has no effect.
*
* If a commissioner is connected using the ephemeral key and is currently active, calling this function does not
* change its state. In this case the `otBorderAgentIsEphemeralKeyActive()` will continue to return `TRUE` until the
* commissioner disconnects.
*
* @param[in] aInstance The OpenThread instance.
*
*/
void otBorderAgentClearEphemeralKey(otInstance *aInstance);
/**
* Indicates whether or not an ephemeral key is currently active.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
*
* @param[in] aInstance The OpenThread instance.
*
* @retval TRUE An ephemeral key is active.
* @retval FALSE No ephemeral key is active.
*
*/
bool otBorderAgentIsEphemeralKeyActive(otInstance *aInstance);
/**
* Callback function pointer to signal changes related to the Border Agent's ephemeral key.
*
* This callback is invoked whenever:
*
* - The Border Agent starts using an ephemeral key.
* - Any parameter related to the ephemeral key, such as the port number, changes.
* - The Border Agent stops using the ephemeral key due to:
* - A direct call to `otBorderAgentClearEphemeralKey()`.
* - The ephemeral key timing out.
* - An external commissioner successfully using the key to connect and then disconnecting.
* - Reaching the maximum number of allowed failed connection attempts.
*
* Any OpenThread API, including `otBorderAgent` APIs, can be safely called from this callback.
*
* @param[in] aContext A pointer to an arbitrary context (provided when callback is set).
*
*/
typedef void (*otBorderAgentEphemeralKeyCallback)(void *aContext);
/**
* Sets the callback function used by the Border Agent to notify any changes related to use of ephemeral key.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
*
* A subsequent call to this function will replace any previously set callback.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aCallback The callback function pointer.
* @param[in] aContext The arbitrary context to use with callback.
*
*/
void otBorderAgentSetEphemeralKeyCallback(otInstance *aInstance,
otBorderAgentEphemeralKeyCallback aCallback,
void *aContext);
/**
* @}
*
+1 -1
View File
@@ -53,7 +53,7 @@ extern "C" {
* @note This number versions both OpenThread platform and user APIs.
*
*/
#define OPENTHREAD_API_VERSION (398)
#define OPENTHREAD_API_VERSION (399)
/**
* @addtogroup api-instance
+87
View File
@@ -358,12 +358,99 @@ Done
Print border agent state.
Possible states are
- `Stopped` : Border Agent is stopped.
- `Started` : Border Agent is running with no active connection with external commissioner.
- `Active` : Border Agent is running and is connected with an external commissioner.
```bash
> ba state
Started
Done
```
### ba ephemeralkey
Indicates if an ephemeral key is active.
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
```bash
> ba ephemeralkey
inactive
Done
> ba ephemeralkey set Z10X20g3J15w1000P60m16 1000
Done
> ba ephemeralkey
active
Done
```
### ba ephemeralkey set \<keystring\> \[timeout\] \[port\]
Sets the ephemeral key for a given timeout duration.
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
The ephemeral key can be set when Border Agent is already running and is not currently connected to any external commissioner (i.e., `ba state` gives `Started`).
The `keystring` string is directly used as the ephemeral PSK (excluding the trailing null `\0` character). Its length MUST be between 6 and 32, inclusive.
The `timeout` is in milliseconds. If not provided or set to zero, the default value of 2 minutes will be used. If the timeout value is larger than 10 minutes, the 10 minutes timeout value will be used instead.
The `port` specifies the UDP port to use with the ephemeral key. If UDP port is zero or is not provided, an ephemeral port will be used. `ba port` will give the current UDP port in use by the Border Agent.
Setting the ephemeral key again before a previously set one is timed out, will replace the previous one.
While the timeout interval is in effect, the ephemeral key can be used only once by an external commissioner to connect. Once the commissioner disconnects, the ephemeral key is cleared, and Border Agent reverts to using PSKc.
```bash
> ba ephemeralkey set Z10X20g3J15w1000P60m16 5000 1234
Done
```
### ba ephemeralkey clear
Cancels the ephemeral key in use if any.
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`.
Can be used to cancel a previously set ephemeral key before it is used or times out. If the Border Agent is not running or there is no ephemeral key in use, calling this function has no effect.
If a commissioner is connected using the ephemeral key and is currently active, calling this method does not change its state. In this case the `ba ephemeralkey` will continue to return `active` until the commissioner disconnects.
```bash
> ba ephemeralkey clear
Done
```
### ba ephemeralkey callback enable
Enables callback from Border Agent for ephemeral key state changes.
```bash
> ba ephemeralkey callback enable
Done
> ba ephemeralkey set W10X12 5000 49155
Done
BorderAgent callback: Ephemeral key active, port:49155
BorderAgent callback: Ephemeral key inactive
```
### ba ephemeralkey callback disable
Disables callback from Border Agent for ephemeral key state changes.
```bash
> ba ephemeralkey callback disable
Done
```
### bufferinfo
Show the current message buffer information.
+114
View File
@@ -610,6 +610,98 @@ template <> otError Interpreter::Process<Cmd("ba")>(Arg aArgs[])
}
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
else if (aArgs[0] == "ephemeralkey")
{
/**
* @cli ba ephemeralkey
* @code
* ba ephemeralkey
* active
* Done
* @endcode
* @par api_copy
* #otBorderAgentIsEphemeralKeyActive
*/
if (aArgs[1].IsEmpty())
{
OutputLine("%sactive", otBorderAgentIsEphemeralKeyActive(GetInstancePtr()) ? "" : "in");
}
/**
* @cli ba ephemeralkey set <keystring> [timeout-in-msec] [port]
* @code
* ba ephemeralkey set Z10X20g3J15w1000P60m16 5000 1234
* Done
* @endcode
* @par api_copy
* #otBorderAgentSetEphemeralKey
*/
else if (aArgs[1] == "set")
{
uint32_t timeout = 0;
uint16_t port = 0;
VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
if (!aArgs[3].IsEmpty())
{
SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout));
}
if (!aArgs[4].IsEmpty())
{
SuccessOrExit(error = aArgs[4].ParseAsUint16(port));
}
error = otBorderAgentSetEphemeralKey(GetInstancePtr(), aArgs[2].GetCString(), timeout, port);
}
/**
* @cli ba ephemeralkey clear
* @code
* ba ephemeralkey clear
* Done
* @endcode
* @par api_copy
* #otBorderAgentClearEphemeralKey
*/
else if (aArgs[1] == "clear")
{
otBorderAgentClearEphemeralKey(GetInstancePtr());
}
/**
* @cli ba ephemeralkey callback (enable, disable)
* @code
* ba ephemeralkey callback enable
* Done
* ba ephemeralkey set W10X1 5000 49155
* Done
* BorderAgent callback: Ephemeral key active, port:49155
* BorderAgent callback: Ephemeral key inactive
* @endcode
* @par api_copy
* #otBorderAgentSetEphemeralKeyCallback
*/
else if (aArgs[1] == "callback")
{
bool enable;
SuccessOrExit(error = ParseEnableOrDisable(aArgs[2], enable));
if (enable)
{
otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), HandleBorderAgentEphemeralKeyStateChange, this);
}
else
{
otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), nullptr, nullptr);
}
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
else
{
ExitNow(error = OT_ERROR_INVALID_COMMAND);
@@ -618,6 +710,28 @@ template <> otError Interpreter::Process<Cmd("ba")>(Arg aArgs[])
exit:
return error;
}
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void *aContext)
{
reinterpret_cast<Interpreter *>(aContext)->HandleBorderAgentEphemeralKeyStateChange();
}
void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void)
{
bool active = otBorderAgentIsEphemeralKeyActive(GetInstancePtr());
OutputFormat("BorderAgent callback: Ephemeral key %sactive", active ? "" : "in");
if (active)
{
OutputFormat(", port:%u", otBorderAgentGetUdpPort(GetInstancePtr()));
}
OutputNewLine();
}
#endif
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+5
View File
@@ -495,6 +495,11 @@ private:
void HandleSntpResponse(uint64_t aTime, otError aResult);
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
static void HandleBorderAgentEphemeralKeyStateChange(void *aContext);
void HandleBorderAgentEphemeralKeyStateChange(void);
#endif
static void HandleDetachGracefullyResult(void *aContext);
void HandleDetachGracefullyResult(void);
+31
View File
@@ -64,4 +64,35 @@ uint16_t otBorderAgentGetUdpPort(otInstance *aInstance)
return AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().GetUdpPort();
}
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
otError otBorderAgentSetEphemeralKey(otInstance *aInstance,
const char *aKeyString,
uint32_t aTimeout,
uint16_t aUdpPort)
{
AssertPointerIsNotNull(aKeyString);
return AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().SetEphemeralKey(aKeyString, aTimeout, aUdpPort);
}
void otBorderAgentClearEphemeralKey(otInstance *aInstance)
{
AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().ClearEphemeralKey();
}
bool otBorderAgentIsEphemeralKeyActive(otInstance *aInstance)
{
return AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().IsEphemeralKeyActive();
}
void otBorderAgentSetEphemeralKeyCallback(otInstance *aInstance,
otBorderAgentEphemeralKeyCallback aCallback,
void *aContext)
{
AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().SetEphemeralKeyCallback(aCallback, aContext);
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
+11
View File
@@ -75,6 +75,17 @@
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#endif
/**
* @def OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
*
* Define to 1 to enable ephemeral key mechanism and its APIs in Border Agent.
*
*/
#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE \
(OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_3_1)
#endif
/**
* @}
*
+157 -9
View File
@@ -239,6 +239,12 @@ BorderAgent::BorderAgent(Instance &aInstance)
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
, mIdInitialized(false)
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
, mUsingEphemeralKey(false)
, mOldUdpPort(0)
, mEphemeralKeyTimer(aInstance)
, mEphemeralKeyTask(aInstance)
#endif
{
mCommissionerAloc.InitAsThreadOriginMeshLocal();
}
@@ -599,25 +605,55 @@ void BorderAgent::HandleConnected(bool aConnected)
LogInfo("Commissioner disconnected");
IgnoreError(Get<Ip6::Udp>().RemoveReceiver(mUdpReceiver));
Get<ThreadNetif>().RemoveUnicastAddress(mCommissionerAloc);
mState = kStateStarted;
mUdpProxyPort = 0;
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
if (mUsingEphemeralKey)
{
RestartAfterRemovingEphemeralKey();
}
else
#endif
{
mState = kStateStarted;
mUdpProxyPort = 0;
}
}
}
uint16_t BorderAgent::GetUdpPort(void) const { return Get<Tmf::SecureAgent>().GetUdpPort(); }
void BorderAgent::Start(void)
Error BorderAgent::Start(uint16_t aUdpPort)
{
Error error;
Pskc pskc;
VerifyOrExit(mState == kStateStopped, error = kErrorNone);
Get<KeyManager>().GetPskc(pskc);
SuccessOrExit(error = Get<Tmf::SecureAgent>().Start(kUdpPort));
SuccessOrExit(error = Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
error = Start(aUdpPort, pskc.m8, Pskc::kSize);
pskc.Clear();
return error;
}
Error BorderAgent::Start(uint16_t aUdpPort, const uint8_t *aPsk, uint8_t aPskLength)
{
Error error = kErrorNone;
VerifyOrExit(mState == kStateStopped);
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
if (mUsingEphemeralKey)
{
SuccessOrExit(error = Get<Tmf::SecureAgent>().Start(aUdpPort, kMaxEphemeralKeyConnectionAttempts,
HandleSecureAgentStopped, this));
}
else
#endif
{
SuccessOrExit(error = Get<Tmf::SecureAgent>().Start(aUdpPort));
}
SuccessOrExit(error = Get<Tmf::SecureAgent>().SetPsk(aPsk, aPskLength));
Get<Tmf::SecureAgent>().SetConnectedCallback(HandleConnected, this);
mState = kStateStarted;
@@ -627,6 +663,7 @@ void BorderAgent::Start(void)
exit:
LogError("start agent", error);
return error;
}
void BorderAgent::HandleTimeout(void)
@@ -642,18 +679,129 @@ void BorderAgent::Stop(void)
{
VerifyOrExit(mState != kStateStopped);
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
if (mUsingEphemeralKey)
{
mUsingEphemeralKey = false;
mEphemeralKeyTimer.Stop();
mEphemeralKeyTask.Post();
}
#endif
mTimer.Stop();
Get<Tmf::SecureAgent>().Stop();
mState = kStateStopped;
mUdpProxyPort = 0;
LogInfo("Border Agent stopped");
exit:
return;
}
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
Error BorderAgent::SetEphemeralKey(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort)
{
Error error = kErrorNone;
uint16_t length = StringLength(aKeyString, kMaxEphemeralKeyLength + 1);
VerifyOrExit(mState == kStateStarted, error = kErrorInvalidState);
VerifyOrExit((length >= kMinEphemeralKeyLength) && (length <= kMaxEphemeralKeyLength), error = kErrorInvalidArgs);
if (!mUsingEphemeralKey)
{
mOldUdpPort = GetUdpPort();
}
Stop();
// We set the `mUsingEphemeralKey` before `Start()` since
// callbacks (like `HandleConnected()`) may be invoked from
// `Start()` itself.
mUsingEphemeralKey = true;
error = Start(aUdpPort, reinterpret_cast<const uint8_t *>(aKeyString), static_cast<uint8_t>(length));
if (error != kErrorNone)
{
mUsingEphemeralKey = false;
IgnoreError(Start(mOldUdpPort));
ExitNow();
}
mEphemeralKeyTask.Post();
if (aTimeout == 0)
{
aTimeout = kDefaultEphemeralKeyTimeout;
}
aTimeout = Min(aTimeout, kMaxEphemeralKeyTimeout);
mEphemeralKeyTimer.Start(aTimeout);
LogInfo("Allow ephemeral key for %lu msec on port %u", ToUlong(aTimeout), GetUdpPort());
exit:
return error;
}
void BorderAgent::ClearEphemeralKey(void)
{
VerifyOrExit(mUsingEphemeralKey);
LogInfo("Clearing ephemeral key");
mEphemeralKeyTimer.Stop();
switch (mState)
{
case kStateStarted:
RestartAfterRemovingEphemeralKey();
break;
case kStateStopped:
case kStateActive:
// If there is an active commissioner connection, we wait till
// it gets disconnected before removing ephemeral key and
// restarting the agent.
break;
}
exit:
return;
}
void BorderAgent::HandleEphemeralKeyTimeout(void)
{
LogInfo("Ephemeral key timed out");
ClearEphemeralKey();
}
void BorderAgent::InvokeEphemeralKeyCallback(void) { mEphemeralKeyCallback.InvokeIfSet(); }
void BorderAgent::RestartAfterRemovingEphemeralKey(void)
{
LogInfo("Removing ephemeral key and restarting agent");
Stop();
IgnoreError(Start(mOldUdpPort));
}
void BorderAgent::HandleSecureAgentStopped(void *aContext)
{
reinterpret_cast<BorderAgent *>(aContext)->HandleSecureAgentStopped();
}
void BorderAgent::HandleSecureAgentStopped(void)
{
LogInfo("Reached max allowed connection attempts with ephemeral key");
RestartAfterRemovingEphemeralKey();
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN)
void BorderAgent::LogError(const char *aActionText, Error aError)
{
+125 -1
View File
@@ -45,6 +45,8 @@
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/tasklet.hpp"
#include "meshcop/secure_transport.hpp"
#include "net/udp6.hpp"
#include "thread/tmf.hpp"
#include "thread/uri_paths.hpp"
@@ -60,6 +62,30 @@ class BorderAgent : public InstanceLocator, private NonCopyable
friend class Tmf::SecureAgent;
public:
/**
* Minimum length of the ephemeral key string.
*
*/
static constexpr uint16_t kMinEphemeralKeyLength = OT_BORDER_AGENT_MIN_EPHEMERAL_KEY_LENGTH;
/**
* Maximum length of the ephemeral key string.
*
*/
static constexpr uint16_t kMaxEphemeralKeyLength = OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_LENGTH;
/**
* Default ephemeral key timeout interval in milliseconds.
*
*/
static constexpr uint32_t kDefaultEphemeralKeyTimeout = OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT;
/**
* Maximum ephemeral key timeout interval in milliseconds.
*
*/
static constexpr uint32_t kMaxEphemeralKeyTimeout = OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT;
typedef otBorderAgentId Id; ///< Border Agent ID.
/**
@@ -125,7 +151,7 @@ public:
* Starts the Border Agent service.
*
*/
void Start(void);
void Start(void) { IgnoreError(Start(kUdpPort)); }
/**
* Stops the Border Agent service.
@@ -141,6 +167,75 @@ public:
*/
State GetState(void) const { return mState; }
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
/**
* Sets the ephemeral key for a given timeout duration.
*
* The ephemeral key can be set when the Border Agent is already running and is not currently connected to any
* external commissioner (i.e., it is in `kStateStarted` state).
*
* The given @p aKeyString is directly used as the ephemeral PSK (excluding the trailing null `\0` character). Its
* length must be between `kMinEphemeralKeyLength` and `kMaxEphemeralKeyLength`, inclusive.
*
* Setting the ephemeral key again before a previously set one is timed out will replace the previous one and will
* reset the timeout.
*
* While the timeout interval is in effect, the ephemeral key can be used only once by an external commissioner to
* connect. Once the commissioner disconnects, the ephemeral key is cleared, and Border Agent reverts to using
* PSKc.
*
* @param[in] aKeyString The ephemeral key.
* @param[in] aTimeout The timeout duration in milliseconds to use the ephemeral key.
* If zero, the default `kDefaultEphemeralKeyTimeout` value will be used.
* If the timeout value is larger than `kMaxEphemeralKeyTimeout`, the max value will be
* used instead.
* @param[in] aUdpPort The UDP port to use with ephemeral key. If UDP port is zero, an ephemeral port will be
* used. `GetUdpPort()` will return the current UDP port being used.
*
* @retval kErrorNone Successfully set the ephemeral key.
* @retval kErrorInvalidState Agent is not running or connected to external commissioner.
* @retval kErrorInvalidArgs The given @p aKeyString is not valid.
* @retval kErrorFailed Failed to set the key (e.g., could not bind to UDP port).
*
*/
Error SetEphemeralKey(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort);
/**
* Cancels the ephemeral key in use if any.
*
* Can be used to cancel a previously set ephemeral key before it times out. If the Border Agent is not running or
* there is no ephemeral key in use, calling this function has no effect.
*
* If a commissioner is connected using the ephemeral key and is currently active, calling this method does not
* change its state. In this case the `IsEphemeralKeyActive()` will continue to return `true` until the commissioner
* disconnects.
*
*/
void ClearEphemeralKey(void);
/**
* Indicates whether or not an ephemeral key is currently active.
*
* @retval TRUE An ephemeral key is active.
* @retval FALSE No ephemeral key is active.
*
*/
bool IsEphemeralKeyActive(void) const { return mUsingEphemeralKey; }
/**
* Callback function pointer to notify when there is any changes related to use of ephemeral key by Border Agent.
*
*
*/
typedef otBorderAgentEphemeralKeyCallback EphemeralKeyCallback;
void SetEphemeralKeyCallback(EphemeralKeyCallback aCallback, void *aContext)
{
mEphemeralKeyCallback.Set(aCallback, aContext);
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
/**
* Returns the UDP Proxy port to which the commissioner is currently
* bound.
@@ -151,9 +246,16 @@ public:
uint16_t GetUdpProxyPort(void) const { return mUdpProxyPort; }
private:
static_assert(kMaxEphemeralKeyLength <= SecureTransport::kPskMaxLength,
"Max ephemeral key length is larger than max PSK len");
static constexpr uint16_t kUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT;
static constexpr uint32_t kKeepAliveTimeout = 50 * 1000; // Timeout to reject a commissioner (in msec)
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
static constexpr uint16_t kMaxEphemeralKeyConnectionAttempts = 10;
#endif
class ForwardContext : public InstanceLocatorInit, public Heap::Allocatable<ForwardContext>
{
public:
@@ -171,6 +273,9 @@ private:
uint8_t mToken[Coap::Message::kMaxTokenLength]; // The CoAP Token of the original request.
};
Error Start(uint16_t aUdpPort);
Error Start(uint16_t aUdpPort, const uint8_t *aPsk, uint8_t aPskLength);
void HandleNotifierEvents(Events aEvents);
Coap::Message::Code CoapCodeFromError(Error aError);
@@ -185,6 +290,14 @@ private:
void HandleTimeout(void);
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
void RestartAfterRemovingEphemeralKey(void);
void HandleEphemeralKeyTimeout(void);
void InvokeEphemeralKeyCallback(void);
static void HandleSecureAgentStopped(void *aContext);
void HandleSecureAgentStopped(void);
#endif
static void HandleCoapResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
@@ -202,6 +315,10 @@ private:
#endif
using TimeoutTimer = TimerMilliIn<BorderAgent, &BorderAgent::HandleTimeout>;
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
using EphemeralKeyTimer = TimerMilliIn<BorderAgent, &BorderAgent::HandleEphemeralKeyTimeout>;
using EphemeralKeyTask = TaskletIn<BorderAgent, &BorderAgent::InvokeEphemeralKeyCallback>;
#endif
State mState;
uint16_t mUdpProxyPort;
@@ -212,6 +329,13 @@ private:
Id mId;
bool mIdInitialized;
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
bool mUsingEphemeralKey;
uint16_t mOldUdpPort;
EphemeralKeyTimer mEphemeralKeyTimer;
EphemeralKeyTask mEphemeralKeyTask;
Callback<EphemeralKeyCallback> mEphemeralKeyCallback;
#endif
};
DeclareTmfHandler(BorderAgent, kUriRelayRx);
+18
View File
@@ -471,6 +471,24 @@ class Node(object):
def get_mle_adv_imax(self):
return self._cli_single_output('mleadvimax')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Border Agent
def ba_get_state(self):
return self._cli_single_output('ba state')
def ba_get_port(self):
return self._cli_single_output('ba port')
def ba_is_ephemeral_key_active(self):
return self._cli_single_output('ba ephemeralkey')
def ba_set_ephemeral_key(self, keystring, timeout=None, port=None):
self._cli_no_output('ba ephemeralkey set', keystring, timeout, port)
def ba_clear_ephemeral_key(self):
self._cli_no_output('ba ephemeralkey clear')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# UDP
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
#
# Copyright (c) 2023, 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.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Validate changes to `IntervalMax` for MLE Advertisement Trickle Timer based on number of
# router neighbors of the device.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 20
cli.Node.set_time_speedup_factor(speedup)
leader = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
leader.form('ba-ephemeral')
verify(leader.get_state() == 'leader')
verify(leader.ba_is_ephemeral_key_active() == 'inactive')
port = int(leader.ba_get_port())
leader.ba_set_ephemeral_key('password', 10000, 1234)
time.sleep(0.1)
verify(leader.ba_is_ephemeral_key_active() == 'active')
verify(int(leader.ba_get_port()) == 1234)
leader.ba_set_ephemeral_key('password2', 200, 45678)
time.sleep(0.100 / speedup)
verify(leader.ba_is_ephemeral_key_active() == 'active')
verify(int(leader.ba_get_port()) == 45678)
time.sleep(0.150 / speedup)
verify(leader.ba_is_ephemeral_key_active() == 'inactive')
verify(int(leader.ba_get_port()) == port)
leader.ba_set_ephemeral_key('newkey')
verify(leader.ba_is_ephemeral_key_active() == 'active')
time.sleep(0.1)
verify(leader.ba_is_ephemeral_key_active() == 'active')
leader.ba_clear_ephemeral_key()
verify(leader.ba_is_ephemeral_key_active() == 'inactive')
verify(int(leader.ba_get_port()) == port)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
@@ -65,6 +65,10 @@
#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#define OPENTHREAD_CONFIG_DIAG_ENABLE 1
#define OPENTHREAD_CONFIG_JOINER_ENABLE 1
+1
View File
@@ -192,6 +192,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-025-mesh-local-prefix-change.py
run cli/test-026-coaps-conn-limit.py
run cli/test-027-slaac-address.py
run cli/test-028-border-agent-ephemeral-key.py
run cli/test-400-srp-client-server.py
run cli/test-601-channel-manager-channel-change.py
# Skip the "channel-select" test on a TREL only radio link, since it