Skip to content

Commit

Permalink
Setup module to support WPA3 Wi-Fi networks (#555)
Browse files Browse the repository at this point in the history
* feat: add support for WPA3 networks
* feat: wpa_cli use updated to configure WPA3

WpaCliSetup extended to support open, WPA2 and WPA3. By default
it attempts to configure based on the existing arguments.
set_network can take an additional argument to explicitly choose
the network security type. (this could be an additional parameter over
MQTT).

Note: WPA3 doesn't support pre shared key (64 hex ditits), it requires the
password/passphrase. Hence over MQTT a quoted "psk": "\"thePassphrase\"" is
required and not the output from the wpa_passphrase command.

* fix: added ieee80211w setting to network when needed
* fix: remove duplicate call to publish_configured_networks()
* fix: configured_networks to publish a single array

Previously configured_networks published an array per wifi device
which resulted in empty arrays where a device had no configured
networks.

Signed-off-by: James Chapman <james.chapman@pionix.de>
  • Loading branch information
james-ctc authored Feb 23, 2024
1 parent e013817 commit fc986b9
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 52 deletions.
5 changes: 5 additions & 0 deletions modules/Setup/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ target_sources(${MODULE_NAME}

# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
target_compile_features(${MODULE_NAME} PRIVATE cxx_std_17)
if((${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME} OR ${PROJECT_NAME}_BUILD_TESTING) AND BUILD_TESTING)
include(CTest)
add_subdirectory(tests)
set(CMAKE_BUILD_TYPE Debug)
endif()
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
20 changes: 12 additions & 8 deletions modules/Setup/Setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,10 @@ void Setup::ready() {

this->discover_network_thread = std::thread([this]() {
while (true) {
if (wifi_scan_enabled) {
if ((this->config.setup_wifi) && (wifi_scan_enabled)) {
this->discover_network();
}
this->publish_hostname();
if (this->config.setup_wifi) {
this->publish_configured_networks();
}
std::this_thread::sleep_for(std::chrono::seconds(5));
}
});
Expand Down Expand Up @@ -517,17 +514,24 @@ bool Setup::rfkill_block(std::string rfkill_id) {

void Setup::publish_configured_networks() {
auto network_devices = this->get_network_devices();

WpaCliSetup::WifiNetworkStatusList all_wifi_networks;

for (auto device : network_devices) {
if (!device.wireless) {
continue;
}
WifiConfigureClass wifi;
auto network_list = wifi.list_networks_status(device.interface);
std::string network_list_var = this->var_base + "configured_networks";
json configured_networks_json = json::array();
configured_networks_json = network_list;
this->mqtt.publish(network_list_var, configured_networks_json.dump());
for (auto& i : network_list) {
all_wifi_networks.push_back(std::move(i));
}
}

std::string network_list_var = this->var_base + "configured_networks";
json configured_networks_json = json::array();
configured_networks_json = all_wifi_networks;
this->mqtt.publish(network_list_var, configured_networks_json.dump());
}

bool Setup::add_and_enable_network(const std::string& interface, const std::string& ssid, const std::string& psk,
Expand Down
108 changes: 85 additions & 23 deletions modules/Setup/WiFiSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ WpaCliSetup::WifiScanList WpaCliSetup::do_scan_results(const std::string& interf
++scan_results_it) {

std::vector<std::string> columns;
std::istringstream ss(*scan_results_it);
std::string value;
while (std::getline(ss, value, '\t')) {
std::istringstream stream(*scan_results_it);
for (std::string value; std::getline(stream, value, '\t');) {
columns.push_back(std::move(value));
}

Expand All @@ -74,13 +73,10 @@ WpaCliSetup::Status WpaCliSetup::do_status(const std::string& interface) {
auto output = run_application(wpa_cli, {"-i", interface, "status"});
if (output.exit_code == 0) {
auto scan_results = output.split_output;
for (auto scan_results_it = scan_results.begin(); scan_results_it != scan_results.end();
++scan_results_it) {

for (auto& scan_result : scan_results) {
std::vector<std::string> columns;
std::istringstream ss(*scan_results_it);
std::string value;
while (std::getline(ss, value, '=')) {
std::istringstream ss(scan_result);
for (std::string value; std::getline(ss, value, '=');) {
columns.push_back(std::move(value));
}

Expand All @@ -99,13 +95,10 @@ WpaCliSetup::Poll WpaCliSetup::do_signal_poll(const std::string& interface) {
auto output = run_application(wpa_cli, {"-i", interface, "signal_poll"});
if (output.exit_code == 0) {
auto scan_results = output.split_output;
for (auto scan_results_it = scan_results.begin(); scan_results_it != scan_results.end();
++scan_results_it) {

for (auto& scan_result : scan_results) {
std::vector<std::string> columns;
std::istringstream ss(*scan_results_it);
std::string value;
while (std::getline(ss, value, '=')) {
std::istringstream ss(scan_result);
for (std::string value; std::getline(ss, value, '=');) {
columns.push_back(std::move(value));
}

Expand Down Expand Up @@ -145,30 +138,100 @@ int WpaCliSetup::add_network(const std::string& interface) {
}

bool WpaCliSetup::set_network(const std::string& interface, int network_id, const std::string& ssid,
const std::string& psk, bool hidden) {
const std::string& psk, network_security_t mode, bool hidden) {
/*
* configuring a network needs:
* - ssid "<SSID>"
* - psk "<Passphrase>" or ABCDEF0123456789... (for WPA2)
* - sae_password "<Passphrase>" (for WPA3)
* - key_mgmt NONE (for open networks)
* - scan_ssid 1 (for hidden networks)
*
* Support for WPA3 requires:
* - key_mgmt WPA-PSK WPA-PSK-SHA256 SAE or SAE if WPA3 only
* - sae_password replaces psk, WPA3 doesn't support PreSharedKey (64 hex digits)
* the passphrase is required
* - for interworking WPA2 and WPA3 the passphrase is needed
* - psk with hex digits (PreSharedKey) doesn't work
*/

/*
* From wpa_supplicant/hostapd
* ieee80211w: Whether management frame protection (MFP) is enabled
* 0 = disabled (default)
* 1 = optional
* 2 = required
* The most common configuration options for this based on the PMF (protected
* management frames) certification program are:
* PMF enabled: ieee80211w=1 and wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256
* PMF required: ieee80211w=2 and wpa_key_mgmt=WPA-EAP-SHA256
* (and similarly for WPA-PSK and WPA-PSK-SHA256 if WPA2-Personal is used)
* WPA3-Personal-only mode: ieee80211w=2 and wpa_key_mgmt=SAE
*/

constexpr std::uint8_t wpa2_psk_size = 64U;

if (!is_wifi_interface(interface)) {
return false;
}

if (psk.empty()) {
// force security mode to none
mode = network_security_t::none;
}

const char* key_mgt = nullptr;
const char* psk_name = nullptr;
const char* ieee80211w = nullptr;

switch (mode) {
case network_security_t::none:
key_mgt = "NONE";
break;
case network_security_t::wpa2_only:
key_mgt = "WPA-PSK";
psk_name = "psk";
break;
case network_security_t::wpa3_only:
key_mgt = "SAE";
psk_name = "sae_password";
ieee80211w = "2";
break;
case network_security_t::wpa2_and_wpa3:
default:
if (psk.size() == wpa2_psk_size) {
// WPA3 doesn't support PSK (hex digits), it needs a passphrase
key_mgt = "WPA-PSK";
psk_name = "psk";
} else if (psk.size() > wpa2_psk_size) {
// WPA2 doesn't support passphrases > 63 characters
key_mgt = "SAE";
psk_name = "sae_password";
ieee80211w = "2";
} else {
key_mgt = "WPA-PSK WPA-PSK-SHA256 SAE";
psk_name = "psk";
ieee80211w = "1";
}
break;
}

auto network_id_string = std::to_string(network_id);
auto ssid_parameter = "\"" + ssid + "\"";

auto output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ssid", ssid_parameter});

if ((output.exit_code == 0) && (psk_name != nullptr)) {
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, psk_name, psk});
}

if (output.exit_code == 0) {
if (psk.empty()) {
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "key_mgmt", "NONE"});
} else {
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "psk", psk});
}
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "key_mgmt", key_mgt});
}

if ((output.exit_code == 0) && (ieee80211w != nullptr)) {
output =
run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ieee80211w", ieee80211w});
}

if (hidden && (output.exit_code == 0)) {
Expand Down Expand Up @@ -252,8 +315,7 @@ WpaCliSetup::WifiNetworkList WpaCliSetup::list_networks(const std::string& inter

std::vector<std::string> columns;
std::istringstream ss(*scan_results_it);
std::string value;
while (std::getline(ss, value, '\t')) {
for (std::string value; std::getline(ss, value, '\t');) {
columns.push_back(std::move(value));
}

Expand Down
36 changes: 23 additions & 13 deletions modules/Setup/WiFiSetup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,41 @@ namespace module {

class WpaCliSetup {
public:
typedef std::vector<std::string> flags_t;
using flags_t = std::vector<std::string>;

enum class network_security_t : std::uint8_t {
none,
wpa2_only,
wpa3_only,
wpa2_and_wpa3,
};

struct WifiScan {
std::string bssid;
std::string ssid;
flags_t flags;
int frequency;
int signal_level;
flags_t flags;
};
typedef std::vector<WifiScan> WifiScanList;
using WifiScanList = std::vector<WifiScan>;

struct WifiNetworkStatus {
std::string interface;
int network_id;
std::string ssid;
bool connected;
int network_id;
int signal_level;
bool connected;
};
typedef std::vector<WifiNetworkStatus> WifiNetworkStatusList;
using WifiNetworkStatusList = std::vector<WifiNetworkStatus>;

struct WifiNetwork {
int network_id;
std::string ssid;
int network_id;
};
typedef std::vector<WifiNetwork> WifiNetworkList;
using WifiNetworkList = std::vector<WifiNetwork>;

typedef std::map<std::string, std::string> Status;
typedef std::map<std::string, std::string> Poll;
using Status = std::map<std::string, std::string>;
using Poll = std::map<std::string, std::string>;

protected:
virtual bool do_scan(const std::string& interface);
Expand All @@ -48,11 +55,14 @@ class WpaCliSetup {
virtual flags_t parse_flags(const std::string& flags);

public:
virtual ~WpaCliSetup() {
}
virtual ~WpaCliSetup() = default;
virtual int add_network(const std::string& interface);
virtual bool set_network(const std::string& interface, int network_id, const std::string& ssid,
const std::string& psk, bool hidden = false);
const std::string& psk, network_security_t mode, bool hidden);
virtual bool set_network(const std::string& interface, int network_id, const std::string& ssid,
const std::string& psk, bool hidden) {
return set_network(interface, network_id, ssid, psk, network_security_t::wpa2_and_wpa3, hidden);
}
virtual bool enable_network(const std::string& interface, int network_id);
virtual bool disable_network(const std::string& interface, int network_id);
virtual bool select_network(const std::string& interface, int network_id);
Expand Down
19 changes: 19 additions & 0 deletions modules/Setup/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set(TEST_TARGET_NAME ${PROJECT_NAME}_tests)
add_executable(${TEST_TARGET_NAME})

target_include_directories(${TEST_TARGET_NAME} PUBLIC ${GTEST_INCLUDE_DIRS} . ..)

target_sources(${TEST_TARGET_NAME} PRIVATE
RunApplicationStub.cpp
WiFiSetupTest.cpp
../WiFiSetup.cpp
)

find_package(GTest REQUIRED)

target_link_libraries(${TEST_TARGET_NAME} PRIVATE
${GTEST_LIBRARIES}
${GTEST_MAIN_LIBRARIES}
)

add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include <RunApplicationStub.hpp>
#include "RunApplicationStub.hpp"
#include <gtest/gtest.h>

#include <utility>
Expand Down Expand Up @@ -47,8 +47,12 @@ RunApplication::RunApplication() :
}),
signal_poll_called(false),
psk_called(false),
sae_password_called(false),
key_mgmt_called(false),
scan_ssid_called(false) {
scan_ssid_called(false),
ieee80211w_called(false),
key_mgmt_value(),
ieee80211w_value() {
active_p = this;
}

Expand All @@ -65,8 +69,14 @@ module::CmdOutput RunApplication::run_application(const std::string& name, std::
} else if (args[2] == "set_network") {
if (args[4] == "psk") {
psk_called = true;
} else if (args[4] == "sae_password") {
sae_password_called = true;
} else if (args[4] == "key_mgmt") {
key_mgmt_called = true;
key_mgmt_value = args[5];
} else if (args[4] == "ieee80211w") {
ieee80211w_called = true;
ieee80211w_value = args[5];
} else if (args[4] == "scan_ssid") {
scan_ssid_called = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ class RunApplication {
std::map<std::string, module::CmdOutput> results;
bool signal_poll_called;
bool psk_called;
bool sae_password_called;
bool key_mgmt_called;
bool scan_ssid_called;
bool ieee80211w_called;
std::string key_mgmt_value;
std::string ieee80211w_value;

RunApplication();
virtual ~RunApplication();
Expand Down
Loading

0 comments on commit fc986b9

Please sign in to comment.