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

Setup module to support WPA3 Wi-Fi networks #555

Merged
merged 5 commits into from
Feb 23, 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
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 @@
std::map<std::string, module::CmdOutput> results;
bool signal_poll_called;
bool psk_called;
bool sae_password_called;

Check notice on line 20 in modules/Setup/tests/RunApplicationStub.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/Setup/tests/RunApplicationStub.hpp#L20

class member 'RunApplication::sae_password_called' is never used.
bool key_mgmt_called;
bool scan_ssid_called;
bool ieee80211w_called;

Check notice on line 23 in modules/Setup/tests/RunApplicationStub.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/Setup/tests/RunApplicationStub.hpp#L23

class member 'RunApplication::ieee80211w_called' is never used.
std::string key_mgmt_value;

Check notice on line 24 in modules/Setup/tests/RunApplicationStub.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/Setup/tests/RunApplicationStub.hpp#L24

class member 'RunApplication::key_mgmt_value' is never used.
std::string ieee80211w_value;

Check notice on line 25 in modules/Setup/tests/RunApplicationStub.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/Setup/tests/RunApplicationStub.hpp#L25

class member 'RunApplication::ieee80211w_value' is never used.

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