Convert FT RRB into new TLV based format. Use AES+SIV as AED cipher. This needs at least 32 byte long keys. These can be provided either by a config filee change or letting a KDF derive the 32 byte key used from the 16 byte key given. This breaks backward compatiblity. Signed-off-by: Michael Braun <michael-dev@xxxxxxxxxxxxx> -- v5: - configuration file fallback for short keys - add authenticated-only packet area v4: - AES-SIV 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 | 3 + hostapd/config_file.c | 24 +- hostapd/hostapd.conf | 12 +- src/ap/hostapd.h | 2 + src/ap/wpa_auth.h | 99 ++-- src/ap/wpa_auth_ft.c | 1052 ++++++++++++++++++++++++++++++----------- src/ap/wpa_auth_glue.c | 2 +- src/ap/wpa_auth_i.h | 2 +- tests/hwsim/test_ap_ft.py | 40 +- tests/hwsim/test_hapd_ctrl.py | 12 +- 10 files changed, 903 insertions(+), 345 deletions(-) diff --git a/hostapd/Makefile b/hostapd/Makefile index 298019e..ab144e2 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -295,7 +295,10 @@ OBJS += ../src/ap/wpa_auth_ft.o NEED_SHA256=y NEED_AES_OMAC1=y NEED_AES_UNWRAP=y +NEED_AES_SIV=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 7b43806..2070a74 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 @@ -1000,6 +1001,25 @@ static int hostapd_config_tx_queue(struct hostapd_config *conf, #ifdef CONFIG_IEEE80211R_AP +static int rkh_dervice_key(const char *pos, u8 *key, size_t key_len) +{ + u8 oldkey[16]; + + if (!hexstr2bin(pos, key, key_len)) + return 0; + + /* try to use old short key for backward compatibility */ + if (hexstr2bin(pos, oldkey, sizeof(oldkey))) + return -1; + + if (hmac_sha256_kdf(oldkey, sizeof(oldkey), "FT OLDKEY", NULL, 0, + key, key_len) < 0) + return -1; + + return 0; +} + + static int add_r0kh(struct hostapd_bss_config *bss, char *value) { struct ft_remote_r0kh *r0kh; @@ -1033,7 +1053,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(pos, r0kh->key, sizeof(r0kh->key)) < 0) { wpa_printf(MSG_ERROR, "Invalid R0KH key: '%s'", pos); os_free(r0kh); return -1; @@ -1078,7 +1098,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(pos, r1kh->key, sizeof(r1kh->key)) < 0) { wpa_printf(MSG_ERROR, "Invalid R1KH key: '%s'", pos); os_free(r1kh); return -1; diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 18c330b..7e61d1c 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1452,21 +1452,21 @@ own_ip_addr=127.0.0.1 #reassociation_deadline=1000 # List of R0KHs in the same Mobility Domain -# format: <MAC address> <NAS Identifier> <128-bit key as hex string> +# format: <MAC address> <NAS Identifier> <256-bit key as hex string> # This list is used to map R0KH-ID (NAS Identifier) to a destination MAC # address when requesting PMK-R1 key from the R0KH that the STA used during the # Initial Mobility Domain Association. -#r0kh=02:01:02:03:04:05 r0kh-1.example.com 000102030405060708090a0b0c0d0e0f -#r0kh=02:01:02:03:04:06 r0kh-2.example.com 00112233445566778899aabbccddeeff +#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. # List of R1KHs in the same Mobility Domain -# format: <MAC address> <R1KH-ID> <128-bit key as hex string> +# format: <MAC address> <R1KH-ID> <256-bit key as hex string> # This list is used to map R1KH-ID to a destination MAC address when sending # PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD # that can request PMK-R1 keys. -#r1kh=02:01:02:03:04:05 02:11:22:33:44:55 000102030405060708090a0b0c0d0e0f -#r1kh=02:01:02:03:04:06 02:11:22:33:44:66 00112233445566778899aabbccddeeff +#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. # Whether PMK-R1 push is enabled at R0KH diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 452ca1e..e7c65f7 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -195,6 +195,8 @@ struct hostapd_data { struct eth_p_oui_ctx *oui_pull; struct eth_p_oui_ctx *oui_resp; struct eth_p_oui_ctx *oui_push; + struct eth_p_oui_ctx *oui_sreq; + struct eth_p_oui_ctx *oui_sresp; #endif /* CONFIG_IEEE80211R_AP */ struct wps_context *wps; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index 22c2093..9bf5884 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -44,54 +44,61 @@ 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; +/* packet layout + * IEEE 802 extended OUI ethertype frame header + * u16 authlen + * multiple of of struct ft_rrb_tlv (authenticated only, length = authlen) + * multiple of of struct ft_rrb_tlv (aes-siv encrypted, all-zero padded to + * blocksize, aes-siv needs an extra + * blocksize length) + */ -#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 2 /* size FT_RRB_NONCE_LEN */ +#define FT_RRB_TIMESTAMP 3 /* le32 unix seconds */ + +#define FT_RRB_R0KH_ID 4 /* FT_R0KH_ID_MAX_LEN */ +#define FT_RRB_R1KH_ID 5 /* FT_R1KH_ID_LEN */ +#define FT_RRB_S1KH_ID 6 /* ETH_ALEN */ + +#define FT_RRB_PMK_R0_NAME 7 /* WPA_PMK_NAME_LEN */ +#define FT_RRB_PMK_R0 8 /* PMK_LEN */ +#define FT_RRB_PMK_R1_NAME 9 /* WPA_PMK_NAME_LEN */ +#define FT_RRB_PMK_R1 10 /* PMK_LEN */ -#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]; +#define FT_RRB_PAIRWISE 11 /* le16 */ + +struct ft_rrb_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: + * auth: + * required: NONCE, R0KH_ID, R1KH_ID + * encrypted: + * required: PMK_R0_NAME, S1KH_ID + * + * response frame TLVs: + * auth: + * required: NONCE, R0KH_ID, R1KH_ID + * encrypted: + * required: S1KH_ID, session TLVs + * + * push frame TLVs: + * auth: + * required: TIMESTAMP, R0KH_ID, R1KH_ID + * encrypted: + * required: S1KH_ID, PMK_R0_NAME, session TLVs + */ + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ @@ -110,7 +117,7 @@ struct ft_remote_r0kh { u8 addr[ETH_ALEN]; u8 id[FT_R0KH_ID_MAX_LEN]; size_t id_len; - u8 key[16]; + u8 key[32]; }; @@ -118,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]; + u8 key[32]; }; diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index 1d4c519..d0e58e3 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -13,6 +13,8 @@ #include "utils/list.h" #include "common/ieee802_11_defs.h" #include "common/ieee802_11_common.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" #include "crypto/aes_wrap.h" #include "crypto/random.h" #include "ap_config.h" @@ -24,11 +26,379 @@ #ifdef CONFIG_IEEE80211R_AP +static const char *ft_rrb_ad = "hostapd FT RRB"; + 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); +struct tlv_list { + int type; + int len; + const u8 *data; +}; + + +/** + * 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 u8 *key, const size_t key_len, + const u8 *enc, const size_t enc_len, + const u8 *auth, const size_t auth_len, + const u8 *src_addr, const u8 type, + u8 **plain, size_t *plain_size) +{ + size_t len; + + wpa_hexdump_key(MSG_DEBUG, "FT: decrypt using key", key, key_len); + + if (!key) { /* skip decryption */ + *plain = os_memdup(enc, enc_len); + if (enc_len > 0 && !*plain) + goto err; + + *plain_size = enc_len; + + return 0; + } + + *plain = NULL; + + /* siv overhead */ + if (enc_len < AES_BLOCK_SIZE) + goto err; + + /* len of plaintext + AES-SIV overhead + * remember: tlv type 0x0000 is reserved or padding and termination */ + len = enc_len - AES_BLOCK_SIZE; + + /* length a multiple of blocksize? */ + if (len % AES_BLOCK_SIZE != 0) + goto err; + + *plain = os_zalloc(enc_len); + if (!*plain) + goto err; + + const u8 *ad[4] = { src_addr, auth, &type, (u8 *) ft_rrb_ad }; + size_t ad_len[4] = { ETH_ALEN, auth_len, sizeof(type), + os_strlen(ft_rrb_ad) }; + if (aes_siv_decrypt(key, key_len, enc, enc_len, 4, ad, ad_len, + *plain) < 0) + goto err; + + *plain_size = len; + return 0; +err: + os_free(*plain); + *plain = NULL; + *plain_size = 0; + + wpa_printf(MSG_ERROR, "FT: Failed to decrypt"); + + 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_rrb_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_rrb_tlv *) plain; + + left -= sizeof(*f); + plain += sizeof(*f); + len = le_to_host16(f->len); + + if (left < len) { + wpa_printf(MSG_DEBUG, "FT: RRB message truncated"); + break; + } + + if (f->type == type16) { + *tlv_len = len; + *tlv_data = plain; + return 0; + } + + left -= len; + plain += len; + } + + return -1; +} + + +static void wpa_ft_rrb_dump(const u8 *plain, const size_t plain_len) +{ + struct ft_rrb_tlv *f; + size_t left; + size_t len; + + left = plain_len; + + wpa_printf(MSG_DEBUG, "FT: RRB dump message"); + while (left >= sizeof(*f)) { + f = (struct ft_rrb_tlv *) plain; + + left -= sizeof(*f); + plain += sizeof(*f); + len = le_to_host16(f->len); + + wpa_printf(MSG_DEBUG, "FT: RRB TLV type = %d, len = %zu", + le_to_host16(f->type), len); + + if (left < len) { + wpa_printf(MSG_DEBUG, "FT: RRB message truncated: left" + " %zu bytes, need %zu", left, len); + break; + } + + wpa_hexdump(MSG_DEBUG, "FT: RRB TLV data", plain, len); + + left -= len; + plain += len; + } + + if (left > 0) + wpa_hexdump(MSG_DEBUG, "FT: RRB TLV padding", plain, left); + + wpa_printf(MSG_DEBUG, "FT: RRB dump message end"); +} + + +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_rrb_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_rrb_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_rrb_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, + int pad, 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 = 0; + if (pad && (tlv_len % AES_BLOCK_SIZE != 0)) + pad_len = AES_BLOCK_SIZE - (tlv_len % AES_BLOCK_SIZE); + + *plain_len = tlv_len + pad_len; + *plain = os_zalloc(*plain_len); + if (*plain == NULL) { + wpa_printf(MSG_ERROR, "FT: Failed to allocate plaintext"); + 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 u8 *key, const size_t key_len, + const u8 *plain, const size_t plain_len, + const u8 *auth, const size_t auth_len, + const u8 *src_addr, const u8 type, u8 *enc) +{ + const u8 *ad[4] = { src_addr, auth, &type, (u8 *) ft_rrb_ad }; + size_t ad_len[4] = { ETH_ALEN, auth_len, sizeof(type), + os_strlen(ft_rrb_ad) }; + + wpa_hexdump_key(MSG_DEBUG, "FT: encrypt using key", key, key_len); + + if (!key) + /* encryption not needed, return plaintext as packet */ + os_memcpy(enc, plain, plain_len); + else if (aes_siv_encrypt(key, key_len, plain, plain_len, 4, ad, + ad_len, enc) < 0) { + wpa_printf(MSG_ERROR, "FT: Failed to encrypt RRB-OUI message"); + return -1; + } + + return 0; +} + + +/** + * 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 u8 *key, const size_t key_len, + const struct tlv_list *tlvs_enc0, + const struct tlv_list *tlvs_enc1, + const struct tlv_list *tlvs_auth, + const u8 *src_addr, const u8 type, + u8 **packet, size_t *packet_len) +{ + u8 *plain = NULL, *auth = NULL, *enc; + size_t plain_len, auth_len; + u16 *head; + int ret = -1; + + if (wpa_ft_rrb_lin(tlvs_enc0, tlvs_enc1, key ? 1 : 0, + &plain, &plain_len) < 0) + goto out; + + if (wpa_ft_rrb_lin(tlvs_auth, NULL, 0, &auth, &auth_len) < 0) + goto out; + + *packet_len = sizeof(*head) + auth_len + plain_len; + if (key) + *packet_len += AES_BLOCK_SIZE; + *packet = os_zalloc(*packet_len); + + if (*packet == NULL) + goto out; + + enc = *packet + sizeof(*head) + auth_len; + if (wpa_ft_rrb_encrypt(key, key_len, plain, plain_len, auth, + auth_len, src_addr, type, enc) < 0) + goto out; + + os_memcpy(*packet + sizeof(*head), auth, auth_len); + + head = (u16 *) *packet; + *head = host_to_le16(auth_len); + + ret = 0; + +out: + os_free(plain); + os_free(auth); + + if (ret) { + wpa_printf(MSG_ERROR, "FT: Failed to build RRB-OUI message"); + os_free(*packet); + *packet = NULL; + *packet_len = 0; + } + + return ret; +} + + +#define RRB_GET_SRC(srcfield, type, field, txt, checklength) do { \ + if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \ + &f_##field##_len, &f_##field) < 0 || \ + (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \ + wpa_printf(MSG_DEBUG, "FT: Missing required " #field " in " \ + "%s from " MACSTR, txt, MAC2STR(src_addr)); \ + wpa_ft_rrb_dump(srcfield, srcfield##_len); \ + goto out; \ + } \ +} while (0) + +#define RRB_GET(type, field, txt, checklength) \ + RRB_GET_SRC(plain, type, field, txt, checklength) +#define RRB_GET_AUTH(type, field, txt, checklength) \ + RRB_GET_SRC(auth, type, field, txt, checklength) + +#define RRB_GET_OPTIONAL_SRC(srcfield, type, field, txt, checklength) do { \ + if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \ + &f_##field##_len, &f_##field) < 0 || \ + (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \ + wpa_printf(MSG_DEBUG, "FT: Missing optional " #field " in " \ + "%s from " MACSTR, txt, MAC2STR(src_addr)); \ + f_##field##_len = 0; \ + f_##field = NULL; \ + } \ +} while (0) + +#define RRB_GET_OPTIONAL(type, field, txt, checklength) \ + RRB_GET_OPTIONAL_SRC(plain, type, field, txt, checklength) +#define RRB_GET_OPTIONAL_AUTH(type, field, txt, checklength) \ + RRB_GET_OPTIONAL_SRC(auth, type, field, txt, checklength) static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst, const u8 *data, size_t data_len) @@ -252,7 +622,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 +632,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; } @@ -325,48 +694,125 @@ static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, } +static void wpa_ft_rrb_lookup_r0kh(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, const u8 *f_r0kh_id, + size_t f_r0kh_id_len, + struct ft_remote_r0kh **r0kh_out) +{ + struct ft_remote_r0kh *r0kh; + + r0kh = wpa_auth->conf.r0kh_list; + for (; r0kh; r0kh = r0kh->next) { + if (src_addr && os_memcmp(r0kh->addr, src_addr, ETH_ALEN)) + continue; + if (f_r0kh_id && (r0kh->id_len != f_r0kh_id_len || + os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len))) + continue; + break; + } + + if (!r0kh) + wpa_printf(MSG_DEBUG, "FT: No matching R0KH found"); + + *r0kh_out = r0kh; +} + + +static void wpa_ft_rrb_lookup_r1kh(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, const u8 *f_r1kh_id, + struct ft_remote_r1kh **r1kh_out) +{ + struct ft_remote_r1kh *r1kh; + + r1kh = wpa_auth->conf.r1kh_list; + for (; r1kh; r1kh = r1kh->next) { + if (src_addr && os_memcmp(r1kh->addr, src_addr, ETH_ALEN)) + continue; + if (f_r1kh_id && + os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN)) + continue; + break; + } + + if (!r1kh) + wpa_printf(MSG_DEBUG, "FT: No matching R1KH found"); + + *r1kh_out = r1kh; +} + + +static int wpa_ft_rrb_check_r0kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r0kh_id, size_t f_r0kh_id_len) +{ + 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; + + return 0; +} + + +static int wpa_ft_rrb_check_r1kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r1kh_id) +{ + if (os_memcmp_const(f_r1kh_id, wpa_auth->conf.r1_key_holder, + FT_R1KH_ID_LEN) != 0) + return -1; + + return 0; +} + + 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_r0kh_r1kh_pull_frame frame, f; + u8 *packet = NULL; + const u8 *key; + size_t packet_len, key_len; - r0kh = sm->wpa_auth->conf.r0kh_list; - while (r0kh) { - if (r0kh->id_len == sm->r0kh_id_len && - os_memcmp_const(r0kh->id, sm->r0kh_id, sm->r0kh_id_len) == - 0) - break; - r0kh = r0kh->next; - } + wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, NULL, sm->r0kh_id, sm->r0kh_id_len, + &r0kh); if (r0kh == NULL) { wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID", sm->r0kh_id, sm->r0kh_id_len); return -1; } + key = r0kh->key; + key_len = sizeof(r0kh->key); - wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request to remote R0KH " - "address " MACSTR, MAC2STR(r0kh->addr)); + wpa_printf(MSG_DEBUG, "FT: Send 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_enc[] = { + { .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN, + .data = pmk_r0_name }, + { .type = FT_RRB_S1KH_ID, .len = ETH_ALEN, + .data = sm->addr }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + struct tlv_list req_auth[] = { + { .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_R1KH_ID, .len = FT_R1KH_ID_LEN, + .data = sm->wpa_auth->conf.r1_key_holder }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + if (wpa_ft_rrb_build(key, key_len, req_enc, NULL, req_auth, + sm->wpa_auth->addr, FT_PACKET_R0KH_R1KH_PULL, + &packet, &packet_len) < 0) return -1; wpabuf_free(sm->ft_pending_req_ies); @@ -375,7 +821,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; } @@ -1422,99 +1871,215 @@ static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm, } +static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len, + const struct tlv_list *tlvs, + const struct wpa_ft_pmk_r0_sa *pmk_r0, + const u8 *r1kh_id, const u8 *s1kh_id, + const struct tlv_list *tlv_auth, + const u8 *src_addr, const u8 type, + 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 (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; + 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, key_len, tlvs, sess_tlv, tlv_auth, + src_addr, type, 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) + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len) { - struct ft_r0kh_r1kh_pull_frame f; - const u8 *crypt; - u8 *plain; + const char *msgtype = "pull request"; + 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_r0kh_r1kh_resp_frame resp, r; - u8 pmk_r0[PMK_LEN]; + const u8 *key = NULL; + size_t key_len = 0; + 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; + const struct wpa_ft_pmk_r0_sa *r0; + int ret = -1; + + wpa_printf(MSG_DEBUG, "FT: Received %s from " MACSTR, + msgtype, MAC2STR(src_addr)); + + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len); + + if (wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len)) { + wpa_printf(MSG_DEBUG, "FT: R0KH-ID mismatch"); + goto out; + } + + 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, src_addr, f_r1kh_id, &r1kh); + if (r1kh == NULL) + 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); + + if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len, + src_addr, type, &plain, &plain_len) < 0) + goto out; + + 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, + f_pmk_r0_name_len); + + RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN); + wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id)); + + struct tlv_list resp[] = { + { .type = FT_RRB_S1KH_ID, .len = f_s1kh_id_len, + .data = f_s1kh_id }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + struct tlv_list resp_auth[] = { + { .type = FT_RRB_NONCE, .len = f_nonce_len, + .data = f_nonce }, + { .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len, + .data = f_r0kh_id }, + { .type = FT_RRB_R1KH_ID, .len = f_r1kh_id_len, + .data = f_r1kh_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"); + goto out; + } + + ret = wpa_ft_rrb_build_r0(key, key_len, resp, r0, f_r1kh_id, f_s1kh_id, + resp_auth, wpa_auth->addr, + FT_PACKET_R0KH_R1KH_RESP, + &packet, &packet_len); + + if (!ret) + wpa_ft_rrb_oui_send(wpa_auth, src_addr, + FT_PACKET_R0KH_R1KH_RESP, packet, + packet_len); + +out: + os_free(plain); plain = NULL; + os_free(packet); packet = NULL; + + return 0; +} + + +/* @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 type, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + const char *msgtype, u8 *s1kh_id_out) +{ + u8 *plain = NULL; + size_t plain_len = 0; + struct ft_remote_r0kh *r0kh; + const u8 *key; + size_t key_len; + const u8 *f_r1kh_id, *f_s1kh_id, *f_r0kh_id; + const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1; + size_t f_r1kh_id_len, f_s1kh_id_len, f_r0kh_id_len; + size_t f_pmk_r1_name_len, f_pairwise_len, f_pmk_r1_len; int pairwise; + int ret = -1; - wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull"); + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len); - if (data_len < sizeof(f)) - return -1; + 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)); - r1kh = wpa_auth->conf.r1kh_list; - while (r1kh) { - if (os_memcmp(r1kh->addr, src_addr, ETH_ALEN) == 0) - break; - r1kh = r1kh->next; - } - if (r1kh == NULL) { - wpa_printf(MSG_DEBUG, "FT: No matching R1KH address found for " - "PMK-R1 pull source address " MACSTR, - MAC2STR(src_addr)); - return -1; + if (wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id)) { + wpa_printf(MSG_DEBUG, "FT: R1KH-ID mismatch"); + goto out; } - 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) { - wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull " - "request from " MACSTR, MAC2STR(src_addr)); - return -1; - } + wpa_ft_rrb_lookup_r0kh(wpa_auth, src_addr, f_r0kh_id, f_r0kh_id_len, + &r0kh); + if (r0kh == NULL) + goto out; + key = r0kh->key; + key_len = sizeof(r0kh->key); - wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce", - f.nonce, sizeof(f.nonce)); - wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR0Name", - f.pmk_r0_name, WPA_PMK_NAME_LEN); - 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; - } + if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len, + src_addr, type, &plain, &plain_len) < 0) + goto out; - if (wpa_derive_pmk_r1(pmk_r0, f.pmk_r0_name, f.r1kh_id, f.s1kh_id, - r.pmk_r1, r.pmk_r1_name) < 0) { - os_memset(pmk_r0, 0, PMK_LEN); - return -1; - } - 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)); + RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN); + wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id)); - 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 (s1kh_id_out) + os_memcpy(s1kh_id_out, f_s1kh_id, ETH_ALEN); - os_memset(pmk_r0, 0, PMK_LEN); + RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16)); + wpa_hexdump(MSG_DEBUG, "FT: pairwise", f_pairwise, f_pairwise_len); - wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_RESP, - (u8 *) &resp, sizeof(resp)); + 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); + + RRB_GET(FT_RRB_PMK_R1, pmk_r1, msgtype, PMK_LEN); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f_pmk_r1, PMK_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) + goto out; + + ret = 0; +out: + if (plain) { + os_memset(plain, 0, plain_len); + os_free(plain); plain = NULL; + } + + return ret; - return 0; } -static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx) +static void ft_finish_pull(struct wpa_state_machine *sm) { - struct wpa_state_machine *sm = eloop_ctx; int res; u8 *resp_ies; size_t resp_ies_len; @@ -1538,169 +2103,106 @@ static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx) } -static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx) +struct ft_get_sta_ctx { + const u8 *nonce; + const u8 *s1kh_id; + struct wpa_state_machine *sm; +}; + + +static int ft_get_sta_cb(struct wpa_state_machine *sm, void *ctx) { - struct ft_r0kh_r1kh_resp_frame *frame = ctx; + struct ft_get_sta_ctx *info = ctx; - if (os_memcmp(frame->s1kh_id, sm->addr, ETH_ALEN) != 0 || - os_memcmp(frame->nonce, sm->ft_pending_pull_nonce, - FT_R0KH_R1KH_PULL_NONCE_LEN) != 0 || + if ((info->s1kh_id && os_memcmp(info->s1kh_id, sm->addr, ETH_ALEN)) || + os_memcmp(info->nonce, sm->ft_pending_pull_nonce, + FT_RRB_NONCE_LEN) != 0 || sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL) return 0; - wpa_printf(MSG_DEBUG, "FT: Response to a pending pull request for " - MACSTR " - process from timeout", MAC2STR(sm->addr)); - eloop_register_timeout(0, 0, ft_pull_resp_cb_finish, sm, NULL); + info->sm = sm; + return 1; } static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, const u8 *src_addr, - const u8 *data, size_t data_len) + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len) { - struct ft_r0kh_r1kh_resp_frame f; - const u8 *crypt; - u8 *plain; - struct ft_remote_r0kh *r0kh; - int pairwise, res; - - 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) - break; - r0kh = r0kh->next; - } - if (r0kh == NULL) { - wpa_printf(MSG_DEBUG, "FT: No matching R0KH address found for " - "PMK-R0 pull response source address " MACSTR, - MAC2STR(src_addr)); + const char *msgtype = "pull response"; + const int type = FT_PACKET_R0KH_R1KH_RESP; + int ret = -1; + struct ft_get_sta_ctx ctx; + u8 s1kh_id[ETH_ALEN]; + const u8 *f_nonce; + size_t f_nonce_len; + + wpa_printf(MSG_DEBUG, "FT: Received %s", msgtype); + + RRB_GET_AUTH(FT_RRB_NONCE, nonce, msgtype, FT_RRB_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len); + + os_memset(&ctx, 0, sizeof(ctx)); + ctx.nonce = f_nonce; + if (!wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) { + /* nonce not found */ + wpa_printf(MSG_DEBUG, "FT: Invalid nonce"); 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) { - wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull " - "response from " MACSTR, MAC2STR(src_addr)); + ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth, + auth_len, msgtype, s1kh_id); + if (ret < 0) 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; + ctx.s1kh_id = s1kh_id; + if (!ret && 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 " - process from timeout", + MAC2STR(ctx.sm->addr)); + ft_finish_pull(ctx.sm); } - 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; +out: + return ret; } static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth, const u8 *src_addr, - const u8 *data, size_t data_len) + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len) { - struct ft_r0kh_r1kh_push_frame f; - const u8 *crypt; - u8 *plain; - struct ft_remote_r0kh *r0kh; + const char *msgtype = "push"; + const int type = FT_PACKET_R0KH_R1KH_PUSH; struct os_time now; - os_time_t tsend; - int pairwise; - - 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) - break; - r0kh = r0kh->next; - } - if (r0kh == NULL) { - wpa_printf(MSG_DEBUG, "FT: No matching R0KH address found for " - "PMK-R0 push source address " MACSTR, - MAC2STR(src_addr)); - return -1; - } + struct os_time tsend; + const u8 *f_timestamp; + size_t f_timestamp_len; - 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) { - wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 push from " - MACSTR, MAC2STR(src_addr)); - return -1; - } + wpa_printf(MSG_DEBUG, "FT: Received %s", msgtype); + RRB_GET_AUTH(FT_RRB_TIMESTAMP, timestamp, msgtype, sizeof(le32)); + tsend.sec = WPA_GET_LE32(f_timestamp); + wpa_printf(MSG_DEBUG, "FT: timestamp=%ld", tsend.sec); os_get_time(&now); - 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); + if ((now.sec > tsend.sec && now.sec - tsend.sec > 60) || + (now.sec < tsend.sec && tsend.sec - now.sec > 60)) { + wpa_printf(MSG_DEBUG, "FT: did not have a valid " + "timestamp: sender time %ld own time %ld\n", + tsend.sec, now.sec); 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 push did not use a matching " - "R1KH-ID (received " MACSTR " own " MACSTR ")", - MAC2STR(f.r1kh_id), - MAC2STR(wpa_auth->conf.r1_key_holder)); + if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth, + auth_len, msgtype, NULL) < 0) return -1; - } - - 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); - - 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); return 0; +out: + return -1; } @@ -1826,6 +2328,10 @@ void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, const u8 *dst_addr, u8 oui_suffix, const u8 *data, size_t data_len) { + const u16 *head; + const u8 *auth, *enc; + size_t alen, elen; + wpa_printf(MSG_DEBUG, "FT: RRB-OUI received frame from remote AP " MACSTR, MAC2STR(src_addr)); wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame - oui_suffix=%d", oui_suffix); @@ -1845,15 +2351,31 @@ void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, return; } + head = (u16 *) data; + if (data_len < sizeof(*head)) { + wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short"); + return; + } + + alen = le_to_host16(*head); + if (data_len < sizeof(*head) + alen) { + wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short"); + return; + } + + auth = data + sizeof(*head); + enc = data + sizeof(*head) + alen; + elen = data_len - sizeof(*head) - alen; + switch (oui_suffix) { case FT_PACKET_R0KH_R1KH_PULL: - wpa_ft_rrb_rx_pull(wpa_auth, src_addr, data, data_len); + wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen); break; case FT_PACKET_R0KH_R1KH_RESP: - wpa_ft_rrb_rx_resp(wpa_auth, src_addr, data, data_len); + wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen); break; case FT_PACKET_R0KH_R1KH_PUSH: - wpa_ft_rrb_rx_push(wpa_auth, src_addr, data, data_len); + wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen); break; } } @@ -1862,41 +2384,45 @@ void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, static int 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); - if (wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_name, r1kh->id, - s1kh_id, f.pmk_r1, f.pmk_r1_name) < 0) - return -1; - 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[] = { + { .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 }, + }; + struct tlv_list push_auth[] = { + { .type = FT_RRB_TIMESTAMP, .len = sizeof(f_timestamp), + .data = f_timestamp }, + { .type = FT_RRB_R0KH_ID, + .len = wpa_auth->conf.r0_key_holder_len, + .data = wpa_auth->conf.r0_key_holder }, + { .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN, + .data = r1kh->id }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + if (wpa_ft_rrb_build_r0(r1kh->key, sizeof(r1kh->key), push, pmk_r0, + r1kh->id, s1kh_id, push_auth, wpa_auth->addr, + FT_PACKET_R0KH_R1KH_PUSH, + &packet, &packet_len) < 0) return -1; 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; return 0; } @@ -1925,7 +2451,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_glue.c b/src/ap/wpa_auth_glue.c index 6f06f43..f9b3c07 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -800,7 +800,7 @@ static void hostapd_rrb_oui_receive(void *ctx, const u8 *src_addr, { struct hostapd_data *hapd = ctx; - wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> " + wpa_printf(MSG_DEBUG, "FT: RRB-OUI received packet " MACSTR " -> " MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr)); if (!is_multicast_ether_addr(dst_addr) && os_memcmp(hapd->own_addr, dst_addr, ETH_ALEN) != 0) diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index 90318d8..3279ad4 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -121,7 +121,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_AP */ diff --git a/tests/hwsim/test_ap_ft.py b/tests/hwsim/test_ap_ft.py index 298fd82..f73cc89 100644 --- a/tests/hwsim/test_ap_ft.py +++ b/tests/hwsim/test_ap_ft.py @@ -55,9 +55,9 @@ def ft_params1a(rsn=True, ssid=None, passphrase=None): def ft_params1(rsn=True, ssid=None, passphrase=None): params = ft_params1a(rsn, ssid, passphrase) - params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 100102030405060708090a0b0c0d0e0f", - "02:00:00:00:04:00 nas2.w1.fi 300102030405060708090a0b0c0d0e0f" ] - params['r1kh'] = "02:00:00:00:04:00 00:01:02:03:04:06 200102030405060708090a0b0c0d0e0f" + params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 100102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f", + "02:00:00:00:04:00 nas2.w1.fi 300102030405060708090a0b0c0d0e0f300102030405060708090a0b0c0d0e0f" ] + params['r1kh'] = "02:00:00:00:04:00 00:01:02:03:04:06 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f" return params def ft_params2a(rsn=True, ssid=None, passphrase=None): @@ -68,36 +68,36 @@ def ft_params2a(rsn=True, ssid=None, passphrase=None): def ft_params2(rsn=True, ssid=None, passphrase=None): params = ft_params2a(rsn, ssid, passphrase) - params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0e0f", - "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0e0f" ] - params['r1kh'] = "02:00:00:00:03:00 00:01:02:03:04:05 300102030405060708090a0b0c0d0e0f" + params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f", + "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" ] + params['r1kh'] = "02:00:00:00:03:00 00:01:02:03:04:05 300102030405060708090a0b0c0d0e0f300102030405060708090a0b0c0d0e0f" return params def ft_params1_r0kh_mismatch(rsn=True, ssid=None, passphrase=None): params = ft_params(rsn, ssid, passphrase) params['nas_identifier'] = "nas1.w1.fi" params['r1_key_holder'] = "000102030405" - params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 100102030405060708090a0b0c0d0e0f", - "12:00:00:00:04:00 nas2.w1.fi 300102030405060708090a0b0c0d0e0f" ] - params['r1kh'] = "12:00:00:00:04:00 10:01:02:03:04:06 200102030405060708090a0b0c0d0e0f" + params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 100102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f", + "12:00:00:00:04:00 nas2.w1.fi 300102030405060708090a0b0c0d0e0f300102030405060708090a0b0c0d0e0f" ] + params['r1kh'] = "12:00:00:00:04:00 10:01:02:03:04:06 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f" return params def ft_params2_incorrect_rrb_key(rsn=True, ssid=None, passphrase=None): params = ft_params(rsn, ssid, passphrase) params['nas_identifier'] = "nas2.w1.fi" params['r1_key_holder'] = "000102030406" - params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0ef1", - "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0ef2" ] - params['r1kh'] = "02:00:00:00:03:00 00:01:02:03:04:05 300102030405060708090a0b0c0d0ef3" + params['r0kh'] = [ "02:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0ef1200102030405060708090a0b0c0d0ef1", + "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0ef2000102030405060708090a0b0c0d0ef2" ] + params['r1kh'] = "02:00:00:00:03:00 00:01:02:03:04:05 300102030405060708090a0b0c0d0ef3300102030405060708090a0b0c0d0ef3" return params def ft_params2_r0kh_mismatch(rsn=True, ssid=None, passphrase=None): params = ft_params(rsn, ssid, passphrase) params['nas_identifier'] = "nas2.w1.fi" params['r1_key_holder'] = "000102030406" - params['r0kh'] = [ "12:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0e0f", - "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0e0f" ] - params['r1kh'] = "12:00:00:00:03:00 10:01:02:03:04:05 300102030405060708090a0b0c0d0e0f" + params['r0kh'] = [ "12:00:00:00:03:00 nas1.w1.fi 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f", + "02:00:00:00:04:00 nas2.w1.fi 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" ] + params['r1kh'] = "12:00:00:00:03:00 10:01:02:03:04:05 300102030405060708090a0b0c0d0e0f300102030405060708090a0b0c0d0e0f" return params def run_roams(dev, apdev, hapd0, hapd1, ssid, passphrase, over_ds=False, @@ -1037,7 +1037,7 @@ def test_ap_ft_ap_oom3(dev, apdev): # This will fail due to not being able to send out PMK-R1 pull request dev[0].roam(bssid1) - with fail_test(hapd1, 1, "aes_wrap;wpa_ft_pull_pmk_r1"): + with fail_test(hapd1, 1, "aes_siv_encrypt;wpa_ft_pull_pmk_r1"): # This will fail due to not being able to send out PMK-R1 pull request dev[0].roam(bssid1) @@ -1246,7 +1246,7 @@ def test_ap_ft_ap_oom10(dev, apdev): bssid1 = hapd1.own_addr() dev[0].scan_for_bss(bssid1, freq="2412") - with fail_test(hapd0, 1, "aes_unwrap;wpa_ft_rrb_rx_pull"): + with fail_test(hapd0, 1, "aes_siv_decrypt;wpa_ft_rrb_rx_pull"): # This will fail to roam if "OK" not in dev[0].request("FT_DS " + bssid1): raise Exception("FT_DS failed") @@ -1258,13 +1258,13 @@ def test_ap_ft_ap_oom10(dev, apdev): raise Exception("FT_DS failed") wait_fail_trigger(hapd0, "GET_FAIL") - with fail_test(hapd0, 1, "aes_wrap;wpa_ft_rrb_rx_pull"): + with fail_test(hapd0, 1, "aes_siv_encrypt;wpa_ft_rrb_rx_pull"): # This will fail to roam if "OK" not in dev[0].request("FT_DS " + bssid1): raise Exception("FT_DS failed") wait_fail_trigger(hapd0, "GET_FAIL") - with fail_test(hapd1, 1, "aes_unwrap;wpa_ft_rrb_rx_resp"): + with fail_test(hapd1, 1, "aes_siv_decrypt;wpa_ft_rrb_rx_resp"): # This will fail to roam if "OK" not in dev[0].request("FT_DS " + bssid1): raise Exception("FT_DS failed") @@ -1286,7 +1286,7 @@ def test_ap_ft_ap_oom11(dev, apdev): wait_fail_trigger(hapd0, "GET_FAIL") dev[1].scan_for_bss(bssid0, freq="2412") - with fail_test(hapd0, 1, "aes_wrap;wpa_ft_generate_pmk_r1"): + with fail_test(hapd0, 1, "aes_siv_encrypt;wpa_ft_generate_pmk_r1"): dev[1].connect(ssid, psk=passphrase, key_mgmt="FT-PSK", proto="WPA2", scan_freq="2412") wait_fail_trigger(hapd0, "GET_FAIL") diff --git a/tests/hwsim/test_hapd_ctrl.py b/tests/hwsim/test_hapd_ctrl.py index d18a6b4..0a32fda 100644 --- a/tests/hwsim/test_hapd_ctrl.py +++ b/tests/hwsim/test_hapd_ctrl.py @@ -295,12 +295,12 @@ def test_hapd_ctrl_set_error_cases(dev, apdev): "wep_key_len_broadcast 20", "wep_rekey_period -1", "wep_default_key 4", - "r0kh 02:00:00:00:03:0q nas1.w1.fi 100102030405060708090a0b0c0d0e0f", - "r0kh 02:00:00:00:03:00 12345678901234567890123456789012345678901234567890.nas1.w1.fi 100102030405060708090a0b0c0d0e0f", - "r0kh 02:00:00:00:03:00 nas1.w1.fi 100q02030405060708090a0b0c0d0e0f", - "r1kh 02:00:00:00:04:q0 00:01:02:03:04:06 200102030405060708090a0b0c0d0e0f", - "r1kh 02:00:00:00:04:00 00:01:02:03:04:q6 200102030405060708090a0b0c0d0e0f", - "r1kh 02:00:00:00:04:00 00:01:02:03:04:06 2q0102030405060708090a0b0c0d0e0f", + "r0kh 02:00:00:00:03:0q nas1.w1.fi 100102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f", + "r0kh 02:00:00:00:03:00 12345678901234567890123456789012345678901234567890.nas1.w1.fi 100102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f", + "r0kh 02:00:00:00:03:00 nas1.w1.fi 100q02030405060708090a0b0c0d0e0f100q02030405060708090a0b0c0d0e0f", + "r1kh 02:00:00:00:04:q0 00:01:02:03:04:06 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f", + "r1kh 02:00:00:00:04:00 00:01:02:03:04:q6 200102030405060708090a0b0c0d0e0f200102030405060708090a0b0c0d0e0f", + "r1kh 02:00:00:00:04:00 00:01:02:03:04:06 2q0102030405060708090a0b0c0d0e0f2q0102030405060708090a0b0c0d0e0f", "roaming_consortium 1", "roaming_consortium 12", "roaming_consortium 112233445566778899aabbccddeeff00", -- 2.1.4 _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap