[PATCHv6,4/5] FT: add support for wildcard R0KH / R1KH

Submitted by michael-dev@fami-braun.de on April 2, 2017, 12:52 p.m.

Details

Message ID 1491137573-643-5-git-send-email-michael-dev@fami-braun.de
State New
Headers show

Commit Message

michael-dev@fami-braun.de April 2, 2017, 12:52 p.m.
Enable using FT RRB without configuring each other AP locally. Instead,
broadcast messages are exchanged.

When an R0KH or R1KH is discovered, it is cached for 1d.

When a station uses an invalid or offline r0kh_id, requests are always
broadcasted. In order to avoid this, if r0kh does not reply, a temporary
blacklist entry is added to r0kh_list.

In order to avoid blocking a valid r0kh when a non-existing pmk_r0_name is
requested, r0kh is required to always reply using a NAK. Resend requests a
few times to ensure blacklisting does not happen due to small packet loss.

In order to free newly created stations later, the r*kh_list start pointer
in conf needs to be updateable from wpa_auto_ft.c, where only wconf is
accessed.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>

--
2017-04-02:
 - add_r1kh in rx_seq only after decrypt passes
v5: squash wildcard test patch changes to wpa_auth_ft.c here
    add debug output for pull req timeout
v4: squash wildcard, positive and negative caching into one
    patch for proper sequence number implementation
v3: merge retransmission into this patch and update comment
---
 hostapd/config_file.c  |   8 +
 hostapd/hostapd.conf   |  23 +++
 src/ap/ap_config.c     |   4 +
 src/ap/ap_config.h     |   4 +
 src/ap/wpa_auth.c      |   3 +
 src/ap/wpa_auth.h      |  12 +-
 src/ap/wpa_auth_ft.c   | 515 ++++++++++++++++++++++++++++++++++++++++++-------
 src/ap/wpa_auth_glue.c |   8 +-
 src/ap/wpa_auth_i.h    |   1 +
 9 files changed, 508 insertions(+), 70 deletions(-)

Patch hide | download patch | download mbox

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 2070a74..8435a36 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2636,6 +2636,14 @@  static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->r0_key_lifetime = atoi(pos);
 	} else if (os_strcmp(buf, "reassociation_deadline") == 0) {
 		bss->reassociation_deadline = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pos_timeout") == 0) {
+		bss->rkh_pos_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_neg_timeout") == 0) {
+		bss->rkh_neg_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pull_timeout") == 0) {
+		bss->rkh_pull_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pull_retries") == 0) {
+		bss->rkh_pull_retries = atoi(pos);
 	} else if (os_strcmp(buf, "r0kh") == 0) {
 		if (add_r0kh(bss, pos) < 0) {
 			wpa_printf(MSG_DEBUG, "Line %d: Invalid r0kh '%s'",
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 7e61d1c..6f8432a 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1459,6 +1459,10 @@  own_ip_addr=127.0.0.1
 #r0kh=02:01:02:03:04:05 r0kh-1.example.com 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
 #r0kh=02:01:02:03:04:06 r0kh-2.example.com 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
 # And so on.. One line per R0KH.
+# Wildcard entry: Upon receiving a response from R0KH, it will be added to this
+#                 list, so subsequent requests won't be broadcasted.
+#                 If R0KH does not reply, it will be blacklisted.
+#r0kh=ff:ff:ff:ff:ff:ff * 00112233445566778899aabbccddeeff
 
 # List of R1KHs in the same Mobility Domain
 # format: <MAC address> <R1KH-ID> <256-bit key as hex string>
@@ -1468,6 +1472,25 @@  own_ip_addr=127.0.0.1
 #r1kh=02:01:02:03:04:05 02:11:22:33:44:55 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
 #r1kh=02:01:02:03:04:06 02:11:22:33:44:66 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
 # And so on.. One line per R1KH.
+# Wildcard entry: Upon receiving a request from an R1KH not yet known,
+#                 it will be added to this list and thus receive push
+#                 notifications.
+#r1kh=00:00:00:00:00:00 00:00:00:00:00:00 00112233445566778899aabbccddeeff
+
+# Timeout (seconds) for newly discovered R0KH/R1KH (see wildcard entries above)
+# Special values: 0 -> do not expire
+# Warning: do not cache implies no sequence number validation with wildcards
+#rkh_pos_timeout = 86400 (default = 1d)
+
+# Timeout (milli seconds) for requesting PMK-R1 from R0KH using PULL request
+# and number of retries.
+#rkh_pull_timeout = 1000 (default = 1s)
+#rkh_pull_retries = 4 (default)
+
+# Timeout (seconds) for non replying R0KH (see wildcard entries above)
+# Special values: 0 -> do not cache
+# default: 60 seconds
+#rkh_neg_timeout = 86400
 
 # Whether PMK-R1 push is enabled at R0KH
 # 0 = do not push PMK-R1 to all configured R1KHs (default)
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 10e1c4b..b889a3c 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -93,6 +93,10 @@  void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 
 #ifdef CONFIG_IEEE80211R_AP
 	bss->ft_over_ds = 1;
+	bss->rkh_pos_timeout = 86400;
+	bss->rkh_neg_timeout = 60;
+	bss->rkh_pull_timeout = 1000;
+	bss->rkh_pull_retries = 4;
 #endif /* CONFIG_IEEE80211R_AP */
 
 	bss->radius_das_time_window = 300;
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 989b071..0d5274a 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -342,6 +342,10 @@  struct hostapd_bss_config {
 	u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN];
 	u8 r1_key_holder[FT_R1KH_ID_LEN];
 	u32 r0_key_lifetime;
+	int rkh_pos_timeout;
+	int rkh_neg_timeout;
+	int rkh_pull_timeout; /* ms */
+	int rkh_pull_retries;
 	u32 reassociation_deadline;
 	struct ft_remote_r0kh *r0kh_list;
 	struct ft_remote_r1kh *r1kh_list;
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 338c6c7..b68b94e 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -704,6 +704,9 @@  void wpa_auth_sta_deinit(struct wpa_state_machine *sm)
 	sm->pending_1_of_4_timeout = 0;
 	eloop_cancel_timeout(wpa_sm_call_step, sm, NULL);
 	eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm);
+#ifdef CONFIG_IEEE80211R_AP
+	wpa_ft_sta_deinit(sm);
+#endif /* CONFIG_IEEE80211R_AP */
 	if (sm->in_step_loop) {
 		/* Must not free state machine while wpa_sm_step() is running.
 		 * Freeing will be completed in the end of wpa_sm_step(). */
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 49c0480..3e08d81 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -100,7 +100,8 @@  struct ft_rrb_seq {
  *   auth:
  *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
  *   encrypted:
- *     required: S1KH_ID, session TLVs
+ *     required: S1KH_ID
+ *     optional: session TLVs
  *
  * push frame TLVs:
  *   auth:
@@ -182,9 +183,13 @@  struct wpa_auth_config {
 	size_t r0_key_holder_len;
 	u8 r1_key_holder[FT_R1KH_ID_LEN];
 	u32 r0_key_lifetime;
+	int rkh_pos_timeout;
+	int rkh_neg_timeout;
+	int rkh_pull_timeout; /* ms */
+	int rkh_pull_retries;
 	u32 reassociation_deadline;
-	struct ft_remote_r0kh *r0kh_list;
-	struct ft_remote_r1kh *r1kh_list;
+	struct ft_remote_r0kh **r0kh_list;
+	struct ft_remote_r1kh **r1kh_list;
 	int pmk_r1_push;
 	int ft_over_ds;
 	int ft_psk_generate_local;
@@ -367,6 +372,7 @@  void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 		       size_t data_len);
 void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr);
 void wpa_ft_deinit(struct wpa_authenticator *wpa_auth);
+void wpa_ft_sta_deinit(struct wpa_state_machine *sm);
 #endif /* CONFIG_IEEE80211R_AP */
 
 void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm);
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index ba380ab..d3b02cd 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -34,6 +34,8 @@  static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
 				     const u8 *current_ap, const u8 *sta_addr,
 				     u16 status, const u8 *resp_ies,
 				     size_t resp_ies_len);
+static void ft_finish_pull(struct wpa_state_machine *sm);
+static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx);
 static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx);
 
 struct tlv_list {
@@ -1004,22 +1006,26 @@  static int wpa_ft_rrb_init_r0kh_seq(struct ft_remote_r0kh *r0kh)
 
 static void wpa_ft_rrb_lookup_r0kh(struct wpa_authenticator *wpa_auth,
 				   const u8 *f_r0kh_id, size_t f_r0kh_id_len,
-				   struct ft_remote_r0kh **r0kh_out)
+				   struct ft_remote_r0kh **r0kh_out,
+				   struct ft_remote_r0kh **r0kh_wildcard)
 {
 	struct ft_remote_r0kh *r0kh = NULL;
 
+	*r0kh_wildcard = NULL;
 	*r0kh_out = NULL;
 
-	r0kh = wpa_auth->conf.r0kh_list;
+	if (wpa_auth->conf.r0kh_list)
+		r0kh = *wpa_auth->conf.r0kh_list;
+
 	for (; r0kh; r0kh = r0kh->next) {
+		if (r0kh->id_len == 1 && r0kh->id[0] == '*')
+			*r0kh_wildcard = r0kh;
 		if (f_r0kh_id && r0kh->id_len == f_r0kh_id_len &&
-		    os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len) == 0) {
+		    os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len) == 0)
 			*r0kh_out = r0kh;
-			break;
-		}
 	}
 
-	if (!*r0kh_out)
+	if (!*r0kh_out && !*r0kh_wildcard)
 		wpa_printf(MSG_DEBUG, "FT: No matching R0KH found");
 
 	if (*r0kh_out && wpa_ft_rrb_init_r0kh_seq(*r0kh_out) < 0)
@@ -1047,23 +1053,26 @@  static int wpa_ft_rrb_init_r1kh_seq(struct ft_remote_r1kh *r1kh)
 
 static void wpa_ft_rrb_lookup_r1kh(struct wpa_authenticator *wpa_auth,
 				   const u8 *f_r1kh_id,
-				   struct ft_remote_r1kh **r1kh_out)
-
+				   struct ft_remote_r1kh **r1kh_out,
+				   struct ft_remote_r1kh **r1kh_wildcard)
 {
 	struct ft_remote_r1kh *r1kh = NULL;
 
+	*r1kh_wildcard = NULL;
 	*r1kh_out = NULL;
 
-	r1kh = wpa_auth->conf.r1kh_list;
+	if (wpa_auth->conf.r1kh_list)
+		r1kh = *wpa_auth->conf.r1kh_list;
 	for (; r1kh; r1kh = r1kh->next) {
+		if (is_zero_ether_addr(r1kh->addr) &&
+		    is_zero_ether_addr(r1kh->id))
+			*r1kh_wildcard = r1kh;
 		if (f_r1kh_id &&
-		    os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN) == 0) {
+		    os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN) == 0)
 			*r1kh_out = r1kh;
-			break;
-		}
 	}
 
-	if (!*r1kh_out)
+	if (!*r1kh_out && !*r1kh_wildcard)
 		wpa_printf(MSG_DEBUG, "FT: No matching R1KH found");
 
 	if (*r1kh_out && wpa_ft_rrb_init_r1kh_seq(*r1kh_out) < 0)
@@ -1094,6 +1103,164 @@  static int wpa_ft_rrb_check_r1kh(struct wpa_authenticator *wpa_auth,
 }
 
 
+static void wpa_ft_rrb_del_r0kh(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_authenticator *wpa_auth = eloop_ctx;
+	struct ft_remote_r0kh *r0kh, *prev = NULL;
+
+	if (!wpa_auth->conf.r0kh_list)
+		return;
+
+	r0kh = *wpa_auth->conf.r0kh_list;
+	while (r0kh) {
+		if (r0kh != timeout_ctx) {
+			r0kh = r0kh->next;
+			continue;
+		}
+		if (prev)
+			prev->next = r0kh->next;
+		else
+			*wpa_auth->conf.r0kh_list = r0kh->next;
+		if (r0kh->seq)
+			wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0);
+		os_free(r0kh->seq);
+		os_free(r0kh);
+		break;
+	}
+}
+
+
+static void wpa_ft_rrb_r0kh_replenish(struct wpa_authenticator *wpa_auth,
+				      struct ft_remote_r0kh *r0kh, int timeout)
+{
+	if (timeout > 0)
+		eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
+					wpa_auth, r0kh);
+}
+
+
+static void wpa_ft_rrb_r0kh_timeout(struct wpa_authenticator *wpa_auth,
+				    struct ft_remote_r0kh *r0kh, int timeout)
+{
+	eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth, r0kh);
+
+	if (timeout > 0)
+		eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
+				       wpa_auth, r0kh);
+}
+
+
+static struct ft_remote_r0kh *
+wpa_ft_rrb_add_r0kh(struct wpa_authenticator *wpa_auth,
+		    struct ft_remote_r0kh *r0kh_wildcard,
+		    const u8 *src_addr, const u8 *r0kh_id, size_t id_len,
+		    int timeout)
+{
+	struct ft_remote_r0kh *r0kh;
+
+	if (!wpa_auth->conf.r0kh_list)
+		return NULL;
+
+	r0kh = os_zalloc(sizeof(*r0kh));
+	if (r0kh == NULL)
+		return NULL;
+
+	os_memcpy(r0kh->addr, src_addr, sizeof(r0kh->addr));
+
+	if (id_len > FT_R0KH_ID_MAX_LEN)
+		id_len = FT_R0KH_ID_MAX_LEN;
+	os_memcpy(r0kh->id, r0kh_id, id_len);
+	r0kh->id_len = id_len;
+
+	os_memcpy(r0kh->key, r0kh_wildcard->key, sizeof(r0kh->key));
+
+	r0kh->next = *wpa_auth->conf.r0kh_list;
+	*wpa_auth->conf.r0kh_list = r0kh;
+
+	if (timeout > 0)
+		eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh,
+				       wpa_auth, r0kh);
+
+	if (wpa_ft_rrb_init_r0kh_seq(r0kh) < 0)
+		return NULL;
+
+	return r0kh;
+}
+
+
+static void wpa_ft_rrb_del_r1kh(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_authenticator *wpa_auth = eloop_ctx;
+	struct ft_remote_r1kh *r1kh, *prev = NULL;
+
+	if (!wpa_auth->conf.r1kh_list)
+		return;
+
+	r1kh = *wpa_auth->conf.r1kh_list;
+	while (r1kh) {
+		if (r1kh != timeout_ctx) {
+			r1kh = r1kh->next;
+			continue;
+		}
+		if (prev)
+			prev->next = r1kh->next;
+		else
+			*wpa_auth->conf.r1kh_list = r1kh->next;
+		if (r1kh->seq)
+			wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0);
+		os_free(r1kh->seq);
+		os_free(r1kh);
+		break;
+	}
+}
+
+
+static void wpa_ft_rrb_r1kh_replenish(struct wpa_authenticator *wpa_auth,
+				      struct ft_remote_r1kh *r1kh, int timeout)
+{
+	if (timeout > 0)
+		eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r1kh,
+					wpa_auth, r1kh);
+}
+
+
+static struct ft_remote_r1kh *
+wpa_ft_rrb_add_r1kh(struct wpa_authenticator *wpa_auth,
+		    struct ft_remote_r1kh *r1kh_wildcard,
+		    const u8 *src_addr, const u8 *r1kh_id, int timeout)
+{
+	struct ft_remote_r1kh *r1kh;
+
+	if (!wpa_auth->conf.r1kh_list)
+		return NULL;
+
+	r1kh = os_zalloc(sizeof(*r1kh));
+	if (r1kh == NULL)
+		return NULL;
+
+	os_memcpy(r1kh->addr, src_addr, sizeof(r1kh->addr));
+	os_memcpy(r1kh->id, r1kh_id, sizeof(r1kh->id));
+	os_memcpy(r1kh->key, r1kh_wildcard->key, sizeof(r1kh->key));
+	r1kh->next = *wpa_auth->conf.r1kh_list;
+	*wpa_auth->conf.r1kh_list = r1kh;
+
+	if (timeout > 0)
+		eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r1kh,
+				       wpa_auth, r1kh);
+
+	if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0)
+		return NULL;
+
+	return r1kh;
+}
+
+
+void wpa_ft_sta_deinit(struct wpa_state_machine *sm)
+{
+	eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL);
+}
+
+
 void wpa_ft_deinit_seq_timeout(struct wpa_authenticator *wpa_auth)
 {
 	struct ft_remote_r0kh *r0kh;
@@ -1101,14 +1268,18 @@  void wpa_ft_deinit_seq_timeout(struct wpa_authenticator *wpa_auth)
 
 	eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, wpa_auth, ELOOP_ALL_CTX);
 
-	r0kh = wpa_auth->conf.r0kh_list;
+	if (!wpa_auth->conf.r0kh_list)
+		return;
+	r0kh = *wpa_auth->conf.r0kh_list;
 	while (r0kh) {
 		if (r0kh->seq)
 			wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0);
 		r0kh = r0kh->next;
 	}
 
-	r1kh = wpa_auth->conf.r1kh_list;
+	if (!wpa_auth->conf.r1kh_list)
+		return;
+	r1kh = *wpa_auth->conf.r1kh_list;
 	while (r1kh) {
 		if (r1kh->seq)
 			wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0);
@@ -1120,6 +1291,52 @@  void wpa_ft_deinit_seq_timeout(struct wpa_authenticator *wpa_auth)
 void wpa_ft_deinit(struct wpa_authenticator *wpa_auth)
 {
 	wpa_ft_deinit_seq_timeout(wpa_auth);
+	eloop_cancel_timeout(wpa_ft_rrb_del_r1kh, wpa_auth, ELOOP_ALL_CTX);
+	eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth, ELOOP_ALL_CTX);
+}
+
+
+static void wpa_ft_block_r0kh(struct wpa_authenticator *wpa_auth,
+			      const u8 *f_r0kh_id, size_t f_r0kh_id_len)
+{
+	struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
+	const u8 *zaddr = (u8 *) "\x00\x00\x00\x00\x00\x00";
+
+	if (!wpa_auth->conf.rkh_neg_timeout)
+		return;
+
+	wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
+			       &r0kh, &r0kh_wildcard);
+
+	if (!r0kh_wildcard)
+		/* r0kh removed after neg_timeout and might need re-adding */
+		return;
+
+	wpa_hexdump(MSG_DEBUG, "FT: Blacklist R0KH-ID",
+		    f_r0kh_id, f_r0kh_id_len);
+
+	if (r0kh) {
+		wpa_ft_rrb_r0kh_timeout(wpa_auth, r0kh,
+					wpa_auth->conf.rkh_neg_timeout);
+		os_memcpy(r0kh->addr, zaddr, ETH_ALEN);
+	} else
+		wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, zaddr, f_r0kh_id,
+				    f_r0kh_id_len,
+				    wpa_auth->conf.rkh_neg_timeout);
+}
+
+static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_state_machine *sm = eloop_ctx;
+
+	wpa_printf(MSG_DEBUG, "FT: Timeout pending pull request for " MACSTR,
+		   MAC2STR(sm->addr));
+	if (sm->ft_pending_pull_left_retries <= 0)
+		wpa_ft_block_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len);
+
+	/* cancel multiple timeouts */
+	eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL);
+	ft_finish_pull(sm);
 }
 
 
@@ -1127,19 +1344,46 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 			      const u8 *ies, size_t ies_len,
 			      const u8 *pmk_r0_name)
 {
-	struct ft_remote_r0kh *r0kh;
+	struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
 	u8 *packet = NULL;
 	const u8 *key, *f_r1kh_id = sm->wpa_auth->conf.r1_key_holder;
 	size_t packet_len, key_len;
 	struct ft_rrb_seq f_seq;
+	int tsecs, tusecs, first;
+	struct wpabuf *ft_pending_req_ies;
+
+	if (sm->ft_pending_pull_left_retries <= 0)
+		return -1;
+	first = (sm->ft_pending_pull_left_retries ==
+		 sm->wpa_auth->conf.rkh_pull_retries);
+	sm->ft_pending_pull_left_retries--;
 
 	wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len,
-			       &r0kh);
+			       &r0kh, &r0kh_wildcard);
+
+	/* keep r0kh sufficiently long in list for seq num check */
+	const int r0kh_timeout = sm->wpa_auth->conf.rkh_pull_timeout / 1000
+				 + 1 + ftRRBseqTimeout;
+	if (r0kh)
+		wpa_ft_rrb_r0kh_replenish(sm->wpa_auth, r0kh, r0kh_timeout);
+	else if (r0kh_wildcard) {
+		wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID");
+		/* r0kh->addr: updated by SEQ_RESP and wpa_ft_expire_pull */
+		r0kh = wpa_ft_rrb_add_r0kh(sm->wpa_auth, r0kh_wildcard,
+					   r0kh_wildcard->addr,
+					   sm->r0kh_id, sm->r0kh_id_len,
+					   r0kh_timeout);
+	}
 	if (r0kh == NULL) {
 		wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
 		return -1;
 	}
+	if (is_zero_ether_addr(r0kh->addr)) {
+		wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID is blacklisted",
+			    sm->r0kh_id, sm->r0kh_id_len);
+		return -1;
+	}
 
 	key = r0kh->key;
 	key_len = sizeof(r0kh->key);
@@ -1154,7 +1398,8 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 				   r0kh->id, r0kh->id_len, f_r1kh_id, key,
 				   key_len, NULL, 0, NULL, 0, NULL);
 
-	if (random_get_bytes(sm->ft_pending_pull_nonce, FT_RRB_NONCE_LEN) < 0) {
+	if (first &&
+	    random_get_bytes(sm->ft_pending_pull_nonce, FT_RRB_NONCE_LEN) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
 			   "nonce");
 		return -1;
@@ -1190,11 +1435,16 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 			     &packet, &packet_len) < 0)
 		return -1;
 
+	ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len);
 	wpabuf_free(sm->ft_pending_req_ies);
-	sm->ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len);
+	sm->ft_pending_req_ies = ft_pending_req_ies;
 	if (sm->ft_pending_req_ies == NULL)
 		return -1;
 
+	tsecs = sm->wpa_auth->conf.rkh_pull_timeout / 1000;
+	tusecs = (sm->wpa_auth->conf.rkh_pull_timeout % 1000) * 1000;
+	eloop_register_timeout(tsecs, tusecs, wpa_ft_expire_pull, sm, NULL);
+
 	wpa_ft_rrb_oui_send(sm->wpa_auth, r0kh->addr, FT_PACKET_R0KH_R1KH_PULL,
 			    packet, packet_len);
 
@@ -1809,7 +2059,8 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 				       pmk_r1, &pairwise) < 0) {
 		if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
 			wpa_printf(MSG_DEBUG, "FT: Did not have matching "
-				   "PMK-R1 and unknown R0KH-ID");
+				   "PMK-R1 and either unknown or blocked "
+				   "R0KH-ID or NAK from R0KH");
 			return WLAN_STATUS_INVALID_PMKID;
 		}
 
@@ -1900,6 +2151,7 @@  void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid,
 	sm->ft_pending_cb = cb;
 	sm->ft_pending_cb_ctx = ctx;
 	sm->ft_pending_auth_transaction = auth_transaction;
+	sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries;
 	res = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies,
 				      &resp_ies_len);
 	if (res < 0) {
@@ -2181,6 +2433,7 @@  static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth,
 	sm->ft_pending_cb = wpa_ft_rrb_rx_request_cb;
 	sm->ft_pending_cb_ctx = sm;
 	os_memcpy(sm->ft_pending_current_ap, current_ap, ETH_ALEN);
+	sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries;
 	res = wpa_ft_process_auth_req(sm, body, len, &resp_ies,
 				      &resp_ies_len);
 	if (res < 0) {
@@ -2259,6 +2512,10 @@  static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
 	u8 f_pairwise[sizeof(le16)];
 	int ret;
 
+	if (!pmk_r0)
+		return wpa_ft_rrb_build(key, key_len, tlvs, NULL, tlv_auth,
+					src_addr, type, packet, packet_len);
+
 	if (wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_name, r1kh_id,
 			      s1kh_id, pmk_r1, pmk_r1_name) < 0)
 		return -1;
@@ -2296,7 +2553,7 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	const int type = FT_PACKET_R0KH_R1KH_PULL;
 	u8 *plain = NULL, *packet = NULL;
 	size_t plain_len = 0, packet_len = 0;
-	struct ft_remote_r1kh *r1kh;
+	struct ft_remote_r1kh *r1kh, *r1kh_wildcard;
 	const u8 *key = NULL;
 	size_t key_len = 0;
 	int seq_ret;
@@ -2321,17 +2578,29 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
 	wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id));
 
-	wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh);
-	if (r1kh == NULL || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)
+	wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh, &r1kh_wildcard);
+	if (r1kh) {
+		key = r1kh->key;
+		key_len = sizeof(r1kh->key);
+	} else if (r1kh_wildcard) {
+		wpa_printf(MSG_DEBUG, "FT: Using wildcard R1KH-ID");
+		key = r1kh_wildcard->key;
+		key_len = sizeof(r1kh_wildcard->key);
+	} else
 		goto out;
-	key = r1kh->key;
-	key_len = sizeof(r1kh->key);
 
 	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "pull request", FT_RRB_NONCE_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len);
 
-	seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len, auth,
-				     auth_len, msgtype, noDefer);
+	seq_ret = FT_RRB_SEQ_DROP;
+	if (r1kh)
+		seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len,
+					     auth, auth_len, msgtype, noDefer);
+	if (!noDefer && r1kh_wildcard &&
+	   (!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0))
+		/* wildcard: r1kh-id unknown or changed addr -> do a seq req */
+		seq_ret = FT_RRB_SEQ_DEFER;
+
 	if (seq_ret == FT_RRB_SEQ_DROP)
 		goto out;
 
@@ -2339,6 +2608,13 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 			       src_addr, type, &plain, &plain_len) < 0)
 		goto out;
 
+	if (!r1kh)
+		r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard, src_addr,
+					   f_r1kh_id,
+					   wpa_auth->conf.rkh_pos_timeout);
+	if (!r1kh)
+		goto out;
+
 	if (seq_ret == FT_RRB_SEQ_DEFER) {
 		wpa_ft_rrb_seq_req(wpa_auth, r1kh->seq, src_addr, f_r0kh_id,
 				   f_r0kh_id_len, f_r1kh_id, key, key_len,
@@ -2349,6 +2625,8 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 
 	wpa_ft_rrb_seq_accept(wpa_auth, r1kh->seq, src_addr, auth, auth_len,
 			      msgtype);
+	wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh,
+				  wpa_auth->conf.rkh_pos_timeout);
 
 	RRB_GET(FT_RRB_PMK_R0_NAME, pmk_r0_name, msgtype, WPA_PMK_NAME_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", f_pmk_r0_name,
@@ -2379,10 +2657,8 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
 	};
 
-	if (wpa_ft_fetch_pmk_r0(wpa_auth, f_s1kh_id, f_pmk_r0_name, &r0) < 0) {
+	if (wpa_ft_fetch_pmk_r0(wpa_auth, f_s1kh_id, f_pmk_r0_name, &r0) < 0)
 		wpa_printf(MSG_DEBUG, "FT: No matching PMK-R0-Name found");
-		goto out;
-	}
 
 	ret = wpa_ft_rrb_build_r0(key, key_len, resp, r0, f_r1kh_id, f_s1kh_id,
 				  resp_auth, wpa_auth->addr,
@@ -2404,6 +2680,7 @@  out:
 
 /* @returns  0 on success
  *          -1 on error
+ *          -2 if FR_RRB_PAIRWISE is missing
  */
 static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 			    const u8 *src_addr, const u8 type,
@@ -2418,7 +2695,7 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 {
 	u8 *plain = NULL;
 	size_t plain_len = 0;
-	struct ft_remote_r0kh *r0kh;
+	struct ft_remote_r0kh *r0kh, *r0kh_wildcard;
 	const u8 *key;
 	size_t key_len;
 	int seq_ret;
@@ -2440,14 +2717,28 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 		goto out;
 	}
 
-	wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, &r0kh);
-	if (r0kh == NULL || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)
+	wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, &r0kh,
+			       &r0kh_wildcard);
+	if (r0kh) {
+		key = r0kh->key;
+		key_len = sizeof(r0kh->key);
+	} else if (r0kh_wildcard) {
+		wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID");
+		key = r0kh_wildcard->key;
+		key_len = sizeof(r0kh_wildcard->key);
+	} else
 		goto out;
-	key = r0kh->key;
-	key_len = sizeof(r0kh->key);
 
-	seq_ret = wpa_ft_rrb_seq_chk(r0kh->seq, src_addr, enc, enc_len, auth,
-				     auth_len, msgtype, cb ? 0 : 1);
+	seq_ret = FT_RRB_SEQ_DROP;
+	if (r0kh)
+		seq_ret = wpa_ft_rrb_seq_chk(r0kh->seq, src_addr, enc, enc_len,
+					     auth, auth_len, msgtype,
+					     cb ? 0 : 1);
+	if (cb && r0kh_wildcard &&
+	   (!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0))
+		/* wildcard: r0kh-id unknown or changed addr -> do a seq req */
+		seq_ret = FT_RRB_SEQ_DEFER;
+
 	if (seq_ret == FT_RRB_SEQ_DROP)
 		goto out;
 
@@ -2455,6 +2746,13 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 			       src_addr, type, &plain, &plain_len) < 0)
 		goto out;
 
+	if (!r0kh)
+		r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, src_addr,
+					   f_r0kh_id, f_r0kh_id_len,
+					   wpa_auth->conf.rkh_pos_timeout);
+	if (!r0kh)
+		goto out;
+
 	if (seq_ret == FT_RRB_SEQ_DEFER) {
 		wpa_ft_rrb_seq_req(wpa_auth, r0kh->seq, src_addr, f_r0kh_id,
 				   f_r0kh_id_len, f_r1kh_id, key, key_len,
@@ -2464,6 +2762,8 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 
 	wpa_ft_rrb_seq_accept(wpa_auth, r0kh->seq, src_addr, auth, auth_len,
 			      msgtype);
+	wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh,
+				  wpa_auth->conf.rkh_pos_timeout);
 
 	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
 	wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id));
@@ -2471,9 +2771,11 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 	if (s1kh_id_out)
 		os_memcpy(s1kh_id_out, f_s1kh_id, ETH_ALEN);
 
+	ret = -2;
 	RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16));
 	wpa_hexdump(MSG_DEBUG, "FT: pairwise", f_pairwise, f_pairwise_len);
 
+	ret = -1;
 	RRB_GET(FT_RRB_PMK_R1_NAME, pmk_r1_name, msgtype, WPA_PMK_NAME_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name",
 		    f_pmk_r1_name, WPA_PMK_NAME_LEN);
@@ -2506,13 +2808,20 @@  static void ft_finish_pull(struct wpa_state_machine *sm)
 	size_t resp_ies_len;
 	u16 status;
 
+	if (sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL)
+		return;
+
 	res = wpa_ft_process_auth_req(sm, wpabuf_head(sm->ft_pending_req_ies),
 				      wpabuf_len(sm->ft_pending_req_ies),
 				      &resp_ies, &resp_ies_len);
+	if (res < 0) {
+		/* this loop is broken by ft_pending_pull_left_retries */
+		wpa_printf(MSG_DEBUG, "FT: Callback postponed until response "
+				      "is available");
+		return;
+	}
 	wpabuf_free(sm->ft_pending_req_ies);
 	sm->ft_pending_req_ies = NULL;
-	if (res < 0)
-		res = WLAN_STATUS_UNSPECIFIED_FAILURE;
 	status = res;
 	wpa_printf(MSG_DEBUG, "FT: Postponed auth callback result for " MACSTR
 		   " - status %u", MAC2STR(sm->addr), status);
@@ -2555,7 +2864,7 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 {
 	const char *msgtype = "pull response";
 	const int type = FT_PACKET_R0KH_R1KH_RESP;
-	int ret = -1;
+	int nak, ret = -1;
 	struct ft_get_sta_ctx ctx;
 	u8 s1kh_id[ETH_ALEN];
 	const u8 *f_nonce;
@@ -2577,6 +2886,11 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 	ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth,
 			       auth_len, msgtype, s1kh_id,
 			       noDefer ? NULL : &wpa_ft_rrb_rx_resp);
+	if (ret == -2) {
+		ret = 0;
+		nak = 1;
+	} else
+		nak = 0;
 	if (ret < 0)
 		return -1;
 
@@ -2584,6 +2898,9 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 	if (wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) {
 		wpa_printf(MSG_DEBUG, "FT: Response to a pending pull request "
 			   "for " MACSTR, MAC2STR(ctx.sm->addr));
+		eloop_cancel_timeout(wpa_ft_expire_pull, ctx.sm, NULL);
+		if (nak)
+			ctx.sm->ft_pending_pull_left_retries = 0;
 		ft_finish_pull(ctx.sm);
 	}
 
@@ -2617,7 +2934,11 @@  static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
 			     const u8 *enc, size_t enc_len,
 			     const u8 *auth, size_t auth_len,
 			     struct ft_remote_seq **rkh_seq,
-			     u8 **key, size_t *key_len)
+			     u8 **key, size_t *key_len,
+			     struct ft_remote_r0kh **r0kh_out,
+			     struct ft_remote_r1kh **r1kh_out,
+			     struct ft_remote_r0kh **r0kh_wildcard_out,
+			     struct ft_remote_r1kh **r1kh_wildcard_out)
 {
 	struct ft_remote_r0kh *r0kh = NULL;
 	struct ft_remote_r1kh *r1kh = NULL;
@@ -2626,6 +2947,8 @@  static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
 	int to_r0kh, to_r1kh;
 	u8 *plain = NULL;
 	size_t plain_len = 0;
+	struct ft_remote_r0kh *r0kh_wildcard;
+	struct ft_remote_r1kh *r1kh_wildcard;
 
 	RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1);
 	RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN);
@@ -2645,31 +2968,38 @@  static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
 
 	if (!to_r0kh) {
 		wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
-				       &r0kh);
-		if (r0kh == NULL ||
-		    os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0) {
+				       &r0kh, &r0kh_wildcard);
+		if (!r0kh_wildcard && (!r0kh ||
+		    os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
 				    f_r0kh_id, f_r0kh_id_len);
 			goto out;
 		}
-
-		*key = r0kh->key;
-		*key_len = sizeof(r0kh->key);
-		*rkh_seq = r0kh->seq;
+		if (r0kh) {
+			*key = r0kh->key;
+			*key_len = sizeof(r0kh->key);
+		} else {
+			*key = r0kh_wildcard->key;
+			*key_len = sizeof(r0kh_wildcard->key);
+		}
 	}
 
 	if (!to_r1kh) {
-		wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh);
-		if (r1kh == NULL ||
-		    os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0) {
+		wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh,
+				       &r1kh_wildcard);
+		if (!r1kh_wildcard && (!r1kh ||
+		    os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID",
 				    f_r1kh_id, FT_R1KH_ID_LEN);
 			goto out;
 		}
-
-		*key = r1kh->key;
-		*key_len = sizeof(r1kh->key);
-		*rkh_seq = r1kh->seq;
+		if (r1kh) {
+			*key = r1kh->key;
+			*key_len = sizeof(r1kh->key);
+		} else {
+			*key = r1kh_wildcard->key;
+			*key_len = sizeof(r1kh_wildcard->key);
+		}
 	}
 
 	if (wpa_ft_rrb_decrypt(*key, *key_len, enc, enc_len, auth, auth_len,
@@ -2678,6 +3008,38 @@  static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
 
 	os_free(plain); plain = NULL;
 
+	if (!to_r0kh) {
+		if (!r0kh)
+			r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard,
+						   src_addr, f_r0kh_id,
+						   f_r0kh_id_len, ftRRBseqTimeout);
+		if (!r0kh)
+			goto out;
+
+		wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh, ftRRBseqTimeout);
+		*rkh_seq = r0kh->seq;
+		if (r0kh_out)
+			*r0kh_out = r0kh;
+		if (r0kh_wildcard_out)
+			*r0kh_wildcard_out = r0kh_wildcard;
+	}
+
+	if (!to_r1kh) {
+		if (!r1kh)
+			r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard,
+						   src_addr, f_r1kh_id,
+						   ftRRBseqTimeout);
+		if (!r1kh)
+			goto out;
+
+		wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh, ftRRBseqTimeout);
+		*rkh_seq = r1kh->seq;
+		if (r1kh_out)
+			*r1kh_out = r1kh;
+		if (r1kh_wildcard_out)
+			*r1kh_wildcard_out = r1kh_wildcard;
+	}
+
 	return 0;
 out:
 	return -1;
@@ -2693,7 +3055,7 @@  static int wpa_ft_rrb_rx_seq_req(struct wpa_authenticator *wpa_auth,
 	struct ft_rrb_seq f_seq;
 	const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id;
 	size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len;
-	struct ft_remote_seq *rkh_seq;
+	struct ft_remote_seq *rkh_seq = NULL;
 	u8 *packet = NULL, *key = NULL;
 	size_t packet_len = 0, key_len = 0;
 
@@ -2701,7 +3063,7 @@  static int wpa_ft_rrb_rx_seq_req(struct wpa_authenticator *wpa_auth,
 
 	if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
 			      enc, enc_len, auth, auth_len, &rkh_seq, &key,
-			      &key_len) < 0)
+			      &key_len, NULL, NULL, NULL, NULL) < 0)
 		goto out;
 
 	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq request", FT_RRB_NONCE_LEN);
@@ -2752,6 +3114,8 @@  static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth,
 {
 	u8 *key = NULL;
 	size_t key_len = 0;
+	struct ft_remote_r0kh *r0kh = NULL, *r0kh_wildcard = NULL;
+	struct ft_remote_r1kh *r1kh = NULL, *r1kh_wildcard = NULL;
 	const u8 *f_nonce, *f_seq;
 	size_t f_nonce_len, f_seq_len;
 	struct ft_remote_seq *rkh_seq = NULL;
@@ -2765,7 +3129,8 @@  static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth,
 
 	if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_RESP,
 			      enc, enc_len, auth, auth_len, &rkh_seq, &key,
-			      &key_len) < 0)
+			      &key_len, &r0kh, &r1kh, &r0kh_wildcard,
+			      &r1kh_wildcard) < 0)
 		goto out;
 
 	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq response", FT_RRB_NONCE_LEN);
@@ -2788,6 +3153,20 @@  static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth,
 		goto out;
 	}
 
+	if (r0kh) {
+		wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh,
+					  wpa_auth->conf.rkh_pos_timeout);
+		if (r0kh_wildcard)
+			os_memcpy(r0kh->addr, src_addr, ETH_ALEN);
+	}
+
+	if (r1kh) {
+		wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh,
+					  wpa_auth->conf.rkh_pos_timeout);
+		if (r1kh_wildcard)
+			os_memcpy(r1kh->addr, src_addr, ETH_ALEN);
+	}
+
 	seq_ret = wpa_ft_rrb_seq_chk(rkh_seq, src_addr, enc, enc_len, auth,
 				     auth_len, "seq response", 1);
 	if (seq_ret == FT_RRB_SEQ_OK) {
@@ -2952,6 +3331,7 @@  void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 	const u16 *head;
 	const u8 *auth, *enc;
 	size_t alen, elen;
+	int noDefer = 0;
 
 	wpa_printf(MSG_DEBUG, "FT: RRB-OUI received frame from remote AP "
 		   MACSTR, MAC2STR(src_addr));
@@ -2969,7 +3349,7 @@  void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 			   "FT: RRB-OUI received frame from remote AP " MACSTR
 			   " to multicast address " MACSTR,
 			   MAC2STR(src_addr), MAC2STR(dst_addr));
-		return;
+		noDefer = 1;
 	}
 
 	head = (u16 *) data;
@@ -2991,23 +3371,23 @@  void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 	switch (oui_suffix) {
 	case FT_PACKET_R0KH_R1KH_PULL:
 		wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen,
-				   0);
+				   noDefer);
 		break;
 	case FT_PACKET_R0KH_R1KH_RESP:
 		wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen,
-				   0);
+				   noDefer);
 		break;
 	case FT_PACKET_R0KH_R1KH_PUSH:
 		wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen,
-				   0);
+				   noDefer);
 		break;
 	case FT_PACKET_R0KH_R1KH_SEQ_REQ:
 		wpa_ft_rrb_rx_seq_req(wpa_auth, src_addr, enc, elen, auth, alen,
-				      0);
+				      noDefer);
 		break;
 	case FT_PACKET_R0KH_R1KH_SEQ_RESP:
 		wpa_ft_rrb_rx_seq_resp(wpa_auth, src_addr, enc, elen, auth,
-				       alen, 0);
+				       alen, noDefer);
 		break;
 	}
 }
@@ -3067,6 +3447,8 @@  void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 
 	if (!wpa_auth->conf.pmk_r1_push)
 		return;
+	if (!wpa_auth->conf.r1kh_list)
+		return;
 
 	r0 = wpa_auth->ft_pmk_cache->pmk_r0;
 	while (r0) {
@@ -3082,7 +3464,10 @@  void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 	wpa_printf(MSG_DEBUG, "FT: Deriving and pushing PMK-R1 keys to R1KHs "
 		   "for STA " MACSTR, MAC2STR(addr));
 
-	for (r1kh = wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) {
+	for (r1kh = *wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) {
+		if (is_zero_ether_addr(r1kh->addr) ||
+		    is_zero_ether_addr(r1kh->id))
+			continue;
 		if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0)
 			continue;
 		wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr);
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index ba964e6..1e4ef37 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -74,8 +74,12 @@  static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 	os_memcpy(wconf->r1_key_holder, conf->r1_key_holder, FT_R1KH_ID_LEN);
 	wconf->r0_key_lifetime = conf->r0_key_lifetime;
 	wconf->reassociation_deadline = conf->reassociation_deadline;
-	wconf->r0kh_list = conf->r0kh_list;
-	wconf->r1kh_list = conf->r1kh_list;
+	wconf->rkh_pos_timeout = conf->rkh_pos_timeout;
+	wconf->rkh_neg_timeout = conf->rkh_neg_timeout;
+	wconf->rkh_pull_timeout = conf->rkh_pull_timeout;
+	wconf->rkh_pull_retries = conf->rkh_pull_retries;
+	wconf->r0kh_list = &conf->r0kh_list;
+	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;
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 4a9c629..3e6c6a1 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -124,6 +124,7 @@  struct wpa_state_machine {
 	u8 ft_pending_pull_nonce[FT_RRB_NONCE_LEN];
 	u8 ft_pending_auth_transaction;
 	u8 ft_pending_current_ap[ETH_ALEN];
+	int ft_pending_pull_left_retries;
 #endif /* CONFIG_IEEE80211R_AP */
 
 	int pending_1_of_4_timeout;