Skip to content

Commit

Permalink
feat: wpa_cli use updated to configure WPA3
Browse files Browse the repository at this point in the history
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.

Signed-off-by: James Chapman <[email protected]>
  • Loading branch information
james-ctc committed Feb 22, 2024
1 parent ec7711e commit 7febcb5
Show file tree
Hide file tree
Showing 7 changed files with 280 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
89 changes: 58 additions & 31 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,42 +138,77 @@ 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
* - psk with hex values doesn't work (on its own) "<Passphrase>" is fine
* - sae_password "<Passphrase>" or ABCDEF0123456789...
*
* For WPA2 and WPA3 support both psk and sae_password will be configured
* - 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
*/

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;

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";
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";
} else {
key_mgt = "WPA-PSK WPA-PSK-SHA256 SAE";
psk_name = "psk";
}
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) {
const char* key_mgmt = (psk.empty()) ? "NONE" : "WPA-PSK WPA-PSK-SHA256 SAE";
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "key_mgmt", key_mgmt});
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) && (!psk.empty())) {
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "psk", psk});
}

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

if (hidden && (output.exit_code == 0)) {
Expand Down Expand Up @@ -264,8 +292,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,10 @@ 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),
key_mgmt_value() {
active_p = this;
}

Expand All @@ -65,8 +67,11 @@ 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] == "scan_ssid") {
scan_ssid_called = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ class RunApplication {
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;
std::string key_mgmt_value;

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::key_mgmt_value' is never used.

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

0 comments on commit 7febcb5

Please sign in to comment.