From ad08c3633cc2858d28b30a3545341e1e4dda4a90 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sun, 23 Nov 2008 19:34:26 +0200 Subject: [PATCH] Added preliminary Wi-Fi Protected Setup (WPS) implementation This adds WPS support for both hostapd and wpa_supplicant. Both programs can be configured to act as WPS Enrollee and Registrar. Both PBC and PIN methods are supported. Currently, hostapd has more complete configuration option for WPS parameters and wpa_supplicant configuration style will likely change in the future. External Registrars are not yet supported in hostapd or wpa_supplicant. While wpa_supplicant has initial support for acting as an Registrar to configure an AP, this is still using number of hardcoded parameters which will need to be made configurable for proper operation. --- hostapd/ChangeLog | 11 + hostapd/Makefile | 15 + hostapd/README-WPS | 173 ++ hostapd/ap.h | 2 + hostapd/beacon.c | 19 + hostapd/config.c | 74 + hostapd/config.h | 16 + hostapd/ctrl_iface.c | 21 + hostapd/defconfig | 3 + hostapd/driver.h | 25 + hostapd/driver_hostap.c | 41 +- hostapd/driver_madwifi.c | 118 ++ hostapd/driver_test.c | 107 +- hostapd/eapol_sm.c | 2 + hostapd/eapol_sm.h | 1 + hostapd/hostapd.c | 88 +- hostapd/hostapd.conf | 79 + hostapd/hostapd.h | 9 + hostapd/hostapd_cli.c | 31 + hostapd/ieee802_11.c | 15 + hostapd/ieee802_1x.c | 41 +- hostapd/wpa.c | 10 +- hostapd/wps_hostapd.c | 604 +++++++ hostapd/wps_hostapd.h | 48 + src/Makefile | 2 +- src/common/defs.h | 4 +- src/common/ieee802_11_common.c | 5 + src/common/ieee802_11_common.h | 2 + src/common/wpa_ctrl.h | 6 + src/drivers/Apple80211.h | 2 + src/drivers/driver.h | 5 +- src/drivers/driver_test.c | 47 +- src/eap_common/eap_wsc_common.c | 39 + src/eap_common/eap_wsc_common.h | 33 + src/eap_peer/eap.c | 27 + src/eap_peer/eap.h | 17 +- src/eap_peer/eap_config.h | 3 + src/eap_peer/eap_methods.c | 7 + src/eap_peer/eap_wsc.c | 552 ++++++ src/eap_server/eap.c | 1 + src/eap_server/eap.h | 1 + src/eap_server/eap_i.h | 1 + src/eap_server/eap_methods.c | 7 + src/eap_server/eap_wsc.c | 461 +++++ src/eapol_supp/eapol_supp_sm.c | 16 + src/eapol_supp/eapol_supp_sm.h | 18 + src/radius/radius_server.c | 3 + src/radius/radius_server.h | 1 + src/rsn_supp/preauth.c | 1 + src/wps/Makefile | 6 + src/wps/wps.c | 158 ++ src/wps/wps.h | 132 ++ src/wps/wps_common.c | 839 +++++++++ src/wps/wps_defs.h | 294 +++ src/wps/wps_dev_attr.c | 316 ++++ src/wps/wps_dev_attr.h | 29 + src/wps/wps_enrollee.c | 1454 +++++++++++++++ src/wps/wps_i.h | 184 ++ src/wps/wps_registrar.c | 1982 +++++++++++++++++++++ wpa_supplicant/Makefile | 24 + wpa_supplicant/config.c | 4 + wpa_supplicant/config_file.c | 2 + wpa_supplicant/config_winreg.c | 2 + wpa_supplicant/ctrl_iface_dbus_handlers.c | 8 + wpa_supplicant/defconfig | 3 + wpa_supplicant/eapol_test.c | 1 + wpa_supplicant/events.c | 172 +- wpa_supplicant/scan.c | 51 +- wpa_supplicant/wpa_supplicant.c | 22 +- wpa_supplicant/wpa_supplicant.conf | 2 + wpa_supplicant/wpas_glue.c | 150 ++ 71 files changed, 8602 insertions(+), 47 deletions(-) create mode 100644 hostapd/README-WPS create mode 100644 hostapd/wps_hostapd.c create mode 100644 hostapd/wps_hostapd.h create mode 100644 src/eap_common/eap_wsc_common.c create mode 100644 src/eap_common/eap_wsc_common.h create mode 100644 src/eap_peer/eap_wsc.c create mode 100644 src/eap_server/eap_wsc.c create mode 100644 src/wps/Makefile create mode 100644 src/wps/wps.c create mode 100644 src/wps/wps.h create mode 100644 src/wps/wps_common.c create mode 100644 src/wps/wps_defs.h create mode 100644 src/wps/wps_dev_attr.c create mode 100644 src/wps/wps_dev_attr.h create mode 100644 src/wps/wps_enrollee.c create mode 100644 src/wps/wps_i.h create mode 100644 src/wps/wps_registrar.c 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;