Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable configuring SAE passwords in hostapd layer #242

Merged
merged 7 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/linux/wpa-controller/Hostapd.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,56 @@ Hostapd::SetPairwiseCipherSuites(std::unordered_map<WpaSecurityProtocol, std::ve
}
}
}

void
Hostapd::SetSaePasswords(std::vector<SaePassword> saePasswords, EnforceConfigurationChange enforceConfigurationChange)
{
try {
// First set the special value which clears all saved passwords.
SetProperty(ProtocolHostapd::PropertyNameSaePassword, ProtocolHostapd::PropertyValueSaePasswordClearAll, EnforceConfigurationChange::Defer);

// Now set the new passwords, deferring the configuration change (if requested) until the end.
for (const auto& saePassword : saePasswords) {
AddSaePassword(saePassword, EnforceConfigurationChange::Defer);
}

// Now that all passwords are set, enforce the configuration change if requested.
if (enforceConfigurationChange == EnforceConfigurationChange::Now) {
Reload();
}
} catch (const HostapdException& e) {
throw HostapdException(std::format("Failed to set sae passwords ({})", e.what()));
}
}

void
Hostapd::AddSaePassword(SaePassword saePassword, EnforceConfigurationChange enforceConfigurationChange)
{
std::string credentialValue(std::cbegin(saePassword.Credential), std::cend(saePassword.Credential));
std::string saePasswordValue;
{
std::ostringstream saePasswordValueBuilder{};

saePasswordValueBuilder << credentialValue;
if (saePassword.PasswordId.has_value()) {
saePasswordValueBuilder << ProtocolHostapd::PropertyValueSaeKeyValueSeparator
<< ProtocolHostapd::PropertyValueSaePasswordKeyPasswordId << ProtocolHostapd::KeyValueDelimiter << saePassword.PasswordId.value();
}
if (saePassword.PeerMacAddress.has_value()) {
saePasswordValueBuilder << ProtocolHostapd::PropertyValueSaeKeyValueSeparator
<< ProtocolHostapd::PropertyValueSaePasswordKeyPeerMac << ProtocolHostapd::KeyValueDelimiter << saePassword.PeerMacAddress.value();
}
if (saePassword.VlanId.has_value()) {
saePasswordValueBuilder << ProtocolHostapd::PropertyValueSaeKeyValueSeparator
<< ProtocolHostapd::PropertyValueSaePasswordKeyVlanId << ProtocolHostapd::KeyValueDelimiter << saePassword.VlanId.value();
}

saePasswordValue = saePasswordValueBuilder.str();
}

try {
SetProperty(ProtocolHostapd::PropertyNameSaePassword, saePasswordValue, enforceConfigurationChange);
} catch (const HostapdException& e) {
throw HostapdException(std::format("Failed to add sae password ({})", e.what()));
}
}
18 changes: 18 additions & 0 deletions src/linux/wpa-controller/include/Wpa/Hostapd.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ struct Hostapd :
void
SetPairwiseCipherSuites(std::unordered_map<WpaSecurityProtocol, std::vector<WpaCipher>> protocolPairwiseCipherMap, EnforceConfigurationChange enforceConfigurationChange) override;

/**
* @brief Set the accepted SAE passwords for the interface.
*
* @param saePasswords The SAE passwords to set.
* @param enforceConfigurationChange When to enforce the configuration change. A value of 'Now' will trigger a configuration reload.
*/
void
SetSaePasswords(std::vector<SaePassword> saePasswords, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Now) override;

/**
* @brief Add an SAE password.
*
* @param saePassword The SAE password to add.
* @param enforceConfigurationChange When to enforce the configuration change. A value of 'Now' will trigger a configuration reload.
*/
void
AddSaePassword(SaePassword saePassword, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Defer) override;

private:
const std::string m_interface;
WpaController m_controller;
Expand Down
20 changes: 20 additions & 0 deletions src/linux/wpa-controller/include/Wpa/IHostapd.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#ifndef I_HOSTAPD_HXX
#define I_HOSTAPD_HXX

#include <cstdint>
#include <exception>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
Expand Down Expand Up @@ -169,6 +171,24 @@ struct IHostapd
*/
virtual void
SetPairwiseCipherSuites(std::unordered_map<WpaSecurityProtocol, std::vector<WpaCipher>> protocolCipherMap, EnforceConfigurationChange enforceConfigurationChange) = 0;

/**
* @brief Set the accepted SAE passwords for the interface.
*
* @param saePasswords The SAE passwords to set.
* @param enforceConfigurationChange When to enforce the configuration change. A value of 'Now' will trigger a configuration reload.
*/
virtual void
SetSaePasswords(std::vector<SaePassword> saePasswords, EnforceConfigurationChange enforceConfigurationChange) = 0;

/**
* @brief Add an SAE password.
*
* @param saePassword The SAE password to add.
* @param enforceConfigurationChange When to enforce the configuration change. A value of 'Now' will trigger a configuration reload.
*/
virtual void
AddSaePassword(SaePassword saePassword, EnforceConfigurationChange enforceConfigurationChange) = 0;
};

/**
Expand Down
19 changes: 19 additions & 0 deletions src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <algorithm>
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -501,6 +502,13 @@ struct ProtocolHostapd :
static constexpr auto PropertyNameWpaPsk = "wpa_psk";
static constexpr auto PropertyNameRsnPairwise = "rsn_pairwise";

static constexpr auto PropertyNameSaePassword = "sae_password";
static constexpr auto PropertyValueSaePasswordKeyPeerMac = "mac";
static constexpr auto PropertyValueSaePasswordKeyPasswordId = "id";
static constexpr auto PropertyValueSaePasswordKeyVlanId = "vlanid";
static constexpr auto PropertyValueSaePasswordClearAll = "";
static constexpr auto PropertyValueSaeKeyValueSeparator = "|";

// Response properties for the "STATUS" command.
// Note: all properties must be terminated with the key-value delimeter (=).
static constexpr auto ResponseStatusPropertyKeyState = PropertyNameState;
Expand Down Expand Up @@ -702,6 +710,17 @@ WpaAuthenticationAlgorithmPropertyValue(WpaAuthenticationAlgorithm wpaAuthentica
{
return std::to_underlying(wpaAuthenticationAlgorithm);
}

/**
* @brief SAE password entry.
*/
struct SaePassword
{
std::vector<uint8_t> Credential;
std::optional<std::string> PasswordId;
std::optional<std::string> PeerMacAddress;
std::optional<int32_t> VlanId;
};
} // namespace Wpa

#endif // HOSTAPD_PROTOCOL_HXX
10 changes: 10 additions & 0 deletions src/linux/wpa-controller/include/Wpa/WpaController.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ struct WpaController
*/
WpaController(std::string_view interfaceName, WpaType type, std::filesystem::path controlSocketPath);

WpaController(const WpaController&) = delete;

WpaController(WpaController&&) = delete;

WpaController&
operator=(const WpaController&) = delete;

WpaController&
operator=(WpaController&&) = delete;

/**
* @brief Destroy the WpaController object.
*/
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/linux/wpa-controller/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target_sources(wpa-controller-test-unit
target_include_directories(wpa-controller-test-unit
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(wpa-controller-test-unit
Expand All @@ -25,4 +26,10 @@ target_link_libraries(wpa-controller-test-unit
wpa-controller
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/detail/config/hostapd.conf.format.hxx.in
${CMAKE_CURRENT_BINARY_DIR}/hostapd.conf.format.hxx
@ONLY
)

catch_discover_tests(wpa-controller-test-unit)
93 changes: 90 additions & 3 deletions tests/unit/linux/wpa-controller/TestHostapd.cxx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

#include <chrono> // NOLINT
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <optional>
Expand All @@ -8,6 +9,8 @@
#include <string_view>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <vector>

#include <Wpa/Hostapd.hxx>
#include <Wpa/IHostapd.hxx>
Expand Down Expand Up @@ -604,8 +607,6 @@ TEST_CASE("Send SetPairwiseCipherSuites() command (root)", "[wpa][hostapd][clien
{
std::unordered_map<WpaSecurityProtocol, std::vector<WpaCipher>> protocolCipherMap{};

std::vector<WpaKeyManagement> keyManagementValidValues{};

for (const auto WpaSecurityProtocol : magic_enum::enum_values<WpaSecurityProtocol>() | std::views::filter(IsWpaSecurityProtocolSupported)) {
for (const auto wpaCipher : WpaCiphersAll | std::views::filter(IsWpaCipherSupported)) {
protocolCipherMap[WpaSecurityProtocol].push_back(wpaCipher);
Expand Down Expand Up @@ -674,4 +675,90 @@ TEST_CASE("Send SetAuthenticationAlgorithms() command (root)", "[wpa][hostapd][c
REQUIRE_NOTHROW(hostapd.SetAuthenticationAlgorithms({ WpaAuthenticationAlgorithm::OpenSystem, WpaAuthenticationAlgorithm::OpenSystem }, EnforceConfigurationChange::Now));
REQUIRE_NOTHROW(hostapd.SetAuthenticationAlgorithms({ WpaAuthenticationAlgorithm::OpenSystem, WpaAuthenticationAlgorithm::OpenSystem }, EnforceConfigurationChange::Defer));
}
}
}

namespace Wpa::Test
{
constexpr std::initializer_list<uint8_t> AsciiPassword{ 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64 };
constexpr auto PasswordIdValid{ "someid" };
constexpr auto PeerMacAddressValid{ "00:11:22:33:44:55" };
constexpr int32_t VlanIdValid{ 1 };

// NOLINTBEGIN(cert-err58-cpp)

const SaePassword SaePasswordValid1{
.Credential = AsciiPassword,
.PasswordId = std::nullopt,
.PeerMacAddress = std::nullopt,
.VlanId = std::nullopt,
};

const SaePassword SaePasswordValid2{
.Credential = AsciiPassword,
.PasswordId = PasswordIdValid,
.PeerMacAddress = std::nullopt,
.VlanId = std::nullopt,
};

const SaePassword SaePasswordValid3{
.Credential = AsciiPassword,
.PasswordId = PasswordIdValid,
.PeerMacAddress = PeerMacAddressValid,
.VlanId = std::nullopt,
};

const SaePassword SaePasswordValidComplete{
.Credential = AsciiPassword,
.PasswordId = PasswordIdValid,
.PeerMacAddress = PeerMacAddressValid,
.VlanId = VlanIdValid,
};

const std::vector<SaePassword> SaePasswordsValid{ SaePasswordValid1, SaePasswordValid2, SaePasswordValid3, SaePasswordValidComplete };

// NOLINTEND(cert-err58-cpp)
} // namespace Wpa::Test

TEST_CASE("Send AddSaePassword() command (root)", "[wpa][hostapd][client][remote]")
{
using namespace Wpa;
using namespace Wpa::Test;

Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault);

SECTION("Doesn't throw")
{
REQUIRE_NOTHROW(hostapd.AddSaePassword(SaePasswordValidComplete, EnforceConfigurationChange::Now));
REQUIRE_NOTHROW(hostapd.AddSaePassword(SaePasswordValidComplete, EnforceConfigurationChange::Defer));
}

SECTION("Succeeds with valid inputs")
{
for (const auto& saePassword : SaePasswordsValid) {
REQUIRE_NOTHROW(hostapd.AddSaePassword(saePassword, EnforceConfigurationChange::Now));
REQUIRE_NOTHROW(hostapd.AddSaePassword(saePassword, EnforceConfigurationChange::Defer));
}
}
}

TEST_CASE("Send SetSaePasswords() command (root)", "[wpa][hostapd][client][remote]")
{
using namespace Wpa;
using namespace Wpa::Test;

Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault);

SECTION("Doesn't throw")
{
REQUIRE_NOTHROW(hostapd.SetSaePasswords({ SaePasswordValidComplete }, EnforceConfigurationChange::Now));
REQUIRE_NOTHROW(hostapd.SetSaePasswords({ SaePasswordValidComplete }, EnforceConfigurationChange::Defer));
}

SECTION("Succeeds with valid inputs")
{
for (const auto& saePassword : SaePasswordsValid) {
REQUIRE_NOTHROW(hostapd.SetSaePasswords({ saePassword }, EnforceConfigurationChange::Now));
REQUIRE_NOTHROW(hostapd.SetSaePasswords({ saePassword }, EnforceConfigurationChange::Defer));
}
}
}
27 changes: 2 additions & 25 deletions tests/unit/linux/wpa-controller/detail/WpaDaemonManager.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,10 @@
#include <magic_enum.hpp>
#include <plog/Log.h>

#include "hostapd.conf.format.hxx"

namespace detail
{
/**
* @brief Default hostapd configuration file contents.
*
* This configuration file supports WPA2 with typical security settings. It is
* meant to be used with a virtualized wlan device created with the
* mac80211_hwsim kernel module. The intention is to use this for basic control
* socket tests, not to exercise specifically wlan functionality.
*
* This string must be formatted with std::format() to provide the interface
* name as the first interpolation argument.
*/
static constexpr auto WpaDaemonHostapdConfigurationFileContentsFormat = R"CONFIG(
interface={}
driver=nl80211
ctrl_interface=/var/run/hostapd
ssid=wificontrollertest
hw_mode=g
channel=1
auth_algs=3
wpa=2
wpa_passphrase=password
wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
)CONFIG";

/**
* @brief Write the default configuration file contents for the specified wpa
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

#ifndef HOSTAPD_CONF_FORMAT_HXX
#define HOSTAPD_CONF_FORMAT_HXX

namespace detail
{
static constexpr auto WpaDaemonHostapdConfigurationFileContentsFormat = R"CONFIG(
interface={}
driver=nl80211
ctrl_interface=@CMAKE_INSTALL_FULL_RUNSTATEDIR@/hostapd
ssid=wificontrollertest
hw_mode=g
channel=1
auth_algs=3
wpa=2
wpa_passphrase=password
wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
)CONFIG";

} // namespace detail

#endif // HOSTAPD_CONF_FORMAT_HXX
Loading