From 20ccf97b3dc1733a70818435298e2ec432458e4b Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 30 May 2020 23:30:42 +0300 Subject: [PATCH] SAE-PK: AP functionality This adds AP side functionality for SAE-PK. The new sae_password configuration parameters can now be used to enable SAE-PK mode whenever SAE is enabled. Signed-off-by: Jouni Malinen --- hostapd/config_file.c | 41 ++++++++++++++++ hostapd/hostapd.conf | 3 +- src/ap/ap_config.c | 69 ++++++++++++++++++++++++++- src/ap/ap_config.h | 3 ++ src/ap/ieee802_11.c | 96 ++++++++++++++++++++++++++++++-------- src/ap/ieee802_11_shared.c | 28 ++++++++++- src/ap/wpa_auth.h | 1 + src/ap/wpa_auth_glue.c | 3 ++ src/ap/wpa_auth_ie.c | 9 +++- 9 files changed, 228 insertions(+), 25 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index bc650e949..49894c5ba 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -14,6 +14,7 @@ #include "utils/common.h" #include "utils/uuid.h" #include "common/ieee802_11_defs.h" +#include "common/sae.h" #include "crypto/sha256.h" #include "crypto/tls.h" #include "drivers/driver.h" @@ -2290,6 +2291,35 @@ static int parse_sae_password(struct hostapd_bss_config *bss, const char *val) pw->vlan_id = atoi(pos2); } +#ifdef CONFIG_SAE_PK + pos2 = os_strstr(pos, "|pk="); + if (pos2) { + const char *epos; + char *tmp; + + if (!end) + end = pos2; + pos2 += 4; + epos = os_strchr(pos2, '|'); + if (epos) { + tmp = os_malloc(epos - pos2 + 1); + if (!tmp) + goto fail; + os_memcpy(tmp, pos2, epos - pos2); + tmp[epos - pos2] = '\0'; + } else { + tmp = os_strdup(pos2); + if (!tmp) + goto fail; + } + + pw->pk = sae_parse_pk(tmp); + str_clear_free(tmp); + if (!pw->pk) + goto fail; + } +#endif /* CONFIG_SAE_PK */ + pos2 = os_strstr(pos, "|id="); if (pos2) { if (!end) @@ -2312,6 +2342,14 @@ static int parse_sae_password(struct hostapd_bss_config *bss, const char *val) pw->password[end - val] = '\0'; } +#ifdef CONFIG_SAE_PK + if (pw->pk && !sae_pk_valid_password(pw->password)) { + wpa_printf(MSG_INFO, + "Invalid SAE password for a SAE-PK sae_password entry"); + goto fail; + } +#endif /* CONFIG_SAE_PK */ + pw->next = bss->sae_passwords; bss->sae_passwords = pw; @@ -2319,6 +2357,9 @@ static int parse_sae_password(struct hostapd_bss_config *bss, const char *val) fail: str_clear_free(pw->password); os_free(pw->identifier); +#ifdef CONFIG_SAE_PK + sae_deinit_pk(pw->pk); +#endif /* CONFIG_SAE_PK */ os_free(pw); return -1; } diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 812c09a9f..2b0f762e5 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1776,7 +1776,8 @@ own_ip_addr=127.0.0.1 # special meaning of removing all previously added entries. # # sae_password uses the following encoding: -#[|mac=][|vlanid=][|id=] +#[|mac=][|vlanid=] +#[|pk=][|id=] # Examples: #sae_password=secret #sae_password=really secret|mac=ff:ff:ff:ff:ff:ff diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 35a32a130..a4e1bbb3d 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -461,7 +461,8 @@ int hostapd_setup_sae_pt(struct hostapd_bss_config *conf) struct hostapd_ssid *ssid = &conf->ssid; struct sae_password_entry *pw; - if ((conf->sae_pwe == 0 && !hostapd_sae_pw_id_in_use(conf)) || + if ((conf->sae_pwe == 0 && !hostapd_sae_pw_id_in_use(conf) && + !hostapd_sae_pk_in_use(conf)) || conf->sae_pwe == 3 || !wpa_key_mgmt_sae(conf->wpa_key_mgmt)) return 0; /* PT not needed */ @@ -711,6 +712,9 @@ static void hostapd_config_free_sae_passwords(struct hostapd_bss_config *conf) #ifdef CONFIG_SAE sae_deinit_pt(tmp->pt); #endif /* CONFIG_SAE */ +#ifdef CONFIG_SAE_PK + sae_deinit_pk(tmp->pk); +#endif /* CONFIG_SAE_PK */ os_free(tmp); } } @@ -1111,6 +1115,25 @@ const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, } +#ifdef CONFIG_SAE_PK +static bool hostapd_sae_pk_password_without_pk(struct hostapd_bss_config *bss) +{ + struct sae_password_entry *pw; + + if (bss->ssid.wpa_passphrase && + sae_pk_valid_password(bss->ssid.wpa_passphrase)) + return true; + + for (pw = bss->sae_passwords; pw; pw = pw->next) { + if (!pw->pk && sae_pk_valid_password(pw->password)) + return true; + } + + return false; +} +#endif /* CONFIG_SAE_PK */ + + static int hostapd_config_check_bss(struct hostapd_bss_config *bss, struct hostapd_config *conf, int full_config) @@ -1294,6 +1317,15 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, } #endif /* CONFIG_OCV */ +#ifdef CONFIG_SAE_PK + if (full_config && hostapd_sae_pk_in_use(bss) && + hostapd_sae_pk_password_without_pk(bss)) { + wpa_printf(MSG_ERROR, + "SAE-PK: SAE password uses SAE-PK style, but does not have PK configured"); + return -1; + } +#endif /* CONFIG_SAE_PK */ + return 0; } @@ -1473,3 +1505,38 @@ int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf) return 2; return with_id; } + + +bool hostapd_sae_pk_in_use(struct hostapd_bss_config *conf) +{ +#ifdef CONFIG_SAE_PK + struct sae_password_entry *pw; + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + if (pw->pk) + return true; + } +#endif /* CONFIG_SAE_PK */ + + return false; +} + + +#ifdef CONFIG_SAE_PK +bool hostapd_sae_pk_exclusively(struct hostapd_bss_config *conf) +{ + bool with_pk = false; + struct sae_password_entry *pw; + + if (conf->ssid.wpa_passphrase) + return false; + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + if (!pw->pk) + return false; + with_pk = true; + } + + return with_pk; +} +#endif /* CONFIG_SAE_PK */ diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index c1b4b1bbb..cafc44edd 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -261,6 +261,7 @@ struct sae_password_entry { u8 peer_addr[ETH_ALEN]; int vlan_id; struct sae_pt *pt; + struct sae_pk *pk; }; struct dpp_controller_conf { @@ -1147,6 +1148,8 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config); void hostapd_set_security_params(struct hostapd_bss_config *bss, int full_config); int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf); +bool hostapd_sae_pk_in_use(struct hostapd_bss_config *conf); +bool hostapd_sae_pk_exclusively(struct hostapd_bss_config *conf); int hostapd_setup_sae_pt(struct hostapd_bss_config *conf); #endif /* HOSTAPD_CONFIG_H */ diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index a10bbdcfb..0822dfd34 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -386,7 +386,8 @@ static int send_auth_reply(struct hostapd_data *hapd, struct sta_info *sta, auth_alg == WLAN_AUTH_SAE) { if (auth_transaction == 1 && sta && (resp == WLAN_STATUS_SUCCESS || - resp == WLAN_STATUS_SAE_HASH_TO_ELEMENT)) { + resp == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + resp == WLAN_STATUS_SAE_PK)) { wpa_printf(MSG_DEBUG, "TESTING: Postpone SAE Commit transmission until Confirm is ready"); os_free(sta->sae_postponed_commit); @@ -478,17 +479,23 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, const char *rx_id = NULL; int use_pt = 0; struct sae_pt *pt = NULL; + const struct sae_pk *pk = NULL; if (sta->sae->tmp) { rx_id = sta->sae->tmp->pw_id; use_pt = sta->sae->tmp->h2e; +#ifdef CONFIG_SAE_PK + os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN); + os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN); +#endif /* CONFIG_SAE_PK */ } if (rx_id && hapd->conf->sae_pwe != 3) use_pt = 1; else if (status_code == WLAN_STATUS_SUCCESS) use_pt = 0; - else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT) + else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK) use_pt = 1; for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) { @@ -502,6 +509,8 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, continue; password = pw->password; pt = pw->pt; + if (!(hapd->conf->mesh & MESH_ENABLED)) + pk = pw->pk; break; } if (!password) { @@ -515,7 +524,7 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, if (update && use_pt && sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr, - NULL, NULL) < 0) + NULL, pk) < 0) return NULL; if (update && !use_pt && @@ -558,7 +567,10 @@ static struct wpabuf * auth_build_sae_confirm(struct hostapd_data *hapd, if (buf == NULL) return NULL; - sae_write_confirm(sta->sae, buf); + if (sae_write_confirm(sta->sae, buf) < 0) { + wpabuf_free(buf); + return NULL; + } return buf; } @@ -575,11 +587,19 @@ static int auth_sae_send_commit(struct hostapd_data *hapd, data = auth_build_sae_commit(hapd, sta, update, status_code); if (!data && sta->sae->tmp && sta->sae->tmp->pw_id) return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER; +#ifdef CONFIG_SAE_PK + if (!data && sta->sae->tmp && sta->sae->tmp->reject_group) + return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; +#endif /* CONFIG_SAE_PK */ if (data == NULL) return WLAN_STATUS_UNSPECIFIED_FAILURE; - status = (sta->sae->tmp && sta->sae->tmp->h2e) ? - WLAN_STATUS_SAE_HASH_TO_ELEMENT : WLAN_STATUS_SUCCESS; + if (sta->sae->tmp && sta->sae->tmp->pk) + status = WLAN_STATUS_SAE_PK; + else if (sta->sae->tmp && sta->sae->tmp->h2e) + status = WLAN_STATUS_SAE_HASH_TO_ELEMENT; + else + status = WLAN_STATUS_SUCCESS; reply_res = send_auth_reply(hapd, sta, sta->addr, bssid, WLAN_AUTH_SAE, 1, status, wpabuf_head(data), @@ -900,9 +920,14 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta, switch (sta->sae->state) { case SAE_NOTHING: if (auth_transaction == 1) { - if (sta->sae->tmp) - sta->sae->tmp->h2e = status_code == - WLAN_STATUS_SAE_HASH_TO_ELEMENT; + if (sta->sae->tmp) { + sta->sae->tmp->h2e = + (status_code == + WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK); + sta->sae->tmp->pk = + status_code == WLAN_STATUS_SAE_PK; + } ret = auth_sae_send_commit(hapd, sta, bssid, !allow_reuse, status_code); if (ret) @@ -1118,14 +1143,20 @@ static int sae_status_success(struct hostapd_data *hapd, u16 status_code) sae_pwe = 1; else if (id_in_use == 1 && sae_pwe == 0) sae_pwe = 2; +#ifdef CONFIG_SAE_PK + if (sae_pwe == 0 && hostapd_sae_pk_in_use(hapd->conf)) + sae_pwe = 2; +#endif /* CONFIG_SAE_PK */ return ((sae_pwe == 0 || sae_pwe == 3) && status_code == WLAN_STATUS_SUCCESS) || (sae_pwe == 1 && - status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT) || + (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK)) || (sae_pwe == 2 && (status_code == WLAN_STATUS_SUCCESS || - status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT)); + status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK)); } @@ -1148,11 +1179,15 @@ static int sae_is_group_enabled(struct hostapd_data *hapd, int group) static int check_sae_rejected_groups(struct hostapd_data *hapd, - const struct wpabuf *groups) + struct sae_data *sae, bool pk) { + const struct wpabuf *groups; size_t i, count; const u8 *pos; + if (!sae->tmp) + return 0; + groups = sae->tmp->peer_rejected_groups; if (!groups) return 0; @@ -1165,8 +1200,29 @@ static int check_sae_rejected_groups(struct hostapd_data *hapd, group = WPA_GET_LE16(pos); pos += 2; enabled = sae_is_group_enabled(hapd, group); - wpa_printf(MSG_DEBUG, "SAE: Rejected group %u is %s", - group, enabled ? "enabled" : "disabled"); + +#ifdef CONFIG_SAE_PK + /* TODO: Could check more explicitly against the matching + * sae_password entry only for the somewhat theoretical case of + * different passwords using different groups for SAE-PK K_AP + * values. */ + if (pk) { + struct sae_password_entry *pw; + + enabled = false; + for (pw = hapd->conf->sae_passwords; pw; + pw = pw->next) { + if (pw->pk && pw->pk->group == group) { + enabled = true; + break; + } + } + } +#endif /* CONFIG_SAE_PK */ + + wpa_printf(MSG_DEBUG, "SAE: Rejected group %u is %s%s", + group, enabled ? "enabled" : "disabled", + pk ? " (PK)" : ""); if (enabled) return 1; } @@ -1339,7 +1395,8 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, ((const u8 *) mgmt) + len - mgmt->u.auth.variable, &token, &token_len, groups, status_code == - WLAN_STATUS_SAE_HASH_TO_ELEMENT); + WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK); if (resp == SAE_SILENTLY_DISCARD) { wpa_printf(MSG_DEBUG, "SAE: Drop commit message from " MACSTR " due to reflection attack", @@ -1369,9 +1426,9 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, if (resp != WLAN_STATUS_SUCCESS) goto reply; - if (sta->sae->tmp && - check_sae_rejected_groups( - hapd, sta->sae->tmp->peer_rejected_groups)) { + if (check_sae_rejected_groups(hapd, sta->sae, + status_code == + WLAN_STATUS_SAE_PK)) { resp = WLAN_STATUS_UNSPECIFIED_FAILURE; goto reply; } @@ -1384,7 +1441,8 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, MACSTR, MAC2STR(sta->addr)); if (sta->sae->tmp) h2e = sta->sae->tmp->h2e; - if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT) + if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK) h2e = 1; data = auth_build_token_req(hapd, sta->sae->group, sta->addr, h2e); diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c index 45683d76e..79ed450e2 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -427,6 +427,14 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx) if (hapd->conf->beacon_prot) *pos |= 0x10; /* Bit 84 - Beacon Protection Enabled */ break; + case 11: /* Bits 88-95 */ +#ifdef CONFIG_SAE_PK + if (hapd->conf->wpa && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + hostapd_sae_pk_exclusively(hapd->conf)) + *pos |= 0x01; /* Bit 88 - SAE PK Exclusively */ +#endif /* CONFIG_SAE_PK */ + break; } } @@ -487,6 +495,12 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid) #endif /* CONFIG_SAE */ if (len < 11 && hapd->conf->beacon_prot) len = 11; +#ifdef CONFIG_SAE_PK + if (len < 12 && hapd->conf->wpa && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + hostapd_sae_pk_exclusively(hapd->conf)) + len = 12; +#endif /* CONFIG_SAE_PK */ if (len < hapd->iface->extended_capa_len) len = hapd->iface->extended_capa_len; if (len == 0) @@ -1081,11 +1095,16 @@ int get_tx_parameters(struct sta_info *sta, int ap_max_chanwidth, u8 * hostapd_eid_rsnxe(struct hostapd_data *hapd, u8 *eid, size_t len) { u8 *pos = eid; + bool sae_pk = false; + +#ifdef CONFIG_SAE_PK + sae_pk = hostapd_sae_pk_in_use(hapd->conf); +#endif /* CONFIG_SAE_PK */ if (!(hapd->conf->wpa & WPA_PROTO_RSN) || !wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) || (hapd->conf->sae_pwe != 1 && hapd->conf->sae_pwe != 2 && - !hostapd_sae_pw_id_in_use(hapd->conf)) || + !hostapd_sae_pw_id_in_use(hapd->conf) && !sae_pk) || hapd->conf->sae_pwe == 3 || len < 3) return pos; @@ -1094,7 +1113,12 @@ u8 * hostapd_eid_rsnxe(struct hostapd_data *hapd, u8 *eid, size_t len) *pos++ = 1; /* bits 0-3 = 0 since only one octet of Extended RSN Capabilities is * used for now */ - *pos++ = BIT(WLAN_RSNX_CAPAB_SAE_H2E); + *pos = BIT(WLAN_RSNX_CAPAB_SAE_H2E); +#ifdef CONFIG_SAE_PK + if (sae_pk) + *pos |= BIT(WLAN_RSNX_CAPAB_SAE_PK); +#endif /* CONFIG_SAE_PK */ + pos++; return pos; } diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index e059f3db3..59794a7f9 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -256,6 +256,7 @@ struct wpa_auth_config { u8 fils_cache_id[FILS_CACHE_ID_LEN]; #endif /* CONFIG_FILS */ int sae_pwe; + bool sae_pk; int owe_ptk_workaround; u8 transition_disable; #ifdef CONFIG_DPP2 diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index b90c9ef61..c01654f38 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -198,6 +198,9 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, wconf->sae_pwe = 1; else if (sae_pw_id == 1 && wconf->sae_pwe == 0) wconf->sae_pwe = 2; +#ifdef CONFIG_SAE_PK + wconf->sae_pk = hostapd_sae_pk_in_use(conf); +#endif /* CONFIG_SAE_PK */ #ifdef CONFIG_OWE wconf->owe_ptk_workaround = conf->owe_ptk_workaround; #endif /* CONFIG_OWE */ diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index 8dfd65700..a7e6354a5 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -378,7 +378,7 @@ int wpa_write_rsnxe(struct wpa_auth_config *conf, u8 *buf, size_t len) { u8 *pos = buf; - if (conf->sae_pwe != 1 && conf->sae_pwe != 2) + if (conf->sae_pwe != 1 && conf->sae_pwe != 2 && !conf->sae_pk) return 0; /* no supported extended RSN capabilities */ if (len < 3) @@ -388,7 +388,12 @@ int wpa_write_rsnxe(struct wpa_auth_config *conf, u8 *buf, size_t len) *pos++ = 1; /* bits 0-3 = 0 since only one octet of Extended RSN Capabilities is * used for now */ - *pos++ = BIT(WLAN_RSNX_CAPAB_SAE_H2E); + *pos = BIT(WLAN_RSNX_CAPAB_SAE_H2E); +#ifdef CONFIG_SAE_PK + if (conf->sae_pk) + *pos |= BIT(WLAN_RSNX_CAPAB_SAE_PK); +#endif /* CONFIG_SAE_PK */ + pos++; return pos - buf; }