diff mbox

[v2,09/33] FT: new rrb message format

Message ID 1474750454-6626-10-git-send-email-michael-dev@fami-braun.de
State Changes Requested
Headers show

Commit Message

michael-dev Sept. 24, 2016, 8:53 p.m. UTC
Convert FT RRB into new TLV based format.
Encryption is unchanged.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 src/ap/wpa_auth.h    | 113 ++++----
 src/ap/wpa_auth_ft.c | 736 ++++++++++++++++++++++++++++++++++++---------------
 src/ap/wpa_auth_i.h  |   2 +-
 3 files changed, 567 insertions(+), 284 deletions(-)

Comments

Jouni Malinen Oct. 16, 2016, 11:57 a.m. UTC | #1
On Sat, Sep 24, 2016 at 10:53:50PM +0200, Michael Braun wrote:
> Convert FT RRB into new TLV based format.
> Encryption is unchanged.

This type of change should be made before doing any of the
non-compatible changes in the pull/push message formats. As such, I
dropped the previous not-yet-applied patches in the series from my
queue. It would probably be good to focus on the message format change
before resubmitting any of the other patches that depend on this.

> diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
> @@ -38,76 +38,63 @@ struct ft_rrb_frame {
>  /* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r */
> -#define FT_PACKET_R0KH_R1KH_PULL 200
> -#define FT_PACKET_R0KH_R1KH_RESP 201
> -#define FT_PACKET_R0KH_R1KH_PUSH 202

> +/* 200, 201 und 202 are reserved by old protocol version */
> +#define FT_PACKET_R0KH_R1KH_PULL 203
> +#define FT_PACKET_R0KH_R1KH_RESP 204
> +#define FT_PACKET_R0KH_R1KH_PUSH 205

It makes sense to not use the old values, but it needs to be kept in
mind that the previous design was pretty bad misuse of the undefined
values and we should not try to continue using something very similar to
define the messages. The Ethertype 89-0d is managed by the IEEE 802.11
standard. Values 1-4 of the Payload Type field have been assigned so far
and all the other values (5-255) are reserved. We should not use them
for the new protocol messages here unless we can first somehow get IEEE
802.11 WG to assign those values for such uses.. And I'm not really sure
I would even try to ask for such allocation for this type of
implementation specific use case.

In other words, I think we need to find other ways of doing this either
by getting a properly assigned Ethertype (or subtype of one) or
alternatively building something on top of IP packets, etc., that would
not need a new Ethertype assignment.

> +/* new packet format */
> +
> +/* packet layout
> + *  | struct ft_rrb_frame | multiple of of struct ft_rrbv1_tlv |

Taken into account the comments above, this struct ft_rrb_frame part
would likely need to change to something else since it is not really
defined for this type of vendor specific use and while we could use the
exact same header definition in a completely different message, that may
not be the cleanest approach.

> + *  | padding for encryption (blocksize) | 8 byte extra for keywrap |
> + * where
> + *  packet_type = FT_PACKET_R0KH_R1KH*
> + *  action_length = size of all struct ft_rrbv1_tlv (without final padding)
> + *                  (aka plaintext length)
> + *  encryption: covers everything after struct ft_rrb_frame
> + */

The TLV part looks pretty reasonable, but I'd replace encryption to use
something quite different, e.g., with an AEAD cipher and additional
integrity protection for some of the unencrypted header fields.
diff mbox

Patch

diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 0e5ad1a..83fc305 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -38,76 +38,63 @@  struct ft_rrb_frame {
 #define FT_PACKET_REQUEST 0
 #define FT_PACKET_RESPONSE 1
 /* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r */
-#define FT_PACKET_R0KH_R1KH_PULL 200
-#define FT_PACKET_R0KH_R1KH_RESP 201
-#define FT_PACKET_R0KH_R1KH_PUSH 202
-
-#define FT_R0KH_R1KH_PULL_NONCE_LEN 16
-#define FT_R0KH_R1KH_PULL_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
-				    FT_R0KH_ID_MAX_LEN + 1 + \
-				    WPA_PMK_NAME_LEN + FT_R1KH_ID_LEN + \
-				    ETH_ALEN)
-#define FT_R0KH_R1KH_PULL_PAD_LEN ((8 - FT_R0KH_R1KH_PULL_DATA_LEN % 8) % 8)
-
-struct ft_r0kh_r1kh_pull_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PULL */
-	le16 data_length; /* little endian length of data (44) */
-	u8 ap_address[ETH_ALEN];
+/* 200, 201 und 202 are reserved by old protocol version */
+#define FT_PACKET_R0KH_R1KH_PULL 203
+#define FT_PACKET_R0KH_R1KH_RESP 204
+#define FT_PACKET_R0KH_R1KH_PUSH 205
+
+
+/* new packet format */
+
+/* packet layout
+ *  | struct ft_rrb_frame | multiple of of struct ft_rrbv1_tlv |
+ *  | padding for encryption (blocksize) | 8 byte extra for keywrap |
+ * where
+ *  packet_type = FT_PACKET_R0KH_R1KH*
+ *  action_length = size of all struct ft_rrbv1_tlv (without final padding)
+ *                  (aka plaintext length)
+ *  encryption: covers everything after struct ft_rrb_frame
+ */
 
-	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN];
-	u8 r0kh_id[FT_R0KH_ID_MAX_LEN];
-	u8 r0kh_id_len;
-	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
-	u8 r1kh_id[FT_R1KH_ID_LEN];
-	u8 s1kh_id[ETH_ALEN];
-	u8 pad[FT_R0KH_R1KH_PULL_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
-} STRUCT_PACKED;
+#define FT_RRB_NONCE_LEN 16
 
-#define FT_R0KH_R1KH_RESP_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
-				    FT_R1KH_ID_LEN + ETH_ALEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
-#define FT_R0KH_R1KH_RESP_PAD_LEN ((8 - FT_R0KH_R1KH_RESP_DATA_LEN % 8) % 8)
-struct ft_r0kh_r1kh_resp_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_RESP */
-	le16 data_length; /* little endian length of data (78) */
-	u8 ap_address[ETH_ALEN];
+#define FT_RRB_LAST_EMPTY     0 /* placeholder */
 
-	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; /* copied from pull */
-	u8 r1kh_id[FT_R1KH_ID_LEN]; /* copied from pull */
-	u8 s1kh_id[ETH_ALEN]; /* copied from pull */
-	u8 pmk_r1[PMK_LEN];
-	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
-	le16 pairwise; /* zero if no entry found */
-	u8 pad[FT_R0KH_R1KH_RESP_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
-} STRUCT_PACKED;
+#define FT_RRB_NONCE          1 /* size FT_RRB_NONCE_LEN */
+#define FT_RRB_TIMESTAMP      2 /* le32 unix seconds */
 
-#define FT_R0KH_R1KH_PUSH_DATA_LEN (4 + FT_R1KH_ID_LEN + ETH_ALEN + \
-				    WPA_PMK_NAME_LEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
-#define FT_R0KH_R1KH_PUSH_PAD_LEN ((8 - FT_R0KH_R1KH_PUSH_DATA_LEN % 8) % 8)
-struct ft_r0kh_r1kh_push_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PUSH */
-	le16 data_length; /* little endian length of data (82) */
-	u8 ap_address[ETH_ALEN];
+#define FT_RRB_R0KH_ID        3 /* FT_R0KH_ID_MAX_LEN */
+#define FT_RRB_R1KH_ID        4 /* FT_R1KH_ID_LEN */
+#define FT_RRB_S1KH_ID        5 /* ETH_ALEN */
+
+#define FT_RRB_PMK_R0_NAME    6 /* WPA_PMK_NAME_LEN */
+#define FT_RRB_PMK_R0         7 /* PMK_LEN */
+#define FT_RRB_PMK_R1_NAME    8 /* WPA_PMK_NAME_LEN */
+#define FT_RRB_PMK_R1         9 /* PMK_LEN */
 
-	/* Encrypted with AES key-wrap */
-	u8 timestamp[4]; /* current time in seconds since unix epoch, little
-			  * endian */
-	u8 r1kh_id[FT_R1KH_ID_LEN];
-	u8 s1kh_id[ETH_ALEN];
-	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
-	u8 pmk_r1[PMK_LEN];
-	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
-	le16 pairwise;
-	u8 pad[FT_R0KH_R1KH_PUSH_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
+#define FT_RRB_PAIRWISE      10 /* le16 */
+
+struct ft_rrbv1_tlv {
+	le16 type;
+	le16 len;
+	/* followed by data of length len */
 } STRUCT_PACKED;
 
+/* session TLVs:
+ *   required: [PMK_R1, PMK_R1_NAME, PAIRWISE]
+ *
+ * pull frame TLVs:
+ *   required: NONCE, R0KH_ID, PMK_R0_NAME, R1KH_ID, S1KH_ID
+ *
+ * response frame TLVs:
+ *   required: NONCE, R1KH_ID, S1KH_ID,
+ *   optional: all session-TLVs
+ *
+ * push frame TLVs:
+ *   required: TIMESTAMP, R1KH_ID, S1KH_ID, PMK_R0_NAME,
+ *             session-TLVs
+ */
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 0c92d40..5abe412 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -32,6 +32,232 @@  static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx);
 static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx);
 
 
+/**
+ * decrypt message
+ * @data  full packet
+ * @kek   encryption key
+ * @plain pointer to store pointer for plaintext
+ *        (only action_length many bytes)
+ *        needs to be freed by caller if not null
+ *        will only be returned on success
+ * @return 0 on success, -1 on error
+ */
+
+static int wpa_ft_rrb_decrypt(const u8 *kek, size_t kek_len,
+			      const u8 *data, const size_t data_len,
+			      u8 **plain, size_t *plain_size)
+{
+	struct ft_rrb_frame *f;
+	size_t alen, blen, clen;
+	const u8 *crypt;
+
+	*plain = NULL;
+
+	if (data_len < sizeof(*f))
+		return -1;
+
+	f = (struct ft_rrb_frame *) data;
+
+	alen = le_to_host16(f->action_length); /* plaintext length */
+	blen = alen + ((8 - (alen % 8)) % 8); /* round up to full block size */
+	clen = blen + 8; /* extra space for keywrap */
+
+	/* ciphertext (packet) long enough for given plaintext length? */
+	if (data_len - sizeof(*f) < clen)
+		return -1;
+	crypt = data + sizeof(*f);
+
+	*plain = os_zalloc(blen);
+	if (!*plain)
+		return -1;
+
+	if (!aes_unwrap(kek, kek_len, blen / 8, crypt, *plain)) {
+		*plain_size = alen;
+		return 0;
+	}
+
+	os_free(*plain);
+	*plain = NULL;
+
+	return -1;
+}
+
+
+/* get first tlv record in packet matching type
+ * @data (decrypted) packet
+ * @return 0 on success else -1
+ */
+static int wpa_ft_rrb_get_tlv(const u8 *plain, const size_t plain_len,
+			      int type, size_t *tlv_len, const u8 **tlv_data)
+{
+	struct ft_rrbv1_tlv *f;
+	size_t left;
+	le16 type16;
+	size_t len;
+
+	left = plain_len;
+	type16 = host_to_le16(type);
+
+	while (left >= sizeof(*f)) {
+		f = (struct ft_rrbv1_tlv *) plain;
+
+		left -= sizeof(*f);
+		plain += sizeof(*f);
+		len = le_to_host16(f->len);
+
+		if (left < len)
+			return -1;
+
+		if (f->type == type16) {
+			*tlv_len = len;
+			*tlv_data = plain;
+			return 0;
+		}
+
+		left -= len;
+		plain += len;
+	}
+
+	return -1;
+}
+
+
+/**
+ * encrypt message
+ * @frame ft_rrb_frame
+ * @kek   encryption key
+ * @plain pointer to store pointer for plaintext
+ *        (only action_length many bytes)
+ *        needs to be freed by caller if not null
+ *        will only be returned on success
+ * @return 0 on success, -1 on error
+ */
+
+struct tlv_list {
+	int type;
+	int len;
+	const u8 *data;
+};
+
+static inline size_t wpa_ft_tlv_len(const struct tlv_list *tlvs)
+{
+	size_t tlv_len = 0;
+	int i;
+
+	if (!tlvs)
+		return 0;
+
+	for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) {
+		tlv_len += sizeof(struct ft_rrbv1_tlv);
+		tlv_len += tlvs[i].len;
+	}
+
+	return tlv_len;
+}
+
+static inline size_t wpa_ft_tlv_lin(const struct tlv_list *tlvs, u8 *start,
+				    u8 *endpos)
+{
+	int i;
+	size_t tlv_len;
+	struct ft_rrbv1_tlv *hdr;
+	u8 *pos = start;
+
+	if (!tlvs)
+		return 0;
+
+	tlv_len = 0;
+	pos = start + tlv_len;
+	for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) {
+		tlv_len += sizeof(*hdr);
+		if (start + tlv_len > endpos)
+			return tlv_len;
+		hdr = (struct ft_rrbv1_tlv *) pos;
+		hdr->type = host_to_le16(tlvs[i].type);
+		hdr->len = host_to_le16(tlvs[i].len);
+		pos = start + tlv_len;
+
+		tlv_len += tlvs[i].len;
+		if (start + tlv_len > endpos)
+			return tlv_len;
+		os_memcpy(pos, tlvs[i].data, tlvs[i].len);
+		pos = start + tlv_len;
+	}
+
+	return tlv_len;
+}
+
+static int wpa_ft_rrb_build(const u8 *kek, size_t kek_len,
+			    const struct ft_rrb_frame *frame,
+			    const struct tlv_list *tlvs1,
+			    const struct tlv_list *tlvs2,
+			    u8 **packet, size_t *packet_len)
+{
+	u8 *plain, *pos, *crypt, *endpos;
+	struct ft_rrb_frame *f2;
+	size_t tlv_len, pad_len;
+
+	*packet = NULL;
+	*packet_len = 0;
+
+	tlv_len = 0;
+	tlv_len += wpa_ft_tlv_len(tlvs1);
+	tlv_len += wpa_ft_tlv_len(tlvs2);
+	pad_len = (8 - (tlv_len % 8)) % 8;
+
+	plain  = os_zalloc(tlv_len + pad_len);
+	if (plain == NULL)
+		return -1;
+
+	pos = plain;
+	endpos = plain + tlv_len;
+	pos += wpa_ft_tlv_lin(tlvs1, pos, endpos);
+	pos += wpa_ft_tlv_lin(tlvs2, pos, endpos);
+
+	/* sanity check */
+	if (pos != endpos) {
+		wpa_printf(MSG_ERROR, "FT: length error building RRB");
+		os_free(plain);
+		return -1;
+	}
+
+	*packet_len = sizeof(*frame) + tlv_len + pad_len + 8;
+	*packet = os_zalloc(*packet_len);
+	if (*packet == NULL) {
+		*packet_len = 0;
+		os_free(plain);
+		return -1;
+	}
+
+	f2 = (struct ft_rrb_frame *) *packet;
+	os_memcpy(*packet, frame, sizeof(*frame));
+	f2->action_length = host_to_le16(tlv_len);
+
+	crypt = *packet + sizeof(*frame);
+
+	if (!aes_wrap(kek, kek_len, (tlv_len + 7) / 8, plain, crypt)) {
+		os_free(plain);
+		return 0;
+	}
+
+	os_free(plain);
+	os_free(*packet);
+	*packet = NULL;
+	return -1;
+}
+
+
+#define RRB_GET(type, field, txt, checklength) do { \
+	if (wpa_ft_rrb_get_tlv(plain, plain_len, type, \
+				&f_##field##_len, &f_##field) < 0 || \
+	    (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \
+		wpa_printf(MSG_DEBUG, "FT: Missing " #field " in PMK-R1 " \
+			   "%s from " MACSTR, txt, MAC2STR(src_addr)); \
+		goto out; \
+	} \
+} while (0)
+
+
 static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst,
 			   const u8 *data, size_t data_len)
 {
@@ -241,7 +467,7 @@  static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
 
 static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
 			       const u8 *spa, const u8 *pmk_r0_name,
-			       u8 *pmk_r0, int *pairwise)
+			       const struct wpa_ft_pmk_r0_sa **r0_out)
 {
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r0_sa *r0;
@@ -251,15 +477,14 @@  static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
 		if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 &&
 		    os_memcmp_const(r0->pmk_r0_name, pmk_r0_name,
 				    WPA_PMK_NAME_LEN) == 0) {
-			os_memcpy(pmk_r0, r0->pmk_r0, PMK_LEN);
-			if (pairwise)
-				*pairwise = r0->pairwise;
+			*r0_out = r0;
 			return 0;
 		}
 
 		r0 = r0->next;
 	}
 
+	*r0_out = NULL;
 	return -1;
 }
 
@@ -391,7 +616,8 @@  static void wpa_ft_rrb_del_r1kh(void *eloop_ctx, void *timeout_ctx)
 
 static void wpa_ft_rrb_add_r1kh(struct wpa_authenticator *wpa_auth,
 				struct ft_remote_r1kh *r1kh_wildcard,
-				const u8 *src_addr, u8 *r1kh_id, int timeout)
+				const u8 *src_addr, const u8 *r1kh_id,
+				int timeout)
 {
 	struct ft_remote_r1kh *r1kh;
 
@@ -454,7 +680,9 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 			      const u8 *pmk_r0_name)
 {
 	struct ft_remote_r0kh *r0kh, *r0kh_wildcard = NULL;
-	struct ft_r0kh_r1kh_pull_frame frame, f;
+	struct ft_rrb_frame req_hdr;
+	u8 *packet = NULL;
+	size_t packet_len;
 	int tsecs, tusecs, first;
 	struct wpabuf *ft_pending_req_ies;
 
@@ -494,36 +722,36 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 	wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request to remote R0KH "
 		   "address " MACSTR, MAC2STR(r0kh->addr));
 
-	os_memset(&frame, 0, sizeof(frame));
-	frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	frame.packet_type = FT_PACKET_R0KH_R1KH_PULL;
-	frame.data_length = host_to_le16(FT_R0KH_R1KH_PULL_DATA_LEN);
-	os_memcpy(frame.ap_address, sm->wpa_auth->addr, ETH_ALEN);
-
-	/* aes_wrap() does not support inplace encryption, so use a temporary
-	 * buffer for the data. */
-	if (first) {
-		if (random_get_bytes(f.nonce, FT_R0KH_R1KH_PULL_NONCE_LEN)) {
-			wpa_printf(MSG_DEBUG, "FT: Failed to get random data "
-					      "for nonce");
-			return -1;
-		}
-		os_memcpy(sm->ft_pending_pull_nonce, f.nonce,
-			  FT_R0KH_R1KH_PULL_NONCE_LEN);
-	} else
-		os_memcpy(f.nonce, sm->ft_pending_pull_nonce,
-			  FT_R0KH_R1KH_PULL_NONCE_LEN);
-	os_memcpy(f.r0kh_id, sm->r0kh_id, FT_R0KH_ID_MAX_LEN);
-	f.r0kh_id_len = sm->r0kh_id_len;
-	os_memcpy(f.pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN);
-	os_memcpy(f.r1kh_id, sm->wpa_auth->conf.r1_key_holder, FT_R1KH_ID_LEN);
-	os_memcpy(f.s1kh_id, sm->addr, ETH_ALEN);
-	os_memset(f.pad, 0, sizeof(f.pad));
-
-	if (aes_wrap(r0kh->key, sizeof(r0kh->key),
-		     (FT_R0KH_R1KH_PULL_DATA_LEN + 7) / 8,
-		     f.nonce, frame.nonce) < 0)
+	os_memset(&req_hdr, 0, sizeof(req_hdr));
+	req_hdr.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
+	req_hdr.packet_type = FT_PACKET_R0KH_R1KH_PULL;
+	os_memcpy(req_hdr.ap_address, sm->wpa_auth->addr, ETH_ALEN);
+
+	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;
+	}
+
+	struct tlv_list req_tlv[] = {
+		{ .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN,
+		  .data = sm->ft_pending_pull_nonce },
+		{ .type = FT_RRB_R0KH_ID, .len = sm->r0kh_id_len,
+		  .data = sm->r0kh_id },
+		{ .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN,
+		  .data = pmk_r0_name },
+		{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
+		  .data = sm->wpa_auth->conf.r1_key_holder },
+		{ .type = FT_RRB_S1KH_ID, .len = ETH_ALEN,
+		  .data = sm->addr },
+		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
+	};
+
+	if (wpa_ft_rrb_build(r0kh->key, sizeof(r0kh->key), &req_hdr,
+			     req_tlv, NULL, &packet, &packet_len) < 0) {
 		return -1;
+	}
 
 	ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len);
 	wpabuf_free(sm->ft_pending_req_ies);
@@ -535,7 +763,10 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 	tusecs = (sm->wpa_auth->conf.rkh_pull_timeout % 1000) * 1000;
 	eloop_register_timeout(tsecs, tusecs, wpa_ft_expire_pull, sm, NULL);
 
-	wpa_ft_rrb_send(sm->wpa_auth, r0kh->addr, (u8 *) &frame, sizeof(frame));
+	wpa_ft_rrb_send(sm->wpa_auth, r0kh->addr, packet, packet_len);
+
+	os_free(packet);
+	packet = NULL;
 
 	return 0;
 }
@@ -1587,23 +1818,65 @@  static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
 }
 
 
+static int wpa_ft_rrb_build_r0(const u8 *kek, size_t kek_len,
+			       const struct ft_rrb_frame *frame,
+			       const struct tlv_list *tlvs,
+			       const struct wpa_ft_pmk_r0_sa *pmk_r0,
+			       const u8 *r1kh_id, const u8 *s1kh_id,
+			       u8 **packet, size_t *packet_len)
+{
+	u8 pmk_r1[PMK_LEN];
+	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
+	u8 f_pairwise[sizeof(le16)];
+	int ret;
+
+	if (!pmk_r0)
+		return wpa_ft_rrb_build(kek, kek_len, frame, tlvs, NULL,
+					packet, packet_len);
+
+	wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_name, r1kh_id,
+			  s1kh_id, pmk_r1, pmk_r1_name);
+	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", pmk_r1, PMK_LEN);
+	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", pmk_r1_name, WPA_PMK_NAME_LEN);
+	WPA_PUT_LE16(f_pairwise, pmk_r0->pairwise);
+
+	struct tlv_list sess_tlv[] = {
+		{ .type = FT_RRB_PMK_R1, .len = sizeof(pmk_r1),
+		  .data = pmk_r1 },
+		{ .type = FT_RRB_PMK_R1_NAME, .len = sizeof(pmk_r1_name),
+		  .data = pmk_r1_name },
+		{ .type = FT_RRB_PAIRWISE, .len = sizeof(f_pairwise),
+		  .data = f_pairwise },
+		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
+	};
+
+	ret = wpa_ft_rrb_build(kek, kek_len, frame, tlvs, sess_tlv,
+			       packet, packet_len);
+
+	os_memset(pmk_r1, 0, sizeof(pmk_r1));
+
+	return ret;
+
+}
+
 static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *data, size_t data_len)
 {
-	struct ft_r0kh_r1kh_pull_frame f;
-	const u8 *crypt;
-	u8 *plain;
+	u8 *plain, *packet = NULL;
+	size_t plain_len, packet_len;
 	struct ft_remote_r1kh *r1kh, *r1kh_wildcard = NULL;
-	struct ft_r0kh_r1kh_resp_frame resp, r;
-	u8 pmk_r0[PMK_LEN];
-	int pairwise;
-
-	wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull");
+	struct ft_rrb_frame resp_hdr;
+	int ret;
+	const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id, *f_s1kh_id, *f_pmk_r0_name;
+	size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len, f_s1kh_id_len;
+	size_t f_pmk_r0_name_len;
+	struct tlv_list *sess_tlv = NULL;
+	const struct wpa_ft_pmk_r0_sa *r0;
 
-	if (data_len < sizeof(f))
-		return -1;
+	wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull request");
 
+	/* get key and decrypt */
 	if (!wpa_auth->conf.r1kh_list)
 		return -1;
 	r1kh = *wpa_auth->conf.r1kh_list;
@@ -1626,71 +1899,68 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 		return -1;
 	}
 
-	crypt = data + offsetof(struct ft_r0kh_r1kh_pull_frame, nonce);
-	os_memset(&f, 0, sizeof(f));
-	plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_pull_frame, nonce);
-	/* aes_unwrap() does not support inplace decryption, so use a temporary
-	 * buffer for the data. */
-	if (aes_unwrap(r1kh->key, sizeof(r1kh->key),
-		       (FT_R0KH_R1KH_PULL_DATA_LEN + 7) / 8,
-		       crypt, plain) < 0) {
+	if (wpa_ft_rrb_decrypt(r1kh->key, sizeof(r1kh->key),
+			       data, data_len, &plain, &plain_len) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull "
 			   "request from " MACSTR, MAC2STR(src_addr));
 		return -1;
 	}
 
-	if (f.r0kh_id_len != wpa_auth->conf.r0_key_holder_len ||
-	    os_memcmp_const(f.r0kh_id, wpa_auth->conf.r0_key_holder,
-			    f.r0kh_id_len) != 0)
-		return -1;
+	RRB_GET(FT_RRB_R0KH_ID, r0kh_id, "pull request", -1);
+	if (f_r0kh_id_len != wpa_auth->conf.r0_key_holder_len ||
+	    os_memcmp_const(f_r0kh_id, wpa_auth->conf.r0_key_holder,
+			    f_r0kh_id_len) != 0)
+		goto out;
 
+	RRB_GET(FT_RRB_NONCE, nonce, "pull request", FT_RRB_NONCE_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce",
-		    f.nonce, sizeof(f.nonce));
+		    f_nonce, f_nonce_len);
+
+	RRB_GET(FT_RRB_PMK_R0_NAME, pmk_r0_name, "pull request",
+		WPA_PMK_NAME_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR0Name",
-		    f.pmk_r0_name, WPA_PMK_NAME_LEN);
+		    f_pmk_r0_name, f_pmk_r0_name_len);
+
+	RRB_GET(FT_RRB_R1KH_ID, r1kh_id, "pull request", FT_R1KH_ID_LEN);
+	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, "pull request", ETH_ALEN);
 	wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR " S1KH-ID="
-		   MACSTR, MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id));
+		   MACSTR, MAC2STR(f_r1kh_id), MAC2STR(f_s1kh_id));
 
 	if (r1kh == r1kh_wildcard && wpa_auth->conf.rkh_pos_timeout)
 		wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard, src_addr,
-				    f.r1kh_id, wpa_auth->conf.rkh_pos_timeout);
-
-	os_memset(&resp, 0, sizeof(resp));
-	resp.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	resp.packet_type = FT_PACKET_R0KH_R1KH_RESP;
-	resp.data_length = host_to_le16(FT_R0KH_R1KH_RESP_DATA_LEN);
-	os_memcpy(resp.ap_address, wpa_auth->addr, ETH_ALEN);
-
-	/* aes_wrap() does not support inplace encryption, so use a temporary
-	 * buffer for the data. */
-	os_memcpy(r.nonce, f.nonce, sizeof(f.nonce));
-	os_memcpy(r.r1kh_id, f.r1kh_id, FT_R1KH_ID_LEN);
-	os_memcpy(r.s1kh_id, f.s1kh_id, ETH_ALEN);
-	if (wpa_ft_fetch_pmk_r0(wpa_auth, f.s1kh_id, f.pmk_r0_name, pmk_r0,
-				&pairwise) < 0) {
-		wpa_printf(MSG_DEBUG, "FT: No matching PMKR0Name found for "
-			   "PMK-R1 pull");
-		r.pairwise = 0;
-	} else {
-		wpa_derive_pmk_r1(pmk_r0, f.pmk_r0_name, f.r1kh_id, f.s1kh_id,
-				  r.pmk_r1, r.pmk_r1_name);
-		wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", r.pmk_r1, PMK_LEN);
-		wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", r.pmk_r1_name,
-			    WPA_PMK_NAME_LEN);
-		r.pairwise = host_to_le16(pairwise);
-	}
-	os_memset(r.pad, 0, sizeof(r.pad));
-
-	if (aes_wrap(r1kh->key, sizeof(r1kh->key),
-		     (FT_R0KH_R1KH_RESP_DATA_LEN + 7) / 8,
-		     r.nonce, resp.nonce) < 0) {
-		os_memset(pmk_r0, 0, PMK_LEN);
-		return -1;
-	}
+				    f_r1kh_id, wpa_auth->conf.rkh_pos_timeout);
 
-	os_memset(pmk_r0, 0, PMK_LEN);
+	os_memset(&resp_hdr, 0, sizeof(resp_hdr));
+	resp_hdr.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
+	resp_hdr.packet_type = FT_PACKET_R0KH_R1KH_RESP;
+	os_memcpy(resp_hdr.ap_address, wpa_auth->addr, ETH_ALEN);
 
-	wpa_ft_rrb_send(wpa_auth, src_addr, (u8 *) &resp, sizeof(resp));
+	struct tlv_list resp_tlv[] = {
+		{ .type = FT_RRB_NONCE, .len = f_nonce_len,
+		  .data = f_nonce },
+		{ .type = FT_RRB_R1KH_ID, .len = f_r1kh_id_len,
+		  .data = f_r1kh_id },
+		{ .type = FT_RRB_S1KH_ID, .len = f_s1kh_id_len,
+		  .data = f_s1kh_id },
+		{ .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)
+		wpa_printf(MSG_DEBUG, "FT: No matching PMK-R0-Name found for "
+			   "PMK-R1 pull request");
+
+	ret = wpa_ft_rrb_build_r0(r1kh->key, sizeof(r1kh->key), &resp_hdr,
+				  resp_tlv, r0, f_r1kh_id, f_s1kh_id,
+				  &packet, &packet_len);
+
+	if (!ret)
+		wpa_ft_rrb_send(wpa_auth, src_addr, packet, packet_len);
+
+out:
+
+	os_free(plain); plain = NULL;
+	os_free(packet); packet = NULL;
+	os_free(sess_tlv); sess_tlv = NULL;
 
 	return 0;
 }
@@ -1730,20 +2000,21 @@  static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx)
 
 
 struct ft_pull_resp_cb_ctx {
-	struct ft_r0kh_r1kh_resp_frame *frame;
+	const u8 *s1kh_id;
+	const u8 *nonce;
+	int nak;
 	struct ft_remote_r0kh *r0kh_wildcard;
-	u8 src_addr[ETH_ALEN];
+	const u8 *src_addr;
 };
 
 static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx)
 {
 	struct ft_pull_resp_cb_ctx *info = ctx;
-	struct ft_r0kh_r1kh_resp_frame *frame = info->frame;
 
-	if (os_memcmp(frame->s1kh_id, sm->addr, ETH_ALEN) != 0)
+	if (os_memcmp(info->s1kh_id, sm->addr, ETH_ALEN) != 0)
 		return 0;
-	if (os_memcmp(frame->nonce, sm->ft_pending_pull_nonce,
-		      FT_R0KH_R1KH_PULL_NONCE_LEN) != 0)
+	if (os_memcmp(info->nonce, sm->ft_pending_pull_nonce,
+		      FT_RRB_NONCE_LEN) != 0)
 		return 0;
 	if (sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL)
 		return 0;
@@ -1751,7 +2022,7 @@  static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx)
 	wpa_printf(MSG_DEBUG, "FT: Response to a pending pull request for "
 		   MACSTR " - process from timeout", MAC2STR(sm->addr));
 
-	if (frame->pairwise == 0)
+	if (info->nak)
 		sm->ft_pending_pull_left_retries = 0;
 	eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL);
 	eloop_cancel_timeout(ft_pull_resp_cb_finish, sm, NULL);
@@ -1768,24 +2039,78 @@  static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx)
 }
 
 
+/* @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 *plain, size_t plain_len,
+			    const char *msgtype)
+{
+	const u8 *f_r1kh_id, *f_s1kh_id;
+	const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1;
+	size_t f_r1kh_id_len, f_s1kh_id_len;
+	size_t f_pmk_r1_name_len, f_pairwise_len, f_pmk_r1_len;
+	int pairwise;
+	int ret = -1;
+
+	RRB_GET(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
+	if (os_memcmp_const(f_r1kh_id, wpa_auth->conf.r1_key_holder,
+			    FT_R1KH_ID_LEN) != 0) {
+		wpa_printf(MSG_DEBUG, "FT: PMK-R1 %s did not use a matching "
+			   "R1KH-ID", msgtype);
+		goto out;
+	}
+
+
+	RRB_GET(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
+	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
+	wpa_printf(MSG_DEBUG, "FT: PMK-R1 %s", msgtype);
+	wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR " S1KH-ID=" MACSTR,
+		   MAC2STR(f_r1kh_id), MAC2STR(f_s1kh_id));
+
+	ret = -2;
+	RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16));
+
+	ret = -1;
+	RRB_GET(FT_RRB_PMK_R1_NAME, pmk_r1_name, msgtype, WPA_PMK_NAME_LEN);
+	RRB_GET(FT_RRB_PMK_R1, pmk_r1, msgtype, PMK_LEN);
+
+	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f_pmk_r1, PMK_LEN);
+	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name",
+		    f_pmk_r1_name, WPA_PMK_NAME_LEN);
+
+	pairwise = WPA_GET_LE16(f_pairwise);
+
+	if (wpa_ft_store_pmk_r1(wpa_auth, f_s1kh_id, f_pmk_r1, f_pmk_r1_name,
+				pairwise) < 0)
+		return -1;
+
+	ret = 0;
+
+out:
+	return ret;
+
+}
+
 static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *data, size_t data_len)
 {
-	struct ft_r0kh_r1kh_resp_frame f;
-	const u8 *crypt;
 	u8 *plain;
+	size_t plain_len;
 	struct ft_remote_r0kh *r0kh, *r0kh_wildcard = NULL;
-	int pairwise, res;
+	int ret, nak;
 	struct ft_pull_resp_cb_ctx ctx;
+	const u8 *f_nonce, *f_s1kh_id;
+	size_t f_nonce_len, f_s1kh_id_len;
 
 	wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull response");
 
-	if (data_len < sizeof(f))
-		return -1;
-
 	if (!wpa_auth->conf.r0kh_list)
 		return -1;
+
 	r0kh = *wpa_auth->conf.r0kh_list;
 	while (r0kh) {
 		if (os_memcmp(r0kh->addr, src_addr, ETH_ALEN) == 0)
@@ -1805,59 +2130,50 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 		return -1;
 	}
 
-	crypt = data + offsetof(struct ft_r0kh_r1kh_resp_frame, nonce);
-	os_memset(&f, 0, sizeof(f));
-	plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_resp_frame, nonce);
-	/* aes_unwrap() does not support inplace decryption, so use a temporary
-	 * buffer for the data. */
-	if (aes_unwrap(r0kh->key, sizeof(r0kh->key),
-		       (FT_R0KH_R1KH_RESP_DATA_LEN + 7) / 8,
-		       crypt, plain) < 0) {
+	if (wpa_ft_rrb_decrypt(r0kh->key, sizeof(r0kh->key),
+			       data, data_len, &plain, &plain_len) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull "
 			   "response from " MACSTR, MAC2STR(src_addr));
 		return -1;
 	}
 
-	if (os_memcmp_const(f.r1kh_id, wpa_auth->conf.r1_key_holder,
-			    FT_R1KH_ID_LEN) != 0) {
-		wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull response did not use a "
-			   "matching R1KH-ID");
-		return -1;
-	}
+	RRB_GET(FT_RRB_NONCE, nonce, "pull response", FT_RRB_NONCE_LEN);
+	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce", f_nonce, f_nonce_len);
 
-	pairwise = le_to_host16(f.pairwise);
-	if (f.pairwise == 0) {
-		res = 0;
-		wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce",
-			    f.nonce, sizeof(f.nonce));
-		wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR
-			   " S1KH-ID=" MACSTR " NACK",
-			   MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id));
-	} else {
-		wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce",
-			    f.nonce, sizeof(f.nonce));
-		wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR
-			   " S1KH-ID=" MACSTR " pairwise=0x%x",
-			   MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id), pairwise);
-		wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1 pull - PMK-R1",
-				f.pmk_r1, PMK_LEN);
-		wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR1Name",
-				f.pmk_r1_name, WPA_PMK_NAME_LEN);
+	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, "pull response", ETH_ALEN);
 
-		res = wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1,
-					  f.pmk_r1_name, pairwise);
-	}
+	ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, plain, plain_len,
+			       "pull response");
+
+	if (ret == -1)
+		goto out;
+
+	if (ret == -2) {
+		ret = 0;
+		nak = 1;
+	} else
+		nak = 0;
 
 	wpa_printf(MSG_DEBUG, "FT: Look for pending pull request");
 
-	ctx.frame = &f;
-	os_memcpy(ctx.src_addr, src_addr, ETH_ALEN);
+	os_memset(&ctx, 0, sizeof(ctx));
+
+	ctx.s1kh_id = f_s1kh_id;
+	ctx.nonce = f_nonce;
+	ctx.nak = nak;
+	ctx.src_addr = src_addr;
 	ctx.r0kh_wildcard = (r0kh == r0kh_wildcard) ? r0kh_wildcard : NULL;
 
 	wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &ctx);
-	os_memset(f.pmk_r1, 0, PMK_LEN);
 
-	return res ? 0 : -1;
+out:
+	if (plain) {
+		os_memset(plain, 0, plain_len);
+		os_free(plain);
+		plain = NULL;
+	}
+
+	return ret;
 }
 
 
@@ -1865,19 +2181,17 @@  static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *data, size_t data_len)
 {
-	struct ft_r0kh_r1kh_push_frame f;
-	const u8 *crypt;
+	int ret = -1;
 	u8 *plain;
+	size_t plain_len;
 	struct ft_remote_r0kh *r0kh, *r0kh_wildcard = NULL;
 	struct os_time now;
 	os_time_t tsend;
-	int pairwise;
+	const u8 *f_timestamp;
+	size_t f_timestamp_len;
 
 	wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 push");
 
-	if (data_len < sizeof(f))
-		return -1;
-
 	if (!wpa_auth->conf.r0kh_list)
 		return -1;
 	r0kh = *wpa_auth->conf.r0kh_list;
@@ -1899,53 +2213,36 @@  static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth,
 		return -1;
 	}
 
-	crypt = data + offsetof(struct ft_r0kh_r1kh_push_frame, timestamp);
-	os_memset(&f, 0, sizeof(f));
-	plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_push_frame,
-				       timestamp);
-	/* aes_unwrap() does not support inplace decryption, so use a temporary
-	 * buffer for the data. */
-	if (aes_unwrap(r0kh->key, sizeof(r0kh->key),
-		       (FT_R0KH_R1KH_PUSH_DATA_LEN + 7) / 8,
-		       crypt, plain) < 0) {
+	if (wpa_ft_rrb_decrypt(r0kh->key, sizeof(r0kh->key),
+			       data, data_len, &plain, &plain_len) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 push from "
 			   MACSTR, MAC2STR(src_addr));
 		return -1;
 	}
 
+	RRB_GET(FT_RRB_TIMESTAMP, timestamp, "push", sizeof(le32));
 	os_get_time(&now);
-	tsend = WPA_GET_LE32(f.timestamp);
+	tsend = WPA_GET_LE32(f_timestamp);
 	if ((now.sec > tsend && now.sec - tsend > 60) ||
 	    (now.sec < tsend && tsend - now.sec > 60)) {
 		wpa_printf(MSG_DEBUG, "FT: PMK-R1 push did not have a valid "
 			   "timestamp: sender time %d own time %d\n",
 			   (int) tsend, (int) now.sec);
-		return -1;
+		goto out;
 	}
 
-	if (os_memcmp_const(f.r1kh_id, wpa_auth->conf.r1_key_holder,
-			    FT_R1KH_ID_LEN) != 0) {
-		wpa_printf(MSG_DEBUG, "FT: PMK-R1 push did not use a matching "
-			   "R1KH-ID (received " MACSTR " own " MACSTR ")",
-			   MAC2STR(f.r1kh_id),
-			   MAC2STR(wpa_auth->conf.r1_key_holder));
-		return -1;
-	}
+	if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, plain, plain_len,
+			     "push") < 0)
+		goto out;
 
-	pairwise = le_to_host16(f.pairwise);
-	wpa_printf(MSG_DEBUG, "FT: PMK-R1 push - R1KH-ID=" MACSTR " S1KH-ID="
-		   MACSTR " pairwise=0x%x",
-		   MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id), pairwise);
-	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1 push - PMK-R1",
-			f.pmk_r1, PMK_LEN);
-	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 push - PMKR1Name",
-			f.pmk_r1_name, WPA_PMK_NAME_LEN);
+	ret = 0;
+out:
 
-	wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name,
-			    pairwise);
-	os_memset(f.pmk_r1, 0, PMK_LEN);
+	os_memset(plain, 0, plain_len);
+	os_free(plain);
+	plain = NULL;
 
-	return 0;
+	return ret;
 }
 
 
@@ -2077,44 +2374,43 @@  int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 				   struct wpa_ft_pmk_r0_sa *pmk_r0,
 				   struct ft_remote_r1kh *r1kh,
-				   const u8 *s1kh_id, int pairwise)
+				   const u8 *s1kh_id)
 {
-	struct ft_r0kh_r1kh_push_frame frame, f;
+	struct ft_rrb_frame push_hdr;
 	struct os_time now;
-	const u8 *plain;
-	u8 *crypt;
-
-	os_memset(&frame, 0, sizeof(frame));
-	frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	frame.packet_type = FT_PACKET_R0KH_R1KH_PUSH;
-	frame.data_length = host_to_le16(FT_R0KH_R1KH_PUSH_DATA_LEN);
-	os_memcpy(frame.ap_address, wpa_auth->addr, ETH_ALEN);
-
-	/* aes_wrap() does not support inplace encryption, so use a temporary
-	 * buffer for the data. */
-	os_memcpy(f.r1kh_id, r1kh->id, FT_R1KH_ID_LEN);
-	os_memcpy(f.s1kh_id, s1kh_id, ETH_ALEN);
-	os_memcpy(f.pmk_r0_name, pmk_r0->pmk_r0_name, WPA_PMK_NAME_LEN);
-	wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_name, r1kh->id,
-			  s1kh_id, f.pmk_r1, f.pmk_r1_name);
-	wpa_printf(MSG_DEBUG, "FT: R1KH-ID " MACSTR, MAC2STR(r1kh->id));
-	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f.pmk_r1, PMK_LEN);
-	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", f.pmk_r1_name,
-		    WPA_PMK_NAME_LEN);
+	u8 *packet;
+	size_t packet_len;
+	u8 f_timestamp[sizeof(le32)];
+
+	os_memset(&push_hdr, 0, sizeof(push_hdr));
+	push_hdr.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
+	push_hdr.packet_type = FT_PACKET_R0KH_R1KH_PUSH;
+	os_memcpy(push_hdr.ap_address, wpa_auth->addr, ETH_ALEN);
+
 	os_get_time(&now);
-	WPA_PUT_LE32(f.timestamp, now.sec);
-	f.pairwise = host_to_le16(pairwise);
-	os_memset(f.pad, 0, sizeof(f.pad));
-	plain = ((const u8 *) &f) + offsetof(struct ft_r0kh_r1kh_push_frame,
-					     timestamp);
-	crypt = ((u8 *) &frame) + offsetof(struct ft_r0kh_r1kh_push_frame,
-					   timestamp);
-	if (aes_wrap(r1kh->key, sizeof(r1kh->key),
-		     (FT_R0KH_R1KH_PUSH_DATA_LEN + 7) / 8,
-		     plain, crypt) < 0)
+	WPA_PUT_LE32(f_timestamp, now.sec);
+
+	struct tlv_list push_tlv[] = {
+		{ .type = FT_RRB_TIMESTAMP, .len = sizeof(f_timestamp),
+		  .data = f_timestamp },
+		{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
+		  .data = r1kh->id },
+		{ .type = FT_RRB_S1KH_ID, .len = ETH_ALEN,
+		  .data = s1kh_id },
+		{ .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN,
+		  .data = pmk_r0->pmk_r0_name },
+		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
+	};
+
+	if (wpa_ft_rrb_build_r0(r1kh->key, sizeof(r1kh->key), &push_hdr,
+				push_tlv, pmk_r0, r1kh->id, s1kh_id,
+				&packet, &packet_len) < 0)
 		return;
 
-	wpa_ft_rrb_send(wpa_auth, r1kh->addr, (u8 *) &frame, sizeof(frame));
+	wpa_ft_rrb_send(wpa_auth, r1kh->addr, packet, packet_len);
+
+	os_free(packet);
+	packet = NULL;
 }
 
 
@@ -2146,7 +2442,7 @@  void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 		if (is_zero_ether_addr(r1kh->addr) ||
 		    is_zero_ether_addr(r1kh->id))
 			continue;
-		wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr, r0->pairwise);
+		wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr);
 	}
 }
 
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index c634c32..eb5d03e 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -128,7 +128,7 @@  struct wpa_state_machine {
 			      const u8 *ies, size_t ies_len);
 	void *ft_pending_cb_ctx;
 	struct wpabuf *ft_pending_req_ies;
-	u8 ft_pending_pull_nonce[FT_R0KH_R1KH_PULL_NONCE_LEN];
+	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;