mirror of
https://github.com/espressif/openthread.git
synced 2026-08-05 02:17:47 +00:00
[border-agent] move EphemeralKeyManager to its own files (#12138)
Moves `EphemeralKeyManager` class and its implementation from `border_agent.hpp` and `border_agent.cpp` to their own separate files `border_agent_ephemeral_key.hpp/cpp`. This is a structural change to improve code organization and does not introduce any functional changes.
This commit is contained in:
@@ -386,6 +386,8 @@ openthread_core_files = [
|
||||
"meshcop/announce_begin_client.hpp",
|
||||
"meshcop/border_agent.cpp",
|
||||
"meshcop/border_agent.hpp",
|
||||
"meshcop/border_agent_ephemeral_key.cpp",
|
||||
"meshcop/border_agent_ephemeral_key.hpp",
|
||||
"meshcop/border_agent_tracker.cpp",
|
||||
"meshcop/border_agent_tracker.hpp",
|
||||
"meshcop/border_agent_txt_data.cpp",
|
||||
|
||||
@@ -161,6 +161,7 @@ set(COMMON_SOURCES
|
||||
mac/wakeup_tx_scheduler.cpp
|
||||
meshcop/announce_begin_client.cpp
|
||||
meshcop/border_agent.cpp
|
||||
meshcop/border_agent_ephemeral_key.cpp
|
||||
meshcop/border_agent_tracker.cpp
|
||||
meshcop/border_agent_txt_data.cpp
|
||||
meshcop/commissioner.cpp
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
#include "mac/mac.hpp"
|
||||
#include "mac/wakeup_tx_scheduler.hpp"
|
||||
#include "meshcop/border_agent.hpp"
|
||||
#include "meshcop/border_agent_ephemeral_key.hpp"
|
||||
#include "meshcop/border_agent_tracker.hpp"
|
||||
#include "meshcop/border_agent_txt_data.hpp"
|
||||
#include "meshcop/commissioner.hpp"
|
||||
|
||||
@@ -478,398 +478,6 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// EphemeralKeyManager
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
const char EphemeralKeyManager::kServiceType[] = "_meshcop-e._udp";
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::SetEnabled(bool aEnabled)
|
||||
{
|
||||
if (aEnabled)
|
||||
{
|
||||
VerifyOrExit(mState == kStateDisabled);
|
||||
SetState(kStateStopped);
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyOrExit(mState != kStateDisabled);
|
||||
Stop();
|
||||
SetState(kStateDisabled);
|
||||
}
|
||||
|
||||
Get<TxtData>().Refresh();
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
Error EphemeralKeyManager::Start(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
uint16_t length;
|
||||
|
||||
VerifyOrExit(mState == kStateStopped, error = kErrorInvalidState);
|
||||
|
||||
length = StringLength(aKeyString, kMaxKeyLength + 1);
|
||||
VerifyOrExit((length >= kMinKeyLength) && (length <= kMaxKeyLength), error = kErrorInvalidArgs);
|
||||
|
||||
IgnoreError(mDtlsTransport.SetMaxConnectionAttempts(kMaxConnectionAttempts, HandleTransportClosed, this));
|
||||
|
||||
mDtlsTransport.SetAcceptCallback(EphemeralKeyManager::HandleAcceptSession, this);
|
||||
mDtlsTransport.SetRemoveSessionCallback(EphemeralKeyManager::HandleRemoveSession, this);
|
||||
|
||||
SuccessOrExit(error = mDtlsTransport.Open());
|
||||
SuccessOrExit(error = mDtlsTransport.Bind(aUdpPort));
|
||||
|
||||
SuccessOrExit(
|
||||
error = mDtlsTransport.SetPsk(reinterpret_cast<const uint8_t *>(aKeyString), static_cast<uint8_t>(length)));
|
||||
|
||||
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<Manager>().mCounters.mEpskcActivations++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcActivated);
|
||||
#endif
|
||||
break;
|
||||
case kErrorInvalidState:
|
||||
Get<Manager>().mCounters.mEpskcInvalidBaStateErrors++;
|
||||
break;
|
||||
case kErrorInvalidArgs:
|
||||
Get<Manager>().mCounters.mEpskcInvalidArgsErrors++;
|
||||
break;
|
||||
default:
|
||||
Get<Manager>().mCounters.mEpskcStartSecureSessionErrors++;
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::Stop(void) { Stop(kReasonLocalDisconnect); }
|
||||
|
||||
void EphemeralKeyManager::Stop(DeactivationReason aReason)
|
||||
{
|
||||
switch (mState)
|
||||
{
|
||||
case kStateStarted:
|
||||
case kStateConnected:
|
||||
case kStateAccepted:
|
||||
break;
|
||||
case kStateDisabled:
|
||||
case kStateStopped:
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
LogInfo("Stopping ephemeral key use - reason: %s", DeactivationReasonToString(aReason));
|
||||
SetState(kStateStopped);
|
||||
|
||||
mTimer.Stop();
|
||||
mDtlsTransport.Close();
|
||||
|
||||
UpdateCountersAndRecordEvent(aReason);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::UpdateCountersAndRecordEvent(DeactivationReason aReason)
|
||||
{
|
||||
struct ReasonToCounterEventEntry
|
||||
{
|
||||
DeactivationReason mReason;
|
||||
uint8_t mEvent; // Raw values of `HistoryTracker::Local::Epskc` enum.
|
||||
uint32_t Counters::*mCounterPtr;
|
||||
};
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
#define ReasonEntry(kReason, kCounter, kEvent) \
|
||||
{ \
|
||||
kReason, \
|
||||
HistoryTracker::Local::kEvent, \
|
||||
&Counters::kCounter, \
|
||||
}
|
||||
#else
|
||||
#define ReasonEntry(kReason, kCounter, kEvent) {kReason, 0, &Counters::kCounter}
|
||||
#endif
|
||||
|
||||
static const ReasonToCounterEventEntry kReasonToCounterEventEntries[] = {
|
||||
ReasonEntry(kReasonLocalDisconnect, mEpskcDeactivationClears, kEpskcDeactivatedLocalClose),
|
||||
ReasonEntry(kReasonSessionTimeout, mEpskcDeactivationClears, kEpskcDeactivatedSessionTimeout),
|
||||
ReasonEntry(kReasonPeerDisconnect, mEpskcDeactivationDisconnects, kEpskcDeactivatedRemoteClose),
|
||||
ReasonEntry(kReasonSessionError, mEpskcStartSecureSessionErrors, kEpskcDeactivatedSessionError),
|
||||
ReasonEntry(kReasonMaxFailedAttempts, mEpskcDeactivationMaxAttempts, kEpskcDeactivatedMaxAttempts),
|
||||
ReasonEntry(kReasonEpskcTimeout, mEpskcDeactivationTimeouts, kEpskcDeactivatedEpskcTimeout),
|
||||
};
|
||||
|
||||
#undef ReasonEntry
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
HistoryTracker::EpskcEvent event = HistoryTracker::Local::kEpskcDeactivatedUnknown;
|
||||
#endif
|
||||
|
||||
for (const ReasonToCounterEventEntry &entry : kReasonToCounterEventEntries)
|
||||
{
|
||||
if (aReason == entry.mReason)
|
||||
{
|
||||
(Get<Manager>().mCounters.*(entry.mCounterPtr))++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
event = static_cast<HistoryTracker::EpskcEvent>(entry.mEvent);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::SetState(State aState)
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
bool isServiceRegistered = ShouldRegisterService();
|
||||
#endif
|
||||
|
||||
VerifyOrExit(mState != aState);
|
||||
LogInfo("Ephemeral key - state: %s -> %s", StateToString(mState), StateToString(aState));
|
||||
mState = aState;
|
||||
mCallbackTask.Post();
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
VerifyOrExit(isServiceRegistered != ShouldRegisterService());
|
||||
RegisterOrUnregisterService();
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
SecureSession *EphemeralKeyManager::HandleAcceptSession(void *aContext, const Ip6::MessageInfo &aMessageInfo)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aMessageInfo);
|
||||
|
||||
return static_cast<EphemeralKeyManager *>(aContext)->HandleAcceptSession();
|
||||
}
|
||||
|
||||
Manager::CoapDtlsSession *EphemeralKeyManager::HandleAcceptSession(void)
|
||||
{
|
||||
CoapDtlsSession *session = nullptr;
|
||||
|
||||
VerifyOrExit(mCoapDtlsSession == nullptr);
|
||||
|
||||
session = CoapDtlsSession::Allocate(GetInstance(), mDtlsTransport);
|
||||
VerifyOrExit(session != nullptr);
|
||||
|
||||
mCoapDtlsSession = session;
|
||||
|
||||
exit:
|
||||
return session;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleRemoveSession(void *aContext, SecureSession &aSession)
|
||||
{
|
||||
static_cast<EphemeralKeyManager *>(aContext)->HandleRemoveSession(aSession);
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleRemoveSession(SecureSession &aSession)
|
||||
{
|
||||
CoapDtlsSession &coapSession = static_cast<CoapDtlsSession &>(aSession);
|
||||
|
||||
coapSession.Cleanup();
|
||||
coapSession.Free();
|
||||
mCoapDtlsSession = nullptr;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleSessionConnected(void)
|
||||
{
|
||||
SetState(kStateConnected);
|
||||
Get<Manager>().mCounters.mEpskcSecureSessionSuccesses++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcConnected);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleSessionDisconnected(SecureSession::ConnectEvent aEvent)
|
||||
{
|
||||
DeactivationReason reason = kReasonUnknown;
|
||||
|
||||
// The ephemeral key can be used once
|
||||
VerifyOrExit((mState == kStateConnected) || (mState == kStateAccepted));
|
||||
|
||||
switch (aEvent)
|
||||
{
|
||||
case SecureSession::kDisconnectedError:
|
||||
reason = kReasonSessionError;
|
||||
break;
|
||||
case SecureSession::kDisconnectedPeerClosed:
|
||||
reason = kReasonPeerDisconnect;
|
||||
break;
|
||||
case SecureSession::kDisconnectedMaxAttempts:
|
||||
reason = kReasonMaxFailedAttempts;
|
||||
break;
|
||||
case SecureSession::kDisconnectedTimeout:
|
||||
reason = kReasonSessionTimeout;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Stop(reason);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleCommissionerPetitionAccepted(void)
|
||||
{
|
||||
SetState(kStateAccepted);
|
||||
Get<Manager>().mCounters.mEpskcCommissionerPetitions++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcPetitioned);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleTimer(void) { Stop(kReasonEpskcTimeout); }
|
||||
|
||||
void EphemeralKeyManager::HandleTask(void) { mCallback.InvokeIfSet(); }
|
||||
|
||||
void EphemeralKeyManager::HandleTransportClosed(void *aContext)
|
||||
{
|
||||
reinterpret_cast<EphemeralKeyManager *>(aContext)->HandleTransportClosed();
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleTransportClosed(void) { Stop(kReasonMaxFailedAttempts); }
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
|
||||
bool EphemeralKeyManager::ShouldRegisterService(void) const
|
||||
{
|
||||
bool shouldRegister = false;
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateDisabled:
|
||||
case kStateStopped:
|
||||
break;
|
||||
case kStateStarted:
|
||||
case kStateConnected:
|
||||
case kStateAccepted:
|
||||
shouldRegister = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return shouldRegister;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::RegisterOrUnregisterService(void)
|
||||
{
|
||||
Dnssd::Service service;
|
||||
|
||||
VerifyOrExit(Get<Dnssd>().IsReady());
|
||||
|
||||
service.Clear();
|
||||
service.mServiceInstance = Get<Manager>().GetServiceName();
|
||||
service.mServiceType = kServiceType;
|
||||
service.mPort = GetUdpPort();
|
||||
|
||||
if (ShouldRegisterService())
|
||||
{
|
||||
Get<Dnssd>().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Get<Dnssd>().UnregisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
|
||||
const char *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 *EphemeralKeyManager::DeactivationReasonToString(DeactivationReason aReason)
|
||||
{
|
||||
static const char *const kReasonStrings[] = {
|
||||
"LocalDisconnect", // (0) kReasonLocalDisconnect
|
||||
"PeerDisconnect", // (1) kReasonPeerDisconnect
|
||||
"SessionError", // (2) kReasonSessionError
|
||||
"SessionTimeout", // (3) kReasonSessionTimeout
|
||||
"MaxFailedAttempts", // (4) kReasonMaxFailedAttempts
|
||||
"EpskcTimeout", // (5) kReasonTimeout
|
||||
"Unknown", // (6) kReasonUnknown
|
||||
};
|
||||
|
||||
struct EnumCheck
|
||||
{
|
||||
InitEnumValidatorCounter();
|
||||
ValidateNextEnum(kReasonLocalDisconnect);
|
||||
ValidateNextEnum(kReasonPeerDisconnect);
|
||||
ValidateNextEnum(kReasonSessionError);
|
||||
ValidateNextEnum(kReasonSessionTimeout);
|
||||
ValidateNextEnum(kReasonMaxFailedAttempts);
|
||||
ValidateNextEnum(kReasonEpskcTimeout);
|
||||
ValidateNextEnum(kReasonUnknown);
|
||||
};
|
||||
|
||||
return kReasonStrings[aReason];
|
||||
}
|
||||
|
||||
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// `Manager::CoapDtlsSession
|
||||
|
||||
|
||||
@@ -364,180 +364,6 @@ private:
|
||||
|
||||
DeclareTmfHandler(Manager, kUriRelayRx);
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
/**
|
||||
* Manages the ephemeral key use by Border Agent.
|
||||
*/
|
||||
class EphemeralKeyManager : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
friend class Manager;
|
||||
|
||||
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.
|
||||
|
||||
/**
|
||||
* Represents the state of the `EphemeralKeyManager`.
|
||||
*/
|
||||
enum State : uint8_t
|
||||
{
|
||||
kStateDisabled = OT_BORDER_AGENT_STATE_DISABLED, ///< Ephemeral key feature is disabled.
|
||||
kStateStopped = OT_BORDER_AGENT_STATE_STOPPED, ///< Enabled, but the key is not set and started.
|
||||
kStateStarted = OT_BORDER_AGENT_STATE_STARTED, ///< Key is set and listening to accept connection.
|
||||
kStateConnected = OT_BORDER_AGENT_STATE_CONNECTED, ///< Session connected, not full commissioner.
|
||||
kStateAccepted = OT_BORDER_AGENT_STATE_ACCEPTED, ///< Session connected and accepted as full commissioner.
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the `EphemeralKeyManager`.
|
||||
*
|
||||
* @param[in] aInstance The OpenThread instance.
|
||||
*/
|
||||
explicit EphemeralKeyManager(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kMaxConnectionAttempts = 10;
|
||||
|
||||
static_assert(kMaxKeyLength <= Dtls::Transport::kPskMaxLength, "Max e-key len is larger than max PSK len");
|
||||
|
||||
using CoapDtlsSession = Manager::CoapDtlsSession;
|
||||
using Counters = Manager::Counters;
|
||||
|
||||
enum DeactivationReason : uint8_t
|
||||
{
|
||||
kReasonLocalDisconnect,
|
||||
kReasonPeerDisconnect,
|
||||
kReasonSessionError,
|
||||
kReasonSessionTimeout,
|
||||
kReasonMaxFailedAttempts,
|
||||
kReasonEpskcTimeout,
|
||||
kReasonUnknown,
|
||||
};
|
||||
|
||||
void SetState(State aState);
|
||||
void Stop(DeactivationReason 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);
|
||||
void UpdateCountersAndRecordEvent(DeactivationReason aReason);
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
bool ShouldRegisterService(void) const;
|
||||
void RegisterOrUnregisterService(void);
|
||||
#endif
|
||||
|
||||
// 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 *DeactivationReasonToString(DeactivationReason aReason);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
static const char kServiceType[];
|
||||
#endif
|
||||
|
||||
using TimeoutTimer = TimerMilliIn<EphemeralKeyManager, &EphemeralKeyManager::HandleTimer>;
|
||||
using CallbackTask = TaskletIn<EphemeralKeyManager, &EphemeralKeyManager::HandleTask>;
|
||||
|
||||
State mState;
|
||||
Dtls::Transport mDtlsTransport;
|
||||
CoapDtlsSession *mCoapDtlsSession;
|
||||
TimeoutTimer mTimer;
|
||||
CallbackTask mCallbackTask;
|
||||
Callback<CallbackHandler> mCallback;
|
||||
};
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
} // namespace BorderAgent
|
||||
} // namespace MeshCoP
|
||||
|
||||
@@ -547,10 +373,6 @@ DefineCoreType(otBorderAgentId, MeshCoP::BorderAgent::Id);
|
||||
|
||||
DefineCoreType(otBorderAgentSessionIterator, MeshCoP::BorderAgent::Manager::SessionIterator);
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
DefineMapEnum(otBorderAgentEphemeralKeyState, MeshCoP::BorderAgent::EphemeralKeyManager::State);
|
||||
#endif
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements the Border Agent Ephemeral Key Manager.
|
||||
*/
|
||||
|
||||
#include "border_agent_ephemeral_key.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
#include "instance/instance.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace MeshCoP {
|
||||
namespace BorderAgent {
|
||||
|
||||
RegisterLogModule("BorderAgent");
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
const char EphemeralKeyManager::kServiceType[] = "_meshcop-e._udp";
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::SetEnabled(bool aEnabled)
|
||||
{
|
||||
if (aEnabled)
|
||||
{
|
||||
VerifyOrExit(mState == kStateDisabled);
|
||||
SetState(kStateStopped);
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyOrExit(mState != kStateDisabled);
|
||||
Stop();
|
||||
SetState(kStateDisabled);
|
||||
}
|
||||
|
||||
Get<TxtData>().Refresh();
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
Error EphemeralKeyManager::Start(const char *aKeyString, uint32_t aTimeout, uint16_t aUdpPort)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
uint16_t length;
|
||||
|
||||
VerifyOrExit(mState == kStateStopped, error = kErrorInvalidState);
|
||||
|
||||
length = StringLength(aKeyString, kMaxKeyLength + 1);
|
||||
VerifyOrExit((length >= kMinKeyLength) && (length <= kMaxKeyLength), error = kErrorInvalidArgs);
|
||||
|
||||
IgnoreError(mDtlsTransport.SetMaxConnectionAttempts(kMaxConnectionAttempts, HandleTransportClosed, this));
|
||||
|
||||
mDtlsTransport.SetAcceptCallback(EphemeralKeyManager::HandleAcceptSession, this);
|
||||
mDtlsTransport.SetRemoveSessionCallback(EphemeralKeyManager::HandleRemoveSession, this);
|
||||
|
||||
SuccessOrExit(error = mDtlsTransport.Open());
|
||||
SuccessOrExit(error = mDtlsTransport.Bind(aUdpPort));
|
||||
|
||||
SuccessOrExit(
|
||||
error = mDtlsTransport.SetPsk(reinterpret_cast<const uint8_t *>(aKeyString), static_cast<uint8_t>(length)));
|
||||
|
||||
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<Manager>().mCounters.mEpskcActivations++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcActivated);
|
||||
#endif
|
||||
break;
|
||||
case kErrorInvalidState:
|
||||
Get<Manager>().mCounters.mEpskcInvalidBaStateErrors++;
|
||||
break;
|
||||
case kErrorInvalidArgs:
|
||||
Get<Manager>().mCounters.mEpskcInvalidArgsErrors++;
|
||||
break;
|
||||
default:
|
||||
Get<Manager>().mCounters.mEpskcStartSecureSessionErrors++;
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::Stop(void) { Stop(kReasonLocalDisconnect); }
|
||||
|
||||
void EphemeralKeyManager::Stop(DeactivationReason aReason)
|
||||
{
|
||||
switch (mState)
|
||||
{
|
||||
case kStateStarted:
|
||||
case kStateConnected:
|
||||
case kStateAccepted:
|
||||
break;
|
||||
case kStateDisabled:
|
||||
case kStateStopped:
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
LogInfo("Stopping ephemeral key use - reason: %s", DeactivationReasonToString(aReason));
|
||||
SetState(kStateStopped);
|
||||
|
||||
mTimer.Stop();
|
||||
mDtlsTransport.Close();
|
||||
|
||||
UpdateCountersAndRecordEvent(aReason);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::UpdateCountersAndRecordEvent(DeactivationReason aReason)
|
||||
{
|
||||
struct ReasonToCounterEventEntry
|
||||
{
|
||||
DeactivationReason mReason;
|
||||
uint8_t mEvent; // Raw values of `HistoryTracker::Local::Epskc` enum.
|
||||
uint32_t Counters::*mCounterPtr;
|
||||
};
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
#define ReasonEntry(kReason, kCounter, kEvent) \
|
||||
{ \
|
||||
kReason, \
|
||||
HistoryTracker::Local::kEvent, \
|
||||
&Counters::kCounter, \
|
||||
}
|
||||
#else
|
||||
#define ReasonEntry(kReason, kCounter, kEvent) {kReason, 0, &Counters::kCounter}
|
||||
#endif
|
||||
|
||||
static const ReasonToCounterEventEntry kReasonToCounterEventEntries[] = {
|
||||
ReasonEntry(kReasonLocalDisconnect, mEpskcDeactivationClears, kEpskcDeactivatedLocalClose),
|
||||
ReasonEntry(kReasonSessionTimeout, mEpskcDeactivationClears, kEpskcDeactivatedSessionTimeout),
|
||||
ReasonEntry(kReasonPeerDisconnect, mEpskcDeactivationDisconnects, kEpskcDeactivatedRemoteClose),
|
||||
ReasonEntry(kReasonSessionError, mEpskcStartSecureSessionErrors, kEpskcDeactivatedSessionError),
|
||||
ReasonEntry(kReasonMaxFailedAttempts, mEpskcDeactivationMaxAttempts, kEpskcDeactivatedMaxAttempts),
|
||||
ReasonEntry(kReasonEpskcTimeout, mEpskcDeactivationTimeouts, kEpskcDeactivatedEpskcTimeout),
|
||||
};
|
||||
|
||||
#undef ReasonEntry
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
HistoryTracker::EpskcEvent event = HistoryTracker::Local::kEpskcDeactivatedUnknown;
|
||||
#endif
|
||||
|
||||
for (const ReasonToCounterEventEntry &entry : kReasonToCounterEventEntries)
|
||||
{
|
||||
if (aReason == entry.mReason)
|
||||
{
|
||||
(Get<Manager>().mCounters.*(entry.mCounterPtr))++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
event = static_cast<HistoryTracker::EpskcEvent>(entry.mEvent);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::SetState(State aState)
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
bool isServiceRegistered = ShouldRegisterService();
|
||||
#endif
|
||||
|
||||
VerifyOrExit(mState != aState);
|
||||
LogInfo("Ephemeral key - state: %s -> %s", StateToString(mState), StateToString(aState));
|
||||
mState = aState;
|
||||
mCallbackTask.Post();
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
VerifyOrExit(isServiceRegistered != ShouldRegisterService());
|
||||
RegisterOrUnregisterService();
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
SecureSession *EphemeralKeyManager::HandleAcceptSession(void *aContext, const Ip6::MessageInfo &aMessageInfo)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aMessageInfo);
|
||||
|
||||
return static_cast<EphemeralKeyManager *>(aContext)->HandleAcceptSession();
|
||||
}
|
||||
|
||||
Manager::CoapDtlsSession *EphemeralKeyManager::HandleAcceptSession(void)
|
||||
{
|
||||
CoapDtlsSession *session = nullptr;
|
||||
|
||||
VerifyOrExit(mCoapDtlsSession == nullptr);
|
||||
|
||||
session = CoapDtlsSession::Allocate(GetInstance(), mDtlsTransport);
|
||||
VerifyOrExit(session != nullptr);
|
||||
|
||||
mCoapDtlsSession = session;
|
||||
|
||||
exit:
|
||||
return session;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleRemoveSession(void *aContext, SecureSession &aSession)
|
||||
{
|
||||
static_cast<EphemeralKeyManager *>(aContext)->HandleRemoveSession(aSession);
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleRemoveSession(SecureSession &aSession)
|
||||
{
|
||||
CoapDtlsSession &coapSession = static_cast<CoapDtlsSession &>(aSession);
|
||||
|
||||
coapSession.Cleanup();
|
||||
coapSession.Free();
|
||||
mCoapDtlsSession = nullptr;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleSessionConnected(void)
|
||||
{
|
||||
SetState(kStateConnected);
|
||||
Get<Manager>().mCounters.mEpskcSecureSessionSuccesses++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcConnected);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleSessionDisconnected(SecureSession::ConnectEvent aEvent)
|
||||
{
|
||||
DeactivationReason reason = kReasonUnknown;
|
||||
|
||||
// The ephemeral key can be used once
|
||||
VerifyOrExit((mState == kStateConnected) || (mState == kStateAccepted));
|
||||
|
||||
switch (aEvent)
|
||||
{
|
||||
case SecureSession::kDisconnectedError:
|
||||
reason = kReasonSessionError;
|
||||
break;
|
||||
case SecureSession::kDisconnectedPeerClosed:
|
||||
reason = kReasonPeerDisconnect;
|
||||
break;
|
||||
case SecureSession::kDisconnectedMaxAttempts:
|
||||
reason = kReasonMaxFailedAttempts;
|
||||
break;
|
||||
case SecureSession::kDisconnectedTimeout:
|
||||
reason = kReasonSessionTimeout;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Stop(reason);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleCommissionerPetitionAccepted(void)
|
||||
{
|
||||
SetState(kStateAccepted);
|
||||
Get<Manager>().mCounters.mEpskcCommissionerPetitions++;
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<HistoryTracker::Local>().RecordEpskcEvent(HistoryTracker::Local::kEpskcPetitioned);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleTimer(void) { Stop(kReasonEpskcTimeout); }
|
||||
|
||||
void EphemeralKeyManager::HandleTask(void) { mCallback.InvokeIfSet(); }
|
||||
|
||||
void EphemeralKeyManager::HandleTransportClosed(void *aContext)
|
||||
{
|
||||
reinterpret_cast<EphemeralKeyManager *>(aContext)->HandleTransportClosed();
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::HandleTransportClosed(void) { Stop(kReasonMaxFailedAttempts); }
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
|
||||
bool EphemeralKeyManager::ShouldRegisterService(void) const
|
||||
{
|
||||
bool shouldRegister = false;
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case kStateDisabled:
|
||||
case kStateStopped:
|
||||
break;
|
||||
case kStateStarted:
|
||||
case kStateConnected:
|
||||
case kStateAccepted:
|
||||
shouldRegister = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return shouldRegister;
|
||||
}
|
||||
|
||||
void EphemeralKeyManager::RegisterOrUnregisterService(void)
|
||||
{
|
||||
Dnssd::Service service;
|
||||
|
||||
VerifyOrExit(Get<Dnssd>().IsReady());
|
||||
|
||||
service.Clear();
|
||||
service.mServiceInstance = Get<Manager>().GetServiceName();
|
||||
service.mServiceType = kServiceType;
|
||||
service.mPort = GetUdpPort();
|
||||
|
||||
if (ShouldRegisterService())
|
||||
{
|
||||
Get<Dnssd>().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Get<Dnssd>().UnregisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
|
||||
const char *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 *EphemeralKeyManager::DeactivationReasonToString(DeactivationReason aReason)
|
||||
{
|
||||
static const char *const kReasonStrings[] = {
|
||||
"LocalDisconnect", // (0) kReasonLocalDisconnect
|
||||
"PeerDisconnect", // (1) kReasonPeerDisconnect
|
||||
"SessionError", // (2) kReasonSessionError
|
||||
"SessionTimeout", // (3) kReasonSessionTimeout
|
||||
"MaxFailedAttempts", // (4) kReasonMaxFailedAttempts
|
||||
"EpskcTimeout", // (5) kReasonEpskcTimeout
|
||||
"Unknown", // (6) kReasonUnknown
|
||||
};
|
||||
|
||||
struct EnumCheck
|
||||
{
|
||||
InitEnumValidatorCounter();
|
||||
ValidateNextEnum(kReasonLocalDisconnect);
|
||||
ValidateNextEnum(kReasonPeerDisconnect);
|
||||
ValidateNextEnum(kReasonSessionError);
|
||||
ValidateNextEnum(kReasonSessionTimeout);
|
||||
ValidateNextEnum(kReasonMaxFailedAttempts);
|
||||
ValidateNextEnum(kReasonEpskcTimeout);
|
||||
ValidateNextEnum(kReasonUnknown);
|
||||
};
|
||||
|
||||
return kReasonStrings[aReason];
|
||||
}
|
||||
|
||||
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
|
||||
|
||||
} // namespace BorderAgent
|
||||
} // namespace MeshCoP
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for the Border Agent Ephemeral Key Manager.
|
||||
*/
|
||||
|
||||
#ifndef BORDER_AGENT_EPHEMERAL_KEY_HPP_
|
||||
#define BORDER_AGENT_EPHEMERAL_KEY_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
#include <openthread/border_agent.h>
|
||||
|
||||
#include "common/locator.hpp"
|
||||
#include "common/non_copyable.hpp"
|
||||
#include "common/tasklet.hpp"
|
||||
#include "common/timer.hpp"
|
||||
#include "meshcop/border_agent.hpp"
|
||||
#include "meshcop/secure_transport.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace MeshCoP {
|
||||
namespace BorderAgent {
|
||||
|
||||
/**
|
||||
* Manages the ephemeral key use by Border Agent.
|
||||
*/
|
||||
class EphemeralKeyManager : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
friend class Manager;
|
||||
|
||||
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.
|
||||
|
||||
/**
|
||||
* Represents the state of the `EphemeralKeyManager`.
|
||||
*/
|
||||
enum State : uint8_t
|
||||
{
|
||||
kStateDisabled = OT_BORDER_AGENT_STATE_DISABLED, ///< Ephemeral key feature is disabled.
|
||||
kStateStopped = OT_BORDER_AGENT_STATE_STOPPED, ///< Enabled, but the key is not set and started.
|
||||
kStateStarted = OT_BORDER_AGENT_STATE_STARTED, ///< Key is set and listening to accept connection.
|
||||
kStateConnected = OT_BORDER_AGENT_STATE_CONNECTED, ///< Session connected, not full commissioner.
|
||||
kStateAccepted = OT_BORDER_AGENT_STATE_ACCEPTED, ///< Session connected and accepted as full commissioner.
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the `EphemeralKeyManager`.
|
||||
*
|
||||
* @param[in] aInstance The OpenThread instance.
|
||||
*/
|
||||
explicit EphemeralKeyManager(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* 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 `EphemeralKeyManager` 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);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kMaxConnectionAttempts = 10;
|
||||
|
||||
static_assert(kMaxKeyLength <= Dtls::Transport::kPskMaxLength, "Max e-key len is larger than max PSK len");
|
||||
|
||||
using CoapDtlsSession = Manager::CoapDtlsSession;
|
||||
using Counters = Manager::Counters;
|
||||
|
||||
enum DeactivationReason : uint8_t
|
||||
{
|
||||
kReasonLocalDisconnect,
|
||||
kReasonPeerDisconnect,
|
||||
kReasonSessionError,
|
||||
kReasonSessionTimeout,
|
||||
kReasonMaxFailedAttempts,
|
||||
kReasonEpskcTimeout,
|
||||
kReasonUnknown,
|
||||
};
|
||||
|
||||
void SetState(State aState);
|
||||
void Stop(DeactivationReason 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);
|
||||
void UpdateCountersAndRecordEvent(DeactivationReason aReason);
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
bool ShouldRegisterService(void) const;
|
||||
void RegisterOrUnregisterService(void);
|
||||
#endif
|
||||
|
||||
// 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 *DeactivationReasonToString(DeactivationReason aReason);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
static const char kServiceType[];
|
||||
#endif
|
||||
|
||||
using TimeoutTimer = TimerMilliIn<EphemeralKeyManager, &EphemeralKeyManager::HandleTimer>;
|
||||
using CallbackTask = TaskletIn<EphemeralKeyManager, &EphemeralKeyManager::HandleTask>;
|
||||
|
||||
State mState;
|
||||
Dtls::Transport mDtlsTransport;
|
||||
CoapDtlsSession *mCoapDtlsSession;
|
||||
TimeoutTimer mTimer;
|
||||
CallbackTask mCallbackTask;
|
||||
Callback<CallbackHandler> mCallback;
|
||||
};
|
||||
|
||||
} // namespace BorderAgent
|
||||
} // namespace MeshCoP
|
||||
|
||||
DefineMapEnum(otBorderAgentEphemeralKeyState, MeshCoP::BorderAgent::EphemeralKeyManager::State);
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
#endif // BORDER_AGENT_EPHEMERAL_KEY_HPP_
|
||||
Reference in New Issue
Block a user