STA: Allow PTK rekeying without Ext KeyID to be disabled as a workaround

Rekeying a pairwise key using only keyid 0 (PTK0 rekey) has many broken
implementations and should be avoided when using or interacting with
one. The effects can be triggered by either end of the connection and
range from hardly noticeable disconnects over long connection freezes up
to leaking clear text MPDUs.

To allow affected users to mitigate the issues, add a new configuration
option "wpa_deny_ptk0_rekey" to replace all PTK0 rekeys with fast
reconnects.

Signed-off-by: Alexander Wetzel <alexander@wetzel-home.de>
This commit is contained in:
Alexander Wetzel 2020-01-10 23:19:09 +01:00 committed by Jouni Malinen
parent 1a7963e36f
commit 1f90a49d02
18 changed files with 174 additions and 8 deletions

View file

@ -200,6 +200,15 @@ static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
}
static int eapol_sm_confirm_auth(struct eapol_sm *sm)
{
if (!sm->ctx->confirm_auth_cb)
return 0;
return sm->ctx->confirm_auth_cb(sm->ctx->ctx);
}
static void eapol_enable_timer_tick(struct eapol_sm *sm)
{
if (sm->timer_tick_enabled)
@ -316,6 +325,11 @@ SM_STATE(SUPP_PAE, AUTHENTICATED)
SM_STATE(SUPP_PAE, RESTART)
{
if (eapol_sm_confirm_auth(sm)) {
/* Don't process restart, we are already reconnecting */
return;
}
SM_ENTRY(SUPP_PAE, RESTART);
sm->eapRestart = TRUE;
if (sm->altAccept) {

View file

@ -298,6 +298,15 @@ struct eapol_ctx {
* @len: Length of anonymous identity in octets
*/
void (*set_anon_id)(void *ctx, const u8 *id, size_t len);
/**
* confirm_auth_cb - Callback confirming if we can install a new PTK
* @ctx: eapol_ctx from eap_peer_sm_init() call
* Returns: 0 when authentication can continue, -1 when reconnecting
*
* Automatically triggers a reconnect when not.
*/
int (*confirm_auth_cb)(void *ctx);
};

View file

@ -183,6 +183,14 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise)
int key_info, ver;
u8 bssid[ETH_ALEN], *rbuf, *key_mic, *mic;
if (pairwise && sm->wpa_deny_ptk0_rekey &&
wpa_sm_get_state(sm) == WPA_COMPLETED) {
wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
"WPA: PTK0 rekey not allowed, reconnecting");
wpa_sm_reconnect(sm);
return;
}
if (wpa_use_akm_defined(sm->key_mgmt))
ver = WPA_KEY_INFO_TYPE_AKM_DEFINED;
else if (wpa_key_mgmt_ft(sm->key_mgmt) ||
@ -618,6 +626,13 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
return;
}
if (sm->wpa_deny_ptk0_rekey && wpa_sm_get_state(sm) == WPA_COMPLETED) {
wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
"WPA: PTK0 rekey not allowed, reconnecting");
wpa_sm_reconnect(sm);
return;
}
wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);
wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: RX message 1 of 4-Way "
"Handshake from " MACSTR " (ver=%d)", MAC2STR(src_addr), ver);
@ -3142,6 +3157,9 @@ int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param,
case WPA_PARAM_SAE_PWE:
sm->sae_pwe = value;
break;
case WPA_PARAM_DENY_PTK0_REKEY:
sm->wpa_deny_ptk0_rekey = value;
break;
default:
break;
}

View file

@ -27,6 +27,7 @@ struct wpa_sm_ctx {
void (*set_state)(void *ctx, enum wpa_states state);
enum wpa_states (*get_state)(void *ctx);
void (*deauthenticate)(void * ctx, u16 reason_code);
void (*reconnect)(void *ctx);
int (*set_key)(void *ctx, enum wpa_alg alg,
const u8 *addr, int key_idx, int set_tx,
const u8 *seq, size_t seq_len,
@ -100,6 +101,7 @@ enum wpa_sm_conf_params {
WPA_PARAM_MFP,
WPA_PARAM_OCV,
WPA_PARAM_SAE_PWE,
WPA_PARAM_DENY_PTK0_REKEY,
};
struct rsn_supp_config {
@ -111,6 +113,7 @@ struct rsn_supp_config {
const u8 *ssid;
size_t ssid_len;
int wpa_ptk_rekey;
int wpa_deny_ptk0_rekey;
int p2p;
int wpa_rsc_relaxation;
int owe_ptk_workaround;

View file

@ -63,6 +63,7 @@ struct wpa_sm {
u8 ssid[32];
size_t ssid_len;
int wpa_ptk_rekey;
int wpa_deny_ptk0_rekey:1;
int p2p;
int wpa_rsc_relaxation;
int owe_ptk_workaround;
@ -210,6 +211,12 @@ static inline int wpa_sm_set_key(struct wpa_sm *sm, enum wpa_alg alg,
seq, seq_len, key, key_len, key_flag);
}
static inline void wpa_sm_reconnect(struct wpa_sm *sm)
{
WPA_ASSERT(sm->ctx->reconnect);
sm->ctx->reconnect(sm->ctx->ctx);
}
static inline void * wpa_sm_get_network_ctx(struct wpa_sm *sm)
{
WPA_ASSERT(sm->ctx->get_network_ctx);

View file

@ -345,6 +345,7 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s,
bss->isolate = !wpa_s->conf->p2p_intra_bss;
bss->force_per_enrollee_psk = wpa_s->global->p2p_per_sta_psk;
bss->wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
if (ssid->p2p_group) {
os_memcpy(bss->ip_addr_go, wpa_s->p2pdev->conf->ip_addr_go, 4);

View file

@ -2495,6 +2495,7 @@ static const struct parse_data ssid_fields[] = {
{ INT(dot11MeshHoldingTimeout) },
#endif /* CONFIG_MESH */
{ INT(wpa_ptk_rekey) },
{ INT_RANGE(wpa_deny_ptk0_rekey, 0, 2) },
{ INT(group_rekey) },
{ STR(bgscan) },
{ INT_RANGE(ignore_broadcast_ssid, 0, 2) },
@ -3020,6 +3021,7 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid)
ssid->pairwise_cipher = DEFAULT_PAIRWISE;
ssid->group_cipher = DEFAULT_GROUP;
ssid->key_mgmt = DEFAULT_KEY_MGMT;
ssid->wpa_deny_ptk0_rekey = PTK0_REKEY_ALLOW_ALWAYS;
ssid->bg_scan_period = DEFAULT_BG_SCAN_PERIOD;
ssid->ht = 1;
#ifdef IEEE8021X_EAPOL

View file

@ -900,6 +900,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
INT_DEF(mesh_rssi_threshold, DEFAULT_MESH_RSSI_THRESHOLD);
#endif /* CONFIG_MESH */
INT(wpa_ptk_rekey);
INT(wpa_deny_ptk0_rekey);
INT(group_rekey);
INT(ignore_broadcast_ssid);
#ifdef CONFIG_DPP

View file

@ -553,6 +553,19 @@ struct wpa_ssid {
*/
int wpa_ptk_rekey;
/** wpa_deny_ptk0_rekey - Control PTK0 rekeying
*
* Rekeying a pairwise key using only keyid 0 (PTK0 rekey) has many
* broken implementations and should be avoided when using or
* interacting with one.
*
* 0 = always rekey when configured/instructed
* 1 = only rekey when the local driver is explicitly indicating it can
* perform this operation without issues
* 2 = never allow PTK0 rekeys
*/
enum ptk0_rekey_handling wpa_deny_ptk0_rekey;
/**
* group_rekey - Group rekeying time in seconds
*

View file

@ -8108,6 +8108,8 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s)
wpa_s->no_keep_alive = 0;
wpa_s->own_disconnect_req = 0;
wpa_s->own_reconnect_req = 0;
wpa_s->deny_ptk0_rekey = 0;
os_free(wpa_s->disallow_aps_bssid);
wpa_s->disallow_aps_bssid = NULL;

View file

@ -2895,6 +2895,7 @@ static void wpa_supplicant_event_assoc(struct wpa_supplicant *wpa_s,
#endif /* CONFIG_AP */
eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL);
wpa_s->own_reconnect_req = 0;
ft_completed = wpa_ft_is_completed(wpa_s->wpa);
if (data && wpa_supplicant_event_associnfo(wpa_s, data) < 0)
@ -3255,21 +3256,25 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s,
if (wpa_s->wpa_state == WPA_COMPLETED &&
wpa_s->current_ssid &&
wpa_s->current_ssid->mode == WPAS_MODE_INFRA &&
!locally_generated &&
disconnect_reason_recoverable(reason_code)) {
(wpa_s->own_reconnect_req ||
(!locally_generated &&
disconnect_reason_recoverable(reason_code)))) {
/*
* It looks like the AP has dropped association with
* us, but could allow us to get back in. Try to
* reconnect to the same BSS without full scan to save
* time for some common cases.
* us, but could allow us to get back in. This is also
* triggered for cases where local reconnection request
* is used to force reassociation with the same BSS.
* Try to reconnect to the same BSS without a full scan
* to save time for some common cases.
*/
fast_reconnect = wpa_s->current_bss;
fast_reconnect_ssid = wpa_s->current_ssid;
} else if (wpa_s->wpa_state >= WPA_ASSOCIATING)
} else if (wpa_s->wpa_state >= WPA_ASSOCIATING) {
wpa_supplicant_req_scan(wpa_s, 0, 100000);
else
} else {
wpa_dbg(wpa_s, MSG_DEBUG, "Do not request new "
"immediate scan");
}
} else {
wpa_dbg(wpa_s, MSG_DEBUG, "Auto connect disabled: do not "
"try to re-connect");

View file

@ -206,6 +206,12 @@ static void supp_deauthenticate(void * ctx, u16 reason_code)
}
static void supp_reconnect(void *ctx)
{
wpa_printf(MSG_DEBUG, "SUPP: %s (TODO)", __func__);
}
static int ibss_rsn_supp_init(struct ibss_rsn_peer *peer, const u8 *own_addr,
const u8 *psk)
{
@ -225,6 +231,7 @@ static int ibss_rsn_supp_init(struct ibss_rsn_peer *peer, const u8 *own_addr,
ctx->mlme_setprotection = supp_mlme_setprotection;
ctx->cancel_auth_timeout = supp_cancel_auth_timeout;
ctx->deauthenticate = supp_deauthenticate;
ctx->reconnect = supp_reconnect;
peer->supp = wpa_sm_init(ctx);
if (peer->supp == NULL) {
wpa_printf(MSG_DEBUG, "SUPP: wpa_sm_init() failed");

View file

@ -41,6 +41,12 @@ static void _wpa_supplicant_deauthenticate(void *wpa_s, u16 reason_code)
}
static void _wpa_supplicant_reconnect(void *wpa_s)
{
wpa_supplicant_reconnect(wpa_s);
}
static u8 * wpa_alloc_eapol(const struct wpa_supplicant *wpa_s, u8 type,
const void *data, u16 data_len,
size_t *msg_len, void **data_pos)
@ -245,6 +251,7 @@ static void wpa_init_conf(struct wpa_supplicant *wpa_s, const char *ifname)
ctx->set_config_blob = wpa_supplicant_set_config_blob;
ctx->get_config_blob = wpa_supplicant_get_config_blob;
ctx->mlme_setprotection = wpa_supplicant_mlme_setprotection;
ctx->reconnect = _wpa_supplicant_reconnect;
wpa_s->wpa = wpa_sm_init(ctx);
assert(wpa_s->wpa != NULL);

View file

@ -1441,6 +1441,7 @@ static const char *network_fields[] = {
"dot11MeshHoldingTimeout",
#endif /* CONFIG_MESH */
"wpa_ptk_rekey", "bgscan", "ignore_broadcast_ssid",
"wpa_deny_ptk0_rekey",
"enable_edmg", "edmg_channel",
#ifdef CONFIG_P2P
"go_p2p_dev_addr", "p2p_client_list", "psk_list",

View file

@ -1760,6 +1760,20 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
} else
wpa_sm_set_pmk_from_pmksa(wpa_s->wpa);
if (ssid->mode != WPAS_MODE_IBSS &&
!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED) &&
(ssid->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_NEVER ||
(ssid->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_LOCAL_OK &&
!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAFE_PTK0_REKEYS)))) {
wpa_msg(wpa_s, MSG_INFO,
"Disable PTK0 rekey support - replaced with reconnect");
wpa_s->deny_ptk0_rekey = 1;
wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DENY_PTK0_REKEY, 1);
} else {
wpa_s->deny_ptk0_rekey = 0;
wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DENY_PTK0_REKEY, 0);
}
return 0;
}
@ -2057,6 +2071,7 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
int rand_style;
wpa_s->own_disconnect_req = 0;
wpa_s->own_reconnect_req = 0;
/*
* If we are starting a new connection, any previously pending EAPOL
@ -3843,6 +3858,15 @@ void wpa_supplicant_deauthenticate(struct wpa_supplicant *wpa_s,
wpa_supplicant_clear_connection(wpa_s, addr);
}
void wpa_supplicant_reconnect(struct wpa_supplicant *wpa_s)
{
wpa_s->own_reconnect_req = 1;
wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_UNSPECIFIED);
}
static void wpa_supplicant_enable_one_network(struct wpa_supplicant *wpa_s,
struct wpa_ssid *ssid)
{
@ -7081,7 +7105,7 @@ void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid)
* There is no point in blacklisting the AP if this event is
* generated based on local request to disconnect.
*/
if (wpa_s->own_disconnect_req) {
if (wpa_s->own_disconnect_req || wpa_s->own_reconnect_req) {
wpa_s->own_disconnect_req = 0;
wpa_dbg(wpa_s, MSG_DEBUG,
"Ignore connection failure due to local request to disconnect");

View file

@ -1101,6 +1101,32 @@ fast_reauth=1
# wpa_ptk_rekey: Maximum lifetime for PTK in seconds. This can be used to
# enforce rekeying of PTK to mitigate some attacks against TKIP deficiencies.
#
# wpa_deny_ptk0_rekey: Control PTK0 rekeying
#
# Rekeying the PTK without using "Extended Key ID for Individually Addressed
# Frames" (two different Key ID values for pairwise keys) can, depending on the
# used cards/drivers, impact the security and stability of connections. Both
# ends can accidentally trick one end to drop all packets send by it until the
# connection is torn down or rekeyed again. Additionally, some drivers may
# skip/break the encryption for the time window the key is updated (normally a
# few milliseconds).
#
# To avoid such issues, wpa_supplicant can now replace all PTK rekeys using only
# keyid 0 (PTK0 rekeys) with fast reconnects.
#
# EAP reauthentication depends on replacing the PTK and is therefore just
# another way to rekey the PTK and is affected by the parameter, too.
#
# "Extended Key ID for Individually Addressed Frames" is avoiding the issues
# using two separate keys and this parameter will be ignored when using it
# (i.e., PTK rekeying is allowed regardless of this parameter value).
#
# Available options:
# 0 = always rekey when configured/instructed (default)
# 1 = only rekey when the local driver is explicitly indicating it can perform
# this operation without issues
# 2 = never allow problematic PTK0 rekeys
#
# group_rekey: Group rekeying time in seconds. This value, if non-zero, is used
# as the dot11RSNAConfigGroupRekeyTime parameter when operating in
# Authenticator role in IBSS, or in AP and mesh modes.

View file

@ -554,6 +554,7 @@ struct wpa_supplicant {
/* Selected configuration (based on Beacon/ProbeResp WPA IE) */
int pairwise_cipher;
int deny_ptk0_rekey;
int group_cipher;
int key_mgmt;
int wpa_proto;
@ -1071,6 +1072,7 @@ struct wpa_supplicant {
unsigned int wmm_ac_supported:1;
unsigned int ext_work_in_progress:1;
unsigned int own_disconnect_req:1;
unsigned int own_reconnect_req:1;
unsigned int ignore_post_flush_scan_res:1;
#define MAC_ADDR_RAND_SCAN BIT(0)
@ -1325,6 +1327,7 @@ const char * wpa_supplicant_get_eap_mode(struct wpa_supplicant *wpa_s);
void wpa_supplicant_cancel_auth_timeout(struct wpa_supplicant *wpa_s);
void wpa_supplicant_deauthenticate(struct wpa_supplicant *wpa_s,
u16 reason_code);
void wpa_supplicant_reconnect(struct wpa_supplicant *wpa_s);
struct wpa_ssid * wpa_supplicant_add_network(struct wpa_supplicant *wpa_s);
int wpa_supplicant_remove_network(struct wpa_supplicant *wpa_s, int id);

View file

@ -486,6 +486,12 @@ static void _wpa_supplicant_deauthenticate(void *wpa_s, u16 reason_code)
}
static void _wpa_supplicant_reconnect(void *wpa_s)
{
wpa_supplicant_reconnect(wpa_s);
}
static void * wpa_supplicant_get_network_ctx(void *wpa_s)
{
return wpa_supplicant_get_ssid(wpa_s);
@ -1058,6 +1064,20 @@ static void wpa_supplicant_eap_error_cb(void *ctx, int error_code)
}
static int wpa_supplicant_eap_auth_start_cb(void *ctx)
{
struct wpa_supplicant *wpa_s = ctx;
if (!wpa_s->new_connection && wpa_s->deny_ptk0_rekey) {
wpa_msg(wpa_s, MSG_INFO,
"WPA: PTK0 rekey not allowed, reconnecting");
wpa_supplicant_reconnect(wpa_s);
return -1;
}
return 0;
}
static void wpa_supplicant_set_anon_id(void *ctx, const u8 *id, size_t len)
{
struct wpa_supplicant *wpa_s = ctx;
@ -1136,6 +1156,7 @@ int wpa_supplicant_init_eapol(struct wpa_supplicant *wpa_s)
ctx->cert_in_cb = wpa_s->conf->cert_in_cb;
ctx->status_cb = wpa_supplicant_status_cb;
ctx->eap_error_cb = wpa_supplicant_eap_error_cb;
ctx->confirm_auth_cb = wpa_supplicant_eap_auth_start_cb;
ctx->set_anon_id = wpa_supplicant_set_anon_id;
ctx->cb_ctx = wpa_s;
wpa_s->eapol = eapol_sm_init(ctx);
@ -1222,6 +1243,7 @@ int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s)
ctx->set_state = _wpa_supplicant_set_state;
ctx->get_state = _wpa_supplicant_get_state;
ctx->deauthenticate = _wpa_supplicant_deauthenticate;
ctx->reconnect = _wpa_supplicant_reconnect;
ctx->set_key = wpa_supplicant_set_key;
ctx->get_network_ctx = wpa_supplicant_get_network_ctx;
ctx->get_bssid = wpa_supplicant_get_bssid;
@ -1286,6 +1308,7 @@ void wpa_supplicant_rsn_supp_set_config(struct wpa_supplicant *wpa_s,
conf.ssid = ssid->ssid;
conf.ssid_len = ssid->ssid_len;
conf.wpa_ptk_rekey = ssid->wpa_ptk_rekey;
conf.wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
conf.owe_ptk_workaround = ssid->owe_ptk_workaround;
#ifdef CONFIG_P2P
if (ssid->p2p_group && wpa_s->current_bss &&