WPS: Add a workaround for TKIP/CCMP mixed mode credentials
authorJouni Malinen <jouni.malinen@atheros.com>
Thu, 5 Mar 2009 19:39:39 +0000 (21:39 +0200)
committerJouni Malinen <j@w1.fi>
Thu, 5 Mar 2009 19:39:39 +0000 (21:39 +0200)
Many deployed APs do not handle negotiation of security parameters well
when both TKIP and CCMP (or both WPA and WPA2) are enabled. The most
common end result seems to be ending up with the least secure option..

As a workaround, check whether the AP advertises WPA2/CCMP in Beacon
frames and add those options for the credential if needed. This allows
the client to select the most secure configuration regardless of how
broken the AP's WPS implementation is as far as auth/encr type
negotiation is concerned.

wpa_supplicant/wps_supplicant.c

index 67ae1ac..e6607c0 100644 (file)
@@ -26,6 +26,7 @@
 #include "ctrl_iface_dbus.h"
 #include "eap_common/eap_wsc_common.h"
 #include "blacklist.h"
+#include "wpa.h"
 #include "wps_supplicant.h"
 #include "dh_groups.h"
 
@@ -84,6 +85,102 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s)
 }
 
 
+static void wpas_wps_security_workaround(struct wpa_supplicant *wpa_s,
+                                        struct wpa_ssid *ssid,
+                                        const struct wps_credential *cred)
+{
+       struct wpa_driver_capa capa;
+       size_t i;
+       struct wpa_scan_res *bss;
+       const u8 *ie;
+       struct wpa_ie_data adv;
+       int wpa2 = 0, ccmp = 0;
+
+       /*
+        * Many existing WPS APs do not know how to negotiate WPA2 or CCMP in
+        * case they are configured for mixed mode operation (WPA+WPA2 and
+        * TKIP+CCMP). Try to use scan results to figure out whether the AP
+        * actually supports stronger security and select that if the client
+        * has support for it, too.
+        */
+
+       if (wpa_drv_get_capa(wpa_s, &capa))
+               return; /* Unknown what driver supports */
+
+       if (wpa_supplicant_get_scan_results(wpa_s) || wpa_s->scan_res == NULL)
+               return; /* Could not get scan results for checking advertised
+                        * parameters */
+
+       for (i = 0; i < wpa_s->scan_res->num; i++) {
+               bss = wpa_s->scan_res->res[i];
+               if (os_memcmp(bss->bssid, cred->mac_addr, ETH_ALEN) != 0)
+                       continue;
+               ie = wpa_scan_get_ie(bss, WLAN_EID_SSID);
+               if (ie == NULL)
+                       continue;
+               if (ie[1] != ssid->ssid_len || ssid->ssid == NULL ||
+                   os_memcmp(ie + 2, ssid->ssid, ssid->ssid_len) != 0)
+                       continue;
+
+               wpa_printf(MSG_DEBUG, "WPS: AP found from scan results");
+               break;
+       }
+
+       if (i == wpa_s->scan_res->num) {
+               wpa_printf(MSG_DEBUG, "WPS: The AP was not found from scan "
+                          "results - use credential as-is");
+               return;
+       }
+
+       ie = wpa_scan_get_ie(bss, WLAN_EID_RSN);
+       if (ie && wpa_parse_wpa_ie(ie, 2 + ie[1], &adv) == 0) {
+               wpa2 = 1;
+               if (adv.pairwise_cipher & WPA_CIPHER_CCMP)
+                       ccmp = 1;
+       } else {
+               ie = wpa_scan_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE);
+               if (ie && wpa_parse_wpa_ie(ie, 2 + ie[1], &adv) == 0 &&
+                   adv.pairwise_cipher & WPA_CIPHER_CCMP)
+                       ccmp = 1;
+       }
+
+       if (ie == NULL && (ssid->proto & WPA_PROTO_WPA) &&
+           (ssid->pairwise_cipher & WPA_CIPHER_TKIP)) {
+               /*
+                * TODO: This could be the initial AP configuration and the
+                * Beacon contents could change shortly. Should request a new
+                * scan and delay addition of the network until the updated
+                * scan results are available.
+                */
+               wpa_printf(MSG_DEBUG, "WPS: The AP did not yet advertise WPA "
+                          "support - use credential as-is");
+               return;
+       }
+
+       if (ccmp && !(ssid->pairwise_cipher & WPA_CIPHER_CCMP) &&
+           (ssid->pairwise_cipher & WPA_CIPHER_TKIP) &&
+           (capa.key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_WPA2_PSK)) {
+               wpa_printf(MSG_DEBUG, "WPS: Add CCMP into the credential "
+                          "based on scan results");
+               if (wpa_s->conf->ap_scan == 1)
+                       ssid->pairwise_cipher |= WPA_CIPHER_CCMP;
+               else
+                       ssid->pairwise_cipher = WPA_CIPHER_CCMP;
+       }
+
+       if (wpa2 && !(ssid->proto & WPA_PROTO_RSN) &&
+           (ssid->proto & WPA_PROTO_WPA) &&
+           (capa.enc & WPA_DRIVER_CAPA_ENC_CCMP)) {
+               wpa_printf(MSG_DEBUG, "WPS: Add WPA2 into the credential "
+                          "based on scan results");
+               if (wpa_s->conf->ap_scan == 1)
+                       ssid->proto |= WPA_PROTO_RSN;
+               else
+                       ssid->proto = WPA_PROTO_RSN;
+       }
+}
+
+
 static int wpa_supplicant_wps_cred(void *ctx,
                                   const struct wps_credential *cred)
 {
@@ -250,6 +347,8 @@ static int wpa_supplicant_wps_cred(void *ctx,
                }
        }
 
+       wpas_wps_security_workaround(wpa_s, ssid, cred);
+
 #ifndef CONFIG_NO_CONFIG_WRITE
        if (wpa_s->conf->update_config &&
            wpa_config_write(wpa_s->confname, wpa_s->conf)) {