diff --git a/autotests/testBasicServiceSetRSN/bss_rsn_test.py b/autotests/testBasicServiceSetRSN/bss_rsn_test.py new file mode 100644 index 00000000..20e0b62c --- /dev/null +++ b/autotests/testBasicServiceSetRSN/bss_rsn_test.py @@ -0,0 +1,70 @@ +#! /usr/bin/python3 + +import unittest +import sys + +import dbus + +sys.path.append('../util') +import iwd +from iwd import IWD +from hwsim import Hwsim +from hostapd import HostapdCLI + +BSS_INTERFACE = 'net.connman.iwd.BasicServiceSet' + + +class Test(unittest.TestCase): + def _get_rsn(self, device, ssid): + ordered_network = device.get_ordered_network(ssid, full_scan=True) + network = ordered_network.network_object + self.assertEqual(len(network.extended_service_set), 1) + + path = network.extended_service_set[0] + proxy = self.wd._bus.get_object(iwd.IWD_SERVICE, path) + properties = dbus.Interface(proxy, iwd.DBUS_PROPERTIES) + + return properties.GetAll(BSS_INTERFACE)['RSN'] + + def test_rsn(self): + device = self.wd.list_devices(1)[0] + + rsn = self._get_rsn(device, 'ssidWPA2') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), {'wpa-psk'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + rsn = self._get_rsn(device, 'ssidSAE') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), {'sae'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + rsn = self._get_rsn(device, 'ssidTransition') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), + {'wpa-psk', 'sae'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + def setUp(self): + self.wd = IWD(True) + + def tearDown(self): + self.wd.stop() + self.wd = None + + @classmethod + def setUpClass(cls): + Hwsim() + cls.hostapd = [ + HostapdCLI(config='ssidWPA2.conf'), + HostapdCLI(config='ssidSAE.conf'), + HostapdCLI(config='ssidTransition.conf'), + ] + + @classmethod + def tearDownClass(cls): + cls.hostapd = None + + +if __name__ == '__main__': + unittest.main(exit=True) diff --git a/autotests/testBasicServiceSetRSN/hw.conf b/autotests/testBasicServiceSetRSN/hw.conf new file mode 100644 index 00000000..a43062d9 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/hw.conf @@ -0,0 +1,9 @@ +[SETUP] +num_radios=4 +hwsim_medium=yes +start_iwd=no + +[HOSTAPD] +rad0=ssidWPA2.conf +rad1=ssidSAE.conf +rad2=ssidTransition.conf diff --git a/autotests/testBasicServiceSetRSN/ssidSAE.conf b/autotests/testBasicServiceSetRSN/ssidSAE.conf new file mode 100644 index 00000000..2ec9ae61 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidSAE.conf @@ -0,0 +1,11 @@ +hw_mode=g +channel=2 +ssid=ssidSAE + +wpa=2 +wpa_key_mgmt=SAE +wpa_pairwise=CCMP +sae_password=secret123 +sae_groups=19 +ieee80211w=2 +sae_pwe=0 diff --git a/autotests/testBasicServiceSetRSN/ssidTransition.conf b/autotests/testBasicServiceSetRSN/ssidTransition.conf new file mode 100644 index 00000000..00170772 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidTransition.conf @@ -0,0 +1,12 @@ +hw_mode=g +channel=3 +ssid=ssidTransition + +wpa=2 +wpa_key_mgmt=WPA-PSK SAE +wpa_pairwise=CCMP +wpa_passphrase=secret123 +sae_password=secret123 +sae_groups=19 +ieee80211w=1 +sae_pwe=0 diff --git a/autotests/testBasicServiceSetRSN/ssidWPA2.conf b/autotests/testBasicServiceSetRSN/ssidWPA2.conf new file mode 100644 index 00000000..d706142b --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidWPA2.conf @@ -0,0 +1,8 @@ +hw_mode=g +channel=1 +ssid=ssidWPA2 + +wpa=2 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +wpa_passphrase=secret123 diff --git a/client/bss.c b/client/bss.c index def4de7f..b5c80087 100644 --- a/client/bss.c +++ b/client/bss.c @@ -32,6 +32,7 @@ struct bss { char *address; + char *rsn; }; static const char *get_address(const void *data) @@ -57,8 +58,82 @@ static void update_address(void *data, struct l_dbus_message_iter *variant) bss->address = l_strdup(value); } +static char *string_array_to_string(struct l_dbus_message_iter *variant) +{ + struct l_dbus_message_iter array; + const char *value; + char **strv; + char *result; + + if (!l_dbus_message_iter_get_variant(variant, "as", &array)) + return NULL; + + strv = l_strv_new(); + + while (l_dbus_message_iter_next_entry(&array, &value)) + strv = l_strv_append(strv, value); + + result = l_strjoinv(strv, ' '); + l_strv_free(strv); + + return result; +} + +static void update_rsn(void *data, struct l_dbus_message_iter *variant) +{ + struct bss *bss = data; + struct l_dbus_message_iter dict; + struct l_dbus_message_iter value; + const char *key; + const char *group_value; + char *key_mgmt = NULL; + char *pairwise = NULL; + char *group = NULL; + + l_free(bss->rsn); + bss->rsn = NULL; + + if (!variant || + !l_dbus_message_iter_get_variant(variant, "a{sv}", &dict)) + goto done; + + while (l_dbus_message_iter_next_entry(&dict, &key, &value)) { + if (!strcmp(key, "KeyMgmt")) { + l_free(key_mgmt); + key_mgmt = string_array_to_string(&value); + } else if (!strcmp(key, "Pairwise")) { + l_free(pairwise); + pairwise = string_array_to_string(&value); + } else if (!strcmp(key, "Group") && + l_dbus_message_iter_get_variant(&value, "s", + &group_value)) { + l_free(group); + group = l_strdup(group_value); + } + } + + if (!key_mgmt || !pairwise || !group) + goto done; + + bss->rsn = l_strdup_printf("KeyMgmt: %s; Pairwise: %s; Group: %s", + key_mgmt, pairwise, group); + +done: + l_free(key_mgmt); + l_free(pairwise); + l_free(group); +} + +static const char *get_rsn(const void *data) +{ + const struct bss *bss = data; + + return bss->rsn; +} + static const struct proxy_interface_property bss_properties[] = { { "Address", "s", update_address, get_address }, + { "RSN", "a{sv}", update_rsn, get_rsn }, { } }; @@ -72,6 +147,7 @@ static void bss_destroy(void *data) struct bss *bss = data; l_free(bss->address); + l_free(bss->rsn); l_free(bss); } diff --git a/client/station.c b/client/station.c index 5066a9f1..eccf8314 100644 --- a/client/station.c +++ b/client/station.c @@ -754,7 +754,7 @@ static enum cmd_status cmd_get_bsses(const char *device_name, sprintf(header, "%s BasicServiceSets", network_get_name(network_proxy)); - proxy_properties_display_header(header, MARGIN, 10, 18); + proxy_properties_display_header(header, MARGIN, 10, 47); for (e = l_queue_get_entries(bss_list); e; e = e->next) { const char *path = e->data; @@ -765,7 +765,7 @@ static enum cmd_status cmd_get_bsses(const char *device_name, continue; display_table_row(MARGIN, 1, strlen(path), path); - proxy_properties_display_inline(bss_i, MARGIN, 10, 18); + proxy_properties_display_inline(bss_i, MARGIN, 10, 47); display_table_row(MARGIN, 1, 1, ""); } diff --git a/doc/basic-service-set.txt b/doc/basic-service-set.txt index f827ca02..b4f2486f 100644 --- a/doc/basic-service-set.txt +++ b/doc/basic-service-set.txt @@ -8,3 +8,26 @@ Object path /net/connman/iwd/{phy0,phy1,...}/{1,2,...}/Xxx Properties string Address [readonly] MAC address of BSS + + dict RSN [readonly, optional] + + Security capabilities advertised in the RSN information + element. The dictionary contains the following entries: + + array(string) KeyMgmt + + Key management suites. Values use the same names as + the wpa_supplicant D-Bus BSS.RSN property, such as + "wpa-psk", "wpa-ft-psk", "sae", "ft-sae", + "wpa-eap", and "owe". + + array(string) Pairwise + + Pairwise ciphers. Possible values are "tkip", + "ccmp", "gcmp", "ccmp-256", and "gcmp-256". + + string Group + + Group cipher. Possible values are "wep40", "wep104", + "tkip", "ccmp", "gcmp", "ccmp-256", and + "gcmp-256". diff --git a/src/network.c b/src/network.c index a5a2375a..aa9e1281 100644 --- a/src/network.c +++ b/src/network.c @@ -2198,10 +2198,120 @@ static bool network_bss_property_get_address(struct l_dbus *dbus, return true; } +struct network_bss_suite_name { + uint32_t suite; + const char *name; +}; + +static const struct network_bss_suite_name network_bss_akm_names[] = { + { IE_RSN_AKM_SUITE_PSK, "wpa-psk" }, + { IE_RSN_AKM_SUITE_FT_USING_PSK, "wpa-ft-psk" }, + { IE_RSN_AKM_SUITE_PSK_SHA256, "wpa-psk-sha256" }, + { IE_RSN_AKM_SUITE_8021X, "wpa-eap" }, + { IE_RSN_AKM_SUITE_FT_OVER_8021X, "wpa-ft-eap" }, + { IE_RSN_AKM_SUITE_8021X_SHA256, "wpa-eap-sha256" }, + { IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256, "wpa-eap-suite-b" }, + { IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384, "wpa-eap-suite-b-192" }, + { IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384, "wpa-ft-eap-sha384" }, + { IE_RSN_AKM_SUITE_FILS_SHA256, "wpa-fils-sha256" }, + { IE_RSN_AKM_SUITE_FILS_SHA384, "wpa-fils-sha384" }, + { IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256, "wpa-ft-fils-sha256" }, + { IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384, "wpa-ft-fils-sha384" }, + { IE_RSN_AKM_SUITE_SAE_SHA256, "sae" }, + { IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256, "ft-sae" }, + { IE_RSN_AKM_SUITE_OWE, "owe" }, +}; + +static const struct network_bss_suite_name network_bss_pairwise_names[] = { + { IE_RSN_CIPHER_SUITE_TKIP, "tkip" }, + { IE_RSN_CIPHER_SUITE_CCMP, "ccmp" }, + { IE_RSN_CIPHER_SUITE_GCMP, "gcmp" }, + { IE_RSN_CIPHER_SUITE_CCMP_256, "ccmp-256" }, + { IE_RSN_CIPHER_SUITE_GCMP_256, "gcmp-256" }, +}; + +static const struct network_bss_suite_name network_bss_group_names[] = { + { IE_RSN_CIPHER_SUITE_WEP40, "wep40" }, + { IE_RSN_CIPHER_SUITE_WEP104, "wep104" }, + { IE_RSN_CIPHER_SUITE_TKIP, "tkip" }, + { IE_RSN_CIPHER_SUITE_CCMP, "ccmp" }, + { IE_RSN_CIPHER_SUITE_GCMP, "gcmp" }, + { IE_RSN_CIPHER_SUITE_CCMP_256, "ccmp-256" }, + { IE_RSN_CIPHER_SUITE_GCMP_256, "gcmp-256" }, +}; + +static void network_bss_append_suite_names( + struct l_dbus_message_builder *builder, + const char *name, uint32_t suites, + const struct network_bss_suite_name *names, + size_t names_len) +{ + size_t i; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', name); + l_dbus_message_builder_enter_variant(builder, "as"); + l_dbus_message_builder_enter_array(builder, "s"); + + for (i = 0; i < names_len; i++) + if (suites & names[i].suite) + l_dbus_message_builder_append_basic(builder, 's', + names[i].name); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); +} + +static const char *network_bss_group_cipher_name( + enum ie_rsn_cipher_suite cipher) +{ + size_t i; + + for (i = 0; i < L_ARRAY_SIZE(network_bss_group_names); i++) + if (network_bss_group_names[i].suite == cipher) + return network_bss_group_names[i].name; + + return ""; +} + +static bool network_bss_property_get_rsn(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct scan_bss *bss = user_data; + struct ie_rsn_info info; + const char *group; + + if (!bss->rsne || ie_parse_rsne_from_data(bss->rsne, bss->rsne[1] + 2, + &info) < 0) + return false; + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + network_bss_append_suite_names(builder, "KeyMgmt", info.akm_suites, + network_bss_akm_names, + L_ARRAY_SIZE(network_bss_akm_names)); + network_bss_append_suite_names(builder, "Pairwise", + info.pairwise_ciphers, + network_bss_pairwise_names, + L_ARRAY_SIZE(network_bss_pairwise_names)); + + group = network_bss_group_cipher_name(info.group_cipher); + dbus_append_dict_basic(builder, "Group", 's', group); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + static void setup_bss_interface(struct l_dbus_interface *interface) { l_dbus_interface_property(interface, "Address", 0, "s", network_bss_property_get_address, NULL); + l_dbus_interface_property(interface, "RSN", 0, "a{sv}", + network_bss_property_get_rsn, NULL); } static int network_init(void)