diff --git a/include/openthread/border_agent.h b/include/openthread/border_agent.h index 94287b5cbea..a9300cd3fdc 100644 --- a/include/openthread/border_agent.h +++ b/include/openthread/border_agent.h @@ -55,26 +55,6 @@ 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) - /** * Represents a Border Agent Identifier. */ @@ -84,15 +64,10 @@ typedef struct otBorderAgentId } otBorderAgentId; /** - * Defines the Border Agent state. + * Defines Border Agent counters. + * + * The `mEpskc` related counters require `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. */ -typedef enum otBorderAgentState -{ - OT_BORDER_AGENT_STATE_STOPPED = 0, ///< Border agent role is disabled. - OT_BORDER_AGENT_STATE_STARTED = 1, ///< Border agent is started. - OT_BORDER_AGENT_STATE_ACTIVE = 2, ///< Border agent is connected with external commissioner. -} otBorderAgentState; - typedef struct otBorderAgentCounters { uint32_t mEpskcActivations; ///< The number of ePSKc activations @@ -106,13 +81,11 @@ typedef struct otBorderAgentCounters uint32_t mEpskcSecureSessionSuccesses; ///< The number of established secure sessions with ePSKc uint32_t mEpskcSecureSessionFailures; ///< The number of failed secure sessions with ePSKc uint32_t mEpskcCommissionerPetitions; ///< The number of successful commissioner petitions with ePSKc - - uint32_t mPskcSecureSessionSuccesses; ///< The number of established secure sessions with PSKc - uint32_t mPskcSecureSessionFailures; ///< The number of failed secure sessions with PSKc - uint32_t mPskcCommissionerPetitions; ///< The number of successful commissioner petitions with PSKc - - uint32_t mMgmtActiveGets; ///< The number of MGMT_ACTIVE_GET.req sent over secure sessions - uint32_t mMgmtPendingGets; ///< The number of MGMT_PENDING_GET.req sent over secure sessions + uint32_t mPskcSecureSessionSuccesses; ///< The number of established secure sessions with PSKc + uint32_t mPskcSecureSessionFailures; ///< The number of failed secure sessions with PSKc + uint32_t mPskcCommissionerPetitions; ///< The number of successful commissioner petitions with PSKc + uint32_t mMgmtActiveGets; ///< The number of MGMT_ACTIVE_GET.req sent over secure sessions + uint32_t mMgmtPendingGets; ///< The number of MGMT_PENDING_GET.req sent over secure sessions } otBorderAgentCounters; /** @@ -125,13 +98,21 @@ typedef struct otBorderAgentCounters const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance); /** - * Gets the #otBorderAgentState of the Thread Border Agent role. + * Indicates whether or not the Border Agent service is active and running. + * + * While the Border Agent is active, external commissioner candidates can try to connect to and establish secure DTLS + * sessions with the Border Agent using PSKc. A connected commissioner can then petition to become a full commissioner. + * + * If `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE` is enabled, independent and separate DTLS transport and + * sessions are used for the ephemeral key. Therefore, the ephemeral key and Border Agent service can be enabled and + * used in parallel. * * @param[in] aInstance A pointer to an OpenThread instance. * - * @returns The current #otBorderAgentState of the Border Agent. + * @retval TRUE The Border Agent is active. + * @retval FALSE The Border Agent is not active. */ -otBorderAgentState otBorderAgentGetState(otInstance *aInstance); +bool otBorderAgentIsActive(otInstance *aInstance); /** * Gets the UDP port of the Thread Border Agent service. @@ -180,117 +161,138 @@ otError otBorderAgentGetId(otInstance *aInstance, otBorderAgentId *aId); */ otError otBorderAgentSetId(otInstance *aInstance, const otBorderAgentId *aId); +/*-------------------------------------------------------------------------------------------------------------------- + * Border Agent Ephemeral Key feature */ + +/** + * 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) + /** - * Indicates whether the Border Agent Ephemeral Key feature is enabled. + * Represents Border Agent's Ephemeral Key Manager state. + */ +typedef enum otBorderAgentEphemeralKeyState +{ + OT_BORDER_AGENT_STATE_DISABLED = 0, ///< Ephemeral Key Manager is disabled. + OT_BORDER_AGENT_STATE_STOPPED = 1, ///< Enabled, but no ephemeral key is in use (not set or started). + OT_BORDER_AGENT_STATE_STARTED = 2, ///< Ephemeral key is set. Listening to accept secure connections. + OT_BORDER_AGENT_STATE_CONNECTED = 3, ///< Session is established with an external commissioner candidate. + OT_BORDER_AGENT_STATE_ACCEPTED = 4, ///< Session is established and candidate is accepted as full commissioner. +} otBorderAgentEphemeralKeyState; + +/** + * Gets the state of Border Agent's Ephemeral Key Manager. * - * The Ephemeral Key feature can only be used when it's enabled. This information will be displayed in a bitmap in the - * txt records of the meshcop service published by this Border Router. + * Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. * - * The default value is `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT`. + * @param[in] aInstance A pointer to an OpenThread instance. * - * @param[in] aInstance The OpenThread instance. + * @returns The current state of Ephemeral Key Manager. */ -bool otBorderAgentIsEphemeralKeyFeatureEnabled(otInstance *aInstance); +otBorderAgentEphemeralKeyState otBorderAgentEphemeralKeyGetState(otInstance *aInstance); /** - * Enables/disables the Border Agent Ephemeral Key feature. + * Enables/disables the Border Agent's Ephemeral Key Manager. + * + * Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. * - * If an ephemeral key is already active and then this method is called to disable the feature, the in-use ephemeral - * key will be cleared. + * If this function is called to disable, while an an ephemeral key is in use, the ephemeral key use will be stopped + * (as if `otBorderAgentEphemeralKeyStop()` is called). * * @param[in] aInstance The OpenThread instance. - * @param[in] aEnabled Whether to enable the BA Ephemeral Key feature. + * @param[in] aEnabled Whether to enable or disable the Ephemeral Key Manager. */ -void otBorderAgentSetEphemeralKeyFeatureEnabled(otInstance *aInstance, bool aEnabled); +void otBorderAgentEphemeralKeySetEnabled(otInstance *aInstance, bool aEnabled); /** - * Sets the ephemeral key for a given timeout duration. + * Starts using an ephemeral key for a given timeout duration. * * Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. * - * The API SHOULD only be called when the Ephemeral Key feature is enabled (which can be set by - * `otBorderAgentSetEphemeralKeyFeatureEnabled`) or configured as enabled by default. + * An ephemeral key can only be set when `otBorderAgentEphemeralKeyGetState()` is `OT_BORDER_AGENT_STATE_STOPPED`, + * i.e., enabled but not yet started. Otherwise, `OT_ERROR_INVALID_STATE` is returned. This means that setting the + * ephemeral key again while a previously set key is still in use will fail. Callers can stop the previous key by + * calling `otBorderAgentEphemeralKeyStop()` before starting with a new key. * - * 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. - * To terminate active commissioner sessions, use the `otBorderAgentDisconnect()` API. + * The Ephemeral Key Manager and the Border Agent service (which uses PSKc) can be enabled and used in parallel, as + * they use independent and separate DTLS transport and sessions. * - * 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. + * The given @p aKeyString is used directly as the ephemeral PSK (excluding the trailing null `\0` character). + * Its length must be between `OT_BORDER_AGENT_MIN_EPHEMERAL_KEY_LENGTH` and `OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_LENGTH`, + * inclusive. Otherwise `OT_ERROR_INVALID_ARGS` is returned. * - * Setting the ephemeral key again before a previously set key has timed out will replace the previously set key and - * reset the timeout. + * When successfully set, the ephemeral key can be used only once by an external commissioner candidate to establish a + * secure session. After the commissioner candidate disconnects, the use of the ephemeral key is stopped. If the + * timeout expires, the use of the ephemeral key is stopped, and any connected session using the key is immediately + * disconnected. * - * During the timeout interval, the ephemeral key can be used only once by an external commissioner to establish a - * connection. After the commissioner disconnects, the ephemeral key is cleared, and the Border Agent reverts to - * using PSKc. If the timeout expires while a commissioner is still connected, the session will be terminated, and the - * Border Agent will cease using the ephemeral key and revert to PSKc. + * The Ephemeral Key Manager limits the number of failed DTLS connections to 10 attempts. After the 10th failed + * attempt, the use of the ephemeral key is automatically stopped (even if the timeout has not yet expired). * * @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_NOT_CAPABLE The Ephemeral Key feature is not enabled. - * @retval OT_ERROR_FAILED Failed to set the key (e.g., could not bind to UDP port). + * @param[in] aKeyString The ephemeral key. + * @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 is used. If the + * timeout value is larger than `OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT`, the maximum value + * is used instead. + * @param[in] aUdpPort The UDP port to use with the ephemeral key. If the UDP port is zero, an ephemeral port will + * be used. `otBorderAgentEphemeralKeyGetUdpPort()` returns the current UDP port being used. + * + * @retval OT_ERROR_NONE Successfully started using the ephemeral key. + * @retval OT_ERROR_INVALID_STATE A previously set ephemeral key is still in use or the feature is disabled. + * @retval OT_ERROR_INVALID_ARGS The given @p aKeyString is not valid. + * @retval OT_ERROR_FAILED Failed to start (e.g., it could not bind to the given UDP port). */ -otError otBorderAgentSetEphemeralKey(otInstance *aInstance, - const char *aKeyString, - uint32_t aTimeout, - uint16_t aUdpPort); +otError otBorderAgentEphemeralKeyStart(otInstance *aInstance, + const char *aKeyString, + uint32_t aTimeout, + uint16_t aUdpPort); /** - * Cancels the ephemeral key that is in use. + * Stops the ephemeral key use and disconnects any session using it. * * 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, or the ephemeral key timeout expires. To terminate active commissioner sessions, use the - * `otBorderAgentDisconnect()` API. + * If there is no ephemeral key in use, calling this function has no effect. * * @param[in] aInstance The OpenThread instance. */ -void otBorderAgentClearEphemeralKey(otInstance *aInstance); +void otBorderAgentEphemeralKeyStop(otInstance *aInstance); /** - * Indicates whether or not an ephemeral key is currently active. + * Gets the UDP port used by Border Agent's Ephemeral Key Manager. * * Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. * - * @param[in] aInstance The OpenThread instance. + * The port is applicable if an ephemeral key is in use, i.e., the state is not `OT_BORDER_AGENT_STATE_DISABLED` or + * `OT_BORDER_AGENT_STATE_STOPPED`. + * + * @param[in] aInstance A pointer to an OpenThread instance. * - * @retval TRUE An ephemeral key is active. - * @retval FALSE No ephemeral key is active. + * @returns The UDP port being used by Border Agent's Ephemeral Key Manager (when active). */ -bool otBorderAgentIsEphemeralKeyActive(otInstance *aInstance); +uint16_t otBorderAgentEphemeralKeyGetUdpPort(otInstance *aInstance); /** - * Callback function pointer to signal changes related to the Border Agent's ephemeral key. - * - * This callback is invoked whenever: + * Callback function pointer to signal state changes to the Border Agent's Ephemeral Key Manager. * - * - The Border Agent starts using an ephemeral key. - * - Any parameter related to the ephemeral key, such as the port number, changes. - * - A commissioner candidate successfully establishes a secure session with the Border Agent using the ephemeral key. - * This situation can be identified by `otBorderAgentGetState()` being `OT_BORDER_AGENT_STATE_ACTIVE` (this event - * can be used to stop advertising the mDNS service "_meshcop-e._udp"). - * - 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. + * This callback is invoked whenever the `otBorderAgentEphemeralKeyGetState()` gets changed. * * Any OpenThread API, including `otBorderAgent` APIs, can be safely called from this callback. * @@ -299,7 +301,7 @@ bool otBorderAgentIsEphemeralKeyActive(otInstance *aInstance); typedef void (*otBorderAgentEphemeralKeyCallback)(void *aContext); /** - * Sets the callback function used by the Border Agent to notify any changes related to use of ephemeral key. + * Sets the callback function to notify state changes of Border Agent's Ephemeral Key Manager. * * Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. * @@ -309,22 +311,18 @@ typedef void (*otBorderAgentEphemeralKeyCallback)(void *aContext); * @param[in] aCallback The callback function pointer. * @param[in] aContext The arbitrary context to use with callback. */ -void otBorderAgentSetEphemeralKeyCallback(otInstance *aInstance, +void otBorderAgentEphemeralKeySetCallback(otInstance *aInstance, otBorderAgentEphemeralKeyCallback aCallback, void *aContext); /** - * Disconnects the Border Agent from any active secure sessions. + * Converts a given `otBorderAgentEphemeralKeyState` to a human-readable string. * - * If Border Agent is connected to a commissioner candidate with ephemeral key, calling this API - * will cause the ephemeral key to be cleared after the session is disconnected. + * @param[in] aState The state to convert. * - * The Border Agent state may not change immediately upon calling this method. The state will be - * updated when the connection update is notified with a delay. - * - * @param[in] aInstance The OpenThread instance. + * @returns Human-readable string corresponding to @p aState. */ -void otBorderAgentDisconnect(otInstance *aInstance); +const char *otBorderAgentEphemeralKeyStateToString(otBorderAgentEphemeralKeyState aState); /** * @} diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 700299dd1b5..abe5496ea4e 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (474) +#define OPENTHREAD_API_VERSION (475) /** * @addtogroup api-instance diff --git a/src/cli/README.md b/src/cli/README.md index 9eaa4974f30..64b9802f4d1 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -364,7 +364,7 @@ Show current Border Agent information. ### ba port -Print border agent service port. +Print Border Agent's service port. ```bash > ba port @@ -374,135 +374,156 @@ Done ### ba state -Print border agent state. +Print Border Agent's 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. +- `Active`: Border Agent is active. +- `Inactive`: Border Agent is not active. ```bash > ba state -Started -Done -``` - -### ba disconnect - -Disconnects border agent from any active secure sessions. - -```bash -> ba disconnect +Active Done ``` ### ba ephemeralkey -Indicates if an ephemeral key is active. +Print the Border Agent's Ephemeral Key Manager state. Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. +Possible states are + +- `Disabled`: Ephemeral Key Manager is disabled. +- `Stopped`: Enabled but no key is in use (not yet set or started). +- `Started`: Ephemeral key is set. Listening to accept secure connections from commissioner candidates. +- `Connected`: Secure session is established with an external commissioner candidate. Not yet accepted as full commissioner. +- `Accepted`: Secure session is established and external candidate is accepted as full commissioner. + ```bash > ba ephemeralkey -inactive +Stopped Done -> ba ephemeralkey set Z10X20g3J15w1000P60m16 1000 +> ba ephemeralkey start Z10X20g3J15w1000P60m16 1000 Done > ba ephemeralkey -active +Started Done ``` -### ba ephemeralkey set \ \[timeout\] \[port\] +### ba ephemeralkey enable -Sets the ephemeral key for a given timeout duration. +Enables the Border agent's Ephemeral Key Manager. 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. +```bash +> ba ephemeralkey enable +Done +``` -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. +### ba ephemeralkey disable -Setting the ephemeral key again before a previously set one is timed out, will replace the previous one. +Disables the Border Agent's Ephemeral Key Manager. -During the timeout interval, the ephemeral key can be used only once by an external commissioner to establish a connection. After the commissioner disconnects, the ephemeral key is cleared, and the Border Agent reverts to using PSKc. If the timeout expires while a commissioner is still connected, the session will be terminated, and the Border Agent will cease using the ephemeral key and revert to PSKc. +Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. ```bash -> ba ephemeralkey set Z10X20g3J15w1000P60m16 5000 1234 +> ba ephemeralkey disable +Done + +> ba ephemeralkey +Disabled Done ``` -### ba ephemeralkey clear +### ba ephemeralkey start \ \[timeout\] \[port\] -Cancels the ephemeral key in use if any. +Starts using an ephemeral key for a given timeout duration. 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. +An ephemeral key can only be set when current state is `Stopped`, i.e., it is enabled but not yet started. This means that setting the ephemeral key again while a previously set key is still in use will fail. Callers can stop the previous key using `ba ephemeralkey stop` before starting with a new key. -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. +The Ephemeral Key Manager and the Border Agent service (which uses PSKc) can be enabled and used in parallel, as they use independent and separate DTLS transport and sessions. -```bash -> ba ephemeralkey clear -Done -``` +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. -### ba ephemeralkey callback enable +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 ephemeralkey port` will give the current UDP port in use. -Enables callback from Border Agent for ephemeral key state changes. +When successfully set, the ephemeral key can be used only once by an external commissioner candidate to establish a secure session. After the commissioner candidate disconnects, the use of the ephemeral key is stopped. If the timeout expires, the use of the ephemeral key is stopped, and any connected session using the key is immediately disconnected. + +The Ephemeral Key Manager limits the number of failed DTLS connections to 10 attempts. After the 10th failed attempt, the use of the ephemeral key is automatically stopped (even if the timeout has not yet expired). ```bash -> ba ephemeralkey callback enable +> ba ephemeralkey start Z10X20g3J15w1000P60m16 5000 1234 Done -> ba ephemeralkey set W10X12 5000 49155 +> ba ephemeralkey +Started Done -BorderAgent callback: Ephemeral key active, port:49155 -BorderAgent callback: Ephemeral key inactive +> ba ephemeralkey port +1234 +Done ``` -### ba ephemeralkey callback disable +### ba ephemeralkey stop -Disables callback from Border Agent for ephemeral key state changes. +Stops the ephemeral key use and disconnects any session using it. + +Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. + +If there is no ephemeral key in use, calling this function has no effect. ```bash -> ba ephemeralkey callback disable +> ba ephemeralkey stop Done ``` -### ba ephemeralkey feature +### ba ephemeralkey port + +Print the port number in use by Ephemeral Key Manager. -Displays if the Ephemeral Key feature is enabled. Note that this indicates whether the ephemeral key feature is ready to use, instead of whether an ephemeral key is active. +Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. ```bash -> ba ephemeralkey feature -Enabled +> ba ephemeralkey port +1234 Done ``` -### ba ephemeralkey feature enable +### ba ephemeralkey callback enable -Enables the Ephemeral Key feature. +Enables callback from Border Agent to be notified of state changes of Border Agent's Ephemeral Key Manager. + +Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. ```bash -> ba ephemeralkey feature enable +> ba ephemeralkey callback enable Done + +> ba ephemeralkey start W10X120 5000 49155 +Done + +BorderAgentEphemeralKey callback - state:Started +BorderAgentEphemeralKey callback - state:Connected +BorderAgentEphemeralKey callback - state:Stopped ``` -### ba ephemeralkey feature disable +### ba ephemeralkey callback disable -Disables the Ephemeral Key feature. +Disables callback from Border Agent to be notified of state changes of Border Agent's Ephemeral Key Manager. + +Requires `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE`. ```bash -> ba ephemeralkey feature disable +> ba ephemeralkey callback disable Done ``` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index df876ae1e40..a2f8e9cce09 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -459,45 +459,21 @@ template <> otError Interpreter::Process(Arg aArgs[]) */ if (aArgs[0] == "port") { - OutputLine("%hu", otBorderAgentGetUdpPort(GetInstancePtr())); + OutputLine("%u", otBorderAgentGetUdpPort(GetInstancePtr())); } /** * @cli ba state * @code * ba state - * Started + * Active * Done * @endcode * @par api_copy - * #otBorderAgentGetState + * #otBorderAgentIsActive */ else if (aArgs[0] == "state") { - static const char *const kStateStrings[] = { - "Stopped", // (0) OT_BORDER_AGENT_STATE_STOPPED - "Started", // (1) OT_BORDER_AGENT_STATE_STARTED - "Active", // (2) OT_BORDER_AGENT_STATE_ACTIVE - }; - - static_assert(0 == OT_BORDER_AGENT_STATE_STOPPED, "OT_BORDER_AGENT_STATE_STOPPED value is incorrect"); - static_assert(1 == OT_BORDER_AGENT_STATE_STARTED, "OT_BORDER_AGENT_STATE_STARTED value is incorrect"); - static_assert(2 == OT_BORDER_AGENT_STATE_ACTIVE, "OT_BORDER_AGENT_STATE_ACTIVE value is incorrect"); - - OutputLine("%s", Stringify(otBorderAgentGetState(GetInstancePtr()), kStateStrings)); - } - /** - * @cli ba disconnect - * @code - * ba disconnect - * Done - * @endcode - * @par - * Disconnects the Border Agent from any active secure sessions - * @sa otBorderAgentDisconnect - */ - else if (aArgs[0] == "disconnect") - { - otBorderAgentDisconnect(GetInstancePtr()); + OutputLine("%s", otBorderAgentIsActive(GetInstancePtr()) ? "Active" : "Inactive"); } #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE /** @@ -540,30 +516,53 @@ template <> otError Interpreter::Process(Arg aArgs[]) #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE else if (aArgs[0] == "ephemeralkey") { + bool enable; + /** * @cli ba ephemeralkey * @code * ba ephemeralkey - * active + * Stopped * Done * @endcode * @par api_copy - * #otBorderAgentIsEphemeralKeyActive + * #otBorderAgentEphemeralKeyGetState */ if (aArgs[1].IsEmpty()) { - OutputLine("%sactive", otBorderAgentIsEphemeralKeyActive(GetInstancePtr()) ? "" : "in"); + otBorderAgentEphemeralKeyState state = otBorderAgentEphemeralKeyGetState(GetInstancePtr()); + + OutputLine("%s", otBorderAgentEphemeralKeyStateToString(state)); + } + /** + * @cli ba ephemeralkey (enable, disable) + * @code + * ba ephemeralkey enable + * Done + * @endcode + * @code + * ba ephemeralkey + * Enabled + * Done + * @endcode + * @cparam ba ephemeralkey @ca{enable|disable} + * @par api_copy + * #otBorderAgentEphemeralKeySetEnabled + */ + else if (ProcessEnableDisable(aArgs + 1, otBorderAgentEphemeralKeySetEnabled) == OT_ERROR_NONE) + { } /** - * @cli ba ephemeralkey set [timeout-in-msec] [port] + * @cli ba ephemeralkey start [timeout-in-msec] [port] * @code - * ba ephemeralkey set Z10X20g3J15w1000P60m16 5000 1234 + * ba ephemeralkey start Z10X20g3J15w1000P60m16 5000 1234 * Done * @endcode + * @cparam ba ephemeralkey start @ca{keystring} [@ca{timeout-in-msec}] [@ca{port}] * @par api_copy - * #otBorderAgentSetEphemeralKey + * #otBorderAgentEphemeralKeyStart */ - else if (aArgs[1] == "set") + else if (aArgs[1] == "start") { uint32_t timeout = 0; uint16_t port = 0; @@ -580,70 +579,64 @@ template <> otError Interpreter::Process(Arg aArgs[]) SuccessOrExit(error = aArgs[4].ParseAsUint16(port)); } - error = otBorderAgentSetEphemeralKey(GetInstancePtr(), aArgs[2].GetCString(), timeout, port); + error = otBorderAgentEphemeralKeyStart(GetInstancePtr(), aArgs[2].GetCString(), timeout, port); } /** - * @cli ba ephemeralkey clear + * @cli ba ephemeralkey stop * @code - * ba ephemeralkey clear + * ba ephemeralkey stop * Done * @endcode * @par api_copy - * #otBorderAgentClearEphemeralKey + * #otBorderAgentEphemeralKeyStop */ - else if (aArgs[1] == "clear") + else if (aArgs[1] == "stop") { - otBorderAgentClearEphemeralKey(GetInstancePtr()); + otBorderAgentEphemeralKeyStop(GetInstancePtr()); + } + /** + * @cli ba ephemeralkey port + * @code + * ba ephemeralkey port + * 49153 + * Done + * @endcode + * @par api_copy + * #otBorderAgentEphemeralKeyGetUdpPort + */ + else if (aArgs[1] == "port") + { + OutputLine("%u", otBorderAgentEphemeralKeyGetUdpPort(GetInstancePtr())); } /** * @cli ba ephemeralkey callback (enable, disable) * @code * ba ephemeralkey callback enable * Done - * ba ephemeralkey set W10X1 5000 49155 + * @endcode + * @code + * ba ephemeralkey start W10X10 5000 49155 * Done - * BorderAgent callback: Ephemeral key active, port:49155 - * BorderAgent callback: Ephemeral key inactive + * BorderAgentEphemeralKey callback - state:Started + * BorderAgentEphemeralKey callback - state:Connected * @endcode + * @cparam ba ephemeralkey callback @ca{enable|disable} * @par api_copy - * #otBorderAgentSetEphemeralKeyCallback + * #otBorderAgentEphemeralKeySetCallback */ else if (aArgs[1] == "callback") { - bool enable; - SuccessOrExit(error = ParseEnableOrDisable(aArgs[2], enable)); if (enable) { - otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), HandleBorderAgentEphemeralKeyStateChange, this); + otBorderAgentEphemeralKeySetCallback(GetInstancePtr(), HandleBorderAgentEphemeralKeyStateChange, this); } else { - otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), nullptr, nullptr); + otBorderAgentEphemeralKeySetCallback(GetInstancePtr(), nullptr, nullptr); } } - /** - * @cli ba ephemeralkey feature (enable, disable) - * @code - * ba ephemeralkey feature - * Enabled - * Done - * @endcode - * @code - * ba ephemeralkey feature enable - * Done - * @endcode - * @cparam ba ephemeralkey feature [@ca{enable|disable}] - * @par api_copy - * #otBorderAgentIsEphemeralKeyFeatureEnabled - * #otBorderAgentSetEphemeralKeyFeatureEnabled - */ - else if (aArgs[1] == "feature") - { - error = ProcessEnableDisable(aArgs + 2, otBorderAgentIsEphemeralKeyFeatureEnabled, - otBorderAgentSetEphemeralKeyFeatureEnabled); - } else { error = OT_ERROR_INVALID_ARGS; @@ -732,16 +725,9 @@ void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void *aContext) void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void) { - bool active = otBorderAgentIsEphemeralKeyActive(GetInstancePtr()); + otBorderAgentEphemeralKeyState state = otBorderAgentEphemeralKeyGetState(GetInstancePtr()); - OutputFormat("BorderAgent callback: Ephemeral key %sactive", active ? "" : "in"); - - if (active) - { - OutputFormat(", port:%u", otBorderAgentGetUdpPort(GetInstancePtr())); - } - - OutputNewLine(); + OutputLine("BorderAgentEphemeralKey callback - state:%s", otBorderAgentEphemeralKeyStateToString(state)); } #endif diff --git a/src/core/api/border_agent_api.cpp b/src/core/api/border_agent_api.cpp index 3f0c1e6f271..f93346018e2 100644 --- a/src/core/api/border_agent_api.cpp +++ b/src/core/api/border_agent_api.cpp @@ -54,24 +54,9 @@ otError otBorderAgentSetId(otInstance *aInstance, const otBorderAgentId *aId) } #endif -otBorderAgentState otBorderAgentGetState(otInstance *aInstance) +bool otBorderAgentIsActive(otInstance *aInstance) { - otBorderAgentState state = OT_BORDER_AGENT_STATE_STOPPED; - - switch (AsCoreType(aInstance).Get().GetState()) - { - case MeshCoP::BorderAgent::kStateStopped: - break; - case MeshCoP::BorderAgent::kStateStarted: - state = OT_BORDER_AGENT_STATE_STARTED; - break; - case MeshCoP::BorderAgent::kStateConnected: - case MeshCoP::BorderAgent::kStateAccepted: - state = OT_BORDER_AGENT_STATE_ACTIVE; - break; - } - - return state; + return AsCoreType(aInstance).Get().IsActive(); } uint16_t otBorderAgentGetUdpPort(otInstance *aInstance) @@ -79,52 +64,57 @@ uint16_t otBorderAgentGetUdpPort(otInstance *aInstance) return AsCoreType(aInstance).Get().GetUdpPort(); } +const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance) +{ + return &AsCoreType(aInstance).Get().GetCounters(); +} + #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE -bool otBorderAgentIsEphemeralKeyFeatureEnabled(otInstance *aInstance) +otBorderAgentEphemeralKeyState otBorderAgentEphemeralKeyGetState(otInstance *aInstance) { - return AsCoreType(aInstance).Get().IsEphemeralKeyFeatureEnabled(); + return MapEnum(AsCoreType(aInstance).Get().GetState()); } -void otBorderAgentSetEphemeralKeyFeatureEnabled(otInstance *aInstance, bool aEnabled) +void otBorderAgentEphemeralKeySetEnabled(otInstance *aInstance, bool aEnabled) { - AsCoreType(aInstance).Get().SetEphemeralKeyFeatureEnabled(aEnabled); + AsCoreType(aInstance).Get().SetEnabled(aEnabled); } -otError otBorderAgentSetEphemeralKey(otInstance *aInstance, - const char *aKeyString, - uint32_t aTimeout, - uint16_t aUdpPort) +otError otBorderAgentEphemeralKeyStart(otInstance *aInstance, + const char *aKeyString, + uint32_t aTimeout, + uint16_t aUdpPort) { AssertPointerIsNotNull(aKeyString); - return AsCoreType(aInstance).Get().SetEphemeralKey(aKeyString, aTimeout, aUdpPort); + return AsCoreType(aInstance).Get().Start(aKeyString, aTimeout, aUdpPort); } -void otBorderAgentClearEphemeralKey(otInstance *aInstance) +void otBorderAgentEphemeralKeyStop(otInstance *aInstance) { - AsCoreType(aInstance).Get().ClearEphemeralKey(); + AsCoreType(aInstance).Get().Stop(); } -bool otBorderAgentIsEphemeralKeyActive(otInstance *aInstance) +uint16_t otBorderAgentEphemeralKeyGetUdpPort(otInstance *aInstance) { - return AsCoreType(aInstance).Get().IsEphemeralKeyActive(); + return AsCoreType(aInstance).Get().GetUdpPort(); } -void otBorderAgentSetEphemeralKeyCallback(otInstance *aInstance, +void otBorderAgentEphemeralKeySetCallback(otInstance *aInstance, otBorderAgentEphemeralKeyCallback aCallback, void *aContext) { - AsCoreType(aInstance).Get().SetEphemeralKeyCallback(aCallback, aContext); + AsCoreType(aInstance).Get().SetCallback(aCallback, aContext); } -#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - -const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance) +const char *otBorderAgentEphemeralKeyStateToString(otBorderAgentEphemeralKeyState aState) { - return &AsCoreType(aInstance).Get().GetCounters(); + OT_ASSERT(aState <= OT_BORDER_AGENT_STATE_ACCEPTED); + + return MeshCoP::BorderAgent::EphemeralKeyManager::StateToString(MapEnum(aState)); } -void otBorderAgentDisconnect(otInstance *aInstance) { AsCoreType(aInstance).Get().Disconnect(); } +#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE diff --git a/src/core/config/border_agent.h b/src/core/config/border_agent.h index 19cb458e0eb..edb99d3809c 100644 --- a/src/core/config/border_agent.h +++ b/src/core/config/border_agent.h @@ -86,7 +86,7 @@ */ #ifndef OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT #define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT \ - (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4) + OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE #endif /** diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index af9262d58f4..ac48d6c4379 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -1024,6 +1024,13 @@ template <> inline MeshCoP::DatasetUpdater &Instance::Get(void) { return mDatase template <> inline MeshCoP::BorderAgent &Instance::Get(void) { return mBorderAgent; } #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE +template <> inline MeshCoP::BorderAgent::EphemeralKeyManager &Instance::Get(void) +{ + return mBorderAgent.GetEphemeralKeyManager(); +} +#endif + #if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE template <> inline AnnounceSender &Instance::Get(void) { return mAnnounceSender; } #endif diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp index 014921d4e19..ba9e63c7b5c 100644 --- a/src/core/meshcop/border_agent.cpp +++ b/src/core/meshcop/border_agent.cpp @@ -54,12 +54,7 @@ BorderAgent::BorderAgent(Instance &aInstance) , mIdInitialized(false) #endif #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - , mIsEphemeralKeyFeatureEnabled(OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT) - , mUsingEphemeralKey(false) - , mDidConnectWithEphemeralKey(false) - , mOldUdpPort(0) - , mEphemeralKeyTimer(aInstance) - , mEphemeralKeyTask(aInstance) + , mEphemeralKeyManager(aInstance) #endif { ClearAllBytes(mCounters); @@ -120,16 +115,8 @@ Error BorderAgent::Start(uint16_t aUdpPort, const uint8_t *aPsk, uint8_t aPskLen VerifyOrExit(mState == kStateStopped); -#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - if (mUsingEphemeralKey) - { - SuccessOrExit(error = mDtlsTransport.SetMaxConnectionAttempts(kMaxEphemeralKeyConnectionAttempts, - HandleDtlsTransportClosed, this)); - } -#endif - - mDtlsTransport.SetAcceptCallback(HandleAcceptSession, this); - mDtlsTransport.SetRemoveSessionCallback(HandleRemoveSession, this); + mDtlsTransport.SetAcceptCallback(BorderAgent::HandleAcceptSession, this); + mDtlsTransport.SetRemoveSessionCallback(BorderAgent::HandleRemoveSession, this); SuccessOrExit(error = mDtlsTransport.Open()); SuccessOrExit(error = mDtlsTransport.Bind(aUdpPort)); @@ -149,15 +136,6 @@ void BorderAgent::Stop(void) { VerifyOrExit(mState != kStateStopped); -#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - if (mUsingEphemeralKey) - { - mUsingEphemeralKey = false; - mEphemeralKeyTimer.Stop(); - mEphemeralKeyTask.Post(); - } -#endif - mDtlsTransport.Close(); mState = kStateStopped; @@ -167,17 +145,6 @@ void BorderAgent::Stop(void) return; } -void BorderAgent::Disconnect(void) -{ - VerifyOrExit(mState == kStateConnected || mState == kStateAccepted); - VerifyOrExit(mCoapDtlsSession != nullptr); - - mCoapDtlsSession->Disconnect(); - -exit: - return; -} - uint16_t BorderAgent::GetUdpPort(void) const { return mDtlsTransport.GetUdpPort(); } void BorderAgent::HandleNotifierEvents(Events aEvents) @@ -196,23 +163,16 @@ void BorderAgent::HandleNotifierEvents(Events aEvents) if (aEvents.ContainsAny(kEventPskcChanged)) { - VerifyOrExit(mState != kStateStopped); + Pskc pskc; -#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - // No-op if Ephemeralkey mode is activated, new pskc will be applied - // when Ephemeralkey mode is deactivated. - VerifyOrExit(!mUsingEphemeralKey); -#endif + VerifyOrExit(mState != kStateStopped); - { - Pskc pskc; - Get().GetPskc(pskc); + Get().GetPskc(pskc); - // If there is secure session already established, it won't be impacted, - // new pskc will be applied for next connection. - SuccessOrExit(mDtlsTransport.SetPsk(pskc.m8, Pskc::kSize)); - pskc.Clear(); - } + // If there is secure session already established, it won't be impacted, + // new pskc will be applied for next connection. + SuccessOrExit(mDtlsTransport.SetPsk(pskc.m8, Pskc::kSize)); + pskc.Clear(); } exit: @@ -241,60 +201,45 @@ BorderAgent::CoapDtlsSession *BorderAgent::HandleAcceptSession(void) return session; } -void BorderAgent::HandleRemoveSession(void *aContext, SecureSession &aSesssion) +void BorderAgent::HandleRemoveSession(void *aContext, SecureSession &aSession) { - static_cast(aContext)->HandleRemoveSession(aSesssion); + static_cast(aContext)->HandleRemoveSession(aSession); } -void BorderAgent::HandleRemoveSession(SecureSession &aSesssion) +void BorderAgent::HandleRemoveSession(SecureSession &aSession) { - CoapDtlsSession &coapSession = static_cast(aSesssion); + CoapDtlsSession &coapSession = static_cast(aSession); coapSession.Cleanup(); coapSession.Free(); mCoapDtlsSession = nullptr; } -void BorderAgent::HandleSessionConnected(CoapDtlsSession &aSesssion) +void BorderAgent::HandleSessionConnected(CoapDtlsSession &aSession) { - OT_UNUSED_VARIABLE(aSesssion); - - mState = kStateConnected; + OT_UNUSED_VARIABLE(aSession); #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - if (mUsingEphemeralKey) + if (mEphemeralKeyManager.OwnsSession(aSession)) { - mDidConnectWithEphemeralKey = true; - mCounters.mEpskcSecureSessionSuccesses++; - mEphemeralKeyTask.Post(); + mEphemeralKeyManager.HandleSessionConnected(); } else #endif { + mState = kStateConnected; mCounters.mPskcSecureSessionSuccesses++; } } -void BorderAgent::HandleSessionDisconnected(CoapDtlsSession &aSesssion, CoapDtlsSession::ConnectEvent aEvent) +void BorderAgent::HandleSessionDisconnected(CoapDtlsSession &aSession, CoapDtlsSession::ConnectEvent aEvent) { - OT_UNUSED_VARIABLE(aSesssion); + OT_UNUSED_VARIABLE(aSession); #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - if (mUsingEphemeralKey) + if (mEphemeralKeyManager.OwnsSession(aSession)) { - if (mDidConnectWithEphemeralKey) - { - RestartAfterRemovingEphemeralKey(); - } - - if (aEvent == CoapDtlsSession::kDisconnectedError) - { - mCounters.mEpskcSecureSessionFailures++; - } - else if (aEvent == CoapDtlsSession::kDisconnectedPeerClosed) - { - mCounters.mEpskcDeactivationDisconnects++; - } + mEphemeralKeyManager.HandleSessionDisconnected(aEvent); } else #endif @@ -308,20 +253,19 @@ void BorderAgent::HandleSessionDisconnected(CoapDtlsSession &aSesssion, CoapDtls } } -void BorderAgent::HandleCommissionerPetitionAccepted(CoapDtlsSession &aSesssion) +void BorderAgent::HandleCommissionerPetitionAccepted(CoapDtlsSession &aSession) { - OT_UNUSED_VARIABLE(aSesssion); - - mState = kStateAccepted; + OT_UNUSED_VARIABLE(aSession); #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - if (mUsingEphemeralKey) + if (mEphemeralKeyManager.OwnsSession(aSession)) { - mCounters.mEpskcCommissionerPetitions++; + mEphemeralKeyManager.HandleCommissionerPetitionAccepted(); } else #endif { + mState = kStateAccepted; mCounters.mPskcCommissionerPetitions++; } } @@ -373,95 +317,130 @@ template <> void BorderAgent::HandleTmf(Coap::Message &aMessage, co FreeMessageOnError(message, error); } -//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// Ephemeral Key +//---------------------------------------------------------------------------------------------------------------------- +// BorderAgent::EphemeralKeyManager #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE -Error BorderAgent::SetEphemeralKey(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort) +BorderAgent::EphemeralKeyManager::EphemeralKeyManager(Instance &aInstance) + : InstanceLocator(aInstance) +#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT + , mState(kStateStopped) +#else + , mState(kStateDisabled) +#endif + , mDtlsTransport(aInstance, kNoLinkSecurity) + , mCoapDtlsSession(nullptr) + , mTimer(aInstance) + , mCallbackTask(aInstance) { - Error error = kErrorNone; - uint16_t length = StringLength(aKeyString, kMaxEphemeralKeyLength + 1); - - VerifyOrExit(mIsEphemeralKeyFeatureEnabled, error = kErrorNotCapable); - VerifyOrExit(mState == kStateStarted, error = kErrorInvalidState); - VerifyOrExit((length >= kMinEphemeralKeyLength) && (length <= kMaxEphemeralKeyLength), error = kErrorInvalidArgs); +} - if (!mUsingEphemeralKey) +void BorderAgent::EphemeralKeyManager::SetEnabled(bool aEnabled) +{ + if (aEnabled) { - mOldUdpPort = GetUdpPort(); + VerifyOrExit(mState == kStateDisabled); + SetState(kStateStopped); + } + else + { + VerifyOrExit(mState != kStateDisabled); + Stop(); + SetState(kStateDisabled); } - Stop(); +exit: + return; +} - // We set the `mUsingEphemeralKey` before `Start()` since - // callbacks (like `HandleConnected()`) may be invoked from - // `Start()` itself. +Error BorderAgent::EphemeralKeyManager::Start(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort) +{ + Error error = kErrorNone; + uint16_t length; - mUsingEphemeralKey = true; - mDidConnectWithEphemeralKey = false; + VerifyOrExit(mState == kStateStopped, error = kErrorInvalidState); - error = Start(aUdpPort, reinterpret_cast(aKeyString), static_cast(length)); + length = StringLength(aKeyString, kMaxKeyLength + 1); + VerifyOrExit((length >= kMinKeyLength) && (length <= kMaxKeyLength), error = kErrorInvalidArgs); - if (error != kErrorNone) - { - mUsingEphemeralKey = false; - IgnoreError(Start(mOldUdpPort)); - mCounters.mEpskcStartSecureSessionErrors++; - ExitNow(); - } + IgnoreError(mDtlsTransport.SetMaxConnectionAttempts(kMaxConnectionAttempts, HandleTransportClosed, this)); - mEphemeralKeyTask.Post(); + mDtlsTransport.SetAcceptCallback(EphemeralKeyManager::HandleAcceptSession, this); + mDtlsTransport.SetRemoveSessionCallback(EphemeralKeyManager::HandleRemoveSession, this); - if (aTimeout == 0) - { - aTimeout = kDefaultEphemeralKeyTimeout; - } + SuccessOrExit(error = mDtlsTransport.Open()); + SuccessOrExit(error = mDtlsTransport.Bind(aUdpPort)); - aTimeout = Min(aTimeout, kMaxEphemeralKeyTimeout); + SuccessOrExit( + error = mDtlsTransport.SetPsk(reinterpret_cast(aKeyString), static_cast(length))); - mEphemeralKeyTimer.Start(aTimeout); - mCounters.mEpskcActivations++; + aTimeout = Min((aTimeout == 0) ? kDefaultTimeout : aTimeout, kMaxTimeout); + mTimer.Start(aTimeout); LogInfo("Allow ephemeral key for %lu msec on port %u", ToUlong(aTimeout), GetUdpPort()); + SetState(kStateStarted); + exit: switch (error) { + case kErrorNone: + Get().mCounters.mEpskcActivations++; + break; case kErrorInvalidState: - mCounters.mEpskcInvalidBaStateErrors++; + Get().mCounters.mEpskcInvalidBaStateErrors++; break; case kErrorInvalidArgs: - mCounters.mEpskcInvalidArgsErrors++; + Get().mCounters.mEpskcInvalidArgsErrors++; break; default: + Get().mCounters.mEpskcStartSecureSessionErrors++; break; } return error; } -void BorderAgent::ClearEphemeralKey(void) -{ - VerifyOrExit(mUsingEphemeralKey); - - LogInfo("Clearing ephemeral key"); - - mCounters.mEpskcDeactivationClears++; +void BorderAgent::EphemeralKeyManager::Stop(void) { Stop(kReasonLocalDisconnect); } +void BorderAgent::EphemeralKeyManager::Stop(StopReason aReason) +{ switch (mState) { case kStateStarted: - RestartAfterRemovingEphemeralKey(); - break; - - case kStateStopped: case kStateConnected: case kStateAccepted: - // If a commissioner connection is currently active, we'll - // wait for it to disconnect or for the ephemeral key timeout - // or `kKeepAliveTimeout` to expire before removing the key - // and restarting the agent. + break; + case kStateDisabled: + case kStateStopped: + ExitNow(); + } + + LogInfo("Stopping ephemeral key use - reason: %s", StopReasonToString(aReason)); + SetState(kStateStopped); + + mTimer.Stop(); + mDtlsTransport.Close(); + + switch (aReason) + { + case kReasonLocalDisconnect: + Get().mCounters.mEpskcDeactivationClears++; + break; + case kReasonPeerDisconnect: + Get().mCounters.mEpskcDeactivationDisconnects++; + break; + case kReasonSessionError: + Get().mCounters.mEpskcStartSecureSessionErrors++; + break; + case kReasonMaxFailedAttempts: + Get().mCounters.mEpskcDeactivationMaxAttempts++; + break; + case kReasonTimeout: + Get().mCounters.mEpskcDeactivationTimeouts++; + break; + case kReasonUnknown: break; } @@ -469,56 +448,162 @@ void BorderAgent::ClearEphemeralKey(void) return; } -void BorderAgent::HandleEphemeralKeyTimeout(void) +void BorderAgent::EphemeralKeyManager::SetState(State aState) { - LogInfo("Ephemeral key timed out"); - mCounters.mEpskcDeactivationTimeouts++; - RestartAfterRemovingEphemeralKey(); + VerifyOrExit(mState != aState); + LogInfo("Ephemeral key - state: %s -> %s", StateToString(mState), StateToString(aState)); + mState = aState; + mCallbackTask.Post(); + +exit: + return; } -void BorderAgent::InvokeEphemeralKeyCallback(void) { mEphemeralKeyCallback.InvokeIfSet(); } +SecureSession *BorderAgent::EphemeralKeyManager::HandleAcceptSession(void *aContext, + const Ip6::MessageInfo &aMessageInfo) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + + return static_cast(aContext)->HandleAcceptSession(); +} -void BorderAgent::RestartAfterRemovingEphemeralKey(void) +BorderAgent::CoapDtlsSession *BorderAgent::EphemeralKeyManager::HandleAcceptSession(void) { - LogInfo("Removing ephemeral key and restarting agent"); + CoapDtlsSession *session = nullptr; + + VerifyOrExit(mCoapDtlsSession == nullptr); + + session = CoapDtlsSession::Allocate(GetInstance(), mDtlsTransport); + VerifyOrExit(session != nullptr); - Stop(); - IgnoreError(Start(mOldUdpPort)); + mCoapDtlsSession = session; + +exit: + return session; } -void BorderAgent::HandleDtlsTransportClosed(void *aContext) +void BorderAgent::EphemeralKeyManager::HandleRemoveSession(void *aContext, SecureSession &aSession) { - reinterpret_cast(aContext)->HandleDtlsTransportClosed(); + static_cast(aContext)->HandleRemoveSession(aSession); } -void BorderAgent::HandleDtlsTransportClosed(void) +void BorderAgent::EphemeralKeyManager::HandleRemoveSession(SecureSession &aSession) { - LogInfo("Reached max allowed connection attempts with ephemeral key"); - mCounters.mEpskcDeactivationMaxAttempts++; - RestartAfterRemovingEphemeralKey(); + CoapDtlsSession &coapSession = static_cast(aSession); + + coapSession.Cleanup(); + coapSession.Free(); + mCoapDtlsSession = nullptr; } -void BorderAgent::SetEphemeralKeyFeatureEnabled(bool aEnabled) +void BorderAgent::EphemeralKeyManager::HandleSessionConnected(void) { - VerifyOrExit(mIsEphemeralKeyFeatureEnabled != aEnabled); - mIsEphemeralKeyFeatureEnabled = aEnabled; + SetState(kStateConnected); + Get().mCounters.mEpskcSecureSessionSuccesses++; +} + +void BorderAgent::EphemeralKeyManager::HandleSessionDisconnected(SecureSession::ConnectEvent aEvent) +{ + StopReason reason = kReasonUnknown; + + // The ephemeral key can be used once + + VerifyOrExit((mState == kStateConnected) || (mState == kStateAccepted)); - if (!mIsEphemeralKeyFeatureEnabled) + switch (aEvent) { - // If there is an active session connected with ephemeral key, we disconnect - // the session. - if (mUsingEphemeralKey) - { - Disconnect(); - } - ClearEphemeralKey(); + case SecureSession::kDisconnectedError: + reason = kReasonSessionError; + break; + case SecureSession::kDisconnectedPeerClosed: + reason = kReasonPeerDisconnect; + break; + case SecureSession::kDisconnectedMaxAttempts: + reason = kReasonMaxFailedAttempts; + break; + default: + break; } - // TODO: Update MeshCoP service after new module is added. + Stop(reason); exit: return; } + +void BorderAgent::EphemeralKeyManager::HandleCommissionerPetitionAccepted(void) +{ + SetState(kStateAccepted); + Get().mCounters.mEpskcCommissionerPetitions++; +} + +void BorderAgent::EphemeralKeyManager::HandleTimer(void) { Stop(kReasonTimeout); } + +void BorderAgent::EphemeralKeyManager::HandleTask(void) { mCallback.InvokeIfSet(); } + +void BorderAgent::EphemeralKeyManager::HandleTransportClosed(void *aContext) +{ + reinterpret_cast(aContext)->HandleTransportClosed(); +} + +void BorderAgent::EphemeralKeyManager::HandleTransportClosed(void) +{ + Stop(kReasonMaxFailedAttempts); + ; +} + +const char *BorderAgent::EphemeralKeyManager::StateToString(State aState) +{ + static const char *const kStateStrings[] = { + "Disabled", // (0) kStateDisabled + "Stopped", // (1) kStateStopped + "Started", // (2) kStateStarted + "Connected", // (3) kStateConnected + "Accepted", // (4) kStateAccepted + }; + + struct EnumCheck + { + InitEnumValidatorCounter(); + ValidateNextEnum(kStateDisabled); + ValidateNextEnum(kStateStopped); + ValidateNextEnum(kStateStarted); + ValidateNextEnum(kStateConnected); + ValidateNextEnum(kStateAccepted); + }; + + return kStateStrings[aState]; +} + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + +const char *BorderAgent::EphemeralKeyManager::StopReasonToString(StopReason aReason) +{ + static const char *const kReasonStrings[] = { + "LocalDisconnect", // (0) kReasonLocalDisconnect + "PeerDisconnect", // (1) kReasonPeerDisconnect + "SessionError", // (2) kReasonSessionError + "MaxFailedAttempts", // (3) kReasonMaxFailedAttempts + "Timeout", // (4) kReasonTimeout + "Unknown", // (5) kReasonUnknown + }; + + struct EnumCheck + { + InitEnumValidatorCounter(); + ValidateNextEnum(kReasonLocalDisconnect); + ValidateNextEnum(kReasonPeerDisconnect); + ValidateNextEnum(kReasonSessionError); + ValidateNextEnum(kReasonMaxFailedAttempts); + ValidateNextEnum(kReasonTimeout); + ValidateNextEnum(kReasonUnknown); + }; + + return kReasonStrings[aReason]; +} + +#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + #endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE //---------------------------------------------------------------------------------------------------------------------- @@ -987,11 +1072,11 @@ void BorderAgent::CoapDtlsSession::HandleTimer(void) //---------------------------------------------------------------------------------------------------------------------- // `BorderAgent::CoapDtlsSession::ForwardContext` -BorderAgent::CoapDtlsSession::ForwardContext::ForwardContext(CoapDtlsSession &aSesssion, +BorderAgent::CoapDtlsSession::ForwardContext::ForwardContext(CoapDtlsSession &aSession, const Coap::Message &aMessage, bool aPetition, bool aSeparate) - : mSession(aSesssion) + : mSession(aSession) , mMessageId(aMessage.GetMessageId()) , mPetition(aPetition) , mSeparate(aSeparate) diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp index 7da89c352ba..feb8297cc5b 100644 --- a/src/core/meshcop/border_agent.hpp +++ b/src/core/meshcop/border_agent.hpp @@ -66,39 +66,19 @@ class BorderAgent : public InstanceLocator, private NonCopyable friend class ot::Notifier; friend class Tmf::Agent; -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; + class CoapDtlsSession; +public: typedef otBorderAgentId Id; ///< Border Agent ID. typedef otBorderAgentCounters Counters; ///< Border Agent Counters. - /** - * Defines the Border Agent state. - */ enum State : uint8_t { - kStateStopped, ///< Stopped/disabled. - kStateStarted, ///< Started and listening for connections. - kStateConnected, ///< Connected to an external commissioner candidate, petition pending. - kStateAccepted, ///< Connected to and accepted an external commissioner. + kStateDisabled = OT_BORDER_AGENT_STATE_DISABLED, ///< Ephemeral key feature is disabled. + kStateStopped = OT_BORDER_AGENT_STATE_STOPPED, ///< Enabled, but the ephemeral key is not set and started. + kStateStarted = OT_BORDER_AGENT_STATE_STARTED, ///< Ephemeral key is set and listening to accept connection + kStateConnected = OT_BORDER_AGENT_STATE_CONNECTED, ///< Session connected with candidate, not full commissioner + kStateAccepted = OT_BORDER_AGENT_STATE_ACCEPTED, ///< Session connected and accepted as full commissioner. }; /** @@ -153,95 +133,161 @@ class BorderAgent : public InstanceLocator, private NonCopyable State GetState(void) const { return mState; } /** - * Disconnects the Border Agent from any active secure sessions. - * - * If Border Agent is connected to a commissioner candidate with ephemeral key, calling this API - * will cause the ephemeral key to be cleared after the session is disconnected. + * Indicates whether the Border Agent service is active. * - * The Border Agent state may not change immediately upon calling this method, the state will be - * updated when the connection update is notified by `HandleConnected()`. + * @retval TRUE Border Agent service is active. + * @retval FALSE Border Agent service is not active. */ - void Disconnect(void); + bool IsActive(void) const { return (mState != kStateDisabled) && (mState != kStateStopped); } #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). To terminate active commissioner sessions, - * use the `Disconnect()` function. - * - * 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. - * - * During the timeout interval, the ephemeral key can be used only once by an external commissioner to establish a - * connection. After the commissioner disconnects, the ephemeral key is cleared, and the Border Agent reverts to - * using PSKc. If the timeout expires while a commissioner is still connected, the session will be terminated, and - * the Border Agent will cease using the ephemeral key and revert to 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). + * Manages the ephemeral key use by Border Agent. */ - Error SetEphemeralKey(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort); + class EphemeralKeyManager : public InstanceLocator, private NonCopyable + { + friend class BorderAgent; - /** - * 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, or the ephemeral key timeout expires. To terminate active commissioner sessions, use the - * `Disconnect()` function. - */ - void ClearEphemeralKey(void); + public: + static constexpr uint16_t kMinKeyLength = OT_BORDER_AGENT_MIN_EPHEMERAL_KEY_LENGTH; ///< Min key len. + static constexpr uint16_t kMaxKeyLength = OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_LENGTH; ///< Max key len. + static constexpr uint32_t kDefaultTimeout = OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT; //< Default timeout. + static constexpr uint32_t kMaxTimeout = OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT; ///< Max timeout. + + typedef otBorderAgentEphemeralKeyCallback CallbackHandler; ///< Callback function pointer. + + /** + * Enables/disables Ephemeral Key Manager. + * + * If this method is called to disable, while an an ephemeral key is in use, the ephemeral key use will + * be stopped (as if `Stop()` is called). + * + * @param[in] aEnabled Whether to enable or disable. + */ + void SetEnabled(bool aEnabled); + + /** + * Starts using an ephemeral key for a given timeout duration. + * + * An ephemeral key can only be set when `GetState()` is `kStateStopped`. Otherwise, `kErrorInvalidState` is + * returned. This means that setting the ephemeral key again while a previously set key is still in use will + * fail. Callers can stop the previous key by calling `Stop()` before starting with a new key. + * + * The given @p aKeyString is used directly as the ephemeral PSK (excluding the trailing null `\0` character). + * Its length must be between `kMinKeyLength` and `kMaxKeyLength`, inclusive. + * + * The ephemeral key can be used only once by an external commissioner candidate to establish a secure session. + * After the commissioner candidate disconnects, the use of the ephemeral key is stopped. If the timeout + * expires, the use of the ephemeral key is also stopped, and any established session using the key is + * immediately disconnected. + * + * @param[in] aKeyString The ephemeral key. + * @param[in] aTimeout The timeout duration, in milliseconds, to use the ephemeral key. + * If zero, the default `kDefaultTimeout` value is used. If the timeout value is + * larger than `kMaxTimeout`, the maximum value is used instead. + * @param[in] aUdpPort The UDP port to use with the ephemeral key. If the UDP port is zero, an ephemeral + * port is used. `GetUdpPort()` returns the current UDP port being used. + * + * @retval kErrorNone Successfully started using the ephemeral key. + * @retval kErrorInvalidState A previously set ephemeral key is still in use or feature is disabled. + * @retval kErrorInvalidArgs The given @p aKeyString is not valid. + * @retval kErrorFailed Failed to start (e.g., it could not bind to the given UDP port). + */ + Error Start(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort); + + /** + * Stops the ephemeral key use and disconnects any established secure session using it. + * + * If there is no ephemeral key in use, calling this method has no effect. + */ + void Stop(void); + + /** + * Gets the state of ephemeral key use and its session. + * + * @returns The `EmpheralKeyManager` state. + */ + State GetState(void) const { return mState; } + + /** + * Gets the UDP port used by ephemeral key DTLS secure transport. + * + * @returns UDP port number. + */ + uint16_t GetUdpPort(void) const { return mDtlsTransport.GetUdpPort(); } + + /** + * Sets the callback. + * + * @param[in] aCallback The callback function pointer. + * @param[in] aContext The context associated and used with callback handler. + */ + void SetCallback(CallbackHandler aCallback, void *aContext) { mCallback.Set(aCallback, aContext); } + + /** + * Converts a given `State` to human-readable string. + * + * @param[in] aState The state to convert. + * + * @returns The string corresponding to @p aState. + */ + static const char *StateToString(State aState); - /** - * 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; } + private: + static constexpr uint16_t kMaxConnectionAttempts = 10; - /** - * Callback function pointer to notify when there is any changes related to use of ephemeral key by Border Agent. - */ - typedef otBorderAgentEphemeralKeyCallback EphemeralKeyCallback; + static_assert(kMaxKeyLength <= Dtls::Transport::kPskMaxLength, "Max e-key len is larger than max PSK len"); - void SetEphemeralKeyCallback(EphemeralKeyCallback aCallback, void *aContext) - { - mEphemeralKeyCallback.Set(aCallback, aContext); - } + enum StopReason : uint8_t + { + kReasonLocalDisconnect, + kReasonPeerDisconnect, + kReasonSessionError, + kReasonMaxFailedAttempts, + kReasonTimeout, + kReasonUnknown, + }; - /** - * Enables/disables the Border Agent Ephemeral Key feature. - * - * The Ephemeral Key feature can only be used when it's enabled. If an ephemeral key is already active and then - * this method is called to disable the feature, the in-use ephemeral key will be cleared. - * - * @param[in] aIsEnabled Whether to enable the BA Ephemeral Key feature. - */ - void SetEphemeralKeyFeatureEnabled(bool aEnabled); + explicit EphemeralKeyManager(Instance &aInstance); + + void SetState(State aState); + void Stop(StopReason aReason); + void HandleTimer(void); + void HandleTask(void); + bool OwnsSession(CoapDtlsSession &aSession) const { return mCoapDtlsSession == &aSession; } + void HandleSessionConnected(void); + void HandleSessionDisconnected(SecureSession::ConnectEvent aEvent); + void HandleCommissionerPetitionAccepted(void); + + // Session or Transport callbacks + static SecureSession *HandleAcceptSession(void *aContext, const Ip6::MessageInfo &aMessageInfo); + CoapDtlsSession *HandleAcceptSession(void); + static void HandleRemoveSession(void *aContext, SecureSession &aSession); + void HandleRemoveSession(SecureSession &aSession); + static void HandleTransportClosed(void *aContext); + void HandleTransportClosed(void); + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + static const char *StopReasonToString(StopReason aReason); +#endif + + using TimeoutTimer = TimerMilliIn; + using CallbackTask = TaskletIn; + + State mState; + Dtls::Transport mDtlsTransport; + CoapDtlsSession *mCoapDtlsSession; + TimeoutTimer mTimer; + CallbackTask mCallbackTask; + Callback mCallback; + }; /** - * Indicates whether the Border Agent Ephemeral Key feature state is enabled. + * Gets the `EphemeralKeyManager` instance. + * + * @returns A reference to the `EphemeralKeyManager`. */ - bool IsEphemeralKeyFeatureEnabled(void) { return mIsEphemeralKeyFeatureEnabled; } + EphemeralKeyManager &GetEphemeralKeyManager(void) { return mEphemeralKeyManager; } #endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE @@ -253,16 +299,9 @@ class BorderAgent : public InstanceLocator, private NonCopyable const Counters &GetCounters(void) { return mCounters; } private: - static_assert(kMaxEphemeralKeyLength <= Dtls::Transport::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 CoapDtlsSession : public Coap::SecureSession, public Heap::Allocatable { friend Heap::Allocatable; @@ -288,7 +327,7 @@ class BorderAgent : public InstanceLocator, private NonCopyable uint8_t mToken[Coap::Message::kMaxTokenLength]; private: - ForwardContext(CoapDtlsSession &aSesssion, const Coap::Message &aMessage, bool aPetition, bool aSeparate); + ForwardContext(CoapDtlsSession &aSession, const Coap::Message &aMessage, bool aPetition, bool aSeparate); }; CoapDtlsSession(Instance &aInstance, Dtls::Transport &aDtlsTransport); @@ -334,28 +373,15 @@ class BorderAgent : public InstanceLocator, private NonCopyable static SecureSession *HandleAcceptSession(void *aContext, const Ip6::MessageInfo &aMessageInfo); CoapDtlsSession *HandleAcceptSession(void); - static void HandleRemoveSession(void *aContext, SecureSession &aSesssion); - void HandleRemoveSession(SecureSession &aSesssion); + static void HandleRemoveSession(void *aContext, SecureSession &aSession); + void HandleRemoveSession(SecureSession &aSession); - void HandleSessionConnected(CoapDtlsSession &aSesssion); - void HandleSessionDisconnected(CoapDtlsSession &aSesssion, CoapDtlsSession::ConnectEvent aEvent); - void HandleCommissionerPetitionAccepted(CoapDtlsSession &aSesssion); + void HandleSessionConnected(CoapDtlsSession &aSession); + void HandleSessionDisconnected(CoapDtlsSession &aSession, CoapDtlsSession::ConnectEvent aEvent); + void HandleCommissionerPetitionAccepted(CoapDtlsSession &aSession); static Coap::Message::Code CoapCodeFromError(Error aError); -#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - void RestartAfterRemovingEphemeralKey(void); - void HandleEphemeralKeyTimeout(void); - void InvokeEphemeralKeyCallback(void); - static void HandleDtlsTransportClosed(void *aContext); - void HandleDtlsTransportClosed(void); -#endif - -#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - using EphemeralKeyTimer = TimerMilliIn; - using EphemeralKeyTask = TaskletIn; -#endif - State mState; Dtls::Transport mDtlsTransport; CoapDtlsSession *mCoapDtlsSession; @@ -364,13 +390,7 @@ class BorderAgent : public InstanceLocator, private NonCopyable bool mIdInitialized; #endif #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE - bool mIsEphemeralKeyFeatureEnabled : 1; - bool mUsingEphemeralKey : 1; - bool mDidConnectWithEphemeralKey : 1; - uint16_t mOldUdpPort; - EphemeralKeyTimer mEphemeralKeyTimer; - EphemeralKeyTask mEphemeralKeyTask; - Callback mEphemeralKeyCallback; + EphemeralKeyManager mEphemeralKeyManager; #endif Counters mCounters; }; @@ -380,6 +400,7 @@ DeclareTmfHandler(BorderAgent, kUriRelayRx); } // namespace MeshCoP DefineCoreType(otBorderAgentId, MeshCoP::BorderAgent::Id); +DefineMapEnum(otBorderAgentEphemeralKeyState, MeshCoP::BorderAgent::State); } // namespace ot diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index b8c50ff2481..f4d1b6824d4 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -36,6 +36,8 @@ namespace ot { namespace Nexus { +using BorderAgent = MeshCoP::BorderAgent; + void TestBorderAgent(void) { Core nexus; @@ -65,15 +67,15 @@ void TestBorderAgent(void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check Border Agent initial state"); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); - SuccessOrQuit(node0.Get().AddUnsecurePort(node0.Get().GetUdpPort())); + SuccessOrQuit(node0.Get().AddUnsecurePort(node0.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection to Border Agent"); sockAddr.SetAddress(node0.Get().GetLinkLocalAddress()); - sockAddr.SetPort(node0.Get().GetUdpPort()); + sockAddr.SetPort(node0.Get().GetUdpPort()); node0.Get().GetPskc(pskc); SuccessOrQuit(node1.Get().SetPsk(pskc.m8, Pskc::kSize)); @@ -85,7 +87,8 @@ void TestBorderAgent(void) VerifyOrQuit(node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().GetCounters().mPskcSecureSessionSuccesses == 1); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Disconnect from candidate side"); @@ -95,7 +98,7 @@ void TestBorderAgent(void) nexus.AdvanceTime(3 * Time::kOneSecondInMsec); VerifyOrQuit(!node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a secure connection again"); @@ -107,7 +110,8 @@ void TestBorderAgent(void) VerifyOrQuit(node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().GetCounters().mPskcSecureSessionSuccesses == 2); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send `Commissioner Petition` TMF command to become full commissioner"); @@ -119,14 +123,17 @@ void TestBorderAgent(void) nexus.AdvanceTime(1 * Time::kOneSecondInMsec); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateAccepted); + + VerifyOrQuit(node0.Get().GetCounters().mPskcSecureSessionSuccesses == 2); + VerifyOrQuit(node0.Get().GetCounters().mPskcCommissionerPetitions == 1); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send `Commissioner Keep Alive` and check timeout behavior"); nexus.AdvanceTime(30 * Time::kOneSecondInMsec); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateAccepted); VerifyOrQuit(node1.Get().IsConnected()); message = node1.Get().NewPriorityConfirmablePostMessage(kUriCommissionerKeepAlive); @@ -139,14 +146,14 @@ void TestBorderAgent(void) nexus.AdvanceTime(49 * Time::kOneSecondInMsec); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateAccepted); VerifyOrQuit(node1.Get().IsConnected()); Log(" Wait for additional 5 seconds (ensuring TIMEOUT_LEAD_PET and session disconnect guard time expires)"); nexus.AdvanceTime(5 * Time::kOneSecondInMsec); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); VerifyOrQuit(!node1.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -156,7 +163,7 @@ void TestBorderAgent(void) nexus.AdvanceTime(1 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); message = node1.Get().NewPriorityConfirmablePostMessage(kUriCommissionerPetition); VerifyOrQuit(message != nullptr); @@ -165,7 +172,10 @@ void TestBorderAgent(void) nexus.AdvanceTime(1 * Time::kOneSecondInMsec); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateAccepted); + + VerifyOrQuit(node0.Get().GetCounters().mPskcSecureSessionSuccesses == 3); + VerifyOrQuit(node0.Get().GetCounters().mPskcCommissionerPetitions == 2); } static bool sEphemeralKeyCallbackCalled = false; @@ -177,22 +187,27 @@ void HandleEphemeralKeyChange(void *aContext) VerifyOrQuit(aContext != nullptr); node = reinterpret_cast(aContext); - Log(" EphemeralKeyCallback() active:%u connected:%u", node->Get().IsEphemeralKeyActive(), - node->Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + Log(" EphemeralKeyCallback() state:%s", + BorderAgent::EphemeralKeyManager::StateToString(node->Get().GetState())); sEphemeralKeyCallbackCalled = true; } void TestBorderAgentEphemeralKey(void) { - static const char kEphemeralKey[] = "nexus1234"; + static const char kEphemeralKey[] = "nexus1234"; + static const char kTooShortEphemeralKey[] = "abcde"; + static const char kTooLongEphemeralKey[] = "012345678901234567890123456789012"; + static constexpr uint16_t kEphemeralKeySize = sizeof(kEphemeralKey) - 1; static constexpr uint16_t kUdpPort = 49155; - Core nexus; - Node &node0 = nexus.CreateNode(); - Node &node1 = nexus.CreateNode(); - Ip6::SockAddr sockAddr; - Coap::Message *message; + Core nexus; + Node &node0 = nexus.CreateNode(); + Node &node1 = nexus.CreateNode(); + Node &node2 = nexus.CreateNode(); + Ip6::SockAddr sockAddr; + Ip6::SockAddr baSockAddr; + Pskc pskc; Log("------------------------------------------------------------------------------------------------------"); Log("TestBorderAgentEphemeralKey"); @@ -201,7 +216,7 @@ void TestBorderAgentEphemeralKey(void) // Form the topology: // - node0 leader acting as Border Agent, - // - node1 staying disconnected (acting as candidate) + // - node1 and node2 staying disconnected (acting as candidate) node0.Form(); nexus.AdvanceTime(50 * Time::kOneSecondInMsec); @@ -211,34 +226,51 @@ void TestBorderAgentEphemeralKey(void) node1.Get().SetPanId(node0.Get().GetPanId()); node1.Get().Up(); + SuccessOrQuit(node2.Get().SetPanChannel(node0.Get().GetPanChannel())); + node2.Get().SetPanId(node0.Get().GetPanId()); + node2.Get().Up(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check Border Agent ephemeral key counter's initial value"); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationMaxAttempts == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationClears == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidArgsErrors == 0); + VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidBaStateErrors == 0); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check Border Agent ephemeral key feature enabled"); - node0.Get().SetEphemeralKeyFeatureEnabled(false); - VerifyOrQuit(!node0.Get().IsEphemeralKeyFeatureEnabled()); - VerifyOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort) == - kErrorNotCapable); + node0.Get().SetEnabled(false); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateDisabled); + VerifyOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, + kUdpPort) == kErrorInvalidState); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidBaStateErrors == 1); - node0.Get().SetEphemeralKeyFeatureEnabled(true); - VerifyOrQuit(node0.Get().IsEphemeralKeyFeatureEnabled()); + node0.Get().SetEnabled(true); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check Border Agent ephemeral key initial state"); sEphemeralKeyCallbackCalled = false; - VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); VerifyOrQuit(!sEphemeralKeyCallbackCalled); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Set and start ephemeral key on Border Agent"); - node0.Get().SetEphemeralKeyCallback(HandleEphemeralKeyChange, &node0); + node0.Get().SetCallback(HandleEphemeralKeyChange, &node0); - SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); - VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 1); SuccessOrQuit(node0.Get().AddUnsecurePort(kUdpPort)); @@ -261,8 +293,11 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(1 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 1); + VerifyOrQuit(sEphemeralKeyCallbackCalled); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -275,22 +310,23 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(3 * Time::kOneSecondInMsec); VerifyOrQuit(!node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); - - VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); VerifyOrQuit(sEphemeralKeyCallbackCalled); + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Start using ephemeral key again with short timeout (20 seconds) and establish a connection"); SuccessOrQuit( - node0.Get().SetEphemeralKey(kEphemeralKey, 20 * Time::kOneSecondInMsec, kUdpPort)); + node0.Get().Start(kEphemeralKey, 20 * Time::kOneSecondInMsec, kUdpPort)); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); - VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); SuccessOrQuit(node1.Get().Open()); SuccessOrQuit(node1.Get().Connect(sockAddr)); @@ -298,8 +334,11 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(2 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); Log(" Check the ephemeral key timeout behavior"); @@ -307,33 +346,41 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(25 * Time::kOneSecondInMsec); VerifyOrQuit(!node1.Get().IsConnected()); - VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); VerifyOrQuit(sEphemeralKeyCallbackCalled); + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Start using ephemeral key without any candidate connecting, check timeout"); - SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); - VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); Log(" Wait more than 120 seconds (default ephemeral key timeout)"); sEphemeralKeyCallbackCalled = false; nexus.AdvanceTime(122 * Time::kOneSecondInMsec); - VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); VerifyOrQuit(sEphemeralKeyCallbackCalled); + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 3); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check that ephemeral key use is stopped after max failed connection attempts"); - SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); - VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); - VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); SuccessOrQuit( node1.Get().SetPsk(reinterpret_cast(kEphemeralKey), kEphemeralKeySize - 2)); @@ -347,8 +394,7 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(3 * Time::kOneSecondInMsec); VerifyOrQuit(!node1.Get().IsConnected()); - VerifyOrQuit(node0.Get().GetState() != MeshCoP::BorderAgent::kStateConnected); - VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); } Log(" Attempt 10 (final attempt) to connect with the wrong key, check that ephemeral key use is stopped"); @@ -358,8 +404,146 @@ void TestBorderAgentEphemeralKey(void) nexus.AdvanceTime(3 * Time::kOneSecondInMsec); VerifyOrQuit(!node1.Get().IsConnected()); - VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 4); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationMaxAttempts == 1); + + node1.Get().Close(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate that disabling ephemeral key will stop and disconnect an active session"); + + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + SuccessOrQuit( + node1.Get().SetPsk(reinterpret_cast(kEphemeralKey), kEphemeralKeySize)); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + sEphemeralKeyCallbackCalled = false; + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + node0.Get().SetEnabled(false); + + nexus.AdvanceTime(0); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateDisabled); VerifyOrQuit(sEphemeralKeyCallbackCalled); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + VerifyOrQuit(!node1.Get().IsConnected()); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 5); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 3); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationMaxAttempts == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationClears == 1); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check ephemeral key can be used along with PSKc"); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + SuccessOrQuit(node0.Get().AddUnsecurePort(node0.Get().GetUdpPort())); + + node0.Get().SetEnabled(true); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); + + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + Log(" Establish a secure session using PSKc (from node2)"); + + baSockAddr.SetAddress(node0.Get().GetLinkLocalAddress()); + baSockAddr.SetPort(node0.Get().GetUdpPort()); + + node0.Get().GetPskc(pskc); + SuccessOrQuit(node2.Get().SetPsk(pskc.m8, Pskc::kSize)); + SuccessOrQuit(node2.Get().Open()); + SuccessOrQuit(node2.Get().Connect(baSockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node2.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + + Log(" Establish a secure session using ephemeral key (from node1)"); + + node1.Get().Close(); + SuccessOrQuit( + node1.Get().SetPsk(reinterpret_cast(kEphemeralKey), kEphemeralKeySize)); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + Log(" Stop the secure session using ephemeral key"); + + node0.Get().Stop(); + + sEphemeralKeyCallbackCalled = false; + + nexus.AdvanceTime(0); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 6); + VerifyOrQuit(node0.Get().GetCounters().mEpskcSecureSessionSuccesses == 4); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationTimeouts == 2); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationDisconnects == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationMaxAttempts == 1); + VerifyOrQuit(node0.Get().GetCounters().mEpskcDeactivationClears == 2); + + Log(" Check the BA session using PSKc is still connected"); + + VerifyOrQuit(node2.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(node2.Get().IsConnected()); + + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateStopped); + VerifyOrQuit(node0.Get().GetState() == BorderAgent::kStateConnected); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check proper error is returned with invalid ephemeral key (too short or long)"); + + VerifyOrQuit(node0.Get().Start(kTooShortEphemeralKey, /* aTimeout */ 0, + kUdpPort) == kErrorInvalidArgs); + + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 6); + VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidArgsErrors == 1); + + VerifyOrQuit(node0.Get().Start(kTooLongEphemeralKey, /* aTimeout */ 0, + kUdpPort) == kErrorInvalidArgs); + VerifyOrQuit(node0.Get().GetCounters().mEpskcActivations == 6); + VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidArgsErrors == 2); } } // namespace Nexus diff --git a/tests/scripts/expect/cli-misc.exp b/tests/scripts/expect/cli-misc.exp index 86433b16c9f..471eb92778e 100755 --- a/tests/scripts/expect/cli-misc.exp +++ b/tests/scripts/expect/cli-misc.exp @@ -181,12 +181,6 @@ expect ": InvalidState" send "ba port\n" expect "Done" -send "ba state\n" -expect "Done" - -send "ba disconnect\n" -expect "Done" - send "prefix meshlocal fd00:dead:beef:cafe::/96\n" expect_line "Error 7: InvalidArgs" send "prefix meshlocal fd00:dead:beef:cafe::/64\n" diff --git a/tests/scripts/thread-cert/border_router/test_ephemeral_key_counters.py b/tests/scripts/thread-cert/border_router/test_ephemeral_key_counters.py deleted file mode 100755 index 8bca67951aa..00000000000 --- a/tests/scripts/thread-cert/border_router/test_ephemeral_key_counters.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2024, 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 logging -import unittest - -import config -import thread_cert - -# Test description: -# This test verifies the counters of ephemeral key activations/deactivations. -# -# Topology: -# -------------- -# | -# BR1 -# - -BR1 = 1 - - -class EphemeralKeyCountersTest(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - - TOPOLOGY = { - BR1: { - 'name': 'BR_1', - 'is_otbr': True, - 'version': '1.4', - 'network_name': 'ot-br1', - }, - } - - def test(self): - br1 = self.nodes[BR1] - - counters = br1.get_border_agent_counters() - self.assertEqual(counters['epskcActivation'], 0) - self.assertEqual(counters['epskcApiDeactivation'], 0) - self.assertEqual(counters['epskcTimeoutDeactivation'], 0) - self.assertEqual(counters['epskcMaxAttemptDeactivation'], 0) - self.assertEqual(counters['epskcDisconnectDeactivation'], 0) - self.assertEqual(counters['epskcInvalidBaStateError'], 0) - self.assertEqual(counters['epskcInvalidArgsError'], 0) - self.assertEqual(counters['epskcStartSecureSessionError'], 0) - self.assertEqual(counters['epskcSecureSessionSuccess'], 0) - self.assertEqual(counters['epskcSecureSessionFailure'], 0) - self.assertEqual(counters['epskcCommissionerPetition'], 0) - self.assertEqual(counters['pskcSecureSessionSuccess'], 0) - self.assertEqual(counters['pskcSecureSessionFailure'], 0) - self.assertEqual(counters['pskcCommissionerPetition'], 0) - self.assertEqual(counters['mgmtActiveGet'], 0) - self.assertEqual(counters['mgmtPendingGet'], 0) - - # activate epskc before border agent is up returns an error - br1.set_epskc('123456789') - - counters = br1.get_border_agent_counters() - self.assertEqual(counters['epskcActivation'], 0) - self.assertEqual(counters['epskcInvalidBaStateError'], 1) - - br1.set_active_dataset(updateExisting=True, network_name='ot-br1') - br1.start() - self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) - self.assertEqual('leader', br1.get_state()) - - # activate epskc and let it timeout - br1.set_epskc('123456789', 10) - self.simulator.go(1) - - counters = br1.get_border_agent_counters() - self.assertEqual(counters['epskcActivation'], 1) - self.assertEqual(counters['epskcApiDeactivation'], 0) - self.assertEqual(counters['epskcTimeoutDeactivation'], 1) - - # activate epskc and clear it - br1.set_epskc('123456789', 10000) - self.simulator.go(1) - br1.clear_epskc() - - counters = br1.get_border_agent_counters() - self.assertEqual(counters['epskcActivation'], 2) - self.assertEqual(counters['epskcApiDeactivation'], 1) - self.assertEqual(counters['epskcTimeoutDeactivation'], 1) - - # set epskc with invalid passcode - br1.set_epskc('123') - self.simulator.go(1) - - counters = br1.get_border_agent_counters() - self.assertEqual(counters['epskcActivation'], 2) - self.assertEqual(counters['epskcApiDeactivation'], 1) - self.assertEqual(counters['epskcTimeoutDeactivation'], 1) - self.assertEqual(counters['epskcInvalidArgsError'], 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/scripts/thread-cert/border_router/test_publish_meshcop_service.py b/tests/scripts/thread-cert/border_router/test_publish_meshcop_service.py index d422e7546f5..7b5c8277a3d 100755 --- a/tests/scripts/thread-cert/border_router/test_publish_meshcop_service.py +++ b/tests/scripts/thread-cert/border_router/test_publish_meshcop_service.py @@ -106,13 +106,13 @@ def test(self): lifetime = 500_000 ephemeral_key = br1.activate_ephemeral_key_mode(lifetime) self.assertEqual(len(ephemeral_key), 9) - self.assertEqual(br1.get_ephemeral_key_state(), 'active') + self.assertEqual(br1.get_ephemeral_key_state(), 'Started') # check Meshcop-e service self.check_meshcop_e_service(host, True) # deactivate ePSKc mode in force br1.deactivate_ephemeral_key_mode(retain_active_session=False) - self.assertEqual(br1.get_ephemeral_key_state(), 'inactive') + self.assertEqual(br1.get_ephemeral_key_state(), 'Stopped') self.simulator.go(10) # check Meshcop-e service self.check_meshcop_e_service(host, False) @@ -121,13 +121,13 @@ def test(self): lifetime = 0 ephemeral_key = br1.activate_ephemeral_key_mode(lifetime) self.assertEqual(len(ephemeral_key), 9) - self.assertEqual(br1.get_ephemeral_key_state(), 'active') + self.assertEqual(br1.get_ephemeral_key_state(), 'Started') # check Meshcop-e service self.check_meshcop_e_service(host, True) # deactivate ePSKc mode NOT in force br1.deactivate_ephemeral_key_mode(retain_active_session=True) - self.assertEqual(br1.get_ephemeral_key_state(), 'inactive') + self.assertEqual(br1.get_ephemeral_key_state(), 'Stopped') self.simulator.go(10) # check Meshcop-e service self.check_meshcop_e_service(host, False) diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index ced19cff9e2..f0ff32f8ff3 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1456,16 +1456,6 @@ def get_trel_port(self): self.send_command(cmd) return int(self._expect_command_output()[0]) - def set_epskc(self, keystring: str, timeout=120000, port=0): - cmd = 'ba ephemeralkey set ' + keystring + ' ' + str(timeout) + ' ' + str(port) - self.send_command(cmd) - self._expect(r"(Done|Error .*)") - - def clear_epskc(self): - cmd = 'ba ephemeralkey clear' - self.send_command(cmd) - self._expect_done() - def get_border_agent_counters(self): cmd = 'ba counters' self.send_command(cmd) @@ -1934,7 +1924,7 @@ def set_state(self, state): def get_ephemeral_key_state(self): cmd = 'ba ephemeralkey' - states = [r'inactive', r'active'] + states = [r'Disabled', r'Stopped', r'Started', r'Connected', r'Accepted'] self.send_command(cmd) return self._expect_result(states) diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 0d1a636cda6..ee66f96fa81 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -525,20 +525,20 @@ def ba_get_state(self): def ba_get_port(self): return self._cli_single_output('ba port') - def ba_is_ephemeral_key_feature_enabled(self): - return self._cli_single_output('ba ephemeralkey feature') + def ba_ephemeral_key_get_state(self): + return self._cli_single_output('ba ephemeralkey') - def ba_set_ephemeral_key_feature_enabled(self, enable): - self._cli_no_output('ba ephemeralkey feature', 'enable' if enable else 'disable') + def ba_ephemeral_key_set_enabled(self, enable): + self._cli_no_output('ba ephemeralkey', 'enable' if enable else 'disable') - def ba_is_ephemeral_key_active(self): - return self._cli_single_output('ba ephemeralkey') + def ba_ephemeral_key_start(self, keystring, timeout=None, port=None): + self._cli_no_output('ba ephemeralkey start', keystring, timeout, port) - def ba_set_ephemeral_key(self, keystring, timeout=None, port=None): - self._cli_no_output('ba ephemeralkey set', keystring, timeout, port) + def ba_ephemeral_key_stop(self): + self._cli_no_output('ba ephemeralkey stop') - def ba_clear_ephemeral_key(self): - self._cli_no_output('ba ephemeralkey clear') + def ba_ephemeral_key_get_port(self): + return self._cli_single_output('ba ephemeralkey port') #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # UDP diff --git a/tests/toranj/cli/test-028-border-agent-ephemeral-key.py b/tests/toranj/cli/test-028-border-agent-ephemeral-key.py index d173fd74eae..ba060804248 100755 --- a/tests/toranj/cli/test-028-border-agent-ephemeral-key.py +++ b/tests/toranj/cli/test-028-border-agent-ephemeral-key.py @@ -55,41 +55,20 @@ verify(leader.get_state() == 'leader') -leader.ba_set_ephemeral_key_feature_enabled(False) -verify(leader.ba_is_ephemeral_key_feature_enabled() == 'Disabled') -leader.ba_set_ephemeral_key_feature_enabled(True) -verify(leader.ba_is_ephemeral_key_feature_enabled() == 'Enabled') +leader.ba_ephemeral_key_set_enabled(False) +verify(leader.ba_ephemeral_key_get_state() == 'Disabled') +leader.ba_ephemeral_key_set_enabled(True) +verify(leader.ba_ephemeral_key_get_state() == 'Stopped') -verify(leader.ba_is_ephemeral_key_active() == 'inactive') - -port = int(leader.ba_get_port()) - -leader.ba_set_ephemeral_key('password', 10000, 1234) +leader.ba_ephemeral_key_start('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') +verify(leader.ba_ephemeral_key_get_state() == 'Started') +verify(int(leader.ba_ephemeral_key_get_port()) == 1234) -leader.ba_clear_ephemeral_key() -verify(leader.ba_is_ephemeral_key_active() == 'inactive') -verify(int(leader.ba_get_port()) == port) +leader.ba_ephemeral_key_stop() +verify(leader.ba_ephemeral_key_get_state() == 'Stopped') # ----------------------------------------------------------------------------------------------------------------------- # Test finished