diff mbox

[16/44] FT: generate PMK_R0 and PMK_R1 for FT-PSK locally

Message ID 1456314830-12935-17-git-send-email-michael-dev@fami-braun.de
State Changes Requested
Headers show

Commit Message

michael-dev Feb. 24, 2016, 11:53 a.m. UTC
From: Michael Braun <michael-dev@fami-braun.de>

IEEE 802.11-2012 mandates that a station should be able to connect
initially without ft_pmk_cache filled. So the target AP has the PSK
available and thus the same information as the origin AP.
Therefore neither caching nor communication between the APs with respect
to PMK_R0 or PMK_R1 or VLANs is required if the target AP derives the
required PMKs locally.

This patch introduces the generation of the required PMKs locally for
FT-PSK. Additionally, PMK-R0 is not stored (and thus pushed) for FT-PSK.

So for FT-PSK networks, no configuration of inter-AP communication is
needed anymore.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 hostapd/config_file.c   |   2 +
 hostapd/hostapd.conf    |   8 +++
 src/ap/ap_config.h      |   1 +
 src/ap/wpa_auth.h       |   1 +
 src/ap/wpa_auth_ft.c    | 128 ++++++++++++++++++++++++++++++++++++++++++++++--
 src/ap/wpa_auth_glue.c  |   1 +
 src/common/defs.h       |   5 ++
 src/common/wpa_common.c |   3 ++
 src/common/wpa_common.h |   3 ++
 9 files changed, 148 insertions(+), 4 deletions(-)

Comments

Jouni Malinen Feb. 28, 2016, 10:47 a.m. UTC | #1
On Wed, Feb 24, 2016 at 12:53:22PM +0100, michael-dev@fami-braun.de wrote:
> IEEE 802.11-2012 mandates that a station should be able to connect
> initially without ft_pmk_cache filled. So the target AP has the PSK
> available and thus the same information as the origin AP.
> Therefore neither caching nor communication between the APs with respect
> to PMK_R0 or PMK_R1 or VLANs is required if the target AP derives the
> required PMKs locally.
> 
> This patch introduces the generation of the required PMKs locally for
> FT-PSK. Additionally, PMK-R0 is not stored (and thus pushed) for FT-PSK.
> 
> So for FT-PSK networks, no configuration of inter-AP communication is
> needed anymore.

Even though this would sound like something that would not depend on the
patches I dropped, there is a dependency on wpa_ft_get_vlan() and a
simple rebasing was not straightforward enough for this, so I had to
drop this as well. I would probably be fine with this type of change
without that dependency as an independent patch or if easier, wait
until the new AP-to-AP message definition gets handled for the earlier
patches to be acceptable.
diff mbox

Patch

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 1531c59..6d0d4d2 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2559,6 +2559,8 @@  static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->pmk_r1_push = atoi(pos);
 	} else if (os_strcmp(buf, "ft_over_ds") == 0) {
 		bss->ft_over_ds = atoi(pos);
+	} else if (os_strcmp(buf, "ft_psk_generate_local") == 0) {
+		bss->ft_psk_generate_local = atoi(pos);
 #endif /* CONFIG_IEEE80211R */
 #ifndef CONFIG_NO_CTRL_IFACE
 	} else if (os_strcmp(buf, "ctrl_interface") == 0) {
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index f7a3fe8..7926da3 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1322,6 +1322,14 @@  own_ip_addr=127.0.0.1
 # 1 = FT-over-DS enabled (default)
 #ft_over_ds=1
 
+# Whether to generate FT response locally for PSK networks
+# This avoids to use PULL or PUSH RRB with FT-PSK networks as
+# the required information (psk and other session data)
+# is already locally available.
+# 0 = disabled (default)
+# 1 = enabled
+#ft_psk_generate_local=0
+
 ##### Neighbor table ##########################################################
 # Maximum number of entries kept in AP table (either for neigbor table or for
 # detecting Overlapping Legacy BSS Condition). The oldest entry will be
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 64143e5..92266c0 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -330,6 +330,7 @@  struct hostapd_bss_config {
 	struct ft_remote_r1kh *r1kh_list;
 	int pmk_r1_push;
 	int ft_over_ds;
+	int ft_psk_generate_local;
 #endif /* CONFIG_IEEE80211R */
 
 	char *ctrl_interface; /* directory for UNIX domain sockets */
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 1a2ff92..14c2681 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -184,6 +184,7 @@  struct wpa_auth_config {
 	struct ft_remote_r1kh *r1kh_list;
 	int pmk_r1_push;
 	int ft_over_ds;
+	int ft_psk_generate_local;
 #endif /* CONFIG_IEEE80211R */
 	int disable_gtk;
 	int ap_mlme;
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 9fef8f2..4319112 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -54,6 +54,17 @@  static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth,
 }
 
 
+static inline const u8 * wpa_ft_get_psk(struct wpa_authenticator *wpa_auth,
+					const u8 *addr, const u8 *p2p_dev_addr,
+					const u8 *prev_psk)
+{
+	if (wpa_auth->cb.get_psk == NULL)
+		return NULL;
+	return wpa_auth->cb.get_psk(wpa_auth->cb.ctx, addr, p2p_dev_addr,
+				    prev_psk);
+}
+
+
 static int
 wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
 	       struct wpa_state_machine **sm,
@@ -493,6 +504,7 @@  int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 	const u8 *r1kh = sm->wpa_auth->conf.r1_key_holder;
 	const u8 *ssid = sm->wpa_auth->conf.ssid;
 	size_t ssid_len = sm->wpa_auth->conf.ssid_len;
+	int psk_local = sm->wpa_auth->conf.ft_psk_generate_local;
 	int expiresIn = sm->wpa_auth->conf.r0_key_lifetime * 60;
 	struct ft_vlan vlan;
 
@@ -512,16 +524,20 @@  int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 			  r0kh, r0kh_len, sm->addr, pmk_r0, pmk_r0_name);
 	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R0", pmk_r0, PMK_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", pmk_r0_name, WPA_PMK_NAME_LEN);
-	wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_name,
-			    sm->pairwise, vlan, expiresIn);
+	if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
+		wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0,
+				    pmk_r0_name, sm->pairwise, vlan,
+				    expiresIn);
 
 	wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
 			  pmk_r1, sm->pmk_r1_name);
 	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", pmk_r1, PMK_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name,
 		    WPA_PMK_NAME_LEN);
-	wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, sm->pmk_r1_name,
-			    sm->pairwise, vlan, expiresIn);
+	if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
+		wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1,
+				    sm->pmk_r1_name, sm->pairwise, vlan,
+				    expiresIn);
 
 	return wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr,
 				 sm->wpa_auth->addr, sm->pmk_r1_name,
@@ -923,6 +939,101 @@  void wpa_ft_install_ptk(struct wpa_state_machine *sm)
 }
 
 
+/* Derive PMK-R1 from PSK, check all available PSK
+ */
+static int wpa_ft_psk_pmk_r1(struct wpa_state_machine *sm,
+			     const u8 *req_pmk_r1_name,
+			     u8 *out_pmk_r1, int *out_pairwise,
+			     struct ft_vlan *out_vlan)
+{
+	const u8 *pmk = NULL;
+	u8 pmk_r0[PMK_LEN], pmk_r0_name[WPA_PMK_NAME_LEN];
+	u8 pmk_r1[PMK_LEN], pmk_r1_name[WPA_PMK_NAME_LEN];
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	const u8 *mdid = wpa_auth->conf.mobility_domain;
+	const u8 *r0kh = sm->r0kh_id;
+	size_t r0kh_len = sm->r0kh_id_len;
+	const u8 *r1kh = wpa_auth->conf.r1_key_holder;
+	const u8 *ssid = wpa_auth->conf.ssid;
+	size_t ssid_len = wpa_auth->conf.ssid_len;
+	int pairwise;
+
+	pairwise = sm->pairwise;
+
+	for (;;) {
+		pmk = wpa_ft_get_psk(wpa_auth, sm->addr, sm->p2p_dev_addr,
+				     pmk);
+		if (pmk == NULL)
+			break;
+
+		wpa_derive_pmk_r0(pmk, PMK_LEN, ssid, ssid_len, mdid, r0kh,
+				  r0kh_len, sm->addr, pmk_r0, pmk_r0_name);
+		wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
+				  pmk_r1, pmk_r1_name);
+
+		if (os_memcmp_const(pmk_r1_name, req_pmk_r1_name,
+				    WPA_PMK_NAME_LEN) != 0)
+			continue;
+
+		/* we found a PSK that matches the request pmk_r1_name */
+		wpa_printf(MSG_DEBUG, "FT: found PSK to generate PMK_R1 "
+				      "locally");
+		os_memcpy(out_pmk_r1, pmk_r1, PMK_LEN);
+		if (out_pairwise)
+			*out_pairwise = pairwise;
+		if (out_vlan &&
+		    wpa_ft_get_vlan(sm->wpa_auth, sm->addr, out_vlan) < 0) {
+			wpa_printf(MSG_DEBUG, "FT: vlan not available for STA "
+					      MACSTR, MAC2STR(sm->addr));
+			return -1;
+		}
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG, "FT: did not find PSK to generate PMK_R1 "
+			      "locally");
+	return -1;
+}
+
+/* Detect the configuration the station asked for.
+ * Required to detect FT-PSK and pairwise cipher.
+ */
+static int wpa_ft_set_keymgmt(struct wpa_state_machine *sm,
+			      struct wpa_ft_ies *parse)
+{
+	int key_mgmt, ciphers;
+
+	if (sm->wpa_key_mgmt)
+		return 0;
+
+	key_mgmt = parse->key_mgmt & sm->wpa_auth->conf.wpa_key_mgmt;
+	if (!key_mgmt) {
+		wpa_printf(MSG_DEBUG, "FT: Invalid WPA key mgmt (0x%x) from "
+			   MACSTR, parse->key_mgmt, MAC2STR(sm->addr));
+		return -1;
+	}
+	if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X;
+	else if (key_mgmt & WPA_KEY_MGMT_FT_PSK)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK;
+	ciphers = parse->pairwise_cipher;
+	if (parse->wpa_proto == WPA_PROTO_RSN)
+		ciphers = ciphers & sm->wpa_auth->conf.rsn_pairwise;
+	else
+		ciphers = ciphers & sm->wpa_auth->conf.wpa_pairwise;
+	if (!ciphers) {
+		wpa_printf(MSG_DEBUG, "Invalid %s pairwise cipher (0x%x) "
+			   "from " MACSTR,
+			   parse->wpa_proto == WPA_PROTO_RSN ? "RSN" : "WPA",
+			   parse->pairwise_cipher, MAC2STR(sm->addr));
+		return -1;
+	}
+	sm->pairwise = wpa_pick_pairwise_cipher(ciphers, 0);
+
+	return 0;
+}
+
+
 static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 				   const u8 *ies, size_t ies_len,
 				   u8 **resp_ies, size_t *resp_ies_len)
@@ -985,6 +1096,9 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 		return WLAN_STATUS_INVALID_PMKID;
 	}
 
+	if (wpa_ft_set_keymgmt(sm, &parse) < 0)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
 	wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name",
 		    parse.rsn_pmkid, WPA_PMK_NAME_LEN);
 	wpa_derive_pmk_r1_name(parse.rsn_pmkid,
@@ -993,6 +1107,12 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 	wpa_hexdump(MSG_DEBUG, "FT: Derived requested PMKR1Name",
 		    pmk_r1_name, WPA_PMK_NAME_LEN);
 
+	if (conf->ft_psk_generate_local &&
+	    wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) {
+		if (wpa_ft_psk_pmk_r1(sm, pmk_r1_name, pmk_r1, &pairwise,
+				       &vlan) < 0)
+			return WLAN_STATUS_INVALID_PMKID;
+	} else
 	if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name, pmk_r1,
 		    &pairwise, &vlan) < 0) {
 		if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index f72ac7f..a85559a 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -81,6 +81,7 @@  static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 	wconf->r1kh_list = conf->r1kh_list;
 	wconf->pmk_r1_push = conf->pmk_r1_push;
 	wconf->ft_over_ds = conf->ft_over_ds;
+	wconf->ft_psk_generate_local = conf->ft_psk_generate_local;
 #endif /* CONFIG_IEEE80211R */
 #ifdef CONFIG_HS20
 	wconf->disable_gtk = conf->disable_dgaf;
diff --git a/src/common/defs.h b/src/common/defs.h
index b3ac4e8..24ab949 100644
--- a/src/common/defs.h
+++ b/src/common/defs.h
@@ -79,6 +79,11 @@  static inline int wpa_key_mgmt_ft(int akm)
 			 WPA_KEY_MGMT_FT_SAE));
 }
 
+static inline int wpa_key_mgmt_ft_psk(int akm)
+{
+	return !!(akm & WPA_KEY_MGMT_FT_PSK);
+}
+
 static inline int wpa_key_mgmt_sae(int akm)
 {
 	return !!(akm & (WPA_KEY_MGMT_SAE |
diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c
index d6295b2..a363206 100644
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -376,6 +376,9 @@  int wpa_ft_parse_ies(const u8 *ies, size_t ies_len,
 			}
 			if (data.num_pmkid == 1 && data.pmkid)
 				parse->rsn_pmkid = data.pmkid;
+			parse->key_mgmt = data.key_mgmt;
+			parse->pairwise_cipher = data.pairwise_cipher;
+			parse->wpa_proto = data.proto;
 			break;
 		case WLAN_EID_MOBILITY_DOMAIN:
 			if (len < sizeof(struct rsn_mdie))
diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index af1d0f0..f0aa1d6 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -430,6 +430,9 @@  struct wpa_ft_ies {
 	size_t igtk_len;
 	const u8 *ric;
 	size_t ric_len;
+	int key_mgmt;
+	int pairwise_cipher;
+	int wpa_proto;
 };
 
 int wpa_ft_parse_ies(const u8 *ies, size_t ies_len, struct wpa_ft_ies *parse);