[PATCHv3 2/4] FT: new rrb message format
Michael Braun
michael-dev at fami-braun.de
Fri Oct 21 04:11:50 PDT 2016
Convert FT RRB into new TLV based format.
Use AES+HMAC-SHA256 in Encrypt-Then-Mac mode as AED cipher.
This breaks backward compatiblity.
Signed-off-by: Michael Braun <michael-dev at fami-braun.de>
--
v3:
- use new extended OUI ethertype
- use encrypt-then-mac with aes+sha256 as AED
- use different keys for encryption and mac using hmac_sha256_kdf
---
hostapd/Makefile | 2 +
hostapd/config_file.c | 23 +-
src/ap/wpa_auth.h | 101 ++++----
src/ap/wpa_auth_ft.c | 695 +++++++++++++++++++++++++++++++++++++-------------
src/ap/wpa_auth_i.h | 2 +-
5 files changed, 601 insertions(+), 222 deletions(-)
diff --git a/hostapd/Makefile b/hostapd/Makefile
index a667bfb..87ec1af 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -296,6 +296,8 @@ NEED_SHA256=y
NEED_AES_OMAC1=y
NEED_AES_UNWRAP=y
NEED_ETH_P_OUI=y
+NEED_SHA256=y
+NEED_HMAC_SHA256_KDF=y
endif
ifdef NEED_ETH_P_OUI
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 8e7bcc7..2644b1e 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -20,6 +20,7 @@
#include "ap/wpa_auth.h"
#include "ap/ap_config.h"
#include "config_file.h"
+#include "crypto/sha256.h"
#ifndef CONFIG_NO_RADIUS
@@ -992,6 +993,24 @@ static int hostapd_config_tx_queue(struct hostapd_config *conf,
#ifdef CONFIG_IEEE80211R
+static int rkh_dervice_key(struct ft_remote_key *rkey, const char *pos)
+{
+ u8 key[16];
+
+ if (hexstr2bin(pos, key, sizeof(key)))
+ return -1;
+
+ if (hmac_sha256_kdf(key, sizeof(key), "FT ENCRYPT", NULL, 0,
+ rkey->encrypt, sizeof(rkey->encrypt)) < 0)
+ return -1;
+
+ if (hmac_sha256_kdf(key, sizeof(key), "FT MAC", NULL, 0,
+ rkey->mac, sizeof(rkey->mac)) < 0)
+ wpa_printf(MSG_ERROR, "hmac_sha256_kdf failed");
+
+ return 0;
+}
+
static int add_r0kh(struct hostapd_bss_config *bss, char *value)
{
struct ft_remote_r0kh *r0kh;
@@ -1025,7 +1044,7 @@ static int add_r0kh(struct hostapd_bss_config *bss, char *value)
os_memcpy(r0kh->id, pos, r0kh->id_len);
pos = next;
- if (hexstr2bin(pos, r0kh->key, sizeof(r0kh->key))) {
+ if (rkh_dervice_key(&r0kh->key, pos) < 0) {
wpa_printf(MSG_ERROR, "Invalid R0KH key: '%s'", pos);
os_free(r0kh);
return -1;
@@ -1070,7 +1089,7 @@ static int add_r1kh(struct hostapd_bss_config *bss, char *value)
}
pos = next;
- if (hexstr2bin(pos, r1kh->key, sizeof(r1kh->key))) {
+ if (rkh_dervice_key(&r1kh->key, pos) < 0) {
wpa_printf(MSG_ERROR, "Invalid R1KH key: '%s'", pos);
os_free(r1kh);
return -1;
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index b56a69b..2041813 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -42,54 +42,58 @@ struct ft_rrb_frame {
#define FT_PACKET_R0KH_R1KH_RESP 0x02
#define FT_PACKET_R0KH_R1KH_PUSH 0x03
-#define FT_R0KH_R1KH_PULL_NONCE_LEN 16
-#define FT_R0KH_R1KH_PULL_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
- 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 nonce[FT_R0KH_R1KH_PULL_NONCE_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;
+/* new packet format */
+
+/* packet layout
+ * | IEEE 802 extended OUI ethertype frame header |
+ * | multiple of of struct ft_rrbv1_tlv |
+ * | all-zero padding for encryption (blocksize) | 8 byte extra for keywrap |
+ * | SHA256-HMAC of size SHA256_MAC_LEN |
+ * where
+ * packet_type = FT_PACKET_R0KH_R1KH*
+ * action_length = size of all struct ft_rrbv1_tlv (without final padding)
+ * (aka plaintext length)
+ * encryption: covers all struct ft_rrbv1_tlv
+ */
-#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 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;
- 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_LEN 16
+
+#define FT_RRB_LAST_EMPTY 0 /* placeholder or padding */
+
+#define FT_RRB_NONCE 1 /* size FT_RRB_NONCE_LEN */
+#define FT_RRB_TIMESTAMP 2 /* le32 unix seconds */
+
+#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 */
+
+#define FT_RRB_PAIRWISE 10 /* le16 */
-#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 {
- /* 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];
+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, 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 */
@@ -103,12 +107,17 @@ struct rsn_pmksa_cache_entry;
struct eapol_state_machine;
+struct ft_remote_key {
+ u8 encrypt[16]; /* encryption key */
+ u8 mac[16]; /* authentication key */
+};
+
struct ft_remote_r0kh {
struct ft_remote_r0kh *next;
u8 addr[ETH_ALEN];
u8 id[FT_R0KH_ID_MAX_LEN];
size_t id_len;
- u8 key[16];
+ struct ft_remote_key key;
};
@@ -116,7 +125,7 @@ struct ft_remote_r1kh {
struct ft_remote_r1kh *next;
u8 addr[ETH_ALEN];
u8 id[FT_R1KH_ID_LEN];
- u8 key[16];
+ struct ft_remote_key key;
};
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index fc96675..a620136 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -15,6 +15,7 @@
#include "common/ieee802_11_common.h"
#include "crypto/aes_wrap.h"
#include "crypto/random.h"
+#include "crypto/sha256.h"
#include "ap_config.h"
#include "ieee802_11.h"
#include "wmm.h"
@@ -30,6 +31,279 @@ static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
size_t resp_ies_len);
+/**
+ * decrypt message
+ * @data full packet
+ * @key encryption and mac 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 struct ft_remote_key *key,
+ const u8 *data, const size_t data_len,
+ u8 **plain, size_t *plain_size)
+{
+ int len, aes_len;
+ u8 mac[SHA256_MAC_LEN];
+
+ *plain = NULL;
+
+ /* extra aes wrap bytes + sha256 length */
+ if (data_len < 8 + SHA256_MAC_LEN)
+ goto err;
+
+ aes_len = data_len - SHA256_MAC_LEN;
+
+ /* len of plaintext + all-zero padding (up to 8 bytes)
+ * rember: tlv type 0x0000 is reserved or padding and termination */
+ len = aes_len - 8;
+
+ /* length a multiple of blocksize? */
+ if (len % 8 != 0)
+ goto err;
+
+ if (hmac_sha256(key->mac, sizeof(key->mac), data, aes_len, mac))
+ goto err;
+
+ if (os_memcmp(mac, data + aes_len, SHA256_MAC_LEN) != 0)
+ goto err;
+
+ *plain = os_zalloc(len);
+ if (!*plain)
+ goto err;
+
+ if (aes_unwrap(key->encrypt, sizeof(key->encrypt), len / 8, data,
+ *plain))
+ goto err;
+
+ *plain_size = len;
+ return 0;
+err:
+ os_free(*plain);
+ *plain = NULL;
+ *plain_size = 0;
+
+ 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)
+ break;
+
+ if (f->type == type16) {
+ *tlv_len = len;
+ *tlv_data = plain;
+ return 0;
+ }
+
+ left -= len;
+ plain += len;
+ }
+
+ return -1;
+}
+
+
+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_lin(const struct tlv_list *tlvs1,
+ const struct tlv_list *tlvs2,
+ u8 **plain, size_t *plain_len)
+{
+ u8 *pos, *endpos;
+ size_t tlv_len, pad_len;
+
+ 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_len = tlv_len + pad_len;
+ *plain = os_zalloc(*plain_len);
+ if (*plain == NULL)
+ goto err;
+
+ 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");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ os_free(*plain);
+ *plain = NULL;
+ *plain_len = 0;
+ return -1;
+}
+
+static int wpa_ft_rrb_encrypt(const struct ft_remote_key *key,
+ const u8 *plain, size_t plain_len,
+ u8 **packet, size_t *packet_len)
+{
+ int aes_len;
+
+ aes_len = plain_len + 8;
+ *packet_len = aes_len + SHA256_MAC_LEN;
+ *packet = os_zalloc(*packet_len);
+
+ if (*packet == NULL)
+ goto err;
+
+ if (aes_wrap(key->encrypt, sizeof(key->encrypt), plain_len / 8, plain,
+ *packet))
+ goto err;
+
+ if (hmac_sha256(key->mac, sizeof(key->mac), *packet, aes_len,
+ *packet + aes_len) < 0)
+ goto err;
+
+ return 0;
+
+err:
+ os_free(*packet);
+ *packet = NULL;
+ *packet_len = 0;
+
+ return -1;
+}
+
+/**
+ * encrypt message
+ * @frame ft_rrb_frame
+ * @key encryption and mac key
+ * @packet pointer to store pointer for ciphertext
+ * (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_build(const struct ft_remote_key *key,
+ const struct tlv_list *tlvs1,
+ const struct tlv_list *tlvs2,
+ u8 **packet, size_t *packet_len)
+{
+ u8 *plain;
+ size_t plain_len;
+ int ret = -1;
+
+ if (wpa_ft_rrb_lin(tlvs1, tlvs2, &plain, &plain_len) < 0)
+ goto out;
+
+ if (wpa_ft_rrb_encrypt(key, plain, plain_len, packet,
+ packet_len) < 0)
+ goto out;
+
+ ret = 0;
+
+out:
+ os_free(plain);
+
+ if (ret) {
+ os_free(*packet);
+ *packet = NULL;
+ *packet_len = 0;
+ }
+
+ return ret;
+}
+
+
+#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)
{
@@ -252,7 +526,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;
@@ -262,15 +536,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;
}
@@ -330,7 +603,8 @@ static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
const u8 *pmk_r0_name)
{
struct ft_remote_r0kh *r0kh;
- struct ft_r0kh_r1kh_pull_frame frame, f;
+ u8 *packet = NULL;
+ size_t packet_len;
r0kh = sm->wpa_auth->conf.r0kh_list;
while (r0kh) {
@@ -349,25 +623,30 @@ 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));
- /* aes_wrap() does not support inplace encryption, so use a temporary
- * buffer for the data. */
- if (random_get_bytes(f.nonce, FT_R0KH_R1KH_PULL_NONCE_LEN)) {
+ if (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;
}
- os_memcpy(sm->ft_pending_pull_nonce, f.nonce,
- FT_R0KH_R1KH_PULL_NONCE_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)
+ 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, req_tlv, NULL, &packet,
+ &packet_len) < 0) {
return -1;
+ }
wpabuf_free(sm->ft_pending_req_ies);
sm->ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len);
@@ -375,7 +654,10 @@ static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
return -1;
wpa_ft_rrb_oui_send(sm->wpa_auth, r0kh->addr, FT_PACKET_R0KH_R1KH_PULL,
- (u8 *) &frame, sizeof(frame));
+ packet, packet_len);
+
+ os_free(packet);
+ packet = NULL;
return 0;
}
@@ -1419,23 +1701,57 @@ static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
}
+static int wpa_ft_rrb_build_r0(const struct ft_remote_key *key,
+ 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;
+
+ 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(key, 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;
- struct ft_r0kh_r1kh_resp_frame resp, r;
- u8 pmk_r0[PMK_LEN];
- int pairwise;
+ 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;
wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull");
- if (data_len < sizeof(f))
- return -1;
-
r1kh = wpa_auth->conf.r1kh_list;
while (r1kh) {
if (os_memcmp(r1kh->addr, src_addr, ETH_ALEN) == 0)
@@ -1449,58 +1765,61 @@ 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, 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;
}
+ 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));
-
- os_memset(&resp, 0, sizeof(resp));
- /* 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");
- return -1;
- }
+ MACSTR, MAC2STR(f_r1kh_id), MAC2STR(f_s1kh_id));
- 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));
+ 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 (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;
+ 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");
+ goto out;
}
- os_memset(pmk_r0, 0, PMK_LEN);
+ ret = wpa_ft_rrb_build_r0(&r1kh->key, resp_tlv, r0, f_r1kh_id,
+ f_s1kh_id, &packet, &packet_len);
+
+ if (!ret)
+ wpa_ft_rrb_oui_send(wpa_auth, src_addr,
+ FT_PACKET_R0KH_R1KH_RESP, packet,
+ packet_len);
- wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_RESP,
- (u8 *) &resp, sizeof(resp));
+out:
+ os_free(plain); plain = NULL;
+ os_free(packet); packet = NULL;
+ os_free(sess_tlv); sess_tlv = NULL;
return 0;
}
@@ -1532,14 +1851,19 @@ static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx)
}
+struct ft_pull_resp_cb_ctx {
+ const u8 *s1kh_id;
+ const u8 *nonce;
+};
+
static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx)
{
- struct ft_r0kh_r1kh_resp_frame *frame = ctx;
+ struct ft_pull_resp_cb_ctx *info = ctx;
- 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;
@@ -1551,21 +1875,71 @@ static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx)
}
+/* @returns 0 on success
+ * -1 on error
+ */
+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));
+
+ RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16));
+ 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;
- int pairwise, res;
+ int ret;
+ 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;
-
r0kh = wpa_auth->conf.r0kh_list;
while (r0kh) {
if (os_memcmp(r0kh->addr, src_addr, ETH_ALEN) == 0)
@@ -1579,44 +1953,41 @@ 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, 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);
+
+ RRB_GET(FT_RRB_S1KH_ID, s1kh_id, "pull response", ETH_ALEN);
+
+ ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, plain, plain_len,
+ "pull response");
+
+ if (ret == -1)
+ goto out;
- pairwise = le_to_host16(f.pairwise);
- 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);
-
- res = wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name,
- pairwise);
wpa_printf(MSG_DEBUG, "FT: Look for pending pull request");
- wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &f);
- os_memset(f.pmk_r1, 0, PMK_LEN);
- return res ? 0 : -1;
+ os_memset(&ctx, 0, sizeof(ctx));
+
+ ctx.s1kh_id = f_s1kh_id;
+ ctx.nonce = f_nonce;
+
+ wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &ctx);
+
+out:
+ if (plain) {
+ os_memset(plain, 0, plain_len);
+ os_free(plain);
+ plain = NULL;
+ }
+
+ return ret;
}
@@ -1624,19 +1995,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;
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;
-
r0kh = wpa_auth->conf.r0kh_list;
while (r0kh) {
if (os_memcmp(r0kh->addr, src_addr, ETH_ALEN) == 0)
@@ -1650,53 +2019,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, 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;
}
@@ -1850,40 +2202,37 @@ void wpa_ft_rrb_oui_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 os_time now;
- const u8 *plain;
- u8 *crypt;
-
- os_memset(&frame, 0, sizeof(frame));
- /* 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_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, push_tlv, pmk_r0, r1kh->id,
+ s1kh_id, &packet, &packet_len) < 0)
return;
wpa_ft_rrb_oui_send(wpa_auth, r1kh->addr, FT_PACKET_R0KH_R1KH_PUSH,
- (u8 *) &frame, sizeof(frame));
+ packet, packet_len);
+
+ os_free(packet);
+ packet = NULL;
}
@@ -1911,7 +2260,7 @@ void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
r1kh = wpa_auth->conf.r1kh_list;
while (r1kh) {
- wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr, r0->pairwise);
+ wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr);
r1kh = r1kh->next;
}
}
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 72b7eb3..90d7645 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];
#endif /* CONFIG_IEEE80211R */
--
2.1.4
More information about the Hostap
mailing list