Merge remote-tracking branch 'origin/debian' into debian
[mech_eap.git] / libeap / src / wps / wps_attr_parse.c
diff --git a/libeap/src/wps/wps_attr_parse.c b/libeap/src/wps/wps_attr_parse.c
new file mode 100644 (file)
index 0000000..756d57e
--- /dev/null
@@ -0,0 +1,663 @@
+/*
+ * Wi-Fi Protected Setup - attribute parsing
+ * Copyright (c) 2008, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "wps_defs.h"
+#include "wps_attr_parse.h"
+
+#ifndef CONFIG_WPS_STRICT
+#define WPS_WORKAROUNDS
+#endif /* CONFIG_WPS_STRICT */
+
+
+static int wps_set_vendor_ext_wfa_subelem(struct wps_parse_attr *attr,
+                                         u8 id, u8 len, const u8 *pos)
+{
+       wpa_printf(MSG_EXCESSIVE, "WPS: WFA subelement id=%u len=%u",
+                  id, len);
+       switch (id) {
+       case WFA_ELEM_VERSION2:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Version2 length "
+                                  "%u", len);
+                       return -1;
+               }
+               attr->version2 = pos;
+               break;
+       case WFA_ELEM_AUTHORIZEDMACS:
+               attr->authorized_macs = pos;
+               attr->authorized_macs_len = len;
+               break;
+       case WFA_ELEM_NETWORK_KEY_SHAREABLE:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key "
+                                  "Shareable length %u", len);
+                       return -1;
+               }
+               attr->network_key_shareable = pos;
+               break;
+       case WFA_ELEM_REQUEST_TO_ENROLL:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Request to Enroll "
+                                  "length %u", len);
+                       return -1;
+               }
+               attr->request_to_enroll = pos;
+               break;
+       case WFA_ELEM_SETTINGS_DELAY_TIME:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Settings Delay "
+                                  "Time length %u", len);
+                       return -1;
+               }
+               attr->settings_delay_time = pos;
+               break;
+       case WFA_ELEM_REGISTRAR_CONFIGURATION_METHODS:
+               if (len != 2) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Configuration Methods length %u",
+                                  len);
+                       return -1;
+               }
+               attr->registrar_configuration_methods = pos;
+               break;
+       default:
+               wpa_printf(MSG_MSGDUMP, "WPS: Skipped unknown WFA Vendor "
+                          "Extension subelement %u", id);
+               break;
+       }
+
+       return 0;
+}
+
+
+static int wps_parse_vendor_ext_wfa(struct wps_parse_attr *attr, const u8 *pos,
+                                   u16 len)
+{
+       const u8 *end = pos + len;
+       u8 id, elen;
+
+       while (end - pos >= 2) {
+               id = *pos++;
+               elen = *pos++;
+               if (elen > end - pos)
+                       break;
+               if (wps_set_vendor_ext_wfa_subelem(attr, id, elen, pos) < 0)
+                       return -1;
+               pos += elen;
+       }
+
+       return 0;
+}
+
+
+static int wps_parse_vendor_ext(struct wps_parse_attr *attr, const u8 *pos,
+                               u16 len)
+{
+       u32 vendor_id;
+
+       if (len < 3) {
+               wpa_printf(MSG_DEBUG, "WPS: Skip invalid Vendor Extension");
+               return 0;
+       }
+
+       vendor_id = WPA_GET_BE24(pos);
+       switch (vendor_id) {
+       case WPS_VENDOR_ID_WFA:
+               return wps_parse_vendor_ext_wfa(attr, pos + 3, len - 3);
+       }
+
+       /* Handle unknown vendor extensions */
+
+       wpa_printf(MSG_MSGDUMP, "WPS: Unknown Vendor Extension (Vendor ID %u)",
+                  vendor_id);
+
+       if (len > WPS_MAX_VENDOR_EXT_LEN) {
+               wpa_printf(MSG_DEBUG, "WPS: Too long Vendor Extension (%u)",
+                          len);
+               return -1;
+       }
+
+       if (attr->num_vendor_ext >= MAX_WPS_PARSE_VENDOR_EXT) {
+               wpa_printf(MSG_DEBUG, "WPS: Skipped Vendor Extension "
+                          "attribute (max %d vendor extensions)",
+                          MAX_WPS_PARSE_VENDOR_EXT);
+               return -1;
+       }
+       attr->vendor_ext[attr->num_vendor_ext] = pos;
+       attr->vendor_ext_len[attr->num_vendor_ext] = len;
+       attr->num_vendor_ext++;
+
+       return 0;
+}
+
+
+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 != WPS_DEV_TYPE_LEN) {
+                       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_OOB_DEVICE_PASSWORD:
+               if (len < WPS_OOB_PUBKEY_HASH_LEN + 2 ||
+                   len > WPS_OOB_PUBKEY_HASH_LEN + 2 +
+                   WPS_OOB_DEVICE_PASSWORD_LEN ||
+                   (len < WPS_OOB_PUBKEY_HASH_LEN + 2 +
+                    WPS_OOB_DEVICE_PASSWORD_MIN_LEN &&
+                    WPA_GET_BE16(pos + WPS_OOB_PUBKEY_HASH_LEN) !=
+                    DEV_PW_NFC_CONNECTION_HANDOVER)) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid OOB Device "
+                                  "Password length %u", len);
+                       return -1;
+               }
+               attr->oob_dev_password = pos;
+               attr->oob_dev_password_len = len;
+               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_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_REQUEST_TYPE:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Request Type "
+                                  "length %u", len);
+                       return -1;
+               }
+               attr->request_type = pos;
+               break;
+       case ATTR_RESPONSE_TYPE:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Response Type "
+                                  "length %u", len);
+                       return -1;
+               }
+               attr->response_type = pos;
+               break;
+       case ATTR_MANUFACTURER:
+               attr->manufacturer = pos;
+               if (len > WPS_MANUFACTURER_MAX_LEN)
+                       attr->manufacturer_len = WPS_MANUFACTURER_MAX_LEN;
+               else
+                       attr->manufacturer_len = len;
+               break;
+       case ATTR_MODEL_NAME:
+               attr->model_name = pos;
+               if (len > WPS_MODEL_NAME_MAX_LEN)
+                       attr->model_name_len = WPS_MODEL_NAME_MAX_LEN;
+               else
+                       attr->model_name_len = len;
+               break;
+       case ATTR_MODEL_NUMBER:
+               attr->model_number = pos;
+               if (len > WPS_MODEL_NUMBER_MAX_LEN)
+                       attr->model_number_len = WPS_MODEL_NUMBER_MAX_LEN;
+               else
+                       attr->model_number_len = len;
+               break;
+       case ATTR_SERIAL_NUMBER:
+               attr->serial_number = pos;
+               if (len > WPS_SERIAL_NUMBER_MAX_LEN)
+                       attr->serial_number_len = WPS_SERIAL_NUMBER_MAX_LEN;
+               else
+                       attr->serial_number_len = len;
+               break;
+       case ATTR_DEV_NAME:
+               if (len > WPS_DEV_NAME_MAX_LEN) {
+                       wpa_printf(MSG_DEBUG,
+                                  "WPS: Ignore too long Device Name (len=%u)",
+                                  len);
+                       break;
+               }
+               attr->dev_name = pos;
+               attr->dev_name_len = len;
+               break;
+       case ATTR_PUBLIC_KEY:
+               /*
+                * The Public Key attribute is supposed to be exactly 192 bytes
+                * in length. Allow couple of bytes shorter one to try to
+                * interoperate with implementations that do not use proper
+                * zero-padding.
+                */
+               if (len < 190 || len > 192) {
+                       wpa_printf(MSG_DEBUG,
+                                  "WPS: Ignore Public Key with unexpected length %u",
+                                  len);
+                       break;
+               }
+               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:
+               if (len > SSID_MAX_LEN) {
+                       wpa_printf(MSG_DEBUG,
+                                  "WPS: Ignore too long SSID (len=%u)", len);
+                       break;
+               }
+               attr->ssid = pos;
+               attr->ssid_len = len;
+               break;
+       case ATTR_NETWORK_KEY:
+               attr->network_key = pos;
+               attr->network_key_len = len;
+               break;
+       case ATTR_AP_SETUP_LOCKED:
+               if (len != 1) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid AP Setup Locked "
+                                  "length %u", len);
+                       return -1;
+               }
+               attr->ap_setup_locked = pos;
+               break;
+       case ATTR_REQUESTED_DEV_TYPE:
+               if (len != WPS_DEV_TYPE_LEN) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Requested Device "
+                                  "Type length %u", len);
+                       return -1;
+               }
+               if (attr->num_req_dev_type >= MAX_REQ_DEV_TYPE_COUNT) {
+                       wpa_printf(MSG_DEBUG, "WPS: Skipped Requested Device "
+                                  "Type attribute (max %u types)",
+                                  MAX_REQ_DEV_TYPE_COUNT);
+                       break;
+               }
+               attr->req_dev_type[attr->num_req_dev_type] = pos;
+               attr->num_req_dev_type++;
+               break;
+       case ATTR_SECONDARY_DEV_TYPE_LIST:
+               if (len > WPS_SEC_DEV_TYPE_MAX_LEN ||
+                   (len % WPS_DEV_TYPE_LEN) > 0) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid Secondary Device "
+                                  "Type length %u", len);
+                       return -1;
+               }
+               attr->sec_dev_type_list = pos;
+               attr->sec_dev_type_list_len = len;
+               break;
+       case ATTR_VENDOR_EXT:
+               if (wps_parse_vendor_ext(attr, pos, len) < 0)
+                       return -1;
+               break;
+       case ATTR_AP_CHANNEL:
+               if (len != 2) {
+                       wpa_printf(MSG_DEBUG, "WPS: Invalid AP Channel "
+                                  "length %u", len);
+                       return -1;
+               }
+               attr->ap_channel = pos;
+               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;
+#ifdef WPS_WORKAROUNDS
+       u16 prev_type = 0;
+#endif /* WPS_WORKAROUNDS */
+
+       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_EXCESSIVE, "WPS: attr type=0x%x len=%u",
+                          type, len);
+               if (len > end - pos) {
+                       wpa_printf(MSG_DEBUG, "WPS: Attribute overflow");
+                       wpa_hexdump_buf(MSG_MSGDUMP, "WPS: Message data", msg);
+#ifdef WPS_WORKAROUNDS
+                       /*
+                        * Some deployed APs seem to have a bug in encoding of
+                        * Network Key attribute in the Credential attribute
+                        * where they add an extra octet after the Network Key
+                        * attribute at least when open network is being
+                        * provisioned.
+                        */
+                       if ((type & 0xff00) != 0x1000 &&
+                           prev_type == ATTR_NETWORK_KEY) {
+                               wpa_printf(MSG_DEBUG, "WPS: Workaround - try "
+                                          "to skip unexpected octet after "
+                                          "Network Key");
+                               pos -= 3;
+                               continue;
+                       }
+#endif /* WPS_WORKAROUNDS */
+                       return -1;
+               }
+
+#ifdef WPS_WORKAROUNDS
+               if (type == 0 && len == 0) {
+                       /*
+                        * Mac OS X 10.6 seems to be adding 0x00 padding to the
+                        * end of M1. Skip those to avoid interop issues.
+                        */
+                       int i;
+                       for (i = 0; i < end - pos; i++) {
+                               if (pos[i])
+                                       break;
+                       }
+                       if (i == end - pos) {
+                               wpa_printf(MSG_DEBUG, "WPS: Workaround - skip "
+                                          "unexpected message padding");
+                               break;
+                       }
+               }
+#endif /* WPS_WORKAROUNDS */
+
+               if (wps_set_attr(attr, type, pos, len) < 0)
+                       return -1;
+
+#ifdef WPS_WORKAROUNDS
+               prev_type = type;
+#endif /* WPS_WORKAROUNDS */
+               pos += len;
+       }
+
+       return 0;
+}