Skip to content

Commit

Permalink
Merge pull request #242 from microsoft/hapdsetauthdata
Browse files Browse the repository at this point in the history
Enable configuring SAE passwords in hostapd layer
  • Loading branch information
abeltrano authored Mar 28, 2024
2 parents 82148aa + 5490862 commit 4c1d720
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 28 deletions.
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

0 comments on commit 4c1d720

Please sign in to comment.