diff --git a/hostapd/ChangeLog b/hostapd/ChangeLog index 8ee6d7b67..0cadf182e 100644 --- a/hostapd/ChangeLog +++ b/hostapd/ChangeLog @@ -1,5 +1,16 @@ ChangeLog for hostapd +????-??-?? - v0.6.7 + * added support for Wi-Fi Protected Setup (WPS) + (hostapd can now be configured to act as an integrated WPS Registrar + and provision credentials for WPS Enrollees using PIN and PBC + methods; external WPS Registrars are not supported); WPS support can + be enabled by adding CONFIG_WPS=y into .config and setting the + runtime configuration variables in hostapd.conf (see WPS section in + the example configuration file); new hostapd_cli commands wps_pin and + wps_pbc are used to configuration WPS negotiation; see README-WPS for + more details + 2008-11-23 - v0.6.6 * added a new configuration option, wpa_ptk_rekey, that can be used to enforce frequent PTK rekeying, e.g., to mitigate some attacks against diff --git a/hostapd/Makefile b/hostapd/Makefile index 4a39ee7f9..d1c42d635 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -258,6 +258,21 @@ TLS_FUNCS=y NEED_T_PRF=y endif +ifdef CONFIG_WPS +CFLAGS += -DCONFIG_WPS -DEAP_WSC +OBJS += ../src/utils/uuid.o +OBJS += wps_hostapd.o +OBJS += ../src/eap_server/eap_wsc.o ../src/eap_common/eap_wsc_common.o +OBJS += ../src/wps/wps.o +OBJS += ../src/wps/wps_common.o +OBJS += ../src/wps/wps_dev_attr.o +OBJS += ../src/wps/wps_enrollee.o +OBJS += ../src/wps/wps_registrar.o +NEED_DH_GROUPS=y +NEED_SHA256=y +NEED_CRYPTO=y +endif + ifdef CONFIG_EAP_IKEV2 CFLAGS += -DEAP_IKEV2 OBJS += ../src/eap_server/eap_ikev2.o ../src/eap_server/ikev2.o diff --git a/hostapd/README-WPS b/hostapd/README-WPS new file mode 100644 index 000000000..5e8a68d90 --- /dev/null +++ b/hostapd/README-WPS @@ -0,0 +1,173 @@ +hostapd and Wi-Fi Protected Setup (WPS) +======================================= + +This document describes how the WPS implementation in hostapd can be +configured and how an external component on an AP (e.g., web UI) is +used to enable enrollment of client devices. + + +Introduction to WPS +------------------- + +Wi-Fi Protected Setup (WPS) is a mechanism for easy configuration of a +wireless network. It allows automated generation of random keys (WPA +passphrase/PSK) and configuration of an access point and client +devices. WPS includes number of methods for setting up connections +with PIN method and push-button configuration (PBC) being the most +commonly deployed options. + +While WPS can enable more home networks to use encryption in the +wireless network, it should be noted that the use of the PIN and +especially PBC mechanisms for authenticating the initial key setup is +not very secure. As such, use of WPS may not be suitable for +environments that require secure network access without chance for +allowing outsiders to gain access during the setup phase. + +WPS uses following terms to describe the entities participating in the +network setup: +- access point: the WLAN access point +- Registrar: a device that control a network and can authorize + addition of new devices); this may be either in the AP ("internal + Registrar") or in an external device, e.g., a laptop, ("external + Registrar") +- Enrollee: a device that is being authorized to use the network + +It should also be noted that the AP and a client device may change +roles (i.e., AP acts as an Enrollee and client device as a Registrar) +when WPS is used to configure the access point. + + +More information about WPS is available from Wi-Fi Alliance: +http://www.wi-fi.org/wifi-protected-setup + + +hostapd implementation +---------------------- + +hostapd includes an optional WPS component that can be used as an +internal WPS Registrar to manage addition of new WPS enabled clients +to the network. In addition, WPS Enrollee functionality in hostapd can +be used to allow external WPS Registrars to configure the access +point, e.g., for initial network setup. The current version of hostapd +does not support use of external WPS Registrars for adding new client +devices. + + +hostapd configuration +--------------------- + +WPS is an optional component that needs to be enabled in hostapd build +configuration (.config). Here is an example configuration that +includes WPS support and uses madwifi driver interface: + +CONFIG_DRIVER_MADWIFI=y +CFLAGS += -I/usr/src/madwifi-0.9.3 +CONFIG_EAP=y +CONFIG_WPS=y + + +Following section shows an example runtime configuration +(hostapd.conf) that enables WPS: + +# Configure the driver and network interface +driver=madwifi +interface=ath0 + +# WPA2-Personal configuration for the AP +ssid=wps-test +wpa=2 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +# Default WPA passphrase for legacy (non-WPS) clients +wpa_passphrase=12345678 +# Enable random per-device PSK generation for WPS clients +# Please note that the file has to exists for hostapd to start (i.e., create an +# empty file as a starting point). +wpa_psk_file=/etc/hostapd.psk + +# Enable control interface for PBC/PIN entry +ctrl_interface=/var/run/hostapd + +# Enable internal EAP server for EAP-WSC (part of Wi-Fi Protected Setup) +eap_server=1 + +# WPS configuration (AP configured, do not allow external WPS Registrars) +wps_state=2 +ap_setup_locked=1 +uuid=87654321-9abc-def0-1234-56789abc0000 +wps_pin_requests=/var/run/hostapd.pin-req +device_name=Wireless AP +manufacturer=Company +model_name=WAP +model_number=123 +serial_number=12345 +device_type=6-0050F204-1 +os_version=01020300 +config_methods=label display push_button keypad + + +External operations +------------------- + +WPS requires either a device PIN code (usually, 8-digit number) or a +pushbutton event (for PBC) to allow a new WPS Enrollee to join the +network. hostapd uses the control interface as an input channel for +these events. + +When a client device (WPS Enrollee) connects to hostapd (WPS +Registrar) in order to start PIN mode negotiation for WPS, an +identifier (Enrollee UUID) is sent. hostapd will need to be configured +with a device password (PIN) for this Enrollee. This is an operation +that requires user interaction (assuming there are no pre-configured +PINs on the AP for a set of Enrollee). + +The PIN request with information about the device is appended to the +wps_pin_requests file (/var/run/hostapd.pin-req in this example). In +addition, hostapd control interface event is sent as a notification of +a new device. The AP could use, e.g., a web UI for showing active +Enrollees to the user and request a PIN for an Enrollee. + +The PIN request file has one line for every Enrollee that connected to +the AP, but for which there was no PIN. Following information is +provided for each Enrollee (separated with tabulators): +- timestamp (seconds from 1970-01-01) +- Enrollee UUID +- MAC address +- Device name +- Manufacturer +- Model Name +- Model Number +- Serial Number +- Device category + +Example line in the /var/run/hostapd.pin-req file: +1200188391 53b63a98-d29e-4457-a2ed-094d7e6a669c Intel(R) Centrino(R) Intel Corporation Intel(R) Centrino(R) - - 1-0050F204-1 + + +When the user enters a PIN for a pending Enrollee, e.g., on the web +UI), hostapd needs to be notified of the new PIN over the control +interface. This can be done either by using the UNIX domain socket +-based control interface directly (src/common/wpa_ctrl.c provides +helper functions for using the interface) or by calling hostapd_cli. + +Example command to add a PIN (12345670) for an Enrollee: + +hostapd_cli wps_pin 53b63a98-d29e-4457-a2ed-094d7e6a669c 12345670 + +After this, the Enrollee can connect to the AP again and complete WPS +negotiation. At that point, a new, random WPA PSK is generated for the +client device and the client can then use that key to connect to the +AP to access the network. + + +If the AP includes a pushbutton, WPS PBC mode can be used. It is +enabled by pushing a button on both the AP and the client at about the +same time (2 minute window). hostapd needs to be notified about the AP +button pushed event over the control interface, e.g., by calling +hostapd_cli: + +hostapd_cli wps_pbc + +At this point, the client has two minutes to complete WPS negotiation +which will generate a new WPA PSK in the same way as the PIN method +described above. diff --git a/hostapd/ap.h b/hostapd/ap.h index 340b97c09..f6596fe56 100644 --- a/hostapd/ap.h +++ b/hostapd/ap.h @@ -33,6 +33,8 @@ #define WLAN_STA_WME BIT(9) #define WLAN_STA_MFP BIT(10) #define WLAN_STA_HT BIT(11) +#define WLAN_STA_WPS BIT(12) +#define WLAN_STA_MAYBE_WPS BIT(13) #define WLAN_STA_NONERP BIT(31) /* Maximum number of supported rates (from both Supported Rates and Extended diff --git a/hostapd/beacon.c b/hostapd/beacon.c index b720489c1..a464db732 100644 --- a/hostapd/beacon.c +++ b/hostapd/beacon.c @@ -27,6 +27,7 @@ #include "hw_features.h" #include "driver.h" #include "sta_info.h" +#include "wps_hostapd.h" static u8 ieee802_11_erp_info(struct hostapd_data *hapd) @@ -145,6 +146,8 @@ void handle_probe_req(struct hostapd_data *hapd, struct ieee80211_mgmt *mgmt, ie = mgmt->u.probe_req.variable; ie_len = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req)); + hostapd_wps_probe_req_rx(hapd, mgmt->sa, ie, ie_len); + if (!hapd->iconf->send_probe_response) return; @@ -243,6 +246,14 @@ void handle_probe_req(struct hostapd_data *hapd, struct ieee80211_mgmt *mgmt, pos = hostapd_eid_ht_capabilities_info(hapd, pos); pos = hostapd_eid_ht_operation(hapd, pos); +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_probe_resp_ie) { + os_memcpy(pos, hapd->wps_probe_resp_ie, + hapd->wps_probe_resp_ie_len); + pos += hapd->wps_probe_resp_ie_len; + } +#endif /* CONFIG_WPS */ + if (hostapd_send_mgmt_frame(hapd, resp, pos - (u8 *) resp, 0) < 0) perror("handle_probe_req: send"); @@ -349,6 +360,14 @@ void ieee802_11_set_beacon(struct hostapd_data *hapd) } #endif /* CONFIG_IEEE80211N */ +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_beacon_ie) { + os_memcpy(tailpos, hapd->wps_beacon_ie, + hapd->wps_beacon_ie_len); + tailpos += hapd->wps_beacon_ie_len; + } +#endif /* CONFIG_WPS */ + tail_len = tailpos > tail ? tailpos - tail : 0; if (hostapd_set_beacon(hapd->conf->iface, hapd, (u8 *) head, head_len, diff --git a/hostapd/config.c b/hostapd/config.c index 2e890b44a..ec5b0b6db 100644 --- a/hostapd/config.c +++ b/hostapd/config.c @@ -26,6 +26,7 @@ #include "wpa_common.h" #include "wpa.h" #include "uuid.h" +#include "eap_common/eap_wsc_common.h" #define MAX_STA_COUNT 2007 @@ -2023,6 +2024,44 @@ struct hostapd_config * hostapd_config_read(const char *fname) bss->max_listen_interval = atoi(pos); } else if (os_strcmp(buf, "okc") == 0) { bss->okc = atoi(pos); +#ifdef CONFIG_WPS + } else if (os_strcmp(buf, "wps_state") == 0) { + bss->wps_state = atoi(pos); + if (bss->wps_state < 0 || bss->wps_state > 2) { + printf("Line %d: invalid wps_state\n", line); + errors++; + } + } else if (os_strcmp(buf, "ap_setup_locked") == 0) { + bss->ap_setup_locked = atoi(pos); + } else if (os_strcmp(buf, "uuid") == 0) { + if (uuid_str2bin(pos, bss->uuid)) { + printf("Line %d: invalid UUID\n", line); + errors++; + } + } else if (os_strcmp(buf, "wps_pin_requests") == 0) { + bss->wps_pin_requests = os_strdup(pos); + } else if (os_strcmp(buf, "device_name") == 0) { + bss->device_name = os_strdup(pos); + } else if (os_strcmp(buf, "manufacturer") == 0) { + bss->manufacturer = os_strdup(pos); + } else if (os_strcmp(buf, "model_name") == 0) { + bss->model_name = os_strdup(pos); + } else if (os_strcmp(buf, "model_number") == 0) { + bss->model_number = os_strdup(pos); + } else if (os_strcmp(buf, "serial_number") == 0) { + bss->serial_number = os_strdup(pos); + } else if (os_strcmp(buf, "device_type") == 0) { + bss->device_type = os_strdup(pos); + } else if (os_strcmp(buf, "config_methods") == 0) { + bss->config_methods = os_strdup(pos); + } else if (os_strcmp(buf, "os_version") == 0) { + if (hexstr2bin(pos, bss->os_version, 4)) { + printf("Line %d: invalid os_version\n", line); + errors++; + } + } else if (os_strcmp(buf, "ap_pin") == 0) { + bss->ap_pin = os_strdup(pos); +#endif /* CONFIG_WPS */ } else { printf("Line %d: unknown configuration item '%s'\n", line, buf); @@ -2216,6 +2255,18 @@ static void hostapd_config_free_bss(struct hostapd_bss_config *conf) } } #endif /* CONFIG_IEEE80211R */ + +#ifdef CONFIG_WPS + os_free(conf->wps_pin_requests); + os_free(conf->device_name); + os_free(conf->manufacturer); + os_free(conf->model_name); + os_free(conf->model_number); + os_free(conf->serial_number); + os_free(conf->device_type); + os_free(conf->config_methods); + os_free(conf->ap_pin); +#endif /* CONFIG_WPS */ } @@ -2314,6 +2365,29 @@ hostapd_get_eap_user(const struct hostapd_bss_config *conf, const u8 *identity, { struct hostapd_eap_user *user = conf->eap_user; +#ifdef CONFIG_WPS + if (conf->wps_state && identity_len == WSC_ID_ENROLLEE_LEN && + os_memcmp(identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN) == 0) { + static struct hostapd_eap_user wsc_enrollee; + os_memset(&wsc_enrollee, 0, sizeof(wsc_enrollee)); + wsc_enrollee.methods[0].method = eap_server_get_type( + "WSC", &wsc_enrollee.methods[0].vendor); + return &wsc_enrollee; + } + + if (conf->wps_state && conf->ap_pin && + identity_len == WSC_ID_REGISTRAR_LEN && + os_memcmp(identity, WSC_ID_REGISTRAR, WSC_ID_REGISTRAR_LEN) == 0) { + static struct hostapd_eap_user wsc_registrar; + os_memset(&wsc_registrar, 0, sizeof(wsc_registrar)); + wsc_registrar.methods[0].method = eap_server_get_type( + "WSC", &wsc_registrar.methods[0].vendor); + wsc_registrar.password = (u8 *) conf->ap_pin; + wsc_registrar.password_len = os_strlen(conf->ap_pin); + return &wsc_registrar; + } +#endif /* CONFIG_WPS */ + while (user) { if (!phase2 && user->identity == NULL) { /* Wildcard match */ diff --git a/hostapd/config.h b/hostapd/config.h index 86f88c18d..324349def 100644 --- a/hostapd/config.h +++ b/hostapd/config.h @@ -285,6 +285,22 @@ struct hostapd_bss_config { u16 max_listen_interval; int okc; /* Opportunistic Key Caching */ + + int wps_state; +#ifdef CONFIG_WPS + int ap_setup_locked; + u8 uuid[16]; + char *wps_pin_requests; + char *device_name; + char *manufacturer; + char *model_name; + char *model_number; + char *serial_number; + char *device_type; + char *config_methods; + u8 os_version[4]; + char *ap_pin; +#endif /* CONFIG_WPS */ }; diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index b978f9618..f8b3a1dbc 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -29,6 +29,7 @@ #include "ctrl_iface.h" #include "sta_info.h" #include "accounting.h" +#include "wps_hostapd.h" struct wpa_ctrl_dst { @@ -217,6 +218,18 @@ static int hostapd_ctrl_iface_new_sta(struct hostapd_data *hapd, } +#ifdef CONFIG_WPS +static int hostapd_ctrl_iface_wps_pin(struct hostapd_data *hapd, char *txt) +{ + char *pin = os_strchr(txt, ' '); + if (pin == NULL) + return -1; + *pin++ = '\0'; + return hostapd_wps_add_pin(hapd, txt, pin); +} +#endif /* CONFIG_WPS */ + + static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, void *sock_ctx) { @@ -300,6 +313,14 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "NEW_STA ", 8) == 0) { if (hostapd_ctrl_iface_new_sta(hapd, buf + 8)) reply_len = -1; +#ifdef CONFIG_WPS + } else if (os_strncmp(buf, "WPS_PIN ", 8) == 0) { + if (hostapd_ctrl_iface_wps_pin(hapd, buf + 8)) + reply_len = -1; + } else if (os_strcmp(buf, "WPS_PBC") == 0) { + if (hostapd_wps_button_pushed(hapd)) + reply_len = -1; +#endif /* CONFIG_WPS */ } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/hostapd/defconfig b/hostapd/defconfig index 709fb0794..cbbd19358 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -101,6 +101,9 @@ CONFIG_EAP_TTLS=y # to add the needed functions. #CONFIG_EAP_FAST=y +# Wi-Fi Protected Setup (WPS) +#CONFIG_WPS=y + # EAP-IKEv2 #CONFIG_EAP_IKEV2=y diff --git a/hostapd/driver.h b/hostapd/driver.h index 76e1990ae..47d0e7619 100644 --- a/hostapd/driver.h +++ b/hostapd/driver.h @@ -180,6 +180,11 @@ struct wpa_driver_ops { const u8 *data, size_t data_len); int (*set_ht_operation)(const char *ifname, void *priv, const u8 *data, size_t data_len); + + int (*set_wps_beacon_ie)(const char *ifname, void *priv, + const u8 *ie, size_t len); + int (*set_wps_probe_resp_ie)(const char *ifname, void *priv, + const u8 *ie, size_t len); }; static inline void * @@ -758,4 +763,24 @@ hostapd_drv_none(struct hostapd_data *hapd) return hapd->driver && os_strcmp(hapd->driver->name, "none") == 0; } +static inline int +hostapd_set_wps_beacon_ie(struct hostapd_data *hapd, const u8 *ie, size_t len) +{ + if (hapd->driver == NULL || hapd->driver->set_wps_beacon_ie == NULL) + return 0; + return hapd->driver->set_wps_beacon_ie(hapd->conf->iface, + hapd->drv_priv, ie, len); +} + +static inline int +hostapd_set_wps_probe_resp_ie(struct hostapd_data *hapd, const u8 *ie, + size_t len) +{ + if (hapd->driver == NULL || + hapd->driver->set_wps_probe_resp_ie == NULL) + return 0; + return hapd->driver->set_wps_probe_resp_ie(hapd->conf->iface, + hapd->drv_priv, ie, len); +} + #endif /* DRIVER_H */ diff --git a/hostapd/driver_hostap.c b/hostapd/driver_hostap.c index 04b0b9433..ceff09989 100644 --- a/hostapd/driver_hostap.c +++ b/hostapd/driver_hostap.c @@ -55,6 +55,8 @@ struct hostap_driver_data { u8 *generic_ie; size_t generic_ie_len; + u8 *wps_ie; + size_t wps_ie_len; }; @@ -770,7 +772,7 @@ static int hostapd_ioctl_set_generic_elem(struct hostap_driver_data *drv) int res; size_t blen, elem_len; - elem_len = drv->generic_ie_len; + elem_len = drv->generic_ie_len + drv->wps_ie_len; blen = PRISM2_HOSTAPD_GENERIC_ELEMENT_HDR_LEN + elem_len; if (blen < sizeof(*param)) blen = sizeof(*param); @@ -785,6 +787,10 @@ static int hostapd_ioctl_set_generic_elem(struct hostap_driver_data *drv) os_memcpy(param->u.generic_elem.data, drv->generic_ie, drv->generic_ie_len); } + if (drv->wps_ie) { + os_memcpy(¶m->u.generic_elem.data[drv->generic_ie_len], + drv->wps_ie, drv->wps_ie_len); + } wpa_hexdump(MSG_DEBUG, "hostap: Set generic IE", param->u.generic_elem.data, elem_len); res = hostapd_ioctl(drv, param, blen); @@ -815,6 +821,36 @@ static int hostap_set_generic_elem(const char *ifname, void *priv, } +static int hostap_set_wps_beacon_ie(const char *ifname, void *priv, + const u8 *ie, size_t len) +{ + /* Host AP driver supports only one set of extra IEs, so we need to + * use the ProbeResp IEs also for Beacon frames since they include more + * information. */ + return 0; +} + + +static int hostap_set_wps_probe_resp_ie(const char *ifname, void *priv, + const u8 *ie, size_t len) +{ + struct hostap_driver_data *drv = priv; + + os_free(drv->wps_ie); + drv->wps_ie = NULL; + drv->wps_ie_len = 0; + if (ie) { + drv->wps_ie = os_malloc(len); + if (drv->wps_ie == NULL) + return -1; + os_memcpy(drv->wps_ie, ie, len); + drv->wps_ie_len = len; + } + + return hostapd_ioctl_set_generic_elem(drv); +} + + static void hostapd_wireless_event_wireless_custom(struct hostap_driver_data *drv, char *custom) @@ -1120,6 +1156,7 @@ static void hostap_driver_deinit(void *priv) close(drv->sock); os_free(drv->generic_ie); + os_free(drv->wps_ie); free(drv); } @@ -1237,4 +1274,6 @@ const struct wpa_driver_ops wpa_driver_hostap_ops = { .get_inact_sec = hostap_get_inact_sec, .sta_clear_stats = hostap_sta_clear_stats, .get_hw_feature_data = hostap_get_hw_feature_data, + .set_wps_beacon_ie = hostap_set_wps_beacon_ie, + .set_wps_probe_resp_ie = hostap_set_wps_probe_resp_ie, }; diff --git a/hostapd/driver_madwifi.c b/hostapd/driver_madwifi.c index d7d604106..dfdf79c85 100644 --- a/hostapd/driver_madwifi.c +++ b/hostapd/driver_madwifi.c @@ -28,6 +28,16 @@ #include #include +#ifdef CONFIG_WPS +#ifdef IEEE80211_IOCTL_FILTERFRAME +#include + +#ifndef ETH_P_80211_RAW +#define ETH_P_80211_RAW 0x0019 +#endif +#endif /* IEEE80211_IOCTL_FILTERFRAME */ +#endif /* CONFIG_WPS */ + /* * Avoid conflicts with hostapd definitions by undefining couple of defines * from madwifi header files. @@ -58,6 +68,7 @@ #include "ieee802_11.h" #include "accounting.h" #include "common.h" +#include "wps_hostapd.h" struct madwifi_driver_data { @@ -729,6 +740,61 @@ madwifi_sta_disassoc(void *priv, const u8 *addr, int reason_code) return ret; } +#ifdef CONFIG_WPS +#ifdef IEEE80211_IOCTL_FILTERFRAME +static void madwifi_raw_receive(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct madwifi_driver_data *drv = ctx; + const struct ieee80211_mgmt *mgmt; + const u8 *end, *ie; + u16 fc; + size_t ie_len; + + /* Send Probe Request information to WPS processing */ + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req)) + return; + mgmt = (const struct ieee80211_mgmt *) buf; + + fc = le_to_host16(mgmt->frame_control); + if (WLAN_FC_GET_TYPE(fc) != WLAN_FC_TYPE_MGMT || + WLAN_FC_GET_STYPE(fc) != WLAN_FC_STYPE_PROBE_REQ) + return; + + end = buf + len; + ie = mgmt->u.probe_req.variable; + ie_len = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req)); + + hostapd_wps_probe_req_rx(drv->hapd, mgmt->sa, ie, ie_len); +} +#endif /* IEEE80211_IOCTL_FILTERFRAME */ +#endif /* CONFIG_WPS */ + +static int madwifi_receive_probe_req(struct madwifi_driver_data *drv) +{ + int ret = 0; +#ifdef CONFIG_WPS +#ifdef IEEE80211_IOCTL_FILTERFRAME + struct ieee80211req_set_filter filt; + + wpa_printf(MSG_DEBUG, "%s Enter", __func__); + filt.app_filterype = IEEE80211_FILTER_TYPE_PROBE_REQ; + + ret = set80211priv(drv, IEEE80211_IOCTL_FILTERFRAME, &filt, + sizeof(struct ieee80211req_set_filter)); + if (ret) + return ret; + + drv->sock_raw = l2_packet_init(drv->iface, NULL, ETH_P_80211_RAW, + madwifi_raw_receive, drv, 1); + if (drv->sock_raw == NULL) + return -1; +#endif /* IEEE80211_IOCTL_FILTERFRAME */ +#endif /* CONFIG_WPS */ + return ret; +} + static int madwifi_del_sta(struct madwifi_driver_data *drv, u8 addr[IEEE80211_ADDR_LEN]) { @@ -749,6 +815,45 @@ madwifi_del_sta(struct madwifi_driver_data *drv, u8 addr[IEEE80211_ADDR_LEN]) return 0; } +#ifdef CONFIG_WPS +static int +madwifi_set_wps_ie(void *priv, const u8 *ie, size_t len, u32 frametype) +{ + struct madwifi_driver_data *drv = priv; + u8 buf[256]; + struct ieee80211req_getset_appiebuf *beac_ie; + + wpa_printf(MSG_DEBUG, "%s buflen = %lu", __func__, + (unsigned long) len); + + beac_ie = (struct ieee80211req_getset_appiebuf *) buf; + beac_ie->app_frmtype = frametype; + beac_ie->app_buflen = len; + memcpy(&(beac_ie->app_buf[0]), ie, len); + + return set80211priv(drv, IEEE80211_IOCTL_SET_APPIEBUF, beac_ie, + sizeof(struct ieee80211req_getset_appiebuf) + len); +} + +static int +madwifi_set_wps_beacon_ie(const char *ifname, void *priv, const u8 *ie, + size_t len) +{ + return madwifi_set_wps_ie(priv, ie, len, IEEE80211_APPIE_FRAME_BEACON); +} + +static int +madwifi_set_wps_probe_resp_ie(const char *ifname, void *priv, const u8 *ie, + size_t len) +{ + return madwifi_set_wps_ie(priv, ie, len, + IEEE80211_APPIE_FRAME_PROBE_RESP); +} +#else /* CONFIG_WPS */ +#define madwifi_set_wps_beacon_ie NULL +#define madwifi_set_wps_probe_resp_ie NULL +#endif /* CONFIG_WPS */ + static int madwifi_process_wpa_ie(struct madwifi_driver_data *drv, struct sta_info *sta) { @@ -788,6 +893,15 @@ madwifi_process_wpa_ie(struct madwifi_driver_data *drv, struct sta_info *sta) #endif /* MADWIFI_NG */ ielen = iebuf[1]; if (ielen == 0) { +#ifdef CONFIG_WPS + if (hapd->conf->wps_state) { + wpa_printf(MSG_DEBUG, "STA did not include WPA/RSN IE " + "in (Re)Association Request - possible WPS " + "use"); + sta->flags |= WLAN_STA_MAYBE_WPS; + return 0; + } +#endif /* CONFIG_WPS */ printf("No WPA/RSN information element for station!?\n"); return -1; /* XXX not right */ } @@ -1254,6 +1368,8 @@ madwifi_init(struct hostapd_data *hapd) madwifi_set_iface_flags(drv, 0); /* mark down during setup */ madwifi_set_privacy(drv->iface, drv, 0); /* default to no privacy */ + madwifi_receive_probe_req(drv); + return drv; bad: if (drv->sock_xmit != NULL) @@ -1360,4 +1476,6 @@ const struct wpa_driver_ops wpa_driver_madwifi_ops = { .set_countermeasures = madwifi_set_countermeasures, .sta_clear_stats = madwifi_sta_clear_stats, .commit = madwifi_commit, + .set_wps_beacon_ie = madwifi_set_wps_beacon_ie, + .set_wps_probe_resp_ie = madwifi_set_wps_probe_resp_ie, }; diff --git a/hostapd/driver_test.c b/hostapd/driver_test.c index fb7bf058d..d524083e9 100644 --- a/hostapd/driver_test.c +++ b/hostapd/driver_test.c @@ -28,6 +28,7 @@ #include "l2_packet/l2_packet.h" #include "ieee802_11.h" #include "hw_features.h" +#include "wps_hostapd.h" struct test_client_socket { @@ -44,6 +45,10 @@ struct test_driver_bss { u8 bssid[ETH_ALEN]; u8 *ie; size_t ielen; + u8 *wps_beacon_ie; + size_t wps_beacon_ie_len; + u8 *wps_probe_resp_ie; + size_t wps_probe_resp_ie_len; u8 ssid[32]; size_t ssid_len; int privacy; @@ -62,6 +67,8 @@ struct test_driver_data { static void test_driver_free_bss(struct test_driver_bss *bss) { free(bss->ie); + free(bss->wps_beacon_ie); + free(bss->wps_probe_resp_ie); free(bss); } @@ -321,14 +328,44 @@ static int test_driver_send_mgmt_frame(void *priv, const void *buf, static void test_driver_scan(struct test_driver_data *drv, - struct sockaddr_un *from, socklen_t fromlen) + struct sockaddr_un *from, socklen_t fromlen, + char *data) { char buf[512], *pos, *end; int ret; struct test_driver_bss *bss; + u8 sa[ETH_ALEN]; + u8 ie[512]; + size_t ielen; + + /* data: optional [ ' ' | STA-addr | ' ' | IEs(hex) ] */ wpa_printf(MSG_DEBUG, "test_driver: SCAN"); + if (*data) { + if (*data != ' ' || + hwaddr_aton(data + 1, sa)) { + wpa_printf(MSG_DEBUG, "test_driver: Unexpected SCAN " + "command format"); + return; + } + + data += 18; + while (*data == ' ') + data++; + ielen = os_strlen(data) / 2; + if (ielen > sizeof(ie)) + ielen = sizeof(ie); + if (hexstr2bin(data, ie, ielen) < 0) + ielen = 0; + + wpa_printf(MSG_DEBUG, "test_driver: Scan from " MACSTR, + MAC2STR(sa)); + wpa_hexdump(MSG_MSGDUMP, "test_driver: scan IEs", ie, ielen); + + hostapd_wps_probe_req_rx(drv->hapd, sa, ie, ielen); + } + for (bss = drv->bss; bss; bss = bss->next) { pos = buf; end = buf + sizeof(buf); @@ -346,6 +383,8 @@ static void test_driver_scan(struct test_driver_data *drv, return; pos += ret; pos += wpa_snprintf_hex(pos, end - pos, bss->ie, bss->ielen); + pos += wpa_snprintf_hex(pos, end - pos, bss->wps_probe_resp_ie, + bss->wps_probe_resp_ie_len); if (bss->privacy) { ret = snprintf(pos, end - pos, " PRIVACY"); @@ -653,8 +692,8 @@ static void test_driver_receive_unix(int sock, void *eloop_ctx, void *sock_ctx) wpa_printf(MSG_DEBUG, "test_driver: received %u bytes", res); - if (strcmp(buf, "SCAN") == 0) { - test_driver_scan(drv, &from, fromlen); + if (strncmp(buf, "SCAN", 4) == 0) { + test_driver_scan(drv, &from, fromlen, buf + 4); } else if (strncmp(buf, "ASSOC ", 6) == 0) { test_driver_assoc(drv, &from, fromlen, buf + 6); } else if (strcmp(buf, "DISASSOC") == 0) { @@ -717,6 +756,66 @@ static int test_driver_set_generic_elem(const char *ifname, void *priv, } +static int test_driver_set_wps_beacon_ie(const char *ifname, void *priv, + const u8 *ie, size_t len) +{ + struct test_driver_data *drv = priv; + struct test_driver_bss *bss; + + bss = test_driver_get_bss(drv, ifname); + if (bss == NULL) + return -1; + + free(bss->wps_beacon_ie); + + if (ie == NULL) { + bss->wps_beacon_ie = NULL; + bss->wps_beacon_ie_len = 0; + return 0; + } + + bss->wps_beacon_ie = malloc(len); + if (bss->wps_beacon_ie == NULL) { + bss->wps_beacon_ie_len = 0; + return -1; + } + + memcpy(bss->wps_beacon_ie, ie, len); + bss->wps_beacon_ie_len = len; + return 0; +} + + +static int test_driver_set_wps_probe_resp_ie(const char *ifname, void *priv, + const u8 *ie, size_t len) +{ + struct test_driver_data *drv = priv; + struct test_driver_bss *bss; + + bss = test_driver_get_bss(drv, ifname); + if (bss == NULL) + return -1; + + free(bss->wps_probe_resp_ie); + + if (ie == NULL) { + bss->wps_probe_resp_ie = NULL; + bss->wps_probe_resp_ie_len = 0; + return 0; + } + + bss->wps_probe_resp_ie = malloc(len); + if (bss->wps_probe_resp_ie == NULL) { + bss->wps_probe_resp_ie_len = 0; + return -1; + } + + memcpy(bss->wps_probe_resp_ie, ie, len); + bss->wps_probe_resp_ie_len = len; + return 0; +} + + static int test_driver_sta_deauth(void *priv, const u8 *addr, int reason) { struct test_driver_data *drv = priv; @@ -1162,4 +1261,6 @@ const struct wpa_driver_ops wpa_driver_test_ops = { .set_sta_vlan = test_driver_set_sta_vlan, .sta_add = test_driver_sta_add, .send_ether = test_driver_send_ether, + .set_wps_beacon_ie = test_driver_set_wps_beacon_ie, + .set_wps_probe_resp_ie = test_driver_set_wps_probe_resp_ie, }; diff --git a/hostapd/eapol_sm.c b/hostapd/eapol_sm.c index 4f5247535..b333db73c 100644 --- a/hostapd/eapol_sm.c +++ b/hostapd/eapol_sm.c @@ -812,6 +812,7 @@ eapol_auth_alloc(struct eapol_authenticator *eapol, const u8 *addr, eap_conf.pac_key_refresh_time = eapol->conf.pac_key_refresh_time; eap_conf.eap_sim_aka_result_ind = eapol->conf.eap_sim_aka_result_ind; eap_conf.tnc = eapol->conf.tnc; + eap_conf.wps = eapol->conf.wps; sm->eap = eap_server_sm_init(sm, &eapol_cb, &eap_conf); if (sm->eap == NULL) { eapol_auth_free(sm); @@ -1263,6 +1264,7 @@ static int eapol_auth_conf_clone(struct eapol_auth_config *dst, dst->pac_key_refresh_time = src->pac_key_refresh_time; dst->eap_sim_aka_result_ind = src->eap_sim_aka_result_ind; dst->tnc = src->tnc; + dst->wps = src->wps; return 0; } diff --git a/hostapd/eapol_sm.h b/hostapd/eapol_sm.h index f2ca800be..7a13e8e7c 100644 --- a/hostapd/eapol_sm.h +++ b/hostapd/eapol_sm.h @@ -56,6 +56,7 @@ struct eapol_auth_config { int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; + struct wps_context *wps; /* * Pointer to hostapd data. This is a temporary workaround for diff --git a/hostapd/hostapd.c b/hostapd/hostapd.c index a4fad98a7..937e48a9e 100644 --- a/hostapd/hostapd.c +++ b/hostapd/hostapd.c @@ -44,11 +44,15 @@ #include "eap_server/tncs.h" #include "version.h" #include "l2_packet/l2_packet.h" +#include "wps_hostapd.h" static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); +static int hostapd_flush_old_stations(struct hostapd_data *hapd); +static int hostapd_setup_wpa(struct hostapd_data *hapd); +static int hostapd_setup_encryption(char *iface, struct hostapd_data *hapd); struct hapd_interfaces { size_t count; @@ -326,41 +330,72 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, } +int hostapd_reload_config(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + struct hostapd_config *newconf, *oldconf; + struct wpa_auth_config wpa_auth_conf; + + newconf = hostapd_config_read(iface->config_fname); + if (newconf == NULL) + return -1; + + /* + * Deauthenticate all stations since the new configuration may not + * allow them to use the BSS anymore. + */ + hostapd_flush_old_stations(hapd); + + /* TODO: update dynamic data based on changed configuration + * items (e.g., open/close sockets, etc.) */ + radius_client_flush(hapd->radius, 0); + + oldconf = hapd->iconf; + hapd->iconf = newconf; + hapd->conf = &newconf->bss[0]; + iface->conf = newconf; + + if (hostapd_setup_wpa_psk(hapd->conf)) { + wpa_printf(MSG_ERROR, "Failed to re-configure WPA PSK " + "after reloading configuration"); + } + + if (hapd->conf->wpa && hapd->wpa_auth == NULL) + hostapd_setup_wpa(hapd); + else if (hapd->conf->wpa) { + hostapd_wpa_auth_conf(&newconf->bss[0], &wpa_auth_conf); + wpa_reconfig(hapd->wpa_auth, &wpa_auth_conf); + } else if (hapd->wpa_auth) { + wpa_deinit(hapd->wpa_auth); + hapd->wpa_auth = NULL; + hostapd_set_privacy(hapd, 0); + hostapd_setup_encryption(hapd->conf->iface, hapd); + } + + ieee802_11_set_beacon(hapd); + + hostapd_config_free(oldconf); + + wpa_printf(MSG_DEBUG, "Reconfigured interface %s", hapd->conf->iface); + + return 0; +} + + #ifndef CONFIG_NATIVE_WINDOWS static void handle_reload(int sig, void *eloop_ctx, void *signal_ctx) { struct hapd_interfaces *hapds = (struct hapd_interfaces *) eloop_ctx; - struct hostapd_config *newconf; size_t i; - struct wpa_auth_config wpa_auth_conf; printf("Signal %d received - reloading configuration\n", sig); for (i = 0; i < hapds->count; i++) { - struct hostapd_data *hapd = hapds->iface[i]->bss[0]; - newconf = hostapd_config_read(hapds->iface[i]->config_fname); - if (newconf == NULL) { + if (hostapd_reload_config(hapds->iface[i]) < 0) { printf("Failed to read new configuration file - " "continuing with old.\n"); continue; } - /* TODO: update dynamic data based on changed configuration - * items (e.g., open/close sockets, remove stations added to - * deny list, etc.) */ - radius_client_flush(hapd->radius, 0); - hostapd_config_free(hapd->iconf); - - hostapd_wpa_auth_conf(&newconf->bss[0], &wpa_auth_conf); - wpa_reconfig(hapd->wpa_auth, &wpa_auth_conf); - - hapd->iconf = newconf; - hapd->conf = &newconf->bss[0]; - hapds->iface[i]->conf = newconf; - - if (hostapd_setup_wpa_psk(hapd->conf)) { - wpa_printf(MSG_ERROR, "Failed to re-configure WPA PSK " - "after reloading configuration"); - } } } @@ -400,7 +435,7 @@ static void hostapd_dump_state(struct hostapd_data *hapd) fprintf(f, "\nSTA=" MACSTR "\n", MAC2STR(sta->addr)); fprintf(f, - " AID=%d flags=0x%x %s%s%s%s%s%s%s%s%s%s%s%s\n" + " AID=%d flags=0x%x %s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" " capability=0x%x listen_interval=%d\n", sta->aid, sta->flags, @@ -418,6 +453,8 @@ static void hostapd_dump_state(struct hostapd_data *hapd) (sta->flags & WLAN_STA_PREAUTH ? "[PREAUTH]" : ""), (sta->flags & WLAN_STA_WME ? "[WME]" : ""), (sta->flags & WLAN_STA_MFP ? "[MFP]" : ""), + (sta->flags & WLAN_STA_WPS ? "[WPS]" : ""), + (sta->flags & WLAN_STA_MAYBE_WPS ? "[MAYBE_WPS]" : ""), (sta->flags & WLAN_STA_NONERP ? "[NonERP]" : ""), sta->capability, sta->listen_interval); @@ -588,6 +625,8 @@ static void hostapd_cleanup(struct hostapd_data *hapd) l2_packet_deinit(hapd->l2); #endif /* CONFIG_IEEE80211R */ + hostapd_deinit_wps(hapd); + hostapd_wireless_event_deinit(hapd); #ifdef EAP_TLS_FUNCS @@ -1178,6 +1217,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd, srv.pac_key_refresh_time = conf->pac_key_refresh_time; srv.eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; srv.tnc = conf->tnc; + srv.wps = hapd->wps; srv.ipv6 = conf->radius_server_ipv6; srv.get_eap_user = hostapd_radius_get_eap_user; @@ -1310,6 +1350,8 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) printf("ACL initialization failed.\n"); return -1; } + if (hostapd_init_wps(hapd, conf)) + return -1; if (ieee802_1x_init(hapd)) { printf("IEEE 802.1X initialization failed.\n"); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 8bd2c5676..2a875cf85 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -822,6 +822,85 @@ own_ip_addr=127.0.0.1 #ap_table_expiration_time=3600 +##### Wi-Fi Protected Setup (WPS) ############################################# + +# WPS state +# 0 = WPS disabled (default) +# 1 = WPS enabled, not configured +# 2 = WPS enabled, configured +#wps_state=2 + +# AP can be configured into a locked state where new WPS Registrar are not +# accepted, but previously authorized Registrars (including the internal one) +# can continue to add new Enrollees. +#ap_setup_locked=1 + +# Universally Unique IDentifier (UUID; see RFC 4122) of the device +# This value is used as the UUID for the internal WPS Registrar. If the AP +# is also using UPnP, this value should be set to the device's UPnP UUID. +#uuid=12345678-9abc-def0-1234-56789abcdef0 + +# Note: If wpa_psk_file is set, WPS is used to generate random, per-device PSKs +# that will be appended to the wpa_psk_file. If wpa_psk_file is not set, the +# default PSK (wpa_psk/wpa_passphrase) will be delivered to Enrollees. Use of +# per-device PSKs is recommended as the more secure option (i.e., make sure to +# set wpa_psk_file when using WPS with WPA-PSK). + +# When an Enrollee requests access to the network with PIN method, the Enrollee +# PIN will need to be entered for the Registrar. PIN request notifications are +# sent to hostapd ctrl_iface monitor. In addition, they can be written to a +# text file that could be used, e.g., to populate the AP administration UI with +# pending PIN requests. If the following variable is set, the PIN requests will +# be written to the configured file. +#wps_pin_requests=/var/run/hostapd_wps_pin_requests + +# Device Name +# User-friendly description of device; up to 32 octets encoded in UTF-8 +#device_name=Wireless AP + +# Manufacturer +# The manufacturer of the device (up to 64 ASCII characters) +#manufacturer=Company + +# Model Name +# Model of the device (up to 32 ASCII characters) +#model_name=WAP + +# Model Number +# Additional device description (up to 32 ASCII characters) +#model_number=123 + +# Serial Number +# Serial number of the device (up to 32 characters) +#serial_number=12345 + +# Primary Device Type +# Used format: -- +# categ = Category as an integer value +# OUI = OUI and type octet as a 4-octet hex-encoded value; 0050F204 for +# default WPS OUI +# subcateg = OUI-specific Sub Category as an integer value +# Examples: +# 1-0050F204-1 (Computer / PC) +# 1-0050F204-2 (Computer / Server) +# 5-0050F204-1 (Storage / NAS) +# 6-0050F204-1 (Network Infrastructure / AP) +#device_type=6-0050F204-1 + +# OS Version +# 4-octet operating system version number (hex string) +#os_version=01020300 + +# Config Methods +# List of the supported configuration methods +#config_methods=label display push_button keypad + +# Access point PIN for initial configuration and adding Registrars +# If not set, hostapd will not allow external WPS Registrars to control the +# access point. +#ap_pin=12345670 + + ##### Multiple BSSID support ################################################## # # Above configuration is using the default interface (wlan#, or multi-SSID VLAN diff --git a/hostapd/hostapd.h b/hostapd/hostapd.h index 74a30dcad..85bcf0b16 100644 --- a/hostapd/hostapd.h +++ b/hostapd/hostapd.h @@ -163,6 +163,14 @@ struct hostapd_data { #endif /* CONFIG_FULL_DYNAMIC_VLAN */ struct l2_packet_data *l2; + struct wps_context *wps; + +#ifdef CONFIG_WPS + u8 *wps_beacon_ie; + size_t wps_beacon_ie_len; + u8 *wps_probe_resp_ie; + size_t wps_probe_resp_ie_len; +#endif /* CONFIG_WPS */ }; @@ -222,5 +230,6 @@ struct hostapd_iface { void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta, int reassoc); +int hostapd_reload_config(struct hostapd_iface *iface); #endif /* HOSTAPD_H */ diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index a3cef1bcd..e5285ae83 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -83,6 +83,10 @@ static const char *commands_help = " sta get MIB variables for one station\n" " all_sta get MIB variables for all stations\n" " new_sta add a new station\n" +#ifdef CONFIG_WPS +" wps_pin add WPS Enrollee PIN (Device Password)\n" +" wps_pbc indicate button pushed to initiate PBC\n" +#endif /* CONFIG_WPS */ " help show this usage help\n" " interface [ifname] show interfaces/select interface\n" " level change debug level\n" @@ -230,6 +234,29 @@ static int hostapd_cli_cmd_new_sta(struct wpa_ctrl *ctrl, int argc, } +#ifdef CONFIG_WPS +static int hostapd_cli_cmd_wps_pin(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[64]; + if (argc != 2) { + printf("Invalid 'wps_pin' command - exactly two arguments, " + "UUID and PIN, are required.\n"); + return -1; + } + snprintf(buf, sizeof(buf), "WPS_PIN %s %s", argv[0], argv[1]); + return wpa_ctrl_command(ctrl, buf); +} + + +static int hostapd_cli_cmd_wps_pbc(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + return wpa_ctrl_command(ctrl, "WPS_PBC"); +} +#endif /* CONFIG_WPS */ + + static int wpa_ctrl_command_sta(struct wpa_ctrl *ctrl, char *cmd, char *addr, size_t addr_len) { @@ -378,6 +405,10 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "sta", hostapd_cli_cmd_sta }, { "all_sta", hostapd_cli_cmd_all_sta }, { "new_sta", hostapd_cli_cmd_new_sta }, +#ifdef CONFIG_WPS + { "wps_pin", hostapd_cli_cmd_wps_pin }, + { "wps_pbc", hostapd_cli_cmd_wps_pbc }, +#endif /* CONFIG_WPS */ { "help", hostapd_cli_cmd_help }, { "interface", hostapd_cli_cmd_interface }, { "level", hostapd_cli_cmd_level }, diff --git a/hostapd/ieee802_11.c b/hostapd/ieee802_11.c index 19dd6809d..0d69d1d0e 100644 --- a/hostapd/ieee802_11.c +++ b/hostapd/ieee802_11.c @@ -825,6 +825,21 @@ static void handle_assoc(struct hostapd_data *hapd, wpa_ie = NULL; wpa_ie_len = 0; } +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && wpa_ie == NULL) { + if (elems.wps_ie) { + wpa_printf(MSG_DEBUG, "STA included WPS IE in " + "(Re)Association Request - assume WPS is " + "used"); + sta->flags |= WLAN_STA_WPS; + } else { + wpa_printf(MSG_DEBUG, "STA did not include WPA/RSN IE " + "in (Re)Association Request - possible WPS " + "use"); + sta->flags |= WLAN_STA_MAYBE_WPS; + } + } else +#endif /* CONFIG_WPS */ if (hapd->conf->wpa && wpa_ie == NULL) { printf("STA " MACSTR ": No WPA/RSN IE in association " "request\n", MAC2STR(sta->addr)); diff --git a/hostapd/ieee802_1x.c b/hostapd/ieee802_1x.c index 59c2d8c7a..5d3cbfb2d 100644 --- a/hostapd/ieee802_1x.c +++ b/hostapd/ieee802_1x.c @@ -670,7 +670,8 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, u16 datalen; struct rsn_pmksa_cache_entry *pmksa; - if (!hapd->conf->ieee802_1x && !hapd->conf->wpa) + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && + !hapd->conf->wps_state) return; wpa_printf(MSG_DEBUG, "IEEE 802.1X: %lu bytes from " MACSTR, @@ -718,7 +719,8 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, return; } - if (!hapd->conf->ieee802_1x || + if ((!hapd->conf->ieee802_1x && + !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS))) || wpa_key_mgmt_wpa_psk(wpa_auth_sta_key_mgmt(sta->wpa_sm))) return; @@ -728,6 +730,18 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, sta); if (!sta->eapol_sm) return; + +#ifdef CONFIG_WPS + if (!hapd->conf->ieee802_1x && + ((sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)) == + WLAN_STA_MAYBE_WPS)) { + /* + * Delay EAPOL frame transmission until a possible WPS + * STA initiates the handshake with EAPOL-Start. + */ + sta->eapol_sm->flags |= EAPOL_SM_WAIT_START; + } +#endif /* CONFIG_WPS */ } /* since we support version 1, we can ignore version field and proceed @@ -801,6 +815,18 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) int reassoc = 1; int force_1x = 0; +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && + (sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS))) { + /* + * Need to enable IEEE 802.1X/EAPOL state machines for possible + * WPS handshake even if IEEE 802.1X/EAPOL is not used for + * authentication in this BSS. + */ + force_1x = 1; + } +#endif /* CONFIG_WPS */ + if ((!force_1x && !hapd->conf->ieee802_1x) || wpa_key_mgmt_wpa_psk(wpa_auth_sta_key_mgmt(sta->wpa_sm))) return; @@ -821,6 +847,16 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) reassoc = 0; } +#ifdef CONFIG_WPS + if (!hapd->conf->ieee802_1x && !(sta->flags & WLAN_STA_WPS)) { + /* + * Delay EAPOL frame transmission until a possible WPS + * initiates the handshake with EAPOL-Start. + */ + sta->eapol_sm->flags |= EAPOL_SM_WAIT_START; + } +#endif /* CONFIG_WPS */ + sta->eapol_sm->eap_if->portEnabled = TRUE; pmksa = wpa_auth_sta_get_pmksa(sta->wpa_sm); @@ -1613,6 +1649,7 @@ int ieee802_1x_init(struct hostapd_data *hapd) conf.pac_key_refresh_time = hapd->conf->pac_key_refresh_time; conf.eap_sim_aka_result_ind = hapd->conf->eap_sim_aka_result_ind; conf.tnc = hapd->conf->tnc; + conf.wps = hapd->wps; os_memset(&cb, 0, sizeof(cb)); cb.eapol_send = ieee802_1x_eapol_send; diff --git a/hostapd/wpa.c b/hostapd/wpa.c index cc01f0282..b1f35ad4b 100644 --- a/hostapd/wpa.c +++ b/hostapd/wpa.c @@ -456,11 +456,11 @@ int wpa_reconfig(struct wpa_authenticator *wpa_auth, return 0; os_memcpy(&wpa_auth->conf, conf, sizeof(*conf)); - /* - * TODO: - * Disassociate stations if configuration changed - * Update WPA/RSN IE - */ + if (wpa_auth_gen_wpa_ie(wpa_auth)) { + wpa_printf(MSG_ERROR, "Could not generate WPA IE."); + return -1; + } + return 0; } diff --git a/hostapd/wps_hostapd.c b/hostapd/wps_hostapd.c new file mode 100644 index 000000000..be1c886c6 --- /dev/null +++ b/hostapd/wps_hostapd.c @@ -0,0 +1,604 @@ +/* + * hostapd / WPS integration + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "hostapd.h" +#include "driver.h" +#include "eloop.h" +#include "uuid.h" +#include "wpa_ctrl.h" +#include "ctrl_iface.h" +#include "ieee802_11_defs.h" +#include "wps/wps.h" +#include "wps/wps_defs.h" +#include "wps/wps_dev_attr.h" +#include "wps_hostapd.h" + + +static int hostapd_wps_new_psk_cb(void *ctx, const u8 *mac_addr, const u8 *psk, + size_t psk_len) +{ + struct hostapd_data *hapd = ctx; + struct hostapd_wpa_psk *p; + struct hostapd_ssid *ssid = &hapd->conf->ssid; + + wpa_printf(MSG_DEBUG, "Received new WPA/WPA2-PSK from WPS for STA " + MACSTR, MAC2STR(mac_addr)); + wpa_hexdump_key(MSG_DEBUG, "Per-device PSK", psk, psk_len); + + if (psk_len != PMK_LEN) { + wpa_printf(MSG_DEBUG, "Unexpected PSK length %lu", + (unsigned long) psk_len); + return -1; + } + + /* Add the new PSK to runtime PSK list */ + p = os_zalloc(sizeof(*p)); + if (p == NULL) + return -1; + os_memcpy(p->addr, mac_addr, ETH_ALEN); + os_memcpy(p->psk, psk, PMK_LEN); + + p->next = ssid->wpa_psk; + ssid->wpa_psk = p; + + if (ssid->wpa_psk_file) { + FILE *f; + char hex[PMK_LEN * 2 + 1]; + /* Add the new PSK to PSK list file */ + f = fopen(ssid->wpa_psk_file, "a"); + if (f == NULL) { + wpa_printf(MSG_DEBUG, "Failed to add the PSK to " + "'%s'", ssid->wpa_psk_file); + return -1; + } + + wpa_snprintf_hex(hex, sizeof(hex), psk, psk_len); + fprintf(f, MACSTR " %s\n", MAC2STR(mac_addr), hex); + fclose(f); + } + + return 0; +} + + +static int hostapd_wps_set_ie_cb(void *ctx, const u8 *beacon_ie, + size_t beacon_ie_len, const u8 *probe_resp_ie, + size_t probe_resp_ie_len) +{ + struct hostapd_data *hapd = ctx; + + os_free(hapd->wps_beacon_ie); + if (beacon_ie_len == 0) { + hapd->wps_beacon_ie = NULL; + hapd->wps_beacon_ie_len = 0; + } else { + hapd->wps_beacon_ie = os_malloc(beacon_ie_len); + if (hapd->wps_beacon_ie == NULL) { + hapd->wps_beacon_ie_len = 0; + return -1; + } + os_memcpy(hapd->wps_beacon_ie, beacon_ie, beacon_ie_len); + hapd->wps_beacon_ie_len = beacon_ie_len; + } + hostapd_set_wps_beacon_ie(hapd, hapd->wps_beacon_ie, + hapd->wps_beacon_ie_len); + + os_free(hapd->wps_probe_resp_ie); + if (probe_resp_ie_len == 0) { + hapd->wps_probe_resp_ie = NULL; + hapd->wps_probe_resp_ie_len = 0; + } else { + hapd->wps_probe_resp_ie = os_malloc(probe_resp_ie_len); + if (hapd->wps_probe_resp_ie == NULL) { + hapd->wps_probe_resp_ie_len = 0; + return -1; + } + os_memcpy(hapd->wps_probe_resp_ie, probe_resp_ie, + probe_resp_ie_len); + hapd->wps_probe_resp_ie_len = probe_resp_ie_len; + } + hostapd_set_wps_probe_resp_ie(hapd, hapd->wps_probe_resp_ie, + hapd->wps_probe_resp_ie_len); + + return 0; +} + + +static void hostapd_wps_pin_needed_cb(void *ctx, const u8 *uuid_e, + const struct wps_device_data *dev) +{ + struct hostapd_data *hapd = ctx; + char uuid[40], txt[400]; + int len; + if (uuid_bin2str(uuid_e, uuid, sizeof(uuid))) + return; + wpa_printf(MSG_DEBUG, "WPS: PIN needed for E-UUID %s", uuid); + len = os_snprintf(txt, sizeof(txt), WPS_EVENT_PIN_NEEDED + "%s " MACSTR " [%s|%s|%s|%s|%s|%d-%08X-%d]", + uuid, MAC2STR(dev->mac_addr), dev->device_name, + dev->manufacturer, dev->model_name, + dev->model_number, dev->serial_number, + dev->categ, dev->oui, dev->sub_categ); + if (len > 0 && len < (int) sizeof(txt)) + hostapd_ctrl_iface_send(hapd, MSG_INFO, txt, len); + + if (hapd->conf->wps_pin_requests) { + FILE *f; + struct os_time t; + f = fopen(hapd->conf->wps_pin_requests, "a"); + if (f == NULL) + return; + os_get_time(&t); + fprintf(f, "%ld\t%s\t" MACSTR "\t%s\t%s\t%s\t%s\t%s" + "\t%d-%08X-%d\n", + t.sec, uuid, MAC2STR(dev->mac_addr), dev->device_name, + dev->manufacturer, dev->model_name, dev->model_number, + dev->serial_number, + dev->categ, dev->oui, dev->sub_categ); + fclose(f); + } +} + + +static int str_starts(const char *str, const char *start) +{ + return os_strncmp(str, start, os_strlen(start)) == 0; +} + + +static void wps_reload_config(void *eloop_data, void *user_ctx) +{ + struct hostapd_iface *iface = eloop_data; + + wpa_printf(MSG_DEBUG, "WPS: Reload configuration data"); + if (hostapd_reload_config(iface) < 0) { + wpa_printf(MSG_WARNING, "WPS: Failed to reload the updated " + "configuration"); + } +} + + +static int hostapd_wps_cred_cb(void *ctx, const struct wps_credential *cred) +{ + struct hostapd_data *hapd = ctx; + FILE *oconf, *nconf; + size_t len, i; + char *tmp_fname; + char buf[1024]; + int multi_bss; + int wpa; + + wpa_printf(MSG_DEBUG, "WPS: Received new AP Settings"); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: SSID", cred->ssid, cred->ssid_len); + wpa_printf(MSG_DEBUG, "WPS: Authentication Type 0x%x", + cred->auth_type); + wpa_printf(MSG_DEBUG, "WPS: Encryption Type 0x%x", cred->encr_type); + wpa_printf(MSG_DEBUG, "WPS: Network Key Index %d", cred->key_idx); + wpa_hexdump_key(MSG_DEBUG, "WPS: Network Key", + cred->key, cred->key_len); + wpa_printf(MSG_DEBUG, "WPS: MAC Address " MACSTR, + MAC2STR(cred->mac_addr)); + + hostapd_ctrl_iface_send(hapd, MSG_INFO, WPS_EVENT_NEW_AP_SETTINGS, + os_strlen(WPS_EVENT_NEW_AP_SETTINGS)); + + len = os_strlen(hapd->iface->config_fname) + 5; + tmp_fname = os_malloc(len); + if (tmp_fname == NULL) + return -1; + os_snprintf(tmp_fname, len, "%s-new", hapd->iface->config_fname); + + oconf = fopen(hapd->iface->config_fname, "r"); + if (oconf == NULL) { + wpa_printf(MSG_WARNING, "WPS: Could not open current " + "configuration file"); + os_free(tmp_fname); + return -1; + } + + nconf = fopen(tmp_fname, "w"); + if (nconf == NULL) { + wpa_printf(MSG_WARNING, "WPS: Could not write updated " + "configuration file"); + os_free(tmp_fname); + fclose(oconf); + return -1; + } + + fprintf(nconf, "# WPS configuration - START\n"); + + fprintf(nconf, "wps_state=2\n"); + + fprintf(nconf, "ssid="); + for (i = 0; i < cred->ssid_len; i++) + fputc(cred->ssid[i], nconf); + fprintf(nconf, "\n"); + + if ((cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) && + (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK))) + wpa = 3; + else if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) + wpa = 2; + else if (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK)) + wpa = 1; + else + wpa = 0; + + if (wpa) { + char *prefix; + fprintf(nconf, "wpa=%d\n", wpa); + + fprintf(nconf, "wpa_key_mgmt="); + prefix = ""; + if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA)) { + fprintf(nconf, "WPA-EAP"); + prefix = " "; + } + if (cred->auth_type & (WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK)) + fprintf(nconf, "%sWPA-PSK", prefix); + fprintf(nconf, "\n"); + + fprintf(nconf, "wpa_pairwise="); + prefix = ""; + if (cred->encr_type & WPS_ENCR_AES) { + fprintf(nconf, "CCMP"); + prefix = " "; + } + if (cred->encr_type & WPS_ENCR_TKIP) { + fprintf(nconf, "%sTKIP", prefix); + } + fprintf(nconf, "\n"); + + if (cred->key_len >= 8 && cred->key_len < 64) { + fprintf(nconf, "wpa_passphrase="); + for (i = 0; i < cred->key_len; i++) + fputc(cred->key[i], nconf); + fprintf(nconf, "\n"); + } else if (cred->key_len == 64) { + fprintf(nconf, "wpa_psk="); + for (i = 0; i < cred->key_len; i++) + fputc(cred->key[i], nconf); + fprintf(nconf, "\n"); + } else { + wpa_printf(MSG_WARNING, "WPS: Invalid key length %lu " + "for WPA/WPA2", + (unsigned long) cred->key_len); + } + + fprintf(nconf, "auth_algs=1\n"); + } else { + if ((cred->auth_type & WPS_AUTH_OPEN) && + (cred->auth_type & WPS_AUTH_SHARED)) + fprintf(nconf, "auth_algs=3\n"); + else if (cred->auth_type & WPS_AUTH_SHARED) + fprintf(nconf, "auth_algs=2\n"); + else + fprintf(nconf, "auth_algs=1\n"); + + if (cred->encr_type & WPS_ENCR_WEP && cred->key_idx < 4) { + fprintf(nconf, "wep_default_key=%d\n", cred->key_idx); + fprintf(nconf, "wep_key%d=", cred->key_idx); + if (cred->key_len != 10 && cred->key_len != 26) + fputc('"', nconf); + for (i = 0; i < cred->key_len; i++) + fputc(cred->key[i], nconf); + if (cred->key_len != 10 && cred->key_len != 26) + fputc('"', nconf); + fprintf(nconf, "\n"); + } + } + + fprintf(nconf, "# WPS configuration - END\n"); + + multi_bss = 0; + while (fgets(buf, sizeof(buf), oconf)) { + if (os_strncmp(buf, "bss=", 4) == 0) + multi_bss = 1; + if (!multi_bss && + (str_starts(buf, "ssid=") || + str_starts(buf, "auth_algs=") || + str_starts(buf, "wps_state=") || + str_starts(buf, "wpa=") || + str_starts(buf, "wpa_psk=") || + str_starts(buf, "wpa_pairwise=") || + str_starts(buf, "rsn_pairwise=") || + str_starts(buf, "wpa_key_mgmt=") || + str_starts(buf, "wpa_passphrase="))) { + fprintf(nconf, "#WPS# %s", buf); + } else + fprintf(nconf, "%s", buf); + } + + fclose(nconf); + fclose(oconf); + + if (rename(tmp_fname, hapd->iface->config_fname) < 0) { + wpa_printf(MSG_WARNING, "WPS: Failed to rename the updated " + "configuration file: %s", strerror(errno)); + os_free(tmp_fname); + return -1; + } + + os_free(tmp_fname); + + /* Schedule configuration reload after short period of time to allow + * EAP-WSC to be finished. + */ + eloop_register_timeout(0, 100000, wps_reload_config, hapd->iface, + NULL); + + /* TODO: dualband AP may need to update multiple configuration files */ + + wpa_printf(MSG_DEBUG, "WPS: AP configuration updated"); + + return 0; +} + + +static void hostapd_wps_clear_ies(struct hostapd_data *hapd) +{ + os_free(hapd->wps_beacon_ie); + hapd->wps_beacon_ie = NULL; + hapd->wps_beacon_ie_len = 0; + hostapd_set_wps_beacon_ie(hapd, NULL, 0); + + os_free(hapd->wps_probe_resp_ie); + hapd->wps_probe_resp_ie = NULL; + hapd->wps_probe_resp_ie_len = 0; + hostapd_set_wps_probe_resp_ie(hapd, NULL, 0); +} + + +int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf) +{ + struct wps_context *wps; + struct wps_registrar_config cfg; + + if (conf->wps_state == 0) { + hostapd_wps_clear_ies(hapd); + return 0; + } + + wps = os_zalloc(sizeof(*wps)); + if (wps == NULL) + return -1; + + wps->cred_cb = hostapd_wps_cred_cb; + wps->cb_ctx = hapd; + + os_memset(&cfg, 0, sizeof(cfg)); + wps->wps_state = hapd->conf->wps_state; + wps->ap_setup_locked = hapd->conf->ap_setup_locked; + os_memcpy(wps->uuid, hapd->conf->uuid, UUID_LEN); + wps->ssid_len = hapd->conf->ssid.ssid_len; + os_memcpy(wps->ssid, hapd->conf->ssid.ssid, wps->ssid_len); + wps->ap = 1; + os_memcpy(wps->dev.mac_addr, hapd->own_addr, ETH_ALEN); + wps->dev.device_name = hapd->conf->device_name; + wps->dev.manufacturer = hapd->conf->manufacturer; + wps->dev.model_name = hapd->conf->model_name; + wps->dev.model_number = hapd->conf->model_number; + wps->dev.serial_number = hapd->conf->serial_number; + if (hapd->conf->config_methods) { + char *m = hapd->conf->config_methods; + if (os_strstr(m, "label")) + wps->config_methods |= WPS_CONFIG_LABEL; + if (os_strstr(m, "display")) + wps->config_methods |= WPS_CONFIG_DISPLAY; + if (os_strstr(m, "push_button")) + wps->config_methods |= WPS_CONFIG_PUSHBUTTON; + if (os_strstr(m, "keypad")) + wps->config_methods |= WPS_CONFIG_KEYPAD; + } + if (hapd->conf->device_type) { + char *pos; + u8 oui[4]; + /* -- */ + wps->dev.categ = atoi(hapd->conf->device_type); + pos = os_strchr(hapd->conf->device_type, '-'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "WPS: Invalid device_type"); + os_free(wps); + return -1; + } + pos++; + if (hexstr2bin(pos, oui, 4)) { + wpa_printf(MSG_ERROR, "WPS: Invalid device_type OUI"); + os_free(wps); + return -1; + } + wps->dev.oui = WPA_GET_BE32(oui); + pos = os_strchr(pos, '-'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "WPS: Invalid device_type"); + os_free(wps); + return -1; + } + pos++; + wps->dev.sub_categ = atoi(pos); + } + wps->dev.os_version = WPA_GET_BE32(hapd->conf->os_version); + + if (conf->wpa & WPA_PROTO_RSN) { + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) + wps->auth_types |= WPS_AUTH_WPA2PSK; + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) + wps->auth_types |= WPS_AUTH_WPA2; + + if (conf->rsn_pairwise & WPA_CIPHER_CCMP) + wps->encr_types |= WPS_ENCR_AES; + if (conf->rsn_pairwise & WPA_CIPHER_TKIP) + wps->encr_types |= WPS_ENCR_TKIP; + } + + if (conf->wpa & WPA_PROTO_WPA) { + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) + wps->auth_types |= WPS_AUTH_WPAPSK; + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) + wps->auth_types |= WPS_AUTH_WPA; + + if (conf->wpa_pairwise & WPA_CIPHER_CCMP) + wps->encr_types |= WPS_ENCR_AES; + if (conf->wpa_pairwise & WPA_CIPHER_TKIP) + wps->encr_types |= WPS_ENCR_TKIP; + } + + if (conf->ssid.security_policy == SECURITY_PLAINTEXT) { + wps->encr_types |= WPS_ENCR_NONE; + wps->auth_types |= WPS_AUTH_OPEN; + } else if (conf->ssid.security_policy == SECURITY_STATIC_WEP) { + wps->encr_types |= WPS_ENCR_WEP; + if (conf->auth_algs & WPA_AUTH_ALG_OPEN) + wps->auth_types |= WPS_AUTH_OPEN; + if (conf->auth_algs & WPA_AUTH_ALG_SHARED) + wps->auth_types |= WPS_AUTH_SHARED; + } else if (conf->ssid.security_policy == SECURITY_IEEE_802_1X) { + wps->auth_types |= WPS_AUTH_OPEN; + if (conf->default_wep_key_len) + wps->encr_types |= WPS_ENCR_WEP; + else + wps->encr_types |= WPS_ENCR_NONE; + } + + if (conf->ssid.wpa_psk_file) { + /* Use per-device PSKs */ + } else if (conf->ssid.wpa_passphrase) { + wps->network_key = (u8 *) os_strdup(conf->ssid.wpa_passphrase); + wps->network_key_len = os_strlen(conf->ssid.wpa_passphrase); + } else if (conf->ssid.wpa_psk) { + wps->network_key = os_malloc(2 * PMK_LEN + 1); + if (wps->network_key == NULL) { + os_free(wps); + return -1; + } + wpa_snprintf_hex((char *) wps->network_key, 2 * PMK_LEN + 1, + conf->ssid.wpa_psk->psk, PMK_LEN); + wps->network_key_len = 2 * PMK_LEN; + } else if (conf->ssid.wep.keys_set && conf->ssid.wep.key[0]) { + wps->network_key = os_malloc(conf->ssid.wep.len[0]); + if (wps->network_key == NULL) { + os_free(wps); + return -1; + } + os_memcpy(wps->network_key, conf->ssid.wep.key[0], + conf->ssid.wep.len[0]); + wps->network_key_len = conf->ssid.wep.len[0]; + } + + if (conf->wps_state == WPS_STATE_NOT_CONFIGURED) { + /* Override parameters to enable security by default */ + wps->auth_types = WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK; + wps->encr_types = WPS_ENCR_AES | WPS_ENCR_TKIP; + } + + cfg.new_psk_cb = hostapd_wps_new_psk_cb; + cfg.set_ie_cb = hostapd_wps_set_ie_cb; + cfg.pin_needed_cb = hostapd_wps_pin_needed_cb; + cfg.cb_ctx = hapd; + + wps->registrar = wps_registrar_init(wps, &cfg); + if (wps->registrar == NULL) { + printf("Failed to initialize WPS Registrar\n"); + os_free(wps->network_key); + os_free(wps); + return -1; + } + + hapd->wps = wps; + + return 0; +} + + +void hostapd_deinit_wps(struct hostapd_data *hapd) +{ + if (hapd->wps == NULL) + return; + wps_registrar_deinit(hapd->wps->registrar); + os_free(hapd->wps->network_key); + os_free(hapd->wps); + hapd->wps = NULL; + hostapd_wps_clear_ies(hapd); +} + + +int hostapd_wps_add_pin(struct hostapd_data *hapd, const char *uuid, + const char *pin) +{ + u8 u[UUID_LEN]; + if (hapd->wps == NULL || uuid_str2bin(uuid, u)) + return -1; + return wps_registrar_add_pin(hapd->wps->registrar, u, + (const u8 *) pin, os_strlen(pin)); +} + + +int hostapd_wps_button_pushed(struct hostapd_data *hapd) +{ + if (hapd->wps == NULL) + return -1; + return wps_registrar_button_pushed(hapd->wps->registrar); +} + + +void hostapd_wps_probe_req_rx(struct hostapd_data *hapd, const u8 *addr, + const u8 *ie, size_t ie_len) +{ + struct wpabuf *wps_ie; + const u8 *end, *pos, *wps; + + if (hapd->wps == NULL) + return; + + pos = ie; + end = ie + ie_len; + wps = NULL; + + while (pos + 1 < end) { + if (pos + 2 + pos[1] > end) + return; + if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 && + WPA_GET_BE32(&pos[2]) == WPS_DEV_OUI_WFA) { + wps = pos; + break; + } + pos += 2 + pos[1]; + } + + if (wps == NULL) + return; /* No WPS IE in Probe Request */ + + wps_ie = wpabuf_alloc(ie_len); + if (wps_ie == NULL) + return; + + /* There may be multiple WPS IEs in the message, so need to concatenate + * their WPS Data fields */ + while (pos + 1 < end) { + if (pos + 2 + pos[1] > end) + break; + if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 && + WPA_GET_BE32(&pos[2]) == WPS_DEV_OUI_WFA) + wpabuf_put_data(wps_ie, pos + 6, pos[1] - 4); + pos += 2 + pos[1]; + } + + if (wpabuf_len(wps_ie) > 0) + wps_registrar_probe_req_rx(hapd->wps->registrar, addr, wps_ie); + + wpabuf_free(wps_ie); +} diff --git a/hostapd/wps_hostapd.h b/hostapd/wps_hostapd.h new file mode 100644 index 000000000..6615c62a1 --- /dev/null +++ b/hostapd/wps_hostapd.h @@ -0,0 +1,48 @@ +/* + * hostapd / WPS integration + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef WPS_HOSTAPD_H +#define WPS_HOSTAPD_H + +#ifdef CONFIG_WPS + +int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf); +void hostapd_deinit_wps(struct hostapd_data *hapd); +int hostapd_wps_add_pin(struct hostapd_data *hapd, const char *uuid, + const char *pin); +int hostapd_wps_button_pushed(struct hostapd_data *hapd); +void hostapd_wps_probe_req_rx(struct hostapd_data *hapd, const u8 *addr, + const u8 *ie, size_t ie_len); + +#else /* CONFIG_WPS */ + +static inline int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf) +{ + return 0; +} + +static inline void hostapd_deinit_wps(struct hostapd_data *hapd) +{ +} + +static inline void hostapd_wps_probe_req_rx(struct hostapd_data *hapd, + const u8 *addr, + const u8 *ie, size_t ie_len) +{ +} +#endif /* CONFIG_WPS */ + +#endif /* WPS_HOSTAPD_H */ diff --git a/src/Makefile b/src/Makefile index 3ff694818..028af4df5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=common crypto drivers hlr_auc_gw eapol_supp eap_common eap_peer eap_server l2_packet radius rsn_supp tls utils +SUBDIRS=common crypto drivers hlr_auc_gw eapol_supp eap_common eap_peer eap_server l2_packet radius rsn_supp tls utils wps all: @echo Nothing to be made. diff --git a/src/common/defs.h b/src/common/defs.h index 9adc2ac0a..4930e73e7 100644 --- a/src/common/defs.h +++ b/src/common/defs.h @@ -42,6 +42,7 @@ typedef enum { FALSE = 0, TRUE = 1 } Boolean; #define WPA_KEY_MGMT_FT_PSK BIT(6) #define WPA_KEY_MGMT_IEEE8021X_SHA256 BIT(7) #define WPA_KEY_MGMT_PSK_SHA256 BIT(8) +#define WPA_KEY_MGMT_WPS BIT(9) static inline int wpa_key_mgmt_wpa_ieee8021x(int akm) { @@ -85,7 +86,8 @@ typedef enum { CIPHER_NONE, CIPHER_WEP40, CIPHER_TKIP, CIPHER_CCMP, typedef enum { KEY_MGMT_802_1X, KEY_MGMT_PSK, KEY_MGMT_NONE, KEY_MGMT_802_1X_NO_WPA, KEY_MGMT_WPA_NONE, KEY_MGMT_FT_802_1X, KEY_MGMT_FT_PSK, - KEY_MGMT_802_1X_SHA256, KEY_MGMT_PSK_SHA256 + KEY_MGMT_802_1X_SHA256, KEY_MGMT_PSK_SHA256, + KEY_MGMT_WPS } wpa_key_mgmt; /** diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index eed3b0f01..8d3fe1a21 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -75,6 +75,11 @@ static int ieee802_11_parse_vendor_specific(u8 *pos, size_t elen, return -1; } break; + case 4: + /* Wi-Fi Protected Setup (WPS) IE */ + elems->wps_ie = pos; + elems->wps_ie_len = elen; + break; default: wpa_printf(MSG_MSGDUMP, "Unknown Microsoft " "information element ignored " diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index 2cb063ead..4f3f4bed0 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -45,6 +45,8 @@ struct ieee802_11_elems { u8 wme_len; u8 *wme_tspec; u8 wme_tspec_len; + u8 *wps_ie; + u8 wps_ie_len; u8 *power_cap; u8 power_cap_len; u8 *supp_channels; diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 05d7a4e54..26b2d2ff5 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -49,6 +49,12 @@ extern "C" { /** New scan results available */ #define WPA_EVENT_SCAN_RESULTS "CTRL-EVENT-SCAN-RESULTS " +#define WPS_EVENT_OVERLAP "WPS-OVERLAP-DETECTED " + +/* hostapd control interface - fixed message prefixes */ +#define WPS_EVENT_PIN_NEEDED "WPS-PIN-NEEDED " +#define WPS_EVENT_NEW_AP_SETTINGS "WPS-NEW-AP-SETTINGS " + /* wpa_supplicant/hostapd control interface access */ diff --git a/src/drivers/Apple80211.h b/src/drivers/Apple80211.h index f72b9b907..2a612e730 100644 --- a/src/drivers/Apple80211.h +++ b/src/drivers/Apple80211.h @@ -105,11 +105,13 @@ WirelessError WirelessDisassociate(WirelessRef ref); * channel: CFNumber(kCFNumberSInt32Type) * signal: CFNumber(kCFNumberSInt32Type) * appleIE: CFData + * WPSNOPINRequired: CFBoolean * noise: CFNumber(kCFNumberSInt32Type) * capability: CFNumber(kCFNumberSInt32Type) * uniCipher: CFArray of CFNumber(kCFNumberSInt32Type) * appleIE_Version: CFNumber(kCFNumberSInt32Type) * appleIE_Robust: CFBoolean + * WPSConfigured: CFBoolean * scanWasDirected: CFBoolean * appleIE_Product: CFNumber(kCFNumberSInt32Type) * authModes: CFArray of CFNumber(kCFNumberSInt32Type) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 766899605..378042eee 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -164,6 +164,8 @@ struct wpa_driver_associate_params { * instead. The driver can determine which version is used by * looking at the first byte of the IE (0xdd for WPA, 0x30 for * WPA2/RSN). + * + * When using WPS, wpa_ie is used for WPS IE instead of WPA/RSN IE. */ const u8 *wpa_ie; /** @@ -928,7 +930,7 @@ struct wpa_driver_ops { * @ies_len: Length of the IE buffer in octets * Returns: 0 on success, -1 on failure */ - int (*set_probe_req_ie)(void *, const u8 *ies, size_t ies_len); + int (*set_probe_req_ie)(void *priv, const u8 *ies, size_t ies_len); /** * set_mode - Request driver to set the operating mode @@ -1239,6 +1241,7 @@ void wpa_supplicant_sta_free_hw_features(struct wpa_hw_modes *hw_features, const u8 * wpa_scan_get_ie(const struct wpa_scan_res *res, u8 ie); #define WPA_IE_VENDOR_TYPE 0x0050f201 +#define WPS_IE_VENDOR_TYPE 0x0050f204 const u8 * wpa_scan_get_vendor_ie(const struct wpa_scan_res *res, u32 vendor_type); int wpa_scan_get_max_rate(const struct wpa_scan_res *res); diff --git a/src/drivers/driver_test.c b/src/drivers/driver_test.c index 8c0b4308a..2e293ae96 100644 --- a/src/drivers/driver_test.c +++ b/src/drivers/driver_test.c @@ -1,6 +1,6 @@ /* * WPA Supplicant - testing driver interface - * Copyright (c) 2004-2007, Jouni Malinen + * Copyright (c) 2004-2008, Jouni Malinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -44,6 +44,8 @@ struct wpa_driver_test_data { size_t assoc_wpa_ie_len; int use_mlme; int associated; + u8 *probe_req_ie; + size_t probe_req_ie_len; }; @@ -85,11 +87,28 @@ static void wpa_driver_scan_dir(struct wpa_driver_test_data *drv, struct dirent *dent; DIR *dir; struct sockaddr_un addr; + char cmd[512], *pos, *end; + int ret; dir = opendir(path); if (dir == NULL) return; + end = cmd + sizeof(cmd); + pos = cmd; + ret = os_snprintf(pos, end - pos, "SCAN " MACSTR, + MAC2STR(drv->own_addr)); + if (ret >= 0 && ret < end - pos) + pos += ret; + if (drv->probe_req_ie) { + ret = os_snprintf(pos, end - pos, " "); + if (ret >= 0 && ret < end - pos) + pos += ret; + pos += wpa_snprintf_hex(pos, end - pos, drv->probe_req_ie, + drv->probe_req_ie_len); + } + end[-1] = '\0'; + while ((dent = readdir(dir))) { if (os_strncmp(dent->d_name, "AP-", 3) != 0) continue; @@ -100,7 +119,7 @@ static void wpa_driver_scan_dir(struct wpa_driver_test_data *drv, os_snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", path, dent->d_name); - if (sendto(drv->test_socket, "SCAN", 4, 0, + if (sendto(drv->test_socket, cmd, os_strlen(cmd), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("sendto(test_socket)"); } @@ -564,6 +583,7 @@ static void wpa_driver_test_deinit(void *priv) os_free(drv->test_dir); for (i = 0; i < MAX_SCAN_RESULTS; i++) os_free(drv->scanres[i]); + os_free(drv->probe_req_ie); os_free(drv); } @@ -936,6 +956,27 @@ int wpa_driver_test_set_bssid(void *priv, const u8 *bssid) #endif /* CONFIG_CLIENT_MLME */ +int wpa_driver_set_probe_req_ie(void *priv, const u8 *ies, size_t ies_len) +{ + struct wpa_driver_test_data *drv = priv; + + os_free(drv->probe_req_ie); + if (ies) { + drv->probe_req_ie = os_malloc(ies_len); + if (drv->probe_req_ie == NULL) { + drv->probe_req_ie_len = 0; + return -1; + } + os_memcpy(drv->probe_req_ie, ies, ies_len); + drv->probe_req_ie_len = ies_len; + } else { + drv->probe_req_ie = NULL; + drv->probe_req_ie_len = 0; + } + return 0; +} + + const struct wpa_driver_ops wpa_driver_test_ops = { "test", "wpa_supplicant test driver", @@ -984,6 +1025,6 @@ const struct wpa_driver_ops wpa_driver_test_ops = { NULL /* update_ft_ies */, NULL /* send_ft_action */, wpa_driver_test_get_scan_results2, - NULL /* set_probe_req_ie */, + wpa_driver_set_probe_req_ie, NULL /* set_mode */ }; diff --git a/src/eap_common/eap_wsc_common.c b/src/eap_common/eap_wsc_common.c new file mode 100644 index 000000000..5d4e8cc11 --- /dev/null +++ b/src/eap_common/eap_wsc_common.c @@ -0,0 +1,39 @@ +/* + * EAP-WSC common routines for Wi-Fi Protected Setup + * Copyright (c) 2007, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "eap_defs.h" +#include "eap_common.h" +#include "wps/wps.h" +#include "eap_wsc_common.h" + +struct wpabuf * eap_wsc_build_frag_ack(u8 id, u8 code) +{ + struct wpabuf *msg; + + msg = eap_msg_alloc(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, 2, code, id); + if (msg == NULL) { + wpa_printf(MSG_ERROR, "EAP-WSC: Failed to allocate memory for " + "FRAG_ACK"); + return NULL; + } + + wpa_printf(MSG_DEBUG, "EAP-WSC: Send WSC/FRAG_ACK"); + wpabuf_put_u8(msg, WSC_FRAG_ACK); /* Op-Code */ + wpabuf_put_u8(msg, 0); /* Flags */ + + return msg; +} diff --git a/src/eap_common/eap_wsc_common.h b/src/eap_common/eap_wsc_common.h new file mode 100644 index 000000000..fdf61d317 --- /dev/null +++ b/src/eap_common/eap_wsc_common.h @@ -0,0 +1,33 @@ +/* + * EAP-WSC definitions for Wi-Fi Protected Setup + * Copyright (c) 2007, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef EAP_WSC_COMMON_H +#define EAP_WSC_COMMON_H + +#define EAP_VENDOR_TYPE_WSC 1 + +#define WSC_FLAGS_MF 0x01 +#define WSC_FLAGS_LF 0x02 + +#define WSC_ID_REGISTRAR "WFA-SimpleConfig-Registrar-1-0" +#define WSC_ID_REGISTRAR_LEN 30 +#define WSC_ID_ENROLLEE "WFA-SimpleConfig-Enrollee-1-0" +#define WSC_ID_ENROLLEE_LEN 29 + +#define WSC_FRAGMENT_SIZE 1400 + + +struct wpabuf * eap_wsc_build_frag_ack(u8 id, u8 code); + +#endif /* EAP_WSC_COMMON_H */ diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c index e518ec3a7..2627faa26 100644 --- a/src/eap_peer/eap.c +++ b/src/eap_peer/eap.c @@ -31,6 +31,7 @@ #include "pcsc_funcs.h" #include "wpa_ctrl.h" #include "state_machine.h" +#include "eap_common/eap_wsc_common.h" #define STATE_MACHINE_DATA struct eap_sm #define STATE_MACHINE_DEBUG_PREFIX "EAP" @@ -2043,3 +2044,29 @@ void eap_invalidate_cached_session(struct eap_sm *sm) if (sm) eap_deinit_prev_method(sm, "invalidate"); } + + +int eap_is_wps_pbc_enrollee(struct eap_peer_config *conf) +{ + if (conf->identity_len != WSC_ID_ENROLLEE_LEN || + os_memcmp(conf->identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN)) + return 0; /* Not a WPS Enrollee */ + + if (conf->phase1 == NULL || os_strstr(conf->phase1, "pbc=1") == NULL) + return 0; /* Not using PBC */ + + return 1; +} + + +int eap_is_wps_pin_enrollee(struct eap_peer_config *conf) +{ + if (conf->identity_len != WSC_ID_ENROLLEE_LEN || + os_memcmp(conf->identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN)) + return 0; /* Not a WPS Enrollee */ + + if (conf->phase1 == NULL || os_strstr(conf->phase1, "pin=") == NULL) + return 0; /* Not using PIN */ + + return 1; +} diff --git a/src/eap_peer/eap.h b/src/eap_peer/eap.h index d3db7d618..cd080328e 100644 --- a/src/eap_peer/eap.h +++ b/src/eap_peer/eap.h @@ -22,6 +22,7 @@ struct eap_sm; struct wpa_config_blob; struct wpabuf; +struct wps_credential; struct eap_method_type { int vendor; @@ -213,6 +214,17 @@ struct eapol_callbacks { */ void (*notify_pending)(void *ctx); + /** + * wps_cred - Notify that new credential was received from WPS + * @ctx: eapol_ctx from eap_peer_sm_init() call + * Returns: 0 on success (credential stored), -1 on failure + * + * This callback is only needed when using WPS Enrollee to configure + * new credentials. This can be left %NULL if no WPS functionality is + * enabled. + */ + int (*wps_cred)(void *ctx, struct wps_credential *cred); + /** * eap_param_needed - Notify that EAP parameter is needed * @ctx: eapol_ctx from eap_peer_sm_init() call @@ -248,7 +260,7 @@ struct eap_config { /** * mac_addr - MAC address of the peer * - * This can be left %NULL if not available. + * This is only used by EAP-WSC and can be left %NULL if not available. */ const u8 *mac_addr; }; @@ -283,6 +295,9 @@ struct wpabuf * eap_get_eapRespData(struct eap_sm *sm); void eap_register_scard_ctx(struct eap_sm *sm, void *ctx); void eap_invalidate_cached_session(struct eap_sm *sm); +int eap_is_wps_pbc_enrollee(struct eap_peer_config *conf); +int eap_is_wps_pin_enrollee(struct eap_peer_config *conf); + #endif /* IEEE8021X_EAPOL */ #endif /* EAP_H */ diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index f6126bb9c..6e4919cff 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -356,6 +356,9 @@ struct eap_peer_config { * 0 = do not use cryptobinding (default) * 1 = use cryptobinding if server supports it * 2 = require cryptobinding + * + * EAP-WSC (WPS) uses following options: pin= and + * uuid= */ char *phase1; diff --git a/src/eap_peer/eap_methods.c b/src/eap_peer/eap_methods.c index 0973b2f4a..727049e79 100644 --- a/src/eap_peer/eap_methods.c +++ b/src/eap_peer/eap_methods.c @@ -455,6 +455,13 @@ int eap_peer_register_methods(void) } #endif /* EAP_GPSK */ +#ifdef EAP_WSC + if (ret == 0) { + int eap_peer_wsc_register(void); + ret = eap_peer_wsc_register(); + } +#endif /* EAP_WSC */ + #ifdef EAP_IKEV2 if (ret == 0) { int eap_peer_ikev2_register(void); diff --git a/src/eap_peer/eap_wsc.c b/src/eap_peer/eap_wsc.c new file mode 100644 index 000000000..e80b7c6b4 --- /dev/null +++ b/src/eap_peer/eap_wsc.c @@ -0,0 +1,552 @@ +/* + * EAP-WSC peer for Wi-Fi Protected Setup + * Copyright (c) 2007-2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "uuid.h" +#include "eap_i.h" +#include "eap_common/eap_wsc_common.h" +#include "wps/wps.h" +#include "wps/wps_defs.h" + + +struct eap_wsc_data { + enum { WAIT_START, MSG, FRAG_ACK, WAIT_FRAG_ACK, DONE, FAIL } state; + int registrar; + struct wpabuf *in_buf; + struct wpabuf *out_buf; + u8 in_op_code, out_op_code; + size_t out_used; + size_t fragment_size; + struct wps_data *wps; + struct wps_context *wps_ctx; +}; + + +static const char * eap_wsc_state_txt(int state) +{ + switch (state) { + case WAIT_START: + return "WAIT_START"; + case MSG: + return "MSG"; + case FRAG_ACK: + return "FRAG_ACK"; + case WAIT_FRAG_ACK: + return "WAIT_FRAG_ACK"; + case DONE: + return "DONE"; + case FAIL: + return "FAIL"; + default: + return "?"; + } +} + + +static void eap_wsc_state(struct eap_wsc_data *data, int state) +{ + wpa_printf(MSG_DEBUG, "EAP-WSC: %s -> %s", + eap_wsc_state_txt(data->state), + eap_wsc_state_txt(state)); + data->state = state; +} + + +static int eap_wsc_new_psk_cb(void *ctx, const u8 *mac_addr, const u8 *psk, + size_t psk_len) +{ + /* struct eap_wsc_data *data = ctx; */ + + wpa_printf(MSG_DEBUG, "EAP-SC: Received new WPA/WPA2-PSK from WPS for " + "STA " MACSTR, MAC2STR(mac_addr)); + wpa_hexdump_key(MSG_DEBUG, "Per-device PSK", psk, psk_len); + + /* TODO */ + + return 0; +} + + +static void eap_wsc_pin_needed_cb(void *ctx, const u8 *uuid_e, + const struct wps_device_data *dev) +{ + /* struct eap_wsc_data *data = ctx; */ + char uuid[40], txt[400]; + int len; + if (uuid_bin2str(uuid_e, uuid, sizeof(uuid))) + return; + wpa_printf(MSG_DEBUG, "EAP-WSC: PIN needed for E-UUID %s", uuid); + len = os_snprintf(txt, sizeof(txt), "WPS-EVENT-PIN-NEEDED " + "%s " MACSTR " [%s|%s|%s|%s|%s|%d-%08X-%d]", + uuid, MAC2STR(dev->mac_addr), dev->device_name, + dev->manufacturer, dev->model_name, + dev->model_number, dev->serial_number, + dev->categ, dev->oui, dev->sub_categ); + if (len > 0 && len < (int) sizeof(txt)) + wpa_printf(MSG_INFO, "%s", txt); +} + + +static void * eap_wsc_init(struct eap_sm *sm) +{ + struct eap_wsc_data *data; + const u8 *identity; + size_t identity_len; + int registrar; + struct wps_config cfg; + u8 uuid[UUID_LEN]; + const char *pos; + const char *phase1; + struct wps_context *wps = NULL; + + identity = eap_get_config_identity(sm, &identity_len); + + if (identity && identity_len == WSC_ID_REGISTRAR_LEN && + os_memcmp(identity, WSC_ID_REGISTRAR, WSC_ID_REGISTRAR_LEN) == 0) + registrar = 1; /* Supplicant is Registrar */ + else if (identity && identity_len == WSC_ID_ENROLLEE_LEN && + os_memcmp(identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN) == 0) + registrar = 0; /* Supplicant is Enrollee */ + else { + wpa_hexdump_ascii(MSG_INFO, "EAP-WSC: Unexpected identity", + identity, identity_len); + return NULL; + } + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + data->state = registrar ? MSG : WAIT_START; + data->registrar = registrar; + + if (registrar) { + struct wps_registrar_config rcfg; + + wps = os_zalloc(sizeof(*wps)); + if (wps == NULL) { + os_free(data); + return NULL; + } + + wps->cb_ctx = data; + + /* TODO: configure.. */ + wps->auth_types = WPS_AUTH_WPA2PSK; + wps->encr_types = WPS_ENCR_AES; + os_memcpy(wps->ssid, "test", 4); + wps->ssid_len = 4; + + os_memset(&rcfg, 0, sizeof(rcfg)); + rcfg.new_psk_cb = eap_wsc_new_psk_cb; + rcfg.pin_needed_cb = eap_wsc_pin_needed_cb; + rcfg.cb_ctx = data; + + wps->registrar = wps_registrar_init(wps, &rcfg); + if (wps->registrar == NULL) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Failed to initialize " + "WPS Registrar"); + os_free(wps->network_key); + os_free(wps); + os_free(data); + return NULL; + } + + data->wps_ctx = wps; + } + + os_memset(&cfg, 0, sizeof(cfg)); + cfg.authenticator = 0; + cfg.wps = wps; + cfg.registrar = data->wps_ctx ? data->wps_ctx->registrar : NULL; + cfg.enrollee_mac_addr = sm->mac_addr; + + phase1 = eap_get_config_phase1(sm); + if (phase1 == NULL) { + wpa_printf(MSG_INFO, "EAP-WSC: phase1 configuration data not " + "set"); + os_free(data); + return NULL; + } + + pos = os_strstr(phase1, "pin="); + if (pos) { + pos += 4; + cfg.pin = (const u8 *) pos; + while (*pos != '\0' && *pos != ' ') + pos++; + cfg.pin_len = pos - (const char *) cfg.pin; + } else { + pos = os_strstr(phase1, "pbc=1"); + if (pos) + cfg.pbc = 1; + } + + if (cfg.pin == NULL && !cfg.pbc) { + wpa_printf(MSG_INFO, "EAP-WSC: PIN or PBC not set in phase1 " + "configuration data"); + os_free(data); + return NULL; + } + + pos = os_strstr(phase1, "uuid="); + if (pos == NULL) { + wpa_printf(MSG_INFO, "EAP-WSC: UUID not set in phase1 " + "configuration data"); + os_free(data); + return NULL; + } + if (uuid_str2bin(pos + 5, uuid)) { + wpa_printf(MSG_INFO, "EAP-WSC: Invalid UUID in phase1 " + "configuration data"); + os_free(data); + return NULL; + } + if (registrar && wps) + os_memcpy(wps->uuid, uuid, UUID_LEN); + else + cfg.uuid = uuid; + cfg.wps_cred_cb = sm->eapol_cb->wps_cred; + cfg.cb_ctx = sm->eapol_ctx; + data->wps = wps_init(&cfg); + if (data->wps == NULL) { + os_free(data); + return NULL; + } + data->fragment_size = WSC_FRAGMENT_SIZE; + + + if (registrar) { + /* Testing */ + wpa_printf(MSG_INFO, "EAP-WSC: Registrar functionality not " + "yet fully supported - using test values"); + u8 uuid_e[UUID_LEN]; + os_memset(uuid_e, 0, UUID_LEN); + wps_registrar_add_pin(data->wps_ctx->registrar, uuid_e, + (const u8 *) "12345670", 8); + } + + return data; +} + + +static void eap_wsc_deinit(struct eap_sm *sm, void *priv) +{ + struct eap_wsc_data *data = priv; + wpabuf_free(data->in_buf); + wpabuf_free(data->out_buf); + wps_deinit(data->wps); + if (data->wps_ctx) { + wps_registrar_deinit(data->wps_ctx->registrar); + os_free(data->wps_ctx->network_key); + os_free(data->wps_ctx); + } + os_free(data); +} + + +static struct wpabuf * eap_wsc_build_msg(struct eap_wsc_data *data, + struct eap_method_ret *ret, u8 id) +{ + struct wpabuf *resp; + u8 flags; + size_t send_len, plen; + + ret->ignore = FALSE; + wpa_printf(MSG_DEBUG, "EAP-WSC: Generating Response"); + ret->allowNotifications = TRUE; + + flags = 0; + send_len = wpabuf_len(data->out_buf) - data->out_used; + if (2 + send_len > data->fragment_size) { + send_len = data->fragment_size - 2; + flags |= WSC_FLAGS_MF; + if (data->out_used == 0) { + flags |= WSC_FLAGS_LF; + send_len -= 2; + } + } + plen = 2 + send_len; + if (flags & WSC_FLAGS_LF) + plen += 2; + resp = eap_msg_alloc(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, plen, + EAP_CODE_RESPONSE, id); + if (resp == NULL) + return NULL; + + wpabuf_put_u8(resp, data->out_op_code); /* Op-Code */ + wpabuf_put_u8(resp, flags); /* Flags */ + if (flags & WSC_FLAGS_LF) + wpabuf_put_be16(resp, wpabuf_len(data->out_buf)); + + wpabuf_put_data(resp, wpabuf_head_u8(data->out_buf) + data->out_used, + send_len); + data->out_used += send_len; + + ret->methodState = METHOD_MAY_CONT; + ret->decision = DECISION_FAIL; + + if (data->out_used == wpabuf_len(data->out_buf)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " + "(message sent completely)", + (unsigned long) send_len); + wpabuf_free(data->out_buf); + data->out_buf = NULL; + data->out_used = 0; + if ((data->state == FAIL && data->out_op_code == WSC_ACK) || + data->out_op_code == WSC_NACK || + data->out_op_code == WSC_Done) { + eap_wsc_state(data, FAIL); + ret->methodState = METHOD_DONE; + } else + eap_wsc_state(data, MSG); + } else { + wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " + "(%lu more to send)", (unsigned long) send_len, + (unsigned long) wpabuf_len(data->out_buf) - + data->out_used); + eap_wsc_state(data, WAIT_FRAG_ACK); + } + + return resp; +} + + +static int eap_wsc_process_cont(struct eap_wsc_data *data, + const u8 *buf, size_t len, u8 op_code) +{ + /* Process continuation of a pending message */ + if (op_code != data->in_op_code) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d in " + "fragment (expected %d)", + op_code, data->in_op_code); + return -1; + } + + if (len > wpabuf_tailroom(data->in_buf)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment overflow"); + eap_wsc_state(data, FAIL); + return -1; + } + + wpabuf_put_data(data->in_buf, buf, len); + wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes, waiting " + "for %lu bytes more", (unsigned long) len, + (unsigned long) wpabuf_tailroom(data->in_buf)); + + return 0; +} + + +static struct wpabuf * eap_wsc_process_fragment(struct eap_wsc_data *data, + struct eap_method_ret *ret, + u8 id, u8 flags, u8 op_code, + u16 message_length, + const u8 *buf, size_t len) +{ + /* Process a fragment that is not the last one of the message */ + if (data->in_buf == NULL && !(flags & WSC_FLAGS_LF)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: No Message Length field in a " + "fragmented packet"); + ret->ignore = TRUE; + return NULL; + } + + if (data->in_buf == NULL) { + /* First fragment of the message */ + data->in_buf = wpabuf_alloc(message_length); + if (data->in_buf == NULL) { + wpa_printf(MSG_DEBUG, "EAP-WSC: No memory for " + "message"); + ret->ignore = TRUE; + return NULL; + } + data->in_op_code = op_code; + wpabuf_put_data(data->in_buf, buf, len); + wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes in first " + "fragment, waiting for %lu bytes more", + (unsigned long) len, + (unsigned long) wpabuf_tailroom(data->in_buf)); + } + + return eap_wsc_build_frag_ack(id, EAP_CODE_RESPONSE); +} + + +static struct wpabuf * eap_wsc_process(struct eap_sm *sm, void *priv, + struct eap_method_ret *ret, + const struct wpabuf *reqData) +{ + struct eap_wsc_data *data = priv; + const u8 *start, *pos, *end; + size_t len; + u8 op_code, flags, id; + u16 message_length = 0; + enum wps_process_res res; + struct wpabuf tmpbuf; + + pos = eap_hdr_validate(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, reqData, + &len); + if (pos == NULL || len < 2) { + ret->ignore = TRUE; + return NULL; + } + + id = eap_get_id(reqData); + + start = pos; + end = start + len; + + op_code = *pos++; + flags = *pos++; + if (flags & WSC_FLAGS_LF) { + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Message underflow"); + ret->ignore = TRUE; + return NULL; + } + message_length = WPA_GET_BE16(pos); + pos += 2; + + if (message_length < end - pos) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Invalid Message " + "Length"); + ret->ignore = TRUE; + return NULL; + } + } + + wpa_printf(MSG_DEBUG, "EAP-WSC: Received packet: Op-Code %d " + "Flags 0x%x Message Length %d", + op_code, flags, message_length); + + if (data->state == WAIT_FRAG_ACK) { + if (op_code != WSC_FRAG_ACK) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d " + "in WAIT_FRAG_ACK state", op_code); + ret->ignore = TRUE; + return NULL; + } + wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment acknowledged"); + eap_wsc_state(data, MSG); + return eap_wsc_build_msg(data, ret, id); + } + + if (op_code != WSC_ACK && op_code != WSC_NACK && op_code != WSC_MSG && + op_code != WSC_Done && op_code != WSC_Start) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d", + op_code); + ret->ignore = TRUE; + return NULL; + } + + if (data->state == WAIT_START) { + if (op_code != WSC_Start) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d " + "in WAIT_START state", op_code); + ret->ignore = TRUE; + return NULL; + } + wpa_printf(MSG_DEBUG, "EAP-WSC: Received start"); + eap_wsc_state(data, MSG); + /* Start message has empty payload, skip processing */ + goto send_msg; + } else if (op_code == WSC_Start) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d", + op_code); + ret->ignore = TRUE; + return NULL; + } + + if (data->in_buf && + eap_wsc_process_cont(data, pos, end - pos, op_code) < 0) { + ret->ignore = TRUE; + return NULL; + } + + if (flags & WSC_FLAGS_MF) { + return eap_wsc_process_fragment(data, ret, id, flags, op_code, + message_length, pos, + end - pos); + } + + if (data->in_buf == NULL) { + /* Wrap unfragmented messages as wpabuf without extra copy */ + wpabuf_set(&tmpbuf, pos, end - pos); + data->in_buf = &tmpbuf; + } + + res = wps_process_msg(data->wps, op_code, data->in_buf); + switch (res) { + case WPS_DONE: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing completed " + "successfully - wait for EAP failure"); + eap_wsc_state(data, FAIL); + break; + case WPS_CONTINUE: + eap_wsc_state(data, MSG); + break; + case WPS_FAILURE: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing failed"); + eap_wsc_state(data, FAIL); + break; + case WPS_PENDING: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing pending"); + ret->ignore = TRUE; + if (data->in_buf == &tmpbuf) + data->in_buf = NULL; + return NULL; + } + + if (data->in_buf != &tmpbuf) + wpabuf_free(data->in_buf); + data->in_buf = NULL; + +send_msg: + if (data->out_buf == NULL) { + data->out_buf = wps_get_msg(data->wps, &data->out_op_code); + if (data->out_buf == NULL) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Failed to receive " + "message from WPS"); + return NULL; + } + data->out_used = 0; + } + + eap_wsc_state(data, MSG); + return eap_wsc_build_msg(data, ret, id); +} + + +int eap_peer_wsc_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, + "WSC"); + if (eap == NULL) + return -1; + + eap->init = eap_wsc_init; + eap->deinit = eap_wsc_deinit; + eap->process = eap_wsc_process; + + ret = eap_peer_method_register(eap); + if (ret) + eap_peer_method_free(eap); + return ret; +} diff --git a/src/eap_server/eap.c b/src/eap_server/eap.c index 0786f56f7..37b136bd9 100644 --- a/src/eap_server/eap.c +++ b/src/eap_server/eap.c @@ -1166,6 +1166,7 @@ struct eap_sm * eap_server_sm_init(void *eapol_ctx, sm->pac_key_refresh_time = conf->pac_key_refresh_time; sm->eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; sm->tnc = conf->tnc; + sm->wps = conf->wps; wpa_printf(MSG_DEBUG, "EAP: Server state machine created"); diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index 0dfca33a4..8a918eb76 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -103,6 +103,7 @@ struct eap_config { int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; + struct wps_context *wps; }; diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h index d955ae997..439e1efe6 100644 --- a/src/eap_server/eap_i.h +++ b/src/eap_server/eap_i.h @@ -181,6 +181,7 @@ struct eap_sm { int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; + struct wps_context *wps; }; int eap_user_get(struct eap_sm *sm, const u8 *identity, size_t identity_len, diff --git a/src/eap_server/eap_methods.c b/src/eap_server/eap_methods.c index dc47e00a2..a7d77387d 100644 --- a/src/eap_server/eap_methods.c +++ b/src/eap_server/eap_methods.c @@ -254,6 +254,13 @@ int eap_server_register_methods(void) } #endif /* EAP_FAST */ +#ifdef EAP_WSC + if (ret == 0) { + int eap_server_wsc_register(void); + ret = eap_server_wsc_register(); + } +#endif /* EAP_WSC */ + #ifdef EAP_IKEV2 if (ret == 0) { int eap_server_ikev2_register(void); diff --git a/src/eap_server/eap_wsc.c b/src/eap_server/eap_wsc.c new file mode 100644 index 000000000..b81b53908 --- /dev/null +++ b/src/eap_server/eap_wsc.c @@ -0,0 +1,461 @@ +/* + * EAP-WSC server for Wi-Fi Protected Setup + * Copyright (c) 2007, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "eap_i.h" +#include "eap_common/eap_wsc_common.h" +#include "wps/wps.h" + + +struct eap_wsc_data { + enum { START, MSG, FRAG_ACK, WAIT_FRAG_ACK, DONE, FAIL } state; + int registrar; + struct wpabuf *in_buf; + struct wpabuf *out_buf; + u8 in_op_code, out_op_code; + size_t out_used; + size_t fragment_size; + struct wps_data *wps; +}; + + +static const char * eap_wsc_state_txt(int state) +{ + switch (state) { + case START: + return "START"; + case MSG: + return "MSG"; + case FRAG_ACK: + return "FRAG_ACK"; + case WAIT_FRAG_ACK: + return "WAIT_FRAG_ACK"; + case DONE: + return "DONE"; + case FAIL: + return "FAIL"; + default: + return "?"; + } +} + + +static void eap_wsc_state(struct eap_wsc_data *data, int state) +{ + wpa_printf(MSG_DEBUG, "EAP-WSC: %s -> %s", + eap_wsc_state_txt(data->state), + eap_wsc_state_txt(state)); + data->state = state; +} + + +static void * eap_wsc_init(struct eap_sm *sm) +{ + struct eap_wsc_data *data; + int registrar; + struct wps_config cfg; + + if (sm->identity && sm->identity_len == WSC_ID_REGISTRAR_LEN && + os_memcmp(sm->identity, WSC_ID_REGISTRAR, WSC_ID_REGISTRAR_LEN) == + 0) + registrar = 0; /* Supplicant is Registrar */ + else if (sm->identity && sm->identity_len == WSC_ID_ENROLLEE_LEN && + os_memcmp(sm->identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN) + == 0) + registrar = 1; /* Supplicant is Enrollee */ + else { + wpa_hexdump_ascii(MSG_INFO, "EAP-WSC: Unexpected identity", + sm->identity, sm->identity_len); + return NULL; + } + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + data->state = registrar ? START : MSG; + data->registrar = registrar; + + os_memset(&cfg, 0, sizeof(cfg)); + cfg.authenticator = 1; + cfg.wps = sm->wps; + if (registrar) { + if (sm->wps == NULL || sm->wps->registrar == NULL) { + wpa_printf(MSG_INFO, "EAP-WSC: WPS Registrar not " + "initialized"); + os_free(data); + return NULL; + } + cfg.registrar = sm->wps->registrar; + } else { + if (sm->user == NULL || sm->user->password == NULL) { + wpa_printf(MSG_INFO, "EAP-WSC: No AP PIN (password) " + "configured for Enrollee functionality"); + os_free(data); + return NULL; + } + cfg.pin = sm->user->password; + cfg.pin_len = sm->user->password_len; + } + data->wps = wps_init(&cfg); + if (data->wps == NULL) { + os_free(data); + return NULL; + } + data->fragment_size = WSC_FRAGMENT_SIZE; + + return data; +} + + +static void eap_wsc_reset(struct eap_sm *sm, void *priv) +{ + struct eap_wsc_data *data = priv; + wpabuf_free(data->in_buf); + wpabuf_free(data->out_buf); + wps_deinit(data->wps); + os_free(data); +} + + +static struct wpabuf * eap_wsc_build_start(struct eap_sm *sm, + struct eap_wsc_data *data, u8 id) +{ + struct wpabuf *req; + + req = eap_msg_alloc(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, 2, + EAP_CODE_REQUEST, id); + if (req == NULL) { + wpa_printf(MSG_ERROR, "EAP-WSC: Failed to allocate memory for " + "request"); + return NULL; + } + + wpa_printf(MSG_DEBUG, "EAP-WSC: Send WSC/Start"); + wpabuf_put_u8(req, WSC_Start); /* Op-Code */ + wpabuf_put_u8(req, 0); /* Flags */ + + return req; +} + + +static struct wpabuf * eap_wsc_build_msg(struct eap_wsc_data *data, u8 id) +{ + struct wpabuf *req; + u8 flags; + size_t send_len, plen; + + flags = 0; + send_len = wpabuf_len(data->out_buf) - data->out_used; + if (2 + send_len > data->fragment_size) { + send_len = data->fragment_size - 2; + flags |= WSC_FLAGS_MF; + if (data->out_used == 0) { + flags |= WSC_FLAGS_LF; + send_len -= 2; + } + } + plen = 2 + send_len; + if (flags & WSC_FLAGS_LF) + plen += 2; + req = eap_msg_alloc(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, plen, + EAP_CODE_REQUEST, id); + if (req == NULL) { + wpa_printf(MSG_ERROR, "EAP-WSC: Failed to allocate memory for " + "request"); + return NULL; + } + + wpabuf_put_u8(req, data->out_op_code); /* Op-Code */ + wpabuf_put_u8(req, flags); /* Flags */ + if (flags & WSC_FLAGS_LF) + wpabuf_put_be16(req, wpabuf_len(data->out_buf)); + + wpabuf_put_data(req, wpabuf_head_u8(data->out_buf) + data->out_used, + send_len); + data->out_used += send_len; + + if (data->out_used == wpabuf_len(data->out_buf)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " + "(message sent completely)", + (unsigned long) send_len); + wpabuf_free(data->out_buf); + data->out_buf = NULL; + data->out_used = 0; + eap_wsc_state(data, MSG); + } else { + wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " + "(%lu more to send)", (unsigned long) send_len, + (unsigned long) wpabuf_len(data->out_buf) - + data->out_used); + eap_wsc_state(data, WAIT_FRAG_ACK); + } + + return req; +} + + +static struct wpabuf * eap_wsc_buildReq(struct eap_sm *sm, void *priv, u8 id) +{ + struct eap_wsc_data *data = priv; + + switch (data->state) { + case START: + return eap_wsc_build_start(sm, data, id); + case MSG: + if (data->out_buf == NULL) { + data->out_buf = wps_get_msg(data->wps, + &data->out_op_code); + if (data->out_buf == NULL) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Failed to " + "receive message from WPS"); + return NULL; + } + data->out_used = 0; + } + /* pass through */ + case WAIT_FRAG_ACK: + return eap_wsc_build_msg(data, id); + case FRAG_ACK: + return eap_wsc_build_frag_ack(id, EAP_CODE_REQUEST); + default: + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected state %d in " + "buildReq", data->state); + return NULL; + } +} + + +static Boolean eap_wsc_check(struct eap_sm *sm, void *priv, + struct wpabuf *respData) +{ + const u8 *pos; + size_t len; + + pos = eap_hdr_validate(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, + respData, &len); + if (pos == NULL || len < 2) { + wpa_printf(MSG_INFO, "EAP-WSC: Invalid frame"); + return TRUE; + } + + return FALSE; +} + + +static int eap_wsc_process_cont(struct eap_wsc_data *data, + const u8 *buf, size_t len, u8 op_code) +{ + /* Process continuation of a pending message */ + if (op_code != data->in_op_code) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d in " + "fragment (expected %d)", + op_code, data->in_op_code); + eap_wsc_state(data, FAIL); + return -1; + } + + if (len > wpabuf_tailroom(data->in_buf)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment overflow"); + eap_wsc_state(data, FAIL); + return -1; + } + + wpabuf_put_data(data->in_buf, buf, len); + wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes, waiting for %lu " + "bytes more", (unsigned long) len, + (unsigned long) wpabuf_tailroom(data->in_buf)); + + return 0; +} + + +static int eap_wsc_process_fragment(struct eap_wsc_data *data, + u8 flags, u8 op_code, u16 message_length, + const u8 *buf, size_t len) +{ + /* Process a fragment that is not the last one of the message */ + if (data->in_buf == NULL && !(flags & WSC_FLAGS_LF)) { + wpa_printf(MSG_DEBUG, "EAP-WSC: No Message Length " + "field in a fragmented packet"); + return -1; + } + + if (data->in_buf == NULL) { + /* First fragment of the message */ + data->in_buf = wpabuf_alloc(message_length); + if (data->in_buf == NULL) { + wpa_printf(MSG_DEBUG, "EAP-WSC: No memory for " + "message"); + return -1; + } + data->in_op_code = op_code; + wpabuf_put_data(data->in_buf, buf, len); + wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes in " + "first fragment, waiting for %lu bytes more", + (unsigned long) len, + (unsigned long) wpabuf_tailroom(data->in_buf)); + } + + return 0; +} + + +static void eap_wsc_process(struct eap_sm *sm, void *priv, + struct wpabuf *respData) +{ + struct eap_wsc_data *data = priv; + const u8 *start, *pos, *end; + size_t len; + u8 op_code, flags; + u16 message_length = 0; + enum wps_process_res res; + struct wpabuf tmpbuf; + + pos = eap_hdr_validate(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, + respData, &len); + if (pos == NULL || len < 2) + return; /* Should not happen; message already verified */ + + start = pos; + end = start + len; + + op_code = *pos++; + flags = *pos++; + if (flags & WSC_FLAGS_LF) { + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Message underflow"); + return; + } + message_length = WPA_GET_BE16(pos); + pos += 2; + + if (message_length < end - pos) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Invalid Message " + "Length"); + return; + } + } + + wpa_printf(MSG_DEBUG, "EAP-WSC: Received packet: Op-Code %d " + "Flags 0x%x Message Length %d", + op_code, flags, message_length); + + if (data->state == WAIT_FRAG_ACK) { + if (op_code != WSC_FRAG_ACK) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d " + "in WAIT_FRAG_ACK state", op_code); + eap_wsc_state(data, FAIL); + return; + } + wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment acknowledged"); + eap_wsc_state(data, MSG); + return; + } + + if (op_code != WSC_ACK && op_code != WSC_NACK && op_code != WSC_MSG && + op_code != WSC_Done) { + wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d", + op_code); + eap_wsc_state(data, FAIL); + return; + } + + if (data->in_buf && + eap_wsc_process_cont(data, pos, end - pos, op_code) < 0) { + eap_wsc_state(data, FAIL); + return; + } + + if (flags & WSC_FLAGS_MF) { + if (eap_wsc_process_fragment(data, flags, op_code, + message_length, pos, end - pos) < + 0) + eap_wsc_state(data, FAIL); + else + eap_wsc_state(data, FRAG_ACK); + return; + } + + if (data->in_buf == NULL) { + /* Wrap unfragmented messages as wpabuf without extra copy */ + wpabuf_set(&tmpbuf, pos, end - pos); + data->in_buf = &tmpbuf; + } + + res = wps_process_msg(data->wps, op_code, data->in_buf); + switch (res) { + case WPS_DONE: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing completed " + "successfully - report EAP failure"); + eap_wsc_state(data, FAIL); + break; + case WPS_CONTINUE: + eap_wsc_state(data, MSG); + break; + case WPS_FAILURE: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing failed"); + eap_wsc_state(data, FAIL); + break; + case WPS_PENDING: + wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing pending"); + sm->method_pending = METHOD_PENDING_WAIT; + break; + } + + if (data->in_buf != &tmpbuf) + wpabuf_free(data->in_buf); + data->in_buf = NULL; +} + + +static Boolean eap_wsc_isDone(struct eap_sm *sm, void *priv) +{ + struct eap_wsc_data *data = priv; + return data->state == FAIL; +} + + +static Boolean eap_wsc_isSuccess(struct eap_sm *sm, void *priv) +{ + /* EAP-WSC will always result in EAP-Failure */ + return FALSE; +} + + +int eap_server_wsc_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, + "WSC"); + if (eap == NULL) + return -1; + + eap->init = eap_wsc_init; + eap->reset = eap_wsc_reset; + eap->buildReq = eap_wsc_buildReq; + eap->check = eap_wsc_check; + eap->process = eap_wsc_process; + eap->isDone = eap_wsc_isDone; + eap->isSuccess = eap_wsc_isSuccess; + + ret = eap_server_method_register(eap); + if (ret) + eap_server_method_free(eap); + return ret; +} diff --git a/src/eapol_supp/eapol_supp_sm.c b/src/eapol_supp/eapol_supp_sm.c index ea65d277f..9af1d6d29 100644 --- a/src/eapol_supp/eapol_supp_sm.c +++ b/src/eapol_supp/eapol_supp_sm.c @@ -1742,6 +1742,20 @@ static void eapol_sm_notify_pending(void *ctx) } +#ifdef CONFIG_WPS +static int eapol_sm_wps_cred(void *ctx, struct wps_credential *cred) +{ + struct eapol_sm *sm = ctx; + wpa_printf(MSG_DEBUG, "EAPOL: received new WPS credential"); + if (sm->ctx->wps_cred) + return sm->ctx->wps_cred(sm->ctx->ctx, cred); + return 0; +} +#else /* CONFIG_WPS */ +#define eapol_sm_wps_cred NULL +#endif /* CONFIG_WPS */ + + #if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG) static void eapol_sm_eap_param_needed(void *ctx, const char *field, const char *txt) @@ -1767,6 +1781,7 @@ static struct eapol_callbacks eapol_cb = eapol_sm_set_config_blob, eapol_sm_get_config_blob, eapol_sm_notify_pending, + eapol_sm_wps_cred, eapol_sm_eap_param_needed }; @@ -1804,6 +1819,7 @@ struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx) conf.pkcs11_engine_path = ctx->pkcs11_engine_path; conf.pkcs11_module_path = ctx->pkcs11_module_path; #endif /* EAP_TLS_OPENSSL */ + conf.mac_addr = ctx->mac_addr; sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf); if (sm->eap == NULL) { diff --git a/src/eapol_supp/eapol_supp_sm.h b/src/eapol_supp/eapol_supp_sm.h index 719fbd3fa..f297d2333 100644 --- a/src/eapol_supp/eapol_supp_sm.h +++ b/src/eapol_supp/eapol_supp_sm.h @@ -63,6 +63,7 @@ struct eapol_config { struct eapol_sm; struct wpa_config_blob; +struct wps_credential; /** * struct eapol_ctx - Global (for all networks) EAPOL state machine context @@ -199,6 +200,23 @@ struct eapol_ctx { */ const char *pkcs11_module_path; #endif /* EAP_TLS_OPENSSL */ + /** + * mac_addr - MAC address of the peer + * + * This is only used by EAP-WSC and can be left %NULL if not available. + */ + const u8 *mac_addr; + + /** + * wps_cred - Notify that new credential was received from WPS + * @ctx: Callback context (ctx) + * Returns: 0 on success (credential stored), -1 on failure + * + * This callback is only needed when using WPS Enrollee to configure + * new credentials. This can be left %NULL if no WPS functionality is + * enabled. + */ + int (*wps_cred)(void *ctx, struct wps_credential *cred); /** * eap_param_needed - Notify that EAP parameter is needed diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index 19be48ac7..11c1b5bc8 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -93,6 +93,7 @@ struct radius_server_data { int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; + struct wps_context *wps; int ipv6; struct os_time start_time; struct radius_server_counters counters; @@ -323,6 +324,7 @@ radius_server_get_new_session(struct radius_server_data *data, eap_conf.pac_key_refresh_time = data->pac_key_refresh_time; eap_conf.eap_sim_aka_result_ind = data->eap_sim_aka_result_ind; eap_conf.tnc = data->tnc; + eap_conf.wps = data->wps; sess->eap = eap_server_sm_init(sess, &radius_server_eapol_cb, &eap_conf); if (sess->eap == NULL) { @@ -1040,6 +1042,7 @@ radius_server_init(struct radius_server_conf *conf) data->get_eap_user = conf->get_eap_user; data->eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; data->tnc = conf->tnc; + data->wps = conf->wps; data->clients = radius_server_read_clients(conf->client_file, conf->ipv6); diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 37c652783..2911e28d0 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -33,6 +33,7 @@ struct radius_server_conf { int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; + struct wps_context *wps; int ipv6; int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); diff --git a/src/rsn_supp/preauth.c b/src/rsn_supp/preauth.c index b00c004cf..e2cf2a86a 100644 --- a/src/rsn_supp/preauth.c +++ b/src/rsn_supp/preauth.c @@ -223,6 +223,7 @@ int rsn_preauth_init(struct wpa_sm *sm, const u8 *dst, ctx->eapol_send_ctx = sm; ctx->set_config_blob = sm->ctx->set_config_blob; ctx->get_config_blob = sm->ctx->get_config_blob; + ctx->mac_addr = sm->own_addr; sm->preauth_eapol = eapol_sm_init(ctx); if (sm->preauth_eapol == NULL) { diff --git a/src/wps/Makefile b/src/wps/Makefile new file mode 100644 index 000000000..37d649c52 --- /dev/null +++ b/src/wps/Makefile @@ -0,0 +1,6 @@ +all: + @echo Nothing to be made. + +clean: + for d in $(SUBDIRS); do make -C $$d clean; done + rm -f *~ *.o *.d diff --git a/src/wps/wps.c b/src/wps/wps.c new file mode 100644 index 000000000..0c95a380b --- /dev/null +++ b/src/wps/wps.c @@ -0,0 +1,158 @@ +/* + * Wi-Fi Protected Setup + * Copyright (c) 2007-2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "wps_i.h" +#include "wps_dev_attr.h" + + +struct wps_data * wps_init(const struct wps_config *cfg) +{ + struct wps_data *data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + data->authenticator = cfg->authenticator; + data->wps = cfg->wps; + data->registrar = cfg->registrar; + if (cfg->enrollee_mac_addr) + os_memcpy(data->mac_addr_e, cfg->enrollee_mac_addr, ETH_ALEN); + if (cfg->uuid) { + os_memcpy(cfg->registrar ? data->uuid_r : data->uuid_e, + cfg->uuid, WPS_UUID_LEN); + } + if (cfg->pin) { + data->dev_pw_id = DEV_PW_DEFAULT; + data->dev_password = os_malloc(cfg->pin_len); + if (data->dev_password == NULL) { + os_free(data); + return NULL; + } + os_memcpy(data->dev_password, cfg->pin, cfg->pin_len); + data->dev_password_len = cfg->pin_len; + } + + data->pbc = cfg->pbc; + if (cfg->pbc) { + /* Use special PIN '00000000' for PBC */ + data->dev_pw_id = DEV_PW_PUSHBUTTON; + os_free(data->dev_password); + data->dev_password = os_malloc(8); + if (data->dev_password == NULL) { + os_free(data); + return NULL; + } + os_memset(data->dev_password, '0', 8); + data->dev_password_len = 8; + } + + data->wps_cred_cb = cfg->wps_cred_cb; + data->cb_ctx = cfg->cb_ctx; + + data->state = data->registrar ? RECV_M1 : SEND_M1; + + return data; +} + + +void wps_deinit(struct wps_data *data) +{ + if (data->wps_pin_revealed) { + wpa_printf(MSG_DEBUG, "WPS: Full PIN information revealed and " + "negotiation failed"); + if (data->registrar) + wps_registrar_invalidate_pin(data->registrar, + data->uuid_e); + } else if (data->registrar) + wps_registrar_unlock_pin(data->registrar, data->uuid_e); + + wpabuf_free(data->dh_privkey); + wpabuf_free(data->dh_pubkey_e); + wpabuf_free(data->dh_pubkey_r); + wpabuf_free(data->last_msg); + os_free(data->dev_password); + os_free(data->new_psk); + wps_device_data_free(&data->peer_dev); + os_free(data); +} + + +enum wps_process_res wps_process_msg(struct wps_data *wps, u8 op_code, + const struct wpabuf *msg) +{ + if (wps->registrar) + return wps_registrar_process_msg(wps, op_code, msg); + else + return wps_enrollee_process_msg(wps, op_code, msg); +} + + +struct wpabuf * wps_get_msg(struct wps_data *wps, u8 *op_code) +{ + if (wps->registrar) + return wps_registrar_get_msg(wps, op_code); + else + return wps_enrollee_get_msg(wps, op_code); +} + + +int wps_is_selected_pbc_registrar(const u8 *buf, size_t len) +{ + struct wps_parse_attr attr; + struct wpabuf msg; + + wpabuf_set(&msg, buf, len); + if (wps_parse_msg(&msg, &attr) < 0 || + !attr.selected_registrar || *attr.selected_registrar == 0 || + !attr.sel_reg_config_methods || + !(WPA_GET_BE16(attr.sel_reg_config_methods) & + WPS_CONFIG_PUSHBUTTON) || + !attr.dev_password_id || + WPA_GET_BE16(attr.dev_password_id) != DEV_PW_PUSHBUTTON) + return 0; + + return 1; +} + + +int wps_is_selected_pin_registrar(const u8 *buf, size_t len) +{ + struct wps_parse_attr attr; + struct wpabuf msg; + + wpabuf_set(&msg, buf, len); + if (wps_parse_msg(&msg, &attr) < 0 || + !attr.selected_registrar || *attr.selected_registrar == 0 || + !attr.sel_reg_config_methods || + !(WPA_GET_BE16(attr.sel_reg_config_methods) & + (WPS_CONFIG_LABEL | WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD)) || + !attr.dev_password_id || + WPA_GET_BE16(attr.dev_password_id) == DEV_PW_PUSHBUTTON) + return 0; + + return 1; +} + + +const u8 * wps_get_uuid_e(const u8 *buf, size_t len) +{ + struct wps_parse_attr attr; + struct wpabuf msg; + + wpabuf_set(&msg, buf, len); + if (wps_parse_msg(&msg, &attr) < 0) + return NULL; + return attr.uuid_e; +} diff --git a/src/wps/wps.h b/src/wps/wps.h new file mode 100644 index 000000000..68b000a63 --- /dev/null +++ b/src/wps/wps.h @@ -0,0 +1,132 @@ +/* + * Wi-Fi Protected Setup + * Copyright (c) 2007, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef WPS_H +#define WPS_H + +enum wsc_op_code { + WSC_Start = 0x01, + WSC_ACK = 0x02, + WSC_NACK = 0x03, + WSC_MSG = 0x04, + WSC_Done = 0x05, + WSC_FRAG_ACK = 0x06 +}; + +struct wps_registrar; + +struct wps_credential { + u8 ssid[32]; + size_t ssid_len; + u16 auth_type; + u16 encr_type; + u8 key_idx; + u8 key[64]; + size_t key_len; + u8 mac_addr[ETH_ALEN]; +}; + +struct wps_config { + int authenticator; + struct wps_context *wps; + struct wps_registrar *registrar; /* NULL for Enrollee */ + const u8 *enrollee_mac_addr; /* NULL for Registrar */ + const u8 *pin; /* Enrollee Device Password (NULL for Registrar or PBC) + */ + size_t pin_len; + const u8 *uuid; /* 128-bit Enrollee UUID (NULL for Registrar) */ + int pbc; + + int (*wps_cred_cb)(void *ctx, struct wps_credential *cred); + void *cb_ctx; +}; + +struct wps_data * wps_init(const struct wps_config *cfg); + +void wps_deinit(struct wps_data *data); + +enum wps_process_res { + WPS_DONE, WPS_CONTINUE, WPS_FAILURE, WPS_PENDING +}; +enum wps_process_res wps_process_msg(struct wps_data *wps, u8 op_code, + const struct wpabuf *msg); + +struct wpabuf * wps_get_msg(struct wps_data *wps, u8 *op_code); + +int wps_is_selected_pbc_registrar(const u8 *buf, size_t len); +int wps_is_selected_pin_registrar(const u8 *buf, size_t len); +const u8 * wps_get_uuid_e(const u8 *buf, size_t len); + + +struct wps_device_data { + u8 mac_addr[ETH_ALEN]; + char *device_name; + char *manufacturer; + char *model_name; + char *model_number; + char *serial_number; + u16 categ; + u32 oui; + u16 sub_categ; + u32 os_version; +}; + + +struct wps_registrar_config { + int (*new_psk_cb)(void *ctx, const u8 *mac_addr, const u8 *psk, + size_t psk_len); + int (*set_ie_cb)(void *ctx, const u8 *beacon_ie, size_t beacon_ie_len, + const u8 *probe_resp_ie, size_t probe_resp_ie_len); + void (*pin_needed_cb)(void *ctx, const u8 *uuid_e, + const struct wps_device_data *dev); + void *cb_ctx; +}; + + +struct wps_context { + int ap; + struct wps_registrar *registrar; + int wps_state; + int ap_setup_locked; + u8 uuid[16]; + u8 ssid[32]; + size_t ssid_len; + struct wps_device_data dev; + u16 config_methods; /* bit field of WPS_CONFIG_* */ + u16 encr_types; /* bit field of WPS_ENCR_* */ + u16 auth_types; /* bit field of WPS_AUTH_* */ + u8 *network_key; /* or NULL to generate per-device PSK */ + size_t network_key_len; + + int (*cred_cb)(void *ctx, const struct wps_credential *cred); + void *cb_ctx; +}; + + +struct wps_registrar * +wps_registrar_init(struct wps_context *wps, + const struct wps_registrar_config *cfg); +void wps_registrar_deinit(struct wps_registrar *reg); +int wps_registrar_add_pin(struct wps_registrar *reg, const u8 *uuid, + const u8 *pin, size_t pin_len); +int wps_registrar_invalidate_pin(struct wps_registrar *reg, const u8 *uuid); +int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid); +int wps_registrar_button_pushed(struct wps_registrar *reg); +void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr, + const struct wpabuf *wps_data); + +struct wpabuf * wps_enrollee_build_assoc_req_ie(void); +struct wpabuf * wps_enrollee_build_probe_req_ie(int pbc, const u8 *uuid); + +#endif /* WPS_H */ diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c new file mode 100644 index 000000000..9d76347f5 --- /dev/null +++ b/src/wps/wps_common.c @@ -0,0 +1,839 @@ +/* + * Wi-Fi Protected Setup - common functionality + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "dh_groups.h" +#include "sha256.h" +#include "aes_wrap.h" +#include "crypto.h" +#include "wps_i.h" + + +static int wps_set_attr(struct wps_parse_attr *attr, u16 type, + const u8 *pos, u16 len) +{ + switch (type) { + case ATTR_VERSION: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Version length %u", + len); + return -1; + } + attr->version = pos; + break; + case ATTR_MSG_TYPE: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type " + "length %u", len); + return -1; + } + attr->msg_type = pos; + break; + case ATTR_ENROLLEE_NONCE: + if (len != WPS_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Enrollee Nonce " + "length %u", len); + return -1; + } + attr->enrollee_nonce = pos; + break; + case ATTR_REGISTRAR_NONCE: + if (len != WPS_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Nonce " + "length %u", len); + return -1; + } + attr->registrar_nonce = pos; + break; + case ATTR_UUID_E: + if (len != WPS_UUID_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-E length %u", + len); + return -1; + } + attr->uuid_e = pos; + break; + case ATTR_UUID_R: + if (len != WPS_UUID_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-R length %u", + len); + return -1; + } + attr->uuid_r = pos; + break; + case ATTR_AUTH_TYPE_FLAGS: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication " + "Type Flags length %u", len); + return -1; + } + attr->auth_type_flags = pos; + break; + case ATTR_ENCR_TYPE_FLAGS: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption Type " + "Flags length %u", len); + return -1; + } + attr->encr_type_flags = pos; + break; + case ATTR_CONN_TYPE_FLAGS: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Connection Type " + "Flags length %u", len); + return -1; + } + attr->conn_type_flags = pos; + break; + case ATTR_CONFIG_METHODS: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Config Methods " + "length %u", len); + return -1; + } + attr->config_methods = pos; + break; + case ATTR_SELECTED_REGISTRAR_CONFIG_METHODS: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Selected " + "Registrar Config Methods length %u", len); + return -1; + } + attr->sel_reg_config_methods = pos; + break; + case ATTR_PRIMARY_DEV_TYPE: + if (len != sizeof(struct wps_dev_type)) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Primary Device " + "Type length %u", len); + return -1; + } + attr->primary_dev_type = pos; + break; + case ATTR_RF_BANDS: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid RF Bands length " + "%u", len); + return -1; + } + attr->rf_bands = pos; + break; + case ATTR_ASSOC_STATE: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Association State " + "length %u", len); + return -1; + } + attr->assoc_state = pos; + break; + case ATTR_CONFIG_ERROR: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Configuration " + "Error length %u", len); + return -1; + } + attr->config_error = pos; + break; + case ATTR_DEV_PASSWORD_ID: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Device Password " + "ID length %u", len); + return -1; + } + attr->dev_password_id = pos; + break; + case ATTR_OS_VERSION: + if (len != 4) { + wpa_printf(MSG_DEBUG, "WPS: Invalid OS Version length " + "%u", len); + return -1; + } + attr->os_version = pos; + break; + case ATTR_WPS_STATE: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Wi-Fi Protected " + "Setup State length %u", len); + return -1; + } + attr->wps_state = pos; + break; + case ATTR_AUTHENTICATOR: + if (len != WPS_AUTHENTICATOR_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Authenticator " + "length %u", len); + return -1; + } + attr->authenticator = pos; + break; + case ATTR_R_HASH1: + if (len != WPS_HASH_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash1 length %u", + len); + return -1; + } + attr->r_hash1 = pos; + break; + case ATTR_R_HASH2: + if (len != WPS_HASH_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash2 length %u", + len); + return -1; + } + attr->r_hash2 = pos; + break; + case ATTR_E_HASH1: + if (len != WPS_HASH_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash1 length %u", + len); + return -1; + } + attr->e_hash1 = pos; + break; + case ATTR_E_HASH2: + if (len != WPS_HASH_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash2 length %u", + len); + return -1; + } + attr->e_hash2 = pos; + break; + case ATTR_R_SNONCE1: + if (len != WPS_SECRET_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce1 length " + "%u", len); + return -1; + } + attr->r_snonce1 = pos; + break; + case ATTR_R_SNONCE2: + if (len != WPS_SECRET_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce2 length " + "%u", len); + return -1; + } + attr->r_snonce2 = pos; + break; + case ATTR_E_SNONCE1: + if (len != WPS_SECRET_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce1 length " + "%u", len); + return -1; + } + attr->e_snonce1 = pos; + break; + case ATTR_E_SNONCE2: + if (len != WPS_SECRET_NONCE_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce2 length " + "%u", len); + return -1; + } + attr->e_snonce2 = pos; + break; + case ATTR_KEY_WRAP_AUTH: + if (len != WPS_KWA_LEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Key Wrap " + "Authenticator length %u", len); + return -1; + } + attr->key_wrap_auth = pos; + break; + case ATTR_AUTH_TYPE: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication " + "Type length %u", len); + return -1; + } + attr->auth_type = pos; + break; + case ATTR_ENCR_TYPE: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption " + "Type length %u", len); + return -1; + } + attr->encr_type = pos; + break; + case ATTR_NETWORK_INDEX: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Network Index " + "length %u", len); + return -1; + } + attr->network_idx = pos; + break; + case ATTR_NETWORK_KEY_INDEX: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key Index " + "length %u", len); + return -1; + } + attr->network_key_idx = pos; + break; + case ATTR_MAC_ADDR: + if (len != ETH_ALEN) { + wpa_printf(MSG_DEBUG, "WPS: Invalid MAC Address " + "length %u", len); + return -1; + } + attr->mac_addr = pos; + break; + case ATTR_KEY_PROVIDED_AUTO: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Key Provided " + "Automatically length %u", len); + return -1; + } + attr->key_prov_auto = pos; + break; + case ATTR_802_1X_ENABLED: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid 802.1X Enabled " + "length %u", len); + return -1; + } + attr->dot1x_enabled = pos; + break; + case ATTR_SELECTED_REGISTRAR: + if (len != 1) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Selected Registrar" + " length %u", len); + return -1; + } + attr->selected_registrar = pos; + break; + case ATTR_MANUFACTURER: + attr->manufacturer = pos; + attr->manufacturer_len = len; + break; + case ATTR_MODEL_NAME: + attr->model_name = pos; + attr->model_name_len = len; + break; + case ATTR_MODEL_NUMBER: + attr->model_number = pos; + attr->model_number_len = len; + break; + case ATTR_SERIAL_NUMBER: + attr->serial_number = pos; + attr->serial_number_len = len; + break; + case ATTR_DEV_NAME: + attr->dev_name = pos; + attr->dev_name_len = len; + break; + case ATTR_PUBLIC_KEY: + attr->public_key = pos; + attr->public_key_len = len; + break; + case ATTR_ENCR_SETTINGS: + attr->encr_settings = pos; + attr->encr_settings_len = len; + break; + case ATTR_CRED: + if (attr->num_cred >= MAX_CRED_COUNT) { + wpa_printf(MSG_DEBUG, "WPS: Skipped Credential " + "attribute (max %d credentials)", + MAX_CRED_COUNT); + break; + } + attr->cred[attr->num_cred] = pos; + attr->cred_len[attr->num_cred] = len; + attr->num_cred++; + break; + case ATTR_SSID: + attr->ssid = pos; + attr->ssid_len = len; + break; + case ATTR_NETWORK_KEY: + attr->network_key = pos; + attr->network_key_len = len; + break; + case ATTR_EAP_TYPE: + attr->eap_type = pos; + attr->eap_type_len = len; + break; + case ATTR_EAP_IDENTITY: + attr->eap_identity = pos; + attr->eap_identity_len = len; + break; + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported attribute type 0x%x " + "len=%u", type, len); + break; + } + + return 0; +} + + +int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr) +{ + const u8 *pos, *end; + u16 type, len; + + os_memset(attr, 0, sizeof(*attr)); + pos = wpabuf_head(msg); + end = pos + wpabuf_len(msg); + + while (pos < end) { + if (end - pos < 4) { + wpa_printf(MSG_DEBUG, "WPS: Invalid message - " + "%lu bytes remaining", + (unsigned long) (end - pos)); + return -1; + } + + type = WPA_GET_BE16(pos); + pos += 2; + len = WPA_GET_BE16(pos); + pos += 2; + wpa_printf(MSG_MSGDUMP, "WPS: attr type=0x%x len=%u", + type, len); + if (len > end - pos) { + wpa_printf(MSG_DEBUG, "WPS: Attribute overflow"); + return -1; + } + + if (wps_set_attr(attr, type, pos, len) < 0) + return -1; + + pos += len; + } + + return 0; +} + + +void wps_kdf(const u8 *key, const char *label, u8 *res, size_t res_len) +{ + u8 i_buf[4], key_bits[4]; + const u8 *addr[3]; + size_t len[3]; + int i, iter; + u8 hash[SHA256_MAC_LEN], *opos; + size_t left; + + WPA_PUT_BE32(key_bits, res_len * 8); + + addr[0] = i_buf; + len[0] = sizeof(i_buf); + addr[1] = (const u8 *) label; + len[1] = os_strlen(label); + addr[2] = key_bits; + len[2] = sizeof(key_bits); + + iter = (res_len + SHA256_MAC_LEN - 1) / SHA256_MAC_LEN; + opos = res; + left = res_len; + + for (i = 1; i <= iter; i++) { + WPA_PUT_BE32(i_buf, i); + hmac_sha256_vector(key, SHA256_MAC_LEN, 3, addr, len, hash); + if (i < iter) { + os_memcpy(opos, hash, SHA256_MAC_LEN); + opos += SHA256_MAC_LEN; + left -= SHA256_MAC_LEN; + } else + os_memcpy(opos, hash, left); + } +} + + +int wps_build_public_key(struct wps_data *wps, struct wpabuf *msg) +{ + struct wpabuf *pubkey; + + wpa_printf(MSG_DEBUG, "WPS: * Public Key"); + pubkey = dh_init(dh_groups_get(WPS_DH_GROUP), &wps->dh_privkey); + if (pubkey == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to initialize " + "Diffie-Hellman handshake"); + return -1; + } + + wpabuf_put_be16(msg, ATTR_PUBLIC_KEY); + wpabuf_put_be16(msg, wpabuf_len(pubkey)); + wpabuf_put_buf(msg, pubkey); + + if (wps->registrar) { + wpabuf_free(wps->dh_pubkey_r); + wps->dh_pubkey_r = pubkey; + } else { + wpabuf_free(wps->dh_pubkey_e); + wps->dh_pubkey_e = pubkey; + } + + return 0; +} + + +int wps_derive_keys(struct wps_data *wps) +{ + struct wpabuf *pubkey, *dh_shared; + u8 dhkey[SHA256_MAC_LEN], kdk[SHA256_MAC_LEN]; + const u8 *addr[3]; + size_t len[3]; + u8 keys[WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN + WPS_EMSK_LEN]; + + if (wps->dh_privkey == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Own DH private key not available"); + return -1; + } + + pubkey = wps->registrar ? wps->dh_pubkey_e : wps->dh_pubkey_r; + if (pubkey == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Peer DH public key not available"); + return -1; + } + + dh_shared = dh_derive_shared(pubkey, wps->dh_privkey, + dh_groups_get(WPS_DH_GROUP)); + if (dh_shared == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to derive DH shared key"); + return -1; + } + + /* Own DH private key is not needed anymore */ + wpabuf_free(wps->dh_privkey); + wps->dh_privkey = NULL; + + wpa_hexdump_buf_key(MSG_DEBUG, "WPS: DH shared key", dh_shared); + + /* DHKey = SHA-256(g^AB mod p) */ + addr[0] = wpabuf_head(dh_shared); + len[0] = wpabuf_len(dh_shared); + sha256_vector(1, addr, len, dhkey); + wpa_hexdump_key(MSG_DEBUG, "WPS: DHKey", dhkey, sizeof(dhkey)); + wpabuf_free(dh_shared); + + /* KDK = HMAC-SHA-256_DHKey(N1 || EnrolleeMAC || N2) */ + addr[0] = wps->nonce_e; + len[0] = WPS_NONCE_LEN; + addr[1] = wps->mac_addr_e; + len[1] = ETH_ALEN; + addr[2] = wps->nonce_r; + len[2] = WPS_NONCE_LEN; + hmac_sha256_vector(dhkey, sizeof(dhkey), 3, addr, len, kdk); + wpa_hexdump_key(MSG_DEBUG, "WPS: KDK", kdk, sizeof(kdk)); + + wps_kdf(kdk, "Wi-Fi Easy and Secure Key Derivation", + keys, sizeof(keys)); + os_memcpy(wps->authkey, keys, WPS_AUTHKEY_LEN); + os_memcpy(wps->keywrapkey, keys + WPS_AUTHKEY_LEN, WPS_KEYWRAPKEY_LEN); + os_memcpy(wps->emsk, keys + WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN, + WPS_EMSK_LEN); + + wpa_hexdump_key(MSG_DEBUG, "WPS: AuthKey", + wps->authkey, WPS_AUTHKEY_LEN); + wpa_hexdump_key(MSG_DEBUG, "WPS: KeyWrapKey", + wps->keywrapkey, WPS_KEYWRAPKEY_LEN); + wpa_hexdump_key(MSG_DEBUG, "WPS: EMSK", wps->emsk, WPS_EMSK_LEN); + + return 0; +} + + +int wps_build_authenticator(struct wps_data *wps, struct wpabuf *msg) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[2]; + size_t len[2]; + + if (wps->last_msg == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Last message not available for " + "building authenticator"); + return -1; + } + + /* Authenticator = HMAC-SHA256_AuthKey(M_prev || M_curr*) + * (M_curr* is M_curr without the Authenticator attribute) + */ + addr[0] = wpabuf_head(wps->last_msg); + len[0] = wpabuf_len(wps->last_msg); + addr[1] = wpabuf_head(msg); + len[1] = wpabuf_len(msg); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 2, addr, len, hash); + + wpa_printf(MSG_DEBUG, "WPS: * Authenticator"); + wpabuf_put_be16(msg, ATTR_AUTHENTICATOR); + wpabuf_put_be16(msg, WPS_AUTHENTICATOR_LEN); + wpabuf_put_data(msg, hash, WPS_AUTHENTICATOR_LEN); + + return 0; +} + + +int wps_process_authenticator(struct wps_data *wps, const u8 *authenticator, + const struct wpabuf *msg) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[2]; + size_t len[2]; + + if (authenticator == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Authenticator attribute " + "included"); + return -1; + } + + if (wps->last_msg == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Last message not available for " + "validating authenticator"); + return -1; + } + + /* Authenticator = HMAC-SHA256_AuthKey(M_prev || M_curr*) + * (M_curr* is M_curr without the Authenticator attribute) + */ + addr[0] = wpabuf_head(wps->last_msg); + len[0] = wpabuf_len(wps->last_msg); + addr[1] = wpabuf_head(msg); + len[1] = wpabuf_len(msg) - 4 - WPS_AUTHENTICATOR_LEN; + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 2, addr, len, hash); + + if (os_memcmp(hash, authenticator, WPS_AUTHENTICATOR_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: Incorrect Authenticator"); + return -1; + } + + return 0; +} + + +void wps_derive_psk(struct wps_data *wps, const u8 *dev_passwd, + size_t dev_passwd_len) +{ + u8 hash[SHA256_MAC_LEN]; + + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, dev_passwd, + (dev_passwd_len + 1) / 2, hash); + os_memcpy(wps->psk1, hash, WPS_PSK_LEN); + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, + dev_passwd + (dev_passwd_len + 1) / 2, + dev_passwd_len / 2, hash); + os_memcpy(wps->psk2, hash, WPS_PSK_LEN); + + wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: Device Password", + dev_passwd, dev_passwd_len); + wpa_hexdump_key(MSG_DEBUG, "WPS: PSK1", wps->psk1, WPS_PSK_LEN); + wpa_hexdump_key(MSG_DEBUG, "WPS: PSK2", wps->psk2, WPS_PSK_LEN); +} + + +struct wpabuf * wps_decrypt_encr_settings(struct wps_data *wps, const u8 *encr, + size_t encr_len) +{ + struct wpabuf *decrypted; + const size_t block_size = 16; + size_t i; + u8 pad; + const u8 *pos; + + /* AES-128-CBC */ + if (encr == NULL || encr_len < 2 * block_size || encr_len % block_size) + { + wpa_printf(MSG_DEBUG, "WPS: No Encrypted Settings received"); + return NULL; + } + + decrypted = wpabuf_alloc(encr_len - block_size); + if (decrypted == NULL) + return NULL; + + wpa_hexdump(MSG_MSGDUMP, "WPS: Encrypted Settings", encr, encr_len); + wpabuf_put_data(decrypted, encr + block_size, encr_len - block_size); + if (aes_128_cbc_decrypt(wps->keywrapkey, encr, wpabuf_mhead(decrypted), + wpabuf_len(decrypted))) { + wpabuf_free(decrypted); + return NULL; + } + + wpa_hexdump_buf_key(MSG_MSGDUMP, "WPS: Decrypted Encrypted Settings", + decrypted); + + pos = wpabuf_head_u8(decrypted) + wpabuf_len(decrypted) - 1; + pad = *pos; + if (pad > wpabuf_len(decrypted)) { + wpa_printf(MSG_DEBUG, "WPS: Invalid PKCS#5 v2.0 pad value"); + wpabuf_free(decrypted); + return NULL; + } + for (i = 0; i < pad; i++) { + if (*pos-- != pad) { + wpa_printf(MSG_DEBUG, "WPS: Invalid PKCS#5 v2.0 pad " + "string"); + wpabuf_free(decrypted); + return NULL; + } + } + decrypted->used -= pad; + + return decrypted; +} + + +int wps_process_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg, + const u8 *key_wrap_auth) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *head; + size_t len; + + if (key_wrap_auth == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No KWA in decrypted attribute"); + return -1; + } + + head = wpabuf_head(msg); + len = wpabuf_len(msg) - 4 - WPS_KWA_LEN; + if (head + len != key_wrap_auth - 4) { + wpa_printf(MSG_DEBUG, "WPS: KWA not in the end of the " + "decrypted attribute"); + return -1; + } + + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, head, len, hash); + if (os_memcmp(hash, key_wrap_auth, WPS_KWA_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: Invalid KWA"); + return -1; + } + + return 0; +} + + +int wps_build_version(struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Version"); + wpabuf_put_be16(msg, ATTR_VERSION); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, WPS_VERSION); + return 0; +} + + +int wps_build_msg_type(struct wpabuf *msg, enum wps_msg_type msg_type) +{ + wpa_printf(MSG_DEBUG, "WPS: * Message Type (%d)", msg_type); + wpabuf_put_be16(msg, ATTR_MSG_TYPE); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, msg_type); + return 0; +} + + +int wps_build_enrollee_nonce(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Enrollee Nonce"); + wpabuf_put_be16(msg, ATTR_ENROLLEE_NONCE); + wpabuf_put_be16(msg, WPS_NONCE_LEN); + wpabuf_put_data(msg, wps->nonce_e, WPS_NONCE_LEN); + return 0; +} + + +int wps_build_registrar_nonce(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Registrar Nonce"); + wpabuf_put_be16(msg, ATTR_REGISTRAR_NONCE); + wpabuf_put_be16(msg, WPS_NONCE_LEN); + wpabuf_put_data(msg, wps->nonce_r, WPS_NONCE_LEN); + return 0; +} + + +int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Authentication Type Flags"); + wpabuf_put_be16(msg, ATTR_AUTH_TYPE_FLAGS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, WPS_AUTH_TYPES); + return 0; +} + + +int wps_build_encr_type_flags(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Encryption Type Flags"); + wpabuf_put_be16(msg, ATTR_ENCR_TYPE_FLAGS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, WPS_ENCR_TYPES); + return 0; +} + + +int wps_build_conn_type_flags(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Connection Type Flags"); + wpabuf_put_be16(msg, ATTR_CONN_TYPE_FLAGS); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, WPS_CONN_ESS); + return 0; +} + + +int wps_build_assoc_state(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Association State"); + wpabuf_put_be16(msg, ATTR_ASSOC_STATE); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, WPS_ASSOC_NOT_ASSOC); + return 0; +} + + +int wps_build_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg) +{ + u8 hash[SHA256_MAC_LEN]; + + wpa_printf(MSG_DEBUG, "WPS: * Key Wrap Authenticator"); + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, wpabuf_head(msg), + wpabuf_len(msg), hash); + + wpabuf_put_be16(msg, ATTR_KEY_WRAP_AUTH); + wpabuf_put_be16(msg, WPS_KWA_LEN); + wpabuf_put_data(msg, hash, WPS_KWA_LEN); + return 0; +} + + +int wps_build_encr_settings(struct wps_data *wps, struct wpabuf *msg, + struct wpabuf *plain) +{ + size_t pad_len; + const size_t block_size = 16; + u8 *iv, *data; + + wpa_printf(MSG_DEBUG, "WPS: * Encrypted Settings"); + + /* PKCS#5 v2.0 pad */ + pad_len = block_size - wpabuf_len(plain) % block_size; + os_memset(wpabuf_put(plain, pad_len), pad_len, pad_len); + + wpabuf_put_be16(msg, ATTR_ENCR_SETTINGS); + wpabuf_put_be16(msg, block_size + wpabuf_len(plain)); + + iv = wpabuf_put(msg, block_size); + if (os_get_random(iv, block_size) < 0) + return -1; + + data = wpabuf_put(msg, 0); + wpabuf_put_buf(msg, plain); + if (aes_128_cbc_encrypt(wps->keywrapkey, iv, data, wpabuf_len(plain))) + return -1; + + return 0; +} diff --git a/src/wps/wps_defs.h b/src/wps/wps_defs.h new file mode 100644 index 000000000..141efba06 --- /dev/null +++ b/src/wps/wps_defs.h @@ -0,0 +1,294 @@ +/* + * Wi-Fi Protected Setup - message definitions + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef WPS_DEFS_H +#define WPS_DEFS_H + +#define WPS_VERSION 0x10 + +/* Diffie-Hellman 1536-bit MODP Group; RFC 3526, Group 5 */ +#define WPS_DH_GROUP 5 + +#define WPS_UUID_LEN 16 +#define WPS_NONCE_LEN 16 +#define WPS_AUTHENTICATOR_LEN 8 +#define WPS_AUTHKEY_LEN 32 +#define WPS_KEYWRAPKEY_LEN 16 +#define WPS_EMSK_LEN 32 +#define WPS_PSK_LEN 16 +#define WPS_SECRET_NONCE_LEN 16 +#define WPS_HASH_LEN 32 +#define WPS_KWA_LEN 8 + +/* Attribute Types */ +enum wps_attribute { + ATTR_AP_CHANNEL = 0x1001, + ATTR_ASSOC_STATE = 0x1002, + ATTR_AUTH_TYPE = 0x1003, + ATTR_AUTH_TYPE_FLAGS = 0x1004, + ATTR_AUTHENTICATOR = 0x1005, + ATTR_CONFIG_METHODS = 0x1008, + ATTR_CONFIG_ERROR = 0x1009, + ATTR_CONFIRM_URL4 = 0x100a, + ATTR_CONFIRM_URL6 = 0x100b, + ATTR_CONN_TYPE = 0x100c, + ATTR_CONN_TYPE_FLAGS = 0x100d, + ATTR_CRED = 0x100e, + ATTR_ENCR_TYPE = 0x100f, + ATTR_ENCR_TYPE_FLAGS = 0x1010, + ATTR_DEV_NAME = 0x1011, + ATTR_DEV_PASSWORD_ID = 0x1012, + ATTR_E_HASH1 = 0x1014, + ATTR_E_HASH2 = 0x1015, + ATTR_E_SNONCE1 = 0x1016, + ATTR_E_SNONCE2 = 0x1017, + ATTR_ENCR_SETTINGS = 0x1018, + ATTR_ENROLLEE_NONCE = 0x101a, + ATTR_FEATURE_ID = 0x101b, + ATTR_IDENTITY = 0x101c, + ATTR_IDENTITY_PROOF = 0x101d, + ATTR_KEY_WRAP_AUTH = 0x101e, + ATTR_KEY_ID = 0x101f, + ATTR_MAC_ADDR = 0x1020, + ATTR_MANUFACTURER = 0x1021, + ATTR_MSG_TYPE = 0x1022, + ATTR_MODEL_NAME = 0x1023, + ATTR_MODEL_NUMBER = 0x1024, + ATTR_NETWORK_INDEX = 0x1026, + ATTR_NETWORK_KEY = 0x1027, + ATTR_NETWORK_KEY_INDEX = 0x1028, + ATTR_NEW_DEVICE_NAME = 0x1029, + ATTR_NEW_PASSWORD = 0x102a, + ATTR_OOB_DEVICE_PASSWORD = 0x102c, + ATTR_OS_VERSION = 0x102d, + ATTR_POWER_LEVEL = 0x102f, + ATTR_PSK_CURRENT = 0x1030, + ATTR_PSK_MAX = 0x1031, + ATTR_PUBLIC_KEY = 0x1032, + ATTR_RADIO_ENABLE = 0x1033, + ATTR_REBOOT = 0x1034, + ATTR_REGISTRAR_CURRENT = 0x1035, + ATTR_REGISTRAR_ESTABLISHED = 0x1036, + ATTR_REGISTRAR_LIST = 0x1037, + ATTR_REGISTRAR_MAX = 0x1038, + ATTR_REGISTRAR_NONCE = 0x1039, + ATTR_REQUEST_TYPE = 0x103a, + ATTR_RESPONSE_TYPE = 0x103b, + ATTR_RF_BANDS = 0x103c, + ATTR_R_HASH1 = 0x103d, + ATTR_R_HASH2 = 0x103e, + ATTR_R_SNONCE1 = 0x103f, + ATTR_R_SNONCE2 = 0x1040, + ATTR_SELECTED_REGISTRAR = 0x1041, + ATTR_SERIAL_NUMBER = 0x1042, + ATTR_WPS_STATE = 0x1044, + ATTR_SSID = 0x1045, + ATTR_TOTAL_NETWORKS = 0x1046, + ATTR_UUID_E = 0x1047, + ATTR_UUID_R = 0x1048, + ATTR_VENDOR_EXT = 0x1049, + ATTR_VERSION = 0x104a, + ATTR_X509_CERT_REQ = 0x104b, + ATTR_X509_CERT = 0x104c, + ATTR_EAP_IDENTITY = 0x104d, + ATTR_MSG_COUNTER = 0x104e, + ATTR_PUBKEY_HASH = 0x104f, + ATTR_REKEY_KEY = 0x1050, + ATTR_KEY_LIFETIME = 0x1051, + ATTR_PERMITTED_CFG_METHODS = 0x1052, + ATTR_SELECTED_REGISTRAR_CONFIG_METHODS = 0x1053, + ATTR_PRIMARY_DEV_TYPE = 0x1054, + ATTR_SECONDARY_DEV_TYP_ELIST = 0x1055, + ATTR_PORTABLE_DEV = 0x1056, + ATTR_AP_SETUP_LOCKED = 0x1057, + ATTR_APPLICATION_EXT = 0x1058, + ATTR_EAP_TYPE = 0x1059, + ATTR_IV = 0x1060, + ATTR_KEY_PROVIDED_AUTO = 0x1061, + ATTR_802_1X_ENABLED = 0x1062, + ATTR_APPSESSIONKEY = 0x1063, + ATTR_WEPTRANSMITKEY = 0x1064 +}; + +/* Device Password ID */ +enum wps_dev_password_id { + DEV_PW_DEFAULT = 0x0000, + DEV_PW_USER_SPECIFIED = 0x0001, + DEV_PW_MACHINE_SPECIFIED = 0x0002, + DEV_PW_REKEY = 0x0003, + DEV_PW_PUSHBUTTON = 0x0004, + DEV_PW_REGISTRAR_SPECIFIED = 0x0005 +}; + +/* Message Type */ +enum wps_msg_type { + WPS_Beacon = 0x01, + WPS_ProbeRequest = 0x02, + WPS_ProbeResponse = 0x03, + WPS_M1 = 0x04, + WPS_M2 = 0x05, + WPS_M2D = 0x06, + WPS_M3 = 0x07, + WPS_M4 = 0x08, + WPS_M5 = 0x09, + WPS_M6 = 0x0a, + WPS_M7 = 0x0b, + WPS_M8 = 0x0c, + WPS_WSC_ACK = 0x0d, + WPS_WSC_NACK = 0x0e, + WPS_WSC_DONE = 0x0f +}; + +/* Authentication Type Flags */ +#define WPS_AUTH_OPEN 0x0001 +#define WPS_AUTH_WPAPSK 0x0002 +#define WPS_AUTH_SHARED 0x0004 +#define WPS_AUTH_WPA 0x0008 +#define WPS_AUTH_WPA2 0x0010 +#define WPS_AUTH_WPA2PSK 0x0020 +#define WPS_AUTH_TYPES (WPS_AUTH_OPEN | WPS_AUTH_WPAPSK | WPS_AUTH_SHARED | \ + WPS_AUTH_WPA | WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK) + +/* Encryption Type Flags */ +#define WPS_ENCR_NONE 0x0001 +#define WPS_ENCR_WEP 0x0002 +#define WPS_ENCR_TKIP 0x0004 +#define WPS_ENCR_AES 0x0008 +#define WPS_ENCR_TYPES (WPS_ENCR_NONE | WPS_ENCR_WEP | WPS_ENCR_TKIP | \ + WPS_ENCR_AES) + +/* Configuration Error */ +enum wps_config_error { + WPS_CFG_NO_ERROR = 0, + WPS_CFG_OOB_IFACE_READ_ERROR = 1, + WPS_CFG_DECRYPTION_CRC_FAILURE = 2, + WPS_CFG_24_CHAN_NOT_SUPPORTED = 3, + WPS_CFG_50_CHAN_NOT_SUPPORTED = 4, + WPS_CFG_SIGNAL_TOO_WEAK = 5, + WPS_CFG_NETWORK_AUTH_FAILURE = 6, + WPS_CFG_NETWORK_ASSOC_FAILURE = 7, + WPS_CFG_NO_DHCP_RESPONSE = 8, + WPS_CFG_FAILED_DHCP_CONFIG = 9, + WPS_CFG_IP_ADDR_CONFLICT = 10, + WPS_CFG_NO_CONN_TO_REGISTRAR = 11, + WPS_CFG_MULTIPLE_PBC_DETECTED = 12, + WPS_CFG_ROGUE_SUSPECTED = 13, + WPS_CFG_DEVICE_BUSY = 14, + WPS_CFG_SETUP_LOCKED = 15, + WPS_CFG_MSG_TIMEOUT = 16, + WPS_CFG_REG_SESS_TIMEOUT = 17, + WPS_CFG_DEV_PASSWORD_AUTH_FAILURE = 18 +}; + +/* RF Bands */ +#define WPS_RF_24GHZ 0x01 +#define WPS_RF_50GHZ 0x02 + +/* Config Methods */ +#define WPS_CONFIG_USBA 0x0001 +#define WPS_CONFIG_ETHERNET 0x0002 +#define WPS_CONFIG_LABEL 0x0004 +#define WPS_CONFIG_DISPLAY 0x0008 +#define WPS_CONFIG_EXT_NFC_TOKEN 0x0010 +#define WPS_CONFIG_INT_NFC_TOKEN 0x0020 +#define WPS_CONFIG_NFC_INTERFACE 0x0040 +#define WPS_CONFIG_PUSHBUTTON 0x0080 +#define WPS_CONFIG_KEYPAD 0x0100 + +/* Connection Type Flags */ +#define WPS_CONN_ESS 0x01 +#define WPS_CONN_IBSS 0x02 + +/* Wi-Fi Protected Setup State */ +enum wps_state { + WPS_STATE_NOT_CONFIGURED = 1, + WPS_STATE_CONFIGURED = 2 +}; + +/* Association State */ +enum wps_assoc_state { + WPS_ASSOC_NOT_ASSOC = 0, + WPS_ASSOC_CONN_SUCCESS = 1, + WPS_ASSOC_CFG_FAILURE = 2, + WPS_ASSOC_FAILURE = 3, + WPS_ASSOC_IP_FAILURE = 4 +}; + + +/* Primary Device Type */ +struct wps_dev_type { + u8 categ_id[2]; + u8 oui[4]; + u8 sub_categ_id[2]; +}; + +#define WPS_DEV_OUI_WFA 0x0050f204 + +enum wps_dev_categ { + WPS_DEV_COMPUTER = 1, + WPS_DEV_INPUT = 2, + WPS_DEV_PRINTER = 3, + WPS_DEV_CAMERA = 4, + WPS_DEV_STORAGE = 5, + WPS_DEV_NETWORK_INFRA = 6, + WPS_DEV_DISPLAY = 7, + WPS_DEV_MULTIMEDIA = 8, + WPS_DEV_GAMING = 9, + WPS_DEV_PHONE = 10 +}; + +enum wps_dev_subcateg { + WPS_DEV_COMPUTER_PC = 1, + WPS_DEV_COMPUTER_SERVER = 2, + WPS_DEV_COMPUTER_MEDIA_CENTER = 3, + WPS_DEV_PRINTER_PRINTER = 1, + WPS_DEV_PRINTER_SCANNER = 2, + WPS_DEV_CAMERA_DIGITAL_STILL_CAMERA = 1, + WPS_DEV_STORAGE_NAS = 1, + WPS_DEV_NETWORK_INFRA_AP = 1, + WPS_DEV_NETWORK_INFRA_ROUTER = 2, + WPS_DEV_NETWORK_INFRA_SWITCH = 3, + WPS_DEV_DISPLAY_TV = 1, + WPS_DEV_DISPLAY_PICTURE_FRAME = 2, + WPS_DEV_DISPLAY_PROJECTOR = 3, + WPS_DEV_MULTIMEDIA_DAR = 1, + WPS_DEV_MULTIMEDIA_PVR = 2, + WPS_DEV_MULTIMEDIA_MCX = 3, + WPS_DEV_GAMING_XBOX = 1, + WPS_DEV_GAMING_XBOX360 = 2, + WPS_DEV_GAMING_PLAYSTATION = 3, + WPS_DEV_PHONE_WINDOWS_MOBILE = 1 +}; + + +/* Request Type */ +enum wps_request_type { + WPS_REQ_ENROLLEE_INFO = 0, + WPS_REQ_ENROLLEE = 1, + WPS_REQ_REGISTRAR = 2, + WPS_REQ_WLAN_MANAGER_REGISTRAR = 3 +}; + +/* Response Type */ +enum wps_response_type { + WPS_RESP_ENROLLEE_INFO = 0, + WPS_RESP_ENROLLEE = 1, + WPS_RESP_REGISTRAR = 2, + WPS_RESP_AP = 3 +}; + +/* Walk Time for push button configuration (in seconds) */ +#define WPS_PBC_WALK_TIME 120 + +#endif /* WPS_DEFS_H */ diff --git a/src/wps/wps_dev_attr.c b/src/wps/wps_dev_attr.c new file mode 100644 index 000000000..d559b2637 --- /dev/null +++ b/src/wps/wps_dev_attr.c @@ -0,0 +1,316 @@ +/* + * Wi-Fi Protected Setup - device attributes + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "wps_i.h" +#include "wps_dev_attr.h" + + +static int wps_build_manufacturer(struct wps_device_data *dev, + struct wpabuf *msg) +{ + size_t len; + wpa_printf(MSG_DEBUG, "WPS: * Manufacturer"); + wpabuf_put_be16(msg, ATTR_MANUFACTURER); + len = dev->manufacturer ? os_strlen(dev->manufacturer) : 0; + wpabuf_put_be16(msg, len); + wpabuf_put_data(msg, dev->manufacturer, len); + return 0; +} + + +static int wps_build_model_name(struct wps_device_data *dev, + struct wpabuf *msg) +{ + size_t len; + wpa_printf(MSG_DEBUG, "WPS: * Model Name"); + wpabuf_put_be16(msg, ATTR_MODEL_NAME); + len = dev->model_name ? os_strlen(dev->model_name) : 0; + wpabuf_put_be16(msg, len); + wpabuf_put_data(msg, dev->model_name, len); + return 0; +} + + +static int wps_build_model_number(struct wps_device_data *dev, + struct wpabuf *msg) +{ + size_t len; + wpa_printf(MSG_DEBUG, "WPS: * Model Number"); + wpabuf_put_be16(msg, ATTR_MODEL_NUMBER); + len = dev->model_number ? os_strlen(dev->model_number) : 0; + wpabuf_put_be16(msg, len); + wpabuf_put_data(msg, dev->model_number, len); + return 0; +} + + +static int wps_build_serial_number(struct wps_device_data *dev, + struct wpabuf *msg) +{ + size_t len; + wpa_printf(MSG_DEBUG, "WPS: * Serial Number"); + wpabuf_put_be16(msg, ATTR_SERIAL_NUMBER); + len = dev->serial_number ? os_strlen(dev->serial_number) : 0; + wpabuf_put_be16(msg, len); + wpabuf_put_data(msg, dev->serial_number, len); + return 0; +} + + +static int wps_build_primary_dev_type(struct wps_device_data *dev, + struct wpabuf *msg) +{ + struct wps_dev_type *d; + wpa_printf(MSG_DEBUG, "WPS: * Primary Device Type"); + wpabuf_put_be16(msg, ATTR_PRIMARY_DEV_TYPE); + wpabuf_put_be16(msg, sizeof(*d)); + d = wpabuf_put(msg, sizeof(*d)); + WPA_PUT_BE16(d->categ_id, dev->categ); + WPA_PUT_BE32(d->oui, dev->oui); + WPA_PUT_BE16(d->sub_categ_id, dev->sub_categ); + return 0; +} + + +static int wps_build_dev_name(struct wps_device_data *dev, struct wpabuf *msg) +{ + size_t len; + wpa_printf(MSG_DEBUG, "WPS: * Device Name"); + wpabuf_put_be16(msg, ATTR_DEV_NAME); + len = dev->device_name ? os_strlen(dev->device_name) : 0; + wpabuf_put_be16(msg, len); + wpabuf_put_data(msg, dev->device_name, len); + return 0; +} + + +int wps_build_device_attrs(struct wps_device_data *dev, struct wpabuf *msg) +{ + if (wps_build_manufacturer(dev, msg) || + wps_build_model_name(dev, msg) || + wps_build_model_number(dev, msg) || + wps_build_serial_number(dev, msg) || + wps_build_primary_dev_type(dev, msg) || + wps_build_dev_name(dev, msg)) + return -1; + return 0; +} + + +int wps_build_os_version(struct wps_device_data *dev, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * OS Version"); + wpabuf_put_be16(msg, ATTR_OS_VERSION); + wpabuf_put_be16(msg, 4); + wpabuf_put_be32(msg, 0x80000000 | dev->os_version); + return 0; +} + + +static int wps_process_manufacturer(struct wps_device_data *dev, const u8 *str, + size_t str_len) +{ + if (str == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Manufacturer received"); + return -1; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Manufacturer", str, str_len); + + os_free(dev->manufacturer); + dev->manufacturer = os_malloc(str_len + 1); + if (dev->manufacturer == NULL) + return -1; + os_memcpy(dev->manufacturer, str, str_len); + dev->manufacturer[str_len] = '\0'; + + return 0; +} + + +static int wps_process_model_name(struct wps_device_data *dev, const u8 *str, + size_t str_len) +{ + if (str == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Model Name received"); + return -1; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Name", str, str_len); + + os_free(dev->model_name); + dev->model_name = os_malloc(str_len + 1); + if (dev->model_name == NULL) + return -1; + os_memcpy(dev->model_name, str, str_len); + dev->model_name[str_len] = '\0'; + + return 0; +} + + +static int wps_process_model_number(struct wps_device_data *dev, const u8 *str, + size_t str_len) +{ + if (str == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Model Number received"); + return -1; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Number", str, str_len); + + os_free(dev->model_number); + dev->model_number = os_malloc(str_len + 1); + if (dev->model_number == NULL) + return -1; + os_memcpy(dev->model_number, str, str_len); + dev->model_number[str_len] = '\0'; + + return 0; +} + + +static int wps_process_serial_number(struct wps_device_data *dev, + const u8 *str, size_t str_len) +{ + if (str == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Serial Number received"); + return -1; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Serial Number", str, str_len); + + os_free(dev->serial_number); + dev->serial_number = os_malloc(str_len + 1); + if (dev->serial_number == NULL) + return -1; + os_memcpy(dev->serial_number, str, str_len); + dev->serial_number[str_len] = '\0'; + + return 0; +} + + +static int wps_process_dev_name(struct wps_device_data *dev, const u8 *str, + size_t str_len) +{ + if (str == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Device Name received"); + return -1; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Device Name", str, str_len); + + os_free(dev->device_name); + dev->device_name = os_malloc(str_len + 1); + if (dev->device_name == NULL) + return -1; + os_memcpy(dev->device_name, str, str_len); + dev->device_name[str_len] = '\0'; + + return 0; +} + + +static int wps_process_primary_dev_type(struct wps_device_data *dev, + const u8 *dev_type) +{ + struct wps_dev_type *d; + + if (dev_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Primary Device Type received"); + return -1; + } + + d = (struct wps_dev_type *) dev_type; + dev->categ = WPA_GET_BE16(d->categ_id); + dev->oui = WPA_GET_BE32(d->oui); + dev->sub_categ = WPA_GET_BE16(d->sub_categ_id); + + wpa_printf(MSG_DEBUG, "WPS: Primary Device Type: category %d " + "OUI %08x sub-category %d", + dev->categ, dev->oui, dev->sub_categ); + + return 0; +} + + +int wps_process_device_attrs(struct wps_device_data *dev, + struct wps_parse_attr *attr) +{ + if (wps_process_manufacturer(dev, attr->manufacturer, + attr->manufacturer_len) || + wps_process_model_name(dev, attr->model_name, + attr->model_name_len) || + wps_process_model_number(dev, attr->model_number, + attr->model_number_len) || + wps_process_serial_number(dev, attr->serial_number, + attr->serial_number_len) || + wps_process_primary_dev_type(dev, attr->primary_dev_type) || + wps_process_dev_name(dev, attr->dev_name, attr->dev_name_len)) + return -1; + return 0; +} + + +int wps_process_os_version(struct wps_device_data *dev, const u8 *ver) +{ + if (ver == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No OS Version received"); + return -1; + } + + dev->os_version = WPA_GET_BE32(ver); + wpa_printf(MSG_DEBUG, "WPS: OS Version %08x", dev->os_version); + + return 0; +} + + +void wps_device_data_dup(struct wps_device_data *dst, + const struct wps_device_data *src) +{ + if (src->device_name) + dst->device_name = os_strdup(src->device_name); + if (src->manufacturer) + dst->manufacturer = os_strdup(src->manufacturer); + if (src->model_name) + dst->model_name = os_strdup(src->model_name); + if (src->model_number) + dst->model_number = os_strdup(src->model_number); + if (src->serial_number) + dst->serial_number = os_strdup(src->serial_number); + dst->categ = src->categ; + dst->oui = src->oui; + dst->sub_categ = src->sub_categ; + dst->os_version = src->os_version; +} + + +void wps_device_data_free(struct wps_device_data *dev) +{ + os_free(dev->device_name); + dev->device_name = NULL; + os_free(dev->manufacturer); + dev->manufacturer = NULL; + os_free(dev->model_name); + dev->model_name = NULL; + os_free(dev->model_number); + dev->model_number = NULL; + os_free(dev->serial_number); + dev->serial_number = NULL; +} diff --git a/src/wps/wps_dev_attr.h b/src/wps/wps_dev_attr.h new file mode 100644 index 000000000..2e2155aca --- /dev/null +++ b/src/wps/wps_dev_attr.h @@ -0,0 +1,29 @@ +/* + * Wi-Fi Protected Setup - device attributes + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef WPS_DEV_ATTR_H +#define WPS_DEV_ATTR_H + +struct wps_parse_attr; + +int wps_build_device_attrs(struct wps_device_data *dev, struct wpabuf *msg); +int wps_build_os_version(struct wps_device_data *dev, struct wpabuf *msg); +int wps_process_device_attrs(struct wps_device_data *dev, + struct wps_parse_attr *attr); +int wps_process_os_version(struct wps_device_data *dev, const u8 *ver); +void wps_device_data_dup(struct wps_device_data *dst, + const struct wps_device_data *src); +void wps_device_data_free(struct wps_device_data *dev); + +#endif /* WPS_DEV_ATTR_H */ diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c new file mode 100644 index 000000000..0eea6103a --- /dev/null +++ b/src/wps/wps_enrollee.c @@ -0,0 +1,1454 @@ +/* + * Wi-Fi Protected Setup - Enrollee + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "sha256.h" +#include "ieee802_11_defs.h" +#include "wps_i.h" + + +static int wps_build_req_type(struct wpabuf *msg, enum wps_request_type type) +{ + wpa_printf(MSG_DEBUG, "WPS: * Request Type"); + wpabuf_put_be16(msg, ATTR_REQUEST_TYPE); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, type); + return 0; +} + + +static int wps_build_uuid_e(struct wpabuf *msg, const u8 *uuid) +{ + wpa_printf(MSG_DEBUG, "WPS: * UUID-E"); + wpabuf_put_be16(msg, ATTR_UUID_E); + wpabuf_put_be16(msg, WPS_UUID_LEN); + wpabuf_put_data(msg, uuid, WPS_UUID_LEN); + return 0; +} + + +static int wps_build_mac_addr(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * MAC Address"); + wpabuf_put_be16(msg, ATTR_MAC_ADDR); + wpabuf_put_be16(msg, ETH_ALEN); + wpabuf_put_data(msg, wps->mac_addr_e, ETH_ALEN); + return 0; +} + + +static int wps_build_config_methods(struct wpabuf *msg, u16 methods) +{ + wpa_printf(MSG_DEBUG, "WPS: * Config Methods"); + wpabuf_put_be16(msg, ATTR_CONFIG_METHODS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, methods); + return 0; +} + + +static int wps_build_wps_state(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State"); + wpabuf_put_be16(msg, ATTR_WPS_STATE); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, WPS_STATE_CONFIGURED); + return 0; +} + + +static int wps_build_manufacturer(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Manufacturer"); + wpabuf_put_be16(msg, ATTR_MANUFACTURER); + wpabuf_put_be16(msg, 5); + wpabuf_put_data(msg, "manuf", 5); /* FIX */ + return 0; +} + + +static int wps_build_model_name(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Model Name"); + wpabuf_put_be16(msg, ATTR_MODEL_NAME); + wpabuf_put_be16(msg, 10); + wpabuf_put_data(msg, "model name", 10); /* FIX */ + return 0; +} + + +static int wps_build_model_number(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Model Number"); + wpabuf_put_be16(msg, ATTR_MODEL_NUMBER); + wpabuf_put_be16(msg, 12); + wpabuf_put_data(msg, "model number", 12); /* FIX */ + return 0; +} + + +static int wps_build_serial_number(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Serial Number"); + wpabuf_put_be16(msg, ATTR_SERIAL_NUMBER); + wpabuf_put_be16(msg, 5); + wpabuf_put_data(msg, "12345", 5); /* FIX */ + return 0; +} + + +static int wps_build_primary_dev_type(struct wps_data *wps, struct wpabuf *msg) +{ + struct wps_dev_type *dev; + wpa_printf(MSG_DEBUG, "WPS: * Primary Device Type"); + wpabuf_put_be16(msg, ATTR_PRIMARY_DEV_TYPE); + wpabuf_put_be16(msg, sizeof(*dev)); + dev = wpabuf_put(msg, sizeof(*dev)); + WPA_PUT_BE16(dev->categ_id, WPS_DEV_COMPUTER); + WPA_PUT_BE32(dev->oui, WPS_DEV_OUI_WFA); + WPA_PUT_BE16(dev->sub_categ_id, WPS_DEV_COMPUTER_PC); + return 0; +} + + +static int wps_build_dev_name(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Device Name"); + wpabuf_put_be16(msg, ATTR_DEV_NAME); + wpabuf_put_be16(msg, 8); + wpabuf_put_data(msg, "dev name", 8); /* FIX */ + return 0; +} + + +static int wps_build_rf_bands(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * RF Bands"); + wpabuf_put_be16(msg, ATTR_RF_BANDS); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, WPS_RF_24GHZ | WPS_RF_50GHZ); + return 0; +} + + +static int wps_build_dev_password_id(struct wpabuf *msg, u16 id) +{ + wpa_printf(MSG_DEBUG, "WPS: * Device Password ID"); + wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, id); + return 0; +} + + +static int wps_build_config_error(struct wps_data *wps, struct wpabuf *msg) +{ + u16 err = WPS_CFG_NO_ERROR; + wpabuf_put_be16(msg, ATTR_CONFIG_ERROR); + wpabuf_put_be16(msg, 2); + if (wps && wps->authenticator && wps->wps->ap_setup_locked) + err = WPS_CFG_SETUP_LOCKED; + wpa_printf(MSG_DEBUG, "WPS: * Configuration Error (%d)", err); + wpabuf_put_be16(msg, err); + return 0; +} + + +static int wps_build_os_version(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * OS Version"); + wpabuf_put_be16(msg, ATTR_OS_VERSION); + wpabuf_put_be16(msg, 4); + wpabuf_put_be32(msg, 0x80000000); /* FIX */ + return 0; +} + + +static int wps_build_e_hash(struct wps_data *wps, struct wpabuf *msg) +{ + u8 *hash; + const u8 *addr[4]; + size_t len[4]; + + if (os_get_random(wps->snonce, 2 * WPS_SECRET_NONCE_LEN) < 0) + return -1; + wpa_hexdump(MSG_DEBUG, "WPS: E-S1", wps->snonce, WPS_SECRET_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: E-S2", + wps->snonce + WPS_SECRET_NONCE_LEN, WPS_SECRET_NONCE_LEN); + + if (wps->dh_pubkey_e == NULL || wps->dh_pubkey_r == NULL) { + wpa_printf(MSG_DEBUG, "WPS: DH public keys not available for " + "E-Hash derivation"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: * E-Hash1"); + wpabuf_put_be16(msg, ATTR_E_HASH1); + wpabuf_put_be16(msg, SHA256_MAC_LEN); + hash = wpabuf_put(msg, SHA256_MAC_LEN); + /* E-Hash1 = HMAC_AuthKey(E-S1 || PSK1 || PK_E || PK_R) */ + addr[0] = wps->snonce; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk1; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + wpa_hexdump(MSG_DEBUG, "WPS: E-Hash1", hash, SHA256_MAC_LEN); + + wpa_printf(MSG_DEBUG, "WPS: * E-Hash2"); + wpabuf_put_be16(msg, ATTR_E_HASH2); + wpabuf_put_be16(msg, SHA256_MAC_LEN); + hash = wpabuf_put(msg, SHA256_MAC_LEN); + /* E-Hash2 = HMAC_AuthKey(E-S2 || PSK2 || PK_E || PK_R) */ + addr[0] = wps->snonce + WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk2; + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + wpa_hexdump(MSG_DEBUG, "WPS: E-Hash2", hash, SHA256_MAC_LEN); + + return 0; +} + + +static int wps_build_e_snonce1(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * E-SNonce1"); + wpabuf_put_be16(msg, ATTR_E_SNONCE1); + wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN); + wpabuf_put_data(msg, wps->snonce, WPS_SECRET_NONCE_LEN); + return 0; +} + + +static int wps_build_e_snonce2(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * E-SNonce2"); + wpabuf_put_be16(msg, ATTR_E_SNONCE2); + wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN); + wpabuf_put_data(msg, wps->snonce + WPS_SECRET_NONCE_LEN, + WPS_SECRET_NONCE_LEN); + return 0; +} + + +static struct wpabuf * wps_build_m1(struct wps_data *wps) +{ + struct wpabuf *msg; + u16 methods; + + if (os_get_random(wps->nonce_e, WPS_NONCE_LEN) < 0) + return NULL; + wpa_hexdump(MSG_DEBUG, "WPS: Enrollee Nonce", + wps->nonce_e, WPS_NONCE_LEN); + + wpa_printf(MSG_DEBUG, "WPS: Building Message M1"); + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + methods = WPS_CONFIG_LABEL | WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD; + if (wps->pbc) + methods |= WPS_CONFIG_PUSHBUTTON; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M1) || + wps_build_uuid_e(msg, wps->uuid_e) || + wps_build_mac_addr(wps, msg) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_public_key(wps, msg) || + wps_build_auth_type_flags(wps, msg) || + wps_build_encr_type_flags(wps, msg) || + wps_build_conn_type_flags(wps, msg) || + wps_build_config_methods(msg, methods) || + wps_build_wps_state(wps, msg) || + wps_build_manufacturer(wps, msg) || + wps_build_model_name(wps, msg) || + wps_build_model_number(wps, msg) || + wps_build_serial_number(wps, msg) || + wps_build_primary_dev_type(wps, msg) || + wps_build_dev_name(wps, msg) || + wps_build_rf_bands(wps, msg) || + wps_build_assoc_state(wps, msg) || + wps_build_dev_password_id(msg, wps->dev_pw_id) || + wps_build_config_error(wps, msg) || + wps_build_os_version(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + wps->state = RECV_M2; + return msg; +} + + +static struct wpabuf * wps_build_m3(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M3"); + + if (wps->dev_password == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Device Password available"); + return NULL; + } + wps_derive_psk(wps, wps->dev_password, wps->dev_password_len); + + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M3) || + wps_build_registrar_nonce(wps, msg) || + wps_build_e_hash(wps, msg) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + wps->state = RECV_M4; + return msg; +} + + +static struct wpabuf * wps_build_m5(struct wps_data *wps) +{ + struct wpabuf *msg, *plain; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M5"); + + plain = wpabuf_alloc(200); + if (plain == NULL) + return NULL; + + msg = wpabuf_alloc(1000); + if (msg == NULL) { + wpabuf_free(plain); + return NULL; + } + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M5) || + wps_build_registrar_nonce(wps, msg) || + wps_build_e_snonce1(wps, plain) || + wps_build_key_wrap_auth(wps, plain) || + wps_build_encr_settings(wps, msg, plain) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(plain); + wpabuf_free(msg); + return NULL; + } + wpabuf_free(plain); + + wps->state = RECV_M6; + return msg; +} + + +static int wps_build_cred_ssid(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * SSID"); + wpabuf_put_be16(msg, ATTR_SSID); + wpabuf_put_be16(msg, wps->wps->ssid_len); + wpabuf_put_data(msg, wps->wps->ssid, wps->wps->ssid_len); + return 0; +} + + +static int wps_build_cred_auth_type(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Authentication Type"); + wpabuf_put_be16(msg, ATTR_AUTH_TYPE); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, wps->wps->auth_types); + return 0; +} + + +static int wps_build_cred_encr_type(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Encryption Type"); + wpabuf_put_be16(msg, ATTR_ENCR_TYPE); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, wps->wps->encr_types); + return 0; +} + + +static int wps_build_cred_network_key(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Network Key"); + wpabuf_put_be16(msg, ATTR_NETWORK_KEY); + wpabuf_put_be16(msg, wps->wps->network_key_len); + wpabuf_put_data(msg, wps->wps->network_key, wps->wps->network_key_len); + return 0; +} + + +static int wps_build_cred_mac_addr(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * MAC Address (AP BSSID)"); + wpabuf_put_be16(msg, ATTR_MAC_ADDR); + wpabuf_put_be16(msg, ETH_ALEN); + wpabuf_put_data(msg, wps->wps->dev.mac_addr, ETH_ALEN); + return 0; +} + + +static struct wpabuf * wps_build_m7(struct wps_data *wps) +{ + struct wpabuf *msg, *plain; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M7"); + + plain = wpabuf_alloc(500); + if (plain == NULL) + return NULL; + + msg = wpabuf_alloc(1000); + if (msg == NULL) { + wpabuf_free(plain); + return NULL; + } + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M7) || + wps_build_registrar_nonce(wps, msg) || + wps_build_e_snonce2(wps, plain) || + (wps->authenticator && + (wps_build_cred_ssid(wps, plain) || + wps_build_cred_mac_addr(wps, plain) || + wps_build_cred_auth_type(wps, plain) || + wps_build_cred_encr_type(wps, plain) || + wps_build_cred_network_key(wps, plain))) || + wps_build_key_wrap_auth(wps, plain) || + wps_build_encr_settings(wps, msg, plain) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(plain); + wpabuf_free(msg); + return NULL; + } + wpabuf_free(plain); + + wps->state = RECV_M8; + return msg; +} + + +static struct wpabuf * wps_build_wsc_done(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_Done"); + + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_WSC_DONE) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + wps->state = wps->authenticator ? RECV_ACK : WPS_FINISHED; + return msg; +} + + +static struct wpabuf * wps_build_wsc_ack(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_ACK"); + + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_WSC_ACK) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + return msg; +} + + +static struct wpabuf * wps_build_wsc_nack(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_NACK"); + + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_WSC_NACK) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg) || + wps_build_config_error(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + return msg; +} + + +struct wpabuf * wps_enrollee_get_msg(struct wps_data *wps, u8 *op_code) +{ + struct wpabuf *msg; + + switch (wps->state) { + case SEND_M1: + msg = wps_build_m1(wps); + *op_code = WSC_MSG; + break; + case SEND_M3: + msg = wps_build_m3(wps); + *op_code = WSC_MSG; + break; + case SEND_M5: + msg = wps_build_m5(wps); + *op_code = WSC_MSG; + break; + case SEND_M7: + msg = wps_build_m7(wps); + *op_code = WSC_MSG; + break; + case RECEIVED_M2D: + msg = wps_build_wsc_ack(wps); + *op_code = WSC_ACK; + if (msg) { + /* Another M2/M2D may be received */ + wps->state = RECV_M2; + } + break; + case SEND_WSC_NACK: + msg = wps_build_wsc_nack(wps); + *op_code = WSC_NACK; + break; + case WPS_MSG_DONE: + msg = wps_build_wsc_done(wps); + *op_code = WSC_Done; + break; + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported state %d for building " + "a message", wps->state); + msg = NULL; + break; + } + + if (*op_code == WSC_MSG && msg) { + /* Save a copy of the last message for Authenticator derivation + */ + wpabuf_free(wps->last_msg); + wps->last_msg = wpabuf_dup(msg); + } + + return msg; +} + + +static int wps_process_registrar_nonce(struct wps_data *wps, const u8 *r_nonce) +{ + if (r_nonce == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Registrar Nonce received"); + return -1; + } + + os_memcpy(wps->nonce_r, r_nonce, WPS_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Registrar Nonce", + wps->nonce_r, WPS_NONCE_LEN); + + return 0; +} + + +static int wps_process_enrollee_nonce(struct wps_data *wps, const u8 *e_nonce) +{ + if (e_nonce == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Enrollee Nonce received"); + return -1; + } + + if (os_memcmp(wps->nonce_e, e_nonce, WPS_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Enrollee Nonce received"); + return -1; + } + + return 0; +} + + +static int wps_process_uuid_r(struct wps_data *wps, const u8 *uuid_r) +{ + if (uuid_r == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No UUID-R received"); + return -1; + } + + os_memcpy(wps->uuid_r, uuid_r, WPS_UUID_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: UUID-R", wps->uuid_r, WPS_UUID_LEN); + + return 0; +} + + +static int wps_process_pubkey(struct wps_data *wps, const u8 *pk, + size_t pk_len) +{ + if (pk == NULL || pk_len == 0) { + wpa_printf(MSG_DEBUG, "WPS: No Public Key received"); + return -1; + } + + wpabuf_free(wps->dh_pubkey_r); + wps->dh_pubkey_r = wpabuf_alloc_copy(pk, pk_len); + if (wps->dh_pubkey_r == NULL) + return -1; + + return wps_derive_keys(wps); +} + + +static int wps_process_r_hash1(struct wps_data *wps, const u8 *r_hash1) +{ + if (r_hash1 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No R-Hash1 received"); + return -1; + } + + os_memcpy(wps->peer_hash1, r_hash1, WPS_HASH_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: R-Hash1", wps->peer_hash1, WPS_HASH_LEN); + + return 0; +} + + +static int wps_process_r_hash2(struct wps_data *wps, const u8 *r_hash2) +{ + if (r_hash2 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No R-Hash2 received"); + return -1; + } + + os_memcpy(wps->peer_hash2, r_hash2, WPS_HASH_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: R-Hash2", wps->peer_hash2, WPS_HASH_LEN); + + return 0; +} + + +static int wps_process_r_snonce1(struct wps_data *wps, const u8 *r_snonce1) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[4]; + size_t len[4]; + + if (r_snonce1 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No R-SNonce1 received"); + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "WPS: R-SNonce1", r_snonce1, + WPS_SECRET_NONCE_LEN); + + /* R-Hash1 = HMAC_AuthKey(R-S1 || PSK1 || PK_E || PK_R) */ + addr[0] = r_snonce1; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk1; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + + if (os_memcmp(wps->peer_hash1, hash, WPS_HASH_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: R-Hash1 derived from R-S1 does " + "not match with the pre-committed value"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Registrar proved knowledge of the first " + "half of the device password"); + + return 0; +} + + +static int wps_process_r_snonce2(struct wps_data *wps, const u8 *r_snonce2) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[4]; + size_t len[4]; + + if (r_snonce2 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No R-SNonce2 received"); + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "WPS: R-SNonce2", r_snonce2, + WPS_SECRET_NONCE_LEN); + + /* R-Hash2 = HMAC_AuthKey(R-S2 || PSK2 || PK_E || PK_R) */ + addr[0] = r_snonce2; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk2; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + + if (os_memcmp(wps->peer_hash2, hash, WPS_HASH_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: R-Hash2 derived from R-S2 does " + "not match with the pre-committed value"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Registrar proved knowledge of the second " + "half of the device password"); + + return 0; +} + + +static int wps_process_cred_network_idx(struct wps_credential *cred, + const u8 *idx) +{ + if (idx == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include " + "Network Index"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Network Index: %d", *idx); + + return 0; +} + + +static int wps_process_cred_ssid(struct wps_credential *cred, const u8 *ssid, + size_t ssid_len) +{ + if (ssid == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include SSID"); + return -1; + } + + /* Remove zero-padding since some Registrar implementations seem to use + * hardcoded 32-octet length for this attribute */ + while (ssid_len > 0 && ssid[ssid_len - 1] == 0) + ssid_len--; + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: SSID", ssid, ssid_len); + if (ssid_len <= sizeof(cred->ssid)) { + os_memcpy(cred->ssid, ssid, ssid_len); + cred->ssid_len = ssid_len; + } + + return 0; +} + + +static int wps_process_cred_auth_type(struct wps_credential *cred, + const u8 *auth_type) +{ + if (auth_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include " + "Authentication Type"); + return -1; + } + + cred->auth_type = WPA_GET_BE16(auth_type); + wpa_printf(MSG_DEBUG, "WPS: Authentication Type: 0x%x", + cred->auth_type); + + return 0; +} + + +static int wps_process_cred_encr_type(struct wps_credential *cred, + const u8 *encr_type) +{ + if (encr_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include " + "Encryption Type"); + return -1; + } + + cred->encr_type = WPA_GET_BE16(encr_type); + wpa_printf(MSG_DEBUG, "WPS: Encryption Type: 0x%x", + cred->encr_type); + + return 0; +} + + +static int wps_process_cred_network_key_idx(struct wps_credential *cred, + const u8 *key_idx) +{ + if (key_idx == NULL) + return 0; /* optional attribute */ + + wpa_printf(MSG_DEBUG, "WPS: Network Key Index: %d", *key_idx); + cred->key_idx = *key_idx; + + return 0; +} + + +static int wps_process_cred_network_key(struct wps_credential *cred, + const u8 *key, size_t key_len) +{ + if (key == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include " + "Network Key"); + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "WPS: Network Key", key, key_len); + if (key_len <= sizeof(cred->key)) { + os_memcpy(cred->key, key, key_len); + cred->key_len = key_len; + } + + return 0; +} + + +static int wps_process_cred_mac_addr(struct wps_credential *cred, + const u8 *mac_addr) +{ + if (mac_addr == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Credential did not include " + "MAC Address"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: MAC Address " MACSTR, MAC2STR(mac_addr)); + os_memcpy(cred->mac_addr, mac_addr, ETH_ALEN); + + return 0; +} + + +static int wps_process_cred_eap_type(struct wps_credential *cred, + const u8 *eap_type, size_t eap_type_len) +{ + if (eap_type == NULL) + return 0; /* optional attribute */ + + wpa_hexdump(MSG_DEBUG, "WPS: EAP Type", eap_type, eap_type_len); + + return 0; +} + + +static int wps_process_cred_eap_identity(struct wps_credential *cred, + const u8 *identity, + size_t identity_len) +{ + if (identity == NULL) + return 0; /* optional attribute */ + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: EAP Identity", + identity, identity_len); + + return 0; +} + + +static int wps_process_cred_key_prov_auto(struct wps_credential *cred, + const u8 *key_prov_auto) +{ + if (key_prov_auto == NULL) + return 0; /* optional attribute */ + + wpa_printf(MSG_DEBUG, "WPS: Key Provided Automatically: %d", + *key_prov_auto); + + return 0; +} + + +static int wps_process_cred_802_1x_enabled(struct wps_credential *cred, + const u8 *dot1x_enabled) +{ + if (dot1x_enabled == NULL) + return 0; /* optional attribute */ + + wpa_printf(MSG_DEBUG, "WPS: 802.1X Enabled: %d", *dot1x_enabled); + + return 0; +} + + +static int wps_process_cred(struct wps_data *wps, const u8 *cred, + size_t cred_len) +{ + struct wps_parse_attr attr; + struct wpabuf msg; + + wpa_printf(MSG_DEBUG, "WPS: Received Credential"); + os_memset(&wps->cred, 0, sizeof(wps->cred)); + wpabuf_set(&msg, cred, cred_len); + /* TODO: support multiple Network Keys */ + if (wps_parse_msg(&msg, &attr) < 0 || + wps_process_cred_network_idx(&wps->cred, attr.network_idx) || + wps_process_cred_ssid(&wps->cred, attr.ssid, attr.ssid_len) || + wps_process_cred_auth_type(&wps->cred, attr.auth_type) || + wps_process_cred_encr_type(&wps->cred, attr.encr_type) || + wps_process_cred_network_key_idx(&wps->cred, attr.network_key_idx) + || + wps_process_cred_network_key(&wps->cred, attr.network_key, + attr.network_key_len) || + wps_process_cred_mac_addr(&wps->cred, attr.mac_addr) || + wps_process_cred_eap_type(&wps->cred, attr.eap_type, + attr.eap_type_len) || + wps_process_cred_eap_identity(&wps->cred, attr.eap_identity, + attr.eap_identity_len) || + wps_process_cred_key_prov_auto(&wps->cred, attr.key_prov_auto) || + wps_process_cred_802_1x_enabled(&wps->cred, attr.dot1x_enabled)) + return -1; + + if (wps->wps_cred_cb) + wps->wps_cred_cb(wps->cb_ctx, &wps->cred); + + return 0; +} + + +static int wps_process_creds(struct wps_data *wps, const u8 *cred[], + size_t cred_len[], size_t num_cred) +{ + size_t i; + + if (wps->authenticator) + return 0; + + if (num_cred == 0) { + wpa_printf(MSG_DEBUG, "WPS: No Credential attributes " + "received"); + return -1; + } + + for (i = 0; i < num_cred; i++) { + if (wps_process_cred(wps, cred[i], cred_len[i])) + return -1; + } + + return 0; +} + + +static int wps_process_ap_settings(struct wps_data *wps, + struct wps_parse_attr *attr) +{ + struct wps_credential cred; + + if (!wps->authenticator) + return 0; + + wpa_printf(MSG_DEBUG, "WPS: Processing AP Settings"); + os_memset(&cred, 0, sizeof(cred)); + /* TODO: optional attributes New Password and Device Password ID */ + if (wps_process_cred_ssid(&cred, attr->ssid, attr->ssid_len) || + wps_process_cred_auth_type(&cred, attr->auth_type) || + wps_process_cred_encr_type(&cred, attr->encr_type) || + wps_process_cred_network_key_idx(&cred, attr->network_key_idx) || + wps_process_cred_network_key(&cred, attr->network_key, + attr->network_key_len) || + wps_process_cred_mac_addr(&cred, attr->mac_addr)) + return -1; + + wpa_printf(MSG_INFO, "WPS: Received new AP configuration from " + "Registrar"); + + if (wps->wps->cred_cb) + wps->wps->cred_cb(wps->wps->cb_ctx, &cred); + + return 0; +} + + +static enum wps_process_res wps_process_m2(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + wpa_printf(MSG_DEBUG, "WPS: Received M2"); + + if (wps->state != RECV_M2) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M2", wps->state); + return WPS_FAILURE; + } + + if (wps_process_registrar_nonce(wps, attr->registrar_nonce) || + wps_process_enrollee_nonce(wps, attr->enrollee_nonce) || + wps_process_uuid_r(wps, attr->uuid_r) || + wps_process_pubkey(wps, attr->public_key, attr->public_key_len) || + wps_process_authenticator(wps, attr->authenticator, msg)) + return WPS_FAILURE; + + if (wps->authenticator && wps->wps->ap_setup_locked) { + wpa_printf(MSG_DEBUG, "WPS: AP Setup is locked - refuse " + "registration of a new Registrar"); + wps->state = SEND_WSC_NACK; + return WPS_CONTINUE; + } + + wps->state = SEND_M3; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m2d(struct wps_data *wps, + struct wps_parse_attr *attr) +{ + wpa_printf(MSG_DEBUG, "WPS: Received M2D"); + + if (wps->state != RECV_M2) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M2D", wps->state); + return WPS_FAILURE; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Manufacturer", + attr->manufacturer, attr->manufacturer_len); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Name", + attr->model_name, attr->model_name_len); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Number", + attr->model_number, attr->model_number_len); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Serial Number", + attr->serial_number, attr->serial_number_len); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: Device Name", + attr->dev_name, attr->dev_name_len); + + /* + * TODO: notify monitor programs (cli/gui/etc.) of the M2D and provide + * user information about the registrar properties. + */ + + wps->state = RECEIVED_M2D; + return WPS_FAILURE; +} + + +static enum wps_process_res wps_process_m4(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + struct wpabuf *decrypted; + struct wps_parse_attr eattr; + + wpa_printf(MSG_DEBUG, "WPS: Received M4"); + + if (wps->state != RECV_M4) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M4", wps->state); + return WPS_FAILURE; + } + + if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg) || + wps_process_r_hash1(wps, attr->r_hash1) || + wps_process_r_hash2(wps, attr->r_hash2)) + return WPS_FAILURE; + + decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings, + attr->encr_settings_len); + if (decrypted == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted " + "Settings attribute"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings " + "attribute"); + if (wps_parse_msg(decrypted, &eattr) < 0 || + wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) || + wps_process_r_snonce1(wps, eattr.r_snonce1)) { + wpabuf_free(decrypted); + return WPS_FAILURE; + } + wpabuf_free(decrypted); + + wps->state = SEND_M5; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m6(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + struct wpabuf *decrypted; + struct wps_parse_attr eattr; + + wpa_printf(MSG_DEBUG, "WPS: Received M6"); + + if (wps->state != RECV_M6) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M6", wps->state); + return WPS_FAILURE; + } + + if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg)) + return WPS_FAILURE; + + decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings, + attr->encr_settings_len); + if (decrypted == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted " + "Settings attribute"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings " + "attribute"); + if (wps_parse_msg(decrypted, &eattr) < 0 || + wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) || + wps_process_r_snonce2(wps, eattr.r_snonce2)) { + wpabuf_free(decrypted); + return WPS_FAILURE; + } + wpabuf_free(decrypted); + + wps->state = SEND_M7; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m8(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + struct wpabuf *decrypted; + struct wps_parse_attr eattr; + + wpa_printf(MSG_DEBUG, "WPS: Received M8"); + + if (wps->state != RECV_M8) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M8", wps->state); + return WPS_FAILURE; + } + + if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg)) + return WPS_FAILURE; + + decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings, + attr->encr_settings_len); + if (decrypted == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted " + "Settings attribute"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings " + "attribute"); + if (wps_parse_msg(decrypted, &eattr) < 0 || + wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) || + wps_process_creds(wps, eattr.cred, eattr.cred_len, + eattr.num_cred) || + wps_process_ap_settings(wps, &eattr)) { + wpabuf_free(decrypted); + return WPS_FAILURE; + } + wpabuf_free(decrypted); + + wps->state = WPS_MSG_DONE; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_wsc_msg(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + enum wps_process_res ret = WPS_CONTINUE; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_MSG"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + switch (*attr.msg_type) { + case WPS_M2: + ret = wps_process_m2(wps, msg, &attr); + break; + case WPS_M2D: + ret = wps_process_m2d(wps, &attr); + break; + case WPS_M4: + ret = wps_process_m4(wps, msg, &attr); + break; + case WPS_M6: + ret = wps_process_m6(wps, msg, &attr); + break; + case WPS_M8: + ret = wps_process_m8(wps, msg, &attr); + break; + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (ret == WPS_CONTINUE) { + /* Save a copy of the last message for Authenticator derivation + */ + wpabuf_free(wps->last_msg); + wps->last_msg = wpabuf_dup(msg); + } + + return ret; +} + + +static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_ACK"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_WSC_ACK) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0)) + { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + return WPS_FAILURE; + } + + if (wps->state == RECV_ACK && wps->authenticator) { + wpa_printf(MSG_DEBUG, "WPS: External Registrar registration " + "completed successfully"); + wps->state = WPS_FINISHED; + return WPS_DONE; + } + + return WPS_FAILURE; +} + + +static enum wps_process_res wps_process_wsc_nack(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_NACK"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_WSC_NACK) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0)) + { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + wpa_hexdump(MSG_DEBUG, "WPS: Received Registrar Nonce", + attr.registrar_nonce, WPS_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Expected Registrar Nonce", + wps->nonce_r, WPS_NONCE_LEN); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + wpa_hexdump(MSG_DEBUG, "WPS: Received Enrollee Nonce", + attr.enrollee_nonce, WPS_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Expected Enrollee Nonce", + wps->nonce_e, WPS_NONCE_LEN); + return WPS_FAILURE; + } + + if (attr.config_error == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Configuration Error attribute " + "in WSC_NACK"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee terminated negotiation with " + "Configuration Error %d", WPA_GET_BE16(attr.config_error)); + + return WPS_FAILURE; +} + + +enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps, u8 op_code, + const struct wpabuf *msg) +{ + + wpa_printf(MSG_DEBUG, "WPS: Processing received message (len=%lu " + "op_code=%d)", + (unsigned long) wpabuf_len(msg), op_code); + + switch (op_code) { + case WSC_MSG: + return wps_process_wsc_msg(wps, msg); + case WSC_ACK: + return wps_process_wsc_ack(wps, msg); + case WSC_NACK: + return wps_process_wsc_nack(wps, msg); + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported op_code %d", op_code); + return WPS_FAILURE; + } +} + + +struct wpabuf * wps_enrollee_build_assoc_req_ie(void) +{ + struct wpabuf *ie; + u8 *len; + + wpa_printf(MSG_DEBUG, "WPS: Building WPS IE for (Re)Association " + "Request"); + ie = wpabuf_alloc(100); + if (ie == NULL) + return NULL; + + wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC); + len = wpabuf_put(ie, 1); + wpabuf_put_be32(ie, WPS_DEV_OUI_WFA); + + if (wps_build_version(ie) || + wps_build_req_type(ie, WPS_REQ_ENROLLEE)) { + wpabuf_free(ie); + return NULL; + } + + *len = wpabuf_len(ie) - 2; + + return ie; +} + + +struct wpabuf * wps_enrollee_build_probe_req_ie(int pbc, const u8 *uuid) +{ + struct wpabuf *ie; + u8 *len; + u16 methods; + + wpa_printf(MSG_DEBUG, "WPS: Building WPS IE for Probe Request"); + ie = wpabuf_alloc(200); + if (ie == NULL) + return NULL; + + wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC); + len = wpabuf_put(ie, 1); + wpabuf_put_be32(ie, WPS_DEV_OUI_WFA); + + if (pbc) + methods = WPS_CONFIG_PUSHBUTTON; + else + methods = WPS_CONFIG_LABEL | WPS_CONFIG_DISPLAY | + WPS_CONFIG_KEYPAD; + + if (wps_build_version(ie) || + wps_build_req_type(ie, WPS_REQ_ENROLLEE) || + wps_build_config_methods(ie, methods) || + wps_build_uuid_e(ie, uuid) || + wps_build_primary_dev_type(NULL, ie) || + wps_build_rf_bands(NULL, ie) || + wps_build_assoc_state(NULL, ie) || + wps_build_config_error(NULL, ie) || + wps_build_dev_password_id(ie, pbc ? DEV_PW_PUSHBUTTON : + DEV_PW_DEFAULT)) { + wpabuf_free(ie); + return NULL; + } + + *len = wpabuf_len(ie) - 2; + + return ie; +} diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h new file mode 100644 index 000000000..462535fc4 --- /dev/null +++ b/src/wps/wps_i.h @@ -0,0 +1,184 @@ +/* + * Wi-Fi Protected Setup - internal definitions + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef WPS_I_H +#define WPS_I_H + +#include "wps.h" +#include "wps_defs.h" + +struct wps_data { + int authenticator; + struct wps_context *wps; + struct wps_registrar *registrar; + enum { + /* Enrollee states */ + SEND_M1, RECV_M2, SEND_M3, RECV_M4, SEND_M5, RECV_M6, SEND_M7, + RECV_M8, RECEIVED_M2D, WPS_MSG_DONE, RECV_ACK, WPS_FINISHED, + SEND_WSC_NACK, + + /* Registrar states */ + RECV_M1, SEND_M2, RECV_M3, SEND_M4, RECV_M5, SEND_M6, + RECV_M7, SEND_M8, RECV_DONE, SEND_M2D, RECV_M2D_ACK + } state; + + u8 uuid_e[WPS_UUID_LEN]; + u8 uuid_r[WPS_UUID_LEN]; + u8 mac_addr_e[ETH_ALEN]; + u8 nonce_e[WPS_NONCE_LEN]; + u8 nonce_r[WPS_NONCE_LEN]; + u8 psk1[WPS_PSK_LEN]; + u8 psk2[WPS_PSK_LEN]; + u8 snonce[2 * WPS_SECRET_NONCE_LEN]; + u8 peer_hash1[WPS_HASH_LEN]; + u8 peer_hash2[WPS_HASH_LEN]; + + struct wpabuf *dh_privkey; + struct wpabuf *dh_pubkey_e; + struct wpabuf *dh_pubkey_r; + u8 authkey[WPS_AUTHKEY_LEN]; + u8 keywrapkey[WPS_KEYWRAPKEY_LEN]; + u8 emsk[WPS_EMSK_LEN]; + + struct wpabuf *last_msg; + + u8 *dev_password; + size_t dev_password_len; + u16 dev_pw_id; + int pbc; + + u16 encr_type; /* available encryption types */ + u16 auth_type; /* available authentication types */ + + u8 *new_psk; + size_t new_psk_len; + + int wps_pin_revealed; + struct wps_credential cred; + + int (*wps_cred_cb)(void *ctx, struct wps_credential *cred); + void *cb_ctx; + + struct wps_device_data peer_dev; +}; + + +struct wps_parse_attr { + /* fixed length fields */ + const u8 *version; /* 1 octet */ + const u8 *msg_type; /* 1 octet */ + const u8 *enrollee_nonce; /* WPS_NONCE_LEN (16) octets */ + const u8 *registrar_nonce; /* WPS_NONCE_LEN (16) octets */ + const u8 *uuid_r; /* WPS_UUID_LEN (16) octets */ + const u8 *uuid_e; /* WPS_UUID_LEN (16) octets */ + const u8 *auth_type_flags; /* 2 octets */ + const u8 *encr_type_flags; /* 2 octets */ + const u8 *conn_type_flags; /* 1 octet */ + const u8 *config_methods; /* 2 octets */ + const u8 *sel_reg_config_methods; /* 2 octets */ + const u8 *primary_dev_type; /* 8 octets */ + const u8 *rf_bands; /* 1 octet */ + const u8 *assoc_state; /* 2 octets */ + const u8 *config_error; /* 2 octets */ + const u8 *dev_password_id; /* 2 octets */ + const u8 *os_version; /* 4 octets */ + const u8 *wps_state; /* 1 octet */ + const u8 *authenticator; /* WPS_AUTHENTICATOR_LEN (8) octets */ + const u8 *r_hash1; /* WPS_HASH_LEN (32) octets */ + const u8 *r_hash2; /* WPS_HASH_LEN (32) octets */ + const u8 *e_hash1; /* WPS_HASH_LEN (32) octets */ + const u8 *e_hash2; /* WPS_HASH_LEN (32) octets */ + const u8 *r_snonce1; /* WPS_SECRET_NONCE_LEN (16) octets */ + const u8 *r_snonce2; /* WPS_SECRET_NONCE_LEN (16) octets */ + const u8 *e_snonce1; /* WPS_SECRET_NONCE_LEN (16) octets */ + const u8 *e_snonce2; /* WPS_SECRET_NONCE_LEN (16) octets */ + const u8 *key_wrap_auth; /* WPS_KWA_LEN (8) octets */ + const u8 *auth_type; /* 2 octets */ + const u8 *encr_type; /* 2 octets */ + const u8 *network_idx; /* 1 octet */ + const u8 *network_key_idx; /* 1 octet */ + const u8 *mac_addr; /* ETH_ALEN (6) octets */ + const u8 *key_prov_auto; /* 1 octet (Bool) */ + const u8 *dot1x_enabled; /* 1 octet (Bool) */ + const u8 *selected_registrar; /* 1 octet (Bool) */ + + /* variable length fields */ + const u8 *manufacturer; + size_t manufacturer_len; + const u8 *model_name; + size_t model_name_len; + const u8 *model_number; + size_t model_number_len; + const u8 *serial_number; + size_t serial_number_len; + const u8 *dev_name; + size_t dev_name_len; + const u8 *public_key; + size_t public_key_len; + const u8 *encr_settings; + size_t encr_settings_len; + const u8 *ssid; /* <= 32 octets */ + size_t ssid_len; + const u8 *network_key; /* <= 64 octets */ + size_t network_key_len; + const u8 *eap_type; /* <= 8 octets */ + size_t eap_type_len; + const u8 *eap_identity; /* <= 64 octets */ + size_t eap_identity_len; + + /* attributes that can occur multiple times */ +#define MAX_CRED_COUNT 10 + const u8 *cred[MAX_CRED_COUNT]; + size_t cred_len[MAX_CRED_COUNT]; + size_t num_cred; +}; + +/* wps_common.c */ +int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr); +void wps_kdf(const u8 *key, const char *label, u8 *res, size_t res_len); +int wps_build_public_key(struct wps_data *wps, struct wpabuf *msg); +int wps_derive_keys(struct wps_data *wps); +int wps_build_authenticator(struct wps_data *wps, struct wpabuf *msg); +int wps_process_authenticator(struct wps_data *wps, const u8 *authenticator, + const struct wpabuf *msg); +void wps_derive_psk(struct wps_data *wps, const u8 *dev_passwd, + size_t dev_passwd_len); +struct wpabuf * wps_decrypt_encr_settings(struct wps_data *wps, const u8 *encr, + size_t encr_len); +int wps_process_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg, + const u8 *key_wrap_auth); +int wps_build_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg); +int wps_build_encr_settings(struct wps_data *wps, struct wpabuf *msg, + struct wpabuf *plain); +int wps_build_version(struct wpabuf *msg); +int wps_build_msg_type(struct wpabuf *msg, enum wps_msg_type msg_type); +int wps_build_enrollee_nonce(struct wps_data *wps, struct wpabuf *msg); +int wps_build_registrar_nonce(struct wps_data *wps, struct wpabuf *msg); +int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg); +int wps_build_encr_type_flags(struct wps_data *wps, struct wpabuf *msg); +int wps_build_conn_type_flags(struct wps_data *wps, struct wpabuf *msg); +int wps_build_assoc_state(struct wps_data *wps, struct wpabuf *msg); + +/* wps_enrollee.c */ +struct wpabuf * wps_enrollee_get_msg(struct wps_data *wps, u8 *op_code); +enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps, u8 op_code, + const struct wpabuf *msg); + +/* wps_registrar.c */ +struct wpabuf * wps_registrar_get_msg(struct wps_data *wps, u8 *op_code); +enum wps_process_res wps_registrar_process_msg(struct wps_data *wps, + u8 op_code, + const struct wpabuf *msg); + +#endif /* WPS_I_H */ diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c new file mode 100644 index 000000000..7982b9880 --- /dev/null +++ b/src/wps/wps_registrar.c @@ -0,0 +1,1982 @@ +/* + * Wi-Fi Protected Setup - Registrar + * Copyright (c) 2008, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "sha256.h" +#include "base64.h" +#include "ieee802_11_defs.h" +#include "eloop.h" +#include "wps_i.h" +#include "wps_dev_attr.h" + + +struct wps_uuid_pin { + struct wps_uuid_pin *next; + u8 uuid[WPS_UUID_LEN]; + u8 *pin; + size_t pin_len; + int locked; +}; + + +static void wps_free_pin(struct wps_uuid_pin *pin) +{ + os_free(pin->pin); + os_free(pin); +} + + +static void wps_free_pins(struct wps_uuid_pin *pins) +{ + struct wps_uuid_pin *pin, *prev; + + pin = pins; + while (pin) { + prev = pin; + pin = pin->next; + wps_free_pin(prev); + } +} + + +struct wps_pbc_session { + struct wps_pbc_session *next; + u8 addr[ETH_ALEN]; + u8 uuid_e[WPS_UUID_LEN]; + struct os_time timestamp; +}; + + +static void wps_free_pbc_sessions(struct wps_pbc_session *pbc) +{ + struct wps_pbc_session *prev; + + while (pbc) { + prev = pbc; + pbc = pbc->next; + os_free(prev); + } +} + + +struct wps_registrar { + struct wps_context *wps; + + int pbc; + int selected_registrar; + + int (*new_psk_cb)(void *ctx, const u8 *mac_addr, const u8 *psk, + size_t psk_len); + int (*set_ie_cb)(void *ctx, const u8 *beacon_ie, size_t beacon_ie_len, + const u8 *probe_resp_ie, size_t probe_resp_ie_len); + void (*pin_needed_cb)(void *ctx, const u8 *uuid_e, + const struct wps_device_data *dev); + void *cb_ctx; + + struct wps_uuid_pin *pins; + struct wps_pbc_session *pbc_sessions; +}; + + +static int wps_set_ie(struct wps_registrar *reg); +static void wps_registrar_pbc_timeout(void *eloop_ctx, void *timeout_ctx); + + +static void wps_registrar_add_pbc_session(struct wps_registrar *reg, + const u8 *addr, const u8 *uuid_e) +{ + struct wps_pbc_session *pbc, *prev = NULL; + struct os_time now; + + os_get_time(&now); + + pbc = reg->pbc_sessions; + while (pbc) { + if (os_memcmp(pbc->addr, addr, ETH_ALEN) == 0 && + os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0) { + if (prev) + prev->next = pbc->next; + else + reg->pbc_sessions = pbc->next; + break; + } + prev = pbc; + pbc = pbc->next; + } + + if (!pbc) { + pbc = os_zalloc(sizeof(*pbc)); + if (pbc == NULL) + return; + os_memcpy(pbc->addr, addr, ETH_ALEN); + if (uuid_e) + os_memcpy(pbc->uuid_e, uuid_e, WPS_UUID_LEN); + } + + pbc->next = reg->pbc_sessions; + reg->pbc_sessions = pbc; + pbc->timestamp = now; + + /* remove entries that have timed out */ + prev = pbc; + pbc = pbc->next; + + while (pbc) { + if (now.sec > pbc->timestamp.sec + WPS_PBC_WALK_TIME) { + prev->next = NULL; + wps_free_pbc_sessions(pbc); + break; + } + prev = pbc; + pbc = pbc->next; + } +} + + +static void wps_registrar_remove_pbc_session(struct wps_registrar *reg, + const u8 *addr, const u8 *uuid_e) +{ + struct wps_pbc_session *pbc, *prev = NULL; + + pbc = reg->pbc_sessions; + while (pbc) { + if (os_memcmp(pbc->addr, addr, ETH_ALEN) == 0 && + os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0) { + if (prev) + prev->next = pbc->next; + else + reg->pbc_sessions = pbc->next; + os_free(pbc); + break; + } + prev = pbc; + pbc = pbc->next; + } +} + + +int wps_registrar_pbc_overlap(struct wps_registrar *reg, + const u8 *addr, const u8 *uuid_e) +{ + int count = 0; + struct wps_pbc_session *pbc; + struct os_time now; + + os_get_time(&now); + + for (pbc = reg->pbc_sessions; pbc; pbc = pbc->next) { + if (now.sec > pbc->timestamp.sec + WPS_PBC_WALK_TIME) + break; + if (addr == NULL || os_memcmp(addr, pbc->addr, ETH_ALEN) || + uuid_e == NULL || + os_memcmp(uuid_e, pbc->uuid_e, WPS_UUID_LEN)) + count++; + } + + if (addr || uuid_e) + count++; + + return count > 1 ? 1 : 0; +} + + +static int wps_build_wps_state(struct wps_context *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State (%d)", + wps->wps_state); + wpabuf_put_be16(msg, ATTR_WPS_STATE); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, wps->wps_state); + return 0; +} + + +static int wps_build_ap_setup_locked(struct wps_context *wps, + struct wpabuf *msg) +{ + if (wps->ap_setup_locked) { + wpa_printf(MSG_DEBUG, "WPS: * AP Setup Locked"); + wpabuf_put_be16(msg, ATTR_AP_SETUP_LOCKED); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, 1); + } + return 0; +} + + +static int wps_build_selected_registrar(struct wps_registrar *reg, + struct wpabuf *msg) +{ + if (!reg->selected_registrar) + return 0; + wpa_printf(MSG_DEBUG, "WPS: * Selected Registrar"); + wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, 1); + return 0; +} + + +static int wps_build_sel_reg_dev_password_id(struct wps_registrar *reg, + struct wpabuf *msg) +{ + u16 id = reg->pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT; + if (!reg->selected_registrar) + return 0; + wpa_printf(MSG_DEBUG, "WPS: * Device Password ID (%d)", id); + wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, id); + return 0; +} + + +static int wps_build_sel_reg_config_methods(struct wps_registrar *reg, + struct wpabuf *msg) +{ + u16 methods; + if (!reg->selected_registrar) + return 0; + methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON; + if (reg->pbc) + methods |= WPS_CONFIG_PUSHBUTTON; + wpa_printf(MSG_DEBUG, "WPS: * Selected Registrar Config Methods (%x)", + methods); + wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR_CONFIG_METHODS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, methods); + return 0; +} + + +static int wps_build_probe_config_methods(struct wps_registrar *reg, + struct wpabuf *msg) +{ + u16 methods; + methods = 0; + wpa_printf(MSG_DEBUG, "WPS: * Config Methods (%x)", methods); + wpabuf_put_be16(msg, ATTR_CONFIG_METHODS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, methods); + return 0; +} + + +static int wps_build_config_methods(struct wps_registrar *reg, + struct wpabuf *msg) +{ + u16 methods; + methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON; + if (reg->pbc) + methods |= WPS_CONFIG_PUSHBUTTON; + wpa_printf(MSG_DEBUG, "WPS: * Config Methods (%x)", methods); + wpabuf_put_be16(msg, ATTR_CONFIG_METHODS); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, methods); + return 0; +} + + +static int wps_build_rf_bands(struct wps_registrar *reg, struct wpabuf *msg) +{ + u8 bands = WPS_RF_24GHZ /* TODO: | WPS_RF_50GHZ */; + wpa_printf(MSG_DEBUG, "WPS: * RF Bands (%x)", bands); + wpabuf_put_be16(msg, ATTR_RF_BANDS); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, bands); + return 0; +} + + +static int wps_build_uuid_e(struct wps_registrar *reg, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * UUID-E"); + wpabuf_put_be16(msg, ATTR_UUID_E); + wpabuf_put_be16(msg, WPS_UUID_LEN); + wpabuf_put_data(msg, reg->wps->uuid, WPS_UUID_LEN); + return 0; +} + + +static int wps_build_resp_type(struct wps_registrar *reg, struct wpabuf *msg) +{ + u8 resp = reg->wps->ap ? WPS_RESP_AP : WPS_RESP_REGISTRAR; + wpa_printf(MSG_DEBUG, "WPS: * Response Type (%d)", resp); + wpabuf_put_be16(msg, ATTR_RESPONSE_TYPE); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, resp); + return 0; +} + + +struct wps_registrar * +wps_registrar_init(struct wps_context *wps, + const struct wps_registrar_config *cfg) +{ + struct wps_registrar *reg = os_zalloc(sizeof(*reg)); + if (reg == NULL) + return NULL; + + reg->wps = wps; + reg->new_psk_cb = cfg->new_psk_cb; + reg->set_ie_cb = cfg->set_ie_cb; + reg->pin_needed_cb = cfg->pin_needed_cb; + reg->cb_ctx = cfg->cb_ctx; + + if (wps_set_ie(reg)) { + wps_registrar_deinit(reg); + return NULL; + } + + return reg; +} + + +void wps_registrar_deinit(struct wps_registrar *reg) +{ + if (reg == NULL) + return; + eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL); + wps_free_pins(reg->pins); + wps_free_pbc_sessions(reg->pbc_sessions); + os_free(reg); +} + + +int wps_registrar_add_pin(struct wps_registrar *reg, const u8 *uuid, + const u8 *pin, size_t pin_len) +{ + struct wps_uuid_pin *p; + + p = os_zalloc(sizeof(*p)); + if (p == NULL) + return -1; + os_memcpy(p->uuid, uuid, WPS_UUID_LEN); + p->pin = os_malloc(pin_len); + if (p->pin == NULL) { + os_free(p); + return -1; + } + os_memcpy(p->pin, pin, pin_len); + p->pin_len = pin_len; + + p->next = reg->pins; + reg->pins = p; + + wpa_printf(MSG_DEBUG, "WPS: A new PIN configured"); + wpa_hexdump(MSG_DEBUG, "WPS: UUID", uuid, WPS_UUID_LEN); + wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: PIN", pin, pin_len); + reg->selected_registrar = 1; + reg->pbc = 0; + wps_set_ie(reg); + + return 0; +} + + +int wps_registrar_invalidate_pin(struct wps_registrar *reg, const u8 *uuid) +{ + struct wps_uuid_pin *pin, *prev; + + prev = NULL; + pin = reg->pins; + while (pin) { + if (os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) { + if (prev == NULL) + reg->pins = pin->next; + else + prev->next = pin->next; + wpa_hexdump(MSG_DEBUG, "WPS: Invalidated PIN for UUID", + pin->uuid, WPS_UUID_LEN); + wps_free_pin(pin); + return 0; + } + prev = pin; + pin = pin->next; + } + + return -1; +} + + +static const u8 * wps_registrar_get_pin(struct wps_registrar *reg, + const u8 *uuid, size_t *pin_len) +{ + struct wps_uuid_pin *pin; + + pin = reg->pins; + while (pin) { + if (os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) { + /* + * Lock the PIN to avoid attacks based on concurrent + * re-use of the PIN that could otherwise avoid PIN + * invalidations. + */ + if (pin->locked) { + wpa_printf(MSG_DEBUG, "WPS: Selected PIN " + "locked - do not allow concurrent " + "re-use"); + return NULL; + } + *pin_len = pin->pin_len; + pin->locked = 1; + return pin->pin; + } + pin = pin->next; + } + + return NULL; +} + + +int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid) +{ + struct wps_uuid_pin *pin; + + pin = reg->pins; + while (pin) { + if (os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) { + pin->locked = 0; + return 0; + } + pin = pin->next; + } + + return -1; +} + + +static void wps_registrar_pbc_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wps_registrar *reg = eloop_ctx; + + wpa_printf(MSG_DEBUG, "WPS: PBC timed out - disable PBC mode"); + reg->selected_registrar = 0; + reg->pbc = 0; + wps_set_ie(reg); +} + + +int wps_registrar_button_pushed(struct wps_registrar *reg) +{ + if (wps_registrar_pbc_overlap(reg, NULL, NULL)) { + wpa_printf(MSG_DEBUG, "WPS: PBC overlap - do not start PBC " + "mode"); + return -1; + } + wpa_printf(MSG_DEBUG, "WPS: Button pushed - PBC mode started"); + reg->selected_registrar = 1; + reg->pbc = 1; + wps_set_ie(reg); + + eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL); + eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wps_registrar_pbc_timeout, + reg, NULL); + return 0; +} + + +static void wps_registrar_pbc_completed(struct wps_registrar *reg) +{ + wpa_printf(MSG_DEBUG, "WPS: PBC completed - stopping PBC mode"); + eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL); + reg->selected_registrar = 0; + reg->pbc = 0; + wps_set_ie(reg); +} + + +void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr, + const struct wpabuf *wps_data) +{ + struct wps_parse_attr attr; + u16 methods; + + wpa_hexdump_buf(MSG_MSGDUMP, + "WPS: Probe Request with WPS data received", + wps_data); + + if (wps_parse_msg(wps_data, &attr) < 0 || + attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported ProbeReq WPS IE " + "version 0x%x", attr.version ? *attr.version : 0); + return; + } + + if (attr.config_methods == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Config Methods attribute in " + "Probe Request"); + return; + } + + methods = WPA_GET_BE16(attr.config_methods); + if (!(methods & WPS_CONFIG_PUSHBUTTON)) + return; /* Not PBC */ + + wpa_printf(MSG_DEBUG, "WPS: Probe Request for PBC received from " + MACSTR, MAC2STR(addr)); + + wps_registrar_add_pbc_session(reg, addr, attr.uuid_e); +} + + +static int wps_cb_new_psk(struct wps_registrar *reg, const u8 *mac_addr, + const u8 *psk, size_t psk_len) +{ + if (reg->new_psk_cb == NULL) + return 0; + + return reg->new_psk_cb(reg->cb_ctx, mac_addr, psk, psk_len); +} + + +static void wps_cb_pin_needed(struct wps_registrar *reg, const u8 *uuid_e, + const struct wps_device_data *dev) +{ + if (reg->pin_needed_cb == NULL) + return; + + reg->pin_needed_cb(reg->cb_ctx, uuid_e, dev); +} + + +static int wps_cb_set_ie(struct wps_registrar *reg, + const struct wpabuf *beacon_ie, + const struct wpabuf *probe_resp_ie) +{ + if (reg->set_ie_cb == NULL) + return 0; + + return reg->set_ie_cb(reg->cb_ctx, wpabuf_head(beacon_ie), + wpabuf_len(beacon_ie), + wpabuf_head(probe_resp_ie), + wpabuf_len(probe_resp_ie)); +} + + +static int wps_set_ie(struct wps_registrar *reg) +{ + struct wpabuf *beacon; + struct wpabuf *probe; + int ret; + u8 *blen, *plen; + + wpa_printf(MSG_DEBUG, "WPS: Build Beacon and Probe Response IEs"); + + beacon = wpabuf_alloc(300); + if (beacon == NULL) + return -1; + probe = wpabuf_alloc(300); + if (probe == NULL) { + wpabuf_free(beacon); + return -1; + } + + wpabuf_put_u8(beacon, WLAN_EID_VENDOR_SPECIFIC); + blen = wpabuf_put(beacon, 1); + wpabuf_put_be32(beacon, WPS_DEV_OUI_WFA); + + wpabuf_put_u8(probe, WLAN_EID_VENDOR_SPECIFIC); + plen = wpabuf_put(probe, 1); + wpabuf_put_be32(probe, WPS_DEV_OUI_WFA); + + if (wps_build_version(beacon) || + wps_build_wps_state(reg->wps, beacon) || + wps_build_ap_setup_locked(reg->wps, beacon) || + wps_build_selected_registrar(reg, beacon) || + wps_build_sel_reg_dev_password_id(reg, beacon) || + wps_build_sel_reg_config_methods(reg, beacon) || + wps_build_version(probe) || + wps_build_wps_state(reg->wps, probe) || + wps_build_ap_setup_locked(reg->wps, probe) || + wps_build_selected_registrar(reg, probe) || + wps_build_sel_reg_dev_password_id(reg, probe) || + wps_build_sel_reg_config_methods(reg, probe) || + wps_build_resp_type(reg, probe) || + wps_build_uuid_e(reg, probe) || + wps_build_device_attrs(®->wps->dev, probe) || + wps_build_probe_config_methods(reg, probe) || + wps_build_rf_bands(reg, probe)) { + wpabuf_free(beacon); + wpabuf_free(probe); + return -1; + } + + *blen = wpabuf_len(beacon) - 2; + *plen = wpabuf_len(probe) - 2; + + ret = wps_cb_set_ie(reg, beacon, probe); + wpabuf_free(beacon); + wpabuf_free(probe); + + return ret; +} + + +static int wps_get_dev_password(struct wps_data *wps) +{ + const u8 *pin; + size_t pin_len; + + os_free(wps->dev_password); + wps->dev_password = NULL; + + if (wps->pbc) { + wpa_printf(MSG_DEBUG, "WPS: Use default PIN for PBC"); + pin = (const u8 *) "00000000"; + pin_len = 8; + } else { + pin = wps_registrar_get_pin(wps->registrar, wps->uuid_e, + &pin_len); + } + if (pin == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Device Password available for " + "the Enrollee"); + wps_cb_pin_needed(wps->registrar, wps->uuid_e, &wps->peer_dev); + return -1; + } + + wps->dev_password = os_malloc(pin_len); + if (wps->dev_password == NULL) + return -1; + os_memcpy(wps->dev_password, pin, pin_len); + wps->dev_password_len = pin_len; + + return 0; +} + + +static int wps_build_uuid_r(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * UUID-R"); + wpabuf_put_be16(msg, ATTR_UUID_R); + wpabuf_put_be16(msg, WPS_UUID_LEN); + wpabuf_put_data(msg, wps->uuid_r, WPS_UUID_LEN); + return 0; +} + + +static int wps_build_dev_password_id(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Device Password ID"); + wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, DEV_PW_DEFAULT); + return 0; +} + + +static int wps_build_config_error(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Configuration Error"); + wpabuf_put_be16(msg, ATTR_CONFIG_ERROR); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, WPS_CFG_NO_ERROR); + return 0; +} + + +static int wps_build_r_hash(struct wps_data *wps, struct wpabuf *msg) +{ + u8 *hash; + const u8 *addr[4]; + size_t len[4]; + + if (os_get_random(wps->snonce, 2 * WPS_SECRET_NONCE_LEN) < 0) + return -1; + wpa_hexdump(MSG_DEBUG, "WPS: R-S1", wps->snonce, WPS_SECRET_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: R-S2", + wps->snonce + WPS_SECRET_NONCE_LEN, WPS_SECRET_NONCE_LEN); + + if (wps->dh_pubkey_e == NULL || wps->dh_pubkey_r == NULL) { + wpa_printf(MSG_DEBUG, "WPS: DH public keys not available for " + "R-Hash derivation"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: * R-Hash1"); + wpabuf_put_be16(msg, ATTR_R_HASH1); + wpabuf_put_be16(msg, SHA256_MAC_LEN); + hash = wpabuf_put(msg, SHA256_MAC_LEN); + /* R-Hash1 = HMAC_AuthKey(R-S1 || PSK1 || PK_E || PK_R) */ + addr[0] = wps->snonce; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk1; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + wpa_hexdump(MSG_DEBUG, "WPS: R-Hash1", hash, SHA256_MAC_LEN); + + wpa_printf(MSG_DEBUG, "WPS: * R-Hash2"); + wpabuf_put_be16(msg, ATTR_R_HASH2); + wpabuf_put_be16(msg, SHA256_MAC_LEN); + hash = wpabuf_put(msg, SHA256_MAC_LEN); + /* R-Hash2 = HMAC_AuthKey(R-S2 || PSK2 || PK_E || PK_R) */ + addr[0] = wps->snonce + WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk2; + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + wpa_hexdump(MSG_DEBUG, "WPS: R-Hash2", hash, SHA256_MAC_LEN); + + return 0; +} + + +static int wps_build_r_snonce1(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * R-SNonce1"); + wpabuf_put_be16(msg, ATTR_R_SNONCE1); + wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN); + wpabuf_put_data(msg, wps->snonce, WPS_SECRET_NONCE_LEN); + return 0; +} + + +static int wps_build_r_snonce2(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * R-SNonce2"); + wpabuf_put_be16(msg, ATTR_R_SNONCE2); + wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN); + wpabuf_put_data(msg, wps->snonce + WPS_SECRET_NONCE_LEN, + WPS_SECRET_NONCE_LEN); + return 0; +} + + +static int wps_build_cred_network_idx(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Network Index"); + wpabuf_put_be16(msg, ATTR_NETWORK_INDEX); + wpabuf_put_be16(msg, 1); + wpabuf_put_u8(msg, 0); + return 0; +} + + +static int wps_build_cred_ssid(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * SSID"); + wpabuf_put_be16(msg, ATTR_SSID); + wpabuf_put_be16(msg, wps->wps->ssid_len); + wpabuf_put_data(msg, wps->wps->ssid, wps->wps->ssid_len); + return 0; +} + + +static int wps_build_cred_auth_type(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Authentication Type (0x%x)", + wps->auth_type); + wpabuf_put_be16(msg, ATTR_AUTH_TYPE); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, wps->auth_type); + return 0; +} + + +static int wps_build_cred_encr_type(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Encryption Type (0x%x)", + wps->encr_type); + wpabuf_put_be16(msg, ATTR_ENCR_TYPE); + wpabuf_put_be16(msg, 2); + wpabuf_put_be16(msg, wps->encr_type); + return 0; +} + + +static int wps_build_cred_network_key(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * Network Key"); + wpabuf_put_be16(msg, ATTR_NETWORK_KEY); + if (wps->wps->wps_state == WPS_STATE_NOT_CONFIGURED && wps->wps->ap) { + u8 r[16]; + /* Generate a random passphrase */ + if (os_get_random(r, sizeof(r)) < 0) + return -1; + os_free(wps->new_psk); + wps->new_psk = base64_encode(r, sizeof(r), &wps->new_psk_len); + if (wps->new_psk == NULL) + return -1; + wps->new_psk_len--; /* remove newline */ + while (wps->new_psk_len && + wps->new_psk[wps->new_psk_len - 1] == '=') + wps->new_psk_len--; + wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: Generated passphrase", + wps->new_psk, wps->new_psk_len); + wpabuf_put_be16(msg, wps->new_psk_len); + wpabuf_put_data(msg, wps->new_psk, wps->new_psk_len); + } else if (wps->wps->network_key) { + wpabuf_put_be16(msg, wps->wps->network_key_len); + wpabuf_put_data(msg, wps->wps->network_key, + wps->wps->network_key_len); + } else if (wps->auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) { + char hex[65]; + /* Generate a random per-device PSK */ + os_free(wps->new_psk); + wps->new_psk_len = 32; + wps->new_psk = os_malloc(wps->new_psk_len); + if (wps->new_psk == NULL) + return -1; + if (os_get_random(wps->new_psk, wps->new_psk_len) < 0) { + os_free(wps->new_psk); + wps->new_psk = NULL; + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "WPS: Generated per-device PSK", + wps->new_psk, wps->new_psk_len); + wpa_snprintf_hex(hex, sizeof(hex), wps->new_psk, + wps->new_psk_len); + wpabuf_put_be16(msg, wps->new_psk_len * 2); + wpabuf_put_data(msg, hex, wps->new_psk_len * 2); + } else { + /* No Network Key */ + wpabuf_put_be16(msg, 0); + } + return 0; +} + + +static int wps_build_cred_mac_addr(struct wps_data *wps, struct wpabuf *msg) +{ + wpa_printf(MSG_DEBUG, "WPS: * MAC Address"); + wpabuf_put_be16(msg, ATTR_MAC_ADDR); + wpabuf_put_be16(msg, ETH_ALEN); + wpabuf_put_data(msg, wps->mac_addr_e, ETH_ALEN); + return 0; +} + + +static int wps_build_cred(struct wps_data *wps, struct wpabuf *msg) +{ + struct wpabuf *cred; + int ap_settings; + + ap_settings = !wps->wps->ap; + + if (ap_settings) + wpa_printf(MSG_DEBUG, "WPS: * AP Settings"); + else + wpa_printf(MSG_DEBUG, "WPS: * Credential"); + + /* Select the best authentication and encryption type */ + if (wps->auth_type & WPS_AUTH_WPA2PSK) + wps->auth_type = WPS_AUTH_WPA2PSK; + else if (wps->auth_type & WPS_AUTH_WPAPSK) + wps->auth_type = WPS_AUTH_WPAPSK; + else if (wps->auth_type & WPS_AUTH_OPEN) + wps->auth_type = WPS_AUTH_OPEN; + else if (wps->auth_type & WPS_AUTH_SHARED) + wps->auth_type = WPS_AUTH_SHARED; + else { + wpa_printf(MSG_DEBUG, "WPS: Unsupported auth_type 0x%x", + wps->auth_type); + return -1; + } + + if (wps->auth_type == WPS_AUTH_WPA2PSK || + wps->auth_type == WPS_AUTH_WPAPSK) { + if (wps->encr_type & WPS_ENCR_AES) + wps->encr_type = WPS_ENCR_AES; + else if (wps->encr_type & WPS_ENCR_TKIP) + wps->encr_type = WPS_ENCR_TKIP; + else { + wpa_printf(MSG_DEBUG, "WPS: No suitable encryption " + "type for WPA/WPA2"); + return -1; + } + } else { + if (wps->encr_type & WPS_ENCR_WEP) + wps->encr_type = WPS_ENCR_WEP; + else if (wps->encr_type & WPS_ENCR_NONE) + wps->encr_type = WPS_ENCR_NONE; + else { + wpa_printf(MSG_DEBUG, "WPS: No suitable encryption " + "type for non-WPA/WPA2 mode"); + return -1; + } + } + + cred = wpabuf_alloc(200); + if (cred == NULL) + return -1; + + if (wps_build_cred_network_idx(wps, cred) || + wps_build_cred_ssid(wps, cred) || + wps_build_cred_auth_type(wps, cred) || + wps_build_cred_encr_type(wps, cred) || + wps_build_cred_network_key(wps, cred) || + wps_build_cred_mac_addr(wps, cred)) { + wpabuf_free(cred); + return -1; + } + + if (ap_settings) { + wpabuf_put_buf(msg, cred); + wpabuf_free(cred); + } else { + wpabuf_put_be16(msg, ATTR_CRED); + wpabuf_put_be16(msg, wpabuf_len(cred)); + wpabuf_put_buf(msg, cred); + wpabuf_free(cred); + } + + return 0; +} + + +static struct wpabuf * wps_build_m2(struct wps_data *wps) +{ + struct wpabuf *msg; + + if (os_get_random(wps->nonce_r, WPS_NONCE_LEN) < 0) + return NULL; + wpa_hexdump(MSG_DEBUG, "WPS: Registrar Nonce", + wps->nonce_r, WPS_NONCE_LEN); + os_memcpy(wps->uuid_r, wps->wps->uuid, WPS_UUID_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: UUID-R", wps->uuid_r, WPS_UUID_LEN); + + wpa_printf(MSG_DEBUG, "WPS: Building Message M2"); + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M2) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg) || + wps_build_uuid_r(wps, msg) || + wps_build_public_key(wps, msg) || + wps_derive_keys(wps) || + wps_build_auth_type_flags(wps, msg) || + wps_build_encr_type_flags(wps, msg) || + wps_build_conn_type_flags(wps, msg) || + wps_build_config_methods(wps->registrar, msg) || + wps_build_device_attrs(&wps->wps->dev, msg) || + wps_build_rf_bands(wps->registrar, msg) || + wps_build_assoc_state(wps, msg) || + wps_build_config_error(wps, msg) || + wps_build_dev_password_id(wps, msg) || + wps_build_os_version(&wps->wps->dev, msg) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + wps->state = RECV_M3; + return msg; +} + + +static struct wpabuf * wps_build_m2d(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M2D"); + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M2D) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg) || + wps_build_uuid_r(wps, msg) || + wps_build_auth_type_flags(wps, msg) || + wps_build_encr_type_flags(wps, msg) || + wps_build_conn_type_flags(wps, msg) || + wps_build_config_methods(wps->registrar, msg) || + wps_build_device_attrs(&wps->wps->dev, msg) || + wps_build_rf_bands(wps->registrar, msg) || + wps_build_assoc_state(wps, msg) || + wps_build_config_error(wps, msg) || + wps_build_os_version(&wps->wps->dev, msg)) { + wpabuf_free(msg); + return NULL; + } + + wps->state = RECV_M2D_ACK; + return msg; +} + + +static struct wpabuf * wps_build_m4(struct wps_data *wps) +{ + struct wpabuf *msg, *plain; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M4"); + + wps_derive_psk(wps, wps->dev_password, wps->dev_password_len); + + plain = wpabuf_alloc(200); + if (plain == NULL) + return NULL; + + msg = wpabuf_alloc(1000); + if (msg == NULL) { + wpabuf_free(plain); + return NULL; + } + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M4) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_r_hash(wps, msg) || + wps_build_r_snonce1(wps, plain) || + wps_build_key_wrap_auth(wps, plain) || + wps_build_encr_settings(wps, msg, plain) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(plain); + wpabuf_free(msg); + return NULL; + } + wpabuf_free(plain); + + wps->state = RECV_M5; + return msg; +} + + +static struct wpabuf * wps_build_m6(struct wps_data *wps) +{ + struct wpabuf *msg, *plain; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M6"); + + plain = wpabuf_alloc(200); + if (plain == NULL) + return NULL; + + msg = wpabuf_alloc(1000); + if (msg == NULL) { + wpabuf_free(plain); + return NULL; + } + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M6) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_r_snonce2(wps, plain) || + wps_build_key_wrap_auth(wps, plain) || + wps_build_encr_settings(wps, msg, plain) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(plain); + wpabuf_free(msg); + return NULL; + } + wpabuf_free(plain); + + wps->wps_pin_revealed = 1; + wps->state = RECV_M7; + return msg; +} + + +static struct wpabuf * wps_build_m8(struct wps_data *wps) +{ + struct wpabuf *msg, *plain; + + wpa_printf(MSG_DEBUG, "WPS: Building Message M8"); + + plain = wpabuf_alloc(500); + if (plain == NULL) + return NULL; + + msg = wpabuf_alloc(1000); + if (msg == NULL) { + wpabuf_free(plain); + return NULL; + } + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_M8) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_cred(wps, plain) || + wps_build_key_wrap_auth(wps, plain) || + wps_build_encr_settings(wps, msg, plain) || + wps_build_authenticator(wps, msg)) { + wpabuf_free(plain); + wpabuf_free(msg); + return NULL; + } + wpabuf_free(plain); + + wps->state = RECV_DONE; + return msg; +} + + +static struct wpabuf * wps_build_wsc_ack(struct wps_data *wps) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_ACK"); + + msg = wpabuf_alloc(1000); + if (msg == NULL) + return NULL; + + if (wps_build_version(msg) || + wps_build_msg_type(msg, WPS_WSC_ACK) || + wps_build_enrollee_nonce(wps, msg) || + wps_build_registrar_nonce(wps, msg)) { + wpabuf_free(msg); + return NULL; + } + + return msg; +} + + +struct wpabuf * wps_registrar_get_msg(struct wps_data *wps, u8 *op_code) +{ + struct wpabuf *msg; + + switch (wps->state) { + case SEND_M2: + if (wps_get_dev_password(wps) < 0) + msg = wps_build_m2d(wps); + else + msg = wps_build_m2(wps); + *op_code = WSC_MSG; + break; + case SEND_M2D: + msg = wps_build_m2d(wps); + *op_code = WSC_MSG; + break; + case SEND_M4: + msg = wps_build_m4(wps); + *op_code = WSC_MSG; + break; + case SEND_M6: + msg = wps_build_m6(wps); + *op_code = WSC_MSG; + break; + case SEND_M8: + msg = wps_build_m8(wps); + *op_code = WSC_MSG; + break; + case RECV_DONE: + msg = wps_build_wsc_ack(wps); + *op_code = WSC_ACK; + break; + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported state %d for building " + "a message", wps->state); + msg = NULL; + break; + } + + if (*op_code == WSC_MSG && msg) { + /* Save a copy of the last message for Authenticator derivation + */ + wpabuf_free(wps->last_msg); + wps->last_msg = wpabuf_dup(msg); + } + + return msg; +} + + +static int wps_process_enrollee_nonce(struct wps_data *wps, const u8 *e_nonce) +{ + if (e_nonce == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Enrollee Nonce received"); + return -1; + } + + os_memcpy(wps->nonce_e, e_nonce, WPS_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Enrollee Nonce", + wps->nonce_e, WPS_NONCE_LEN); + + return 0; +} + + +static int wps_process_registrar_nonce(struct wps_data *wps, const u8 *r_nonce) +{ + if (r_nonce == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Registrar Nonce received"); + return -1; + } + + if (os_memcmp(wps->nonce_r, r_nonce, WPS_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Nonce received"); + return -1; + } + + return 0; +} + + +static int wps_process_uuid_e(struct wps_data *wps, const u8 *uuid_e) +{ + if (uuid_e == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No UUID-E received"); + return -1; + } + + os_memcpy(wps->uuid_e, uuid_e, WPS_UUID_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: UUID-E", wps->uuid_e, WPS_UUID_LEN); + + return 0; +} + + +static int wps_process_dev_password_id(struct wps_data *wps, const u8 *pw_id) +{ + if (pw_id == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Device Password ID received"); + return -1; + } + + wps->dev_pw_id = WPA_GET_BE16(pw_id); + wpa_printf(MSG_DEBUG, "WPS: Device Password ID %d", wps->dev_pw_id); + + return 0; +} + + +static int wps_process_e_hash1(struct wps_data *wps, const u8 *e_hash1) +{ + if (e_hash1 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No E-Hash1 received"); + return -1; + } + + os_memcpy(wps->peer_hash1, e_hash1, WPS_HASH_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: E-Hash1", wps->peer_hash1, WPS_HASH_LEN); + + return 0; +} + + +static int wps_process_e_hash2(struct wps_data *wps, const u8 *e_hash2) +{ + if (e_hash2 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No E-Hash2 received"); + return -1; + } + + os_memcpy(wps->peer_hash2, e_hash2, WPS_HASH_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: E-Hash2", wps->peer_hash2, WPS_HASH_LEN); + + return 0; +} + + +static int wps_process_e_snonce1(struct wps_data *wps, const u8 *e_snonce1) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[4]; + size_t len[4]; + + if (e_snonce1 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No E-SNonce1 received"); + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "WPS: E-SNonce1", e_snonce1, + WPS_SECRET_NONCE_LEN); + + /* E-Hash1 = HMAC_AuthKey(E-S1 || PSK1 || PK_E || PK_R) */ + addr[0] = e_snonce1; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk1; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + + if (os_memcmp(wps->peer_hash1, hash, WPS_HASH_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: E-Hash1 derived from E-S1 does " + "not match with the pre-committed value"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee proved knowledge of the first " + "half of the device password"); + + return 0; +} + + +static int wps_process_e_snonce2(struct wps_data *wps, const u8 *e_snonce2) +{ + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[4]; + size_t len[4]; + + if (e_snonce2 == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No E-SNonce2 received"); + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "WPS: E-SNonce2", e_snonce2, + WPS_SECRET_NONCE_LEN); + + /* E-Hash2 = HMAC_AuthKey(E-S2 || PSK2 || PK_E || PK_R) */ + addr[0] = e_snonce2; + len[0] = WPS_SECRET_NONCE_LEN; + addr[1] = wps->psk2; + len[1] = WPS_PSK_LEN; + addr[2] = wpabuf_head(wps->dh_pubkey_e); + len[2] = wpabuf_len(wps->dh_pubkey_e); + addr[3] = wpabuf_head(wps->dh_pubkey_r); + len[3] = wpabuf_len(wps->dh_pubkey_r); + hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash); + + if (os_memcmp(wps->peer_hash2, hash, WPS_HASH_LEN) != 0) { + wpa_printf(MSG_DEBUG, "WPS: E-Hash2 derived from E-S2 does " + "not match with the pre-committed value"); + wps_registrar_invalidate_pin(wps->registrar, wps->uuid_e); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee proved knowledge of the second " + "half of the device password"); + wps->wps_pin_revealed = 0; + wps_registrar_unlock_pin(wps->registrar, wps->uuid_e); + + return 0; +} + + +static int wps_process_mac_addr(struct wps_data *wps, const u8 *mac_addr) +{ + if (mac_addr == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No MAC Address received"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee MAC Address " MACSTR, + MAC2STR(mac_addr)); + os_memcpy(wps->mac_addr_e, mac_addr, ETH_ALEN); + os_memcpy(wps->peer_dev.mac_addr, mac_addr, ETH_ALEN); + + return 0; +} + + +static int wps_process_pubkey(struct wps_data *wps, const u8 *pk, + size_t pk_len) +{ + if (pk == NULL || pk_len == 0) { + wpa_printf(MSG_DEBUG, "WPS: No Public Key received"); + return -1; + } + + wpabuf_free(wps->dh_pubkey_e); + wps->dh_pubkey_e = wpabuf_alloc_copy(pk, pk_len); + if (wps->dh_pubkey_e == NULL) + return -1; + + return 0; +} + + +static int wps_process_auth_type_flags(struct wps_data *wps, const u8 *auth) +{ + u16 auth_types; + + if (auth == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Authentication Type flags " + "received"); + return -1; + } + + auth_types = WPA_GET_BE16(auth); + + wpa_printf(MSG_DEBUG, "WPS: Enrollee Authentication Type flags 0x%x", + auth_types); + wps->auth_type = wps->wps->auth_types & auth_types; + if (wps->auth_type == 0) { + wpa_printf(MSG_DEBUG, "WPS: No match in supported " + "authentication types"); + return -1; + } + + return 0; +} + + +static int wps_process_encr_type_flags(struct wps_data *wps, const u8 *encr) +{ + u16 encr_types; + + if (encr == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Encryption Type flags " + "received"); + return -1; + } + + encr_types = WPA_GET_BE16(encr); + + wpa_printf(MSG_DEBUG, "WPS: Enrollee Encryption Type flags 0x%x", + encr_types); + wps->encr_type = wps->wps->encr_types & encr_types; + if (wps->encr_type == 0) { + wpa_printf(MSG_DEBUG, "WPS: No match in supported " + "encryption types"); + return -1; + } + + return 0; +} + + +static int wps_process_conn_type_flags(struct wps_data *wps, const u8 *conn) +{ + if (conn == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Connection Type flags " + "received"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee Connection Type flags 0x%x", + *conn); + + return 0; +} + + +static int wps_process_config_methods(struct wps_data *wps, const u8 *methods) +{ + u16 m; + + if (methods == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Config Methods received"); + return -1; + } + + m = WPA_GET_BE16(methods); + + wpa_printf(MSG_DEBUG, "WPS: Enrollee Config Methods 0x%x", m); + + return 0; +} + + +static int wps_process_wps_state(struct wps_data *wps, const u8 *state) +{ + if (state == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Wi-Fi Protected Setup State " + "received"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee Wi-Fi Protected Setup State %d", + *state); + + return 0; +} + + +static int wps_process_rf_bands(struct wps_data *wps, const u8 *bands) +{ + if (bands == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No RF Bands received"); + return -1; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee RF Bands 0x%x", *bands); + + return 0; +} + + +static int wps_process_assoc_state(struct wps_data *wps, const u8 *assoc) +{ + u16 a; + + if (assoc == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Association State received"); + return -1; + } + + a = WPA_GET_BE16(assoc); + wpa_printf(MSG_DEBUG, "WPS: Enrollee Association State %d", a); + + return 0; +} + + +static int wps_process_config_error(struct wps_data *wps, const u8 *err) +{ + u16 e; + + if (err == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Configuration Error received"); + return -1; + } + + e = WPA_GET_BE16(err); + wpa_printf(MSG_DEBUG, "WPS: Enrollee Configuration Error %d", e); + + return 0; +} + + +static enum wps_process_res wps_process_m1(struct wps_data *wps, + struct wps_parse_attr *attr) +{ + wpa_printf(MSG_DEBUG, "WPS: Received M1"); + + if (wps->state != RECV_M1) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M1", wps->state); + return WPS_FAILURE; + } + + if (wps_process_uuid_e(wps, attr->uuid_e) || + wps_process_mac_addr(wps, attr->mac_addr) || + wps_process_enrollee_nonce(wps, attr->enrollee_nonce) || + wps_process_pubkey(wps, attr->public_key, attr->public_key_len) || + wps_process_auth_type_flags(wps, attr->auth_type_flags) || + wps_process_encr_type_flags(wps, attr->encr_type_flags) || + wps_process_conn_type_flags(wps, attr->conn_type_flags) || + wps_process_config_methods(wps, attr->config_methods) || + wps_process_wps_state(wps, attr->wps_state) || + wps_process_device_attrs(&wps->peer_dev, attr) || + wps_process_rf_bands(wps, attr->rf_bands) || + wps_process_assoc_state(wps, attr->assoc_state) || + wps_process_dev_password_id(wps, attr->dev_password_id) || + wps_process_config_error(wps, attr->config_error) || + wps_process_os_version(&wps->peer_dev, attr->os_version)) + return WPS_FAILURE; + + if (wps->dev_pw_id != DEV_PW_DEFAULT && + wps->dev_pw_id != DEV_PW_USER_SPECIFIED && + wps->dev_pw_id != DEV_PW_MACHINE_SPECIFIED && + wps->dev_pw_id != DEV_PW_REGISTRAR_SPECIFIED && + (wps->dev_pw_id != DEV_PW_PUSHBUTTON || !wps->registrar->pbc)) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported Device Password ID %d", + wps->dev_pw_id); + wps->state = SEND_M2D; + return WPS_CONTINUE; + } + + if (wps->dev_pw_id == DEV_PW_PUSHBUTTON) { + if (wps_registrar_pbc_overlap(wps->registrar, wps->mac_addr_e, + wps->uuid_e)) { + wpa_printf(MSG_DEBUG, "WPS: PBC overlap - deny PBC " + "negotiation"); + wps->state = SEND_M2D; + return WPS_CONTINUE; + } + wps_registrar_add_pbc_session(wps->registrar, wps->mac_addr_e, + wps->uuid_e); + wps->pbc = 1; + } + + wps->state = SEND_M2; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m3(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + wpa_printf(MSG_DEBUG, "WPS: Received M3"); + + if (wps->state != RECV_M3) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M3", wps->state); + return WPS_FAILURE; + } + + if (wps_process_registrar_nonce(wps, attr->registrar_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg) || + wps_process_e_hash1(wps, attr->e_hash1) || + wps_process_e_hash2(wps, attr->e_hash2)) + return WPS_FAILURE; + + wps->state = SEND_M4; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m5(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + struct wpabuf *decrypted; + struct wps_parse_attr eattr; + + wpa_printf(MSG_DEBUG, "WPS: Received M5"); + + if (wps->state != RECV_M5) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M5", wps->state); + return WPS_FAILURE; + } + + if (wps_process_registrar_nonce(wps, attr->registrar_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg)) + return WPS_FAILURE; + + decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings, + attr->encr_settings_len); + if (decrypted == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted " + "Settings attribute"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings " + "attribute"); + if (wps_parse_msg(decrypted, &eattr) < 0 || + wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) || + wps_process_e_snonce1(wps, eattr.e_snonce1)) { + wpabuf_free(decrypted); + return WPS_FAILURE; + } + wpabuf_free(decrypted); + + wps->state = SEND_M6; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_m7(struct wps_data *wps, + const struct wpabuf *msg, + struct wps_parse_attr *attr) +{ + struct wpabuf *decrypted; + struct wps_parse_attr eattr; + + wpa_printf(MSG_DEBUG, "WPS: Received M7"); + + if (wps->state != RECV_M7) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving M7", wps->state); + return WPS_FAILURE; + } + + if (wps_process_registrar_nonce(wps, attr->registrar_nonce) || + wps_process_authenticator(wps, attr->authenticator, msg)) + return WPS_FAILURE; + + decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings, + attr->encr_settings_len); + if (decrypted == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted " + "Settings attribute"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings " + "attribute"); + if (wps_parse_msg(decrypted, &eattr) < 0 || + wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) || + wps_process_e_snonce2(wps, eattr.e_snonce2)) { + wpabuf_free(decrypted); + return WPS_FAILURE; + } + wpabuf_free(decrypted); + + wps->state = SEND_M8; + return WPS_CONTINUE; +} + + +static enum wps_process_res wps_process_wsc_msg(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + enum wps_process_res ret = WPS_CONTINUE; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_MSG"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_M1 && + (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, + WPS_NONCE_LEN != 0))) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + return WPS_FAILURE; + } + + switch (*attr.msg_type) { + case WPS_M1: + ret = wps_process_m1(wps, &attr); + break; + case WPS_M3: + ret = wps_process_m3(wps, msg, &attr); + break; + case WPS_M5: + ret = wps_process_m5(wps, msg, &attr); + break; + case WPS_M7: + ret = wps_process_m7(wps, msg, &attr); + break; + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (ret == WPS_CONTINUE) { + /* Save a copy of the last message for Authenticator derivation + */ + wpabuf_free(wps->last_msg); + wps->last_msg = wpabuf_dup(msg); + } + + return ret; +} + + +static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_ACK"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_WSC_ACK) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0)) + { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + return WPS_FAILURE; + } + + if (wps->state == RECV_M2D_ACK) { + /* TODO: support for multiple registrars and sending of + * multiple M2/M2D messages */ + + wpa_printf(MSG_DEBUG, "WPS: No more registrars available - " + "terminate negotiation"); + } + + return WPS_FAILURE; +} + + +static enum wps_process_res wps_process_wsc_nack(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_NACK"); + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_WSC_NACK) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0)) + { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + return WPS_FAILURE; + } + + if (attr.config_error == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Configuration Error attribute " + "in WSC_NACK"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Enrollee terminated negotiation with " + "Configuration Error %d", WPA_GET_BE16(attr.config_error)); + + return WPS_FAILURE; +} + + +static enum wps_process_res wps_process_wsc_done(struct wps_data *wps, + const struct wpabuf *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "WPS: Received WSC_Done"); + + if (wps->state != RECV_DONE) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for " + "receiving WSC_Done", wps->state); + return WPS_FAILURE; + } + + if (wps_parse_msg(msg, &attr) < 0) + return WPS_FAILURE; + + if (attr.version == NULL || *attr.version != WPS_VERSION) { + wpa_printf(MSG_DEBUG, "WPS: Unsupported message version 0x%x", + attr.version ? *attr.version : 0); + return WPS_FAILURE; + } + + if (attr.msg_type == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute"); + return WPS_FAILURE; + } + + if (*attr.msg_type != WPS_WSC_DONE) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d", + *attr.msg_type); + return WPS_FAILURE; + } + + if (attr.registrar_nonce == NULL || + os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0)) + { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce"); + return WPS_FAILURE; + } + + if (attr.enrollee_nonce == NULL || + os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) { + wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce"); + return WPS_FAILURE; + } + + wpa_printf(MSG_DEBUG, "WPS: Negotiation completed successfully"); + + if (wps->wps->wps_state == WPS_STATE_NOT_CONFIGURED && wps->new_psk && + wps->wps->ap) { + struct wps_credential cred; + + wpa_printf(MSG_DEBUG, "WPS: Moving to Configured state based " + "on first Enrollee connection"); + + os_memset(&cred, 0, sizeof(cred)); + os_memcpy(cred.ssid, wps->wps->ssid, wps->wps->ssid_len); + cred.ssid_len = wps->wps->ssid_len; + cred.auth_type = WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK; + cred.encr_type = WPS_ENCR_TKIP | WPS_ENCR_AES; + os_memcpy(cred.key, wps->new_psk, wps->new_psk_len); + cred.key_len = wps->new_psk_len; + + wps->wps->wps_state = WPS_STATE_CONFIGURED; + wpa_hexdump_ascii_key(MSG_DEBUG, + "WPS: Generated random passphrase", + wps->new_psk, wps->new_psk_len); + if (wps->wps->cred_cb) + wps->wps->cred_cb(wps->wps->cb_ctx, &cred); + + os_free(wps->new_psk); + wps->new_psk = NULL; + } + + if (wps->new_psk) { + if (wps_cb_new_psk(wps->registrar, wps->mac_addr_e, + wps->new_psk, wps->new_psk_len)) { + wpa_printf(MSG_DEBUG, "WPS: Failed to configure the " + "new PSK"); + } + os_free(wps->new_psk); + wps->new_psk = NULL; + } + + if (wps->pbc) { + wps_registrar_remove_pbc_session(wps->registrar, + wps->mac_addr_e, wps->uuid_e); + wps_registrar_pbc_completed(wps->registrar); + } + + return WPS_DONE; +} + + +enum wps_process_res wps_registrar_process_msg(struct wps_data *wps, + u8 op_code, + const struct wpabuf *msg) +{ + + wpa_printf(MSG_DEBUG, "WPS: Processing received message (len=%lu " + "op_code=%d)", + (unsigned long) wpabuf_len(msg), op_code); + + switch (op_code) { + case WSC_MSG: + return wps_process_wsc_msg(wps, msg); + case WSC_ACK: + return wps_process_wsc_ack(wps, msg); + case WSC_NACK: + return wps_process_wsc_nack(wps, msg); + case WSC_Done: + return wps_process_wsc_done(wps, msg); + default: + wpa_printf(MSG_DEBUG, "WPS: Unsupported op_code %d", op_code); + return WPS_FAILURE; + } +} diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index a8350086f..2a068d764 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -493,6 +493,26 @@ endif NEED_SHA256=y endif +ifdef CONFIG_WPS +# EAP-WSC +ifeq ($(CONFIG_EAP_WSC), dyn) +CFLAGS += -DCONFIG_WPS -DEAP_WSC_DYNAMIC +EAPDYN += ../src/eap_peer/eap_wsc.so +else +CFLAGS += -DCONFIG_WPS -DEAP_WSC +OBJS += ../src/utils/uuid.o +OBJS += ../src/eap_peer/eap_wsc.o ../src/eap_common/eap_wsc_common.o +OBJS += ../src/wps/wps.o +OBJS += ../src/wps/wps_common.o +OBJS += ../src/wps/wps_dev_attr.o +OBJS += ../src/wps/wps_enrollee.o +OBJS += ../src/wps/wps_registrar.o +OBJS_h += ../src/eap_server/eap_wsc.o +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +endif + ifdef CONFIG_EAP_IKEV2 # EAP-IKEv2 ifeq ($(CONFIG_EAP_IKEV2), dyn) @@ -1080,6 +1100,10 @@ eap_sake.so: ../src/eap_peer/eap_sake.c ../src/eap_common/eap_sake_common.c $(CC) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $^ \ -Deap_peer_sake_register=eap_peer_method_dynamic_init +eap_wsc.so: ../src/eap_peer/eap_wsc.c ../src/eap_common/eap_wsc_common.c ../src/wps/wps.c + $(CC) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $^ \ + -Deap_peer_wsc_register=eap_peer_method_dynamic_init + eap_ikev2.so: ../src/eap_peer/eap_ikev2.c ../src/eap_peer/ikev2.c ../src/eap_common/eap_ikev2_common.o ../src/eap_common/ikev2_common.c $(CC) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $^ \ -Deap_peer_ikev2_register=eap_peer_method_dynamic_init diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index c95e7a074..c5e300e0b 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -515,6 +515,10 @@ static int wpa_config_parse_key_mgmt(const struct parse_data *data, else if (os_strcmp(start, "WPA-EAP-SHA256") == 0) val |= WPA_KEY_MGMT_IEEE8021X_SHA256; #endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_WPS + else if (os_strcmp(start, "WPS") == 0) + val |= WPA_KEY_MGMT_WPS; +#endif /* CONFIG_WPS */ else { wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'", line, start); diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 359c5f10b..3153214a9 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -871,6 +871,8 @@ int wpa_config_write(const char *name, struct wpa_config *config) wpa_config_write_global(f, config); for (ssid = config->ssid; ssid; ssid = ssid->next) { + if (ssid->key_mgmt == WPA_KEY_MGMT_WPS) + continue; /* do not save temporary WPS networks */ fprintf(f, "\nnetwork={\n"); wpa_config_write_network(f, ssid); fprintf(f, "}\n"); diff --git a/wpa_supplicant/config_winreg.c b/wpa_supplicant/config_winreg.c index 4ec50f636..48e5c80c8 100644 --- a/wpa_supplicant/config_winreg.c +++ b/wpa_supplicant/config_winreg.c @@ -874,6 +874,8 @@ int wpa_config_write(const char *name, struct wpa_config *config) wpa_config_delete_subkeys(hk, TEXT("networks")); for (ssid = config->ssid, id = 0; ssid; ssid = ssid->next, id++) { + if (ssid->key_mgmt == WPA_KEY_MGMT_WPS) + continue; /* do not save temporary WPS networks */ if (wpa_config_write_network(hk, ssid, id)) errors++; } diff --git a/wpa_supplicant/ctrl_iface_dbus_handlers.c b/wpa_supplicant/ctrl_iface_dbus_handlers.c index 62751adfc..015eb5460 100644 --- a/wpa_supplicant/ctrl_iface_dbus_handlers.c +++ b/wpa_supplicant/ctrl_iface_dbus_handlers.c @@ -420,6 +420,14 @@ DBusMessage * wpas_dbus_bssid_properties(DBusMessage *message, goto error; } + ie = wpa_scan_get_vendor_ie(res, WPS_IE_VENDOR_TYPE); + if (ie) { + if (!wpa_dbus_dict_append_byte_array(&iter_dict, "wpsie", + (const char *) ie, + ie[1] + 2)) + goto error; + } + if (res->freq) { if (!wpa_dbus_dict_append_int32(&iter_dict, "frequency", res->freq)) diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index abf2408d0..e62b9b3df 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -170,6 +170,9 @@ CONFIG_EAP_LEAP=y # EAP-TNC and related Trusted Network Connect support (experimental) #CONFIG_EAP_TNC=y +# Wi-Fi Protected Setup (WPS) +#CONFIG_WPS=y + # EAP-IKEv2 #CONFIG_EAP_IKEV2=y diff --git a/wpa_supplicant/eapol_test.c b/wpa_supplicant/eapol_test.c index 4d9678569..f650c85ab 100644 --- a/wpa_supplicant/eapol_test.c +++ b/wpa_supplicant/eapol_test.c @@ -336,6 +336,7 @@ static int test_eapol(struct eapol_test_data *e, struct wpa_supplicant *wpa_s, ctx->pkcs11_engine_path = wpa_s->conf->pkcs11_engine_path; ctx->pkcs11_module_path = wpa_s->conf->pkcs11_module_path; #endif /* EAP_TLS_OPENSSL */ + ctx->mac_addr = wpa_s->own_addr; wpa_s->eapol = eapol_sm_init(ctx); if (wpa_s->eapol == NULL) { diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 27dfd55ff..34b06e3d7 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -31,6 +31,7 @@ #include "ieee802_11_defs.h" #include "blacklist.h" #include "wpas_glue.h" +#include "wps/wps.h" static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s) @@ -276,6 +277,53 @@ static int wpa_supplicant_ssid_bss_match(struct wpa_ssid *ssid, int proto_match = 0; const u8 *rsn_ie, *wpa_ie; +#ifdef CONFIG_WPS + if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) { + const u8 *wps_ie; + wps_ie = wpa_scan_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE); + if (eap_is_wps_pbc_enrollee(&ssid->eap)) { + if (!wps_ie) { + wpa_printf(MSG_DEBUG, " skip - non-WPS AP"); + return 0; + } + + if (!wps_is_selected_pbc_registrar(wps_ie + 6, + wps_ie[1] - 4)) { + wpa_printf(MSG_DEBUG, " skip - WPS AP " + "without active PBC Registrar"); + return 0; + } + + /* TODO: overlap detection */ + wpa_printf(MSG_DEBUG, " selected based on WPS IE " + "(Active PBC)"); + return 1; + } + + if (eap_is_wps_pin_enrollee(&ssid->eap)) { + if (!wps_ie) { + wpa_printf(MSG_DEBUG, " skip - non-WPS AP"); + return 0; + } + + if (!wps_is_selected_pin_registrar(wps_ie + 6, + wps_ie[1] - 4)) { + wpa_printf(MSG_DEBUG, " skip - WPS AP " + "without active PIN Registrar"); + return 0; + } + wpa_printf(MSG_DEBUG, " selected based on WPS IE " + "(Active PIN)"); + return 1; + } + + if (wps_ie) { + wpa_printf(MSG_DEBUG, " selected based on WPS IE"); + return 1; + } + } +#endif /* CONFIG_WPS */ + rsn_ie = wpa_scan_get_ie(bss, WLAN_EID_RSN); while ((ssid->proto & WPA_PROTO_RSN) && rsn_ie) { proto_match++; @@ -364,6 +412,34 @@ static int wpa_supplicant_ssid_bss_match(struct wpa_ssid *ssid, } +#ifdef CONFIG_WPS +static int wps_ssid_wildcard_ok(struct wpa_ssid *ssid, + struct wpa_scan_res *bss) +{ + const u8 *wps_ie; + + if (eap_is_wps_pbc_enrollee(&ssid->eap)) { + wps_ie = wpa_scan_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE); + if (wps_ie && + wps_is_selected_pbc_registrar(wps_ie + 6, wps_ie[1] - 4)) { + /* allow wildcard SSID for WPS PBC */ + return 1; + } + } + + if (eap_is_wps_pin_enrollee(&ssid->eap)) { + wps_ie = wpa_scan_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE); + if (wps_ie && + wps_is_selected_pin_registrar(wps_ie + 6, wps_ie[1] - 4)) { + /* allow wildcard SSID for WPS PIN */ + return 1; + } + } + + return 0; +} +#endif /* CONFIG_WPS */ + static struct wpa_scan_res * wpa_supplicant_select_bss_wpa(struct wpa_supplicant *wpa_s, struct wpa_ssid *group, @@ -409,13 +485,22 @@ wpa_supplicant_select_bss_wpa(struct wpa_supplicant *wpa_s, } for (ssid = group; ssid; ssid = ssid->pnext) { + int check_ssid = 1; + if (ssid->disabled) { wpa_printf(MSG_DEBUG, " skip - disabled"); continue; } - if (ssid_len != ssid->ssid_len || - os_memcmp(ssid_, ssid->ssid, ssid_len) != 0) { +#ifdef CONFIG_WPS + if (ssid->ssid_len == 0 && + wps_ssid_wildcard_ok(ssid, bss)) + check_ssid = 0; +#endif /* CONFIG_WPS */ + + if (check_ssid && + (ssid_len != ssid->ssid_len || + os_memcmp(ssid_, ssid->ssid, ssid_len) != 0)) { wpa_printf(MSG_DEBUG, " skip - " "SSID mismatch"); continue; @@ -485,12 +570,26 @@ wpa_supplicant_select_bss_non_wpa(struct wpa_supplicant *wpa_s, } for (ssid = group; ssid; ssid = ssid->pnext) { + int check_ssid = ssid->ssid_len != 0; + if (ssid->disabled) { wpa_printf(MSG_DEBUG, " skip - disabled"); continue; } - if (ssid->ssid_len != 0 && +#ifdef CONFIG_WPS + if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) { + /* Only allow wildcard SSID match if an AP + * advertises active WPS operation that matches + * with our mode. */ + check_ssid = 1; + if (ssid->ssid_len == 0 && + wps_ssid_wildcard_ok(ssid, bss)) + check_ssid = 0; + } +#endif /* CONFIG_WPS */ + + if (check_ssid && (ssid_len != ssid->ssid_len || os_memcmp(ssid_, ssid->ssid, ssid_len) != 0)) { wpa_printf(MSG_DEBUG, " skip - " @@ -507,6 +606,7 @@ wpa_supplicant_select_bss_non_wpa(struct wpa_supplicant *wpa_s, } if (!(ssid->key_mgmt & WPA_KEY_MGMT_NONE) && + !(ssid->key_mgmt & WPA_KEY_MGMT_WPS) && !(ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) { wpa_printf(MSG_DEBUG, " skip - " @@ -570,6 +670,65 @@ wpa_supplicant_select_bss(struct wpa_supplicant *wpa_s, struct wpa_ssid *group, } +#ifdef CONFIG_WPS + +static int wpa_scan_pbc_overlap(struct wpa_supplicant *wpa_s, + struct wpa_scan_res *selected, + struct wpa_ssid *ssid) +{ + const u8 *sel_uuid, *uuid; + size_t i; + const u8 *wps_ie; + + if (!eap_is_wps_pbc_enrollee(&ssid->eap)) + return 0; + + /* Make sure that only one AP is in active PBC mode */ + wps_ie = wpa_scan_get_vendor_ie(selected, WPS_IE_VENDOR_TYPE); + if (wps_ie) + sel_uuid = wps_get_uuid_e(wps_ie + 6, wps_ie[1] - 4); + else + sel_uuid = NULL; + if (!sel_uuid) { + wpa_printf(MSG_DEBUG, "WPS: UUID-E not " + "available for PBC overlap " + "detection"); + return 1; + } + + for (i = 0; i < wpa_s->scan_res->num; i++) { + struct wpa_scan_res *bss = wpa_s->scan_res->res[i]; + if (bss == selected) + continue; + wps_ie = wpa_scan_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE); + if (!wps_ie) + continue; + if (!wps_is_selected_pbc_registrar(wps_ie + 6, + wps_ie[1] - 4)) + continue; + uuid = wps_get_uuid_e(wps_ie + 6, wps_ie[1] - 4); + if (uuid == NULL) { + wpa_printf(MSG_DEBUG, "WPS: UUID-E not " + "available for PBC overlap " + "detection (other BSS)"); + return 1; + } + if (os_memcmp(sel_uuid, uuid, 16) != 0) + return 1; /* PBC overlap */ + + /* TODO: verify that this is reasonable dual-band situation */ + } + + return 0; +} + +#else /* CONFIG_WPS */ + +#define wpa_scan_pbc_overlap(w, s, i) 0 + +#endif /* CONFIG_WPS */ + + static void wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s) { int prio, timeout; @@ -619,6 +778,13 @@ static void wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s) } if (selected) { + if (wpa_scan_pbc_overlap(wpa_s, selected, ssid)) { + wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_OVERLAP + "PBC session overlap"); + timeout = 10; + goto req_scan; + } + /* Do not trigger new association unless the BSSID has changed * or if reassociation is requested. If we are in process of * associating with the selected BSSID, do not trigger new diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index 3c5bad96e..04b6d4472 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -20,6 +20,7 @@ #include "wpa_supplicant_i.h" #include "mlme.h" #include "uuid.h" +#include "wps/wps.h" static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s) @@ -41,13 +42,45 @@ static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s) } +#ifdef CONFIG_WPS +static int wpas_wps_in_use(struct wpa_config *conf, u8 *uuid) +{ + struct wpa_ssid *ssid; + int wps = 0; + const char *pos; + + for (ssid = conf->ssid; ssid; ssid = ssid->next) { + if (!(ssid->key_mgmt & WPA_KEY_MGMT_WPS)) + continue; + + wps = 1; + if (!ssid->eap.phase1) + continue; + + pos = os_strstr(ssid->eap.phase1, "uuid="); + if (pos) + uuid_str2bin(pos + 5, uuid); + + if (os_strstr(ssid->eap.phase1, "pbc=1")) + return 2; + } + + return wps; +} +#endif /* CONFIG_WPS */ + static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) { struct wpa_supplicant *wpa_s = eloop_ctx; struct wpa_ssid *ssid; int enabled, scan_req = 0, ret; + struct wpabuf *wps_ie = NULL; const u8 *extra_ie = NULL; size_t extra_ie_len = 0; + int wps = 0; +#ifdef CONFIG_WPS + u8 uuid[UUID_LEN]; +#endif /* CONFIG_WPS */ if (wpa_s->disconnected && !wpa_s->scan_req) return; @@ -134,8 +167,12 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) } else wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN; +#ifdef CONFIG_WPS + wps = wpas_wps_in_use(wpa_s->conf, uuid); +#endif /* CONFIG_WPS */ + if (wpa_s->scan_res_tried == 0 && wpa_s->conf->ap_scan == 1 && - !wpa_s->use_client_mlme) { + !wpa_s->use_client_mlme && wps != 2) { wpa_s->scan_res_tried++; wpa_s->scan_req = scan_req; wpa_printf(MSG_DEBUG, "Trying to get current scan results " @@ -145,6 +182,16 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) return; } +#ifdef CONFIG_WPS + if (wps) { + wps_ie = wps_enrollee_build_probe_req_ie(wps == 2, uuid); + if (wps_ie) { + extra_ie = wpabuf_head(wps_ie); + extra_ie_len = wpabuf_len(wps_ie); + } + } +#endif /* CONFIG_WPS */ + if (wpa_s->use_client_mlme) { ieee80211_sta_set_probe_req_ie(wpa_s, extra_ie, extra_ie_len); ret = ieee80211_sta_req_scan(wpa_s, ssid ? ssid->ssid : NULL, @@ -155,6 +202,8 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) ssid ? ssid->ssid_len : 0); } + wpabuf_free(wps_ie); + if (ret) { wpa_printf(MSG_WARNING, "Failed to initiate AP scan."); wpa_supplicant_req_scan(wpa_s, 10, 0); diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index dba3f133a..e9c92283d 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -38,6 +38,7 @@ #include "ieee802_11_defs.h" #include "blacklist.h" #include "wpas_glue.h" +#include "wps/wps.h" const char *wpa_supplicant_version = "wpa_supplicant v" VERSION_STR "\n" @@ -282,7 +283,8 @@ void wpa_supplicant_initiate_eapol(struct wpa_supplicant *wpa_s) eapol_conf.workaround = ssid->eap_workaround; eapol_conf.eap_disabled = !wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) && - wpa_s->key_mgmt != WPA_KEY_MGMT_IEEE8021X_NO_WPA; + wpa_s->key_mgmt != WPA_KEY_MGMT_IEEE8021X_NO_WPA && + wpa_s->key_mgmt != WPA_KEY_MGMT_WPS; eapol_sm_notify_config(wpa_s->eapol, &ssid->eap, &eapol_conf); #endif /* IEEE8021X_EAPOL */ } @@ -302,7 +304,9 @@ void wpa_supplicant_set_non_wpa_policy(struct wpa_supplicant *wpa_s, { int i; - if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) + if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) + wpa_s->key_mgmt = WPA_KEY_MGMT_WPS; + else if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_NO_WPA; else wpa_s->key_mgmt = WPA_KEY_MGMT_NONE; @@ -635,6 +639,8 @@ static wpa_key_mgmt key_mgmt2driver(int key_mgmt) return KEY_MGMT_802_1X_SHA256; case WPA_KEY_MGMT_PSK_SHA256: return KEY_MGMT_PSK_SHA256; + case WPA_KEY_MGMT_WPS: + return KEY_MGMT_WPS; case WPA_KEY_MGMT_PSK: default: return KEY_MGMT_PSK; @@ -1001,6 +1007,16 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, "results)"); return; } +#ifdef CONFIG_WPS + } else if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) { + struct wpabuf *wps_ie = wps_enrollee_build_assoc_req_ie(); + if (wps_ie && wpabuf_len(wps_ie) <= sizeof(wpa_ie)) { + wpa_ie_len = wpabuf_len(wps_ie); + os_memcpy(wpa_ie, wpabuf_head(wps_ie), wpa_ie_len); + } + wpabuf_free(wps_ie); + wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); +#endif /* CONFIG_WPS */ } else { wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); wpa_ie_len = 0; @@ -1019,6 +1035,8 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, wep_keys_set = 1; } } + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) + use_crypt = 0; #ifdef IEEE8021X_EAPOL if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) { diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index dd4f57979..a4c1ca788 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -397,6 +397,8 @@ fast_reauth=1 # * 0 = do not use cryptobinding (default) # * 1 = use cryptobinding if server supports it # * 2 = require cryptobinding +# EAP-WSC (WPS) uses following options: pin= and +# uuid=. # phase2: Phase2 (inner authentication with TLS tunnel) parameters # (string with field-value pairs, e.g., "auth=MSCHAPV2" for EAP-PEAP or # "autheap=MSCHAPV2 autheap=MD5" for EAP-TTLS) diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index e1e2cd66e..41159bb8f 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -25,6 +25,8 @@ #include "pmksa_cache.h" #include "mlme.h" #include "ieee802_11_defs.h" +#include "wps/wps.h" +#include "wps/wps_defs.h" #include "wpa_ctrl.h" #include "wpas_glue.h" @@ -228,6 +230,19 @@ static void wpa_supplicant_eapol_cb(struct eapol_sm *eapol, int success, wpa_printf(MSG_DEBUG, "EAPOL authentication completed %ssuccessfully", success ? "" : "un"); +#ifdef CONFIG_WPS + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS && wpa_s->current_ssid && + !(wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { + wpa_printf(MSG_DEBUG, "WPS: Network configuration replaced - " + "try to associate with the received credential"); + wpa_supplicant_deauthenticate(wpa_s, + WLAN_REASON_DEAUTH_LEAVING); + wpa_s->reassociate = 1; + wpa_supplicant_req_scan(wpa_s, 0, 0); + return; + } +#endif /* CONFIG_WPS */ + if (!success) { /* * Make sure we do not get stuck here waiting for long EAPOL @@ -491,6 +506,139 @@ static int wpa_supplicant_send_ft_action(void *ctx, u8 action, #endif /* CONFIG_NO_WPA */ +#ifdef CONFIG_WPS +static int wpa_supplicant_wps_cred(void *ctx, struct wps_credential *cred) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *ssid = wpa_s->current_ssid; + + wpa_msg(wpa_s, MSG_INFO, "WPS: New credential received"); + + if (ssid && (ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { + wpa_printf(MSG_DEBUG, "WPS: Replace WPS network block based " + "on the received credential"); + os_free(ssid->eap.identity); + ssid->eap.identity = NULL; + ssid->eap.identity_len = 0; + os_free(ssid->eap.phase1); + ssid->eap.phase1 = NULL; + os_free(ssid->eap.eap_methods); + ssid->eap.eap_methods = NULL; + } else { + wpa_printf(MSG_DEBUG, "WPS: Create a new network based on the " + "received credential"); + ssid = wpa_config_add_network(wpa_s->conf); + if (ssid == NULL) + return -1; + } + + wpa_config_set_network_defaults(ssid); + + os_free(ssid->ssid); + ssid->ssid = os_malloc(cred->ssid_len); + if (ssid->ssid) { + os_memcpy(ssid->ssid, cred->ssid, cred->ssid_len); + ssid->ssid_len = cred->ssid_len; + } + + switch (cred->encr_type) { + case WPS_ENCR_NONE: + ssid->pairwise_cipher = ssid->group_cipher = WPA_CIPHER_NONE; + break; + case WPS_ENCR_WEP: + ssid->pairwise_cipher = ssid->group_cipher = + WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104; + if (cred->key_len > 0 && cred->key_len <= MAX_WEP_KEY_LEN && + cred->key_idx < NUM_WEP_KEYS) { + os_memcpy(ssid->wep_key[cred->key_idx], cred->key, + cred->key_len); + ssid->wep_key_len[cred->key_idx] = cred->key_len; + ssid->wep_tx_keyidx = cred->key_idx; + } + break; + case WPS_ENCR_TKIP: + ssid->pairwise_cipher = WPA_CIPHER_TKIP; + ssid->group_cipher = WPA_CIPHER_TKIP; + break; + case WPS_ENCR_AES: + ssid->pairwise_cipher = WPA_CIPHER_CCMP; + ssid->group_cipher = WPA_CIPHER_CCMP | WPA_CIPHER_TKIP; + break; + } + + switch (cred->auth_type) { + case WPS_AUTH_OPEN: + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_NONE; + ssid->proto = 0; + break; + case WPS_AUTH_SHARED: + ssid->auth_alg = WPA_AUTH_ALG_SHARED; + ssid->key_mgmt = WPA_KEY_MGMT_NONE; + ssid->proto = 0; + break; + case WPS_AUTH_WPAPSK: + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_PSK; + ssid->proto = WPA_PROTO_WPA; + break; + case WPS_AUTH_WPA: + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X; + ssid->proto = WPA_PROTO_WPA; + break; + case WPS_AUTH_WPA2: + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X; + ssid->proto = WPA_PROTO_RSN; + break; + case WPS_AUTH_WPA2PSK: + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_PSK; + ssid->proto = WPA_PROTO_RSN; + break; + } + + if (ssid->key_mgmt == WPA_KEY_MGMT_PSK) { + if (cred->key_len == 2 * PMK_LEN) { + if (hexstr2bin((const char *) cred->key, ssid->psk, + PMK_LEN)) { + wpa_printf(MSG_ERROR, "WPS: Invalid Network " + "Key"); + return -1; + } + ssid->psk_set = 1; + } else if (cred->key_len >= 8 && cred->key_len < 2 * PMK_LEN) { + os_free(ssid->passphrase); + ssid->passphrase = os_malloc(cred->key_len + 1); + if (ssid->passphrase == NULL) + return -1; + os_memcpy(ssid->passphrase, cred->key, cred->key_len); + ssid->passphrase[cred->key_len] = '\0'; + wpa_config_update_psk(ssid); + } else { + wpa_printf(MSG_ERROR, "WPS: Invalid Network Key " + "length %lu", + (unsigned long) cred->key_len); + return -1; + } + } + +#ifndef CONFIG_NO_CONFIG_WRITE + if (wpa_s->conf->update_config && + wpa_config_write(wpa_s->confname, wpa_s->conf)) { + wpa_printf(MSG_DEBUG, "WPS: Failed to update configuration"); + return -1; + } +#endif /* CONFIG_NO_CONFIG_WRITE */ + + return 0; +} +#else /* CONFIG_WPS */ +#define wpa_supplicant_wps_cred NULL +#endif /* CONFIG_WPS */ + + #if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG) static void wpa_supplicant_eap_param_needed(void *ctx, const char *field, const char *txt) @@ -554,6 +702,8 @@ int wpa_supplicant_init_eapol(struct wpa_supplicant *wpa_s) ctx->pkcs11_engine_path = wpa_s->conf->pkcs11_engine_path; ctx->pkcs11_module_path = wpa_s->conf->pkcs11_module_path; #endif /* EAP_TLS_OPENSSL */ + ctx->mac_addr = wpa_s->own_addr; + ctx->wps_cred = wpa_supplicant_wps_cred; ctx->eap_param_needed = wpa_supplicant_eap_param_needed; ctx->cb = wpa_supplicant_eapol_cb; ctx->cb_ctx = wpa_s;