Skip to contentSkip to conversation

wscutil: Use a utility for building authorized_macs

PATCH In review Andrew Zaborowski 2 participants 9 messages Aug 28, 2020

lore
Share & tools
lore
#0 lore raw

PATCH 1/8 wscutil: Use a utility for building authorized_macs

Add the wfa_build_authorized_macs function (wfa_ prefix following the wfa_extract_ naming) and use it in wsc_build_probe_response. The logic is changed slightly to treat the first 6-zeros address in the array as the end of the array.

---
 src/wscutil.c | 32 +++++++++++++++++---------------
 1 file changed, 17 insertions(+), 15 deletions(-)
src/wscutil.c +17 -15
index d78a0354..f15cd215 100644
@@ -2094,6 +2094,20 @@ static void build_wsc_state(struct wsc_attr_builder *builder,
 	wsc_attr_builder_put_u8(builder, 1);				\
 	wsc_attr_builder_put_u8(builder, 0x20)
 
+static void wfa_build_authorized_macs(struct wsc_attr_builder *builder,
+				const uint8_t authorized_macs[static 30])
+{
+	int count;
+
+	for (count = 1; count < 5; count++)
+		if (util_mem_is_zero(authorized_macs + count * 6, 6))
+			break;
+
+	wsc_attr_builder_put_u8(builder, WSC_WFA_EXTENSION_AUTHORIZED_MACS);
+	wsc_attr_builder_put_u8(builder, count * 6);
+	wsc_attr_builder_put_bytes(builder, authorized_macs, count * 6);
+}
+
 uint8_t *wsc_build_credential(const struct wsc_credential *in, size_t *out_len)
 {
 	struct wsc_attr_builder *builder;
@@ -2204,21 +2218,9 @@ uint8_t *wsc_build_probe_response(
 
 	START_WFA_VENDOR_EXTENSION();
 
-	if (!util_mem_is_zero(probe_response->authorized_macs, 30)) {
-		int count;
-
-		for (count = 1; count < 5; count++)
-			if (util_mem_is_zero(probe_response->authorized_macs +
-						count * 6, 30 - count * 6))
-				break;
-
-		wsc_attr_builder_put_u8(builder,
-					WSC_WFA_EXTENSION_AUTHORIZED_MACS);
-		wsc_attr_builder_put_u8(builder, count * 6);
-		wsc_attr_builder_put_bytes(builder,
-					probe_response->authorized_macs,
-					count * 6);
-	}
+	if (!util_mem_is_zero(probe_response->authorized_macs, 6))
+		wfa_build_authorized_macs(builder,
+					probe_response->authorized_macs);
 
 	if (probe_response->reg_config_methods) {
 		wsc_attr_builder_put_u8(builder,
Signature · 1 line
2.25.1
#1 lore raw

PATCH 2/8 wscutil: Add wsc_build_beacon

For consistency also update wsc_build_probe_response to use the same __builtin_popcount based rf_bands check.

---
 src/wscutil.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/wscutil.h |  1 +
 2 files changed, 49 insertions(+), 1 deletion(-)
src/wscutil.c +48 -1
index f15cd215..f5ff5d75 100644
@@ -2138,6 +2138,53 @@ static void build_credential(struct wsc_attr_builder *builder,
 	l_free(data);
 }
 
+uint8_t *wsc_build_beacon(const struct wsc_beacon *beacon, size_t *out_len)
+{
+	struct wsc_attr_builder *builder;
+	uint8_t *ret;
+
+	builder = wsc_attr_builder_new(512);
+	build_version(builder, 0x10);
+	build_wsc_state(builder, beacon->state);
+
+	if (beacon->ap_setup_locked)
+		build_ap_setup_locked(builder, true);
+
+	if (beacon->selected_registrar) {
+		build_selected_registrar(builder, true);
+		build_device_password_id(builder, beacon->device_password_id);
+		build_selected_registrar_configuration_methods(builder,
+					beacon->selected_reg_config_methods);
+	}
+
+	/* These two "should be provided" if dual-band */
+	if (__builtin_popcount(beacon->rf_bands) > 1) {
+		if (beacon->selected_registrar)
+			build_uuid_e(builder, beacon->uuid_e);
+
+		build_rf_bands(builder, beacon->rf_bands);
+	}
+
+	if (!beacon->version2)
+		goto done;
+
+	START_WFA_VENDOR_EXTENSION();
+
+	if (!util_mem_is_zero(beacon->authorized_macs, 6))
+		wfa_build_authorized_macs(builder, beacon->authorized_macs);
+
+	if (beacon->reg_config_methods) {
+		wsc_attr_builder_put_u8(builder,
+			WSC_WFA_EXTENSION_REGISTRAR_CONFIGRATION_METHODS);
+		wsc_attr_builder_put_u8(builder, 2);
+		wsc_attr_builder_put_u16(builder, beacon->reg_config_methods);
+	}
+
+done:
+	ret = wsc_attr_builder_free(builder, false, out_len);
+	return ret;
+}
+
 uint8_t *wsc_build_probe_request(const struct wsc_probe_request *probe_request,
 							size_t *out_len)
 {
@@ -2210,7 +2257,7 @@ uint8_t *wsc_build_probe_response(
 	build_device_name(builder, probe_response->device_name);
 	build_configuration_methods(builder, probe_response->config_methods);
 
-	if (probe_response->rf_bands & (probe_response->rf_bands - 1))
+	if (__builtin_popcount(probe_response->rf_bands) > 1)
 		build_rf_bands(builder, probe_response->rf_bands);
 
 	if (!probe_response->version2)
src/wscutil.h +1 -0
index 45d72fbb..5bb26d84 100644
@@ -604,6 +604,7 @@ int wsc_parse_wsc_done(const uint8_t *pdu, uint32_t len, struct wsc_done *out);
 
 uint8_t *wsc_build_credential(const struct wsc_credential *in, size_t *out_len);
 
+uint8_t *wsc_build_beacon(const struct wsc_beacon *beacon, size_t *out_len);
 uint8_t *wsc_build_probe_request(const struct wsc_probe_request *probe_request,
 				size_t *out_len);
 uint8_t *wsc_build_probe_response(
Signature · 1 line
2.25.1
#2 lore raw

PATCH 3/8 eapol: Handle the use_eapol_start flag on authenticator

In reply to Andrew Zaborowski

Reuse this flag on the authenticator side with a slightly different
meaning: when it's true we're forced to wait for the EAPoL-Start before sending the first EAPoL-EAP frame to the supplicant, such as is required in a WSC enrollee registration when the Association Request didn't have a v2.0 WSC IE.

---
 src/eapol.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
src/eapol.c +9 -4
index 2d339163..977f720b 100644
@@ -2417,7 +2417,7 @@ bool eapol_start(struct eapol_sm *sm)
 		sm->timeout = l_timeout_create(eapol_4way_handshake_time,
 				eapol_timeout, sm, NULL);
 
-	if (sm->use_eapol_start) {
+	if (!sm->handshake->authenticator && sm->use_eapol_start) {
 		/*
 		 * We start a short timeout, if EAP packets are not received
 		 * from AP, then we send the EAPoL-Start
@@ -2442,9 +2442,14 @@ bool eapol_start(struct eapol_sm *sm)
 		if (!sm->protocol_version)
 			sm->protocol_version = EAPOL_PROTOCOL_VERSION_2004;
 
-		if (sm->handshake->settings_8021x)
-			eap_start(sm->eap);
-		else {
+		if (sm->handshake->settings_8021x) {
+			/*
+			 * If we're allowed to, send EAP Identity request
+			 * immediately, otherwise wait for an EAPoL-Start.
+			 */
+			if (!sm->use_eapol_start)
+				eap_start(sm->eap);
+		} else {
 			if (L_WARN_ON(!sm->handshake->have_pmk))
 				return false;
 
Signature · 1 line
2.25.1
#3 lore raw

PATCH 4/8 ap: Stop ongoing handshake on reassociation

In reply to Andrew Zaborowski

On a new association or re-association, in addition to forgetting a complete RSN Association, also stop the EAPoL SM to stop any ongoing handshake.

Do this in a new function ap_stop_handshake that is now used in a few places that had copies of the same few lines. I'll be adding some more lines to this function for WSC support.

---
 src/ap.c | 51 +++++++++++++++++++++++++--------------------------
 1 file changed, 25 insertions(+), 26 deletions(-)
src/ap.c +25 -26
index bc8b11e8..fa7d030a 100644
@@ -106,6 +106,19 @@ void ap_config_free(struct ap_config *config)
 	l_free(config);
 }
 
+static void ap_stop_handshake(struct sta_state *sta)
+{
+	if (sta->sm) {
+		eapol_sm_free(sta->sm);
+		sta->sm = NULL;
+	}
+
+	if (sta->hs) {
+		handshake_state_free(sta->hs);
+		sta->hs = NULL;
+	}
+}
+
 static void ap_sta_free(void *data)
 {
 	struct sta_state *sta = data;
@@ -120,11 +133,7 @@ static void ap_sta_free(void *data)
 	if (sta->gtk_query_cmd_id)
 		l_genl_family_cancel(ap->nl80211, sta->gtk_query_cmd_id);
 
-	if (sta->sm)
-		eapol_sm_free(sta->sm);
-
-	if (sta->hs)
-		handshake_state_free(sta->hs);
+	ap_stop_handshake(sta);
 
 	l_free(sta);
 }
@@ -180,14 +189,7 @@ static void ap_del_station(struct sta_state *sta, uint16_t reason,
 		sta->gtk_query_cmd_id = 0;
 	}
 
-	if (sta->sm)
-		eapol_sm_free(sta->sm);
-
-	if (sta->hs)
-		handshake_state_free(sta->hs);
-
-	sta->hs = NULL;
-	sta->sm = NULL;
+	ap_stop_handshake(sta);
 
 	if (send_event)
 		ap->event_func(AP_EVENT_STATION_REMOVED, &event_data,
@@ -268,14 +270,7 @@ static void ap_drop_rsna(struct sta_state *sta)
 		l_error("Issuing DEL_KEY failed");
 	}
 
-	if (sta->sm)
-		eapol_sm_free(sta->sm);
-
-	if (sta->hs)
-		handshake_state_free(sta->hs);
-
-	sta->hs = NULL;
-	sta->sm = NULL;
+	ap_stop_handshake(sta);
 
 	if (ap->event_func) {
 		struct ap_event_station_removed_data event_data = {};
@@ -938,6 +933,12 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 		goto unsupported;
 	}
 
+	/* 802.11-2016 11.3.5.3 j) */
+	if (sta->rsna)
+		ap_drop_rsna(sta);
+	else if (sta->associated)
+		ap_stop_handshake(sta);
+
 	if (!sta->associated) {
 		/*
 		 * Everything fine so far, assign an AID, send response.
@@ -961,10 +962,6 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 
 	sta->assoc_rsne = l_memdup(rsn, rsn[1] + 2);
 
-	/* 802.11-2016 11.3.5.3 j) */
-	if (sta->rsna)
-		ap_drop_rsna(sta);
-
 	sta->assoc_resp_cmd_id = ap_assoc_resp(ap, sta, sta->addr, sta->aid, 0,
 						reassoc,
 						ap_success_assoc_resp_cb);
@@ -987,8 +984,10 @@ bad_frame:
 	 *
 	 * For now, we need to drop the RSNA.
 	 */
-	if (sta->associated && sta->rsna)
+	if (sta->rsna)
 		ap_drop_rsna(sta);
+	else if (sta->associated)
+		ap_stop_handshake(sta);
 
 	if (rates)
 		l_uintset_free(rates);
Signature · 1 line
2.25.1
#4 lore raw

PATCH 5/8 ap: Push Button mode API and beacon changes

In reply to Andrew Zaborowski

This adds the API for putting the AP in Push Button mode, which we'll need to P2P GO side but may be useful on its own too. A WSC IE is added to our beacons and probe responses indicating whether the PBC mode is active.

---
 src/ap.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/ap.h |  15 ++++
 2 files changed, 217 insertions(+), 3 deletions(-)
src/ap.c +202 -3
index fa7d030a..ce776553 100644
@@ -45,6 +45,7 @@
 #include "src/dbus.h"
 #include "src/nl80211util.h"
 #include "src/frame-xchg.h"
+#include "src/wscutil.h"
 #include "src/ap.h"
 
 struct ap_state {
@@ -64,6 +65,9 @@ struct ap_state {
 	uint32_t mlme_watch;
 	uint8_t gtk[CRYPTO_MAX_GTK_LEN];
 	uint8_t gtk_index;
+	struct l_timeout *wsc_pbc_timeout;
+	uint16_t wsc_dpid;
+	uint8_t wsc_uuid_r[16];
 
 	uint16_t last_aid;
 	struct l_queue *sta_states;
@@ -103,6 +107,7 @@ void ap_config_free(struct ap_config *config)
 	}
 
 	l_free(config->authorized_macs);
+	l_free(config->wsc_name);
 	l_free(config);
 }
 
@@ -358,10 +363,15 @@ static size_t ap_build_beacon_pr_head(struct ap_state *ap,
 }
 
 /* Beacon / Probe Response frame portion after the TIM IE */
-static size_t ap_build_beacon_pr_tail(struct ap_state *ap, uint8_t *out_buf)
+static size_t ap_build_beacon_pr_tail(struct ap_state *ap, bool pr,
+					uint8_t *out_buf)
 {
 	size_t len;
 	struct ie_rsn_info rsn;
+	uint8_t *wsc_data;
+	size_t wsc_data_size;
+	uint8_t *wsc_ie;
+	size_t wsc_ie_size;
 
 	/* TODO: Country IE between TIM IE and RSNE */
 
@@ -371,9 +381,140 @@ static size_t ap_build_beacon_pr_tail(struct ap_state *ap, uint8_t *out_buf)
 		return 0;
 	len = 2 + out_buf[1];
 
+	/* WSC IE */
+	if (pr) {
+		struct wsc_probe_response wsc_pr = {};
+
+		wsc_pr.version2 = true;
+		wsc_pr.state = WSC_STATE_CONFIGURED;
+
+		if (ap->wsc_pbc_timeout) {
+			wsc_pr.selected_registrar = true;
+			wsc_pr.device_password_id = ap->wsc_dpid;
+			wsc_pr.selected_reg_config_methods =
+				WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
+		}
+
+		wsc_pr.response_type = WSC_RESPONSE_TYPE_AP;
+		memcpy(wsc_pr.uuid_e, ap->wsc_uuid_r, sizeof(wsc_pr.uuid_e));
+		wsc_pr.primary_device_type =
+			ap->config->wsc_primary_device_type;
+
+		if (ap->config->wsc_name)
+			l_strlcpy(wsc_pr.device_name, ap->config->wsc_name,
+					sizeof(wsc_pr.device_name));
+
+		wsc_pr.config_methods =
+			WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
+
+		if (ap->config->authorized_macs_num) {
+			size_t len;
+
+			len = ap->config->authorized_macs_num * 6;
+			if (len > sizeof(wsc_pr.authorized_macs))
+				len = sizeof(wsc_pr.authorized_macs);
+
+			memcpy(wsc_pr.authorized_macs,
+				ap->config->authorized_macs, len);
+		}
+
+		wsc_data = wsc_build_probe_response(&wsc_pr, &wsc_data_size);
+	} else {
+		struct wsc_beacon wsc_beacon = {};
+
+		wsc_beacon.version2 = true;
+		wsc_beacon.state = WSC_STATE_CONFIGURED;
+
+		if (ap->wsc_pbc_timeout) {
+			wsc_beacon.selected_registrar = true;
+			wsc_beacon.device_password_id = ap->wsc_dpid;
+			wsc_beacon.selected_reg_config_methods =
+				WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
+		}
+
+		if (ap->config->authorized_macs_num) {
+			size_t len;
+
+			len = ap->config->authorized_macs_num * 6;
+			if (len > sizeof(wsc_beacon.authorized_macs))
+				len = sizeof(wsc_beacon.authorized_macs);
+
+			memcpy(wsc_beacon.authorized_macs,
+				ap->config->authorized_macs, len);
+		}
+
+		wsc_data = wsc_build_beacon(&wsc_beacon, &wsc_data_size);
+	}
+
+	if (!wsc_data)
+		return 0;
+
+	wsc_ie = ie_tlv_encapsulate_wsc_payload(wsc_data, wsc_data_size,
+						&wsc_ie_size);
+	l_free(wsc_data);
+
+	if (!wsc_ie)
+		return 0;
+
+	memcpy(out_buf + len, wsc_ie, wsc_ie_size);
+	len += wsc_ie_size;
+	l_free(wsc_ie);
+
 	return len;
 }
 
+static void ap_set_beacon_cb(struct l_genl_msg *msg, void *user_data)
+{
+	int error = l_genl_msg_get_error(msg);
+
+	if (error < 0)
+		l_error("SET_BEACON failed: %s (%i)", strerror(-error), -error);
+}
+
+static void ap_update_beacon(struct ap_state *ap)
+{
+	struct l_genl_msg *cmd;
+	uint8_t head[256], tail[256];
+	size_t head_len, tail_len;
+	uint64_t wdev_id = netdev_get_wdev_id(ap->netdev);
+	static const uint8_t bcast_addr[6] = {
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+	};
+
+	head_len = ap_build_beacon_pr_head(ap, MPDU_MANAGEMENT_SUBTYPE_BEACON,
+						bcast_addr, head, sizeof(head));
+	tail_len = ap_build_beacon_pr_tail(ap, false, tail);
+	if (L_WARN_ON(!head_len || !tail_len))
+		return;
+
+	cmd = l_genl_msg_new_sized(NL80211_CMD_SET_BEACON,
+					32 + head_len + tail_len);
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_WDEV, 8, &wdev_id);
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_BEACON_HEAD, head_len, head);
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_BEACON_TAIL, tail_len, tail);
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_IE, 0, "");
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_IE_PROBE_RESP, 0, "");
+	l_genl_msg_append_attr(cmd, NL80211_ATTR_IE_ASSOC_RESP, 0, "");
+
+	if (l_genl_family_send(ap->nl80211, cmd, ap_set_beacon_cb, NULL, NULL))
+		return;
+
+	l_genl_msg_unref(cmd);
+	l_error("Issuing SET_BEACON failed");
+}
+
+static void ap_wsc_exit_pbc(struct ap_state *ap)
+{
+	if (!ap->wsc_pbc_timeout)
+		return;
+
+	l_timeout_remove(ap->wsc_pbc_timeout);
+	ap->wsc_dpid = 0;
+	ap_update_beacon(ap);
+
+	ap->event_func(AP_EVENT_PBC_MODE_EXIT, NULL, ap->user_data);
+}
+
 static uint32_t ap_send_mgmt_frame(struct ap_state *ap,
 					const struct mmpdu_header *frame,
 					size_t frame_len, bool wait_ack,
@@ -1069,6 +1210,9 @@ bad_frame:
 		l_error("Sending error Reassociation Response failed");
 }
 
+#define AP_WSC_PBC_MONITOR_TIME	120
+#define AP_WSC_PBC_WALK_TIME	120
+
 static void ap_probe_resp_cb(struct l_genl_msg *msg, void *user_data)
 {
 	if (l_genl_msg_get_error(msg) < 0)
@@ -1167,7 +1311,7 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
 	len = ap_build_beacon_pr_head(ap,
 					MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE,
 					hdr->address_2, resp, sizeof(resp));
-	len += ap_build_beacon_pr_tail(ap, resp + len);
+	len += ap_build_beacon_pr_tail(ap, true, resp + len);
 
 	ap_send_mgmt_frame(ap, (struct mmpdu_header *) resp, len, false,
 				ap_probe_resp_cb, NULL);
@@ -1397,7 +1541,7 @@ static struct l_genl_msg *ap_build_cmd_start_ap(struct ap_state *ap)
 
 	head_len = ap_build_beacon_pr_head(ap, MPDU_MANAGEMENT_SUBTYPE_BEACON,
 						bcast_addr, head, sizeof(head));
-	tail_len = ap_build_beacon_pr_tail(ap, tail);
+	tail_len = ap_build_beacon_pr_tail(ap, false, tail);
 
 	if (!head_len || !tail_len)
 		return NULL;
@@ -1501,6 +1645,17 @@ struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
 		/* TODO: Start a Get Survey to decide the channel */
 		config->channel = 6;
 
+	if (!config->wsc_name)
+		config->wsc_name = l_strdup(config->ssid);
+
+	if (!config->wsc_primary_device_type.category) {
+		/* Make ourselves a WFA standard PC by default */
+		config->wsc_primary_device_type.category = 1;
+		memcpy(config->wsc_primary_device_type.oui, wsc_wfa_oui, 3);
+		config->wsc_primary_device_type.oui_type = 0x04;
+		config->wsc_primary_device_type.subcategory = 1;
+	}
+
 	/* TODO: Add all ciphers supported by wiphy */
 	ap->ciphers = wiphy_select_cipher(wiphy, 0xffff);
 	ap->group_cipher = wiphy_select_cipher(wiphy, 0xffff);
@@ -1523,6 +1678,8 @@ struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
 		l_uintset_put(ap->rates, 22); /* 11 Mbps*/
 	}
 
+	wsc_uuid_from_addr(netdev_get_address(netdev), ap->wsc_uuid_r);
+
 	if (crypto_psk_from_passphrase(config->psk, (uint8_t *) config->ssid,
 					strlen(config->ssid), ap->pmk) < 0)
 		goto error;
@@ -1702,6 +1859,45 @@ bool ap_station_disconnect(struct ap_state *ap, const uint8_t *mac,
 	return true;
 }
 
+static void ap_wsc_pbc_timeout_cb(struct l_timeout *timeout, void *user_data)
+{
+	struct ap_state *ap = user_data;
+
+	l_debug("PBC mode timeout");
+	ap_wsc_exit_pbc(ap);
+}
+
+static void ap_wsc_pbc_timeout_destroy(void *user_data)
+{
+	struct ap_state *ap = user_data;
+
+	ap->wsc_pbc_timeout = NULL;
+}
+
+bool ap_push_button(struct ap_state *ap)
+{
+	if (!ap->started)
+		return false;
+
+	/*
+	 * WSC v2.0.5 Section 11.3: "Multiple presses of the button are
+	 * permitted.  If a PBC button on an Enrollee or Registrar is
+	 * pressed again during Walk Time, the timers for that device are
+	 * restarted at that time [...]"
+	 */
+	if (ap->wsc_pbc_timeout) {
+		l_timeout_modify(ap->wsc_pbc_timeout, AP_WSC_PBC_WALK_TIME);
+		return true;
+	}
+
+	ap->wsc_pbc_timeout = l_timeout_create(AP_WSC_PBC_WALK_TIME,
+						ap_wsc_pbc_timeout_cb, ap,
+						ap_wsc_pbc_timeout_destroy);
+	ap->wsc_dpid = WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON;
+	ap_update_beacon(ap);
+	return true;
+}
+
 struct ap_if_data {
 	struct netdev *netdev;
 	struct ap_state *ap;
@@ -1747,6 +1943,9 @@ static void ap_if_event_func(enum ap_event_type type, const void *event_data,
 
 	case AP_EVENT_STATION_ADDED:
 	case AP_EVENT_STATION_REMOVED:
+	case AP_EVENT_REGISTRATION_START:
+	case AP_EVENT_REGISTRATION_SUCCESS:
+	case AP_EVENT_PBC_MODE_EXIT:
 		/* Ignored */
 		break;
 	}
src/ap.h +15 -0
index 330ae011..cb5238d1 100644
@@ -29,6 +29,9 @@ enum ap_event_type {
 	AP_EVENT_STOPPING,
 	AP_EVENT_STATION_ADDED,
 	AP_EVENT_STATION_REMOVED,
+	AP_EVENT_REGISTRATION_START,
+	AP_EVENT_REGISTRATION_SUCCESS,
+	AP_EVENT_PBC_MODE_EXIT,
 };
 
 struct ap_event_station_added_data {
@@ -41,6 +44,14 @@ struct ap_event_station_removed_data {
 	enum mmpdu_reason_code reason;
 };
 
+struct ap_event_registration_start_data {
+	const uint8_t *mac;
+};
+
+struct ap_event_registration_success_data {
+	const uint8_t *mac;
+};
+
 typedef void (*ap_event_func_t)(enum ap_event_type type, const void *event_data,
 				void *user_data);
 typedef void (*ap_stopped_func_t)(void *user_data);
@@ -51,6 +62,8 @@ struct ap_config {
 	uint8_t channel;
 	uint8_t *authorized_macs;
 	unsigned int authorized_macs_num;
+	char *wsc_name;
+	struct wsc_primary_device_type wsc_primary_device_type;
 	bool no_cck_rates : 1;
 };
 
@@ -64,3 +77,5 @@ void ap_free(struct ap_state *ap);
 
 bool ap_station_disconnect(struct ap_state *ap, const uint8_t *mac,
 				enum mmpdu_reason_code reason);
+
+bool ap_push_button(struct ap_state *ap);
Signature · 1 line
2.25.1
#5 lore raw

PATCH 6/8 ap: WSC Probe Request processing logic

In reply to Andrew Zaborowski

Implement the caching of WSC probe requests -- when an Enrollee later associates to start registration we need to have its Probe Request on file. Also use this cache for PBC "Session Overlap" detection.

---
 src/ap.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 155 insertions(+)
src/ap.c +155 -0
index ce776553..df0a9942 100644
@@ -65,6 +65,7 @@ struct ap_state {
 	uint32_t mlme_watch;
 	uint8_t gtk[CRYPTO_MAX_GTK_LEN];
 	uint8_t gtk_index;
+	struct l_queue *wsc_pbc_probes;
 	struct l_timeout *wsc_pbc_timeout;
 	uint16_t wsc_dpid;
 	uint8_t wsc_uuid_r[16];
@@ -92,6 +93,12 @@ struct sta_state {
 	uint32_t gtk_query_cmd_id;
 };
 
+struct ap_wsc_pbc_probe_record {
+	uint8_t mac[6];
+	uint8_t uuid_e[16];
+	uint64_t timestamp;
+};
+
 static uint32_t netdev_watch;
 
 void ap_config_free(struct ap_config *config)
@@ -165,6 +172,8 @@ static void ap_reset(struct ap_state *ap)
 	ap_config_free(ap->config);
 	ap->config = NULL;
 
+	l_queue_destroy(ap->wsc_pbc_probes, l_free);
+
 	ap->started = false;
 }
 
@@ -643,6 +652,24 @@ error:
 	ap_del_station(sta, MMPDU_REASON_CODE_UNSPECIFIED, true);
 }
 
+struct ap_pbc_record_expiry_data {
+	uint64_t min_time;
+	const uint8_t *mac;
+};
+
+static bool ap_wsc_pbc_record_expire(void *data, void *user_data)
+{
+	struct ap_wsc_pbc_probe_record *record = data;
+	const struct ap_pbc_record_expiry_data *expiry_data = user_data;
+
+	if (record->timestamp > expiry_data->min_time &&
+			memcmp(record->mac, expiry_data->mac, 6))
+		return false;
+
+	l_free(record);
+	return true;
+}
+
 static struct l_genl_msg *ap_build_cmd_del_key(struct ap_state *ap)
 {
 	uint32_t ifindex = netdev_get_ifindex(ap->netdev);
@@ -1213,6 +1240,115 @@ bad_frame:
 #define AP_WSC_PBC_MONITOR_TIME	120
 #define AP_WSC_PBC_WALK_TIME	120
 
+static void ap_process_wsc_probe_req(struct ap_state *ap, const uint8_t *from,
+					const uint8_t *wsc_data,
+					size_t wsc_data_len)
+{
+	struct wsc_probe_request req;
+	struct ap_pbc_record_expiry_data expiry_data;
+	struct ap_wsc_pbc_probe_record *record;
+	uint64_t now;
+	bool empty;
+	uint8_t first_sta_addr[6] = {};
+	const struct l_queue_entry *entry;
+
+	if (wsc_parse_probe_request(wsc_data, wsc_data_len, &req) < 0)
+		return;
+
+	if (!(req.config_methods & WSC_CONFIGURATION_METHOD_PUSH_BUTTON))
+		return;
+
+	if (req.device_password_id != WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON)
+		return;
+
+	/* Save the address of the first enrollee record */
+	record = l_queue_peek_head(ap->wsc_pbc_probes);
+	if (record)
+		memcpy(first_sta_addr, record->mac, 6);
+
+	now = l_time_now();
+
+	/*
+	 * Expire entries older than PBC Monitor Time.  While there also drop
+	 * older entries from the same Enrollee that sent us this new Probe
+	 * Request.  It's unclear whether we should also match by the UUID-E.
+	 */
+	expiry_data.min_time = now - AP_WSC_PBC_MONITOR_TIME * 1000000;
+	expiry_data.mac = from;
+	l_queue_foreach_remove(ap->wsc_pbc_probes, ap_wsc_pbc_record_expire,
+				&expiry_data);
+
+	empty = l_queue_isempty(ap->wsc_pbc_probes);
+
+	if (!ap->wsc_pbc_probes)
+		ap->wsc_pbc_probes = l_queue_new();
+
+	/* Add new record */
+	record = l_new(struct ap_wsc_pbc_probe_record, 1);
+	memcpy(record->mac, from, 6);
+	memcpy(record->uuid_e, req.uuid_e, sizeof(record->uuid_e));
+	record->timestamp = now;
+	l_queue_push_tail(ap->wsc_pbc_probes, record);
+
+	/*
+	 * If queue was non-empty and we've added one more record then we
+	 * now have seen more than one PBC enrollee during the PBC Monitor
+	 * Time and must exit "active PBC mode" due to "session overlap".
+	 * WSC v2.0.5 Section 11.3:
+	 * "Within the PBC Monitor Time, if the Registrar receives PBC
+	 * probe requests from more than one Enrollee [...] then the
+	 * Registrar SHALL signal a "session overlap" error.  As a result,
+	 * the Registrar shall refuse to enter active PBC mode and shall
+	 * also refuse to perform a PBC-based Registration Protocol
+	 * exchange [...]"
+	 */
+	if (empty)
+		return;
+
+	if (ap->wsc_pbc_timeout) {
+		l_debug("Exiting PBC mode due to Session Overlap");
+		ap_wsc_exit_pbc(ap);
+	}
+
+	/*
+	 * "If the Registrar is engaged in PBC Registration Protocol
+	 * exchange with an Enrollee and receives a Probe Request or M1
+	 * Message from another Enrollee, then the Registrar should
+	 * signal a "session overlap" error".
+	 *
+	 * For simplicity just interrupt the handshake with that enrollee.
+	 */
+	for (entry = l_queue_get_entries(ap->sta_states); entry;
+			entry = entry->next) {
+		struct sta_state *sta = entry->data;
+
+		if (!sta->associated || sta->assoc_rsne)
+			continue;
+
+		/*
+		 * Check whether this enrollee is in PBC Registration
+		 * Protocol by comparing its mac with the first (and only)
+		 * record we had in ap->wsc_pbc_probes.  If we had more
+		 * than one record we wouldn't have been in
+		 * "active PBC mode".
+		 */
+		if (memcmp(sta->addr, first_sta_addr, 6) ||
+				!memcmp(sta->addr, from, 6))
+			continue;
+
+		l_debug("Interrupting handshake with %s due to Session Overlap",
+			util_address_to_string(sta->addr));
+
+		if (sta->hs) {
+			netdev_handshake_failed(sta->hs,
+					MMPDU_REASON_CODE_DISASSOC_AP_BUSY);
+			sta->sm = NULL;
+		}
+
+		ap_remove_sta(sta);
+	}
+}
+
 static void ap_probe_resp_cb(struct l_genl_msg *msg, void *user_data)
 {
 	if (l_genl_msg_get_error(msg) < 0)
@@ -1239,6 +1375,8 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
 	const uint8_t *bssid = netdev_get_address(ap->netdev);
 	bool match = false;
 	uint8_t resp[512];
+	uint8_t *wsc_data;
+	ssize_t wsc_data_len;
 
 	l_info("AP Probe Request from %s",
 		util_address_to_string(hdr->address_2));
@@ -1308,6 +1446,18 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
 	if (!match)
 		return;
 
+	/*
+	 * Process the WSC IE first as it may cause us to exit "active PBC
+	 * mode" and that can be immediately reflected in our Probe Response.
+	 */
+	wsc_data = ie_tlv_extract_wsc_payload(req->ies, body_len - sizeof(*req),
+						&wsc_data_len);
+	if (wsc_data) {
+		ap_process_wsc_probe_req(ap, hdr->address_2,
+						wsc_data, wsc_data_len);
+		l_free(wsc_data);
+	}
+
 	len = ap_build_beacon_pr_head(ap,
 					MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE,
 					hdr->address_2, resp, sizeof(resp));
@@ -1879,6 +2029,11 @@ bool ap_push_button(struct ap_state *ap)
 	if (!ap->started)
 		return false;
 
+	if (l_queue_length(ap->wsc_pbc_probes) > 1) {
+		l_debug("Can't start PBC mode due to Session Overlap");
+		return false;
+	}
+
 	/*
 	 * WSC v2.0.5 Section 11.3: "Multiple presses of the button are
 	 * permitted.  If a PBC button on an Enrollee or Registrar is
Signature · 1 line
2.25.1
#6 lore raw

PATCH 7/8 ap: Parse WSC PBC association request and build response

In reply to Andrew Zaborowski

Check the conditions for PBC enrollee registration when we receive the Association Request with WSC IE and indicate to the enrollee whether we accept the association using a WSC IE in the Association Response. After this, a NULL sta->assoc_rsne indicates that the station is not establishing the RSNA and is a WSC enrollee.

---
 src/ap.c | 186 ++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 151 insertions(+), 35 deletions(-)
src/ap.c +151 -35
index df0a9942..d7d0bbad 100644
@@ -91,6 +91,8 @@ struct sta_state {
 	struct eapol_sm *sm;
 	struct handshake_state *hs;
 	uint32_t gtk_query_cmd_id;
+	uint8_t wsc_uuid_e[16];
+	bool wsc_v2;
 };
 
 struct ap_wsc_pbc_probe_record {
@@ -906,7 +908,7 @@ static void ap_fail_assoc_resp_cb(struct l_genl_msg *msg, void *user_data)
 }
 
 static uint32_t ap_assoc_resp(struct ap_state *ap, struct sta_state *sta,
-				const uint8_t *dest, uint16_t aid,
+				const uint8_t *dest,
 				enum mmpdu_reason_code status_code,
 				bool reassoc, l_genl_msg_func_t callback)
 {
@@ -934,7 +936,7 @@ static uint32_t ap_assoc_resp(struct ap_state *ap, struct sta_state *sta,
 	resp = (void *) mmpdu_body(mpdu);
 	l_put_le16(capability, &resp->capability);
 	resp->status_code = L_CPU_TO_LE16(status_code);
-	resp->aid = L_CPU_TO_LE16(aid | 0xc000);
+	resp->aid = sta ? L_CPU_TO_LE16(sta->aid | 0xc000) : 0;
 
 	/* Supported Rates IE */
 	resp->ies[ies_len++] = IE_TYPE_SUPPORTED_RATES;
@@ -956,6 +958,38 @@ static uint32_t ap_assoc_resp(struct ap_state *ap, struct sta_state *sta,
 	resp->ies[ies_len++] = count;
 	ies_len += count;
 
+	if (sta && !sta->assoc_rsne) {
+		struct wsc_association_response wsc_resp = {};
+		uint8_t *wsc_data;
+		size_t wsc_data_len;
+		uint8_t *wsc_ie;
+		size_t wsc_ie_len;
+
+		wsc_resp.response_type = WSC_RESPONSE_TYPE_AP;
+		wsc_resp.version2 = sta->wsc_v2;
+
+		wsc_data = wsc_build_association_response(&wsc_resp,
+								&wsc_data_len);
+		if (!wsc_data) {
+			l_error("wsc_build_beacon error");
+			goto send_frame;
+		}
+
+		wsc_ie = ie_tlv_encapsulate_wsc_payload(wsc_data, wsc_data_len,
+							&wsc_ie_len);
+		l_free(wsc_data);
+
+		if (!wsc_ie) {
+			l_error("ie_tlv_encapsulate_wsc_payload error");
+			goto send_frame;
+		}
+
+		memcpy(resp->ies + ies_len, wsc_ie, wsc_ie_len);
+		ies_len += wsc_ie_len;
+		l_free(wsc_ie);
+	}
+
+send_frame:
 	return ap_send_mgmt_frame(ap, mpdu, resp->ies + ies_len - mpdu_buf,
 					true, callback, sta);
 }
@@ -1030,8 +1064,8 @@ static int ap_parse_supported_rates(struct ie_tlv_iter *iter,
  */
 static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 				const struct mmpdu_field_capability *capability,
-				uint16_t listen_interval,
-				struct ie_tlv_iter *ies)
+				uint16_t listen_interval, const uint8_t *ies,
+				size_t ies_len)
 {
 	struct ap_state *ap = sta->ap;
 	const char *ssid = NULL;
@@ -1040,6 +1074,9 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 	struct l_uintset *rates = NULL;
 	struct ie_rsn_info rsn_info;
 	int err;
+	struct ie_tlv_iter iter;
+	uint8_t *wsc_data = NULL;
+	ssize_t wsc_data_len;
 
 	if (sta->assoc_resp_cmd_id)
 		return;
@@ -1049,16 +1086,20 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 		goto unsupported;
 	}
 
-	while (ie_tlv_iter_next(ies))
-		switch (ie_tlv_iter_get_tag(ies)) {
+	wsc_data = ie_tlv_extract_wsc_payload(ies, ies_len, &wsc_data_len);
+
+	ie_tlv_iter_init(&iter, ies, ies_len);
+
+	while (ie_tlv_iter_next(&iter))
+		switch (ie_tlv_iter_get_tag(&iter)) {
 		case IE_TYPE_SSID:
-			ssid = (const char *) ie_tlv_iter_get_data(ies);
-			ssid_len = ie_tlv_iter_get_length(ies);
+			ssid = (const char *) ie_tlv_iter_get_data(&iter);
+			ssid_len = ie_tlv_iter_get_length(&iter);
 			break;
 
 		case IE_TYPE_SUPPORTED_RATES:
 		case IE_TYPE_EXTENDED_SUPPORTED_RATES:
-			if (ap_parse_supported_rates(ies, &rates) < 0) {
+			if (ap_parse_supported_rates(&iter, &rates) < 0) {
 				err = MMPDU_REASON_CODE_INVALID_IE;
 				goto bad_frame;
 			}
@@ -1066,16 +1107,26 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 			break;
 
 		case IE_TYPE_RSN:
-			if (ie_parse_rsne(ies, &rsn_info) < 0) {
+			/*
+			 * WSC v2.0.5 Section 8.2:
+			 * "Note that during the WSC association [...] the
+			 * RSN IE and the WPA IE are irrelevant and shall be
+			 * ignored by both the station and AP."
+			 */
+			if (wsc_data)
+				break;
+
+			if (ie_parse_rsne(&iter, &rsn_info) < 0) {
 				err = MMPDU_REASON_CODE_INVALID_IE;
 				goto bad_frame;
 			}
 
-			rsn = (const uint8_t *) ie_tlv_iter_get_data(ies) - 2;
+			rsn = (const uint8_t *) ie_tlv_iter_get_data(&iter) - 2;
 			break;
 		}
 
-	if (!rates || !ssid || !rsn || ssid_len != strlen(ap->config->ssid) ||
+	if (!rates || !ssid || (!wsc_data && !rsn) ||
+			ssid_len != strlen(ap->config->ssid) ||
 			memcmp(ssid, ap->config->ssid, ssid_len)) {
 		err = MMPDU_REASON_CODE_INVALID_IE;
 		goto bad_frame;
@@ -1086,19 +1137,82 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 		goto unsupported;
 	}
 
-	if (rsn_info.mfpr && rsn_info.spp_a_msdu_required) {
-		err = MMPDU_REASON_CODE_UNSPECIFIED;
-		goto unsupported;
-	}
+	/* Is the client requesting RSNA establishment or WSC registration */
+	if (!rsn) {
+		struct wsc_association_request wsc_req;
+		struct ap_event_registration_start_data event_data;
+		struct ap_wsc_pbc_probe_record *record;
 
-	if (!(rsn_info.pairwise_ciphers & ap->ciphers)) {
-		err = MMPDU_REASON_CODE_INVALID_PAIRWISE_CIPHER;
-		goto unsupported;
-	}
+		if (wsc_parse_association_request(wsc_data, wsc_data_len,
+							&wsc_req) < 0) {
+			err = MMPDU_REASON_CODE_INVALID_IE;
+			goto bad_frame;
+		}
 
-	if (rsn_info.akm_suites != IE_RSN_AKM_SUITE_PSK) {
-		err = MMPDU_REASON_CODE_INVALID_AKMP;
-		goto unsupported;
+		l_free(wsc_data);
+		wsc_data = NULL;
+
+		if (wsc_req.request_type !=
+				WSC_REQUEST_TYPE_ENROLLEE_OPEN_8021X) {
+			err = MMPDU_REASON_CODE_INVALID_IE;
+			goto bad_frame;
+		}
+
+		if (!ap->wsc_pbc_timeout) {
+			l_debug("WSC association from %s but we're not in "
+				"PBC mode", util_address_to_string(sta->addr));
+			err = MMPDU_REASON_CODE_UNSPECIFIED;
+			goto bad_frame;
+		}
+
+		if (l_queue_isempty(ap->wsc_pbc_probes)) {
+			l_debug("%s tried to register as enrollee but we "
+				"don't have their Probe Request record",
+				util_address_to_string(sta->addr));
+			err = MMPDU_REASON_CODE_UNSPECIFIED;
+			goto bad_frame;
+		}
+
+		/*
+		 * For PBC, the Enrollee must have sent the only PBC Probe
+		 * Request within the monitor time and walk time.
+		 */
+		record = l_queue_peek_head(ap->wsc_pbc_probes);
+		if (memcmp(sta->addr, record->mac, 6)) {
+			l_debug("Session overlap during %s's attempt to "
+				"register as WSC enrollee",
+				util_address_to_string(sta->addr));
+			err = MMPDU_REASON_CODE_UNSPECIFIED;
+			goto bad_frame;
+		}
+
+		memcpy(sta->wsc_uuid_e, record->uuid_e, 16);
+		sta->wsc_v2 = wsc_req.version2;
+
+		event_data.mac = sta->addr;
+		ap->event_func(AP_EVENT_REGISTRATION_START, &event_data,
+				ap->user_data);
+
+		/*
+		 * Since we're starting the PBC Registration Protocol
+		 * we can now exit the "active PBC mode".
+		 */
+		ap_wsc_exit_pbc(ap);
+	} else {
+		if (rsn_info.mfpr && rsn_info.spp_a_msdu_required) {
+			err = MMPDU_REASON_CODE_UNSPECIFIED;
+			goto unsupported;
+		}
+
+		if (!(rsn_info.pairwise_ciphers & ap->ciphers)) {
+			err = MMPDU_REASON_CODE_INVALID_PAIRWISE_CIPHER;
+			goto unsupported;
+		}
+
+		if (rsn_info.akm_suites != IE_RSN_AKM_SUITE_PSK) {
+			err = MMPDU_REASON_CODE_INVALID_AKMP;
+			goto unsupported;
+		}
 	}
 
 	/* 802.11-2016 11.3.5.3 j) */
@@ -1128,10 +1242,12 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
 	if (sta->assoc_rsne)
 		l_free(sta->assoc_rsne);
 
-	sta->assoc_rsne = l_memdup(rsn, rsn[1] + 2);
+	if (rsn)
+		sta->assoc_rsne = l_memdup(rsn, rsn[1] + 2);
+	else
+		sta->assoc_rsne = NULL;
 
-	sta->assoc_resp_cmd_id = ap_assoc_resp(ap, sta, sta->addr, sta->aid, 0,
-						reassoc,
+	sta->assoc_resp_cmd_id = ap_assoc_resp(ap, sta, sta->addr, 0, reassoc,
 						ap_success_assoc_resp_cb);
 	if (!sta->assoc_resp_cmd_id)
 		l_error("Sending success (Re)Association Response failed");
@@ -1160,7 +1276,9 @@ bad_frame:
 	if (rates)
 		l_uintset_free(rates);
 
-	if (!ap_assoc_resp(ap, NULL, sta->addr, 0, err, reassoc,
+	l_free(wsc_data);
+
+	if (!ap_assoc_resp(ap, sta, sta->addr, err, reassoc,
 				ap_fail_assoc_resp_cb))
 		l_error("Sending error (Re)Association Response failed");
 }
@@ -1174,7 +1292,6 @@ static void ap_assoc_req_cb(const struct mmpdu_header *hdr, const void *body,
 	const uint8_t *from = hdr->address_2;
 	const struct mmpdu_association_request *req = body;
 	const uint8_t *bssid = netdev_get_address(ap->netdev);
-	struct ie_tlv_iter iter;
 
 	l_info("AP Association Request from %s", util_address_to_string(from));
 
@@ -1184,7 +1301,7 @@ static void ap_assoc_req_cb(const struct mmpdu_header *hdr, const void *body,
 
 	sta = l_queue_find(ap->sta_states, ap_sta_match_addr, from);
 	if (!sta) {
-		if (!ap_assoc_resp(ap, NULL, from, 0,
+		if (!ap_assoc_resp(ap, NULL, from,
 				MMPDU_REASON_CODE_STA_REQ_ASSOC_WITHOUT_AUTH,
 				false, ap_fail_assoc_resp_cb))
 			l_error("Sending error Association Response failed");
@@ -1192,9 +1309,9 @@ static void ap_assoc_req_cb(const struct mmpdu_header *hdr, const void *body,
 		return;
 	}
 
-	ie_tlv_iter_init(&iter, req->ies, body_len - sizeof(*req));
 	ap_assoc_reassoc(sta, false, &req->capability,
-				L_LE16_TO_CPU(req->listen_interval), &iter);
+				L_LE16_TO_CPU(req->listen_interval),
+				req->ies, body_len - sizeof(*req));
 }
 
 /* 802.11-2016 9.3.3.8 */
@@ -1206,7 +1323,6 @@ static void ap_reassoc_req_cb(const struct mmpdu_header *hdr, const void *body,
 	const uint8_t *from = hdr->address_2;
 	const struct mmpdu_reassociation_request *req = body;
 	const uint8_t *bssid = netdev_get_address(ap->netdev);
-	struct ie_tlv_iter iter;
 	int err;
 
 	l_info("AP Reassociation Request from %s",
@@ -1227,13 +1343,13 @@ static void ap_reassoc_req_cb(const struct mmpdu_header *hdr, const void *body,
 		goto bad_frame;
 	}
 
-	ie_tlv_iter_init(&iter, req->ies, body_len - sizeof(*req));
 	ap_assoc_reassoc(sta, true, &req->capability,
-				L_LE16_TO_CPU(req->listen_interval), &iter);
+				L_LE16_TO_CPU(req->listen_interval),
+				req->ies, body_len - sizeof(*req));
 	return;
 
 bad_frame:
-	if (!ap_assoc_resp(ap, NULL, from, 0, err, true, ap_fail_assoc_resp_cb))
+	if (!ap_assoc_resp(ap, NULL, from, err, true, ap_fail_assoc_resp_cb))
 		l_error("Sending error Reassociation Response failed");
 }
 
Signature · 1 line
2.25.1
#7 lore raw

PATCH 8/8 ap: Start EAP-WSC authentication with WSC enrollees

In reply to Andrew Zaborowski

After association and sending the SET_STATION commands, set up the handshake_state and eapol_sm for EAP-WSC and start the handshake.

---
 src/ap.c | 227 +++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 186 insertions(+), 41 deletions(-)
src/ap.c +186 -41
index d7d0bbad..1cee185c 100644
@@ -46,6 +46,7 @@
 #include "src/nl80211util.h"
 #include "src/frame-xchg.h"
 #include "src/wscutil.h"
+#include "src/eap-wsc.h"
 #include "src/ap.h"
 
 struct ap_state {
@@ -91,6 +92,8 @@ struct sta_state {
 	struct eapol_sm *sm;
 	struct handshake_state *hs;
 	uint32_t gtk_query_cmd_id;
+	struct l_idle *stop_handshake_work;
+	struct l_settings *wsc_settings;
 	uint8_t wsc_uuid_e[16];
 	bool wsc_v2;
 };
@@ -131,6 +134,23 @@ static void ap_stop_handshake(struct sta_state *sta)
 		handshake_state_free(sta->hs);
 		sta->hs = NULL;
 	}
+
+	if (sta->wsc_settings) {
+		l_settings_free(sta->wsc_settings);
+		sta->wsc_settings = NULL;
+	}
+
+	if (sta->stop_handshake_work) {
+		l_idle_remove(sta->stop_handshake_work);
+		sta->stop_handshake_work = NULL;
+	}
+}
+
+static void ap_stop_handshake_work(struct l_idle *idle, void *user_data)
+{
+	struct sta_state *sta = user_data;
+
+	ap_stop_handshake(sta);
 }
 
 static void ap_sta_free(void *data)
@@ -560,6 +580,45 @@ static uint32_t ap_send_mgmt_frame(struct ap_state *ap,
 	return id;
 }
 
+static void ap_start_handshake(struct sta_state *sta, bool use_eapol_start)
+{
+	struct ap_state *ap = sta->ap;
+	const uint8_t *own_addr = netdev_get_address(ap->netdev);
+	struct ie_rsn_info rsn;
+	uint8_t bss_rsne[24];
+
+	handshake_state_set_ssid(sta->hs, (void *) ap->config->ssid,
+					strlen(ap->config->ssid));
+	handshake_state_set_authenticator_address(sta->hs, own_addr);
+	handshake_state_set_supplicant_address(sta->hs, sta->addr);
+
+	ap_set_rsn_info(ap, &rsn);
+	/*
+	 * Note: This assumes the length that ap_set_rsn_info() requires. If
+	 * ap_set_rsn_info() changes then this will need to be updated.
+	 */
+	ie_build_rsne(&rsn, bss_rsne);
+	handshake_state_set_authenticator_ie(sta->hs, bss_rsne);
+
+	sta->sm = eapol_sm_new(sta->hs);
+	if (!sta->sm) {
+		ap_stop_handshake(sta);
+		l_error("could not create sm object");
+		goto error;
+	}
+
+	eapol_sm_set_listen_interval(sta->sm, sta->listen_interval);
+	eapol_sm_set_use_eapol_start(sta->sm, use_eapol_start);
+
+	eapol_register(sta->sm);
+	eapol_start(sta->sm);
+
+	return;
+
+error:
+	ap_del_station(sta, MMPDU_REASON_CODE_UNSPECIFIED, true);
+}
+
 static void ap_handshake_event(struct handshake_state *hs,
 		enum handshake_event event, void *user_data, ...)
 {
@@ -587,53 +646,18 @@ static void ap_handshake_event(struct handshake_state *hs,
 
 static void ap_start_rsna(struct sta_state *sta, const uint8_t *gtk_rsc)
 {
-	struct ap_state *ap = sta->ap;
-	struct netdev *netdev = sta->ap->netdev;
-	const uint8_t *own_addr = netdev_get_address(netdev);
-	struct ie_rsn_info rsn;
-	uint8_t bss_rsne[24];
-
-	ap_set_rsn_info(ap, &rsn);
-	/*
-	 * TODO: This assumes the length that ap_set_rsn_info() requires. If
-	 * ap_set_rsn_info() changes then this will need to be updated.
-	 */
-	ie_build_rsne(&rsn, bss_rsne);
-
 	/* this handshake setup assumes PSK network */
-	sta->hs = netdev_handshake_state_new(netdev);
-
-	handshake_state_set_event_func(sta->hs, ap_handshake_event, sta);
-	handshake_state_set_ssid(sta->hs, (void *) ap->config->ssid,
-					strlen(ap->config->ssid));
+	sta->hs = netdev_handshake_state_new(sta->ap->netdev);
 	handshake_state_set_authenticator(sta->hs, true);
-	handshake_state_set_authenticator_ie(sta->hs, bss_rsne);
+	handshake_state_set_event_func(sta->hs, ap_handshake_event, sta);
 	handshake_state_set_supplicant_ie(sta->hs, sta->assoc_rsne);
-	handshake_state_set_pmk(sta->hs, ap->pmk, 32);
-	handshake_state_set_authenticator_address(sta->hs, own_addr);
-	handshake_state_set_supplicant_address(sta->hs, sta->addr);
+	handshake_state_set_pmk(sta->hs, sta->ap->pmk, 32);
 
 	if (gtk_rsc)
-		handshake_state_set_gtk(sta->hs, ap->gtk, ap->gtk_index,
-					gtk_rsc);
-
-	sta->sm = eapol_sm_new(sta->hs);
-	if (!sta->sm) {
-		handshake_state_free(sta->hs);
-		sta->hs = NULL;
-		l_error("could not create sm object");
-		goto error;
-	}
-
-	eapol_sm_set_listen_interval(sta->sm, sta->listen_interval);
-
-	eapol_register(sta->sm);
-	eapol_start(sta->sm);
+		handshake_state_set_gtk(sta->hs, sta->ap->gtk,
+					sta->ap->gtk_index, gtk_rsc);
 
-	return;
-
-error:
-	ap_del_station(sta, MMPDU_REASON_CODE_UNSPECIFIED, true);
+	ap_start_handshake(sta, false);
 }
 
 static void ap_gtk_query_cb(struct l_genl_msg *msg, void *user_data)
@@ -672,6 +696,114 @@ static bool ap_wsc_pbc_record_expire(void *data, void *user_data)
 	return true;
 }
 
+static void ap_stop_handshake_schedule(struct sta_state *sta)
+{
+	if (sta->stop_handshake_work)
+		return;
+
+	sta->stop_handshake_work = l_idle_create(ap_stop_handshake_work,
+							sta, NULL);
+}
+
+static void ap_wsc_handshake_event(struct handshake_state *hs,
+		enum handshake_event event, void *user_data, ...)
+{
+	struct sta_state *sta = user_data;
+	va_list args;
+	struct ap_event_registration_success_data event_data;
+	struct ap_pbc_record_expiry_data expiry_data;
+
+	va_start(args, user_data);
+
+	switch (event) {
+	case HANDSHAKE_EVENT_FAILED:
+		sta->sm = NULL;
+		ap_stop_handshake_schedule(sta);
+		/*
+		 * Some diagrams in WSC v2.0.5 indicate we should
+		 * automatically deauthenticate the Enrollee.  The text
+		 * generally indicates the Enrollee may disassociate
+		 * meaning that we should neither deauthenticate nor
+		 * disassociate it automatically.  Some places indicate
+		 * that the enrollee can send a new EAPoL-Start right away
+		 * on an unsuccessful registration, we don't implement
+		 * this for now.  STA remains associated but not authorized
+		 * and basically has no other option than to re-associate
+		 * or disassociate/deauthenticate.
+		 */
+		break;
+	case HANDSHAKE_EVENT_EAP_NOTIFY:
+		if (va_arg(args, unsigned int) != EAP_WSC_EVENT_CREDENTIAL_SENT)
+			break;
+
+		/*
+		 * WSC v2.0.5 Section 11.3:
+		 * "If the Registrar successfully runs the PBC method to
+		 * completion with an Enrollee, that Enrollee's probe requests
+		 * are removed from the Monitor Time check the next time the
+		 * Registrar's PBC button is pressed."
+		 */
+		expiry_data.min_time = 0;
+		expiry_data.mac = sta->addr;
+		l_queue_foreach_remove(sta->ap->wsc_pbc_probes,
+					ap_wsc_pbc_record_expire,
+					&expiry_data);
+
+		event_data.mac = sta->addr;
+		sta->ap->event_func(AP_EVENT_REGISTRATION_SUCCESS, &event_data,
+					sta->ap->user_data);
+		break;
+	default:
+		break;
+	}
+
+	va_end(args);
+}
+
+static void ap_start_eap_wsc(struct sta_state *sta)
+{
+	struct ap_state *ap = sta->ap;
+
+	/*
+	 * WSC v2.0.5 Section 8.2: "The AP is allowed to send
+	 * EAP-Request/Identity to the station before EAPOL-Start is received
+	 * if a WSC IE is included in the (re)association request and the
+	 * WSC IE is version 2.0 or higher.
+	 */
+	bool wait_for_eapol_start = !sta->wsc_v2;
+
+	L_AUTO_FREE_VAR(char *, uuid_r_str) = NULL;
+	L_AUTO_FREE_VAR(char *, uuid_e_str) = NULL;
+
+	uuid_r_str = l_util_hexstring(ap->wsc_uuid_r, 16);
+	uuid_e_str = l_util_hexstring(sta->wsc_uuid_e, 16);
+
+	sta->wsc_settings = l_settings_new();
+	l_settings_set_string(sta->wsc_settings, "Security", "EAP-Method",
+				"WSC-R");
+	l_settings_set_string(sta->wsc_settings, "WSC", "EnrolleeMAC",
+				util_address_to_string(sta->addr));
+	l_settings_set_string(sta->wsc_settings, "WSC", "UUID-R",
+				uuid_r_str);
+	l_settings_set_string(sta->wsc_settings, "WSC", "UUID-E",
+				uuid_e_str);
+	l_settings_set_uint(sta->wsc_settings, "WSC", "RFBand",
+				WSC_RF_BAND_2_4_GHZ);
+	l_settings_set_uint(sta->wsc_settings, "WSC", "ConfigurationMethods",
+				WSC_CONFIGURATION_METHOD_PUSH_BUTTON);
+	l_settings_set_string(sta->wsc_settings, "WSC", "WPA2-SSID",
+				ap->config->ssid);
+	l_settings_set_string(sta->wsc_settings, "WSC", "WPA2-Passphrase",
+				ap->config->psk);
+
+	sta->hs = netdev_handshake_state_new(ap->netdev);
+	handshake_state_set_authenticator(sta->hs, true);
+	handshake_state_set_event_func(sta->hs, ap_wsc_handshake_event, sta);
+	handshake_state_set_8021x_config(sta->hs, sta->wsc_settings);
+
+	ap_start_handshake(sta, wait_for_eapol_start);
+}
+
 static struct l_genl_msg *ap_build_cmd_del_key(struct ap_state *ap)
 {
 	uint32_t ifindex = netdev_get_ifindex(ap->netdev);
@@ -738,6 +870,19 @@ static void ap_associate_sta_cb(struct l_genl_msg *msg, void *user_data)
 		return;
 	}
 
+	/*
+	 * WSC v2.0.5 Section 8.2:
+	 * "Therefore if a WSC IE is present in the (re)association request,
+	 * the AP shall engage in EAP-WSC with the station and shall not
+	 * attempt any other security handshake."
+	 *
+	 * So no need for group traffic, skip the GTK setup below.
+	 */
+	if (!sta->assoc_rsne) {
+		ap_start_eap_wsc(sta);
+		return;
+	}
+
 	/*
 	 * Set up the group key.  If this is our first STA then we have
 	 * to add the new GTK to the kernel.  In theory we should be
Signature · 1 line
2.25.1
#8 lore raw
In reply to Andrew Zaborowski

Hi Andrew,

Quoted 8 lines · Andrew Zaborowski

Add the wfa_build_authorized_macs function (wfa_ prefix following the wfa_extract_ naming) and use it in wsc_build_probe_response. The logic is changed slightly to treat the first 6-zeros address in the array as the end of the array.

---
  src/wscutil.c | 32 +++++++++++++++++---------------
  1 file changed, 17 insertions(+), 15 deletions(-)

All applied, thanks.

Regards,
-Denis