Browse Source

Pull latest hostap commits to get new testing commands

Mathy Vanhoef 7 years ago
parent
commit
7cd7a6ca5b
100 changed files with 21679 additions and 1975 deletions
  1. 2 0
      .gitignore
  2. 1 1
      CONTRIBUTIONS
  3. 1 1
      COPYING
  4. 1 1
      README
  5. 108 2
      doc/dbus.doxygen
  6. 1 1
      doc/eap.doxygen
  7. 1 1
      eap_example/eap_example_server.c
  8. 132 10
      hostapd/Android.mk
  9. 226 11
      hostapd/Makefile
  10. 9 21
      hostapd/README
  11. 14 3
      hostapd/android.config
  12. 464 97
      hostapd/config_file.c
  13. 5 0
      hostapd/config_file.h
  14. 646 148
      hostapd/ctrl_iface.c
  15. 33 3
      hostapd/defconfig
  16. 2 2
      hostapd/hlr_auc_gw.c
  17. 1 2
      hostapd/hostapd.android.rc
  18. 338 18
      hostapd/hostapd.conf
  19. 409 121
      hostapd/hostapd_cli.c
  20. 56 9
      hostapd/main.c
  21. 35 24
      hs20/client/osu_client.c
  22. 2 2
      hs20/server/hs20-osu-server.txt
  23. 2 4
      hs20/server/spp_server.c
  24. 8 1
      src/ap/Makefile
  25. 14 32
      src/ap/acs.c
  26. 5 0
      src/ap/acs.h
  27. 104 24
      src/ap/ap_config.c
  28. 101 6
      src/ap/ap_config.h
  29. 69 6
      src/ap/ap_drv_ops.c
  30. 12 3
      src/ap/ap_drv_ops.h
  31. 13 3
      src/ap/ap_mlme.c
  32. 4 3
      src/ap/authsrv.c
  33. 71 2
      src/ap/beacon.c
  34. 1 1
      src/ap/beacon.h
  35. 43 9
      src/ap/bss_load.c
  36. 346 8
      src/ap/ctrl_iface_ap.c
  37. 4 0
      src/ap/ctrl_iface_ap.h
  38. 59 3
      src/ap/dfs.c
  39. 4 1
      src/ap/dfs.h
  40. 9 30
      src/ap/dhcp_snoop.c
  41. 2059 0
      src/ap/dpp_hostapd.c
  42. 43 0
      src/ap/dpp_hostapd.h
  43. 346 26
      src/ap/drv_callbacks.c
  44. 191 0
      src/ap/eth_p_oui.c
  45. 28 0
      src/ap/eth_p_oui.h
  46. 641 0
      src/ap/fils_hlp.c
  47. 27 0
      src/ap/fils_hlp.h
  48. 714 0
      src/ap/gas_query_ap.c
  49. 43 0
      src/ap/gas_query_ap.h
  50. 327 56
      src/ap/gas_serv.c
  51. 5 1
      src/ap/gas_serv.h
  52. 181 45
      src/ap/hostapd.c
  53. 84 6
      src/ap/hostapd.h
  54. 46 78
      src/ap/hw_features.c
  55. 728 48
      src/ap/ieee802_11.c
  56. 31 0
      src/ap/ieee802_11.h
  57. 12 3
      src/ap/ieee802_11_auth.c
  58. 2 1
      src/ap/ieee802_11_auth.h
  59. 88 0
      src/ap/ieee802_11_he.c
  60. 55 11
      src/ap/ieee802_11_ht.c
  61. 178 15
      src/ap/ieee802_11_shared.c
  62. 1 1
      src/ap/ieee802_11_vht.c
  63. 76 26
      src/ap/ieee802_1x.c
  64. 5 0
      src/ap/ieee802_1x.h
  65. 1 0
      src/ap/ndisc_snoop.c
  66. 6 3
      src/ap/neighbor_db.c
  67. 1 1
      src/ap/neighbor_db.h
  68. 0 396
      src/ap/peerkey_auth.c
  69. 128 6
      src/ap/pmksa_cache_auth.c
  70. 10 0
      src/ap/pmksa_cache_auth.h
  71. 147 17
      src/ap/rrm.c
  72. 5 0
      src/ap/rrm.h
  73. 121 8
      src/ap/sta_info.c
  74. 55 10
      src/ap/sta_info.h
  75. 1 0
      src/ap/taxonomy.c
  76. 6 9
      src/ap/wmm.c
  77. 76 24
      src/ap/wnm_ap.c
  78. 1 0
      src/ap/wnm_ap.h
  79. 240 273
      src/ap/wpa_auth.c
  80. 175 81
      src/ap/wpa_auth.h
  81. 1207 51
      src/ap/wpa_auth_ft.c
  82. 409 56
      src/ap/wpa_auth_glue.c
  83. 66 33
      src/ap/wpa_auth_i.h
  84. 171 51
      src/ap/wpa_auth_ie.c
  85. 2 12
      src/ap/wpa_auth_ie.h
  86. 3 1
      src/ap/wps_hostapd.c
  87. 55 0
      src/common/common_module_tests.c
  88. 37 1
      src/common/ctrl_iface_common.c
  89. 5 1
      src/common/ctrl_iface_common.h
  90. 59 5
      src/common/defs.h
  91. 263 0
      src/common/dhcp.h
  92. 7667 0
      src/common/dpp.c
  93. 434 0
      src/common/dpp.h
  94. 1 1
      src/common/gas.c
  95. 3 0
      src/common/gas.h
  96. 483 0
      src/common/gas_server.c
  97. 44 0
      src/common/gas_server.h
  98. 103 3
      src/common/hw_features_common.c
  99. 3 0
      src/common/hw_features_common.h
  100. 424 1
      src/common/ieee802_11_common.c

+ 2 - 0
.gitignore

@@ -8,6 +8,7 @@
 *~
 .config
 tests/hwsim/logs
+tests/remote/logs
 wpaspy/build
 wpa_supplicant/eapol_test
 wpa_supplicant/nfc_pw_token
@@ -31,3 +32,4 @@ wlantest/libwlantest.a
 wlantest/test_vectors
 wlantest/wlantest
 wlantest/wlantest_cli
+**/parallel-vm.log

+ 1 - 1
CONTRIBUTIONS

@@ -140,7 +140,7 @@ The license terms used for hostap.git files
 
 Modified BSD license (no advertisement clause):
 
-Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi> and contributors
+Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi> and contributors
 All Rights Reserved.
 
 Redistribution and use in source and binary forms, with or without

+ 1 - 1
COPYING

@@ -1,7 +1,7 @@
 wpa_supplicant and hostapd
 --------------------------
 
-Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi> and contributors
+Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi> and contributors
 All Rights Reserved.
 
 

+ 1 - 1
README

@@ -12,7 +12,7 @@ license (the license below with the 3rd clause removed).
 wpa_supplicant and hostapd
 --------------------------
 
-Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi> and contributors
+Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi> and contributors
 All Rights Reserved.
 
 These programs are licensed under the BSD license (the one with

+ 108 - 2
doc/dbus.doxygen

@@ -16,6 +16,7 @@ Interfaces:
 - \ref dbus_peer
 - \ref dbus_group
 - \ref dbus_persistent_group
+- \ref dbus_mesh
 
 
 \section dbus_main fi.w1.wpa_supplicant1
@@ -455,6 +456,24 @@ fi.w1.wpa_supplicant1.CreateInterface.
 	  <dd>Initiating the TDLS operation failed for an unknown reason.</dd>
 	</dl>
       </li>
+      <li>
+	<h3>TDLSChannelSwitch ( a{sv} : args ) --> nothing</h3>
+	<p>Configure TDLS channel switching behavior with a peer.</p>
+	<h4>Arguments</h4>
+	<dl>
+	  <dt>a{sv} : args</dt>
+	  <dd>A dictionary with arguments identifying the peer and channel switching behavior.</dd>
+	</dl>
+      </li>
+      <li>
+	<h3>TDLSCancelChannelSwitch ( s : peer_address ) --> nothing</h3>
+	<p>Disable channel switching for a TDLS session with a peer.</p>
+	<h4>Arguments</h4>
+	<dl>
+	  <dt>s : peer_address</dt>
+	  <dd>MAC address for the peer.</dd>
+	</dl>
+      </li>
       <li>
 	<h3>VendorElemAdd ( i: frame_id, ay: ielems ) --> nothing</h3>
 	<p>Add Vendor Elements to corresponding frame ID.</p>
@@ -511,6 +530,10 @@ fi.w1.wpa_supplicant1.CreateInterface.
 	<h3>SaveConfig ( ) --> nothing</h3>
 	<p>Save configuration to the configuration file.</p>
       </li>
+      <li>
+	<h3>AbortScan ( ) --> nothing</h3>
+	<p>Abort ongoing scan operation.</p>
+      </li>
       <li>
 	<h3>EAPLogoff ( ) --> nothing</h3>
 	<p>IEEE 802.1X EAPOL state machine logoff.</p>
@@ -625,8 +648,9 @@ fi.w1.wpa_supplicant1.CreateInterface.
 	<p>Capabilities of the interface. Dictionary contains following entries:</p>
 	<table>
 	  <tr><th>Key</th><th>Value type</th><th>Description</th>
-	  <tr><td>Pairwise</td><td>as</td><td>Possible array elements: "ccmp", "tkip", "none"</td>
-	  <tr><td>Group</td><td>as</td><td>Possible array elements: "ccmp", "tkip", "wep104", "wep40"</td>
+	  <tr><td>Pairwise</td><td>as</td><td>Possible array elements: "ccmp-256", "gcmp-256", "ccmp", "gcmp", "tkip", "none"</td>
+	  <tr><td>Group</td><td>as</td><td>Possible array elements: "ccmp-256", "gcmp-256", "ccmp", "gcmp", "tkip", "wep104", "wep40"</td>
+	  <tr><td>GroupMgmt</td><td>as</td><td>Possible array elements: "aes-128-cmac", "bip-gmac-128", "bip-gmac-256", "bip-cmac-256"</td>
 	  <tr><td>KeyMgmt</td><td>as</td><td>Possible array elements: "wpa-psk", "wpa-ft-psk", "wpa-psk-sha256", "wpa-eap", "wpa-ft-eap", "wpa-eap-sha256", "ieee8021x", "wpa-none", "wps", "none"</td>
 	  <tr><td>Protocol</td><td>as</td><td>Possible array elements: "rsn", "wpa"</td>
 	  <tr><td>AuthAlg</td><td>as</td><td>Possible array elements: "open", "shared", "leap"</td>
@@ -1220,6 +1244,30 @@ Interface for performing WPS (Wi-Fi Simple Config) operations.
 	<h3>ConfigMethods - s - (read/write)</h3>
 	<p>The currently advertised WPS configuration methods. Available methods: usba ethernet label display ext_nfc_token int_nfc_token nfc_interface push_button keypad virtual_display physical_display virtual_push_button physical_push_button.</p>
       </li>
+      <li>
+	<h3>DeviceName - s - (read/write)</h3>
+	<p>User-friendly description of device; up to 32 octets encoded in UTF-8.</p>
+      </li>
+      <li>
+	<h3>Manufacturer - s - (read/write)</h3>
+	<p>The manufacturer of the device (up to 64 ASCII characters).</p>
+      </li>
+      <li>
+	<h3>ModelName - s - (read/write)</h3>
+	<p>Model of the device (up to 32 ASCII characters).</p>
+      </li>
+      <li>
+	<h3>ModelNumber - s - (read/write)</h3>
+	<p>Additional device description (up to 32 ASCII characters).</p>
+      </li>
+      <li>
+	<h3>SerialNumber - s - (read/write)</h3>
+	<p>Serial number of the device (up to 32 characters).</p>
+      </li>
+      <li>
+	<h3>DeviceType - ay - (read/write)</h3>
+	<p>Device Type (8 octet value with 2 octet category, 4 octet OUI, 2 octet subcategory.</p>
+      </li>
     </ul>
 
 \subsection dbus_wps_signals Signals
@@ -1297,6 +1345,7 @@ Interface for performing P2P (Wi-Fi Peer-to-Peer) P2P Device operations.
 	<tr><td>Timeout</td><td>i</td><td>Timeout for operating in seconds</td><td>no</td></tr>
 	<tr><td>RequestedDeviceTypes</td><td>aay</td><td>WPS Device Types to search for</td><td>no</td></tr>
 	<tr><td>DiscoveryType</td><td>s</td><td>"start_with_full" (default, if not specified), "social", "progressive"</td><td>no</td></tr>
+	<tr><td>freq</td><td>i</td><td>Initial scan channel (frequency in MHz) for the start_with_full case to limit the initial scan to the specified channel</td><td>no</td></tr>
 	</table>
       </dd>
     </dl>
@@ -2205,4 +2254,61 @@ Interface implemented by objects representing persistent P2P groups.
   </li>
 </ul>
 
+\section dbus_mesh fi.w1.wpa_supplicant1.Interface.Mesh
+
+Interface for performing mesh operations.
+
+\subsection dbus_mesh_properties Properties
+
+<ul>
+  <li>
+    <h3>MeshPeers - aay - (read)</h3>
+  </li>
+
+  <li>
+    <h3>MeshGroup - ay - (read)</h3>
+  </li>
+</ul>
+
+\subsection dbus_mesh_signals Signals
+
+<ul>
+  <li>
+    <h3>MeshGroupStarted ( a{sv} : args )</h3>
+    <p></p>
+    <h4>Arguments</h4>
+    <dl>
+      <dt>a{sv} : args</dt>
+      <dd>A dictionary containing information of the started mesh group.</dd>
+    </dl>
+  </li>
+  <li>
+    <h3>MeshGroupRemoved ( a{sv} : args )</h3>
+    <p></p>
+    <h4>Arguments</h4>
+    <dl>
+      <dt>a{sv} : args</dt>
+      <dd>A dictionary containing information of the removed mesh group.</dd>
+    </dl>
+  </li>
+  <li>
+    <h3>MeshPeerConnected ( a{sv} : args )</h3>
+    <p></p>
+    <h4>Arguments</h4>
+    <dl>
+      <dt>a{sv} : args</dt>
+      <dd>A dictionary containing information of the connected mesh peer.</dd>
+    </dl>
+  </li>
+  <li>
+    <h3>MeshPeerDisconnected ( a{sv} : args )</h3>
+    <p></p>
+    <h4>Arguments</h4>
+    <dl>
+      <dt>a{sv} : args</dt>
+      <dd>A dictionary containing information of the disconnected mesh peer.</dd>
+    </dl>
+  </li>
+</ul>
+
 */

+ 1 - 1
doc/eap.doxygen

@@ -34,7 +34,7 @@ order to make it possible to select which of the methods are included
 in the build.
 
 EAP methods must implement the interface defined in \ref eap_i.h. struct
-eap_method defines the needed function pointers that each EAP method
+\ref eap_method defines the needed function pointers that each EAP method
 must provide. In addition, the EAP type and name are registered using
 this structure. This interface is based on section 4.4 of RFC 4137.
 

+ 1 - 1
eap_example/eap_example_server.c

@@ -277,7 +277,7 @@ int eap_example_server_step(void)
 	}
 
 	if (process && eap_ctx.eap_if->eapReqData) {
-		/* Send EAP response to the server */
+		/* Send EAP request to the peer */
 		eap_example_peer_rx(wpabuf_head(eap_ctx.eap_if->eapReqData),
 				    wpabuf_len(eap_ctx.eap_if->eapReqData));
 	}

+ 132 - 10
hostapd/Android.mk

@@ -38,6 +38,9 @@ endif
 L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/misc/wifi/sockets\"
 L_CFLAGS += -DCONFIG_CTRL_IFACE_DIR=\"/data/system/hostapd\"
 
+# Use Android specific directory for hostapd_cli command completion history
+L_CFLAGS += -DCONFIG_HOSTAPD_CLI_HISTORY_DIR=\"/data/misc/wifi\"
+
 # To force sizeof(enum) = 4
 ifeq ($(TARGET_ARCH),arm)
 L_CFLAGS += -mabi=aapcs-linux
@@ -212,11 +215,6 @@ L_CFLAGS += -DCONFIG_RSN_PREAUTH
 CONFIG_L2_PACKET=y
 endif
 
-ifdef CONFIG_PEERKEY
-L_CFLAGS += -DCONFIG_PEERKEY
-OBJS += src/ap/peerkey_auth.c
-endif
-
 ifdef CONFIG_HS20
 NEED_AES_OMAC1=y
 CONFIG_PROXYARP=y
@@ -244,11 +242,20 @@ NEED_AES_OMAC1=y
 endif
 
 ifdef CONFIG_IEEE80211R
-L_CFLAGS += -DCONFIG_IEEE80211R
+L_CFLAGS += -DCONFIG_IEEE80211R -DCONFIG_IEEE80211R_AP
 OBJS += src/ap/wpa_auth_ft.c
 NEED_SHA256=y
 NEED_AES_OMAC1=y
 NEED_AES_UNWRAP=y
+NEED_AES_SIV=y
+NEED_ETH_P_OUI=y
+NEED_SHA256=y
+NEED_HMAC_SHA256_KDF=y
+endif
+
+ifdef NEED_ETH_P_OUI
+L_CFLAGS += -DCONFIG_ETH_P_OUI
+OBJS += src/ap/eth_p_oui.c
 endif
 
 ifdef CONFIG_SAE
@@ -258,8 +265,30 @@ NEED_ECC=y
 NEED_DH_GROUPS=y
 endif
 
+ifdef CONFIG_OWE
+L_CFLAGS += -DCONFIG_OWE
+NEED_ECC=y
+NEED_HMAC_SHA256_KDF=y
+NEED_HMAC_SHA384_KDF=y
+NEED_HMAC_SHA512_KDF=y
+NEED_SHA256=y
+NEED_SHA384=y
+NEED_SHA512=y
+endif
+
+ifdef CONFIG_FILS
+L_CFLAGS += -DCONFIG_FILS
+OBJS += src/ap/fils_hlp.c
+NEED_SHA384=y
+NEED_AES_SIV=y
+ifdef CONFIG_FILS_SK_PFS
+L_CFLAGS += -DCONFIG_FILS_SK_PFS
+NEED_ECC=y
+endif
+endif
+
 ifdef CONFIG_WNM
-L_CFLAGS += -DCONFIG_WNM
+L_CFLAGS += -DCONFIG_WNM -DCONFIG_WNM_AP
 OBJS += src/ap/wnm_ap.c
 endif
 
@@ -271,6 +300,10 @@ ifdef CONFIG_IEEE80211AC
 L_CFLAGS += -DCONFIG_IEEE80211AC
 endif
 
+ifdef CONFIG_IEEE80211AX
+L_CFLAGS += -DCONFIG_IEEE80211AX
+endif
+
 ifdef CONFIG_MBO
 L_CFLAGS += -DCONFIG_MBO
 OBJS += src/ap/mbo_ap.c
@@ -422,6 +455,7 @@ ifdef CONFIG_EAP_PWD
 L_CFLAGS += -DEAP_SERVER_PWD
 OBJS += src/eap_server/eap_server_pwd.c src/eap_common/eap_pwd_common.c
 NEED_SHA256=y
+NEED_ECC=y
 endif
 
 ifdef CONFIG_EAP_EKE
@@ -499,6 +533,23 @@ endif
 
 endif
 
+ifdef CONFIG_DPP
+L_CFLAGS += -DCONFIG_DPP
+OBJS += src/common/dpp.c
+OBJS += src/ap/dpp_hostapd.c
+OBJS += src/ap/gas_query_ap.c
+NEED_AES_SIV=y
+NEED_HMAC_SHA256_KDF=y
+NEED_HMAC_SHA384_KDF=y
+NEED_HMAC_SHA512_KDF=y
+NEED_SHA256=y
+NEED_SHA384=y
+NEED_SHA512=y
+NEED_JSON=y
+NEED_GAS=y
+NEED_BASE64=y
+endif
+
 ifdef CONFIG_EAP_IKEV2
 L_CFLAGS += -DEAP_SERVER_IKEV2
 OBJS += src/eap_server/eap_server_ikev2.c src/eap_server/ikev2.c
@@ -581,25 +632,40 @@ NEED_SHA256=y
 NEED_TLS_PRF_SHA256=y
 LIBS += -lcrypto
 LIBS_h += -lcrypto
+ifndef CONFIG_TLS_DEFAULT_CIPHERS
+CONFIG_TLS_DEFAULT_CIPHERS = "DEFAULT:!EXP:!LOW"
+endif
+L_CFLAGS += -DTLS_DEFAULT_CIPHERS=\"$(CONFIG_TLS_DEFAULT_CIPHERS)\"
 endif
 
 ifeq ($(CONFIG_TLS), gnutls)
+ifndef CONFIG_CRYPTO
+# default to libgcrypt
+CONFIG_CRYPTO=gnutls
+endif
 ifdef TLS_FUNCS
 OBJS += src/crypto/tls_gnutls.c
 LIBS += -lgnutls -lgpg-error
 endif
-OBJS += src/crypto/crypto_gnutls.c
-HOBJS += src/crypto/crypto_gnutls.c
+OBJS += src/crypto/crypto_$(CONFIG_CRYPTO).c
+HOBJS += src/crypto/crypto_$(CONFIG_CRYPTO).c
 ifdef NEED_FIPS186_2_PRF
 OBJS += src/crypto/fips_prf_internal.c
 OBJS += src/crypto/sha1-internal.c
 endif
+ifeq ($(CONFIG_CRYPTO), gnutls)
 LIBS += -lgcrypt
 LIBS_h += -lgcrypt
-CONFIG_INTERNAL_SHA256=y
 CONFIG_INTERNAL_RC4=y
 CONFIG_INTERNAL_DH_GROUP5=y
 endif
+ifeq ($(CONFIG_CRYPTO), nettle)
+LIBS += -lnettle -lgmp
+LIBS_p += -lnettle -lgmp
+CONFIG_INTERNAL_RC4=y
+CONFIG_INTERNAL_DH_GROUP5=y
+endif
+endif
 
 ifeq ($(CONFIG_TLS), internal)
 ifndef CONFIG_CRYPTO
@@ -715,6 +781,12 @@ endif
 ifdef NEED_AES_EAX
 AESOBJS += src/crypto/aes-eax.c
 NEED_AES_CTR=y
+NEED_AES_OMAC1=y
+endif
+ifdef NEED_AES_SIV
+AESOBJS += src/crypto/aes-siv.c
+NEED_AES_CTR=y
+NEED_AES_OMAC1=y
 endif
 ifdef NEED_AES_CTR
 AESOBJS += src/crypto/aes-ctr.c
@@ -749,8 +821,10 @@ endif
 SHA1OBJS =
 ifdef NEED_SHA1
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), gnutls)
 SHA1OBJS += src/crypto/sha1.c
 endif
+endif
 SHA1OBJS += src/crypto/sha1-prf.c
 ifdef CONFIG_INTERNAL_SHA1
 SHA1OBJS += src/crypto/sha1-internal.c
@@ -774,8 +848,10 @@ OBJS += $(SHA1OBJS)
 endif
 
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), gnutls)
 OBJS += src/crypto/md5.c
 endif
+endif
 
 ifdef NEED_MD5
 ifdef CONFIG_INTERNAL_MD5
@@ -811,8 +887,10 @@ endif
 ifdef NEED_SHA256
 L_CFLAGS += -DCONFIG_SHA256
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), gnutls)
 OBJS += src/crypto/sha256.c
 endif
+endif
 OBJS += src/crypto/sha256-prf.c
 ifdef CONFIG_INTERNAL_SHA256
 OBJS += src/crypto/sha256-internal.c
@@ -820,11 +898,36 @@ endif
 ifdef NEED_TLS_PRF_SHA256
 OBJS += src/crypto/sha256-tlsprf.c
 endif
+ifdef NEED_HMAC_SHA256_KDF
+OBJS += src/crypto/sha256-kdf.c
+endif
+ifdef NEED_HMAC_SHA384_KDF
+OBJS += src/crypto/sha384-kdf.c
+endif
+ifdef NEED_HMAC_SHA512_KDF
+OBJS += src/crypto/sha512-kdf.c
+endif
 endif
 ifdef NEED_SHA384
 L_CFLAGS += -DCONFIG_SHA384
+ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), gnutls)
+OBJS += src/crypto/sha384.c
+endif
+endif
 OBJS += src/crypto/sha384-prf.c
 endif
+ifdef NEED_SHA512
+L_CFLAGS += -DCONFIG_SHA512
+ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+OBJS += src/crypto/sha512.c
+endif
+endif
+endif
+OBJS += src/crypto/sha512-prf.c
+endif
 
 ifdef CONFIG_INTERNAL_SHA384
 L_CFLAGS += -DCONFIG_INTERNAL_SHA384
@@ -881,6 +984,11 @@ ifdef NEED_BASE64
 OBJS += src/utils/base64.c
 endif
 
+ifdef NEED_JSON
+OBJS += src/utils/json.c
+L_CFLAGS += -DCONFIG_JSON
+endif
+
 ifdef NEED_AP_MLME
 OBJS += src/ap/wmm.c
 OBJS += src/ap/ap_list.c
@@ -897,6 +1005,10 @@ ifdef CONFIG_IEEE80211AC
 OBJS += src/ap/ieee802_11_vht.c
 endif
 
+ifdef CONFIG_IEEE80211AX
+OBJS += src/ap/ieee802_11_he.c
+endif
+
 ifdef CONFIG_P2P_MANAGER
 L_CFLAGS += -DCONFIG_P2P_MANAGER
 OBJS += src/ap/p2p_hostapd.c
@@ -910,6 +1022,10 @@ endif
 
 ifdef CONFIG_INTERWORKING
 L_CFLAGS += -DCONFIG_INTERWORKING
+NEED_GAS=y
+endif
+
+ifdef NEED_GAS
 OBJS += src/common/gas.c
 OBJS += src/ap/gas_serv.c
 endif
@@ -935,6 +1051,10 @@ ifdef CONFIG_NO_STDOUT_DEBUG
 L_CFLAGS += -DCONFIG_NO_STDOUT_DEBUG
 endif
 
+ifdef CONFIG_DEBUG_SYSLOG
+L_CFLAGS += -DCONFIG_DEBUG_SYSLOG
+endif
+
 ifdef CONFIG_DEBUG_LINUX_TRACING
 L_CFLAGS += -DCONFIG_DEBUG_LINUX_TRACING
 endif
@@ -968,6 +1088,7 @@ endif
 include $(CLEAR_VARS)
 LOCAL_MODULE := hostapd_cli
 LOCAL_MODULE_TAGS := debug
+LOCAL_PROPRIETARY_MODULE := true
 LOCAL_SHARED_LIBRARIES := libc libcutils liblog
 LOCAL_CFLAGS := $(L_CFLAGS)
 LOCAL_SRC_FILES := $(OBJS_c)
@@ -978,6 +1099,7 @@ include $(BUILD_EXECUTABLE)
 include $(CLEAR_VARS)
 LOCAL_MODULE := hostapd
 LOCAL_MODULE_TAGS := optional
+LOCAL_PROPRIETARY_MODULE := true
 ifdef CONFIG_DRIVER_CUSTOM
 LOCAL_STATIC_LIBRARIES := libCustomWifi
 endif

+ 226 - 11
hostapd/Makefile

@@ -258,11 +258,6 @@ CFLAGS += -DCONFIG_RSN_PREAUTH
 CONFIG_L2_PACKET=y
 endif
 
-ifdef CONFIG_PEERKEY
-CFLAGS += -DCONFIG_PEERKEY
-OBJS += ../src/ap/peerkey_auth.o
-endif
-
 ifdef CONFIG_HS20
 NEED_AES_OMAC1=y
 CONFIG_PROXYARP=y
@@ -290,11 +285,20 @@ NEED_AES_OMAC1=y
 endif
 
 ifdef CONFIG_IEEE80211R
-CFLAGS += -DCONFIG_IEEE80211R
+CFLAGS += -DCONFIG_IEEE80211R -DCONFIG_IEEE80211R_AP
 OBJS += ../src/ap/wpa_auth_ft.o
 NEED_SHA256=y
 NEED_AES_OMAC1=y
 NEED_AES_UNWRAP=y
+NEED_AES_SIV=y
+NEED_ETH_P_OUI=y
+NEED_SHA256=y
+NEED_HMAC_SHA256_KDF=y
+endif
+
+ifdef NEED_ETH_P_OUI
+CFLAGS += -DCONFIG_ETH_P_OUI
+OBJS += ../src/ap/eth_p_oui.o
 endif
 
 ifdef CONFIG_SAE
@@ -305,8 +309,30 @@ NEED_DH_GROUPS=y
 NEED_AP_MLME=y
 endif
 
+ifdef CONFIG_OWE
+CFLAGS += -DCONFIG_OWE
+NEED_ECC=y
+NEED_HMAC_SHA256_KDF=y
+NEED_HMAC_SHA384_KDF=y
+NEED_HMAC_SHA512_KDF=y
+NEED_SHA256=y
+NEED_SHA384=y
+NEED_SHA512=y
+endif
+
+ifdef CONFIG_FILS
+CFLAGS += -DCONFIG_FILS
+OBJS += ../src/ap/fils_hlp.o
+NEED_SHA384=y
+NEED_AES_SIV=y
+ifdef CONFIG_FILS_SK_PFS
+CFLAGS += -DCONFIG_FILS_SK_PFS
+NEED_ECC=y
+endif
+endif
+
 ifdef CONFIG_WNM
-CFLAGS += -DCONFIG_WNM
+CFLAGS += -DCONFIG_WNM -DCONFIG_WNM_AP
 OBJS += ../src/ap/wnm_ap.o
 endif
 
@@ -318,6 +344,11 @@ ifdef CONFIG_IEEE80211AC
 CFLAGS += -DCONFIG_IEEE80211AC
 endif
 
+ifdef CONFIG_IEEE80211AX
+CFLAGS += -DCONFIG_IEEE80211AX
+OBJS += ../src/ap/ieee802_11_he.o
+endif
+
 ifdef CONFIG_MBO
 CFLAGS += -DCONFIG_MBO
 OBJS += ../src/ap/mbo_ap.o
@@ -458,6 +489,7 @@ ifdef CONFIG_EAP_PWD
 CFLAGS += -DEAP_SERVER_PWD
 OBJS += ../src/eap_server/eap_server_pwd.o ../src/eap_common/eap_pwd_common.o
 NEED_SHA256=y
+NEED_ECC=y
 endif
 
 ifdef CONFIG_EAP_EKE
@@ -535,6 +567,23 @@ endif
 
 endif
 
+ifdef CONFIG_DPP
+CFLAGS += -DCONFIG_DPP
+OBJS += ../src/common/dpp.o
+OBJS += ../src/ap/dpp_hostapd.o
+OBJS += ../src/ap/gas_query_ap.o
+NEED_AES_SIV=y
+NEED_HMAC_SHA256_KDF=y
+NEED_HMAC_SHA384_KDF=y
+NEED_HMAC_SHA512_KDF=y
+NEED_SHA256=y
+NEED_SHA384=y
+NEED_SHA512=y
+NEED_JSON=y
+NEED_GAS=y
+NEED_BASE64=y
+endif
+
 ifdef CONFIG_EAP_IKEV2
 CFLAGS += -DEAP_SERVER_IKEV2
 OBJS += ../src/eap_server/eap_server_ikev2.o ../src/eap_server/ikev2.o
@@ -602,7 +651,29 @@ CFLAGS += -DCONFIG_TLSV12
 NEED_SHA256=y
 endif
 
+ifeq ($(CONFIG_TLS), wolfssl)
+CONFIG_CRYPTO=wolfssl
+ifdef TLS_FUNCS
+OBJS += ../src/crypto/tls_wolfssl.o
+LIBS += -lwolfssl -lm
+endif
+OBJS += ../src/crypto/crypto_wolfssl.o
+HOBJS += ../src/crypto/crypto_wolfssl.o
+ifdef NEED_FIPS186_2_PRF
+OBJS += ../src/crypto/fips_prf_wolfssl.o
+endif
+NEED_SHA256=y
+NEED_TLS_PRF_SHA256=y
+LIBS += -lwolfssl -lm
+LIBS_h += -lwolfssl -lm
+ifdef CONFIG_TLS_ADD_DL
+LIBS += -ldl
+LIBS_h += -ldl
+endif
+endif
+
 ifeq ($(CONFIG_TLS), openssl)
+CONFIG_CRYPTO=openssl
 ifdef TLS_FUNCS
 OBJS += ../src/crypto/tls_openssl.o
 OBJS += ../src/crypto/tls_openssl_ocsp.o
@@ -621,25 +692,40 @@ ifdef CONFIG_TLS_ADD_DL
 LIBS += -ldl
 LIBS_h += -ldl
 endif
+ifndef CONFIG_TLS_DEFAULT_CIPHERS
+CONFIG_TLS_DEFAULT_CIPHERS = "DEFAULT:!EXP:!LOW"
+endif
+CFLAGS += -DTLS_DEFAULT_CIPHERS=\"$(CONFIG_TLS_DEFAULT_CIPHERS)\"
 endif
 
 ifeq ($(CONFIG_TLS), gnutls)
+ifndef CONFIG_CRYPTO
+# default to libgcrypt
+CONFIG_CRYPTO=gnutls
+endif
 ifdef TLS_FUNCS
 OBJS += ../src/crypto/tls_gnutls.o
 LIBS += -lgnutls -lgpg-error
 endif
-OBJS += ../src/crypto/crypto_gnutls.o
-HOBJS += ../src/crypto/crypto_gnutls.o
+OBJS += ../src/crypto/crypto_$(CONFIG_CRYPTO).o
+HOBJS += ../src/crypto/crypto_$(CONFIG_CRYPTO).o
 ifdef NEED_FIPS186_2_PRF
 OBJS += ../src/crypto/fips_prf_internal.o
 SHA1OBJS += ../src/crypto/sha1-internal.o
 endif
+ifeq ($(CONFIG_CRYPTO), gnutls)
 LIBS += -lgcrypt
 LIBS_h += -lgcrypt
-CONFIG_INTERNAL_SHA256=y
 CONFIG_INTERNAL_RC4=y
 CONFIG_INTERNAL_DH_GROUP5=y
 endif
+ifeq ($(CONFIG_CRYPTO), nettle)
+LIBS += -lnettle -lgmp
+LIBS_p += -lnettle -lgmp
+CONFIG_INTERNAL_RC4=y
+CONFIG_INTERNAL_DH_GROUP5=y
+endif
+endif
 
 ifeq ($(CONFIG_TLS), internal)
 ifndef CONFIG_CRYPTO
@@ -720,6 +806,47 @@ CONFIG_INTERNAL_RC4=y
 endif
 endif
 
+ifeq ($(CONFIG_TLS), linux)
+OBJS += ../src/crypto/crypto_linux.o
+ifdef TLS_FUNCS
+OBJS += ../src/crypto/crypto_internal-rsa.o
+OBJS += ../src/crypto/tls_internal.o
+OBJS += ../src/tls/tlsv1_common.o
+OBJS += ../src/tls/tlsv1_record.o
+OBJS += ../src/tls/tlsv1_cred.o
+OBJS += ../src/tls/tlsv1_server.o
+OBJS += ../src/tls/tlsv1_server_write.o
+OBJS += ../src/tls/tlsv1_server_read.o
+OBJS += ../src/tls/asn1.o
+OBJS += ../src/tls/rsa.o
+OBJS += ../src/tls/x509v3.o
+OBJS += ../src/tls/pkcs1.o
+OBJS += ../src/tls/pkcs5.o
+OBJS += ../src/tls/pkcs8.o
+NEED_SHA256=y
+NEED_BASE64=y
+NEED_TLS_PRF=y
+ifdef CONFIG_TLSV12
+NEED_TLS_PRF_SHA256=y
+endif
+NEED_MODEXP=y
+NEED_CIPHER=y
+CFLAGS += -DCONFIG_TLS_INTERNAL
+CFLAGS += -DCONFIG_TLS_INTERNAL_SERVER
+endif
+ifdef NEED_MODEXP
+OBJS += ../src/crypto/crypto_internal-modexp.o
+OBJS += ../src/tls/bignum.o
+CFLAGS += -DCONFIG_INTERNAL_LIBTOMMATH
+CFLAGS += -DLTM_FAST
+endif
+CONFIG_INTERNAL_DH_GROUP5=y
+ifdef NEED_FIPS186_2_PRF
+OBJS += ../src/crypto/fips_prf_internal.o
+OBJS += ../src/crypto/sha1-internal.o
+endif
+endif
+
 ifeq ($(CONFIG_TLS), none)
 ifdef TLS_FUNCS
 OBJS += ../src/crypto/tls_none.o
@@ -750,11 +877,19 @@ AESOBJS += ../src/crypto/aes-internal.o ../src/crypto/aes-internal-enc.o
 endif
 
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), wolfssl)
 AESOBJS += ../src/crypto/aes-wrap.o
 endif
+endif
 ifdef NEED_AES_EAX
 AESOBJS += ../src/crypto/aes-eax.o
 NEED_AES_CTR=y
+NEED_AES_OMAC1=y
+endif
+ifdef NEED_AES_SIV
+AESOBJS += ../src/crypto/aes-siv.o
+NEED_AES_CTR=y
+NEED_AES_OMAC1=y
 endif
 ifdef NEED_AES_CTR
 AESOBJS += ../src/crypto/aes-ctr.o
@@ -763,20 +898,30 @@ ifdef NEED_AES_ENCBLOCK
 AESOBJS += ../src/crypto/aes-encblock.o
 endif
 ifdef NEED_AES_OMAC1
+ifneq ($(CONFIG_TLS), linux)
 AESOBJS += ../src/crypto/aes-omac1.o
 endif
+endif
 ifdef NEED_AES_UNWRAP
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), wolfssl)
 NEED_AES_DEC=y
 AESOBJS += ../src/crypto/aes-unwrap.o
 endif
 endif
+endif
+endif
 ifdef NEED_AES_CBC
 NEED_AES_DEC=y
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), wolfssl)
 AESOBJS += ../src/crypto/aes-cbc.o
 endif
 endif
+endif
+endif
 ifdef NEED_AES_DEC
 ifdef CONFIG_INTERNAL_AES
 AESOBJS += ../src/crypto/aes-internal-dec.o
@@ -788,8 +933,14 @@ endif
 
 ifdef NEED_SHA1
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+ifneq ($(CONFIG_TLS), wolfssl)
 SHA1OBJS += ../src/crypto/sha1.o
 endif
+endif
+endif
+endif
 SHA1OBJS += ../src/crypto/sha1-prf.o
 ifdef CONFIG_INTERNAL_SHA1
 SHA1OBJS += ../src/crypto/sha1-internal.o
@@ -798,8 +949,10 @@ SHA1OBJS += ../src/crypto/fips_prf_internal.o
 endif
 endif
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), wolfssl)
 SHA1OBJS += ../src/crypto/sha1-pbkdf2.o
 endif
+endif
 ifdef NEED_T_PRF
 SHA1OBJS += ../src/crypto/sha1-tprf.o
 endif
@@ -813,8 +966,14 @@ OBJS += $(SHA1OBJS)
 endif
 
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+ifneq ($(CONFIG_TLS), wolfssl)
 OBJS += ../src/crypto/md5.o
 endif
+endif
+endif
+endif
 
 ifdef NEED_MD5
 ifdef CONFIG_INTERNAL_MD5
@@ -830,6 +989,7 @@ endif
 endif
 
 ifdef NEED_DES
+CFLAGS += -DCONFIG_DES
 ifdef CONFIG_INTERNAL_DES
 OBJS += ../src/crypto/des-internal.o
 endif
@@ -850,8 +1010,14 @@ endif
 ifdef NEED_SHA256
 CFLAGS += -DCONFIG_SHA256
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+ifneq ($(CONFIG_TLS), wolfssl)
 OBJS += ../src/crypto/sha256.o
 endif
+endif
+endif
+endif
 OBJS += ../src/crypto/sha256-prf.o
 ifdef CONFIG_INTERNAL_SHA256
 OBJS += ../src/crypto/sha256-internal.o
@@ -862,11 +1028,39 @@ endif
 ifdef NEED_HMAC_SHA256_KDF
 OBJS += ../src/crypto/sha256-kdf.o
 endif
+ifdef NEED_HMAC_SHA384_KDF
+OBJS += ../src/crypto/sha384-kdf.o
+endif
+ifdef NEED_HMAC_SHA512_KDF
+OBJS += ../src/crypto/sha512-kdf.o
+endif
 endif
 ifdef NEED_SHA384
 CFLAGS += -DCONFIG_SHA384
+ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+ifneq ($(CONFIG_TLS), wolfssl)
+OBJS += ../src/crypto/sha384.o
+endif
+endif
+endif
+endif
 OBJS += ../src/crypto/sha384-prf.o
 endif
+ifdef NEED_SHA512
+CFLAGS += -DCONFIG_SHA512
+ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), gnutls)
+ifneq ($(CONFIG_TLS), wolfssl)
+OBJS += ../src/crypto/sha512.o
+endif
+endif
+endif
+endif
+OBJS += ../src/crypto/sha512-prf.o
+endif
 
 ifdef CONFIG_INTERNAL_SHA384
 CFLAGS += -DCONFIG_INTERNAL_SHA384
@@ -902,9 +1096,13 @@ HOBJS += ../src/crypto/random.o
 HOBJS += ../src/utils/eloop.o
 HOBJS += $(SHA1OBJS)
 ifneq ($(CONFIG_TLS), openssl)
+ifneq ($(CONFIG_TLS), linux)
+ifneq ($(CONFIG_TLS), wolfssl)
 HOBJS += ../src/crypto/md5.o
 endif
 endif
+endif
+endif
 
 ifdef CONFIG_RADIUS_SERVER
 CFLAGS += -DRADIUS_SERVER
@@ -923,6 +1121,11 @@ ifdef NEED_BASE64
 OBJS += ../src/utils/base64.o
 endif
 
+ifdef NEED_JSON
+OBJS += ../src/utils/json.o
+CFLAGS += -DCONFIG_JSON
+endif
+
 ifdef NEED_AP_MLME
 OBJS += ../src/ap/wmm.o
 OBJS += ../src/ap/ap_list.o
@@ -952,6 +1155,10 @@ endif
 
 ifdef CONFIG_INTERWORKING
 CFLAGS += -DCONFIG_INTERWORKING
+NEED_GAS=y
+endif
+
+ifdef NEED_GAS
 OBJS += ../src/common/gas.o
 OBJS += ../src/ap/gas_serv.o
 endif
@@ -983,6 +1190,10 @@ ifdef CONFIG_NO_STDOUT_DEBUG
 CFLAGS += -DCONFIG_NO_STDOUT_DEBUG
 endif
 
+ifdef CONFIG_DEBUG_SYSLOG
+CFLAGS += -DCONFIG_DEBUG_SYSLOG
+endif
+
 ifdef CONFIG_DEBUG_LINUX_TRACING
 CFLAGS += -DCONFIG_DEBUG_LINUX_TRACING
 endif
@@ -1082,7 +1293,8 @@ endif
 ifdef CONFIG_INTERNAL_MD5
 NOBJS += ../src/crypto/md5-internal.o
 endif
-NOBJS += ../src/crypto/crypto_openssl.o ../src/utils/os_$(CONFIG_OS).o
+NOBJS += ../src/crypto/crypto_$(CONFIG_CRYPTO).o
+NOBJS += ../src/utils/os_$(CONFIG_OS).o
 NOBJS += ../src/utils/wpa_debug.o
 NOBJS += ../src/utils/wpabuf.o
 ifdef CONFIG_WPA_TRACE
@@ -1099,6 +1311,9 @@ ifdef CONFIG_INTERNAL_AES
 HOBJS += ../src/crypto/aes-internal.o
 HOBJS += ../src/crypto/aes-internal-enc.o
 endif
+ifeq ($(CONFIG_TLS), linux)
+HOBJS += ../src/crypto/crypto_linux.o
+endif
 
 nt_password_hash: $(NOBJS)
 	$(Q)$(CC) $(LDFLAGS) -o nt_password_hash $(NOBJS) $(LIBS_n)

+ 9 - 21
hostapd/README

@@ -2,7 +2,7 @@ hostapd - user space IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP
 	  Authenticator and RADIUS authentication server
 ================================================================
 
-Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi> and contributors
+Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi> and contributors
 All Rights Reserved.
 
 This program is licensed under the BSD license (the one with
@@ -70,7 +70,7 @@ Requirements
 Current hardware/software requirements:
 - drivers:
 	Host AP driver for Prism2/2.5/3.
-	(http://hostap.epitest.fi/)
+	(http://w1.fi/hostap-driver.html)
 	Please note that station firmware version needs to be 1.7.0 or newer
 	to work in WPA mode.
 
@@ -81,8 +81,7 @@ Current hardware/software requirements:
 	Any wired Ethernet driver for wired IEEE 802.1X authentication
 	(experimental code)
 
-	FreeBSD -current (with some kernel mods that have not yet been
-	committed when hostapd v0.3.0 was released)
+	FreeBSD -current
 	BSD net80211 layer (e.g., Atheros driver)
 
 
@@ -186,23 +185,13 @@ Authenticator and RADIUS encapsulation between the Authenticator and
 the Authentication Server. Other than this, the functionality is similar
 to the case with the co-located Authentication Server.
 
-Authentication Server and Supplicant
-------------------------------------
+Authentication Server
+---------------------
 
 Any RADIUS server supporting EAP should be usable as an IEEE 802.1X
 Authentication Server with hostapd Authenticator. FreeRADIUS
 (http://www.freeradius.org/) has been successfully tested with hostapd
-Authenticator and both Xsupplicant (http://www.open1x.org) and Windows
-XP Supplicants. EAP/TLS was used with Xsupplicant and
-EAP/MD5-Challenge with Windows XP.
-
-http://www.missl.cs.umd.edu/wireless/eaptls/ has useful information
-about using EAP/TLS with FreeRADIUS and Xsupplicant (just replace
-Cisco access point with Host AP driver, hostapd daemon, and a Prism2
-card ;-). http://www.freeradius.org/doc/EAP-MD5.html has information
-about using EAP/MD5 with FreeRADIUS, including instructions for WinXP
-configuration. http://www.denobula.com/EAPTLS.pdf has a HOWTO on
-EAP/TLS use with WinXP Supplicant.
+Authenticator.
 
 Automatic WEP key configuration
 -------------------------------
@@ -243,16 +232,15 @@ networks that require some kind of security. Task group I (Security)
 of IEEE 802.11 working group (http://www.ieee802.org/11/) has worked
 to address the flaws of the base standard and has in practice
 completed its work in May 2004. The IEEE 802.11i amendment to the IEEE
-802.11 standard was approved in June 2004 and this amendment is likely
-to be published in July 2004.
+802.11 standard was approved in June 2004 and this amendment was
+published in July 2004.
 
 Wi-Fi Alliance (http://www.wi-fi.org/) used a draft version of the
 IEEE 802.11i work (draft 3.0) to define a subset of the security
 enhancements that can be implemented with existing wlan hardware. This
 is called Wi-Fi Protected Access<TM> (WPA). This has now become a
 mandatory component of interoperability testing and certification done
-by Wi-Fi Alliance. Wi-Fi provides information about WPA at its web
-site (http://www.wi-fi.org/OpenSection/protected_access.asp).
+by Wi-Fi Alliance.
 
 IEEE 802.11 standard defined wired equivalent privacy (WEP) algorithm
 for protecting wireless networks. WEP uses RC4 with 40-bit keys,

+ 14 - 3
hostapd/android.config

@@ -44,9 +44,6 @@ CONFIG_DRIVER_NL80211_QCA=y
 # WPA2/IEEE 802.11i RSN pre-authentication
 #CONFIG_RSN_PREAUTH=y
 
-# PeerKey handshake for Station to Station Link (IEEE 802.11e DLS)
-#CONFIG_PEERKEY=y
-
 # IEEE 802.11w (management frame protection)
 # This version is an experimental implementation based on IEEE 802.11w/D1.0
 # draft and is subject to change since the standard has not yet been finalized.
@@ -199,3 +196,17 @@ CONFIG_AP=y
 # These extentions facilitate efficient use of multiple frequency bands
 # available to the AP and the devices that may associate with it.
 #CONFIG_MBO=y
+
+# Include internal line edit mode in hostapd_cli.
+CONFIG_WPA_CLI_EDIT=y
+
+# Opportunistic Wireless Encryption (OWE)
+# Experimental implementation of draft-harkins-owe-07.txt
+#CONFIG_OWE=y
+
+# Wpa_supplicant's random pool is not necessary on Android. Randomness is
+# already provided by the entropymixer service which ensures sufficient
+# entropy is maintained across reboots. Commit b410eb1913 'Initialize
+# /dev/urandom earlier in boot' seeds /dev/urandom with that entropy before
+# either wpa_supplicant or hostapd are run.
+CONFIG_NO_RANDOM_POOL=y

+ 464 - 97
hostapd/config_file.c

@@ -14,6 +14,8 @@
 #include "utils/common.h"
 #include "utils/uuid.h"
 #include "common/ieee802_11_defs.h"
+#include "crypto/sha256.h"
+#include "crypto/tls.h"
 #include "drivers/driver.h"
 #include "eap_server/eap.h"
 #include "radius/radius_client.h"
@@ -111,7 +113,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
 #endif /* CONFIG_NO_VLAN */
 
 
-static int hostapd_acl_comp(const void *a, const void *b)
+int hostapd_acl_comp(const void *a, const void *b)
 {
 	const struct mac_acl_entry *aa = a;
 	const struct mac_acl_entry *bb = b;
@@ -119,6 +121,44 @@ static int hostapd_acl_comp(const void *a, const void *b)
 }
 
 
+int hostapd_add_acl_maclist(struct mac_acl_entry **acl, int *num,
+			    int vlan_id, const u8 *addr)
+{
+	struct mac_acl_entry *newacl;
+
+	newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl));
+	if (!newacl) {
+		wpa_printf(MSG_ERROR, "MAC list reallocation failed");
+		return -1;
+	}
+
+	*acl = newacl;
+	os_memcpy((*acl)[*num].addr, addr, ETH_ALEN);
+	os_memset(&(*acl)[*num].vlan_id, 0, sizeof((*acl)[*num].vlan_id));
+	(*acl)[*num].vlan_id.untagged = vlan_id;
+	(*acl)[*num].vlan_id.notempty = !!vlan_id;
+	(*num)++;
+
+	return 0;
+}
+
+
+void hostapd_remove_acl_mac(struct mac_acl_entry **acl, int *num,
+			    const u8 *addr)
+{
+	int i = 0;
+
+	while (i < *num) {
+		if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == 0) {
+			os_remove_in_array(*acl, *num, sizeof(**acl), i);
+			(*num)--;
+		} else {
+			i++;
+		}
+	}
+}
+
+
 static int hostapd_config_read_maclist(const char *fname,
 				       struct mac_acl_entry **acl, int *num)
 {
@@ -126,12 +166,8 @@ static int hostapd_config_read_maclist(const char *fname,
 	char buf[128], *pos;
 	int line = 0;
 	u8 addr[ETH_ALEN];
-	struct mac_acl_entry *newacl;
 	int vlan_id;
 
-	if (!fname)
-		return 0;
-
 	f = fopen(fname, "r");
 	if (!f) {
 		wpa_printf(MSG_ERROR, "MAC list file '%s' not found.", fname);
@@ -139,7 +175,7 @@ static int hostapd_config_read_maclist(const char *fname,
 	}
 
 	while (fgets(buf, sizeof(buf), f)) {
-		int i, rem = 0;
+		int rem = 0;
 
 		line++;
 
@@ -169,16 +205,7 @@ static int hostapd_config_read_maclist(const char *fname,
 		}
 
 		if (rem) {
-			i = 0;
-			while (i < *num) {
-				if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) ==
-				    0) {
-					os_remove_in_array(*acl, *num,
-							   sizeof(**acl), i);
-					(*num)--;
-				} else
-					i++;
-			}
+			hostapd_remove_acl_mac(acl, num, addr);
 			continue;
 		}
 		vlan_id = 0;
@@ -190,25 +217,16 @@ static int hostapd_config_read_maclist(const char *fname,
 		if (*pos != '\0')
 			vlan_id = atoi(pos);
 
-		newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl));
-		if (newacl == NULL) {
-			wpa_printf(MSG_ERROR, "MAC list reallocation failed");
+		if (hostapd_add_acl_maclist(acl, num, vlan_id, addr) < 0) {
 			fclose(f);
 			return -1;
 		}
-
-		*acl = newacl;
-		os_memcpy((*acl)[*num].addr, addr, ETH_ALEN);
-		os_memset(&(*acl)[*num].vlan_id, 0,
-			  sizeof((*acl)[*num].vlan_id));
-		(*acl)[*num].vlan_id.untagged = vlan_id;
-		(*acl)[*num].vlan_id.notempty = !!vlan_id;
-		(*num)++;
 	}
 
 	fclose(f);
 
-	qsort(*acl, *num, sizeof(**acl), hostapd_acl_comp);
+	if (*acl)
+		qsort(*acl, *num, sizeof(**acl), hostapd_acl_comp);
 
 	return 0;
 }
@@ -223,9 +241,6 @@ static int hostapd_config_read_eap_user(const char *fname,
 	int line = 0, ret = 0, num_methods;
 	struct hostapd_eap_user *user = NULL, *tail = NULL, *new_user = NULL;
 
-	if (!fname)
-		return 0;
-
 	if (os_strncmp(fname, "sqlite:", 7) == 0) {
 #ifdef CONFIG_SQLITE
 		os_free(conf->eap_user_sqlite);
@@ -312,13 +327,12 @@ static int hostapd_config_read_eap_user(const char *fname,
 				goto failed;
 			}
 
-			user->identity = os_malloc(pos - start);
+			user->identity = os_memdup(start, pos - start);
 			if (user->identity == NULL) {
 				wpa_printf(MSG_ERROR, "Failed to allocate "
 					   "memory for EAP identity");
 				goto failed;
 			}
-			os_memcpy(user->identity, start, pos - start);
 			user->identity_len = pos - start;
 
 			if (pos[0] == '"' && pos[1] == '*') {
@@ -436,13 +450,12 @@ static int hostapd_config_read_eap_user(const char *fname,
 				goto failed;
 			}
 
-			user->password = os_malloc(pos - start);
+			user->password = os_memdup(start, pos - start);
 			if (user->password == NULL) {
 				wpa_printf(MSG_ERROR, "Failed to allocate "
 					   "memory for EAP password");
 				goto failed;
 			}
-			os_memcpy(user->password, start, pos - start);
 			user->password_len = pos - start;
 
 			pos++;
@@ -522,15 +535,10 @@ static int hostapd_config_read_eap_user(const char *fname,
 	fclose(f);
 
 	if (ret == 0) {
-		user = conf->eap_user;
-		while (user) {
-			struct hostapd_eap_user *prev;
-
-			prev = user;
-			user = user->next;
-			hostapd_config_free_eap_user(prev);
-		}
+		hostapd_config_free_eap_users(conf->eap_user);
 		conf->eap_user = new_user;
+	} else {
+		hostapd_config_free_eap_users(new_user);
 	}
 
 	return ret;
@@ -684,12 +692,12 @@ static int hostapd_config_parse_key_mgmt(int line, const char *value)
 			val |= WPA_KEY_MGMT_PSK;
 		else if (os_strcmp(start, "WPA-EAP") == 0)
 			val |= WPA_KEY_MGMT_IEEE8021X;
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 		else if (os_strcmp(start, "FT-PSK") == 0)
 			val |= WPA_KEY_MGMT_FT_PSK;
 		else if (os_strcmp(start, "FT-EAP") == 0)
 			val |= WPA_KEY_MGMT_FT_IEEE8021X;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 		else if (os_strcmp(start, "WPA-PSK-SHA256") == 0)
 			val |= WPA_KEY_MGMT_PSK_SHA256;
@@ -710,6 +718,26 @@ static int hostapd_config_parse_key_mgmt(int line, const char *value)
 		else if (os_strcmp(start, "WPA-EAP-SUITE-B-192") == 0)
 			val |= WPA_KEY_MGMT_IEEE8021X_SUITE_B_192;
 #endif /* CONFIG_SUITEB192 */
+#ifdef CONFIG_FILS
+		else if (os_strcmp(start, "FILS-SHA256") == 0)
+			val |= WPA_KEY_MGMT_FILS_SHA256;
+		else if (os_strcmp(start, "FILS-SHA384") == 0)
+			val |= WPA_KEY_MGMT_FILS_SHA384;
+#ifdef CONFIG_IEEE80211R_AP
+		else if (os_strcmp(start, "FT-FILS-SHA256") == 0)
+			val |= WPA_KEY_MGMT_FT_FILS_SHA256;
+		else if (os_strcmp(start, "FT-FILS-SHA384") == 0)
+			val |= WPA_KEY_MGMT_FT_FILS_SHA384;
+#endif /* CONFIG_IEEE80211R_AP */
+#endif /* CONFIG_FILS */
+#ifdef CONFIG_OWE
+		else if (os_strcmp(start, "OWE") == 0)
+			val |= WPA_KEY_MGMT_OWE;
+#endif /* CONFIG_OWE */
+#ifdef CONFIG_DPP
+		else if (os_strcmp(start, "DPP") == 0)
+			val |= WPA_KEY_MGMT_DPP;
+#endif /* CONFIG_DPP */
 		else {
 			wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'",
 				   line, start);
@@ -755,17 +783,34 @@ static int hostapd_config_read_wep(struct hostapd_wep_keys *wep, int keyidx,
 {
 	size_t len = os_strlen(val);
 
-	if (keyidx < 0 || keyidx > 3 || wep->key[keyidx] != NULL)
+	if (keyidx < 0 || keyidx > 3)
+		return -1;
+
+	if (len == 0) {
+		int i, set = 0;
+
+		bin_clear_free(wep->key[keyidx], wep->len[keyidx]);
+		wep->key[keyidx] = NULL;
+		wep->len[keyidx] = 0;
+		for (i = 0; i < NUM_WEP_KEYS; i++) {
+			if (wep->key[i])
+				set++;
+		}
+		if (!set)
+			wep->keys_set = 0;
+		return 0;
+	}
+
+	if (wep->key[keyidx] != NULL)
 		return -1;
 
 	if (val[0] == '"') {
 		if (len < 2 || val[len - 1] != '"')
 			return -1;
 		len -= 2;
-		wep->key[keyidx] = os_malloc(len);
+		wep->key[keyidx] = os_memdup(val + 1, len);
 		if (wep->key[keyidx] == NULL)
 			return -1;
-		os_memcpy(wep->key[keyidx], val + 1, len);
 		wep->len[keyidx] = len;
 	} else {
 		if (len & 1)
@@ -978,7 +1023,27 @@ static int hostapd_config_tx_queue(struct hostapd_config *conf,
 }
 
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
+
+static int rkh_derive_key(const char *pos, u8 *key, size_t key_len)
+{
+	u8 oldkey[16];
+	int ret;
+
+	if (!hexstr2bin(pos, key, key_len))
+		return 0;
+
+	/* Try to use old short key for backwards compatibility */
+	if (hexstr2bin(pos, oldkey, sizeof(oldkey)))
+		return -1;
+
+	ret = hmac_sha256_kdf(oldkey, sizeof(oldkey), "FT OLDKEY", NULL, 0,
+			      key, key_len);
+	os_memset(oldkey, 0, sizeof(oldkey));
+	return ret;
+}
+
+
 static int add_r0kh(struct hostapd_bss_config *bss, char *value)
 {
 	struct ft_remote_r0kh *r0kh;
@@ -1012,7 +1077,7 @@ static int add_r0kh(struct hostapd_bss_config *bss, char *value)
 	os_memcpy(r0kh->id, pos, r0kh->id_len);
 
 	pos = next;
-	if (hexstr2bin(pos, r0kh->key, sizeof(r0kh->key))) {
+	if (rkh_derive_key(pos, r0kh->key, sizeof(r0kh->key)) < 0) {
 		wpa_printf(MSG_ERROR, "Invalid R0KH key: '%s'", pos);
 		os_free(r0kh);
 		return -1;
@@ -1057,7 +1122,7 @@ static int add_r1kh(struct hostapd_bss_config *bss, char *value)
 	}
 
 	pos = next;
-	if (hexstr2bin(pos, r1kh->key, sizeof(r1kh->key))) {
+	if (rkh_derive_key(pos, r1kh->key, sizeof(r1kh->key)) < 0) {
 		wpa_printf(MSG_ERROR, "Invalid R1KH key: '%s'", pos);
 		os_free(r1kh);
 		return -1;
@@ -1068,7 +1133,7 @@ static int add_r1kh(struct hostapd_bss_config *bss, char *value)
 
 	return 0;
 }
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 
 #ifdef CONFIG_IEEE80211N
@@ -1085,6 +1150,10 @@ static int hostapd_config_ht_capab(struct hostapd_config *conf,
 		conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
 		conf->secondary_channel = 1;
 	}
+	if (os_strstr(capab, "[HT40+]") && os_strstr(capab, "[HT40-]")) {
+		conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
+		conf->ht40_plus_minus_allowed = 1;
+	}
 	if (os_strstr(capab, "[SMPS-STATIC]")) {
 		conf->ht_capab &= ~HT_CAP_INFO_SMPS_MASK;
 		conf->ht_capab |= HT_CAP_INFO_SMPS_STATIC;
@@ -1986,6 +2055,54 @@ static int parse_wpabuf_hex(int line, const char *name, struct wpabuf **buf,
 }
 
 
+#ifdef CONFIG_FILS
+static int parse_fils_realm(struct hostapd_bss_config *bss, const char *val)
+{
+	struct fils_realm *realm;
+	size_t len;
+
+	len = os_strlen(val);
+	realm = os_zalloc(sizeof(*realm) + len + 1);
+	if (!realm)
+		return -1;
+
+	os_memcpy(realm->realm, val, len);
+	if (fils_domain_name_hash(val, realm->hash) < 0) {
+		os_free(realm);
+		return -1;
+	}
+	dl_list_add_tail(&bss->fils_realms, &realm->list);
+
+	return 0;
+}
+#endif /* CONFIG_FILS */
+
+
+#ifdef EAP_SERVER
+static unsigned int parse_tls_flags(const char *val)
+{
+	unsigned int flags = 0;
+
+	if (os_strstr(val, "[ALLOW-SIGN-RSA-MD5]"))
+		flags |= TLS_CONN_ALLOW_SIGN_RSA_MD5;
+	if (os_strstr(val, "[DISABLE-TIME-CHECKS]"))
+		flags |= TLS_CONN_DISABLE_TIME_CHECKS;
+	if (os_strstr(val, "[DISABLE-TLSv1.0]"))
+		flags |= TLS_CONN_DISABLE_TLSv1_0;
+	if (os_strstr(val, "[DISABLE-TLSv1.1]"))
+		flags |= TLS_CONN_DISABLE_TLSv1_1;
+	if (os_strstr(val, "[DISABLE-TLSv1.2]"))
+		flags |= TLS_CONN_DISABLE_TLSv1_2;
+	if (os_strstr(val, "[SUITEB]"))
+		flags |= TLS_CONN_SUITEB;
+	if (os_strstr(val, "[SUITEB-NO-ECDH]"))
+		flags |= TLS_CONN_SUITEB_NO_ECDH | TLS_CONN_SUITEB;
+
+	return flags;
+}
+#endif /* EAP_SERVER */
+
+
 static int hostapd_config_fill(struct hostapd_config *conf,
 			       struct hostapd_bss_config *bss,
 			       const char *buf, char *pos, int line)
@@ -2001,20 +2118,21 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		os_strlcpy(bss->wds_bridge, pos, sizeof(bss->wds_bridge));
 	} else if (os_strcmp(buf, "driver") == 0) {
 		int j;
-		/* clear to get error below if setting is invalid */
-		conf->driver = NULL;
+		const struct wpa_driver_ops *driver = NULL;
+
 		for (j = 0; wpa_drivers[j]; j++) {
 			if (os_strcmp(pos, wpa_drivers[j]->name) == 0) {
-				conf->driver = wpa_drivers[j];
+				driver = wpa_drivers[j];
 				break;
 			}
 		}
-		if (conf->driver == NULL) {
+		if (!driver) {
 			wpa_printf(MSG_ERROR,
 				   "Line %d: invalid/unknown driver '%s'",
 				   line, pos);
 			return 1;
 		}
+		conf->driver = driver;
 	} else if (os_strcmp(buf, "driver_params") == 0) {
 		os_free(conf->driver_params);
 		conf->driver_params = os_strdup(pos);
@@ -2058,13 +2176,16 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 	} else if (os_strcmp(buf, "utf8_ssid") == 0) {
 		bss->ssid.utf8_ssid = atoi(pos) > 0;
 	} else if (os_strcmp(buf, "macaddr_acl") == 0) {
-		bss->macaddr_acl = atoi(pos);
-		if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED &&
-		    bss->macaddr_acl != DENY_UNLESS_ACCEPTED &&
-		    bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) {
+		enum macaddr_acl acl = atoi(pos);
+
+		if (acl != ACCEPT_UNLESS_DENIED &&
+		    acl != DENY_UNLESS_ACCEPTED &&
+		    acl != USE_EXTERNAL_RADIUS_AUTH) {
 			wpa_printf(MSG_ERROR, "Line %d: unknown macaddr_acl %d",
-				   line, bss->macaddr_acl);
+				   line, acl);
+			return 1;
 		}
+		bss->macaddr_acl = acl;
 	} else if (os_strcmp(buf, "accept_mac_file") == 0) {
 		if (hostapd_config_read_maclist(pos, &bss->accept_mac,
 						&bss->num_accept_mac)) {
@@ -2091,8 +2212,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->skip_inactivity_poll = atoi(pos);
 	} else if (os_strcmp(buf, "country_code") == 0) {
 		os_memcpy(conf->country, pos, 2);
-		/* FIX: make this configurable */
-		conf->country[2] = ' ';
+	} else if (os_strcmp(buf, "country3") == 0) {
+		conf->country[2] = strtol(pos, NULL, 16);
 	} else if (os_strcmp(buf, "ieee80211d") == 0) {
 		conf->ieee80211d = atoi(pos);
 	} else if (os_strcmp(buf, "ieee80211h") == 0) {
@@ -2100,13 +2221,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 	} else if (os_strcmp(buf, "ieee8021x") == 0) {
 		bss->ieee802_1x = atoi(pos);
 	} else if (os_strcmp(buf, "eapol_version") == 0) {
-		bss->eapol_version = atoi(pos);
-		if (bss->eapol_version < 1 || bss->eapol_version > 2) {
+		int eapol_version = atoi(pos);
+
+		if (eapol_version < 1 || eapol_version > 2) {
 			wpa_printf(MSG_ERROR,
 				   "Line %d: invalid EAPOL version (%d): '%s'.",
-				   line, bss->eapol_version, pos);
+				   line, eapol_version, pos);
 			return 1;
 		}
+		bss->eapol_version = eapol_version;
 		wpa_printf(MSG_DEBUG, "eapol_version=%d", bss->eapol_version);
 #ifdef EAP_SERVER
 	} else if (os_strcmp(buf, "eap_authenticator") == 0) {
@@ -2133,6 +2256,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->check_crl = atoi(pos);
 	} else if (os_strcmp(buf, "tls_session_lifetime") == 0) {
 		bss->tls_session_lifetime = atoi(pos);
+	} else if (os_strcmp(buf, "tls_flags") == 0) {
+		bss->tls_flags = parse_tls_flags(pos);
 	} else if (os_strcmp(buf, "ocsp_stapling_response") == 0) {
 		os_free(bss->ocsp_stapling_response);
 		bss->ocsp_stapling_response = os_strdup(pos);
@@ -2207,8 +2332,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 	} else if (os_strcmp(buf, "pwd_group") == 0) {
 		bss->pwd_group = atoi(pos);
 #endif /* EAP_SERVER_PWD */
+#ifdef CONFIG_ERP
 	} else if (os_strcmp(buf, "eap_server_erp") == 0) {
 		bss->eap_server_erp = atoi(pos);
+#endif /* CONFIG_ERP */
 #endif /* EAP_SERVER */
 	} else if (os_strcmp(buf, "eap_message") == 0) {
 		char *term;
@@ -2234,24 +2361,25 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		os_free(bss->erp_domain);
 		bss->erp_domain = os_strdup(pos);
 	} else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) {
-		bss->default_wep_key_len = atoi(pos);
-		if (bss->default_wep_key_len > 13) {
-			wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %lu (= %lu bits)",
-				   line,
-				   (unsigned long) bss->default_wep_key_len,
-				   (unsigned long)
-				   bss->default_wep_key_len * 8);
+		int val = atoi(pos);
+
+		if (val < 0 || val > 13) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid WEP key len %d (= %d bits)",
+				   line, val, val * 8);
 			return 1;
 		}
+		bss->default_wep_key_len = val;
 	} else if (os_strcmp(buf, "wep_key_len_unicast") == 0) {
-		bss->individual_wep_key_len = atoi(pos);
-		if (bss->individual_wep_key_len < 0 ||
-		    bss->individual_wep_key_len > 13) {
-			wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %d (= %d bits)",
-				   line, bss->individual_wep_key_len,
-				   bss->individual_wep_key_len * 8);
+		int val = atoi(pos);
+
+		if (val < 0 || val > 13) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid WEP key len %d (= %d bits)",
+				   line, val, val * 8);
 			return 1;
 		}
+		bss->individual_wep_key_len = val;
 	} else if (os_strcmp(buf, "wep_rekey_period") == 0) {
 		bss->wep_rekeying_period = atoi(pos);
 		if (bss->wep_rekeying_period < 0) {
@@ -2433,12 +2561,37 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->wpa = atoi(pos);
 	} else if (os_strcmp(buf, "wpa_group_rekey") == 0) {
 		bss->wpa_group_rekey = atoi(pos);
+		bss->wpa_group_rekey_set = 1;
 	} else if (os_strcmp(buf, "wpa_strict_rekey") == 0) {
 		bss->wpa_strict_rekey = atoi(pos);
 	} else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) {
 		bss->wpa_gmk_rekey = atoi(pos);
 	} else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) {
 		bss->wpa_ptk_rekey = atoi(pos);
+	} else if (os_strcmp(buf, "wpa_group_update_count") == 0) {
+		char *endp;
+		unsigned long val = strtoul(pos, &endp, 0);
+
+		if (*endp || val < 1 || val > (u32) -1) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid wpa_group_update_count=%lu; allowed range 1..4294967295",
+				   line, val);
+			return 1;
+		}
+		bss->wpa_group_update_count = (u32) val;
+	} else if (os_strcmp(buf, "wpa_pairwise_update_count") == 0) {
+		char *endp;
+		unsigned long val = strtoul(pos, &endp, 0);
+
+		if (*endp || val < 1 || val > (u32) -1) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid wpa_pairwise_update_count=%lu; allowed range 1..4294967295",
+				   line, val);
+			return 1;
+		}
+		bss->wpa_pairwise_update_count = (u32) val;
+	} else if (os_strcmp(buf, "wpa_disable_eapol_key_retries") == 0) {
+		bss->wpa_disable_eapol_key_retries = atoi(pos);
 	} else if (os_strcmp(buf, "wpa_passphrase") == 0) {
 		int len = os_strlen(pos);
 		if (len < 8 || len > 63) {
@@ -2497,7 +2650,7 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		if (bss->wpa_pairwise &
 		    (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) {
 			wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'",
-				   bss->wpa_pairwise, pos);
+				   line, pos);
 			return 1;
 		}
 	} else if (os_strcmp(buf, "rsn_pairwise") == 0) {
@@ -2507,7 +2660,21 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		if (bss->rsn_pairwise &
 		    (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) {
 			wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'",
-				   bss->rsn_pairwise, pos);
+				   line, pos);
+			return 1;
+		}
+	} else if (os_strcmp(buf, "group_cipher") == 0) {
+		bss->group_cipher = hostapd_config_parse_cipher(line, pos);
+		if (bss->group_cipher == -1 || bss->group_cipher == 0)
+			return 1;
+		if (bss->group_cipher != WPA_CIPHER_TKIP &&
+		    bss->group_cipher != WPA_CIPHER_CCMP &&
+		    bss->group_cipher != WPA_CIPHER_GCMP &&
+		    bss->group_cipher != WPA_CIPHER_GCMP_256 &&
+		    bss->group_cipher != WPA_CIPHER_CCMP_256) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: unsupported group cipher suite '%s'",
+				   line, pos);
 			return 1;
 		}
 #ifdef CONFIG_RSN_PREAUTH
@@ -2517,11 +2684,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		os_free(bss->rsn_preauth_interfaces);
 		bss->rsn_preauth_interfaces = os_strdup(pos);
 #endif /* CONFIG_RSN_PREAUTH */
-#ifdef CONFIG_PEERKEY
 	} else if (os_strcmp(buf, "peerkey") == 0) {
-		bss->peerkey = atoi(pos);
-#endif /* CONFIG_PEERKEY */
-#ifdef CONFIG_IEEE80211R
+		wpa_printf(MSG_INFO,
+			   "Line %d: Obsolete peerkey parameter ignored", line);
+#ifdef CONFIG_IEEE80211R_AP
 	} else if (os_strcmp(buf, "mobility_domain") == 0) {
 		if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN ||
 		    hexstr2bin(pos, bss->mobility_domain,
@@ -2543,6 +2709,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->r0_key_lifetime = atoi(pos);
 	} else if (os_strcmp(buf, "reassociation_deadline") == 0) {
 		bss->reassociation_deadline = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pos_timeout") == 0) {
+		bss->rkh_pos_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_neg_timeout") == 0) {
+		bss->rkh_neg_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pull_timeout") == 0) {
+		bss->rkh_pull_timeout = atoi(pos);
+	} else if (os_strcmp(buf, "rkh_pull_retries") == 0) {
+		bss->rkh_pull_retries = atoi(pos);
 	} else if (os_strcmp(buf, "r0kh") == 0) {
 		if (add_r0kh(bss, pos) < 0) {
 			wpa_printf(MSG_DEBUG, "Line %d: Invalid r0kh '%s'",
@@ -2559,7 +2733,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->pmk_r1_push = atoi(pos);
 	} else if (os_strcmp(buf, "ft_over_ds") == 0) {
 		bss->ft_over_ds = atoi(pos);
-#endif /* CONFIG_IEEE80211R */
+	} else if (os_strcmp(buf, "ft_psk_generate_local") == 0) {
+		bss->ft_psk_generate_local = atoi(pos);
+#endif /* CONFIG_IEEE80211R_AP */
 #ifndef CONFIG_NO_CTRL_IFACE
 	} else if (os_strcmp(buf, "ctrl_interface") == 0) {
 		os_free(bss->ctrl_interface);
@@ -2637,6 +2813,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 				   line, pos);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "acs_exclude_dfs") == 0) {
+		conf->acs_exclude_dfs = atoi(pos);
 	} else if (os_strcmp(buf, "channel") == 0) {
 		if (os_strcmp(pos, "acs_survey") == 0) {
 #ifndef CONFIG_ACS
@@ -2687,21 +2865,34 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		}
 #endif /* CONFIG_ACS */
 	} else if (os_strcmp(buf, "dtim_period") == 0) {
-		bss->dtim_period = atoi(pos);
-		if (bss->dtim_period < 1 || bss->dtim_period > 255) {
+		int val = atoi(pos);
+
+		if (val < 1 || val > 255) {
 			wpa_printf(MSG_ERROR, "Line %d: invalid dtim_period %d",
-				   line, bss->dtim_period);
+				   line, val);
 			return 1;
 		}
+		bss->dtim_period = val;
 	} else if (os_strcmp(buf, "bss_load_update_period") == 0) {
-		bss->bss_load_update_period = atoi(pos);
-		if (bss->bss_load_update_period < 0 ||
-		    bss->bss_load_update_period > 100) {
+		int val = atoi(pos);
+
+		if (val < 0 || val > 100) {
 			wpa_printf(MSG_ERROR,
 				   "Line %d: invalid bss_load_update_period %d",
-				   line, bss->bss_load_update_period);
+				   line, val);
 			return 1;
 		}
+		bss->bss_load_update_period = val;
+	} else if (os_strcmp(buf, "chan_util_avg_period") == 0) {
+		int val = atoi(pos);
+
+		if (val < 0) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid chan_util_avg_period",
+				   line);
+			return 1;
+		}
+		bss->chan_util_avg_period = val;
 	} else if (os_strcmp(buf, "rts_threshold") == 0) {
 		conf->rts_threshold = atoi(pos);
 		if (conf->rts_threshold < -1 || conf->rts_threshold > 65535) {
@@ -2741,6 +2932,40 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 				   line);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "beacon_rate") == 0) {
+		int val;
+
+		if (os_strncmp(pos, "ht:", 3) == 0) {
+			val = atoi(pos + 3);
+			if (val < 0 || val > 31) {
+				wpa_printf(MSG_ERROR,
+					   "Line %d: invalid beacon_rate HT-MCS %d",
+					   line, val);
+				return 1;
+			}
+			conf->rate_type = BEACON_RATE_HT;
+			conf->beacon_rate = val;
+		} else if (os_strncmp(pos, "vht:", 4) == 0) {
+			val = atoi(pos + 4);
+			if (val < 0 || val > 9) {
+				wpa_printf(MSG_ERROR,
+					   "Line %d: invalid beacon_rate VHT-MCS %d",
+					   line, val);
+				return 1;
+			}
+			conf->rate_type = BEACON_RATE_VHT;
+			conf->beacon_rate = val;
+		} else {
+			val = atoi(pos);
+			if (val < 10 || val > 10000) {
+				wpa_printf(MSG_ERROR,
+					   "Line %d: invalid legacy beacon_rate %d",
+					   line, val);
+				return 1;
+			}
+			conf->rate_type = BEACON_RATE_LEGACY;
+			conf->beacon_rate = val;
+		}
 	} else if (os_strcmp(buf, "preamble") == 0) {
 		if (atoi(pos))
 			conf->preamble = SHORT_PREAMBLE;
@@ -2898,6 +3123,24 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 	} else if (os_strcmp(buf, "use_sta_nsts") == 0) {
 		bss->use_sta_nsts = atoi(pos);
 #endif /* CONFIG_IEEE80211AC */
+#ifdef CONFIG_IEEE80211AX
+	} else if (os_strcmp(buf, "ieee80211ax") == 0) {
+		conf->ieee80211ax = atoi(pos);
+	} else if (os_strcmp(buf, "he_su_beamformer") == 0) {
+		conf->he_phy_capab.he_su_beamformer = atoi(pos);
+	} else if (os_strcmp(buf, "he_su_beamformee") == 0) {
+		conf->he_phy_capab.he_su_beamformee = atoi(pos);
+	} else if (os_strcmp(buf, "he_mu_beamformer") == 0) {
+		conf->he_phy_capab.he_mu_beamformer = atoi(pos);
+	} else if (os_strcmp(buf, "he_bss_color") == 0) {
+		conf->he_op.he_bss_color = atoi(pos);
+	} else if (os_strcmp(buf, "he_default_pe_duration") == 0) {
+		conf->he_op.he_default_pe_duration = atoi(pos);
+	} else if (os_strcmp(buf, "he_twt_required") == 0) {
+		conf->he_op.he_twt_required = atoi(pos);
+	} else if (os_strcmp(buf, "he_rts_threshold") == 0) {
+		conf->he_op.he_rts_threshold = atoi(pos);
+#endif /* CONFIG_IEEE80211AX */
 	} else if (os_strcmp(buf, "max_listen_interval") == 0) {
 		bss->max_listen_interval = atoi(pos);
 	} else if (os_strcmp(buf, "disable_pmksa_caching") == 0) {
@@ -2978,7 +3221,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		}
 	} else if (os_strcmp(buf, "ap_pin") == 0) {
 		os_free(bss->ap_pin);
-		bss->ap_pin = os_strdup(pos);
+		if (*pos == '\0')
+			bss->ap_pin = NULL;
+		else
+			bss->ap_pin = os_strdup(pos);
 	} else if (os_strcmp(buf, "skip_cred_build") == 0) {
 		bss->skip_cred_build = atoi(pos);
 	} else if (os_strcmp(buf, "extra_cred") == 0) {
@@ -3089,12 +3335,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->time_zone = os_strdup(pos);
 		if (bss->time_zone == NULL)
 			return 1;
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 	} else if (os_strcmp(buf, "wnm_sleep_mode") == 0) {
 		bss->wnm_sleep_mode = atoi(pos);
+	} else if (os_strcmp(buf, "wnm_sleep_mode_no_keys") == 0) {
+		bss->wnm_sleep_mode_no_keys = atoi(pos);
 	} else if (os_strcmp(buf, "bss_transition") == 0) {
 		bss->bss_transition = atoi(pos);
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 #ifdef CONFIG_INTERWORKING
 	} else if (os_strcmp(buf, "interworking") == 0) {
 		bss->interworking = atoi(pos);
@@ -3210,7 +3458,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		if (parse_anqp_elem(bss, pos, line) < 0)
 			return 1;
 	} else if (os_strcmp(buf, "gas_frag_limit") == 0) {
-		bss->gas_frag_limit = atoi(pos);
+		int val = atoi(pos);
+
+		if (val <= 0) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid gas_frag_limit '%s'",
+				   line, pos);
+			return 1;
+		}
+		bss->gas_frag_limit = val;
 	} else if (os_strcmp(buf, "gas_comeback_delay") == 0) {
 		bss->gas_comeback_delay = atoi(pos);
 	} else if (os_strcmp(buf, "qos_map_set") == 0) {
@@ -3309,6 +3565,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 #ifdef CONFIG_MBO
 	} else if (os_strcmp(buf, "mbo") == 0) {
 		bss->mbo_enabled = atoi(pos);
+	} else if (os_strcmp(buf, "mbo_cell_data_conn_pref") == 0) {
+		bss->mbo_cell_data_conn_pref = atoi(pos);
+	} else if (os_strcmp(buf, "oce") == 0) {
+		bss->oce = atoi(pos);
 #endif /* CONFIG_MBO */
 #ifdef CONFIG_TESTING_OPTIONS
 #define PARSE_TEST_PROBABILITY(_val)				\
@@ -3377,6 +3637,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 
 		wpabuf_free(bss->own_ie_override);
 		bss->own_ie_override = tmp;
+	} else if (os_strcmp(buf, "sae_reflection_attack") == 0) {
+		bss->sae_reflection_attack = atoi(pos);
+	} else if (os_strcmp(buf, "sae_commit_override") == 0) {
+		wpabuf_free(bss->sae_commit_override);
+		bss->sae_commit_override = wpabuf_parse_bin(pos);
+	} else if (os_strcmp(buf, "sae_password") == 0) {
+		os_free(bss->sae_password);
+		bss->sae_password = os_strdup(pos);
 #endif /* CONFIG_TESTING_OPTIONS */
 	} else if (os_strcmp(buf, "vendor_elements") == 0) {
 		if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos))
@@ -3386,6 +3654,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 			return 1;
 	} else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) {
 		bss->sae_anti_clogging_threshold = atoi(pos);
+	} else if (os_strcmp(buf, "sae_sync") == 0) {
+		bss->sae_sync = atoi(pos);
 	} else if (os_strcmp(buf, "sae_groups") == 0) {
 		if (hostapd_parse_intlist(&bss->sae_groups, pos)) {
 			wpa_printf(MSG_ERROR,
@@ -3393,6 +3663,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 				   line, pos);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "sae_require_mfp") == 0) {
+		bss->sae_require_mfp = atoi(pos);
 	} else if (os_strcmp(buf, "local_pwr_constraint") == 0) {
 		int val = atoi(pos);
 		if (val < 0 || val > 255) {
@@ -3478,19 +3750,114 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 	} else if (os_strcmp(buf, "lci") == 0) {
 		wpabuf_free(conf->lci);
 		conf->lci = wpabuf_parse_bin(pos);
+		if (conf->lci && wpabuf_len(conf->lci) == 0) {
+			wpabuf_free(conf->lci);
+			conf->lci = NULL;
+		}
 	} else if (os_strcmp(buf, "civic") == 0) {
 		wpabuf_free(conf->civic);
 		conf->civic = wpabuf_parse_bin(pos);
+		if (conf->civic && wpabuf_len(conf->civic) == 0) {
+			wpabuf_free(conf->civic);
+			conf->civic = NULL;
+		}
 	} else if (os_strcmp(buf, "rrm_neighbor_report") == 0) {
 		if (atoi(pos))
 			bss->radio_measurements[0] |=
 				WLAN_RRM_CAPS_NEIGHBOR_REPORT;
+	} else if (os_strcmp(buf, "rrm_beacon_report") == 0) {
+		if (atoi(pos))
+			bss->radio_measurements[0] |=
+				WLAN_RRM_CAPS_BEACON_REPORT_PASSIVE |
+				WLAN_RRM_CAPS_BEACON_REPORT_ACTIVE |
+				WLAN_RRM_CAPS_BEACON_REPORT_TABLE;
 	} else if (os_strcmp(buf, "gas_address3") == 0) {
 		bss->gas_address3 = atoi(pos);
+	} else if (os_strcmp(buf, "stationary_ap") == 0) {
+		conf->stationary_ap = atoi(pos);
 	} else if (os_strcmp(buf, "ftm_responder") == 0) {
 		bss->ftm_responder = atoi(pos);
 	} else if (os_strcmp(buf, "ftm_initiator") == 0) {
 		bss->ftm_initiator = atoi(pos);
+#ifdef CONFIG_FILS
+	} else if (os_strcmp(buf, "fils_cache_id") == 0) {
+		if (hexstr2bin(pos, bss->fils_cache_id, FILS_CACHE_ID_LEN)) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid fils_cache_id '%s'",
+				   line, pos);
+			return 1;
+		}
+		bss->fils_cache_id_set = 1;
+	} else if (os_strcmp(buf, "fils_realm") == 0) {
+		if (parse_fils_realm(bss, pos) < 0)
+			return 1;
+	} else if (os_strcmp(buf, "fils_dh_group") == 0) {
+		bss->fils_dh_group = atoi(pos);
+	} else if (os_strcmp(buf, "dhcp_server") == 0) {
+		if (hostapd_parse_ip_addr(pos, &bss->dhcp_server)) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid IP address '%s'",
+				   line, pos);
+			return 1;
+		}
+	} else if (os_strcmp(buf, "dhcp_rapid_commit_proxy") == 0) {
+		bss->dhcp_rapid_commit_proxy = atoi(pos);
+	} else if (os_strcmp(buf, "fils_hlp_wait_time") == 0) {
+		bss->fils_hlp_wait_time = atoi(pos);
+	} else if (os_strcmp(buf, "dhcp_server_port") == 0) {
+		bss->dhcp_server_port = atoi(pos);
+	} else if (os_strcmp(buf, "dhcp_relay_port") == 0) {
+		bss->dhcp_relay_port = atoi(pos);
+#endif /* CONFIG_FILS */
+	} else if (os_strcmp(buf, "multicast_to_unicast") == 0) {
+		bss->multicast_to_unicast = atoi(pos);
+	} else if (os_strcmp(buf, "broadcast_deauth") == 0) {
+		bss->broadcast_deauth = atoi(pos);
+#ifdef CONFIG_DPP
+	} else if (os_strcmp(buf, "dpp_connector") == 0) {
+		os_free(bss->dpp_connector);
+		bss->dpp_connector = os_strdup(pos);
+	} else if (os_strcmp(buf, "dpp_netaccesskey") == 0) {
+		if (parse_wpabuf_hex(line, buf, &bss->dpp_netaccesskey, pos))
+			return 1;
+	} else if (os_strcmp(buf, "dpp_netaccesskey_expiry") == 0) {
+		bss->dpp_netaccesskey_expiry = strtol(pos, NULL, 0);
+	} else if (os_strcmp(buf, "dpp_csign") == 0) {
+		if (parse_wpabuf_hex(line, buf, &bss->dpp_csign, pos))
+			return 1;
+#endif /* CONFIG_DPP */
+#ifdef CONFIG_OWE
+	} else if (os_strcmp(buf, "owe_transition_bssid") == 0) {
+		if (hwaddr_aton(pos, bss->owe_transition_bssid)) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid owe_transition_bssid",
+				   line);
+			return 1;
+		}
+	} else if (os_strcmp(buf, "owe_transition_ssid") == 0) {
+		size_t slen;
+		char *str = wpa_config_parse_string(pos, &slen);
+
+		if (!str || slen < 1 || slen > SSID_MAX_LEN) {
+			wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'",
+				   line, pos);
+			os_free(str);
+			return 1;
+		}
+		os_memcpy(bss->owe_transition_ssid, str, slen);
+		bss->owe_transition_ssid_len = slen;
+		os_free(str);
+	} else if (os_strcmp(buf, "owe_transition_ifname") == 0) {
+		os_strlcpy(bss->owe_transition_ifname, pos,
+			   sizeof(bss->owe_transition_ifname));
+	} else if (os_strcmp(buf, "owe_groups") == 0) {
+		if (hostapd_parse_intlist(&bss->owe_groups, pos)) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid owe_groups value '%s'",
+				   line, pos);
+			return 1;
+		}
+#endif /* CONFIG_OWE */
 	} else {
 		wpa_printf(MSG_ERROR,
 			   "Line %d: unknown configuration item '%s'",

+ 5 - 0
hostapd/config_file.h

@@ -13,5 +13,10 @@ struct hostapd_config * hostapd_config_read(const char *fname);
 int hostapd_set_iface(struct hostapd_config *conf,
 		      struct hostapd_bss_config *bss, const char *field,
 		      char *value);
+int hostapd_acl_comp(const void *a, const void *b);
+int hostapd_add_acl_maclist(struct mac_acl_entry **acl, int *num,
+			    int vlan_id, const u8 *addr);
+void hostapd_remove_acl_mac(struct mac_acl_entry **acl, int *num,
+			    const u8 *addr);
 
 #endif /* CONFIG_FILE_H */

File diff suppressed because it is too large
+ 646 - 148
hostapd/ctrl_iface.c


+ 33 - 3
hostapd/defconfig

@@ -50,9 +50,6 @@ CONFIG_IAPP=y
 # WPA2/IEEE 802.11i RSN pre-authentication
 CONFIG_RSN_PREAUTH=y
 
-# PeerKey handshake for Station to Station Link (IEEE 802.11e DLS)
-CONFIG_PEERKEY=y
-
 # IEEE 802.11w (management frame protection)
 CONFIG_IEEE80211W=y
 
@@ -157,6 +154,12 @@ CONFIG_IPV6=y
 # IEEE 802.11ac (Very High Throughput) support
 #CONFIG_IEEE80211AC=y
 
+# IEEE 802.11ax HE support
+# Note: This is experimental and work in progress. The definitions are still
+# subject to change and this should not be expected to interoperate with the
+# final IEEE 802.11ax version.
+#CONFIG_IEEE80211AX=y
+
 # Remove debugging code that is printing out debug messages to stdout.
 # This can be used to reduce the size of the hostapd considerably if debugging
 # code is not needed.
@@ -166,6 +169,9 @@ CONFIG_IPV6=y
 # Disabled by default.
 #CONFIG_DEBUG_FILE=y
 
+# Send debug messages to syslog instead of stdout
+#CONFIG_DEBUG_SYSLOG=y
+
 # Add support for sending all debug messages (regardless of debug verbosity)
 # to the Linux kernel tracing facility. This helps debug the entire stack by
 # making it easy to record everything happening from the driver up into the
@@ -256,6 +262,7 @@ CONFIG_IPV6=y
 # openssl = OpenSSL (default)
 # gnutls = GnuTLS
 # internal = Internal TLSv1 implementation (experimental)
+# linux = Linux kernel AF_ALG and internal TLSv1 implementation (experimental)
 # none = Empty template
 #CONFIG_TLS=openssl
 
@@ -268,6 +275,10 @@ CONFIG_IPV6=y
 # can be enabled to enable use of stronger crypto algorithms.
 #CONFIG_TLSV12=y
 
+# Select which ciphers to use by default with OpenSSL if the user does not
+# specify them.
+#CONFIG_TLS_DEFAULT_CIPHERS="DEFAULT:!EXP:!LOW"
+
 # If CONFIG_TLS=internal is used, additional library and include paths are
 # needed for LibTomMath. Alternatively, an integrated, minimal version of
 # LibTomMath can be used. See beginning of libtommath.c for details on benefits
@@ -343,3 +354,22 @@ CONFIG_IPV6=y
 # a client, from which a signature can be produced which can identify the model
 # of client device like "Nexus 6P" or "iPhone 5s".
 #CONFIG_TAXONOMY=y
+
+# Fast Initial Link Setup (FILS) (IEEE 802.11ai)
+# Note: This is an experimental and not yet complete implementation. This
+# should not be enabled for production use.
+#CONFIG_FILS=y
+# FILS shared key authentication with PFS
+#CONFIG_FILS_SK_PFS=y
+
+# Include internal line edit mode in hostapd_cli. This can be used to provide
+# limited command line editing and history support.
+#CONFIG_WPA_CLI_EDIT=y
+
+# Opportunistic Wireless Encryption (OWE)
+# Experimental implementation of draft-harkins-owe-07.txt
+#CONFIG_OWE=y
+
+# Override default value for the wpa_disable_eapol_key_retries configuration
+# parameter. See that parameter in hostapd.conf for more details.
+#CFLAGS += -DDEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES=1

+ 2 - 2
hostapd/hlr_auc_gw.c

@@ -1,6 +1,6 @@
 /*
  * HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator
- * Copyright (c) 2005-2007, 2012-2016, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2005-2007, 2012-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -973,7 +973,7 @@ static void usage(void)
 {
 	printf("HLR/AuC testing gateway for hostapd EAP-SIM/AKA "
 	       "database/authenticator\n"
-	       "Copyright (c) 2005-2016, Jouni Malinen <j@w1.fi>\n"
+	       "Copyright (c) 2005-2017, Jouni Malinen <j@w1.fi>\n"
 	       "\n"
 	       "usage:\n"
 	       "hlr_auc_gw [-hu] [-s<socket path>] [-g<triplet file>] "

+ 1 - 2
hostapd/hostapd.android.rc

@@ -9,8 +9,7 @@
 on post-fs-data
     mkdir /data/misc/wifi/hostapd 0770 wifi wifi
 
-service hostapd /system/bin/hostapd \
-        -e /data/misc/wifi/entropy.bin \
+service hostapd /vendor/bin/hostapd \
         /data/misc/wifi/hostapd.conf
     class main
     user wifi

+ 338 - 18
hostapd/hostapd.conf

@@ -98,8 +98,25 @@ ssid=testnetwork
 # Country code (ISO/IEC 3166-1). Used to set regulatory domain.
 # Set as needed to indicate country in which device is operating.
 # This can limit available channels and transmit power.
+# These two octets are used as the first two octets of the Country String
+# (dot11CountryString)
 #country_code=US
 
+# The third octet of the Country String (dot11CountryString)
+# This parameter is used to set the third octet of the country string.
+#
+# All environments of the current frequency band and country (default)
+#country3=0x20
+# Outdoor environment only
+#country3=0x4f
+# Indoor environment only
+#country3=0x49
+# Noncountry entity (country_code=XX)
+#country3=0x58
+# IEEE 802.11 standard Annex E table indication: 0x01 .. 0x1f
+# Annex E, Table E-4 (Global operating classes)
+#country3=0x04
+
 # Enable IEEE 802.11d. This advertises the country_code and the set of allowed
 # channels and transmit power levels based on the regulatory limits. The
 # country_code setting must be configured with the correct country for
@@ -182,6 +199,11 @@ channel=1
 #chanlist=100 104 108 112 116
 #chanlist=1 6 11-13
 
+# Exclude DFS channels from ACS
+# This option can be used to exclude all DFS channels from the ACS channel list
+# in cases where the driver supports DFS channels.
+#acs_exclude_dfs=1
+
 # Beacon interval in kus (1.024 ms) (default: 100; range 15..65535)
 beacon_int=100
 
@@ -227,6 +249,19 @@ fragm_threshold=-1
 #basic_rates=10 20 55 110
 #basic_rates=60 120 240
 
+# Beacon frame TX rate configuration
+# This sets the TX rate that is used to transmit Beacon frames. If this item is
+# not included, the driver default rate (likely lowest rate) is used.
+# Legacy (CCK/OFDM rates):
+#    beacon_rate=<legacy rate in 100 kbps>
+# HT:
+#    beacon_rate=ht:<HT MCS>
+# VHT:
+#    beacon_rate=vht:<VHT MCS>
+#
+# For example, beacon_rate=10 for 1 Mbps or beacon_rate=60 for 6 Mbps (OFDM).
+#beacon_rate=10
+
 # Short Preamble
 # This parameter can be used to enable optional use of short preamble for
 # frames sent at 2 Mbps, 5.5 Mbps, and 11 Mbps to improve network performance.
@@ -294,7 +329,7 @@ ignore_broadcast_ssid=0
 
 # TX queue parameters (EDCF / bursting)
 # tx_queue_<queue name>_<param>
-# queues: data0, data1, data2, data3, after_beacon, beacon
+# queues: data0, data1, data2, data3
 #		(data0 is the highest priority queue)
 # parameters:
 #   aifs: AIFS (default 2)
@@ -476,12 +511,38 @@ wmm_ac_vo_acm=0
 # Beacon and Probe Response frames.
 #bss_load_update_period=50
 
+# Channel utilization averaging period (in BUs)
+# This field is used to enable and configure channel utilization average
+# calculation with bss_load_update_period. This should be in multiples of
+# bss_load_update_period for more accurate calculation.
+#chan_util_avg_period=600
+
 # Fixed BSS Load value for testing purposes
 # This field can be used to configure hostapd to add a fixed BSS Load element
 # into Beacon and Probe Response frames for testing purposes. The format is
 # <station count>:<channel utilization>:<available admission capacity>
 #bss_load_test=12:80:20000
 
+# Multicast to unicast conversion
+# Request that the AP will do multicast-to-unicast conversion for ARP, IPv4, and
+# IPv6 frames (possibly within 802.1Q). If enabled, such frames are to be sent
+# to each station separately, with the DA replaced by their own MAC address
+# rather than the group address.
+#
+# Note that this may break certain expectations of the receiver, such as the
+# ability to drop unicast IP packets received within multicast L2 frames, or the
+# ability to not send ICMP destination unreachable messages for packets received
+# in L2 multicast (which is required, but the receiver can't tell the difference
+# if this new option is enabled).
+#
+# This also doesn't implement the 802.11 DMS (directed multicast service).
+#
+#multicast_to_unicast=0
+
+# Send broadcast Deauthentication frame on AP start/stop
+# Default: 1 (enabled)
+#broadcast_deauth=1
+
 ##### IEEE 802.11n related configuration ######################################
 
 # ieee80211n: Whether IEEE 802.11n (HT) is enabled
@@ -692,6 +753,47 @@ wmm_ac_vo_acm=0
 # setting use_sta_nsts=1.
 #use_sta_nsts=0
 
+##### IEEE 802.11ax related configuration #####################################
+
+#ieee80211ax: Whether IEEE 802.11ax (HE) is enabled
+# 0 = disabled (default)
+# 1 = enabled
+#ieee80211ax=1
+
+#he_su_beamformer: HE single user beamformer support
+# 0 = not supported (default)
+# 1 = supported
+#he_su_beamformer=1
+
+#he_su_beamformee: HE single user beamformee support
+# 0 = not supported (default)
+# 1 = supported
+#he_su_beamformee=1
+
+#he_mu_beamformer: HE multiple user beamformer support
+# 0 = not supported (default)
+# 1 = supported
+#he_mu_beamformer=1
+
+# he_bss_color: BSS color
+# 0 = no BSS color (default)
+# unsigned integer = BSS color
+#he_bss_color=0
+
+#he_default_pe_duration: The duration of PE field in an HE PPDU in us
+# Possible values are 0 us (default), 4 us, 8 us, 12 us, and 16 us
+#he_default_pe_duration=0
+
+#he_twt_required: Whether TWT is required
+# 0 = not required (default)
+# 1 = required
+#he_twt_required=0
+
+#he_rts_threshold: Duration of STA transmission
+# 0 = not set (default)
+# unsigned integer = duration in units of 16 us
+#he_rts_threshold=0
+
 ##### IEEE 802.1X-2004 related configuration ##################################
 
 # Require IEEE 802.1X authorization
@@ -835,7 +937,8 @@ eap_server=0
 # OpenSSL cipher string
 #
 # This is an OpenSSL specific configuration option for configuring the default
-# ciphers. If not set, "DEFAULT:!EXP:!LOW" is used as the default.
+# ciphers. If not set, the value configured at build time ("DEFAULT:!EXP:!LOW"
+# by default) is used.
 # See https://www.openssl.org/docs/apps/ciphers.html for OpenSSL documentation
 # on cipher suite configuration. This is applicable only if hostapd is built to
 # use OpenSSL.
@@ -1163,31 +1266,60 @@ wpa_passphrase=abcdefgh
 # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
 # entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be
 # added to enable SHA256-based stronger algorithms.
+# FILS-SHA256 = Fast Initial Link Setup with SHA256
+# FILS-SHA384 = Fast Initial Link Setup with SHA384
+# FT-FILS-SHA256 = FT and Fast Initial Link Setup with SHA256
+# FT-FILS-SHA384 = FT and Fast Initial Link Setup with SHA384
 # (dot11RSNAConfigAuthenticationSuitesTable)
 #wpa_key_mgmt=WPA-PSK WPA-EAP
 
 # Set of accepted cipher suites (encryption algorithms) for pairwise keys
 # (unicast packets). This is a space separated list of algorithms:
-# CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0]
-# TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
+# CCMP = AES in Counter mode with CBC-MAC (CCMP-128)
+# TKIP = Temporal Key Integrity Protocol
+# CCMP-256 = AES in Counter mode with CBC-MAC with 256-bit key
+# GCMP = Galois/counter mode protocol (GCMP-128)
+# GCMP-256 = Galois/counter mode protocol with 256-bit key
 # Group cipher suite (encryption algorithm for broadcast and multicast frames)
 # is automatically selected based on this configuration. If only CCMP is
 # allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise,
-# TKIP will be used as the group cipher.
+# TKIP will be used as the group cipher. The optional group_cipher parameter can
+# be used to override this automatic selection.
+#
 # (dot11RSNAConfigPairwiseCiphersTable)
 # Pairwise cipher for WPA (v1) (default: TKIP)
 wpa_pairwise=CCMP
 # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value)
 rsn_pairwise=CCMP
 
+# Optional override for automatic group cipher selection
+# This can be used to select a specific group cipher regardless of which
+# pairwise ciphers were enabled for WPA and RSN. It should be noted that
+# overriding the group cipher with an unexpected value can result in
+# interoperability issues and in general, this parameter is mainly used for
+# testing purposes.
+#group_cipher=CCMP
+
 # Time interval for rekeying GTK (broadcast/multicast encryption keys) in
 # seconds. (dot11RSNAConfigGroupRekeyTime)
-#wpa_group_rekey=600
+# This defaults to 86400 seconds (once per day) when using CCMP/GCMP as the
+# group cipher and 600 seconds (once per 10 minutes) when using TKIP as the
+# group cipher.
+#wpa_group_rekey=86400
 
 # Rekey GTK when any STA that possesses the current GTK is leaving the BSS.
 # (dot11RSNAConfigGroupRekeyStrict)
 #wpa_strict_rekey=1
 
+# The number of times EAPOL-Key Message 1/2 in the RSN Group Key Handshake is
+#retried per GTK Handshake attempt. (dot11RSNAConfigGroupUpdateCount)
+# This value should only be increased when stations are constantly
+# deauthenticated during GTK rekeying with the log message
+# "group key handshake failed...".
+# You should consider to also increase wpa_pairwise_update_count then.
+# Range 1..4294967295; default: 4
+#wpa_group_update_count=4
+
 # Time interval for rekeying GMK (master key used internally to generate GTKs
 # (in seconds).
 #wpa_gmk_rekey=86400
@@ -1196,6 +1328,36 @@ rsn_pairwise=CCMP
 # PTK to mitigate some attacks against TKIP deficiencies.
 #wpa_ptk_rekey=600
 
+# The number of times EAPOL-Key Message 1/4 and Message 3/4 in the RSN 4-Way
+# Handshake are retried per 4-Way Handshake attempt.
+# (dot11RSNAConfigPairwiseUpdateCount)
+# Range 1..4294967295; default: 4
+#wpa_pairwise_update_count=4
+
+# Workaround for key reinstallation attacks
+#
+# This parameter can be used to disable retransmission of EAPOL-Key frames that
+# are used to install keys (EAPOL-Key message 3/4 and group message 1/2). This
+# is similar to setting wpa_group_update_count=1 and
+# wpa_pairwise_update_count=1, but with no impact to message 1/4 and with
+# extended timeout on the response to avoid causing issues with stations that
+# may use aggressive power saving have very long time in replying to the
+# EAPOL-Key messages.
+#
+# This option can be used to work around key reinstallation attacks on the
+# station (supplicant) side in cases those station devices cannot be updated
+# for some reason. By removing the retransmissions the attacker cannot cause
+# key reinstallation with a delayed frame transmission. This is related to the
+# station side vulnerabilities CVE-2017-13077, CVE-2017-13078, CVE-2017-13079,
+# CVE-2017-13080, and CVE-2017-13081.
+#
+# This workaround might cause interoperability issues and reduced robustness of
+# key negotiation especially in environments with heavy traffic load due to the
+# number of attempts to perform the key exchange is reduced significantly. As
+# such, this workaround is disabled by default (unless overridden in build
+# configuration). To enable this, set the parameter to 1.
+#wpa_disable_eapol_key_retries=1
+
 # Enable IEEE 802.11i/RSN/WPA2 pre-authentication. This is used to speed up
 # roaming be pre-authenticating IEEE 802.1X/EAP part of the full RSN
 # authentication and key handshake before actually associating with a new AP.
@@ -1211,12 +1373,6 @@ rsn_pairwise=CCMP
 # one.
 #rsn_preauth_interfaces=eth0
 
-# peerkey: Whether PeerKey negotiation for direct links (IEEE 802.11e) is
-# allowed. This is only used with RSN/WPA2.
-# 0 = disabled (default)
-# 1 = enabled
-#peerkey=1
-
 # ieee80211w: Whether management frame protection (MFP) is enabled
 # 0 = disabled (default)
 # 1 = optional
@@ -1259,11 +1415,25 @@ rsn_pairwise=CCMP
 # 1 = enabled
 #okc=1
 
+# SAE password
+# This parameter can be used to set a password for SAE. By default, the
+# wpa_passphrase value is used if this separate parameter is not used, but
+# wpa_passphrase follows the WPA-PSK constraints (8..63 characters) even though
+# SAE passwords do not have such constraints. If the BSS enabled both SAE and
+# WPA-PSK and both values are set, SAE uses the sae_password value and WPA-PSK
+# uses the wpa_passphrase value.
+#sae_password=secret
+
 # SAE threshold for anti-clogging mechanism (dot11RSNASAEAntiCloggingThreshold)
 # This parameter defines how many open SAE instances can be in progress at the
 # same time before the anti-clogging mechanism is taken into use.
 #sae_anti_clogging_threshold=5
 
+# Maximum number of SAE synchronization errors (dot11RSNASAESync)
+# The offending SAe peer will be disconnected if more than this many
+# synchronization errors happen.
+#sae_sync=5
+
 # Enabled SAE finite cyclic groups
 # SAE implementation are required to support group 19 (ECC group defined over a
 # 256-bit prime order field). All groups that are supported by the
@@ -1273,6 +1443,75 @@ rsn_pairwise=CCMP
 # http://www.iana.org/assignments/ipsec-registry/ipsec-registry.xml#ipsec-registry-9
 #sae_groups=19 20 21 25 26
 
+# Require MFP for all associations using SAE
+# This parameter can be used to enforce negotiation of MFP for all associations
+# that negotiate use of SAE. This is used in cases where SAE-capable devices are
+# known to be MFP-capable and the BSS is configured with optional MFP
+# (ieee80211w=1) for legacy support. The non-SAE stations can connect without
+# MFP while SAE stations are required to negotiate MFP if sae_require_mfp=1.
+#sae_require_mfp=0
+
+# FILS Cache Identifier (16-bit value in hexdump format)
+#fils_cache_id=0011
+
+# FILS Realm Information
+# One or more FILS realms need to be configured when FILS is enabled. This list
+# of realms is used to define which realms (used in keyName-NAI by the client)
+# can be used with FILS shared key authentication for ERP.
+#fils_realm=example.com
+#fils_realm=example.org
+
+# FILS DH Group for PFS
+# 0 = PFS disabled with FILS shared key authentication (default)
+# 1-65535 DH Group to use for FILS PFS
+#fils_dh_group=0
+
+# OWE DH groups
+# OWE implementations are required to support group 19 (NIST P-256). All groups
+# that are supported by the implementation (e.g., groups 19, 20, and 21 when
+# using OpenSSL) are enabled by default. This configuration parameter can be
+# used to specify a limited set of allowed groups. The group values are listed
+# in the IANA registry:
+# http://www.iana.org/assignments/ipsec-registry/ipsec-registry.xml#ipsec-registry-10
+#owe_groups=19 20 21
+
+# OWE transition mode configuration
+# Pointer to the matching open/OWE BSS
+#owe_transition_bssid=<bssid>
+# SSID in same format as ssid2 described above.
+#owe_transition_ssid=<SSID>
+# Alternatively, OWE transition mode BSSID/SSID can be configured with a
+# reference to a BSS operated by this hostapd process.
+#owe_transition_ifname=<ifname>
+
+# DHCP server for FILS HLP
+# If configured, hostapd will act as a DHCP relay for all FILS HLP requests
+# that include a DHCPDISCOVER message and send them to the specific DHCP
+# server for processing. hostapd will then wait for a response from that server
+# before replying with (Re)Association Response frame that encapsulates this
+# DHCP response. own_ip_addr is used as the local address for the communication
+# with the DHCP server.
+#dhcp_server=127.0.0.1
+
+# DHCP server UDP port
+# Default: 67
+#dhcp_server_port=67
+
+# DHCP relay UDP port on the local device
+# Default: 67; 0 means not to bind any specific port
+#dhcp_relay_port=67
+
+# DHCP rapid commit proxy
+# If set to 1, this enables hostapd to act as a DHCP rapid commit proxy to
+# allow the rapid commit options (two message DHCP exchange) to be used with a
+# server that supports only the four message DHCP exchange. This is disabled by
+# default (= 0) and can be enabled by setting this to 1.
+#dhcp_rapid_commit_proxy=0
+
+# Wait time for FILS HLP (dot11HLPWaitTime) in TUs
+# default: 30 TUs (= 30.72 milliseconds)
+#fils_hlp_wait_time=30
+
 ##### IEEE 802.11r configuration ##############################################
 
 # Mobility Domain identifier (dot11FTMobilityDomainID, MDID)
@@ -1299,22 +1538,52 @@ rsn_pairwise=CCMP
 #reassociation_deadline=1000
 
 # List of R0KHs in the same Mobility Domain
-# format: <MAC address> <NAS Identifier> <128-bit key as hex string>
+# format: <MAC address> <NAS Identifier> <256-bit key as hex string>
 # This list is used to map R0KH-ID (NAS Identifier) to a destination MAC
 # address when requesting PMK-R1 key from the R0KH that the STA used during the
 # Initial Mobility Domain Association.
-#r0kh=02:01:02:03:04:05 r0kh-1.example.com 000102030405060708090a0b0c0d0e0f
-#r0kh=02:01:02:03:04:06 r0kh-2.example.com 00112233445566778899aabbccddeeff
+#r0kh=02:01:02:03:04:05 r0kh-1.example.com 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
+#r0kh=02:01:02:03:04:06 r0kh-2.example.com 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
 # And so on.. One line per R0KH.
+# Wildcard entry:
+# Upon receiving a response from R0KH, it will be added to this list, so
+# subsequent requests won't be broadcast. If R0KH does not reply, it will be
+# blacklisted.
+#r0kh=ff:ff:ff:ff:ff:ff * 00112233445566778899aabbccddeeff
 
 # List of R1KHs in the same Mobility Domain
-# format: <MAC address> <R1KH-ID> <128-bit key as hex string>
+# format: <MAC address> <R1KH-ID> <256-bit key as hex string>
 # This list is used to map R1KH-ID to a destination MAC address when sending
 # PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD
 # that can request PMK-R1 keys.
-#r1kh=02:01:02:03:04:05 02:11:22:33:44:55 000102030405060708090a0b0c0d0e0f
-#r1kh=02:01:02:03:04:06 02:11:22:33:44:66 00112233445566778899aabbccddeeff
+#r1kh=02:01:02:03:04:05 02:11:22:33:44:55 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
+#r1kh=02:01:02:03:04:06 02:11:22:33:44:66 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
 # And so on.. One line per R1KH.
+# Wildcard entry:
+# Upon receiving a request from an R1KH not yet known, it will be added to this
+# list and thus will receive push notifications.
+#r1kh=00:00:00:00:00:00 00:00:00:00:00:00 00112233445566778899aabbccddeeff
+
+# Timeout (seconds) for newly discovered R0KH/R1KH (see wildcard entries above)
+# Special values: 0 -> do not expire
+# Warning: do not cache implies no sequence number validation with wildcards
+#rkh_pos_timeout=86400 (default = 1 day)
+
+# Timeout (milliseconds) for requesting PMK-R1 from R0KH using PULL request
+# and number of retries.
+#rkh_pull_timeout=1000 (default = 1 second)
+#rkh_pull_retries=4 (default)
+
+# Timeout (seconds) for non replying R0KH (see wildcard entries above)
+# Special values: 0 -> do not cache
+# default: 60 seconds
+#rkh_neg_timeout=60
+
+# Note: The R0KH/R1KH keys used to be 128-bit in length before the message
+# format was changed. That shorter key length is still supported for backwards
+# compatibility of the configuration files. If such a shorter key is used, a
+# 256-bit key is derived from it. For new deployments, configuring the 256-bit
+# key is recommended.
 
 # Whether PMK-R1 push is enabled at R0KH
 # 0 = do not push PMK-R1 to all configured R1KHs (default)
@@ -1326,6 +1595,14 @@ rsn_pairwise=CCMP
 # 1 = FT-over-DS enabled (default)
 #ft_over_ds=1
 
+# Whether to generate FT response locally for PSK networks
+# This avoids use of PMK-R1 push/pull from other APs with FT-PSK networks as
+# the required information (PSK and other session data) is already locally
+# available.
+# 0 = disabled (default)
+# 1 = enabled
+#ft_psk_generate_local=0
+
 ##### Neighbor table ##########################################################
 # Maximum number of entries kept in AP table (either for neigbor table or for
 # detecting Overlapping Legacy BSS Condition). The oldest entry will be
@@ -1596,6 +1873,18 @@ rsn_pairwise=CCMP
 # 1 = enabled (allow stations to use WNM-Sleep Mode)
 #wnm_sleep_mode=1
 
+# WNM-Sleep Mode GTK/IGTK workaround
+# Normally, WNM-Sleep Mode exit with management frame protection negotiated
+# would result in the current GTK/IGTK getting added into the WNM-Sleep Mode
+# Response frame. Some station implementations may have a vulnerability that
+# results in GTK/IGTK reinstallation based on this frame being replayed. This
+# configuration parameter can be used to disable that behavior and use EAPOL-Key
+# frames for GTK/IGTK update instead. This would likely be only used with
+# wpa_disable_eapol_key_retries=1 that enables a workaround for similar issues
+# with EAPOL-Key. This is related to station side vulnerabilities CVE-2017-13087
+# and CVE-2017-13088. To enable this AP-side workaround, set the parameter to 1.
+#wnm_sleep_mode_no_keys=0
+
 # BSS Transition Management
 # 0 = disabled (default)
 # 1 = enabled
@@ -1879,6 +2168,28 @@ rsn_pairwise=CCMP
 #
 #osu_server_uri=...
 
+##### Multiband Operation (MBO) ###############################################
+#
+# MBO enabled
+# 0 = disabled (default)
+# 1 = enabled
+#mbo=1
+#
+# Cellular data connection preference
+# 0 = Excluded - AP does not want STA to use the cellular data connection
+# 1 = AP prefers the STA not to use cellular data connection
+# 255 = AP prefers the STA to use cellular data connection
+#mbo_cell_data_conn_pref=1
+
+##### Optimized Connectivity Experience (OCE) #################################
+#
+# Enable OCE specific features (bitmap)
+# BIT(0) - Reserved
+# Set BIT(1) (= 2) to enable OCE in STA-CFON mode
+# Set BIT(2) (= 4) to enable OCE in AP mode
+# Default is 0 = OCE disabled
+#oce=0
+
 ##### Fast Session Transfer (FST) support #####################################
 #
 # The options in this section are only available when the build configuration
@@ -1916,6 +2227,9 @@ rsn_pairwise=CCMP
 # Enable neighbor report via radio measurements
 #rrm_neighbor_report=1
 
+# Enable beacon report via radio measurements
+#rrm_beacon_report=1
+
 # Publish fine timing measurement (FTM) responder functionality
 # This parameter only controls publishing via Extended Capabilities element.
 # Actual functionality is managed outside hostapd.
@@ -1925,6 +2239,12 @@ rsn_pairwise=CCMP
 # This parameter only controls publishing via Extended Capabilities element.
 # Actual functionality is managed outside hostapd.
 #ftm_initiator=0
+#
+# Stationary AP config indicates that the AP doesn't move hence location data
+# can be considered as always up to date. If configured, LCI data will be sent
+# as a radio measurement even if the request doesn't contain a max age element
+# that allows sending of such data. Default: 0.
+#stationary_ap=0
 
 ##### TESTING OPTIONS #########################################################
 #

+ 409 - 121
hostapd/hostapd_cli.c

@@ -1,6 +1,6 @@
 /*
  * hostapd - command line interface for hostapd daemon
- * Copyright (c) 2004-2016, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -21,7 +21,7 @@
 
 static const char *const hostapd_cli_version =
 "hostapd_cli v" VERSION_STR "\n"
-"Copyright (c) 2004-2016, Jouni Malinen <j@w1.fi> and contributors";
+"Copyright (c) 2004-2017, Jouni Malinen <j@w1.fi> and contributors";
 
 static struct wpa_ctrl *ctrl_conn;
 static int hostapd_cli_quit = 0;
@@ -45,6 +45,8 @@ static DEFINE_DL_LIST(stations); /* struct cli_txt_entry */
 static void print_help(FILE *stream, const char *cmd);
 static char ** list_cmd_list(void);
 static void hostapd_cli_receive(int sock, void *eloop_ctx, void *sock_ctx);
+static void update_stations(struct wpa_ctrl *ctrl);
+static void cli_event(const char *str);
 
 
 static void usage(void)
@@ -147,13 +149,45 @@ static void hostapd_cli_close_connection(void)
 }
 
 
+static int hostapd_cli_reconnect(const char *ifname)
+{
+	char *next_ctrl_ifname;
+
+	hostapd_cli_close_connection();
+
+	if (!ifname)
+		return -1;
+
+	next_ctrl_ifname = os_strdup(ifname);
+	os_free(ctrl_ifname);
+	ctrl_ifname = next_ctrl_ifname;
+	if (!ctrl_ifname)
+		return -1;
+
+	ctrl_conn = hostapd_cli_open_connection(ctrl_ifname);
+	if (!ctrl_conn)
+		return -1;
+	if (!interactive && !action_file)
+		return 0;
+	if (wpa_ctrl_attach(ctrl_conn) == 0) {
+		hostapd_cli_attached = 1;
+		register_event_handler(ctrl_conn);
+		update_stations(ctrl_conn);
+	} else {
+		printf("Warning: Failed to attach to hostapd.\n");
+	}
+	return 0;
+}
+
+
 static void hostapd_cli_msg_cb(char *msg, size_t len)
 {
+	cli_event(msg);
 	printf("%s\n", msg);
 }
 
 
-static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd, int print)
+static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, int print)
 {
 	char buf[4096];
 	size_t len;
@@ -181,7 +215,7 @@ static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd, int print)
 }
 
 
-static inline int wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd)
+static inline int wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd)
 {
 	return _wpa_ctrl_command(ctrl, cmd, 1);
 }
@@ -286,6 +320,21 @@ static int hostapd_cli_cmd_sta(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static char ** hostapd_complete_stations(const char *str, int pos)
+{
+	int arg = get_cmd_arg_num(str, pos);
+	char **res = NULL;
+
+	switch (arg) {
+	case 1:
+		res = cli_txt_list_array(&stations);
+		break;
+	}
+
+	return res;
+}
+
+
 static int hostapd_cli_cmd_new_sta(struct wpa_ctrl *ctrl, int argc,
 				   char *argv[])
 {
@@ -318,21 +367,6 @@ static int hostapd_cli_cmd_deauthenticate(struct wpa_ctrl *ctrl, int argc,
 }
 
 
-static char ** hostapd_complete_deauthenticate(const char *str, int pos)
-{
-	int arg = get_cmd_arg_num(str, pos);
-	char **res = NULL;
-
-	switch (arg) {
-	case 1:
-		res = cli_txt_list_array(&stations);
-		break;
-	}
-
-	return res;
-}
-
-
 static int hostapd_cli_cmd_disassociate(struct wpa_ctrl *ctrl, int argc,
 					char *argv[])
 {
@@ -351,21 +385,6 @@ static int hostapd_cli_cmd_disassociate(struct wpa_ctrl *ctrl, int argc,
 }
 
 
-static char ** hostapd_complete_disassociate(const char *str, int pos)
-{
-	int arg = get_cmd_arg_num(str, pos);
-	char **res = NULL;
-
-	switch (arg) {
-	case 1:
-		res = cli_txt_list_array(&stations);
-		break;
-	}
-
-	return res;
-}
-
-
 #ifdef CONFIG_TAXONOMY
 static int hostapd_cli_cmd_signature(struct wpa_ctrl *ctrl, int argc,
 				     char *argv[])
@@ -701,8 +720,8 @@ static int hostapd_cli_cmd_get_config(struct wpa_ctrl *ctrl, int argc,
 }
 
 
-static int wpa_ctrl_command_sta(struct wpa_ctrl *ctrl, char *cmd,
-				char *addr, size_t addr_len)
+static int wpa_ctrl_command_sta(struct wpa_ctrl *ctrl, const char *cmd,
+				char *addr, size_t addr_len, int print)
 {
 	char buf[4096], *pos;
 	size_t len;
@@ -726,7 +745,8 @@ static int wpa_ctrl_command_sta(struct wpa_ctrl *ctrl, char *cmd,
 	buf[len] = '\0';
 	if (memcmp(buf, "FAIL", 4) == 0)
 		return -1;
-	printf("%s", buf);
+	if (print)
+		printf("%s", buf);
 
 	pos = buf;
 	while (*pos != '\0' && *pos != '\n')
@@ -742,16 +762,33 @@ static int hostapd_cli_cmd_all_sta(struct wpa_ctrl *ctrl, int argc,
 {
 	char addr[32], cmd[64];
 
-	if (wpa_ctrl_command_sta(ctrl, "STA-FIRST", addr, sizeof(addr)))
+	if (wpa_ctrl_command_sta(ctrl, "STA-FIRST", addr, sizeof(addr), 1))
 		return 0;
 	do {
 		snprintf(cmd, sizeof(cmd), "STA-NEXT %s", addr);
-	} while (wpa_ctrl_command_sta(ctrl, cmd, addr, sizeof(addr)) == 0);
+	} while (wpa_ctrl_command_sta(ctrl, cmd, addr, sizeof(addr), 1) == 0);
 
 	return -1;
 }
 
 
+static int hostapd_cli_cmd_list_sta(struct wpa_ctrl *ctrl, int argc,
+				    char *argv[])
+{
+	char addr[32], cmd[64];
+
+	if (wpa_ctrl_command_sta(ctrl, "STA-FIRST", addr, sizeof(addr), 0))
+		return 0;
+	do {
+		if (os_strcmp(addr, "") != 0)
+			printf("%s\n", addr);
+		os_snprintf(cmd, sizeof(cmd), "STA-NEXT %s", addr);
+	} while (wpa_ctrl_command_sta(ctrl, cmd, addr, sizeof(addr), 0) == 0);
+
+	return 0;
+}
+
+
 static int hostapd_cli_cmd_help(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
 	print_help(stdout, argc > 0 ? argv[0] : NULL);
@@ -888,6 +925,25 @@ static int hostapd_cli_cmd_level(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static void update_stations(struct wpa_ctrl *ctrl)
+{
+	char addr[32], cmd[64];
+
+	if (!ctrl || !interactive)
+		return;
+
+	cli_txt_list_flush(&stations);
+
+	if (wpa_ctrl_command_sta(ctrl, "STA-FIRST", addr, sizeof(addr), 0))
+		return;
+	do {
+		if (os_strcmp(addr, "") != 0)
+			cli_txt_list_add(&stations, addr);
+		os_snprintf(cmd, sizeof(cmd), "STA-NEXT %s", addr);
+	} while (wpa_ctrl_command_sta(ctrl, cmd, addr, sizeof(addr), 0) == 0);
+}
+
+
 static void hostapd_cli_get_interfaces(struct wpa_ctrl *ctrl,
 				       struct dl_list *interfaces)
 {
@@ -940,23 +996,7 @@ static int hostapd_cli_cmd_interface(struct wpa_ctrl *ctrl, int argc,
 		hostapd_cli_list_interfaces(ctrl);
 		return 0;
 	}
-
-	hostapd_cli_close_connection();
-	os_free(ctrl_ifname);
-	ctrl_ifname = os_strdup(argv[0]);
-	if (ctrl_ifname == NULL)
-		return -1;
-
-	if (hostapd_cli_open_connection(ctrl_ifname)) {
-		printf("Connected to interface '%s.\n", ctrl_ifname);
-		if (wpa_ctrl_attach(ctrl_conn) == 0) {
-			hostapd_cli_attached = 1;
-			register_event_handler(ctrl_conn);
-		} else {
-			printf("Warning: Failed to attach to "
-			       "hostapd.\n");
-		}
-	} else {
+	if (hostapd_cli_reconnect(argv[0]) != 0) {
 		printf("Could not connect to interface '%s' - re-trying\n",
 			ctrl_ifname);
 	}
@@ -984,7 +1024,7 @@ static char ** hostapd_complete_interface(const char *str, int pos)
 
 static int hostapd_cli_cmd_set(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
-	char cmd[256];
+	char cmd[2048];
 	int res;
 
 	if (argc != 2) {
@@ -1002,6 +1042,44 @@ static int hostapd_cli_cmd_set(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static char ** hostapd_complete_set(const char *str, int pos)
+{
+	int arg = get_cmd_arg_num(str, pos);
+	const char *fields[] = {
+#ifdef CONFIG_WPS_TESTING
+		"wps_version_number", "wps_testing_dummy_cred",
+		"wps_corrupt_pkhash",
+#endif /* CONFIG_WPS_TESTING */
+#ifdef CONFIG_INTERWORKING
+		"gas_frag_limit",
+#endif /* CONFIG_INTERWORKING */
+#ifdef CONFIG_TESTING_OPTIONS
+		"ext_mgmt_frame_handling", "ext_eapol_frame_io",
+#endif /* CONFIG_TESTING_OPTIONS */
+#ifdef CONFIG_MBO
+		"mbo_assoc_disallow",
+#endif /* CONFIG_MBO */
+		"deny_mac_file", "accept_mac_file",
+	};
+	int i, num_fields = ARRAY_SIZE(fields);
+
+	if (arg == 1) {
+		char **res;
+
+		res = os_calloc(num_fields + 1, sizeof(char *));
+		if (!res)
+			return NULL;
+		for (i = 0; i < num_fields; i++) {
+			res[i] = os_strdup(fields[i]);
+			if (!res[i])
+				return res;
+		}
+		return res;
+	}
+	return NULL;
+}
+
+
 static int hostapd_cli_cmd_get(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
 	char cmd[256];
@@ -1022,6 +1100,31 @@ static int hostapd_cli_cmd_get(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static char ** hostapd_complete_get(const char *str, int pos)
+{
+	int arg = get_cmd_arg_num(str, pos);
+	const char *fields[] = {
+		"version", "tls_library",
+	};
+	int i, num_fields = ARRAY_SIZE(fields);
+
+	if (arg == 1) {
+		char **res;
+
+		res = os_calloc(num_fields + 1, sizeof(char *));
+		if (!res)
+			return NULL;
+		for (i = 0; i < num_fields; i++) {
+			res[i] = os_strdup(fields[i]);
+			if (!res[i])
+				return res;
+		}
+		return res;
+	}
+	return NULL;
+}
+
+
 #ifdef CONFIG_FST
 static int hostapd_cli_cmd_fst(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
@@ -1185,14 +1288,14 @@ static int hostapd_cli_cmd_set_neighbor(struct wpa_ctrl *ctrl, int argc,
 	char cmd[2048];
 	int res;
 
-	if (argc < 3 || argc > 5) {
-		printf("Invalid set_neighbor command: needs 3-5 arguments\n");
+	if (argc < 3 || argc > 6) {
+		printf("Invalid set_neighbor command: needs 3-6 arguments\n");
 		return -1;
 	}
 
-	res = os_snprintf(cmd, sizeof(cmd), "SET_NEIGHBOR %s %s %s %s %s",
+	res = os_snprintf(cmd, sizeof(cmd), "SET_NEIGHBOR %s %s %s %s %s %s",
 			  argv[0], argv[1], argv[2], argc >= 4 ? argv[3] : "",
-			  argc == 5 ? argv[4] : "");
+			  argc >= 5 ? argv[4] : "", argc == 6 ? argv[5] : "");
 	if (os_snprintf_error(sizeof(cmd), res)) {
 		printf("Too long SET_NEIGHBOR command.\n");
 		return -1;
@@ -1261,6 +1364,122 @@ static int hostapd_cli_cmd_driver_flags(struct wpa_ctrl *ctrl, int argc,
 }
 
 
+#ifdef CONFIG_DPP
+
+static int hostapd_cli_cmd_dpp_qr_code(struct wpa_ctrl *ctrl, int argc,
+				       char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_QR_CODE", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_bootstrap_gen(struct wpa_ctrl *ctrl, int argc,
+					     char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_BOOTSTRAP_GEN", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_bootstrap_remove(struct wpa_ctrl *ctrl, int argc,
+						char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_BOOTSTRAP_REMOVE", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_bootstrap_get_uri(struct wpa_ctrl *ctrl,
+						 int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_BOOTSTRAP_GET_URI", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_bootstrap_info(struct wpa_ctrl *ctrl, int argc,
+					      char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_BOOTSTRAP_INFO", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_auth_init(struct wpa_ctrl *ctrl, int argc,
+					 char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_AUTH_INIT", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_listen(struct wpa_ctrl *ctrl, int argc,
+				      char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_LISTEN", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_stop_listen(struct wpa_ctrl *ctrl, int argc,
+				       char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "DPP_STOP_LISTEN");
+}
+
+
+static int hostapd_cli_cmd_dpp_configurator_add(struct wpa_ctrl *ctrl, int argc,
+						char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_CONFIGURATOR_ADD", 0, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_configurator_remove(struct wpa_ctrl *ctrl,
+						   int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_CONFIGURATOR_REMOVE", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_configurator_get_key(struct wpa_ctrl *ctrl,
+						    int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_CONFIGURATOR_GET_KEY", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_pkex_add(struct wpa_ctrl *ctrl, int argc,
+					char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_PKEX_ADD", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_dpp_pkex_remove(struct wpa_ctrl *ctrl, int argc,
+					   char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DPP_PKEX_REMOVE", 1, argc, argv);
+}
+
+#endif /* CONFIG_DPP */
+
+
+static int hostapd_cli_cmd_accept_macacl(struct wpa_ctrl *ctrl, int argc,
+					 char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "ACCEPT_ACL", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_deny_macacl(struct wpa_ctrl *ctrl, int argc,
+				       char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "DENY_ACL", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_poll_sta(struct wpa_ctrl *ctrl, int argc,
+				    char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "POLL_STA", 1, argc, argv);
+}
+
+
 struct hostapd_cli_cmd {
 	const char *cmd;
 	int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);
@@ -1273,26 +1492,30 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
 	  "= pings hostapd" },
 	{ "mib", hostapd_cli_cmd_mib, NULL,
 	  "= get MIB variables (dot1x, dot11, radius)" },
-	{ "relog", hostapd_cli_cmd_relog, NULL, NULL },
-	{ "status", hostapd_cli_cmd_status, NULL, NULL },
-	{ "sta", hostapd_cli_cmd_sta, NULL,
+	{ "relog", hostapd_cli_cmd_relog, NULL,
+	  "= reload/truncate debug log output file" },
+	{ "status", hostapd_cli_cmd_status, NULL,
+	  "= show interface status info" },
+	{ "sta", hostapd_cli_cmd_sta, hostapd_complete_stations,
 	  "<addr> = get MIB variables for one station" },
 	{ "all_sta", hostapd_cli_cmd_all_sta, NULL,
 	   "= get MIB variables for all stations" },
+	{ "list_sta", hostapd_cli_cmd_list_sta, NULL,
+	   "= list all stations" },
 	{ "new_sta", hostapd_cli_cmd_new_sta, NULL,
 	  "<addr> = add a new station" },
 	{ "deauthenticate", hostapd_cli_cmd_deauthenticate,
-	  hostapd_complete_deauthenticate,
+	  hostapd_complete_stations,
 	  "<addr> = deauthenticate a station" },
 	{ "disassociate", hostapd_cli_cmd_disassociate,
-	  hostapd_complete_disassociate,
+	  hostapd_complete_stations,
 	  "<addr> = disassociate a station" },
 #ifdef CONFIG_TAXONOMY
-	{ "signature", hostapd_cli_cmd_signature, NULL,
+	{ "signature", hostapd_cli_cmd_signature, hostapd_complete_stations,
 	  "<addr> = get taxonomy signature for a station" },
 #endif /* CONFIG_TAXONOMY */
 #ifdef CONFIG_IEEE80211W
-	{ "sa_query", hostapd_cli_cmd_sa_query, NULL,
+	{ "sa_query", hostapd_cli_cmd_sa_query, hostapd_complete_stations,
 	  "<addr> = send SA Query to a station" },
 #endif /* CONFIG_IEEE80211W */
 #ifdef CONFIG_WPS
@@ -1321,9 +1544,12 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
 	{ "wps_get_status", hostapd_cli_cmd_wps_get_status, NULL,
 	  "= show current WPS status" },
 #endif /* CONFIG_WPS */
-	{ "disassoc_imminent", hostapd_cli_cmd_disassoc_imminent, NULL, NULL },
-	{ "ess_disassoc", hostapd_cli_cmd_ess_disassoc, NULL, NULL },
-	{ "bss_tm_req", hostapd_cli_cmd_bss_tm_req, NULL, NULL },
+	{ "disassoc_imminent", hostapd_cli_cmd_disassoc_imminent, NULL,
+	  "= send Disassociation Imminent notification" },
+	{ "ess_disassoc", hostapd_cli_cmd_ess_disassoc, NULL,
+	  "= send ESS Dissassociation Imminent notification" },
+	{ "bss_tm_req", hostapd_cli_cmd_bss_tm_req, NULL,
+	  "= send BSS Transition Management Request" },
 	{ "get_config", hostapd_cli_cmd_get_config, NULL,
 	  "= show current configuration" },
 	{ "help", hostapd_cli_cmd_help, hostapd_cli_complete_help,
@@ -1331,35 +1557,100 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
 	{ "interface", hostapd_cli_cmd_interface, hostapd_complete_interface,
 	  "[ifname] = show interfaces/select interface" },
 #ifdef CONFIG_FST
-	{ "fst", hostapd_cli_cmd_fst, NULL, NULL },
+	{ "fst", hostapd_cli_cmd_fst, NULL,
+	  "<params...> = send FST-MANAGER control interface command" },
 #endif /* CONFIG_FST */
-	{ "raw", hostapd_cli_cmd_raw, NULL, NULL },
+	{ "raw", hostapd_cli_cmd_raw, NULL,
+	  "<params..> = send unprocessed command" },
 	{ "level", hostapd_cli_cmd_level, NULL,
 	  "<debug level> = change debug level" },
 	{ "license", hostapd_cli_cmd_license, NULL,
 	  "= show full hostapd_cli license" },
 	{ "quit", hostapd_cli_cmd_quit, NULL,
 	  "= exit hostapd_cli" },
-	{ "set", hostapd_cli_cmd_set, NULL, NULL },
-	{ "get", hostapd_cli_cmd_get, NULL, NULL },
-	{ "set_qos_map_set", hostapd_cli_cmd_set_qos_map_set, NULL, NULL },
-	{ "send_qos_map_conf", hostapd_cli_cmd_send_qos_map_conf, NULL, NULL },
-	{ "chan_switch", hostapd_cli_cmd_chan_switch, NULL, NULL },
-	{ "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif, NULL, NULL },
-	{ "hs20_deauth_req", hostapd_cli_cmd_hs20_deauth_req, NULL, NULL },
-	{ "vendor", hostapd_cli_cmd_vendor, NULL, NULL },
-	{ "enable", hostapd_cli_cmd_enable, NULL, NULL },
-	{ "reload", hostapd_cli_cmd_reload, NULL, NULL },
-	{ "disable", hostapd_cli_cmd_disable, NULL, NULL },
-	{ "erp_flush", hostapd_cli_cmd_erp_flush, NULL, NULL },
-	{ "log_level", hostapd_cli_cmd_log_level, NULL, NULL },
-	{ "pmksa", hostapd_cli_cmd_pmksa, NULL, NULL },
-	{ "pmksa_flush", hostapd_cli_cmd_pmksa_flush, NULL, NULL },
-	{ "set_neighbor", hostapd_cli_cmd_set_neighbor, NULL, NULL },
-	{ "remove_neighbor", hostapd_cli_cmd_remove_neighbor, NULL, NULL },
-	{ "req_lci", hostapd_cli_cmd_req_lci, NULL, NULL },
-	{ "req_range", hostapd_cli_cmd_req_range, NULL, NULL },
-	{ "driver_flags", hostapd_cli_cmd_driver_flags, NULL, NULL },
+	{ "set", hostapd_cli_cmd_set, hostapd_complete_set,
+	  "<name> <value> = set runtime variables" },
+	{ "get", hostapd_cli_cmd_get, hostapd_complete_get,
+	  "<name> = get runtime info" },
+	{ "set_qos_map_set", hostapd_cli_cmd_set_qos_map_set, NULL,
+	  "<arg,arg,...> = set QoS Map set element" },
+	{ "send_qos_map_conf", hostapd_cli_cmd_send_qos_map_conf,
+	  hostapd_complete_stations,
+	  "<addr> = send QoS Map Configure frame" },
+	{ "chan_switch", hostapd_cli_cmd_chan_switch, NULL,
+	  "<cs_count> <freq> [sec_channel_offset=] [center_freq1=]\n"
+	  "  [center_freq2=] [bandwidth=] [blocktx] [ht|vht]\n"
+	  "  = initiate channel switch announcement" },
+	{ "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif, NULL,
+	  "<addr> <url>\n"
+	  "  = send WNM-Notification Subscription Remediation Request" },
+	{ "hs20_deauth_req", hostapd_cli_cmd_hs20_deauth_req, NULL,
+	  "<addr> <code (0/1)> <Re-auth-Delay(sec)> [url]\n"
+	  "  = send WNM-Notification imminent deauthentication indication" },
+	{ "vendor", hostapd_cli_cmd_vendor, NULL,
+	  "<vendor id> <sub command id> [<hex formatted data>]\n"
+	  "  = send vendor driver command" },
+	{ "enable", hostapd_cli_cmd_enable, NULL,
+	  "= enable hostapd on current interface" },
+	{ "reload", hostapd_cli_cmd_reload, NULL,
+	  "= reload configuration for current interface" },
+	{ "disable", hostapd_cli_cmd_disable, NULL,
+	  "= disable hostapd on current interface" },
+	{ "erp_flush", hostapd_cli_cmd_erp_flush, NULL,
+	  "= drop all ERP keys"},
+	{ "log_level", hostapd_cli_cmd_log_level, NULL,
+	  "[level] = show/change log verbosity level" },
+	{ "pmksa", hostapd_cli_cmd_pmksa, NULL,
+	  " = show PMKSA cache entries" },
+	{ "pmksa_flush", hostapd_cli_cmd_pmksa_flush, NULL,
+	  " = flush PMKSA cache" },
+	{ "set_neighbor", hostapd_cli_cmd_set_neighbor, NULL,
+	  "<addr> <ssid=> <nr=> [lci=] [civic=] [stat]\n"
+	  "  = add AP to neighbor database" },
+	{ "remove_neighbor", hostapd_cli_cmd_remove_neighbor, NULL,
+	  "<addr> <ssid=> = remove AP from neighbor database" },
+	{ "req_lci", hostapd_cli_cmd_req_lci, hostapd_complete_stations,
+	  "<addr> = send LCI request to a station"},
+	{ "req_range", hostapd_cli_cmd_req_range, NULL,
+	  " = send FTM range request"},
+	{ "driver_flags", hostapd_cli_cmd_driver_flags, NULL,
+	  " = show supported driver flags"},
+#ifdef CONFIG_DPP
+	{ "dpp_qr_code", hostapd_cli_cmd_dpp_qr_code, NULL,
+	  "report a scanned DPP URI from a QR Code" },
+	{ "dpp_bootstrap_gen", hostapd_cli_cmd_dpp_bootstrap_gen, NULL,
+	  "type=<qrcode> [chan=..] [mac=..] [info=..] [curve=..] [key=..] = generate DPP bootstrap information" },
+	{ "dpp_bootstrap_remove", hostapd_cli_cmd_dpp_bootstrap_remove, NULL,
+	  "*|<id> = remove DPP bootstrap information" },
+	{ "dpp_bootstrap_get_uri", hostapd_cli_cmd_dpp_bootstrap_get_uri, NULL,
+	  "<id> = get DPP bootstrap URI" },
+	{ "dpp_bootstrap_info", hostapd_cli_cmd_dpp_bootstrap_info, NULL,
+	  "<id> = show DPP bootstrap information" },
+	{ "dpp_auth_init", hostapd_cli_cmd_dpp_auth_init, NULL,
+	  "peer=<id> [own=<id>] = initiate DPP bootstrapping" },
+	{ "dpp_listen", hostapd_cli_cmd_dpp_listen, NULL,
+	  "<freq in MHz> = start DPP listen" },
+	{ "dpp_stop_listen", hostapd_cli_cmd_dpp_stop_listen, NULL,
+	  "= stop DPP listen" },
+	{ "dpp_configurator_add", hostapd_cli_cmd_dpp_configurator_add, NULL,
+	  "[curve=..] [key=..] = add DPP configurator" },
+	{ "dpp_configurator_remove", hostapd_cli_cmd_dpp_configurator_remove,
+	  NULL,
+	  "*|<id> = remove DPP configurator" },
+	{ "dpp_configurator_remove", hostapd_cli_cmd_dpp_configurator_get_key,
+	  NULL,
+	  "<id> = Get DPP configurator's private key" },
+	{ "dpp_pkex_add", hostapd_cli_cmd_dpp_pkex_add, NULL,
+	  "add PKEX code" },
+	{ "dpp_pkex_remove", hostapd_cli_cmd_dpp_pkex_remove, NULL,
+	  "*|<id> = remove DPP pkex information" },
+#endif /* CONFIG_DPP */
+	{ "accept_acl", hostapd_cli_cmd_accept_macacl, NULL,
+	  "=Add/Delete/Show/Clear accept MAC ACL" },
+	{ "deny_acl", hostapd_cli_cmd_deny_macacl, NULL,
+	  "=Add/Delete/Show/Clear deny MAC ACL" },
+	{ "poll_sta", hostapd_cli_cmd_poll_sta, hostapd_complete_stations,
+	  "<addr> = poll a STA to check connectivity with a QoS null frame" },
 	{ NULL, NULL, NULL, NULL }
 };
 
@@ -1471,7 +1762,7 @@ static void hostapd_cli_recv_pending(struct wpa_ctrl *ctrl, int in_read,
 	if (ctrl_conn == NULL)
 		return;
 	while (wpa_ctrl_pending(ctrl)) {
-		char buf[256];
+		char buf[4096];
 		size_t len = sizeof(buf) - 1;
 		if (wpa_ctrl_recv(ctrl, buf, &len) == 0) {
 			buf[len] = '\0';
@@ -1504,19 +1795,8 @@ static void hostapd_cli_ping(void *eloop_ctx, void *timeout_ctx)
 		printf("Connection to hostapd lost - trying to reconnect\n");
 		hostapd_cli_close_connection();
 	}
-	if (!ctrl_conn) {
-		ctrl_conn = hostapd_cli_open_connection(ctrl_ifname);
-		if (ctrl_conn) {
-			printf("Connection to hostapd re-established\n");
-			if (wpa_ctrl_attach(ctrl_conn) == 0) {
-				hostapd_cli_attached = 1;
-				register_event_handler(ctrl_conn);
-			} else {
-				printf("Warning: Failed to attach to "
-				       "hostapd.\n");
-			}
-		}
-	}
+	if (!ctrl_conn && hostapd_cli_reconnect(ctrl_ifname) == 0)
+		printf("Connection to hostapd re-established\n");
 	if (ctrl_conn)
 		hostapd_cli_recv_pending(ctrl_conn, 1, 0);
 	eloop_register_timeout(ping_interval, 0, hostapd_cli_ping, NULL, NULL);
@@ -1611,17 +1891,34 @@ static char ** hostapd_cli_edit_completion_cb(void *ctx, const char *str,
 
 static void hostapd_cli_interactive(void)
 {
+	char *hfile = NULL;
+	char *home;
+
 	printf("\nInteractive mode\n\n");
 
+#ifdef CONFIG_HOSTAPD_CLI_HISTORY_DIR
+	home = CONFIG_HOSTAPD_CLI_HISTORY_DIR;
+#else /* CONFIG_HOSTAPD_CLI_HISTORY_DIR */
+	home = getenv("HOME");
+#endif /* CONFIG_HOSTAPD_CLI_HISTORY_DIR */
+	if (home) {
+		const char *fname = ".hostapd_cli_history";
+		int hfile_len = os_strlen(home) + 1 + os_strlen(fname) + 1;
+		hfile = os_malloc(hfile_len);
+		if (hfile)
+			os_snprintf(hfile, hfile_len, "%s/%s", home, fname);
+	}
+
 	eloop_register_signal_terminate(hostapd_cli_eloop_terminate, NULL);
 	edit_init(hostapd_cli_edit_cmd_cb, hostapd_cli_edit_eof_cb,
-		  hostapd_cli_edit_completion_cb, NULL, NULL, NULL);
+		  hostapd_cli_edit_completion_cb, NULL, hfile, NULL);
 	eloop_register_timeout(ping_interval, 0, hostapd_cli_ping, NULL, NULL);
 
 	eloop_run();
 
 	cli_txt_list_flush(&stations);
-	edit_deinit(NULL, NULL);
+	edit_deinit(hfile, NULL);
+	os_free(hfile);
 	eloop_cancel_timeout(hostapd_cli_ping, NULL, NULL);
 }
 
@@ -1748,7 +2045,7 @@ int main(int argc, char *argv[])
 				closedir(dir);
 			}
 		}
-		ctrl_conn = hostapd_cli_open_connection(ctrl_ifname);
+		hostapd_cli_reconnect(ctrl_ifname);
 		if (ctrl_conn) {
 			if (warning_displayed)
 				printf("Connection established.\n");
@@ -1769,17 +2066,8 @@ int main(int argc, char *argv[])
 		continue;
 	}
 
-	if (interactive || action_file) {
-		if (wpa_ctrl_attach(ctrl_conn) == 0) {
-			hostapd_cli_attached = 1;
-			register_event_handler(ctrl_conn);
-		} else {
-			printf("Warning: Failed to attach to hostapd.\n");
-			if (action_file)
-				return -1;
-		}
-	}
-
+	if (action_file && !hostapd_cli_attached)
+		return -1;
 	if (daemonize && os_daemonize(pid_file) && eloop_sock_requeue())
 		return -1;
 

+ 56 - 9
hostapd/main.c

@@ -1,6 +1,6 @@
 /*
  * hostapd / main()
- * Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -24,6 +24,7 @@
 #include "ap/hostapd.h"
 #include "ap/ap_config.h"
 #include "ap/ap_drv_ops.h"
+#include "ap/dpp_hostapd.h"
 #include "fst/fst.h"
 #include "config_file.h"
 #include "eap_register.h"
@@ -108,6 +109,10 @@ static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module,
 			    module_str ? module_str : "",
 			    module_str ? ": " : "", txt);
 
+#ifdef CONFIG_DEBUG_SYSLOG
+	if (wpa_debug_syslog)
+		conf_stdout = 0;
+#endif /* CONFIG_DEBUG_SYSLOG */
 	if ((conf_stdout & module) && level >= conf_stdout_level) {
 		wpa_debug_print_timestamp();
 		wpa_printf(MSG_INFO, "%s", format);
@@ -451,7 +456,7 @@ static void show_version(void)
 		"hostapd v" VERSION_STR "\n"
 		"User space daemon for IEEE 802.11 AP management,\n"
 		"IEEE 802.1X/WPA/WPA2/EAP/RADIUS Authenticator\n"
-		"Copyright (c) 2002-2016, Jouni Malinen <j@w1.fi> "
+		"Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi> "
 		"and contributors\n");
 }
 
@@ -480,10 +485,13 @@ static void usage(void)
 		"   -f   log output to debug file instead of stdout\n"
 #endif /* CONFIG_DEBUG_FILE */
 #ifdef CONFIG_DEBUG_LINUX_TRACING
-		"   -T = record to Linux tracing in addition to logging\n"
+		"   -T   record to Linux tracing in addition to logging\n"
 		"        (records all messages regardless of debug verbosity)\n"
 #endif /* CONFIG_DEBUG_LINUX_TRACING */
 		"   -i   list of interface names to use\n"
+#ifdef CONFIG_DEBUG_SYSLOG
+		"   -s   log output to syslog instead of stdout\n"
+#endif /* CONFIG_DEBUG_SYSLOG */
 		"   -S   start all the interfaces synchronously\n"
 		"   -t   include timestamps in some debug messages\n"
 		"   -v   show hostapd version\n");
@@ -549,14 +557,14 @@ static int hostapd_get_ctrl_iface_group(struct hapd_interfaces *interfaces,
 
 static int hostapd_get_interface_names(char ***if_names,
 				       size_t *if_names_size,
-				       char *optarg)
+				       char *arg)
 {
 	char *if_name, *tmp, **nnames;
 	size_t i;
 
-	if (!optarg)
+	if (!arg)
 		return -1;
-	if_name = strtok_r(optarg, ",", &tmp);
+	if_name = strtok_r(arg, ",", &tmp);
 
 	while (if_name) {
 		nnames = os_realloc_array(*if_names, 1 + *if_names_size,
@@ -659,9 +667,15 @@ int main(int argc, char *argv[])
 	interfaces.global_iface_name = NULL;
 	interfaces.global_ctrl_sock = -1;
 	dl_list_init(&interfaces.global_ctrl_dst);
+#ifdef CONFIG_ETH_P_OUI
+	dl_list_init(&interfaces.eth_p_oui);
+#endif /* CONFIG_ETH_P_OUI */
+#ifdef CONFIG_DPP
+	hostapd_dpp_init_global(&interfaces);
+#endif /* CONFIG_DPP */
 
 	for (;;) {
-		c = getopt(argc, argv, "b:Bde:f:hi:KP:STtu:vg:G:");
+		c = getopt(argc, argv, "b:Bde:f:hi:KP:sSTtu:vg:G:");
 		if (c < 0)
 			break;
 		switch (c) {
@@ -718,6 +732,11 @@ int main(int argc, char *argv[])
 			bss_config = tmp_bss;
 			bss_config[num_bss_configs++] = optarg;
 			break;
+#ifdef CONFIG_DEBUG_SYSLOG
+		case 's':
+			wpa_debug_syslog = 1;
+			break;
+#endif /* CONFIG_DEBUG_SYSLOG */
 		case 'S':
 			start_ifaces_in_sync = 1;
 			break;
@@ -746,6 +765,10 @@ int main(int argc, char *argv[])
 		wpa_debug_open_file(log_file);
 	else
 		wpa_debug_setup_stdout();
+#ifdef CONFIG_DEBUG_SYSLOG
+	if (wpa_debug_syslog)
+		wpa_debug_open_syslog();
+#endif /* CONFIG_DEBUG_SYSLOG */
 #ifdef CONFIG_DEBUG_LINUX_TRACING
 	if (enable_trace_dbg) {
 		int tret = wpa_debug_open_linux_tracing();
@@ -850,8 +873,27 @@ int main(int argc, char *argv[])
 	 */
 	interfaces.terminate_on_error = interfaces.count;
 	for (i = 0; i < interfaces.count; i++) {
-		if (hostapd_driver_init(interfaces.iface[i]) ||
-		    hostapd_setup_interface(interfaces.iface[i]))
+		if (hostapd_driver_init(interfaces.iface[i]))
+			goto out;
+#ifdef CONFIG_MBO
+		for (j = 0; j < interfaces.iface[i]->num_bss; j++) {
+			struct hostapd_data *hapd = interfaces.iface[i]->bss[j];
+
+			if (hapd && (hapd->conf->oce & OCE_STA_CFON) &&
+			    (interfaces.iface[i]->drv_flags &
+			     WPA_DRIVER_FLAGS_OCE_STA_CFON))
+				hapd->enable_oce = OCE_STA_CFON;
+
+			if (hapd && (hapd->conf->oce & OCE_AP) &&
+			    (interfaces.iface[i]->drv_flags &
+			     WPA_DRIVER_FLAGS_OCE_STA_CFON)) {
+				/* TODO: Need to add OCE-AP support */
+				wpa_printf(MSG_ERROR,
+					   "OCE-AP feature is not yet supported");
+			}
+		}
+#endif /* CONFIG_MBO */
+		if (hostapd_setup_interface(interfaces.iface[i]))
 			goto out;
 	}
 
@@ -877,11 +919,16 @@ int main(int argc, char *argv[])
 	}
 	os_free(interfaces.iface);
 
+#ifdef CONFIG_DPP
+	hostapd_dpp_deinit_global(&interfaces);
+#endif /* CONFIG_DPP */
+
 	if (interfaces.eloop_initialized)
 		eloop_cancel_timeout(hostapd_periodic, &interfaces, NULL);
 	hostapd_global_deinit(pid_file, interfaces.eloop_initialized);
 	os_free(pid_file);
 
+	wpa_debug_close_syslog();
 	if (log_file)
 		wpa_debug_close_file();
 	wpa_debug_close_linux_tracing();

+ 35 - 24
hs20/client/osu_client.c

@@ -105,6 +105,35 @@ static int valid_fqdn(const char *fqdn)
 }
 
 
+static int android_update_permission(const char *path, mode_t mode)
+{
+#ifdef ANDROID
+	/* we need to change file/folder permission for Android */
+
+	if (!path) {
+		wpa_printf(MSG_ERROR, "file path null");
+		return -1;
+	}
+
+	/* Allow processes running with Group ID as AID_WIFI,
+	 * to read files from SP, SP/<fqdn>, Cert and osu-info directories */
+	if (chown(path, -1, AID_WIFI)) {
+		wpa_printf(MSG_INFO, "CTRL: Could not chown directory: %s",
+			   strerror(errno));
+		return -1;
+	}
+
+	if (chmod(path, mode) < 0) {
+		wpa_printf(MSG_INFO, "CTRL: Could not chmod directory: %s",
+			   strerror(errno));
+		return -1;
+	}
+#endif  /* ANDROID */
+
+	return 0;
+}
+
+
 int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert)
 {
 	xml_node_t *node;
@@ -169,6 +198,8 @@ int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert)
 	}
 
 	mkdir("Cert", S_IRWXU);
+	android_update_permission("Cert", S_IRWXU | S_IRWXG);
+
 	if (est_load_cacerts(ctx, url) < 0 ||
 	    est_build_csr(ctx, url) < 0 ||
 	    est_simple_enroll(ctx, url, user, pw) < 0)
@@ -578,20 +609,8 @@ int hs20_add_pps_mo(struct hs20_osu_client *ctx, const char *uri,
 		}
 	}
 
-#ifdef ANDROID
-	/* Allow processes running with Group ID as AID_WIFI,
-	 * to read files from SP/<fqdn> directory */
-	if (chown(fname, -1, AID_WIFI)) {
-		wpa_printf(MSG_INFO, "CTRL: Could not chown directory: %s",
-			   strerror(errno));
-		/* Try to continue anyway */
-	}
-	if (chmod(fname, S_IRWXU | S_IRGRP | S_IXGRP) < 0) {
-		wpa_printf(MSG_INFO, "CTRL: Could not chmod directory: %s",
-			   strerror(errno));
-		/* Try to continue anyway */
-	}
-#endif /* ANDROID */
+	android_update_permission("SP", S_IRWXU | S_IRGRP | S_IXGRP);
+	android_update_permission(fname, S_IRWXU | S_IRGRP | S_IXGRP);
 
 	snprintf(fname, fname_len, "SP/%s/pps.xml", fqdn);
 
@@ -2134,7 +2153,7 @@ static int cmd_osu_select(struct hs20_osu_client *ctx, const char *dir,
 	char fname[255];
 	FILE *f;
 	struct osu_data *osu = NULL, *last = NULL;
-	size_t osu_count, i, j;
+	size_t osu_count = 0, i, j;
 	int ret;
 
 	write_summary(ctx, "OSU provider selection");
@@ -2346,15 +2365,7 @@ static int cmd_signup(struct hs20_osu_client *ctx, int no_prod_assoc,
 		return -1;
 	}
 
-#ifdef ANDROID
-	/* Allow processes running with Group ID as AID_WIFI
-	 * to read/write files from osu-info directory
-	 */
-	if (chown(fname, -1, AID_WIFI)) {
-		wpa_printf(MSG_INFO, "Could not chown osu-info directory: %s",
-			   strerror(errno));
-	}
-#endif /* ANDROID */
+	android_update_permission(fname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
 
 	snprintf(buf, sizeof(buf), "SET osu_dir %s", fname);
 	if (wpa_command(ifname, buf) < 0) {

+ 2 - 2
hs20/server/hs20-osu-server.txt

@@ -110,8 +110,8 @@ cd ca
 ./clean.sh
 rm -fr rootCA"
 old_hostname=myserver.local
-./setup.sh -C "Hotspot 2.0 Trust Root CA - CT" -d $old_hostname \
-   -I "Hotspot 2.0 Intermediate CA - CT" -o $old_hostname-osu-client \
+./setup.sh -C "Hotspot 2.0 Trust Root CA - CT" \
+   -o $old_hostname-osu-client \
    -O $old_hostname-oscp -p lanforge -S $old_hostname \
    -V $old_hostname-osu-revoked \
    -m local -u http://$old_hostname:8888/

+ 2 - 4
hs20/server/spp_server.c

@@ -1823,10 +1823,8 @@ static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx,
 	}
 
 	if (strcasecmp(req_reason, "User input completed") == 0) {
-		if (devinfo)
-			db_add_session_devinfo(ctx, session_id, devinfo);
-		if (devdetail)
-			db_add_session_devdetail(ctx, session_id, devdetail);
+		db_add_session_devinfo(ctx, session_id, devinfo);
+		db_add_session_devdetail(ctx, session_id, devdetail);
 		ret = hs20_user_input_complete(ctx, user, realm, dmacc,
 					       session_id);
 		hs20_eventlog_node(ctx, user, realm, session_id,

+ 8 - 1
src/ap/Makefile

@@ -10,12 +10,15 @@ include ../lib.rules
 
 CFLAGS += -DHOSTAPD
 CFLAGS += -DNEED_AP_MLME
+CFLAGS += -DCONFIG_ETH_P_OUI
 CFLAGS += -DCONFIG_HS20
 CFLAGS += -DCONFIG_INTERWORKING
 CFLAGS += -DCONFIG_IEEE80211R
+CFLAGS += -DCONFIG_IEEE80211R_AP
 CFLAGS += -DCONFIG_IEEE80211W
 CFLAGS += -DCONFIG_WPS
 CFLAGS += -DCONFIG_PROXYARP
+CFLAGS += -DCONFIG_IPV6
 CFLAGS += -DCONFIG_IAPP
 
 LIB_OBJS= \
@@ -32,6 +35,7 @@ LIB_OBJS= \
 	dhcp_snoop.o \
 	drv_callbacks.o \
 	eap_user_db.o \
+	eth_p_oui.o \
 	gas_serv.o \
 	hostapd.o \
 	hs20.o \
@@ -43,14 +47,17 @@ LIB_OBJS= \
 	ieee802_11_shared.o \
 	ieee802_11_vht.o \
 	ieee802_1x.o \
+	neighbor_db.o \
 	ndisc_snoop.o \
 	p2p_hostapd.o \
-	peerkey_auth.o \
 	pmksa_cache_auth.o \
 	preauth_auth.o \
+	rrm.o \
 	sta_info.o \
 	tkip_countermeasures.o \
 	utils.o \
+	vlan.o \
+	vlan_ifconfig.o \
 	vlan_init.o \
 	wmm.o \
 	wnm_ap.o \

+ 14 - 32
src/ap/acs.c

@@ -260,7 +260,7 @@ static void acs_clean_chan_surveys(struct hostapd_channel_data *chan)
 }
 
 
-static void acs_cleanup(struct hostapd_iface *iface)
+void acs_cleanup(struct hostapd_iface *iface)
 {
 	int i;
 	struct hostapd_channel_data *chan;
@@ -331,10 +331,8 @@ acs_survey_chan_interference_factor(struct hostapd_iface *iface,
 	long double int_factor = 0;
 	unsigned count = 0;
 
-	if (dl_list_empty(&chan->survey_list))
-		return;
-
-	if (chan->flag & HOSTAPD_CHAN_DISABLED)
+	if (dl_list_empty(&chan->survey_list) ||
+	    (chan->flag & HOSTAPD_CHAN_DISABLED))
 		return;
 
 	chan->interference_factor = 0;
@@ -359,9 +357,8 @@ acs_survey_chan_interference_factor(struct hostapd_iface *iface,
 			   (unsigned long) survey->channel_time_rx);
 	}
 
-	if (!count)
-		return;
-	chan->interference_factor /= count;
+	if (count)
+		chan->interference_factor /= count;
 }
 
 
@@ -450,13 +447,9 @@ static int acs_surveys_are_sufficient(struct hostapd_iface *iface)
 
 	for (i = 0; i < iface->current_mode->num_channels; i++) {
 		chan = &iface->current_mode->channels[i];
-		if (chan->flag & HOSTAPD_CHAN_DISABLED)
-			continue;
-
-		if (!acs_survey_list_is_sufficient(chan))
-			continue;
-
-		valid++;
+		if (!(chan->flag & HOSTAPD_CHAN_DISABLED) &&
+		    acs_survey_list_is_sufficient(chan))
+			valid++;
 	}
 
 	/* We need at least survey data for one channel */
@@ -466,13 +459,9 @@ static int acs_surveys_are_sufficient(struct hostapd_iface *iface)
 
 static int acs_usable_chan(struct hostapd_channel_data *chan)
 {
-	if (dl_list_empty(&chan->survey_list))
-		return 0;
-	if (chan->flag & HOSTAPD_CHAN_DISABLED)
-		return 0;
-	if (!acs_survey_list_is_sufficient(chan))
-		return 0;
-	return 1;
+	return !dl_list_empty(&chan->survey_list) &&
+		!(chan->flag & HOSTAPD_CHAN_DISABLED) &&
+		acs_survey_list_is_sufficient(chan);
 }
 
 
@@ -788,10 +777,7 @@ static int acs_study_survey_based(struct hostapd_iface *iface)
 
 static int acs_study_options(struct hostapd_iface *iface)
 {
-	int err;
-
-	err = acs_study_survey_based(iface);
-	if (err == 0)
+	if (acs_study_survey_based(iface) == 0)
 		return 0;
 
 	/* TODO: If no surveys are available/sufficient this is a good
@@ -920,14 +906,11 @@ static int acs_request_scan(struct hostapd_iface *iface)
 
 enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 {
-	int err;
-
 	wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");
 
 	if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {
 		wpa_printf(MSG_INFO, "ACS: Offloading to driver");
-		err = hostapd_drv_do_acs(iface->bss[0]);
-		if (err)
+		if (hostapd_drv_do_acs(iface->bss[0]))
 			return HOSTAPD_CHAN_INVALID;
 		return HOSTAPD_CHAN_ACS;
 	}
@@ -937,8 +920,7 @@ enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 
 	acs_cleanup(iface);
 
-	err = acs_request_scan(iface);
-	if (err < 0)
+	if (acs_request_scan(iface) < 0)
 		return HOSTAPD_CHAN_INVALID;
 
 	hostapd_set_state(iface, HAPD_IFACE_ACS);

+ 5 - 0
src/ap/acs.h

@@ -13,6 +13,7 @@
 #ifdef CONFIG_ACS
 
 enum hostapd_chan_status acs_init(struct hostapd_iface *iface);
+void acs_cleanup(struct hostapd_iface *iface);
 
 #else /* CONFIG_ACS */
 
@@ -22,6 +23,10 @@ static inline enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 	return HOSTAPD_CHAN_INVALID;
 }
 
+static inline void acs_cleanup(struct hostapd_iface *iface)
+{
+}
+
 #endif /* CONFIG_ACS */
 
 #endif /* ACS_H */

+ 104 - 24
src/ap/ap_config.c

@@ -13,6 +13,7 @@
 #include "radius/radius_client.h"
 #include "common/ieee802_11_defs.h"
 #include "common/eapol_common.h"
+#include "common/dhcp.h"
 #include "eap_common/eap_wsc_common.h"
 #include "eap_server/eap.h"
 #include "wpa_auth.h"
@@ -36,6 +37,10 @@ static void hostapd_config_free_vlan(struct hostapd_bss_config *bss)
 }
 
 
+#ifndef DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES
+#define DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES 0
+#endif /* DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES */
+
 void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 {
 	dl_list_init(&bss->anqp_elem);
@@ -55,6 +60,10 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 
 	bss->wpa_group_rekey = 86400;
 	bss->wpa_gmk_rekey = 86400;
+	bss->wpa_group_update_count = 4;
+	bss->wpa_pairwise_update_count = 4;
+	bss->wpa_disable_eapol_key_retries =
+		DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES;
 	bss->wpa_key_mgmt = WPA_KEY_MGMT_PSK;
 	bss->wpa_pairwise = WPA_CIPHER_TKIP;
 	bss->wpa_group = WPA_CIPHER_TKIP;
@@ -88,13 +97,33 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 	/* Set to -1 as defaults depends on HT in setup */
 	bss->wmm_enabled = -1;
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	bss->ft_over_ds = 1;
-#endif /* CONFIG_IEEE80211R */
+	bss->rkh_pos_timeout = 86400;
+	bss->rkh_neg_timeout = 60;
+	bss->rkh_pull_timeout = 1000;
+	bss->rkh_pull_retries = 4;
+#endif /* CONFIG_IEEE80211R_AP */
 
 	bss->radius_das_time_window = 300;
 
 	bss->sae_anti_clogging_threshold = 5;
+	bss->sae_sync = 5;
+
+	bss->gas_frag_limit = 1400;
+
+#ifdef CONFIG_FILS
+	dl_list_init(&bss->fils_realms);
+	bss->fils_hlp_wait_time = 30;
+	bss->dhcp_server_port = DHCP_SERVER_PORT;
+	bss->dhcp_relay_port = DHCP_SERVER_PORT;
+#endif /* CONFIG_FILS */
+
+	bss->broadcast_deauth = 1;
+
+#ifdef CONFIG_MBO
+	bss->mbo_cell_data_conn_pref = -1;
+#endif /* CONFIG_MBO */
 }
 
 
@@ -192,6 +221,11 @@ struct hostapd_config * hostapd_config_defaults(void)
 	conf->acs_num_scans = 5;
 #endif /* CONFIG_ACS */
 
+	/* The third octet of the country string uses an ASCII space character
+	 * by default to indicate that the regulations encompass all
+	 * environments for the current frequency band in the country. */
+	conf->country[2] = ' ';
+
 	return conf;
 }
 
@@ -329,13 +363,7 @@ int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf)
 		ssid->wpa_psk->group = 1;
 	}
 
-	if (ssid->wpa_psk_file) {
-		if (hostapd_config_read_wpa_psk(ssid->wpa_psk_file,
-						&conf->ssid))
-			return -1;
-	}
-
-	return 0;
+	return hostapd_config_read_wpa_psk(ssid->wpa_psk_file, &conf->ssid);
 }
 
 
@@ -384,6 +412,18 @@ void hostapd_config_free_eap_user(struct hostapd_eap_user *user)
 }
 
 
+void hostapd_config_free_eap_users(struct hostapd_eap_user *user)
+{
+	struct hostapd_eap_user *prev_user;
+
+	while (user) {
+		prev_user = user;
+		user = user->next;
+		hostapd_config_free_eap_user(prev_user);
+	}
+}
+
+
 static void hostapd_config_free_wep(struct hostapd_wep_keys *keys)
 {
 	int i;
@@ -420,10 +460,22 @@ static void hostapd_config_free_anqp_elem(struct hostapd_bss_config *conf)
 }
 
 
-void hostapd_config_free_bss(struct hostapd_bss_config *conf)
+static void hostapd_config_free_fils_realms(struct hostapd_bss_config *conf)
 {
-	struct hostapd_eap_user *user, *prev_user;
+#ifdef CONFIG_FILS
+	struct fils_realm *realm;
+
+	while ((realm = dl_list_first(&conf->fils_realms, struct fils_realm,
+				      list))) {
+		dl_list_del(&realm->list);
+		os_free(realm);
+	}
+#endif /* CONFIG_FILS */
+}
+
 
+void hostapd_config_free_bss(struct hostapd_bss_config *conf)
+{
 	if (conf == NULL)
 		return;
 
@@ -436,12 +488,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 	os_free(conf->ssid.vlan_tagged_interface);
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
-	user = conf->eap_user;
-	while (user) {
-		prev_user = user;
-		user = user->next;
-		hostapd_config_free_eap_user(prev_user);
-	}
+	hostapd_config_free_eap_users(conf->eap_user);
 	os_free(conf->eap_user_sqlite);
 
 	os_free(conf->eap_req_id_text);
@@ -477,7 +524,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 	hostapd_config_free_vlan(conf);
 	os_free(conf->time_zone);
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	{
 		struct ft_remote_r0kh *r0kh, *r0kh_prev;
 		struct ft_remote_r1kh *r1kh, *r1kh_prev;
@@ -498,7 +545,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 			os_free(r1kh_prev);
 		}
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_WPS
 	os_free(conf->wps_pin_requests);
@@ -570,6 +617,9 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 	wpabuf_free(conf->assocresp_elements);
 
 	os_free(conf->sae_groups);
+#ifdef CONFIG_OWE
+	os_free(conf->owe_groups);
+#endif /* CONFIG_OWE */
 
 	os_free(conf->wowlan_triggers);
 
@@ -577,11 +627,22 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 
 #ifdef CONFIG_TESTING_OPTIONS
 	wpabuf_free(conf->own_ie_override);
+	wpabuf_free(conf->sae_commit_override);
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	os_free(conf->no_probe_resp_if_seen_on);
 	os_free(conf->no_auth_if_seen_on);
 
+	hostapd_config_free_fils_realms(conf);
+
+#ifdef CONFIG_DPP
+	os_free(conf->dpp_connector);
+	wpabuf_free(conf->dpp_netaccesskey);
+	wpabuf_free(conf->dpp_csign);
+#endif /* CONFIG_DPP */
+
+	os_free(conf->sae_password);
+
 	os_free(conf);
 }
 
@@ -802,7 +863,7 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
 		}
 	}
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (full_config && wpa_key_mgmt_ft(bss->wpa_key_mgmt) &&
 	    (bss->nas_identifier == NULL ||
 	     os_strlen(bss->nas_identifier) < 1 ||
@@ -812,7 +873,7 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
 			   "string");
 		return -1;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_IEEE80211N
 	if (full_config && conf->ieee80211n &&
@@ -848,6 +909,16 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
 		wpa_printf(MSG_ERROR,
 			   "VHT (IEEE 802.11ac) with WEP is not allowed, disabling VHT capabilities");
 	}
+
+	if (full_config && conf->ieee80211ac && bss->wpa &&
+	    !(bss->wpa_pairwise & WPA_CIPHER_CCMP) &&
+	    !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP |
+				   WPA_CIPHER_CCMP_256 | WPA_CIPHER_GCMP_256)))
+	{
+		bss->disable_11ac = 1;
+		wpa_printf(MSG_ERROR,
+			   "VHT (IEEE 802.11ac) with WPA/WPA2 requires CCMP/GCMP to be enabled, disabling VHT capabilities");
+	}
 #endif /* CONFIG_IEEE80211AC */
 
 #ifdef CONFIG_WPS
@@ -866,7 +937,9 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
 
 	if (full_config && bss->wps_state && bss->wpa &&
 	    (!(bss->wpa & 2) ||
-	     !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP)))) {
+	     !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP |
+				    WPA_CIPHER_CCMP_256 |
+				    WPA_CIPHER_GCMP_256)))) {
 		wpa_printf(MSG_INFO, "WPS: WPA/TKIP configuration without "
 			   "WPA2/CCMP/GCMP forced WPS to be disabled");
 		bss->wps_state = 0;
@@ -976,8 +1049,15 @@ void hostapd_set_security_params(struct hostapd_bss_config *bss,
 
 	if ((bss->wpa & 2) && bss->rsn_pairwise == 0)
 		bss->rsn_pairwise = bss->wpa_pairwise;
-	bss->wpa_group = wpa_select_ap_group_cipher(bss->wpa, bss->wpa_pairwise,
-						    bss->rsn_pairwise);
+	if (bss->group_cipher)
+		bss->wpa_group = bss->group_cipher;
+	else
+		bss->wpa_group = wpa_select_ap_group_cipher(bss->wpa,
+							    bss->wpa_pairwise,
+							    bss->rsn_pairwise);
+	if (!bss->wpa_group_rekey_set)
+		bss->wpa_group_rekey = bss->wpa_group == WPA_CIPHER_TKIP ?
+			600 : 86400;
 
 	if (full_config) {
 		bss->radius->auth_server = bss->radius->auth_servers;

+ 101 - 6
src/ap/ap_config.h

@@ -224,6 +224,12 @@ struct anqp_element {
 	struct wpabuf *payload;
 };
 
+struct fils_realm {
+	struct dl_list list;
+	u8 hash[2];
+	char realm[];
+};
+
 
 /**
  * struct hostapd_bss_config - Per-BSS configuration
@@ -242,7 +248,8 @@ struct hostapd_bss_config {
 	int max_num_sta; /* maximum number of STAs in station table */
 
 	int dtim_period;
-	int bss_load_update_period;
+	unsigned int bss_load_update_period;
+	unsigned int chan_util_avg_period;
 
 	int ieee802_1x; /* use IEEE 802.1X */
 	int eapol_version;
@@ -287,7 +294,7 @@ struct hostapd_bss_config {
 	char iapp_iface[IFNAMSIZ + 1]; /* interface used with IAPP broadcast
 					* frames */
 
-	enum {
+	enum macaddr_acl {
 		ACCEPT_UNLESS_DENIED = 0,
 		DENY_UNLESS_ACCEPTED = 1,
 		USE_EXTERNAL_RADIUS_AUTH = 2
@@ -319,27 +326,36 @@ struct hostapd_bss_config {
 		PSK_RADIUS_REQUIRED = 2
 	} wpa_psk_radius;
 	int wpa_pairwise;
+	int group_cipher; /* wpa_group value override from configuation */
 	int wpa_group;
 	int wpa_group_rekey;
+	int wpa_group_rekey_set;
 	int wpa_strict_rekey;
 	int wpa_gmk_rekey;
 	int wpa_ptk_rekey;
+	u32 wpa_group_update_count;
+	u32 wpa_pairwise_update_count;
+	int wpa_disable_eapol_key_retries;
 	int rsn_pairwise;
 	int rsn_preauth;
 	char *rsn_preauth_interfaces;
-	int peerkey;
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	/* IEEE 802.11r - Fast BSS Transition */
 	u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN];
 	u8 r1_key_holder[FT_R1KH_ID_LEN];
 	u32 r0_key_lifetime;
+	int rkh_pos_timeout;
+	int rkh_neg_timeout;
+	int rkh_pull_timeout; /* ms */
+	int rkh_pull_retries;
 	u32 reassociation_deadline;
 	struct ft_remote_r0kh *r0kh_list;
 	struct ft_remote_r1kh *r1kh_list;
 	int pmk_r1_push;
 	int ft_over_ds;
-#endif /* CONFIG_IEEE80211R */
+	int ft_psk_generate_local;
+#endif /* CONFIG_IEEE80211R_AP */
 
 	char *ctrl_interface; /* directory for UNIX domain sockets */
 #ifndef CONFIG_NATIVE_WINDOWS
@@ -353,6 +369,7 @@ struct hostapd_bss_config {
 	char *private_key_passwd;
 	int check_crl;
 	unsigned int tls_session_lifetime;
+	unsigned int tls_flags;
 	char *ocsp_stapling_response;
 	char *ocsp_stapling_response_multi;
 	char *dh_file;
@@ -464,6 +481,7 @@ struct hostapd_bss_config {
 	int time_advertisement;
 	char *time_zone;
 	int wnm_sleep_mode;
+	int wnm_sleep_mode_no_keys;
 	int bss_transition;
 
 	/* IEEE 802.11u - Interworking */
@@ -508,7 +526,7 @@ struct hostapd_bss_config {
 	struct dl_list anqp_elem; /* list of struct anqp_element */
 
 	u16 gas_comeback_delay;
-	int gas_frag_limit;
+	size_t gas_frag_limit;
 	int gas_address3;
 
 	u8 qos_map_set[16 + 2 * 21];
@@ -566,7 +584,10 @@ struct hostapd_bss_config {
 	struct wpabuf *assocresp_elements;
 
 	unsigned int sae_anti_clogging_threshold;
+	unsigned int sae_sync;
+	int sae_require_mfp;
 	int *sae_groups;
+	char *sae_password;
 
 	char *wowlan_triggers; /* Wake-on-WLAN triggers */
 
@@ -574,6 +595,8 @@ struct hostapd_bss_config {
 	u8 bss_load_test[5];
 	u8 bss_load_test_set;
 	struct wpabuf *own_ie_override;
+	int sae_reflection_attack;
+	struct wpabuf *sae_commit_override;
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #define MESH_ENABLED BIT(0)
@@ -591,12 +614,69 @@ struct hostapd_bss_config {
 
 #ifdef CONFIG_MBO
 	int mbo_enabled;
+	/**
+	 * oce - Enable OCE in AP and/or STA-CFON mode
+	 *  - BIT(0) is Reserved
+	 *  - Set BIT(1) to enable OCE in STA-CFON mode
+	 *  - Set BIT(2) to enable OCE in AP mode
+	 */
+	unsigned int oce;
+	int mbo_cell_data_conn_pref;
 #endif /* CONFIG_MBO */
 
 	int ftm_responder;
 	int ftm_initiator;
+
+#ifdef CONFIG_FILS
+	u8 fils_cache_id[FILS_CACHE_ID_LEN];
+	int fils_cache_id_set;
+	struct dl_list fils_realms; /* list of struct fils_realm */
+	int fils_dh_group;
+	struct hostapd_ip_addr dhcp_server;
+	int dhcp_rapid_commit_proxy;
+	unsigned int fils_hlp_wait_time;
+	u16 dhcp_server_port;
+	u16 dhcp_relay_port;
+#endif /* CONFIG_FILS */
+
+	int multicast_to_unicast;
+
+	int broadcast_deauth;
+
+#ifdef CONFIG_DPP
+	char *dpp_connector;
+	struct wpabuf *dpp_netaccesskey;
+	unsigned int dpp_netaccesskey_expiry;
+	struct wpabuf *dpp_csign;
+#endif /* CONFIG_DPP */
+
+#ifdef CONFIG_OWE
+	macaddr owe_transition_bssid;
+	u8 owe_transition_ssid[SSID_MAX_LEN];
+	size_t owe_transition_ssid_len;
+	char owe_transition_ifname[IFNAMSIZ + 1];
+	int *owe_groups;
+#endif /* CONFIG_OWE */
+};
+
+/**
+ * struct he_phy_capabilities_info - HE PHY capabilities
+ */
+struct he_phy_capabilities_info {
+	Boolean he_su_beamformer;
+	Boolean he_su_beamformee;
+	Boolean he_mu_beamformer;
 };
 
+/**
+ * struct he_operation - HE operation
+ */
+struct he_operation {
+	u8 he_bss_color;
+	u8 he_default_pe_duration;
+	u8 he_twt_required;
+	u8 he_rts_threshold;
+};
 
 /**
  * struct hostapd_config - Per-radio interface configuration
@@ -612,6 +692,7 @@ struct hostapd_config {
 	u8 channel;
 	u8 acs;
 	struct wpa_freq_range_list acs_ch_list;
+	int acs_exclude_dfs;
 	enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */
 	enum {
 		LONG_PREAMBLE = 0,
@@ -620,6 +701,8 @@ struct hostapd_config {
 
 	int *supported_rates;
 	int *basic_rates;
+	unsigned int beacon_rate;
+	enum beacon_rate_type rate_type;
 
 	const struct wpa_driver_ops *driver;
 	char *driver_params;
@@ -635,6 +718,9 @@ struct hostapd_config {
 			  * ' ' (ascii 32): all environments
 			  * 'O': Outdoor environemnt only
 			  * 'I': Indoor environment only
+			  * 'X': Used with noncountry entity ("XXX")
+			  * 0x00..0x31: identifying IEEE 802.11 standard
+			  *	Annex E table (0x04 = global table)
 			  */
 
 	int ieee80211d;
@@ -675,6 +761,7 @@ struct hostapd_config {
 	u8 vht_oper_chwidth;
 	u8 vht_oper_centr_freq_seg0_idx;
 	u8 vht_oper_centr_freq_seg1_idx;
+	u8 ht40_plus_minus_allowed;
 
 	/* Use driver-generated interface addresses when adding multiple BSSs */
 	u8 use_driver_iface_addr;
@@ -707,6 +794,13 @@ struct hostapd_config {
 
 	struct wpabuf *lci;
 	struct wpabuf *civic;
+	int stationary_ap;
+
+	int ieee80211ax;
+#ifdef CONFIG_IEEE80211AX
+	struct he_phy_capabilities_info he_phy_capab;
+	struct he_operation he_op;
+#endif /* CONFIG_IEEE80211AX */
 };
 
 
@@ -714,6 +808,7 @@ int hostapd_mac_comp(const void *a, const void *b);
 struct hostapd_config * hostapd_config_defaults(void);
 void hostapd_config_defaults_bss(struct hostapd_bss_config *bss);
 void hostapd_config_free_eap_user(struct hostapd_eap_user *user);
+void hostapd_config_free_eap_users(struct hostapd_eap_user *user);
 void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **p);
 void hostapd_config_free_bss(struct hostapd_bss_config *conf);
 void hostapd_config_free(struct hostapd_config *conf);

+ 69 - 6
src/ap/ap_drv_ops.c

@@ -19,6 +19,7 @@
 #include "ap_config.h"
 #include "p2p_hostapd.h"
 #include "hs20.h"
+#include "wpa_auth.h"
 #include "ap_drv_ops.h"
 
 
@@ -99,6 +100,13 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd,
 		goto fail;
 #endif /* CONFIG_FST */
 
+#ifdef CONFIG_FILS
+	pos = hostapd_eid_fils_indic(hapd, buf, 0);
+	if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+	    add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
+#endif /* CONFIG_FILS */
+
 	if (add_buf(&beacon, hapd->wps_beacon_ie) < 0 ||
 	    add_buf(&proberesp, hapd->wps_probe_resp_ie) < 0)
 		goto fail;
@@ -168,7 +176,7 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd,
 #endif /* CONFIG_HS20 */
 
 #ifdef CONFIG_MBO
-	if (hapd->conf->mbo_enabled) {
+	if (hapd->conf->mbo_enabled || hapd->enable_oce) {
 		pos = hostapd_eid_mbo(hapd, buf, sizeof(buf));
 		if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
 		    add_buf_data(&proberesp, buf, pos - buf) < 0 ||
@@ -177,6 +185,13 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_MBO */
 
+#ifdef CONFIG_OWE
+	pos = hostapd_eid_owe_trans(hapd, buf, sizeof(buf));
+	if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+	    add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
+#endif /* CONFIG_OWE */
+
 	add_buf(&beacon, hapd->conf->vendor_elements);
 	add_buf(&proberesp, hapd->conf->vendor_elements);
 	add_buf(&assocresp, hapd->conf->assocresp_elements);
@@ -340,10 +355,44 @@ int hostapd_add_sta_node(struct hostapd_data *hapd, const u8 *addr,
 int hostapd_sta_auth(struct hostapd_data *hapd, const u8 *addr,
 		     u16 seq, u16 status, const u8 *ie, size_t len)
 {
+	struct wpa_driver_sta_auth_params params;
+#ifdef CONFIG_FILS
+	struct sta_info *sta;
+#endif /* CONFIG_FILS */
+
 	if (hapd->driver == NULL || hapd->driver->sta_auth == NULL)
 		return 0;
-	return hapd->driver->sta_auth(hapd->drv_priv, hapd->own_addr, addr,
-				      seq, status, ie, len);
+
+	os_memset(&params, 0, sizeof(params));
+
+#ifdef CONFIG_FILS
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "Station " MACSTR
+			   " not found for sta_auth processing",
+			   MAC2STR(addr));
+		return 0;
+	}
+
+	if (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	    sta->auth_alg == WLAN_AUTH_FILS_PK) {
+		params.fils_auth = 1;
+		wpa_auth_get_fils_aead_params(sta->wpa_sm, params.fils_anonce,
+					      params.fils_snonce,
+					      params.fils_kek,
+					      &params.fils_kek_len);
+	}
+#endif /* CONFIG_FILS */
+
+	params.own_addr = hapd->own_addr;
+	params.addr = addr;
+	params.seq = seq;
+	params.status = status;
+	params.ie = ie;
+	params.len = len;
+
+	return hapd->driver->sta_auth(hapd->drv_priv, &params);
 }
 
 
@@ -554,13 +603,13 @@ int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs,
 
 struct hostapd_hw_modes *
 hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes,
-			    u16 *flags)
+			    u16 *flags, u8 *dfs_domain)
 {
 	if (hapd->driver == NULL ||
 	    hapd->driver->get_hw_feature_data == NULL)
 		return NULL;
 	return hapd->driver->get_hw_feature_data(hapd->drv_priv, num_modes,
-						 flags);
+						 flags, dfs_domain);
 }
 
 
@@ -694,6 +743,15 @@ int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
 		sta = ap_get_sta(hapd, dst);
 		if (!sta || !(sta->flags & WLAN_STA_ASSOC))
 			bssid = wildcard_bssid;
+	} else if (is_broadcast_ether_addr(dst) &&
+		   len > 0 && data[0] == WLAN_ACTION_PUBLIC) {
+		/*
+		 * The only current use case of Public Action frames with
+		 * broadcast destination address is DPP PKEX. That case is
+		 * directing all devices and not just the STAs within the BSS,
+		 * so have to use the wildcard BSSID value.
+		 */
+		bssid = wildcard_bssid;
 	}
 	return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst,
 					 hapd->own_addr, bssid, data, len, 0);
@@ -774,7 +832,9 @@ static void hostapd_get_hw_mode_any_channels(struct hostapd_data *hapd,
 		if ((acs_ch_list_all ||
 		     freq_range_list_includes(&hapd->iface->conf->acs_ch_list,
 					      chan->chan)) &&
-		    !(chan->flag & HOSTAPD_CHAN_DISABLED))
+		    !(chan->flag & HOSTAPD_CHAN_DISABLED) &&
+		    !(hapd->iface->conf->acs_exclude_dfs &&
+		      (chan->flag & HOSTAPD_CHAN_RADAR)))
 			int_array_add_unique(freq_list, chan->freq);
 	}
 }
@@ -829,6 +889,9 @@ int hostapd_drv_do_acs(struct hostapd_data *hapd)
 				    &hapd->iface->conf->acs_ch_list,
 				    chan->chan))
 				continue;
+			if (hapd->iface->conf->acs_exclude_dfs &&
+			    (chan->flag & HOSTAPD_CHAN_RADAR))
+				continue;
 			if (!(chan->flag & HOSTAPD_CHAN_DISABLED)) {
 				channels[num_channels++] = chan->chan;
 				int_array_add_unique(&freq_list, chan->freq);

+ 12 - 3
src/ap/ap_drv_ops.h

@@ -72,7 +72,7 @@ int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs,
 				int cw_min, int cw_max, int burst_time);
 struct hostapd_hw_modes *
 hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes,
-			    u16 *flags);
+			    u16 *flags, u8 *dfs_domain);
 int hostapd_driver_commit(struct hostapd_data *hapd);
 int hostapd_drv_none(struct hostapd_data *hapd);
 int hostapd_driver_scan(struct hostapd_data *hapd,
@@ -103,6 +103,14 @@ int hostapd_drv_send_action_addr3_ap(struct hostapd_data *hapd,
 				     unsigned int freq,
 				     unsigned int wait, const u8 *dst,
 				     const u8 *data, size_t len);
+static inline void
+hostapd_drv_send_action_cancel_wait(struct hostapd_data *hapd)
+{
+	if (!hapd->driver || !hapd->driver->send_action_cancel_wait ||
+	    !hapd->drv_priv)
+		return;
+	hapd->driver->send_action_cancel_wait(hapd->drv_priv);
+}
 int hostapd_add_sta_node(struct hostapd_data *hapd, const u8 *addr,
 			 u16 auth_alg);
 int hostapd_sta_auth(struct hostapd_data *hapd, const u8 *addr,
@@ -274,8 +282,9 @@ static inline const char * hostapd_drv_get_radio_name(struct hostapd_data *hapd)
 static inline int hostapd_drv_switch_channel(struct hostapd_data *hapd,
 					     struct csa_settings *settings)
 {
-	if (hapd->driver == NULL || hapd->driver->switch_channel == NULL)
-		return -ENOTSUP;
+	if (hapd->driver == NULL || hapd->driver->switch_channel == NULL ||
+	    hapd->drv_priv == NULL)
+		return -1;
 
 	return hapd->driver->switch_channel(hapd->drv_priv, settings);
 }

+ 13 - 3
src/ap/ap_mlme.c

@@ -57,7 +57,11 @@ void mlme_authenticate_indication(struct hostapd_data *hapd,
 		       HOSTAPD_LEVEL_DEBUG,
 		       "MLME-AUTHENTICATE.indication(" MACSTR ", %s)",
 		       MAC2STR(sta->addr), mlme_auth_alg_str(sta->auth_alg));
-	if (sta->auth_alg != WLAN_AUTH_FT && !(sta->flags & WLAN_STA_MFP))
+	if (sta->auth_alg != WLAN_AUTH_FT &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK_PFS &&
+	    sta->auth_alg != WLAN_AUTH_FILS_PK &&
+	    !(sta->flags & WLAN_STA_MFP))
 		mlme_deletekeys_request(hapd, sta);
 	ap_sta_clear_disconnect_timeouts(hapd, sta);
 }
@@ -105,7 +109,10 @@ void mlme_associate_indication(struct hostapd_data *hapd, struct sta_info *sta)
 		       HOSTAPD_LEVEL_DEBUG,
 		       "MLME-ASSOCIATE.indication(" MACSTR ")",
 		       MAC2STR(sta->addr));
-	if (sta->auth_alg != WLAN_AUTH_FT)
+	if (sta->auth_alg != WLAN_AUTH_FT &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK_PFS &&
+	    sta->auth_alg != WLAN_AUTH_FILS_PK)
 		mlme_deletekeys_request(hapd, sta);
 	ap_sta_clear_disconnect_timeouts(hapd, sta);
 }
@@ -130,7 +137,10 @@ void mlme_reassociate_indication(struct hostapd_data *hapd,
 		       HOSTAPD_LEVEL_DEBUG,
 		       "MLME-REASSOCIATE.indication(" MACSTR ")",
 		       MAC2STR(sta->addr));
-	if (sta->auth_alg != WLAN_AUTH_FT)
+	if (sta->auth_alg != WLAN_AUTH_FT &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK &&
+	    sta->auth_alg != WLAN_AUTH_FILS_SK_PFS &&
+	    sta->auth_alg != WLAN_AUTH_FILS_PK)
 		mlme_deletekeys_request(hapd, sta);
 	ap_sta_clear_disconnect_timeouts(hapd, sta);
 }

+ 4 - 3
src/ap/authsrv.c

@@ -71,11 +71,10 @@ static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity,
 	}
 
 	if (eap_user->password) {
-		user->password = os_malloc(eap_user->password_len);
+		user->password = os_memdup(eap_user->password,
+					   eap_user->password_len);
 		if (user->password == NULL)
 			goto out;
-		os_memcpy(user->password, eap_user->password,
-			  eap_user->password_len);
 		user->password_len = eap_user->password_len;
 		user->password_hash = eap_user->password_hash;
 	}
@@ -133,6 +132,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
 	srv.erp = conf->eap_server_erp;
 	srv.erp_domain = conf->erp_domain;
 	srv.tls_session_lifetime = conf->tls_session_lifetime;
+	srv.tls_flags = conf->tls_flags;
 
 	hapd->radius_srv = radius_server_init(&srv);
 	if (hapd->radius_srv == NULL) {
@@ -157,6 +157,7 @@ int authsrv_init(struct hostapd_data *hapd)
 
 		os_memset(&conf, 0, sizeof(conf));
 		conf.tls_session_lifetime = hapd->conf->tls_session_lifetime;
+		conf.tls_flags = hapd->conf->tls_flags;
 		hapd->ssl_ctx = tls_init(&conf);
 		if (hapd->ssl_ctx == NULL) {
 			wpa_printf(MSG_ERROR, "Failed to initialize TLS");

+ 71 - 2
src/ap/beacon.c

@@ -16,6 +16,7 @@
 #include "common/ieee802_11_defs.h"
 #include "common/ieee802_11_common.h"
 #include "common/hw_features_common.h"
+#include "common/wpa_ctrl.h"
 #include "wps/wps_defs.h"
 #include "p2p/p2p.h"
 #include "hostapd.h"
@@ -30,6 +31,7 @@
 #include "hs20.h"
 #include "dfs.h"
 #include "taxonomy.h"
+#include "ieee802_11_auth.h"
 
 
 #ifdef NEED_AP_MLME
@@ -392,7 +394,15 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd,
 			2 + sizeof(struct ieee80211_vht_operation);
 	}
 
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax) {
+		buflen += 3 + sizeof(struct ieee80211_he_capabilities) +
+			3 + sizeof(struct ieee80211_he_operation);
+	}
+#endif /* CONFIG_IEEE80211AX */
+
 	buflen += hostapd_mbo_ie_len(hapd);
+	buflen += hostapd_eid_owe_trans_len(hapd);
 
 	resp = os_zalloc(buflen);
 	if (resp == NULL)
@@ -491,6 +501,18 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd,
 		pos = hostapd_eid_txpower_envelope(hapd, pos);
 		pos = hostapd_eid_wb_chsw_wrapper(hapd, pos);
 	}
+#endif /* CONFIG_IEEE80211AC */
+
+	pos = hostapd_eid_fils_indic(hapd, pos, 0);
+
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax) {
+		pos = hostapd_eid_he_capab(hapd, pos);
+		pos = hostapd_eid_he_operation(hapd, pos);
+	}
+#endif /* CONFIG_IEEE80211AX */
+
+#ifdef CONFIG_IEEE80211AC
 	if (hapd->conf->vendor_vht)
 		pos = hostapd_eid_vendor_vht(hapd, pos);
 #endif /* CONFIG_IEEE80211AC */
@@ -526,6 +548,7 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd,
 #endif /* CONFIG_HS20 */
 
 	pos = hostapd_eid_mbo(hapd, pos, (u8 *) resp + buflen - pos);
+	pos = hostapd_eid_owe_trans(hapd, pos, (u8 *) resp + buflen - pos);
 
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements),
@@ -618,7 +641,7 @@ static struct hostapd_sta_info * sta_track_get(struct hostapd_iface *iface,
 }
 
 
-void sta_track_add(struct hostapd_iface *iface, const u8 *addr)
+void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal)
 {
 	struct hostapd_sta_info *info;
 
@@ -628,6 +651,7 @@ void sta_track_add(struct hostapd_iface *iface, const u8 *addr)
 		dl_list_del(&info->list);
 		dl_list_add_tail(&iface->sta_seen, &info->list);
 		os_get_reltime(&info->last_seen);
+		info->ssi_signal = ssi_signal;
 		return;
 	}
 
@@ -637,6 +661,7 @@ void sta_track_add(struct hostapd_iface *iface, const u8 *addr)
 		return;
 	os_memcpy(info->addr, addr, ETH_ALEN);
 	os_get_reltime(&info->last_seen);
+	info->ssi_signal = ssi_signal;
 
 	if (iface->num_sta_seen >= iface->conf->track_sta_max_num) {
 		/* Expire oldest entry to make room for a new one */
@@ -707,14 +732,30 @@ void handle_probe_req(struct hostapd_data *hapd,
 	int ret;
 	u16 csa_offs[2];
 	size_t csa_offs_len;
+	u32 session_timeout, acct_interim_interval;
+	struct vlan_description vlan_id;
+	struct hostapd_sta_wpa_psk_short *psk = NULL;
+	char *identity = NULL;
+	char *radius_cui = NULL;
 
 	if (len < IEEE80211_HDRLEN)
 		return;
 	ie = ((const u8 *) mgmt) + IEEE80211_HDRLEN;
 	if (hapd->iconf->track_sta_max_num)
-		sta_track_add(hapd->iface, mgmt->sa);
+		sta_track_add(hapd->iface, mgmt->sa, ssi_signal);
 	ie_len = len - IEEE80211_HDRLEN;
 
+	ret = ieee802_11_allowed_address(hapd, mgmt->sa, (const u8 *) mgmt, len,
+					 &session_timeout,
+					 &acct_interim_interval, &vlan_id,
+					 &psk, &identity, &radius_cui, 1);
+	if (ret == HOSTAPD_ACL_REJECT) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			"Ignore Probe Request frame from " MACSTR
+			" due to ACL reject ", MAC2STR(mgmt->sa));
+		return;
+	}
+
 	for (i = 0; hapd->probereq_cb && i < hapd->num_probereq_cb; i++)
 		if (hapd->probereq_cb[i].cb(hapd->probereq_cb[i].ctx,
 					    mgmt->sa, mgmt->da, mgmt->bssid,
@@ -909,6 +950,9 @@ void handle_probe_req(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
+	wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, RX_PROBE_REQUEST "sa=" MACSTR
+		     " signal=%d", MAC2STR(mgmt->sa), ssi_signal);
+
 	resp = hostapd_gen_probe_resp(hapd, mgmt, elems.p2p != NULL,
 				      &resp_len);
 	if (resp == NULL)
@@ -1033,7 +1077,15 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_IEEE80211AC */
 
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax) {
+		tail_len += 3 + sizeof(struct ieee80211_he_capabilities) +
+			3 + sizeof(struct ieee80211_he_operation);
+	}
+#endif /* CONFIG_IEEE80211AX */
+
 	tail_len += hostapd_mbo_ie_len(hapd);
+	tail_len += hostapd_eid_owe_trans_len(hapd);
 
 	tailpos = tail = os_malloc(tail_len);
 	if (head == NULL || tail == NULL) {
@@ -1155,6 +1207,18 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 		tailpos = hostapd_eid_txpower_envelope(hapd, tailpos);
 		tailpos = hostapd_eid_wb_chsw_wrapper(hapd, tailpos);
 	}
+#endif /* CONFIG_IEEE80211AC */
+
+	tailpos = hostapd_eid_fils_indic(hapd, tailpos, 0);
+
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax) {
+		tailpos = hostapd_eid_he_capab(hapd, tailpos);
+		tailpos = hostapd_eid_he_operation(hapd, tailpos);
+	}
+#endif /* CONFIG_IEEE80211AX */
+
+#ifdef CONFIG_IEEE80211AC
 	if (hapd->conf->vendor_vht)
 		tailpos = hostapd_eid_vendor_vht(hapd, tailpos);
 #endif /* CONFIG_IEEE80211AC */
@@ -1189,6 +1253,8 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 #endif /* CONFIG_HS20 */
 
 	tailpos = hostapd_eid_mbo(hapd, tailpos, tail + tail_len - tailpos);
+	tailpos = hostapd_eid_owe_trans(hapd, tailpos,
+					tail + tail_len - tailpos);
 
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(tailpos, wpabuf_head(hapd->conf->vendor_elements),
@@ -1211,6 +1277,8 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 	params->dtim_period = hapd->conf->dtim_period;
 	params->beacon_int = hapd->iconf->beacon_int;
 	params->basic_rates = hapd->iface->basic_rates;
+	params->beacon_rate = hapd->iconf->beacon_rate;
+	params->rate_type = hapd->iconf->rate_type;
 	params->ssid = hapd->conf->ssid.ssid;
 	params->ssid_len = hapd->conf->ssid.ssid_len;
 	if ((hapd->conf->wpa & (WPA_PROTO_WPA | WPA_PROTO_RSN)) ==
@@ -1274,6 +1342,7 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 		params->osen = 1;
 	}
 #endif /* CONFIG_HS20 */
+	params->multicast_to_unicast = hapd->conf->multicast_to_unicast;
 	params->pbss = hapd->conf->pbss;
 	return 0;
 }

+ 1 - 1
src/ap/beacon.h

@@ -21,7 +21,7 @@ int ieee802_11_update_beacons(struct hostapd_iface *iface);
 int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 			       struct wpa_driver_ap_params *params);
 void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params);
-void sta_track_add(struct hostapd_iface *iface, const u8 *addr);
+void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal);
 void sta_track_del(struct hostapd_sta_info *info);
 void sta_track_expire(struct hostapd_iface *iface, int force);
 struct hostapd_data *

+ 43 - 9
src/ap/bss_load.c

@@ -16,11 +16,35 @@
 #include "beacon.h"
 
 
+static int get_bss_load_update_timeout(struct hostapd_data *hapd,
+				       unsigned int *sec, unsigned int *usec)
+{
+	unsigned int update_period = hapd->conf->bss_load_update_period;
+	unsigned int beacon_int = hapd->iconf->beacon_int;
+	unsigned int update_timeout;
+
+	if (!update_period || !beacon_int) {
+		wpa_printf(MSG_ERROR,
+			   "BSS Load: Invalid BSS load update configuration (period=%u beacon_int=%u)",
+			   update_period, beacon_int);
+		return -1;
+	}
+
+	update_timeout = update_period * beacon_int;
+
+	*sec = ((update_timeout / 1000) * 1024) / 1000;
+	*usec = (update_timeout % 1000) * 1024;
+
+	return 0;
+}
+
+
 static void update_channel_utilization(void *eloop_data, void *user_data)
 {
 	struct hostapd_data *hapd = eloop_data;
 	unsigned int sec, usec;
 	int err;
+	struct hostapd_iface *iface = hapd->iface;
 
 	if (!(hapd->beacon_set_done && hapd->started))
 		return;
@@ -33,8 +57,24 @@ static void update_channel_utilization(void *eloop_data, void *user_data)
 
 	ieee802_11_set_beacon(hapd);
 
-	sec = ((hapd->bss_load_update_timeout / 1000) * 1024) / 1000;
-	usec = (hapd->bss_load_update_timeout % 1000) * 1024;
+	if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0)
+		return;
+
+	if (hapd->conf->chan_util_avg_period) {
+		iface->chan_util_samples_sum += iface->channel_utilization;
+		iface->chan_util_num_sample_periods +=
+			hapd->conf->bss_load_update_period;
+		if (iface->chan_util_num_sample_periods >=
+		    hapd->conf->chan_util_avg_period) {
+			iface->chan_util_average =
+				iface->chan_util_samples_sum /
+				(iface->chan_util_num_sample_periods /
+				 hapd->conf->bss_load_update_period);
+			iface->chan_util_samples_sum = 0;
+			iface->chan_util_num_sample_periods = 0;
+		}
+	}
+
 	eloop_register_timeout(sec, usec, update_channel_utilization, hapd,
 			       NULL);
 }
@@ -42,17 +82,11 @@ static void update_channel_utilization(void *eloop_data, void *user_data)
 
 int bss_load_update_init(struct hostapd_data *hapd)
 {
-	struct hostapd_bss_config *conf = hapd->conf;
-	struct hostapd_config *iconf = hapd->iconf;
 	unsigned int sec, usec;
 
-	if (!conf->bss_load_update_period || !iconf->beacon_int)
+	if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0)
 		return -1;
 
-	hapd->bss_load_update_timeout = conf->bss_load_update_period *
-					iconf->beacon_int;
-	sec = ((hapd->bss_load_update_timeout / 1000) * 1024) / 1000;
-	usec = (hapd->bss_load_update_timeout % 1000) * 1024;
 	eloop_register_timeout(sec, usec, update_channel_utilization, hapd,
 			       NULL);
 	return 0;

+ 346 - 8
src/ap/ctrl_iface_ap.c

@@ -26,23 +26,141 @@
 #include "taxonomy.h"
 
 
+static size_t hostapd_write_ht_mcs_bitmask(char *buf, size_t buflen,
+					   size_t curr_len, const u8 *mcs_set)
+{
+	int ret;
+	size_t len = curr_len;
+
+	ret = os_snprintf(buf + len, buflen - len,
+			  "ht_mcs_bitmask=");
+	if (os_snprintf_error(buflen - len, ret))
+		return len;
+	len += ret;
+
+	/* 77 first bits (+ 3 reserved bits) */
+	len += wpa_snprintf_hex(buf + len, buflen - len, mcs_set, 10);
+
+	ret = os_snprintf(buf + len, buflen - len, "\n");
+	if (os_snprintf_error(buflen - len, ret))
+		return curr_len;
+	len += ret;
+
+	return len;
+}
+
+
 static int hostapd_get_sta_tx_rx(struct hostapd_data *hapd,
 				 struct sta_info *sta,
 				 char *buf, size_t buflen)
 {
 	struct hostap_sta_driver_data data;
 	int ret;
+	int len = 0;
 
 	if (hostapd_drv_read_sta_data(hapd, &data, sta->addr) < 0)
 		return 0;
 
 	ret = os_snprintf(buf, buflen, "rx_packets=%lu\ntx_packets=%lu\n"
-			  "rx_bytes=%llu\ntx_bytes=%llu\ninactive_msec=%lu\n",
+			  "rx_bytes=%llu\ntx_bytes=%llu\ninactive_msec=%lu\n"
+			  "signal=%d\n",
 			  data.rx_packets, data.tx_packets,
-			  data.rx_bytes, data.tx_bytes, data.inactive_msec);
+			  data.rx_bytes, data.tx_bytes, data.inactive_msec,
+			  data.signal);
 	if (os_snprintf_error(buflen, ret))
 		return 0;
-	return ret;
+	len += ret;
+
+	ret = os_snprintf(buf + len, buflen - len, "rx_rate_info=%lu",
+			  data.current_rx_rate);
+	if (os_snprintf_error(buflen - len, ret))
+		return len;
+	len += ret;
+	if (data.flags & STA_DRV_DATA_RX_MCS) {
+		ret = os_snprintf(buf + len, buflen - len, " mcs %u",
+				  data.rx_mcs);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_RX_VHT_MCS) {
+		ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u",
+				  data.rx_vhtmcs);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_RX_VHT_NSS) {
+		ret = os_snprintf(buf + len, buflen - len, " vhtnss %u",
+				  data.rx_vht_nss);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_RX_SHORT_GI) {
+		ret = os_snprintf(buf + len, buflen - len, " shortGI");
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	ret = os_snprintf(buf + len, buflen - len, "\n");
+	if (!os_snprintf_error(buflen - len, ret))
+		len += ret;
+
+	ret = os_snprintf(buf + len, buflen - len, "tx_rate_info=%lu",
+			  data.current_tx_rate);
+	if (os_snprintf_error(buflen - len, ret))
+		return len;
+	len += ret;
+	if (data.flags & STA_DRV_DATA_TX_MCS) {
+		ret = os_snprintf(buf + len, buflen - len, " mcs %u",
+				  data.tx_mcs);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_TX_VHT_MCS) {
+		ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u",
+				  data.tx_vhtmcs);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_TX_VHT_NSS) {
+		ret = os_snprintf(buf + len, buflen - len, " vhtnss %u",
+				  data.tx_vht_nss);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	if (data.flags & STA_DRV_DATA_TX_SHORT_GI) {
+		ret = os_snprintf(buf + len, buflen - len, " shortGI");
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+	ret = os_snprintf(buf + len, buflen - len, "\n");
+	if (!os_snprintf_error(buflen - len, ret))
+		len += ret;
+
+	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
+		ret = os_snprintf(buf + len, buflen - len,
+				  "rx_vht_mcs_map=%04x\n"
+				  "tx_vht_mcs_map=%04x\n",
+				  le_to_host16(sta->vht_capabilities->
+					       vht_supported_mcs_set.rx_map),
+				  le_to_host16(sta->vht_capabilities->
+					       vht_supported_mcs_set.tx_map));
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+
+	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) {
+		len = hostapd_write_ht_mcs_bitmask(buf, buflen, len,
+						   sta->ht_capabilities->
+						   supported_mcs_set);
+	}
+
+	if (data.flags & STA_DRV_DATA_LAST_ACK_RSSI) {
+		ret = os_snprintf(buf + len, buflen - len,
+				  "last_ack_signal=%d\n", data.last_ack_rssi);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+
+	return len;
 }
 
 
@@ -176,6 +294,46 @@ static int hostapd_ctrl_iface_sta_mib(struct hostapd_data *hapd,
 		len += os_snprintf(buf + len, buflen - len, "\n");
 	}
 
+	if (sta->power_capab) {
+		ret = os_snprintf(buf + len, buflen - len,
+				  "min_txpower=%d\n"
+				  "max_txpower=%d\n",
+				  sta->min_tx_power, sta->max_tx_power);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+
+#ifdef CONFIG_IEEE80211AC
+	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
+		res = os_snprintf(buf + len, buflen - len,
+				  "vht_caps_info=0x%08x\n",
+				  le_to_host32(sta->vht_capabilities->
+					       vht_capabilities_info));
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+	}
+#endif /* CONFIG_IEEE80211AC */
+
+#ifdef CONFIG_IEEE80211N
+	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) {
+		res = os_snprintf(buf + len, buflen - len,
+				  "ht_caps_info=0x%04x\n",
+				  le_to_host16(sta->ht_capabilities->
+					       ht_capabilities_info));
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+	}
+#endif /* CONFIG_IEEE80211N */
+
+	if (sta->ext_capability &&
+	    buflen - len > (unsigned) (11 + 2 * sta->ext_capability[0])) {
+		len += os_snprintf(buf + len, buflen - len, "ext_capab=");
+		len += wpa_snprintf_hex(buf + len, buflen - len,
+					sta->ext_capability + 1,
+					sta->ext_capability[0]);
+		len += os_snprintf(buf + len, buflen - len, "\n");
+	}
+
 	return len;
 }
 
@@ -477,7 +635,8 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 			      size_t buflen)
 {
 	struct hostapd_iface *iface = hapd->iface;
-	int len = 0, ret;
+	struct hostapd_hw_modes *mode = iface->current_mode;
+	int len = 0, ret, j;
 	size_t i;
 
 	ret = os_snprintf(buf + len, buflen - len,
@@ -537,13 +696,17 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 			  "channel=%u\n"
 			  "secondary_channel=%d\n"
 			  "ieee80211n=%d\n"
-			  "ieee80211ac=%d\n",
+			  "ieee80211ac=%d\n"
+			  "beacon_int=%u\n"
+			  "dtim_period=%d\n",
 			  iface->conf->channel,
 			  iface->conf->ieee80211n && !hapd->conf->disable_11n ?
 			  iface->conf->secondary_channel : 0,
 			  iface->conf->ieee80211n && !hapd->conf->disable_11n,
 			  iface->conf->ieee80211ac &&
-			  !hapd->conf->disable_11ac);
+			  !hapd->conf->disable_11ac,
+			  iface->conf->beacon_int,
+			  hapd->conf->dtim_period);
 	if (os_snprintf_error(buflen - len, ret))
 		return len;
 	len += ret;
@@ -551,15 +714,76 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 		ret = os_snprintf(buf + len, buflen - len,
 				  "vht_oper_chwidth=%d\n"
 				  "vht_oper_centr_freq_seg0_idx=%d\n"
-				  "vht_oper_centr_freq_seg1_idx=%d\n",
+				  "vht_oper_centr_freq_seg1_idx=%d\n"
+				  "vht_caps_info=%08x\n",
 				  iface->conf->vht_oper_chwidth,
 				  iface->conf->vht_oper_centr_freq_seg0_idx,
-				  iface->conf->vht_oper_centr_freq_seg1_idx);
+				  iface->conf->vht_oper_centr_freq_seg1_idx,
+				  iface->conf->vht_capab);
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
 	}
 
+	if (iface->conf->ieee80211ac && !hapd->conf->disable_11ac && mode) {
+		u16 rxmap = WPA_GET_LE16(&mode->vht_mcs_set[0]);
+		u16 txmap = WPA_GET_LE16(&mode->vht_mcs_set[4]);
+
+		ret = os_snprintf(buf + len, buflen - len,
+				  "rx_vht_mcs_map=%04x\n"
+				  "tx_vht_mcs_map=%04x\n",
+				  rxmap, txmap);
+		if (os_snprintf_error(buflen - len, ret))
+			return len;
+		len += ret;
+	}
+
+	if (iface->conf->ieee80211n && !hapd->conf->disable_11n) {
+		ret = os_snprintf(buf + len, buflen - len,
+				  "ht_caps_info=%04x\n",
+				  hapd->iconf->ht_capab);
+		if (os_snprintf_error(buflen - len, ret))
+			return len;
+		len += ret;
+	}
+
+	if (iface->conf->ieee80211n && !hapd->conf->disable_11n && mode) {
+		len = hostapd_write_ht_mcs_bitmask(buf, buflen, len,
+						   mode->mcs_set);
+	}
+
+	if (iface->current_rates && iface->num_rates) {
+		ret = os_snprintf(buf + len, buflen - len, "supported_rates=");
+		if (os_snprintf_error(buflen - len, ret))
+			return len;
+		len += ret;
+
+		for (j = 0; j < iface->num_rates; j++) {
+			ret = os_snprintf(buf + len, buflen - len, "%s%02x",
+					  j > 0 ? " " : "",
+					  iface->current_rates[j].rate / 5);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
+		ret = os_snprintf(buf + len, buflen - len, "\n");
+		if (os_snprintf_error(buflen - len, ret))
+			return len;
+		len += ret;
+	}
+
+	for (j = 0; mode && j < mode->num_channels; j++) {
+		if (mode->channels[j].freq == iface->freq) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "max_txpower=%u\n",
+					  mode->channels[j].max_tx_power);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+			break;
+		}
+	}
+
 	for (i = 0; i < iface->num_bss; i++) {
 		struct hostapd_data *bss = iface->bss[i];
 		ret = os_snprintf(buf + len, buflen - len,
@@ -578,6 +802,15 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 		len += ret;
 	}
 
+	if (hapd->conf->chan_util_avg_period) {
+		ret = os_snprintf(buf + len, buflen - len,
+				  "chan_util_avg=%u\n",
+				  iface->chan_util_average);
+		if (os_snprintf_error(buflen - len, ret))
+			return len;
+		len += ret;
+	}
+
 	return len;
 }
 
@@ -639,3 +872,108 @@ void hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd)
 {
 	wpa_auth_pmksa_flush(hapd->wpa_auth);
 }
+
+
+int hostapd_ctrl_iface_pmksa_add(struct hostapd_data *hapd, char *cmd)
+{
+	u8 spa[ETH_ALEN];
+	u8 pmkid[PMKID_LEN];
+	u8 pmk[PMK_LEN_MAX];
+	size_t pmk_len;
+	char *pos, *pos2;
+	int akmp = 0, expiration = 0;
+
+	/*
+	 * Entry format:
+	 * <STA addr> <PMKID> <PMK> <expiration in seconds> <akmp>
+	 */
+
+	if (hwaddr_aton(cmd, spa))
+		return -1;
+
+	pos = os_strchr(cmd, ' ');
+	if (!pos)
+		return -1;
+	pos++;
+
+	if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0)
+		return -1;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return -1;
+	pos++;
+
+	pos2 = os_strchr(pos, ' ');
+	if (!pos2)
+		return -1;
+	pmk_len = (pos2 - pos) / 2;
+	if (pmk_len < PMK_LEN || pmk_len > PMK_LEN_MAX ||
+	    hexstr2bin(pos, pmk, pmk_len) < 0)
+		return -1;
+
+	pos = pos2 + 1;
+
+	if (sscanf(pos, "%d %d", &expiration, &akmp) != 2)
+		return -1;
+
+	return wpa_auth_pmksa_add2(hapd->wpa_auth, spa, pmk, pmk_len,
+				   pmkid, expiration, akmp);
+}
+
+
+#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
+#ifdef CONFIG_MESH
+
+int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd,
+				       const u8 *addr, char *buf, size_t len)
+{
+	return wpa_auth_pmksa_list_mesh(hapd->wpa_auth, addr, buf, len);
+}
+
+
+void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd)
+{
+	u8 spa[ETH_ALEN];
+	u8 pmkid[PMKID_LEN];
+	u8 pmk[PMK_LEN_MAX];
+	char *pos;
+	int expiration;
+
+	/*
+	 * Entry format:
+	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
+	 */
+
+	if (hwaddr_aton(cmd, spa))
+		return NULL;
+
+	pos = os_strchr(cmd, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0)
+		return NULL;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (hexstr2bin(pos, pmk, PMK_LEN) < 0)
+		return NULL;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (sscanf(pos, "%d", &expiration) != 1)
+		return NULL;
+
+	return wpa_auth_pmksa_create_entry(aa, spa, pmk, pmkid, expiration);
+}
+
+#endif /* CONFIG_MESH */
+#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */

+ 4 - 0
src/ap/ctrl_iface_ap.h

@@ -32,5 +32,9 @@ int hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd);
 int hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf,
 				  size_t len);
 void hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd);
+int hostapd_ctrl_iface_pmksa_add(struct hostapd_data *hapd, char *cmd);
+int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd,
+				       const u8 *addr, char *buf, size_t len);
+void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd);
 
 #endif /* CTRL_IFACE_AP_H */

+ 59 - 3
src/ap/dfs.c

@@ -1,7 +1,7 @@
 /*
  * DFS - Dynamic Frequency Selection
  * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
- * Copyright (c) 2013-2015, Qualcomm Atheros, Inc.
+ * Copyright (c) 2013-2017, Qualcomm Atheros, Inc.
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -747,6 +747,23 @@ int hostapd_handle_dfs(struct hostapd_iface *iface)
 }
 
 
+static int hostapd_config_dfs_chan_available(struct hostapd_iface *iface)
+{
+	int n_chans, n_chans1, start_chan_idx, start_chan_idx1;
+
+	/* Get the start (first) channel for current configuration */
+	start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1);
+	if (start_chan_idx < 0)
+		return 0;
+
+	/* Get the number of used channels, depending on width */
+	n_chans = dfs_get_used_n_chans(iface, &n_chans1);
+
+	/* Check if all channels are DFS available */
+	return dfs_check_chans_available(iface, start_chan_idx, n_chans);
+}
+
+
 int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
 			     int ht_enabled, int chan_offset, int chan_width,
 			     int cf1, int cf2)
@@ -767,8 +784,21 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
 			set_dfs_state(iface, freq, ht_enabled, chan_offset,
 				      chan_width, cf1, cf2,
 				      HOSTAPD_CHAN_DFS_AVAILABLE);
-			iface->cac_started = 0;
-			hostapd_setup_interface_complete(iface, 0);
+			/*
+			 * Just mark the channel available when CAC completion
+			 * event is received in enabled state. CAC result could
+			 * have been propagated from another radio having the
+			 * same regulatory configuration. When CAC completion is
+			 * received during non-HAPD_IFACE_ENABLED state, make
+			 * sure the configured channel is available because this
+			 * CAC completion event could have been propagated from
+			 * another radio.
+			 */
+			if (iface->state != HAPD_IFACE_ENABLED &&
+			    hostapd_config_dfs_chan_available(iface)) {
+				hostapd_setup_interface_complete(iface, 0);
+				iface->cac_started = 0;
+			}
 		}
 	}
 
@@ -776,6 +806,25 @@ int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
 }
 
 
+int hostapd_dfs_pre_cac_expired(struct hostapd_iface *iface, int freq,
+				int ht_enabled, int chan_offset, int chan_width,
+				int cf1, int cf2)
+{
+	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_PRE_CAC_EXPIRED
+		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
+		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
+
+	/* Proceed only if DFS is not offloaded to the driver */
+	if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)
+		return 0;
+
+	set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width,
+		      cf1, cf2, HOSTAPD_CHAN_DFS_USABLE);
+
+	return 0;
+}
+
+
 static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface)
 {
 	struct hostapd_channel_data *channel;
@@ -840,6 +889,13 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface)
 	if (iface->cac_started)
 		return hostapd_dfs_start_channel_switch_cac(iface);
 
+	/*
+	 * Allow selection of DFS channel in ETSI to comply with
+	 * uniform spreading.
+	 */
+	if (iface->dfs_domain == HOSTAPD_DFS_REGION_ETSI)
+		skip_radar = 0;
+
 	/* Perform channel switch/CSA */
 	channel = dfs_get_valid_channel(iface, &secondary_channel,
 					&vht_oper_centr_freq_seg0_idx,

+ 4 - 1
src/ap/dfs.h

@@ -1,7 +1,7 @@
 /*
  * DFS - Dynamic Frequency Selection
  * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
- * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ * Copyright (c) 2013-2017, Qualcomm Atheros, Inc.
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -14,6 +14,9 @@ int hostapd_handle_dfs(struct hostapd_iface *iface);
 int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq,
 			     int ht_enabled, int chan_offset, int chan_width,
 			     int cf1, int cf2);
+int hostapd_dfs_pre_cac_expired(struct hostapd_iface *iface, int freq,
+				int ht_enabled, int chan_offset, int chan_width,
+				int cf1, int cf2);
 int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq,
 			       int ht_enabled,
 			       int chan_offset, int chan_width,

+ 9 - 30
src/ap/dhcp_snoop.c

@@ -7,10 +7,9 @@
  */
 
 #include "utils/includes.h"
-#include <netinet/ip.h>
-#include <netinet/udp.h>
 
 #include "utils/common.h"
+#include "common/dhcp.h"
 #include "l2_packet/l2_packet.h"
 #include "hostapd.h"
 #include "sta_info.h"
@@ -18,29 +17,6 @@
 #include "x_snoop.h"
 #include "dhcp_snoop.h"
 
-struct bootp_pkt {
-	struct iphdr iph;
-	struct udphdr udph;
-	u8 op;
-	u8 htype;
-	u8 hlen;
-	u8 hops;
-	be32 xid;
-	be16 secs;
-	be16 flags;
-	be32 client_ip;
-	be32 your_ip;
-	be32 server_ip;
-	be32 relay_ip;
-	u8 hw_addr[16];
-	u8 serv_name[64];
-	u8 boot_file[128];
-	u8 exten[312];
-} STRUCT_PACKED;
-
-#define DHCPACK	5
-static const u8 ic_bootp_cookie[] = { 99, 130, 83, 99 };
-
 
 static const char * ipaddr_str(u32 addr)
 {
@@ -74,24 +50,26 @@ static void handle_dhcp(void *ctx, const u8 *src_addr, const u8 *buf,
 	if (tot_len > (unsigned int) (len - ETH_HLEN))
 		return;
 
-	if (os_memcmp(b->exten, ic_bootp_cookie, ARRAY_SIZE(ic_bootp_cookie)))
+	if (WPA_GET_BE32(b->exten) != DHCP_MAGIC)
 		return;
 
 	/* Parse DHCP options */
 	end = (const u8 *) b + tot_len;
 	pos = &b->exten[4];
-	while (pos < end && *pos != 0xff) {
+	while (pos < end && *pos != DHCP_OPT_END) {
 		const u8 *opt = pos++;
 
-		if (*opt == 0) /* padding */
+		if (*opt == DHCP_OPT_PAD)
 			continue;
 
+		if (pos >= end || 1 + *pos > end - pos)
+			break;
 		pos += *pos + 1;
 		if (pos >= end)
 			break;
 
 		switch (*opt) {
-		case 1:  /* subnet mask */
+		case DHCP_OPT_SUBNET_MASK:
 			if (opt[1] == 4)
 				subnet_mask = WPA_GET_BE32(&opt[2]);
 			if (subnet_mask == 0)
@@ -101,7 +79,7 @@ static void handle_dhcp(void *ctx, const u8 *src_addr, const u8 *buf,
 				prefixlen--;
 			}
 			break;
-		case 53: /* message type */
+		case DHCP_OPT_MSG_TYPE:
 			if (opt[1])
 				msgtype = opt[2];
 			break;
@@ -176,4 +154,5 @@ int dhcp_snoop_init(struct hostapd_data *hapd)
 void dhcp_snoop_deinit(struct hostapd_data *hapd)
 {
 	l2_packet_deinit(hapd->sock_dhcp);
+	hapd->sock_dhcp = NULL;
 }

+ 2059 - 0
src/ap/dpp_hostapd.c

@@ -0,0 +1,2059 @@
+/*
+ * hostapd / DPP integration
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/dpp.h"
+#include "common/gas.h"
+#include "common/wpa_ctrl.h"
+#include "hostapd.h"
+#include "ap_drv_ops.h"
+#include "gas_query_ap.h"
+#include "wpa_auth.h"
+#include "dpp_hostapd.h"
+
+
+static void hostapd_dpp_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx);
+static void hostapd_dpp_auth_success(struct hostapd_data *hapd, int initiator);
+static void hostapd_dpp_init_timeout(void *eloop_ctx, void *timeout_ctx);
+static int hostapd_dpp_auth_init_next(struct hostapd_data *hapd);
+
+static const u8 broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+
+static struct dpp_configurator *
+hostapd_dpp_configurator_get_id(struct hostapd_data *hapd, unsigned int id)
+{
+	struct dpp_configurator *conf;
+
+	dl_list_for_each(conf, &hapd->iface->interfaces->dpp_configurator,
+			 struct dpp_configurator, list) {
+		if (conf->id == id)
+			return conf;
+	}
+	return NULL;
+}
+
+
+static unsigned int hapd_dpp_next_id(struct hostapd_data *hapd)
+{
+	struct dpp_bootstrap_info *bi;
+	unsigned int max_id = 0;
+
+	dl_list_for_each(bi, &hapd->iface->interfaces->dpp_bootstrap,
+			 struct dpp_bootstrap_info, list) {
+		if (bi->id > max_id)
+			max_id = bi->id;
+	}
+	return max_id + 1;
+}
+
+
+/**
+ * hostapd_dpp_qr_code - Parse and add DPP bootstrapping info from a QR Code
+ * @hapd: Pointer to hostapd_data
+ * @cmd: DPP URI read from a QR Code
+ * Returns: Identifier of the stored info or -1 on failure
+ */
+int hostapd_dpp_qr_code(struct hostapd_data *hapd, const char *cmd)
+{
+	struct dpp_bootstrap_info *bi;
+	struct dpp_authentication *auth = hapd->dpp_auth;
+
+	bi = dpp_parse_qr_code(cmd);
+	if (!bi)
+		return -1;
+
+	bi->id = hapd_dpp_next_id(hapd);
+	dl_list_add(&hapd->iface->interfaces->dpp_bootstrap, &bi->list);
+
+	if (auth && auth->response_pending &&
+	    dpp_notify_new_qr_code(auth, bi) == 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Sending out pending authentication response");
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+			" freq=%u type=%d",
+			MAC2STR(auth->peer_mac_addr), auth->curr_freq,
+			DPP_PA_AUTHENTICATION_RESP);
+		hostapd_drv_send_action(hapd, auth->curr_freq, 0,
+					auth->peer_mac_addr,
+					wpabuf_head(hapd->dpp_auth->resp_msg),
+					wpabuf_len(hapd->dpp_auth->resp_msg));
+	}
+
+	return bi->id;
+}
+
+
+static char * get_param(const char *cmd, const char *param)
+{
+	const char *pos, *end;
+	char *val;
+	size_t len;
+
+	pos = os_strstr(cmd, param);
+	if (!pos)
+		return NULL;
+
+	pos += os_strlen(param);
+	end = os_strchr(pos, ' ');
+	if (end)
+		len = end - pos;
+	else
+		len = os_strlen(pos);
+	val = os_malloc(len + 1);
+	if (!val)
+		return NULL;
+	os_memcpy(val, pos, len);
+	val[len] = '\0';
+	return val;
+}
+
+
+int hostapd_dpp_bootstrap_gen(struct hostapd_data *hapd, const char *cmd)
+{
+	char *chan = NULL, *mac = NULL, *info = NULL, *pk = NULL, *curve = NULL;
+	char *key = NULL;
+	u8 *privkey = NULL;
+	size_t privkey_len = 0;
+	size_t len;
+	int ret = -1;
+	struct dpp_bootstrap_info *bi;
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		goto fail;
+
+	if (os_strstr(cmd, "type=qrcode"))
+		bi->type = DPP_BOOTSTRAP_QR_CODE;
+	else if (os_strstr(cmd, "type=pkex"))
+		bi->type = DPP_BOOTSTRAP_PKEX;
+	else
+		goto fail;
+
+	chan = get_param(cmd, " chan=");
+	mac = get_param(cmd, " mac=");
+	info = get_param(cmd, " info=");
+	curve = get_param(cmd, " curve=");
+	key = get_param(cmd, " key=");
+
+	if (key) {
+		privkey_len = os_strlen(key) / 2;
+		privkey = os_malloc(privkey_len);
+		if (!privkey ||
+		    hexstr2bin(key, privkey, privkey_len) < 0)
+			goto fail;
+	}
+
+	pk = dpp_keygen(bi, curve, privkey, privkey_len);
+	if (!pk)
+		goto fail;
+
+	len = 4; /* "DPP:" */
+	if (chan) {
+		if (dpp_parse_uri_chan_list(bi, chan) < 0)
+			goto fail;
+		len += 3 + os_strlen(chan); /* C:...; */
+	}
+	if (mac) {
+		if (dpp_parse_uri_mac(bi, mac) < 0)
+			goto fail;
+		len += 3 + os_strlen(mac); /* M:...; */
+	}
+	if (info) {
+		if (dpp_parse_uri_info(bi, info) < 0)
+			goto fail;
+		len += 3 + os_strlen(info); /* I:...; */
+	}
+	len += 4 + os_strlen(pk);
+	bi->uri = os_malloc(len + 1);
+	if (!bi->uri)
+		goto fail;
+	os_snprintf(bi->uri, len + 1, "DPP:%s%s%s%s%s%s%s%s%sK:%s;;",
+		    chan ? "C:" : "", chan ? chan : "", chan ? ";" : "",
+		    mac ? "M:" : "", mac ? mac : "", mac ? ";" : "",
+		    info ? "I:" : "", info ? info : "", info ? ";" : "",
+		    pk);
+	bi->id = hapd_dpp_next_id(hapd);
+	dl_list_add(&hapd->iface->interfaces->dpp_bootstrap, &bi->list);
+	ret = bi->id;
+	bi = NULL;
+fail:
+	os_free(curve);
+	os_free(pk);
+	os_free(chan);
+	os_free(mac);
+	os_free(info);
+	str_clear_free(key);
+	bin_clear_free(privkey, privkey_len);
+	dpp_bootstrap_info_free(bi);
+	return ret;
+}
+
+
+static struct dpp_bootstrap_info *
+dpp_bootstrap_get_id(struct hostapd_data *hapd, unsigned int id)
+{
+	struct dpp_bootstrap_info *bi;
+
+	dl_list_for_each(bi, &hapd->iface->interfaces->dpp_bootstrap,
+			 struct dpp_bootstrap_info, list) {
+		if (bi->id == id)
+			return bi;
+	}
+	return NULL;
+}
+
+
+static int dpp_bootstrap_del(struct hapd_interfaces *ifaces, unsigned int id)
+{
+	struct dpp_bootstrap_info *bi, *tmp;
+	int found = 0;
+
+	dl_list_for_each_safe(bi, tmp, &ifaces->dpp_bootstrap,
+			      struct dpp_bootstrap_info, list) {
+		if (id && bi->id != id)
+			continue;
+		found = 1;
+		dl_list_del(&bi->list);
+		dpp_bootstrap_info_free(bi);
+	}
+
+	if (id == 0)
+		return 0; /* flush succeeds regardless of entries found */
+	return found ? 0 : -1;
+}
+
+
+int hostapd_dpp_bootstrap_remove(struct hostapd_data *hapd, const char *id)
+{
+	unsigned int id_val;
+
+	if (os_strcmp(id, "*") == 0) {
+		id_val = 0;
+	} else {
+		id_val = atoi(id);
+		if (id_val == 0)
+			return -1;
+	}
+
+	return dpp_bootstrap_del(hapd->iface->interfaces, id_val);
+}
+
+
+const char * hostapd_dpp_bootstrap_get_uri(struct hostapd_data *hapd,
+					   unsigned int id)
+{
+	struct dpp_bootstrap_info *bi;
+
+	bi = dpp_bootstrap_get_id(hapd, id);
+	if (!bi)
+		return NULL;
+	return bi->uri;
+}
+
+
+int hostapd_dpp_bootstrap_info(struct hostapd_data *hapd, int id,
+			       char *reply, int reply_size)
+{
+	struct dpp_bootstrap_info *bi;
+
+	bi = dpp_bootstrap_get_id(hapd, id);
+	if (!bi)
+		return -1;
+	return os_snprintf(reply, reply_size, "type=%s\n"
+			   "mac_addr=" MACSTR "\n"
+			   "info=%s\n"
+			   "num_freq=%u\n"
+			   "curve=%s\n",
+			   dpp_bootstrap_type_txt(bi->type),
+			   MAC2STR(bi->mac_addr),
+			   bi->info ? bi->info : "",
+			   bi->num_freq,
+			   bi->curve->name);
+}
+
+
+static void hostapd_dpp_auth_resp_retry_timeout(void *eloop_ctx,
+						void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct dpp_authentication *auth = hapd->dpp_auth;
+
+	if (!auth || !auth->resp_msg)
+		return;
+
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Retry Authentication Response after timeout");
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d",
+		MAC2STR(auth->peer_mac_addr), auth->curr_freq,
+		DPP_PA_AUTHENTICATION_RESP);
+	hostapd_drv_send_action(hapd, auth->curr_freq, 500, auth->peer_mac_addr,
+				wpabuf_head(auth->resp_msg),
+				wpabuf_len(auth->resp_msg));
+}
+
+
+static void hostapd_dpp_auth_resp_retry(struct hostapd_data *hapd)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	unsigned int wait_time, max_tries;
+
+	if (!auth || !auth->resp_msg)
+		return;
+
+	if (hapd->dpp_resp_max_tries)
+		max_tries = hapd->dpp_resp_max_tries;
+	else
+		max_tries = 5;
+	auth->auth_resp_tries++;
+	if (auth->auth_resp_tries >= max_tries) {
+		wpa_printf(MSG_INFO,
+			   "DPP: No confirm received from initiator - stopping exchange");
+		hostapd_drv_send_action_cancel_wait(hapd);
+		dpp_auth_deinit(hapd->dpp_auth);
+		hapd->dpp_auth = NULL;
+		return;
+	}
+
+	if (hapd->dpp_resp_retry_time)
+		wait_time = hapd->dpp_resp_retry_time;
+	else
+		wait_time = 1000;
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Schedule retransmission of Authentication Response frame in %u ms",
+		wait_time);
+	eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL);
+	eloop_register_timeout(wait_time / 1000,
+			       (wait_time % 1000) * 1000,
+			       hostapd_dpp_auth_resp_retry_timeout, hapd, NULL);
+}
+
+
+void hostapd_dpp_tx_status(struct hostapd_data *hapd, const u8 *dst,
+			   const u8 *data, size_t data_len, int ok)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+
+	wpa_printf(MSG_DEBUG, "DPP: TX status: dst=" MACSTR " ok=%d",
+		   MAC2STR(dst), ok);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX_STATUS "dst=" MACSTR
+		" result=%s", MAC2STR(dst), ok ? "SUCCESS" : "FAILED");
+
+	if (!hapd->dpp_auth) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Ignore TX status since there is no ongoing authentication exchange");
+		return;
+	}
+
+	if (hapd->dpp_auth->remove_on_tx_status) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Terminate authentication exchange due to an earlier error");
+		eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL);
+		eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout,
+				     hapd, NULL);
+		eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd,
+				     NULL);
+		hostapd_drv_send_action_cancel_wait(hapd);
+		dpp_auth_deinit(hapd->dpp_auth);
+		hapd->dpp_auth = NULL;
+		return;
+	}
+
+	if (hapd->dpp_auth_ok_on_ack)
+		hostapd_dpp_auth_success(hapd, 1);
+
+	if (!is_broadcast_ether_addr(dst) && !ok) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unicast DPP Action frame was not ACKed");
+		if (auth->waiting_auth_resp) {
+			/* In case of DPP Authentication Request frame, move to
+			 * the next channel immediately. */
+			hostapd_drv_send_action_cancel_wait(hapd);
+			hostapd_dpp_auth_init_next(hapd);
+			return;
+		}
+		if (auth->waiting_auth_conf) {
+			hostapd_dpp_auth_resp_retry(hapd);
+			return;
+		}
+	}
+
+	if (!is_broadcast_ether_addr(dst) && auth->waiting_auth_resp && ok) {
+		/* Allow timeout handling to stop iteration if no response is
+		 * received from a peer that has ACKed a request. */
+		auth->auth_req_ack = 1;
+	}
+
+	if (!hapd->dpp_auth_ok_on_ack && hapd->dpp_auth->neg_freq > 0 &&
+	    hapd->dpp_auth->curr_freq != hapd->dpp_auth->neg_freq) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Move from curr_freq %u MHz to neg_freq %u MHz for response",
+			   hapd->dpp_auth->curr_freq,
+			   hapd->dpp_auth->neg_freq);
+		hostapd_drv_send_action_cancel_wait(hapd);
+
+		if (hapd->dpp_auth->neg_freq !=
+		    (unsigned int) hapd->iface->freq && hapd->iface->freq > 0) {
+			/* TODO: Listen operation on non-operating channel */
+			wpa_printf(MSG_INFO,
+				   "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)",
+				   hapd->dpp_auth->neg_freq, hapd->iface->freq);
+		}
+	}
+
+	if (hapd->dpp_auth_ok_on_ack)
+		hapd->dpp_auth_ok_on_ack = 0;
+}
+
+
+static void hostapd_dpp_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	unsigned int freq;
+	struct os_reltime now, diff;
+	unsigned int wait_time, diff_ms;
+
+	if (!auth || !auth->waiting_auth_resp)
+		return;
+
+	wait_time = hapd->dpp_resp_wait_time ?
+		hapd->dpp_resp_wait_time : 2000;
+	os_get_reltime(&now);
+	os_reltime_sub(&now, &hapd->dpp_last_init, &diff);
+	diff_ms = diff.sec * 1000 + diff.usec / 1000;
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Reply wait timeout - wait_time=%u diff_ms=%u",
+		   wait_time, diff_ms);
+
+	if (auth->auth_req_ack && diff_ms >= wait_time) {
+		/* Peer ACK'ed Authentication Request frame, but did not reply
+		 * with Authentication Response frame within two seconds. */
+		wpa_printf(MSG_INFO,
+			   "DPP: No response received from responder - stopping initiation attempt");
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_AUTH_INIT_FAILED);
+		hostapd_drv_send_action_cancel_wait(hapd);
+		hostapd_dpp_listen_stop(hapd);
+		dpp_auth_deinit(auth);
+		hapd->dpp_auth = NULL;
+		return;
+	}
+
+	if (diff_ms >= wait_time) {
+		/* Authentication Request frame was not ACK'ed and no reply
+		 * was receiving within two seconds. */
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Continue Initiator channel iteration");
+		hostapd_drv_send_action_cancel_wait(hapd);
+		hostapd_dpp_listen_stop(hapd);
+		hostapd_dpp_auth_init_next(hapd);
+		return;
+	}
+
+	/* Driver did not support 2000 ms long wait_time with TX command, so
+	 * schedule listen operation to continue waiting for the response.
+	 *
+	 * DPP listen operations continue until stopped, so simply schedule a
+	 * new call to this function at the point when the two second reply
+	 * wait has expired. */
+	wait_time -= diff_ms;
+
+	freq = auth->curr_freq;
+	if (auth->neg_freq > 0)
+		freq = auth->neg_freq;
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Continue reply wait on channel %u MHz for %u ms",
+		   freq, wait_time);
+	hapd->dpp_in_response_listen = 1;
+
+	if (freq != (unsigned int) hapd->iface->freq && hapd->iface->freq > 0) {
+		/* TODO: Listen operation on non-operating channel */
+		wpa_printf(MSG_INFO,
+			   "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)",
+			   freq, hapd->iface->freq);
+	}
+
+	eloop_register_timeout(wait_time / 1000, (wait_time % 1000) * 1000,
+			       hostapd_dpp_reply_wait_timeout, hapd, NULL);
+}
+
+
+static void hostapd_dpp_set_testing_options(struct hostapd_data *hapd,
+					    struct dpp_authentication *auth)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	if (hapd->dpp_config_obj_override)
+		auth->config_obj_override =
+			os_strdup(hapd->dpp_config_obj_override);
+	if (hapd->dpp_discovery_override)
+		auth->discovery_override =
+			os_strdup(hapd->dpp_discovery_override);
+	if (hapd->dpp_groups_override)
+		auth->groups_override = os_strdup(hapd->dpp_groups_override);
+	auth->ignore_netaccesskey_mismatch =
+		hapd->dpp_ignore_netaccesskey_mismatch;
+#endif /* CONFIG_TESTING_OPTIONS */
+}
+
+
+static void hostapd_dpp_set_configurator(struct hostapd_data *hapd,
+					 struct dpp_authentication *auth,
+					 const char *cmd)
+{
+	const char *pos, *end;
+	struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL;
+	struct dpp_configurator *conf = NULL;
+	u8 ssid[32] = { "test" };
+	size_t ssid_len = 4;
+	char pass[64] = { };
+	size_t pass_len = 0;
+	u8 psk[PMK_LEN];
+	int psk_set = 0;
+
+	if (!cmd)
+		return;
+
+	wpa_printf(MSG_DEBUG, "DPP: Set configurator parameters: %s", cmd);
+	pos = os_strstr(cmd, " ssid=");
+	if (pos) {
+		pos += 6;
+		end = os_strchr(pos, ' ');
+		ssid_len = end ? (size_t) (end - pos) : os_strlen(pos);
+		ssid_len /= 2;
+		if (ssid_len > sizeof(ssid) ||
+		    hexstr2bin(pos, ssid, ssid_len) < 0)
+			goto fail;
+	}
+
+	pos = os_strstr(cmd, " pass=");
+	if (pos) {
+		pos += 6;
+		end = os_strchr(pos, ' ');
+		pass_len = end ? (size_t) (end - pos) : os_strlen(pos);
+		pass_len /= 2;
+		if (pass_len > sizeof(pass) - 1 || pass_len < 8 ||
+		    hexstr2bin(pos, (u8 *) pass, pass_len) < 0)
+			goto fail;
+	}
+
+	pos = os_strstr(cmd, " psk=");
+	if (pos) {
+		pos += 5;
+		if (hexstr2bin(pos, psk, PMK_LEN) < 0)
+			goto fail;
+		psk_set = 1;
+	}
+
+	if (os_strstr(cmd, " conf=sta-")) {
+		conf_sta = os_zalloc(sizeof(struct dpp_configuration));
+		if (!conf_sta)
+			goto fail;
+		os_memcpy(conf_sta->ssid, ssid, ssid_len);
+		conf_sta->ssid_len = ssid_len;
+		if (os_strstr(cmd, " conf=sta-psk") ||
+		    os_strstr(cmd, " conf=sta-sae") ||
+		    os_strstr(cmd, " conf=sta-psk-sae")) {
+			if (os_strstr(cmd, " conf=sta-psk-sae"))
+				conf_sta->akm = DPP_AKM_PSK_SAE;
+			else if (os_strstr(cmd, " conf=sta-sae"))
+				conf_sta->akm = DPP_AKM_SAE;
+			else
+				conf_sta->akm = DPP_AKM_PSK;
+			if (psk_set) {
+				os_memcpy(conf_sta->psk, psk, PMK_LEN);
+			} else {
+				conf_sta->passphrase = os_strdup(pass);
+				if (!conf_sta->passphrase)
+					goto fail;
+			}
+		} else if (os_strstr(cmd, " conf=sta-dpp")) {
+			conf_sta->akm = DPP_AKM_DPP;
+		} else {
+			goto fail;
+		}
+	}
+
+	if (os_strstr(cmd, " conf=ap-")) {
+		conf_ap = os_zalloc(sizeof(struct dpp_configuration));
+		if (!conf_ap)
+			goto fail;
+		os_memcpy(conf_ap->ssid, ssid, ssid_len);
+		conf_ap->ssid_len = ssid_len;
+		if (os_strstr(cmd, " conf=ap-psk") ||
+		    os_strstr(cmd, " conf=ap-sae") ||
+		    os_strstr(cmd, " conf=ap-psk-sae")) {
+			if (os_strstr(cmd, " conf=ap-psk-sae"))
+				conf_ap->akm = DPP_AKM_PSK_SAE;
+			else if (os_strstr(cmd, " conf=ap-sae"))
+				conf_ap->akm = DPP_AKM_SAE;
+			else
+				conf_ap->akm = DPP_AKM_PSK;
+			if (psk_set) {
+				os_memcpy(conf_ap->psk, psk, PMK_LEN);
+			} else {
+				conf_ap->passphrase = os_strdup(pass);
+				if (!conf_ap->passphrase)
+					goto fail;
+			}
+		} else if (os_strstr(cmd, " conf=ap-dpp")) {
+			conf_ap->akm = DPP_AKM_DPP;
+		} else {
+			goto fail;
+		}
+	}
+
+	pos = os_strstr(cmd, " expiry=");
+	if (pos) {
+		long int val;
+
+		pos += 8;
+		val = strtol(pos, NULL, 0);
+		if (val <= 0)
+			goto fail;
+		if (conf_sta)
+			conf_sta->netaccesskey_expiry = val;
+		if (conf_ap)
+			conf_ap->netaccesskey_expiry = val;
+	}
+
+	pos = os_strstr(cmd, " configurator=");
+	if (pos) {
+		auth->configurator = 1;
+		pos += 14;
+		conf = hostapd_dpp_configurator_get_id(hapd, atoi(pos));
+		if (!conf) {
+			wpa_printf(MSG_INFO,
+				   "DPP: Could not find the specified configurator");
+			goto fail;
+		}
+	}
+	auth->conf_sta = conf_sta;
+	auth->conf_ap = conf_ap;
+	auth->conf = conf;
+	return;
+
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: Failed to set configurator parameters");
+	dpp_configuration_free(conf_sta);
+	dpp_configuration_free(conf_ap);
+}
+
+
+static void hostapd_dpp_init_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+
+	if (!hapd->dpp_auth)
+		return;
+	wpa_printf(MSG_DEBUG, "DPP: Retry initiation after timeout");
+	hostapd_dpp_auth_init_next(hapd);
+}
+
+
+static int hostapd_dpp_auth_init_next(struct hostapd_data *hapd)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	const u8 *dst;
+	unsigned int wait_time, max_wait_time, freq, max_tries, used;
+	struct os_reltime now, diff;
+
+	if (!auth)
+		return -1;
+
+	if (auth->freq_idx == 0)
+		os_get_reltime(&hapd->dpp_init_iter_start);
+
+	if (auth->freq_idx >= auth->num_freq) {
+		auth->num_freq_iters++;
+		if (hapd->dpp_init_max_tries)
+			max_tries = hapd->dpp_init_max_tries;
+		else
+			max_tries = 5;
+		if (auth->num_freq_iters >= max_tries || auth->auth_req_ack) {
+			wpa_printf(MSG_INFO,
+				   "DPP: No response received from responder - stopping initiation attempt");
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
+				DPP_EVENT_AUTH_INIT_FAILED);
+			eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout,
+					     hapd, NULL);
+			hostapd_drv_send_action_cancel_wait(hapd);
+			dpp_auth_deinit(hapd->dpp_auth);
+			hapd->dpp_auth = NULL;
+			return -1;
+		}
+		auth->freq_idx = 0;
+		eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL);
+		if (hapd->dpp_init_retry_time)
+			wait_time = hapd->dpp_init_retry_time;
+		else
+			wait_time = 10000;
+		os_get_reltime(&now);
+		os_reltime_sub(&now, &hapd->dpp_init_iter_start, &diff);
+		used = diff.sec * 1000 + diff.usec / 1000;
+		if (used > wait_time)
+			wait_time = 0;
+		else
+			wait_time -= used;
+		wpa_printf(MSG_DEBUG, "DPP: Next init attempt in %u ms",
+			   wait_time);
+		eloop_register_timeout(wait_time / 1000,
+				       (wait_time % 1000) * 1000,
+				       hostapd_dpp_init_timeout, hapd,
+				       NULL);
+		return 0;
+	}
+	freq = auth->freq[auth->freq_idx++];
+	auth->curr_freq = freq;
+
+	if (is_zero_ether_addr(auth->peer_bi->mac_addr))
+		dst = broadcast;
+	else
+		dst = auth->peer_bi->mac_addr;
+	hapd->dpp_auth_ok_on_ack = 0;
+	eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL);
+	wait_time = 2000; /* TODO: hapd->max_remain_on_chan; */
+	max_wait_time = hapd->dpp_resp_wait_time ?
+		hapd->dpp_resp_wait_time : 2000;
+	if (wait_time > max_wait_time)
+		wait_time = max_wait_time;
+	wait_time += 10; /* give the driver some extra time to complete */
+	eloop_register_timeout(wait_time / 1000, (wait_time % 1000) * 1000,
+			       hostapd_dpp_reply_wait_timeout, hapd, NULL);
+	wait_time -= 10;
+	if (auth->neg_freq > 0 && freq != auth->neg_freq) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Initiate on %u MHz and move to neg_freq %u MHz for response",
+			   freq, auth->neg_freq);
+	}
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d",
+		MAC2STR(dst), freq, DPP_PA_AUTHENTICATION_REQ);
+	auth->auth_req_ack = 0;
+	os_get_reltime(&hapd->dpp_last_init);
+	return hostapd_drv_send_action(hapd, freq, wait_time,
+				       dst,
+				       wpabuf_head(hapd->dpp_auth->req_msg),
+				       wpabuf_len(hapd->dpp_auth->req_msg));
+}
+
+
+int hostapd_dpp_auth_init(struct hostapd_data *hapd, const char *cmd)
+{
+	const char *pos;
+	struct dpp_bootstrap_info *peer_bi, *own_bi = NULL;
+	u8 allowed_roles = DPP_CAPAB_CONFIGURATOR;
+	unsigned int neg_freq = 0;
+
+	pos = os_strstr(cmd, " peer=");
+	if (!pos)
+		return -1;
+	pos += 6;
+	peer_bi = dpp_bootstrap_get_id(hapd, atoi(pos));
+	if (!peer_bi) {
+		wpa_printf(MSG_INFO,
+			   "DPP: Could not find bootstrapping info for the identified peer");
+		return -1;
+	}
+
+	pos = os_strstr(cmd, " own=");
+	if (pos) {
+		pos += 5;
+		own_bi = dpp_bootstrap_get_id(hapd, atoi(pos));
+		if (!own_bi) {
+			wpa_printf(MSG_INFO,
+				   "DPP: Could not find bootstrapping info for the identified local entry");
+			return -1;
+		}
+
+		if (peer_bi->curve != own_bi->curve) {
+			wpa_printf(MSG_INFO,
+				   "DPP: Mismatching curves in bootstrapping info (peer=%s own=%s)",
+				   peer_bi->curve->name, own_bi->curve->name);
+			return -1;
+		}
+	}
+
+	pos = os_strstr(cmd, " role=");
+	if (pos) {
+		pos += 6;
+		if (os_strncmp(pos, "configurator", 12) == 0)
+			allowed_roles = DPP_CAPAB_CONFIGURATOR;
+		else if (os_strncmp(pos, "enrollee", 8) == 0)
+			allowed_roles = DPP_CAPAB_ENROLLEE;
+		else if (os_strncmp(pos, "either", 6) == 0)
+			allowed_roles = DPP_CAPAB_CONFIGURATOR |
+				DPP_CAPAB_ENROLLEE;
+		else
+			goto fail;
+	}
+
+	pos = os_strstr(cmd, " neg_freq=");
+	if (pos)
+		neg_freq = atoi(pos + 10);
+
+	if (hapd->dpp_auth) {
+		eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL);
+		eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout,
+				     hapd, NULL);
+		eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd,
+				     NULL);
+		hostapd_drv_send_action_cancel_wait(hapd);
+		dpp_auth_deinit(hapd->dpp_auth);
+	}
+
+	hapd->dpp_auth = dpp_auth_init(hapd->msg_ctx, peer_bi, own_bi,
+				       allowed_roles, neg_freq,
+				       hapd->iface->hw_features,
+				       hapd->iface->num_hw_features);
+	if (!hapd->dpp_auth)
+		goto fail;
+	hostapd_dpp_set_testing_options(hapd, hapd->dpp_auth);
+	hostapd_dpp_set_configurator(hapd, hapd->dpp_auth, cmd);
+
+	hapd->dpp_auth->neg_freq = neg_freq;
+
+	if (!is_zero_ether_addr(peer_bi->mac_addr))
+		os_memcpy(hapd->dpp_auth->peer_mac_addr, peer_bi->mac_addr,
+			  ETH_ALEN);
+
+	return hostapd_dpp_auth_init_next(hapd);
+fail:
+	return -1;
+}
+
+
+int hostapd_dpp_listen(struct hostapd_data *hapd, const char *cmd)
+{
+	int freq;
+
+	freq = atoi(cmd);
+	if (freq <= 0)
+		return -1;
+
+	if (os_strstr(cmd, " role=configurator"))
+		hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR;
+	else if (os_strstr(cmd, " role=enrollee"))
+		hapd->dpp_allowed_roles = DPP_CAPAB_ENROLLEE;
+	else
+		hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR |
+			DPP_CAPAB_ENROLLEE;
+	hapd->dpp_qr_mutual = os_strstr(cmd, " qr=mutual") != NULL;
+
+	if (freq != hapd->iface->freq && hapd->iface->freq > 0) {
+		/* TODO: Listen operation on non-operating channel */
+		wpa_printf(MSG_INFO,
+			   "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)",
+			   freq, hapd->iface->freq);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+void hostapd_dpp_listen_stop(struct hostapd_data *hapd)
+{
+	/* TODO: Stop listen operation on non-operating channel */
+}
+
+
+static void hostapd_dpp_rx_auth_req(struct hostapd_data *hapd, const u8 *src,
+				    const u8 *hdr, const u8 *buf, size_t len,
+				    unsigned int freq)
+{
+	const u8 *r_bootstrap, *i_bootstrap;
+	u16 r_bootstrap_len, i_bootstrap_len;
+	struct dpp_bootstrap_info *bi, *own_bi = NULL, *peer_bi = NULL;
+
+	wpa_printf(MSG_DEBUG, "DPP: Authentication Request from " MACSTR,
+		   MAC2STR(src));
+
+	r_bootstrap = dpp_get_attr(buf, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
+				   &r_bootstrap_len);
+	if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Missing or invalid required Responder Bootstrapping Key Hash attribute");
+		return;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash",
+		    r_bootstrap, r_bootstrap_len);
+
+	i_bootstrap = dpp_get_attr(buf, len, DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+				   &i_bootstrap_len);
+	if (!i_bootstrap || i_bootstrap_len != SHA256_MAC_LEN) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Missing or invalid required Initiator Bootstrapping Key Hash attribute");
+		return;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Bootstrapping Key Hash",
+		    i_bootstrap, i_bootstrap_len);
+
+	/* Try to find own and peer bootstrapping key matches based on the
+	 * received hash values */
+	dl_list_for_each(bi, &hapd->iface->interfaces->dpp_bootstrap,
+			 struct dpp_bootstrap_info, list) {
+		if (!own_bi && bi->own &&
+		    os_memcmp(bi->pubkey_hash, r_bootstrap,
+			      SHA256_MAC_LEN) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Found matching own bootstrapping information");
+			own_bi = bi;
+		}
+
+		if (!peer_bi && !bi->own &&
+		    os_memcmp(bi->pubkey_hash, i_bootstrap,
+			      SHA256_MAC_LEN) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Found matching peer bootstrapping information");
+			peer_bi = bi;
+		}
+
+		if (own_bi && peer_bi)
+			break;
+	}
+
+	if (!own_bi) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"No matching own bootstrapping key found - ignore message");
+		return;
+	}
+
+	if (hapd->dpp_auth) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Already in DPP authentication exchange - ignore new one");
+		return;
+	}
+
+	hapd->dpp_auth_ok_on_ack = 0;
+	hapd->dpp_auth = dpp_auth_req_rx(hapd->msg_ctx, hapd->dpp_allowed_roles,
+					 hapd->dpp_qr_mutual,
+					 peer_bi, own_bi, freq, hdr, buf, len);
+	if (!hapd->dpp_auth) {
+		wpa_printf(MSG_DEBUG, "DPP: No response generated");
+		return;
+	}
+	hostapd_dpp_set_testing_options(hapd, hapd->dpp_auth);
+	hostapd_dpp_set_configurator(hapd, hapd->dpp_auth,
+				     hapd->dpp_configurator_params);
+	os_memcpy(hapd->dpp_auth->peer_mac_addr, src, ETH_ALEN);
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d",
+		MAC2STR(src), hapd->dpp_auth->curr_freq,
+		DPP_PA_AUTHENTICATION_RESP);
+	hostapd_drv_send_action(hapd, hapd->dpp_auth->curr_freq, 0,
+				src, wpabuf_head(hapd->dpp_auth->resp_msg),
+				wpabuf_len(hapd->dpp_auth->resp_msg));
+}
+
+
+static void hostapd_dpp_handle_config_obj(struct hostapd_data *hapd,
+					  struct dpp_authentication *auth)
+{
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_RECEIVED);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_AKM "%s",
+		dpp_akm_str(auth->akm));
+	if (auth->ssid_len)
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_SSID "%s",
+			wpa_ssid_txt(auth->ssid, auth->ssid_len));
+	if (auth->connector) {
+		/* TODO: Save the Connector and consider using a command
+		 * to fetch the value instead of sending an event with
+		 * it. The Connector could end up being larger than what
+		 * most clients are ready to receive as an event
+		 * message. */
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONNECTOR "%s",
+			auth->connector);
+	} else if (auth->passphrase[0]) {
+		char hex[64 * 2 + 1];
+
+		wpa_snprintf_hex(hex, sizeof(hex),
+				 (const u8 *) auth->passphrase,
+				 os_strlen(auth->passphrase));
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_PASS "%s",
+			hex);
+	} else if (auth->psk_set) {
+		char hex[PMK_LEN * 2 + 1];
+
+		wpa_snprintf_hex(hex, sizeof(hex), auth->psk, PMK_LEN);
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_PSK "%s",
+			hex);
+	}
+	if (auth->c_sign_key) {
+		char *hex;
+		size_t hexlen;
+
+		hexlen = 2 * wpabuf_len(auth->c_sign_key) + 1;
+		hex = os_malloc(hexlen);
+		if (hex) {
+			wpa_snprintf_hex(hex, hexlen,
+					 wpabuf_head(auth->c_sign_key),
+					 wpabuf_len(auth->c_sign_key));
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
+				DPP_EVENT_C_SIGN_KEY "%s", hex);
+			os_free(hex);
+		}
+	}
+	if (auth->net_access_key) {
+		char *hex;
+		size_t hexlen;
+
+		hexlen = 2 * wpabuf_len(auth->net_access_key) + 1;
+		hex = os_malloc(hexlen);
+		if (hex) {
+			wpa_snprintf_hex(hex, hexlen,
+					 wpabuf_head(auth->net_access_key),
+					 wpabuf_len(auth->net_access_key));
+			if (auth->net_access_key_expiry)
+				wpa_msg(hapd->msg_ctx, MSG_INFO,
+					DPP_EVENT_NET_ACCESS_KEY "%s %lu", hex,
+					(unsigned long)
+					auth->net_access_key_expiry);
+			else
+				wpa_msg(hapd->msg_ctx, MSG_INFO,
+					DPP_EVENT_NET_ACCESS_KEY "%s", hex);
+			os_free(hex);
+		}
+	}
+}
+
+
+static void hostapd_dpp_gas_resp_cb(void *ctx, const u8 *addr, u8 dialog_token,
+				    enum gas_query_ap_result result,
+				    const struct wpabuf *adv_proto,
+				    const struct wpabuf *resp, u16 status_code)
+{
+	struct hostapd_data *hapd = ctx;
+	const u8 *pos;
+	struct dpp_authentication *auth = hapd->dpp_auth;
+
+	if (!auth || !auth->auth_success) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress");
+		return;
+	}
+	if (!resp || status_code != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "DPP: GAS query did not succeed");
+		goto fail;
+	}
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: Configuration Response adv_proto",
+			adv_proto);
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: Configuration Response (GAS response)",
+			resp);
+
+	if (wpabuf_len(adv_proto) != 10 ||
+	    !(pos = wpabuf_head(adv_proto)) ||
+	    pos[0] != WLAN_EID_ADV_PROTO ||
+	    pos[1] != 8 ||
+	    pos[3] != WLAN_EID_VENDOR_SPECIFIC ||
+	    pos[4] != 5 ||
+	    WPA_GET_BE24(&pos[5]) != OUI_WFA ||
+	    pos[8] != 0x1a ||
+	    pos[9] != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Not a DPP Advertisement Protocol ID");
+		goto fail;
+	}
+
+	if (dpp_conf_resp_rx(auth, resp) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Configuration attempt failed");
+		goto fail;
+	}
+
+	hostapd_dpp_handle_config_obj(hapd, auth);
+	dpp_auth_deinit(hapd->dpp_auth);
+	hapd->dpp_auth = NULL;
+	return;
+
+fail:
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED);
+	dpp_auth_deinit(hapd->dpp_auth);
+	hapd->dpp_auth = NULL;
+}
+
+
+static void hostapd_dpp_start_gas_client(struct hostapd_data *hapd)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	struct wpabuf *buf, *conf_req;
+	char json[100];
+	int res;
+	int netrole_ap = 1;
+
+	os_snprintf(json, sizeof(json),
+		    "{\"name\":\"Test\","
+		    "\"wi-fi_tech\":\"infra\","
+		    "\"netRole\":\"%s\"}",
+		    netrole_ap ? "ap" : "sta");
+	wpa_printf(MSG_DEBUG, "DPP: GAS Config Attributes: %s", json);
+
+	conf_req = dpp_build_conf_req(auth, json);
+	if (!conf_req) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No configuration request data available");
+		return;
+	}
+
+	buf = gas_build_initial_req(0, 10 + 2 + wpabuf_len(conf_req));
+	if (!buf) {
+		wpabuf_free(conf_req);
+		return;
+	}
+
+	/* Advertisement Protocol IE */
+	wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO);
+	wpabuf_put_u8(buf, 8); /* Length */
+	wpabuf_put_u8(buf, 0x7f);
+	wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
+	wpabuf_put_u8(buf, 5);
+	wpabuf_put_be24(buf, OUI_WFA);
+	wpabuf_put_u8(buf, DPP_OUI_TYPE);
+	wpabuf_put_u8(buf, 0x01);
+
+	/* GAS Query */
+	wpabuf_put_le16(buf, wpabuf_len(conf_req));
+	wpabuf_put_buf(buf, conf_req);
+	wpabuf_free(conf_req);
+
+	wpa_printf(MSG_DEBUG, "DPP: GAS request to " MACSTR " (freq %u MHz)",
+		   MAC2STR(auth->peer_mac_addr), auth->curr_freq);
+
+	res = gas_query_ap_req(hapd->gas, auth->peer_mac_addr, auth->curr_freq,
+			       buf, hostapd_dpp_gas_resp_cb, hapd);
+	if (res < 0) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			"GAS: Failed to send Query Request");
+		wpabuf_free(buf);
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: GAS query started with dialog token %u", res);
+	}
+}
+
+
+static void hostapd_dpp_auth_success(struct hostapd_data *hapd, int initiator)
+{
+	wpa_printf(MSG_DEBUG, "DPP: Authentication succeeded");
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_AUTH_SUCCESS "init=%d",
+		initiator);
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at Authentication Confirm");
+		if (hapd->dpp_auth->configurator) {
+			/* Prevent GAS response */
+			hapd->dpp_auth->auth_success = 0;
+		}
+		return;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (!hapd->dpp_auth->configurator)
+		hostapd_dpp_start_gas_client(hapd);
+}
+
+
+static void hostapd_dpp_rx_auth_resp(struct hostapd_data *hapd, const u8 *src,
+				     const u8 *hdr, const u8 *buf, size_t len,
+				     unsigned int freq)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	struct wpabuf *msg;
+
+	wpa_printf(MSG_DEBUG, "DPP: Authentication Response from " MACSTR,
+		   MAC2STR(src));
+
+	if (!auth) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No DPP Authentication in progress - drop");
+		return;
+	}
+
+	if (!is_zero_ether_addr(auth->peer_mac_addr) &&
+	    os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
+			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
+		return;
+	}
+
+	eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL);
+
+	if (auth->curr_freq != freq && auth->neg_freq == freq) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder accepted request for different negotiation channel");
+		auth->curr_freq = freq;
+	}
+
+	eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL);
+	msg = dpp_auth_resp_rx(auth, hdr, buf, len);
+	if (!msg) {
+		if (auth->auth_resp_status == DPP_STATUS_RESPONSE_PENDING) {
+			wpa_printf(MSG_DEBUG, "DPP: Wait for full response");
+			return;
+		}
+		wpa_printf(MSG_DEBUG, "DPP: No confirm generated");
+		return;
+	}
+	os_memcpy(auth->peer_mac_addr, src, ETH_ALEN);
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d", MAC2STR(src), auth->curr_freq,
+		DPP_PA_AUTHENTICATION_CONF);
+	hostapd_drv_send_action(hapd, auth->curr_freq, 0, src,
+				wpabuf_head(msg), wpabuf_len(msg));
+	wpabuf_free(msg);
+	hapd->dpp_auth_ok_on_ack = 1;
+}
+
+
+static void hostapd_dpp_rx_auth_conf(struct hostapd_data *hapd, const u8 *src,
+				     const u8 *hdr, const u8 *buf, size_t len)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+
+	wpa_printf(MSG_DEBUG, "DPP: Authentication Confirmation from " MACSTR,
+		   MAC2STR(src));
+
+	if (!auth) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No DPP Authentication in progress - drop");
+		return;
+	}
+
+	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
+			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
+		return;
+	}
+
+	if (dpp_auth_conf_rx(auth, hdr, buf, len) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Authentication failed");
+		return;
+	}
+
+	hostapd_dpp_auth_success(hapd, 0);
+}
+
+
+static void hostapd_dpp_send_peer_disc_resp(struct hostapd_data *hapd,
+					    const u8 *src, unsigned int freq,
+					    u8 trans_id,
+					    enum dpp_status_error status)
+{
+	struct wpabuf *msg;
+
+	msg = dpp_alloc_msg(DPP_PA_PEER_DISCOVERY_RESP,
+			    5 + 5 + 4 + os_strlen(hapd->conf->dpp_connector));
+	if (!msg)
+		return;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_TRANSACTION_ID_PEER_DISC_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Transaction ID");
+		goto skip_trans_id;
+	}
+	if (dpp_test == DPP_TEST_INVALID_TRANSACTION_ID_PEER_DISC_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Transaction ID");
+		trans_id ^= 0x01;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Transaction ID */
+	wpabuf_put_le16(msg, DPP_ATTR_TRANSACTION_ID);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, trans_id);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_trans_id:
+	if (dpp_test == DPP_TEST_NO_STATUS_PEER_DISC_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Status");
+		goto skip_status;
+	}
+	if (dpp_test == DPP_TEST_INVALID_STATUS_PEER_DISC_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status");
+		status = 254;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, status);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_status:
+	if (dpp_test == DPP_TEST_NO_CONNECTOR_PEER_DISC_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Connector");
+		goto skip_connector;
+	}
+	if (status == DPP_STATUS_OK &&
+	    dpp_test == DPP_TEST_INVALID_CONNECTOR_PEER_DISC_RESP) {
+		char *connector;
+
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Connector");
+		connector = dpp_corrupt_connector_signature(
+			hapd->conf->dpp_connector);
+		if (!connector) {
+			wpabuf_free(msg);
+			return;
+		}
+		wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR);
+		wpabuf_put_le16(msg, os_strlen(connector));
+		wpabuf_put_str(msg, connector);
+		os_free(connector);
+		goto skip_connector;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* DPP Connector */
+	if (status == DPP_STATUS_OK) {
+		wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR);
+		wpabuf_put_le16(msg, os_strlen(hapd->conf->dpp_connector));
+		wpabuf_put_str(msg, hapd->conf->dpp_connector);
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_connector:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_printf(MSG_DEBUG, "DPP: Send Peer Discovery Response to " MACSTR
+		   " status=%d", MAC2STR(src), status);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d status=%d", MAC2STR(src), freq,
+		DPP_PA_PEER_DISCOVERY_RESP, status);
+	hostapd_drv_send_action(hapd, freq, 0, src,
+				wpabuf_head(msg), wpabuf_len(msg));
+	wpabuf_free(msg);
+}
+
+
+static void hostapd_dpp_rx_peer_disc_req(struct hostapd_data *hapd,
+					 const u8 *src,
+					 const u8 *buf, size_t len,
+					 unsigned int freq)
+{
+	const u8 *connector, *trans_id;
+	u16 connector_len, trans_id_len;
+	struct os_time now;
+	struct dpp_introduction intro;
+	os_time_t expire;
+	int expiration;
+	enum dpp_status_error res;
+
+	wpa_printf(MSG_DEBUG, "DPP: Peer Discovery Request from " MACSTR,
+		   MAC2STR(src));
+	if (!hapd->wpa_auth ||
+	    !(hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) ||
+	    !(hapd->conf->wpa & WPA_PROTO_RSN)) {
+		wpa_printf(MSG_DEBUG, "DPP: DPP AKM not in use");
+		return;
+	}
+
+	if (!hapd->conf->dpp_connector || !hapd->conf->dpp_netaccesskey ||
+	    !hapd->conf->dpp_csign) {
+		wpa_printf(MSG_DEBUG, "DPP: No own Connector/keys set");
+		return;
+	}
+
+	os_get_time(&now);
+
+	if (hapd->conf->dpp_netaccesskey_expiry &&
+	    (os_time_t) hapd->conf->dpp_netaccesskey_expiry < now.sec) {
+		wpa_printf(MSG_INFO, "DPP: Own netAccessKey expired");
+		return;
+	}
+
+	trans_id = dpp_get_attr(buf, len, DPP_ATTR_TRANSACTION_ID,
+			       &trans_id_len);
+	if (!trans_id || trans_id_len != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Peer did not include Transaction ID");
+		return;
+	}
+
+	connector = dpp_get_attr(buf, len, DPP_ATTR_CONNECTOR, &connector_len);
+	if (!connector) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Peer did not include its Connector");
+		return;
+	}
+
+	res = dpp_peer_intro(&intro, hapd->conf->dpp_connector,
+			     wpabuf_head(hapd->conf->dpp_netaccesskey),
+			     wpabuf_len(hapd->conf->dpp_netaccesskey),
+			     wpabuf_head(hapd->conf->dpp_csign),
+			     wpabuf_len(hapd->conf->dpp_csign),
+			     connector, connector_len, &expire);
+	if (res == 255) {
+		wpa_printf(MSG_INFO,
+			   "DPP: Network Introduction protocol resulted in internal failure (peer "
+			   MACSTR ")", MAC2STR(src));
+		return;
+	}
+	if (res != DPP_STATUS_OK) {
+		wpa_printf(MSG_INFO,
+			   "DPP: Network Introduction protocol resulted in failure (peer "
+			   MACSTR " status %d)", MAC2STR(src), res);
+		hostapd_dpp_send_peer_disc_resp(hapd, src, freq, trans_id[0],
+						res);
+		return;
+	}
+
+	if (!expire || (os_time_t) hapd->conf->dpp_netaccesskey_expiry < expire)
+		expire = hapd->conf->dpp_netaccesskey_expiry;
+	if (expire)
+		expiration = expire - now.sec;
+	else
+		expiration = 0;
+
+	if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
+				intro.pmkid, expiration,
+				WPA_KEY_MGMT_DPP) < 0) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry");
+		return;
+	}
+
+	hostapd_dpp_send_peer_disc_resp(hapd, src, freq, trans_id[0],
+					DPP_STATUS_OK);
+}
+
+
+static void
+hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src,
+				 const u8 *buf, size_t len,
+				 unsigned int freq)
+{
+	struct wpabuf *msg;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Request from " MACSTR,
+		   MAC2STR(src));
+
+	/* TODO: Support multiple PKEX codes by iterating over all the enabled
+	 * values here */
+
+	if (!hapd->dpp_pkex_code || !hapd->dpp_pkex_bi) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code configured - ignore request");
+		return;
+	}
+
+	if (hapd->dpp_pkex) {
+		/* TODO: Support parallel operations */
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Already in PKEX session - ignore new request");
+		return;
+	}
+
+	hapd->dpp_pkex = dpp_pkex_rx_exchange_req(hapd->msg_ctx,
+						  hapd->dpp_pkex_bi,
+						  hapd->own_addr, src,
+						  hapd->dpp_pkex_identifier,
+						  hapd->dpp_pkex_code,
+						  buf, len);
+	if (!hapd->dpp_pkex) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to process the request - ignore it");
+		return;
+	}
+
+	msg = hapd->dpp_pkex->exchange_resp;
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d", MAC2STR(src), freq,
+		DPP_PA_PKEX_EXCHANGE_RESP);
+	hostapd_drv_send_action(hapd, freq, 0, src,
+				wpabuf_head(msg), wpabuf_len(msg));
+	if (hapd->dpp_pkex->failed) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Terminate PKEX exchange due to an earlier error");
+		if (hapd->dpp_pkex->t > hapd->dpp_pkex->own_bi->pkex_t)
+			hapd->dpp_pkex->own_bi->pkex_t = hapd->dpp_pkex->t;
+		dpp_pkex_free(hapd->dpp_pkex);
+		hapd->dpp_pkex = NULL;
+	}
+}
+
+
+static void
+hostapd_dpp_rx_pkex_exchange_resp(struct hostapd_data *hapd, const u8 *src,
+				  const u8 *buf, size_t len, unsigned int freq)
+{
+	struct wpabuf *msg;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Response from " MACSTR,
+		   MAC2STR(src));
+
+	/* TODO: Support multiple PKEX codes by iterating over all the enabled
+	 * values here */
+
+	if (!hapd->dpp_pkex || !hapd->dpp_pkex->initiator ||
+	    hapd->dpp_pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return;
+	}
+
+	msg = dpp_pkex_rx_exchange_resp(hapd->dpp_pkex, src, buf, len);
+	if (!msg) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to process the response");
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Request to " MACSTR,
+		   MAC2STR(src));
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d", MAC2STR(src), freq,
+		DPP_PA_PKEX_COMMIT_REVEAL_REQ);
+	hostapd_drv_send_action(hapd, freq, 0, src,
+				wpabuf_head(msg), wpabuf_len(msg));
+	wpabuf_free(msg);
+}
+
+
+static void
+hostapd_dpp_rx_pkex_commit_reveal_req(struct hostapd_data *hapd, const u8 *src,
+				      const u8 *hdr, const u8 *buf, size_t len,
+				      unsigned int freq)
+{
+	struct wpabuf *msg;
+	struct dpp_pkex *pkex = hapd->dpp_pkex;
+	struct dpp_bootstrap_info *bi;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Request from " MACSTR,
+		   MAC2STR(src));
+
+	if (!pkex || pkex->initiator || !pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return;
+	}
+
+	msg = dpp_pkex_rx_commit_reveal_req(pkex, hdr, buf, len);
+	if (!msg) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to process the request");
+		if (hapd->dpp_pkex->failed) {
+			wpa_printf(MSG_DEBUG, "DPP: Terminate PKEX exchange");
+			if (hapd->dpp_pkex->t > hapd->dpp_pkex->own_bi->pkex_t)
+				hapd->dpp_pkex->own_bi->pkex_t =
+					hapd->dpp_pkex->t;
+			dpp_pkex_free(hapd->dpp_pkex);
+			hapd->dpp_pkex = NULL;
+		}
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Response to "
+		   MACSTR, MAC2STR(src));
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d", MAC2STR(src), freq,
+		DPP_PA_PKEX_COMMIT_REVEAL_RESP);
+	hostapd_drv_send_action(hapd, freq, 0, src,
+				wpabuf_head(msg), wpabuf_len(msg));
+	wpabuf_free(msg);
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		return;
+	bi->id = hapd_dpp_next_id(hapd);
+	bi->type = DPP_BOOTSTRAP_PKEX;
+	os_memcpy(bi->mac_addr, src, ETH_ALEN);
+	bi->num_freq = 1;
+	bi->freq[0] = freq;
+	bi->curve = pkex->own_bi->curve;
+	bi->pubkey = pkex->peer_bootstrap_key;
+	pkex->peer_bootstrap_key = NULL;
+	dpp_pkex_free(pkex);
+	hapd->dpp_pkex = NULL;
+	if (dpp_bootstrap_key_hash(bi) < 0) {
+		dpp_bootstrap_info_free(bi);
+		return;
+	}
+	dl_list_add(&hapd->iface->interfaces->dpp_bootstrap, &bi->list);
+}
+
+
+static void
+hostapd_dpp_rx_pkex_commit_reveal_resp(struct hostapd_data *hapd, const u8 *src,
+				       const u8 *hdr, const u8 *buf, size_t len,
+				       unsigned int freq)
+{
+	int res;
+	struct dpp_bootstrap_info *bi, *own_bi;
+	struct dpp_pkex *pkex = hapd->dpp_pkex;
+	char cmd[500];
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Response from " MACSTR,
+		   MAC2STR(src));
+
+	if (!pkex || !pkex->initiator || !pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return;
+	}
+
+	res = dpp_pkex_rx_commit_reveal_resp(pkex, hdr, buf, len);
+	if (res < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to process the response");
+		return;
+	}
+
+	own_bi = pkex->own_bi;
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		return;
+	bi->id = hapd_dpp_next_id(hapd);
+	bi->type = DPP_BOOTSTRAP_PKEX;
+	os_memcpy(bi->mac_addr, src, ETH_ALEN);
+	bi->num_freq = 1;
+	bi->freq[0] = freq;
+	bi->curve = own_bi->curve;
+	bi->pubkey = pkex->peer_bootstrap_key;
+	pkex->peer_bootstrap_key = NULL;
+	dpp_pkex_free(pkex);
+	hapd->dpp_pkex = NULL;
+	if (dpp_bootstrap_key_hash(bi) < 0) {
+		dpp_bootstrap_info_free(bi);
+		return;
+	}
+	dl_list_add(&hapd->iface->interfaces->dpp_bootstrap, &bi->list);
+
+	os_snprintf(cmd, sizeof(cmd), " peer=%u %s",
+		    bi->id,
+		    hapd->dpp_pkex_auth_cmd ? hapd->dpp_pkex_auth_cmd : "");
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Start authentication after PKEX with parameters: %s",
+		   cmd);
+	if (hostapd_dpp_auth_init(hapd, cmd) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Authentication initialization failed");
+		return;
+	}
+}
+
+
+void hostapd_dpp_rx_action(struct hostapd_data *hapd, const u8 *src,
+			   const u8 *buf, size_t len, unsigned int freq)
+{
+	u8 crypto_suite;
+	enum dpp_public_action_frame_type type;
+	const u8 *hdr;
+	unsigned int pkex_t;
+
+	if (len < DPP_HDR_LEN)
+		return;
+	if (WPA_GET_BE24(buf) != OUI_WFA || buf[3] != DPP_OUI_TYPE)
+		return;
+	hdr = buf;
+	buf += 4;
+	len -= 4;
+	crypto_suite = *buf++;
+	type = *buf++;
+	len -= 2;
+
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Received DPP Public Action frame crypto suite %u type %d from "
+		   MACSTR " freq=%u",
+		   crypto_suite, type, MAC2STR(src), freq);
+	if (crypto_suite != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported crypto suite %u",
+			   crypto_suite);
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR
+			" freq=%u type=%d ignore=unsupported-crypto-suite",
+			MAC2STR(src), freq, type);
+		return;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Received message attributes", buf, len);
+	if (dpp_check_attrs(buf, len) < 0) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR
+			" freq=%u type=%d ignore=invalid-attributes",
+			MAC2STR(src), freq, type);
+		return;
+	}
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR
+		" freq=%u type=%d", MAC2STR(src), freq, type);
+
+	switch (type) {
+	case DPP_PA_AUTHENTICATION_REQ:
+		hostapd_dpp_rx_auth_req(hapd, src, hdr, buf, len, freq);
+		break;
+	case DPP_PA_AUTHENTICATION_RESP:
+		hostapd_dpp_rx_auth_resp(hapd, src, hdr, buf, len, freq);
+		break;
+	case DPP_PA_AUTHENTICATION_CONF:
+		hostapd_dpp_rx_auth_conf(hapd, src, hdr, buf, len);
+		break;
+	case DPP_PA_PEER_DISCOVERY_REQ:
+		hostapd_dpp_rx_peer_disc_req(hapd, src, buf, len, freq);
+		break;
+	case DPP_PA_PKEX_EXCHANGE_REQ:
+		hostapd_dpp_rx_pkex_exchange_req(hapd, src, buf, len, freq);
+		break;
+	case DPP_PA_PKEX_EXCHANGE_RESP:
+		hostapd_dpp_rx_pkex_exchange_resp(hapd, src, buf, len, freq);
+		break;
+	case DPP_PA_PKEX_COMMIT_REVEAL_REQ:
+		hostapd_dpp_rx_pkex_commit_reveal_req(hapd, src, hdr, buf, len,
+						      freq);
+		break;
+	case DPP_PA_PKEX_COMMIT_REVEAL_RESP:
+		hostapd_dpp_rx_pkex_commit_reveal_resp(hapd, src, hdr, buf, len,
+						       freq);
+		break;
+	default:
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Ignored unsupported frame subtype %d", type);
+		break;
+	}
+
+	if (hapd->dpp_pkex)
+		pkex_t = hapd->dpp_pkex->t;
+	else if (hapd->dpp_pkex_bi)
+		pkex_t = hapd->dpp_pkex_bi->pkex_t;
+	else
+		pkex_t = 0;
+	if (pkex_t >= PKEX_COUNTER_T_LIMIT) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PKEX_T_LIMIT "id=0");
+		hostapd_dpp_pkex_remove(hapd, "*");
+	}
+}
+
+
+struct wpabuf *
+hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa,
+			    const u8 *query, size_t query_len)
+{
+	struct dpp_authentication *auth = hapd->dpp_auth;
+	struct wpabuf *resp;
+
+	wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR, MAC2STR(sa));
+	if (!auth || !auth->auth_success ||
+	    os_memcmp(sa, auth->peer_mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress");
+		return NULL;
+	}
+	wpa_hexdump(MSG_DEBUG,
+		    "DPP: Received Configuration Request (GAS Query Request)",
+		    query, query_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_REQ_RX "src=" MACSTR,
+		MAC2STR(sa));
+	resp = dpp_conf_req_rx(auth, query, query_len);
+	if (!resp)
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED);
+	return resp;
+}
+
+
+void hostapd_dpp_gas_status_handler(struct hostapd_data *hapd, int ok)
+{
+	if (!hapd->dpp_auth)
+		return;
+
+	eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL);
+	eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL);
+	hostapd_drv_send_action_cancel_wait(hapd);
+
+	if (ok)
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_SENT);
+	else
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED);
+	dpp_auth_deinit(hapd->dpp_auth);
+	hapd->dpp_auth = NULL;
+}
+
+
+static unsigned int hostapd_dpp_next_configurator_id(struct hostapd_data *hapd)
+{
+	struct dpp_configurator *conf;
+	unsigned int max_id = 0;
+
+	dl_list_for_each(conf, &hapd->iface->interfaces->dpp_configurator,
+			 struct dpp_configurator, list) {
+		if (conf->id > max_id)
+			max_id = conf->id;
+	}
+	return max_id + 1;
+}
+
+
+int hostapd_dpp_configurator_add(struct hostapd_data *hapd, const char *cmd)
+{
+	char *curve = NULL;
+	char *key = NULL;
+	u8 *privkey = NULL;
+	size_t privkey_len = 0;
+	int ret = -1;
+	struct dpp_configurator *conf = NULL;
+
+	curve = get_param(cmd, " curve=");
+	key = get_param(cmd, " key=");
+
+	if (key) {
+		privkey_len = os_strlen(key) / 2;
+		privkey = os_malloc(privkey_len);
+		if (!privkey ||
+		    hexstr2bin(key, privkey, privkey_len) < 0)
+			goto fail;
+	}
+
+	conf = dpp_keygen_configurator(curve, privkey, privkey_len);
+	if (!conf)
+		goto fail;
+
+	conf->id = hostapd_dpp_next_configurator_id(hapd);
+	dl_list_add(&hapd->iface->interfaces->dpp_configurator, &conf->list);
+	ret = conf->id;
+	conf = NULL;
+fail:
+	os_free(curve);
+	str_clear_free(key);
+	bin_clear_free(privkey, privkey_len);
+	dpp_configurator_free(conf);
+	return ret;
+}
+
+
+static int dpp_configurator_del(struct hapd_interfaces *ifaces, unsigned int id)
+{
+	struct dpp_configurator *conf, *tmp;
+	int found = 0;
+
+	dl_list_for_each_safe(conf, tmp, &ifaces->dpp_configurator,
+			      struct dpp_configurator, list) {
+		if (id && conf->id != id)
+			continue;
+		found = 1;
+		dl_list_del(&conf->list);
+		dpp_configurator_free(conf);
+	}
+
+	if (id == 0)
+		return 0; /* flush succeeds regardless of entries found */
+	return found ? 0 : -1;
+}
+
+
+int hostapd_dpp_configurator_remove(struct hostapd_data *hapd, const char *id)
+{
+	unsigned int id_val;
+
+	if (os_strcmp(id, "*") == 0) {
+		id_val = 0;
+	} else {
+		id_val = atoi(id);
+		if (id_val == 0)
+			return -1;
+	}
+
+	return dpp_configurator_del(hapd->iface->interfaces, id_val);
+}
+
+
+int hostapd_dpp_configurator_sign(struct hostapd_data *hapd, const char *cmd)
+{
+	struct dpp_authentication *auth;
+	int ret = -1;
+	char *curve = NULL;
+
+	auth = os_zalloc(sizeof(*auth));
+	if (!auth)
+		return -1;
+
+	curve = get_param(cmd, " curve=");
+	hostapd_dpp_set_configurator(hapd, auth, cmd);
+
+	if (dpp_configurator_own_config(auth, curve, 1) == 0) {
+		hostapd_dpp_handle_config_obj(hapd, auth);
+		ret = 0;
+	}
+
+	dpp_auth_deinit(auth);
+	os_free(curve);
+
+	return ret;
+}
+
+
+int hostapd_dpp_configurator_get_key(struct hostapd_data *hapd, unsigned int id,
+				     char *buf, size_t buflen)
+{
+	struct dpp_configurator *conf;
+
+	conf = hostapd_dpp_configurator_get_id(hapd, id);
+	if (!conf)
+		return -1;
+
+	return dpp_configurator_get_key(conf, buf, buflen);
+}
+
+
+int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd)
+{
+	struct dpp_bootstrap_info *own_bi;
+	const char *pos, *end;
+
+	pos = os_strstr(cmd, " own=");
+	if (!pos)
+		return -1;
+	pos += 5;
+	own_bi = dpp_bootstrap_get_id(hapd, atoi(pos));
+	if (!own_bi) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Identified bootstrap info not found");
+		return -1;
+	}
+	if (own_bi->type != DPP_BOOTSTRAP_PKEX) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Identified bootstrap info not for PKEX");
+		return -1;
+	}
+	hapd->dpp_pkex_bi = own_bi;
+	own_bi->pkex_t = 0; /* clear pending errors on new code */
+
+	os_free(hapd->dpp_pkex_identifier);
+	hapd->dpp_pkex_identifier = NULL;
+	pos = os_strstr(cmd, " identifier=");
+	if (pos) {
+		pos += 12;
+		end = os_strchr(pos, ' ');
+		if (!end)
+			return -1;
+		hapd->dpp_pkex_identifier = os_malloc(end - pos + 1);
+		if (!hapd->dpp_pkex_identifier)
+			return -1;
+		os_memcpy(hapd->dpp_pkex_identifier, pos, end - pos);
+		hapd->dpp_pkex_identifier[end - pos] = '\0';
+	}
+
+	pos = os_strstr(cmd, " code=");
+	if (!pos)
+		return -1;
+	os_free(hapd->dpp_pkex_code);
+	hapd->dpp_pkex_code = os_strdup(pos + 6);
+	if (!hapd->dpp_pkex_code)
+		return -1;
+
+	if (os_strstr(cmd, " init=1")) {
+		struct wpabuf *msg;
+
+		wpa_printf(MSG_DEBUG, "DPP: Initiating PKEX");
+		dpp_pkex_free(hapd->dpp_pkex);
+		hapd->dpp_pkex = dpp_pkex_init(hapd->msg_ctx, own_bi,
+					       hapd->own_addr,
+					       hapd->dpp_pkex_identifier,
+					       hapd->dpp_pkex_code);
+		if (!hapd->dpp_pkex)
+			return -1;
+
+		msg = hapd->dpp_pkex->exchange_req;
+		/* TODO: Which channel to use? */
+		wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+			" freq=%u type=%d", MAC2STR(broadcast), 2437,
+			DPP_PA_PKEX_EXCHANGE_REQ);
+		hostapd_drv_send_action(hapd, 2437, 0, broadcast,
+					wpabuf_head(msg), wpabuf_len(msg));
+	}
+
+	/* TODO: Support multiple PKEX info entries */
+
+	os_free(hapd->dpp_pkex_auth_cmd);
+	hapd->dpp_pkex_auth_cmd = os_strdup(cmd);
+
+	return 1;
+}
+
+
+int hostapd_dpp_pkex_remove(struct hostapd_data *hapd, const char *id)
+{
+	unsigned int id_val;
+
+	if (os_strcmp(id, "*") == 0) {
+		id_val = 0;
+	} else {
+		id_val = atoi(id);
+		if (id_val == 0)
+			return -1;
+	}
+
+	if ((id_val != 0 && id_val != 1) || !hapd->dpp_pkex_code)
+		return -1;
+
+	/* TODO: Support multiple PKEX entries */
+	os_free(hapd->dpp_pkex_code);
+	hapd->dpp_pkex_code = NULL;
+	os_free(hapd->dpp_pkex_identifier);
+	hapd->dpp_pkex_identifier = NULL;
+	os_free(hapd->dpp_pkex_auth_cmd);
+	hapd->dpp_pkex_auth_cmd = NULL;
+	hapd->dpp_pkex_bi = NULL;
+	/* TODO: Remove dpp_pkex only if it is for the identified PKEX code */
+	dpp_pkex_free(hapd->dpp_pkex);
+	hapd->dpp_pkex = NULL;
+	return 0;
+}
+
+
+void hostapd_dpp_stop(struct hostapd_data *hapd)
+{
+	dpp_auth_deinit(hapd->dpp_auth);
+	hapd->dpp_auth = NULL;
+	dpp_pkex_free(hapd->dpp_pkex);
+	hapd->dpp_pkex = NULL;
+}
+
+
+int hostapd_dpp_init(struct hostapd_data *hapd)
+{
+	hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR | DPP_CAPAB_ENROLLEE;
+	hapd->dpp_init_done = 1;
+	return 0;
+}
+
+
+void hostapd_dpp_deinit(struct hostapd_data *hapd)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	os_free(hapd->dpp_config_obj_override);
+	hapd->dpp_config_obj_override = NULL;
+	os_free(hapd->dpp_discovery_override);
+	hapd->dpp_discovery_override = NULL;
+	os_free(hapd->dpp_groups_override);
+	hapd->dpp_groups_override = NULL;
+	hapd->dpp_ignore_netaccesskey_mismatch = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (!hapd->dpp_init_done)
+		return;
+	eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL);
+	eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL);
+	eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL);
+	dpp_auth_deinit(hapd->dpp_auth);
+	hapd->dpp_auth = NULL;
+	hostapd_dpp_pkex_remove(hapd, "*");
+	hapd->dpp_pkex = NULL;
+	os_free(hapd->dpp_configurator_params);
+	hapd->dpp_configurator_params = NULL;
+}
+
+
+void hostapd_dpp_init_global(struct hapd_interfaces *ifaces)
+{
+	dl_list_init(&ifaces->dpp_bootstrap);
+	dl_list_init(&ifaces->dpp_configurator);
+	ifaces->dpp_init_done = 1;
+}
+
+
+void hostapd_dpp_deinit_global(struct hapd_interfaces *ifaces)
+{
+	if (!ifaces->dpp_init_done)
+		return;
+	dpp_bootstrap_del(ifaces, 0);
+	dpp_configurator_del(ifaces, 0);
+}

+ 43 - 0
src/ap/dpp_hostapd.h

@@ -0,0 +1,43 @@
+/*
+ * hostapd / DPP integration
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef DPP_HOSTAPD_H
+#define DPP_HOSTAPD_H
+
+int hostapd_dpp_qr_code(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_bootstrap_gen(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_bootstrap_remove(struct hostapd_data *hapd, const char *id);
+const char * hostapd_dpp_bootstrap_get_uri(struct hostapd_data *hapd,
+					   unsigned int id);
+int hostapd_dpp_bootstrap_info(struct hostapd_data *hapd, int id,
+			       char *reply, int reply_size);
+int hostapd_dpp_auth_init(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_listen(struct hostapd_data *hapd, const char *cmd);
+void hostapd_dpp_listen_stop(struct hostapd_data *hapd);
+void hostapd_dpp_rx_action(struct hostapd_data *hapd, const u8 *src,
+			   const u8 *buf, size_t len, unsigned int freq);
+void hostapd_dpp_tx_status(struct hostapd_data *hapd, const u8 *dst,
+			   const u8 *data, size_t data_len, int ok);
+struct wpabuf *
+hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa,
+			    const u8 *query, size_t query_len);
+void hostapd_dpp_gas_status_handler(struct hostapd_data *hapd, int ok);
+int hostapd_dpp_configurator_add(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_configurator_remove(struct hostapd_data *hapd, const char *id);
+int hostapd_dpp_configurator_sign(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_configurator_get_key(struct hostapd_data *hapd, unsigned int id,
+				     char *buf, size_t buflen);
+int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd);
+int hostapd_dpp_pkex_remove(struct hostapd_data *hapd, const char *id);
+void hostapd_dpp_stop(struct hostapd_data *hapd);
+int hostapd_dpp_init(struct hostapd_data *hapd);
+void hostapd_dpp_deinit(struct hostapd_data *hapd);
+void hostapd_dpp_init_global(struct hapd_interfaces *ifaces);
+void hostapd_dpp_deinit_global(struct hapd_interfaces *ifaces);
+
+#endif /* DPP_HOSTAPD_H */

+ 346 - 26
src/ap/drv_callbacks.c

@@ -31,10 +31,74 @@
 #include "wps_hostapd.h"
 #include "ap_drv_ops.h"
 #include "ap_config.h"
+#include "ap_mlme.h"
 #include "hw_features.h"
 #include "dfs.h"
 #include "beacon.h"
 #include "mbo_ap.h"
+#include "dpp_hostapd.h"
+#include "fils_hlp.h"
+
+
+#ifdef CONFIG_FILS
+void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd,
+				      struct sta_info *sta)
+{
+	u16 reply_res = WLAN_STATUS_SUCCESS;
+	struct ieee802_11_elems elems;
+	u8 buf[IEEE80211_MAX_MMPDU_SIZE], *p = buf;
+	int new_assoc;
+
+	wpa_printf(MSG_DEBUG, "%s FILS: Finish association with " MACSTR,
+		   __func__, MAC2STR(sta->addr));
+	eloop_cancel_timeout(fils_hlp_timeout, hapd, sta);
+	if (!sta->fils_pending_assoc_req)
+		return;
+
+	ieee802_11_parse_elems(sta->fils_pending_assoc_req,
+			       sta->fils_pending_assoc_req_len, &elems, 0);
+	if (!elems.fils_session) {
+		wpa_printf(MSG_DEBUG, "%s failed to find FILS Session element",
+			   __func__);
+		return;
+	}
+
+	p = hostapd_eid_assoc_fils_session(sta->wpa_sm, p,
+					   elems.fils_session,
+					   sta->fils_hlp_resp);
+
+	reply_res = hostapd_sta_assoc(hapd, sta->addr,
+				      sta->fils_pending_assoc_is_reassoc,
+				      WLAN_STATUS_SUCCESS,
+				      buf, p - buf);
+	ap_sta_set_authorized(hapd, sta, 1);
+	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
+	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
+	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
+	hostapd_set_sta_flags(hapd, sta);
+	wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS);
+	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
+	hostapd_new_assoc_sta(hapd, sta, !new_assoc);
+	os_free(sta->fils_pending_assoc_req);
+	sta->fils_pending_assoc_req = NULL;
+	sta->fils_pending_assoc_req_len = 0;
+	wpabuf_free(sta->fils_hlp_resp);
+	sta->fils_hlp_resp = NULL;
+	wpabuf_free(sta->hlp_dhcp_discover);
+	sta->hlp_dhcp_discover = NULL;
+	fils_hlp_deinit(hapd);
+
+	/*
+	 * Remove the station in case transmission of a success response fails
+	 * (the STA was added associated to the driver) or if the station was
+	 * previously added unassociated.
+	 */
+	if (reply_res != WLAN_STATUS_SUCCESS || sta->added_unassoc) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
+}
+#endif /* CONFIG_FILS */
 
 
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
@@ -45,10 +109,10 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 	struct ieee802_11_elems elems;
 	const u8 *ie;
 	size_t ielen;
-#if defined(CONFIG_IEEE80211R) || defined(CONFIG_IEEE80211W)
+#if defined(CONFIG_IEEE80211R_AP) || defined(CONFIG_IEEE80211W) || defined(CONFIG_FILS)
 	u8 buf[sizeof(struct ieee80211_mgmt) + 1024];
 	u8 *p = buf;
-#endif /* CONFIG_IEEE80211R || CONFIG_IEEE80211W */
+#endif /* CONFIG_IEEE80211R_AP || CONFIG_IEEE80211W || CONFIG_FILS */
 	u16 reason = WLAN_REASON_UNSPECIFIED;
 	u16 status = WLAN_STATUS_SUCCESS;
 	const u8 *p2p_dev_addr = NULL;
@@ -198,7 +262,9 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 #endif /* CONFIG_WPS */
 
 			wpa_printf(MSG_DEBUG, "No WPA/RSN IE from STA");
-			return -1;
+			reason = WLAN_REASON_INVALID_IE;
+			status = WLAN_STATUS_INVALID_IE;
+			goto fail;
 		}
 #ifdef CONFIG_WPS
 		if (hapd->conf->wps_state && ie[0] == 0xdd && ie[1] >= 4 &&
@@ -231,7 +297,8 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 		}
 		res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
 					  ie, ielen,
-					  elems.mdie, elems.mdie_len);
+					  elems.mdie, elems.mdie_len,
+					  elems.owe_dh, elems.owe_dh_len);
 		if (res != WPA_IE_OK) {
 			wpa_printf(MSG_DEBUG,
 				   "WPA/RSN information element rejected? (res %u)",
@@ -252,8 +319,8 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 				reason = WLAN_REASON_INVALID_IE;
 				status = WLAN_STATUS_INVALID_IE;
 			} else if (res == WPA_INVALID_MGMT_GROUP_CIPHER) {
-				reason = WLAN_REASON_GROUP_CIPHER_NOT_VALID;
-				status = WLAN_STATUS_GROUP_CIPHER_NOT_VALID;
+				reason = WLAN_REASON_CIPHER_SUITE_REJECTED;
+				status = WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
 			}
 #endif /* CONFIG_IEEE80211W */
 			else {
@@ -293,7 +360,7 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 			sta->flags &= ~WLAN_STA_MFP;
 #endif /* CONFIG_IEEE80211W */
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 		if (sta->auth_alg == WLAN_AUTH_FT) {
 			status = wpa_ft_validate_reassoc(sta->wpa_sm, req_ies,
 							 req_ies_len);
@@ -307,7 +374,7 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 				goto fail;
 			}
 		}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 	} else if (hapd->conf->wps_state) {
 #ifdef CONFIG_WPS
 		struct wpabuf *wps;
@@ -375,19 +442,124 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 skip_wpa_check:
 #endif /* CONFIG_WPS */
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, buf, sizeof(buf),
 					sta->auth_alg, req_ies, req_ies_len);
+#endif /* CONFIG_IEEE80211R_AP */
+
+#ifdef CONFIG_FILS
+	if (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	    sta->auth_alg == WLAN_AUTH_FILS_PK) {
+		int delay_assoc = 0;
+
+		if (!req_ies)
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+		if (!wpa_fils_validate_fils_session(sta->wpa_sm, req_ies,
+						    req_ies_len,
+						    sta->fils_session)) {
+			wpa_printf(MSG_DEBUG,
+				   "FILS: Session validation failed");
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+
+		res = wpa_fils_validate_key_confirm(sta->wpa_sm, req_ies,
+						    req_ies_len);
+		if (res < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "FILS: Key Confirm validation failed");
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+
+		if (fils_process_hlp(hapd, sta, req_ies, req_ies_len) > 0) {
+			wpa_printf(MSG_DEBUG,
+				   "FILS: Delaying Assoc Response (HLP)");
+			delay_assoc = 1;
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "FILS: Going ahead with Assoc Response (no HLP)");
+		}
+
+		if (sta) {
+			wpa_printf(MSG_DEBUG, "FILS: HLP callback cleanup");
+			eloop_cancel_timeout(fils_hlp_timeout, hapd, sta);
+			os_free(sta->fils_pending_assoc_req);
+			sta->fils_pending_assoc_req = NULL;
+			sta->fils_pending_assoc_req_len = 0;
+			wpabuf_free(sta->fils_hlp_resp);
+			sta->fils_hlp_resp = NULL;
+			sta->fils_drv_assoc_finish = 0;
+		}
+
+		if (sta && delay_assoc && status == WLAN_STATUS_SUCCESS) {
+			u8 *req_tmp;
+
+			req_tmp = os_malloc(req_ies_len);
+			if (!req_tmp) {
+				wpa_printf(MSG_DEBUG,
+					   "FILS: buffer allocation failed for assoc req");
+				goto fail;
+			}
+			os_memcpy(req_tmp, req_ies, req_ies_len);
+			sta->fils_pending_assoc_req = req_tmp;
+			sta->fils_pending_assoc_req_len = req_ies_len;
+			sta->fils_pending_assoc_is_reassoc = reassoc;
+			sta->fils_drv_assoc_finish = 1;
+			wpa_printf(MSG_DEBUG,
+				   "FILS: Waiting for HLP processing before sending (Re)Association Response frame to "
+				   MACSTR, MAC2STR(sta->addr));
+			eloop_register_timeout(
+				0, hapd->conf->fils_hlp_wait_time * 1024,
+				fils_hlp_timeout, hapd, sta);
+			return 0;
+		}
+		p = hostapd_eid_assoc_fils_session(sta->wpa_sm, p,
+						   elems.fils_session,
+						   sta->fils_hlp_resp);
+		wpa_hexdump(MSG_DEBUG, "FILS Assoc Resp BUF (IEs)",
+			    buf, p - buf);
+	}
+#endif /* CONFIG_FILS */
+
+#ifdef CONFIG_OWE
+	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) &&
+	    wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE &&
+	    elems.owe_dh) {
+		u8 *npos;
+
+		npos = owe_assoc_req_process(hapd, sta,
+					     elems.owe_dh, elems.owe_dh_len,
+					     p, sizeof(buf) - (p - buf),
+					     &reason);
+		if (npos)
+			p = npos;
+		if (!npos &&
+		    reason == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED) {
+			status = WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
+			hostapd_sta_assoc(hapd, addr, reassoc, status, buf,
+					  p - buf);
+			return 0;
+		}
+
+		if (!npos || reason != WLAN_STATUS_SUCCESS)
+			goto fail;
+	}
+#endif /* CONFIG_OWE */
 
+#if defined(CONFIG_IEEE80211R_AP) || defined(CONFIG_FILS) || defined(CONFIG_OWE)
 	hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf);
 
-	if (sta->auth_alg == WLAN_AUTH_FT)
+	if (sta->auth_alg == WLAN_AUTH_FT ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	    sta->auth_alg == WLAN_AUTH_FILS_PK)
 		ap_sta_set_authorized(hapd, sta, 1);
-#else /* CONFIG_IEEE80211R */
+#else /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 	/* Keep compiler silent about unused variables */
 	if (status) {
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 
 	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
@@ -397,6 +569,12 @@ skip_wpa_check:
 
 	if (reassoc && (sta->auth_alg == WLAN_AUTH_FT))
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FT);
+#ifdef CONFIG_FILS
+	else if (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+		 sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+		 sta->auth_alg == WLAN_AUTH_FILS_PK)
+		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS);
+#endif /* CONFIG_FILS */
 	else
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC);
 
@@ -414,9 +592,9 @@ skip_wpa_check:
 	return 0;
 
 fail:
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf);
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 	hostapd_drv_sta_disassoc(hapd, sta->addr, reason);
 	ap_free_sta(hapd, sta);
 	return -1;
@@ -464,15 +642,81 @@ void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr)
 {
 	struct sta_info *sta = ap_get_sta(hapd, addr);
 
-	if (!sta || !hapd->conf->disassoc_low_ack)
+	if (!sta || !hapd->conf->disassoc_low_ack || sta->agreed_to_steer)
 		return;
 
 	hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_INFO,
 		       "disconnected due to excessive missing ACKs");
 	hostapd_drv_sta_disassoc(hapd, addr, WLAN_REASON_DISASSOC_LOW_ACK);
-	if (sta)
-		ap_sta_disassociate(hapd, sta, WLAN_REASON_DISASSOC_LOW_ACK);
+	ap_sta_disassociate(hapd, sta, WLAN_REASON_DISASSOC_LOW_ACK);
+}
+
+
+void hostapd_event_sta_opmode_changed(struct hostapd_data *hapd, const u8 *addr,
+				      enum smps_mode smps_mode,
+				      enum chan_width chan_width, u8 rx_nss)
+{
+	struct sta_info *sta = ap_get_sta(hapd, addr);
+	const char *txt;
+
+	if (!sta)
+		return;
+
+	switch (smps_mode) {
+	case SMPS_AUTOMATIC:
+		txt = "automatic";
+		break;
+	case SMPS_OFF:
+		txt = "off";
+		break;
+	case SMPS_DYNAMIC:
+		txt = "dynamic";
+		break;
+	case SMPS_STATIC:
+		txt = "static";
+		break;
+	default:
+		txt = NULL;
+		break;
+	}
+	if (txt) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_SMPS_MODE_CHANGED
+			MACSTR " %s", MAC2STR(addr), txt);
+	}
+
+	switch (chan_width) {
+	case CHAN_WIDTH_20_NOHT:
+		txt = "20(no-HT)";
+		break;
+	case CHAN_WIDTH_20:
+		txt = "20";
+		break;
+	case CHAN_WIDTH_40:
+		txt = "40";
+		break;
+	case CHAN_WIDTH_80:
+		txt = "80";
+		break;
+	case CHAN_WIDTH_80P80:
+		txt = "80+80";
+		break;
+	case CHAN_WIDTH_160:
+		txt = "160";
+		break;
+	default:
+		txt = NULL;
+		break;
+	}
+	if (txt) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_MAX_BW_CHANGED
+			MACSTR " %s", MAC2STR(addr), txt);
+	}
+
+	if (rx_nss != 0xff) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_N_SS_CHANGED
+			MACSTR " %d", MAC2STR(addr), rx_nss);
+	}
 }
 
 
@@ -690,7 +934,7 @@ int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da,
 
 #ifdef HOSTAPD
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 static void hostapd_notify_auth_ft_finish(void *ctx, const u8 *dst,
 					  const u8 *bssid,
 					  u16 auth_transaction, u16 status,
@@ -709,7 +953,33 @@ static void hostapd_notify_auth_ft_finish(void *ctx, const u8 *dst,
 
 	hostapd_sta_auth(hapd, dst, auth_transaction, status, ies, ies_len);
 }
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
+
+
+#ifdef CONFIG_FILS
+static void hostapd_notify_auth_fils_finish(struct hostapd_data *hapd,
+					    struct sta_info *sta, u16 resp,
+					    struct wpabuf *data, int pub)
+{
+	if (resp == WLAN_STATUS_SUCCESS) {
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG, "authentication OK (FILS)");
+		sta->flags |= WLAN_STA_AUTH;
+		wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
+		sta->auth_alg = WLAN_AUTH_FILS_SK;
+		mlme_authenticate_indication(hapd, sta);
+	} else {
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "authentication failed (FILS)");
+	}
+
+	hostapd_sta_auth(hapd, sta->addr, 2, resp,
+			 data ? wpabuf_head(data) : NULL,
+			 data ? wpabuf_len(data) : 0);
+	wpabuf_free(data);
+}
+#endif /* CONFIG_FILS */
 
 
 static void hostapd_notif_auth(struct hostapd_data *hapd,
@@ -730,7 +1000,7 @@ static void hostapd_notif_auth(struct hostapd_data *hapd,
 	}
 	sta->flags &= ~WLAN_STA_PREAUTH;
 	ieee802_1x_notify_pre_auth(sta->eapol_sm, 0);
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (rx_auth->auth_type == WLAN_AUTH_FT && hapd->wpa_auth) {
 		sta->auth_alg = WLAN_AUTH_FT;
 		if (sta->wpa_sm == NULL)
@@ -748,7 +1018,19 @@ static void hostapd_notif_auth(struct hostapd_data *hapd,
 				    hostapd_notify_auth_ft_finish, hapd);
 		return;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
+
+#ifdef CONFIG_FILS
+	if (rx_auth->auth_type == WLAN_AUTH_FILS_SK) {
+		sta->auth_alg = WLAN_AUTH_FILS_SK;
+		handle_auth_fils(hapd, sta, rx_auth->ies, rx_auth->ies_len,
+				 rx_auth->auth_type, rx_auth->auth_transaction,
+				 rx_auth->status_code,
+				 hostapd_notify_auth_fils_finish);
+		return;
+	}
+#endif /* CONFIG_FILS */
+
 fail:
 	hostapd_sta_auth(hapd, rx_auth->peer, rx_auth->auth_transaction + 1,
 			 status, resp_ies, resp_ies_len);
@@ -781,13 +1063,13 @@ static void hostapd_action_rx(struct hostapd_data *hapd,
 		wpa_printf(MSG_DEBUG, "%s: station not found", __func__);
 		return;
 	}
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (mgmt->u.action.category == WLAN_ACTION_FT) {
 		const u8 *payload = drv_mgmt->frame + 24 + 1;
 
 		wpa_ft_action_rx(sta->wpa_sm, payload, plen);
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 	if (mgmt->u.action.category == WLAN_ACTION_SA_QUERY && plen >= 4) {
 		ieee802_11_sa_query_action(
@@ -796,18 +1078,34 @@ static void hostapd_action_rx(struct hostapd_data *hapd,
 			mgmt->u.action.u.sa_query_resp.trans_id);
 	}
 #endif /* CONFIG_IEEE80211W */
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 	if (mgmt->u.action.category == WLAN_ACTION_WNM) {
 		ieee802_11_rx_wnm_action_ap(hapd, mgmt, drv_mgmt->frame_len);
 	}
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 #ifdef CONFIG_FST
 	if (mgmt->u.action.category == WLAN_ACTION_FST && hapd->iface->fst) {
 		fst_rx_action(hapd->iface->fst, mgmt, drv_mgmt->frame_len);
 		return;
 	}
 #endif /* CONFIG_FST */
-
+#ifdef CONFIG_DPP
+	if (plen >= 1 + 4 &&
+	    mgmt->u.action.u.vs_public_action.action ==
+	    WLAN_PA_VENDOR_SPECIFIC &&
+	    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
+	    OUI_WFA &&
+	    mgmt->u.action.u.vs_public_action.variable[0] ==
+	    DPP_OUI_TYPE) {
+		const u8 *pos, *end;
+
+		pos = mgmt->u.action.u.vs_public_action.oui;
+		end = drv_mgmt->frame + drv_mgmt->frame_len;
+		hostapd_dpp_rx_action(hapd, mgmt->sa, pos, end - pos,
+				      drv_mgmt->freq);
+		return;
+	}
+#endif /* CONFIG_DPP */
 }
 
 
@@ -891,6 +1189,7 @@ static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt)
 	}
 
 	os_memset(&fi, 0, sizeof(fi));
+	fi.freq = rx_mgmt->freq;
 	fi.datarate = rx_mgmt->datarate;
 	fi.ssi_signal = rx_mgmt->ssi_signal;
 
@@ -1122,6 +1421,16 @@ static void hostapd_event_dfs_radar_detected(struct hostapd_data *hapd,
 }
 
 
+static void hostapd_event_dfs_pre_cac_expired(struct hostapd_data *hapd,
+					      struct dfs_event *radar)
+{
+	wpa_printf(MSG_DEBUG, "DFS Pre-CAC expired on %d MHz", radar->freq);
+	hostapd_dfs_pre_cac_expired(hapd->iface, radar->freq, radar->ht_enabled,
+				    radar->chan_offset, radar->chan_width,
+				    radar->cf1, radar->cf2);
+}
+
+
 static void hostapd_event_dfs_cac_finished(struct hostapd_data *hapd,
 					   struct dfs_event *radar)
 {
@@ -1314,6 +1623,11 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 			break;
 		hostapd_event_dfs_radar_detected(hapd, &data->dfs_event);
 		break;
+	case EVENT_DFS_PRE_CAC_EXPIRED:
+		if (!data)
+			break;
+		hostapd_event_dfs_pre_cac_expired(hapd, &data->dfs_event);
+		break;
 	case EVENT_DFS_CAC_FINISHED:
 		if (!data)
 			break;
@@ -1367,6 +1681,12 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 					     &data->acs_selected_channels);
 		break;
 #endif /* CONFIG_ACS */
+	case EVENT_STATION_OPMODE_CHANGED:
+		hostapd_event_sta_opmode_changed(hapd, data->sta_opmode.addr,
+						 data->sta_opmode.smps_mode,
+						 data->sta_opmode.chan_width,
+						 data->sta_opmode.rx_nss);
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "Unknown event %d", event);
 		break;

+ 191 - 0
src/ap/eth_p_oui.c

@@ -0,0 +1,191 @@
+/*
+ * hostapd / IEEE 802 OUI Extended EtherType 88-B7
+ * Copyright (c) 2016, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "l2_packet/l2_packet.h"
+#include "hostapd.h"
+#include "eth_p_oui.h"
+
+/*
+ * See IEEE Std 802-2014, Clause 9.2.4 for the definition of the OUI Extended
+ * EtherType 88-B7. This file implements this with OUI 00:13:74 and
+ * vendor-specific subtype 0x0001.
+ */
+static const u8 global_oui[] = { 0x00, 0x13, 0x74, 0x00, 0x01 };
+
+struct eth_p_oui_iface {
+	struct dl_list list;
+	char ifname[IFNAMSIZ + 1];
+	struct l2_packet_data *l2;
+	struct dl_list receiver;
+};
+
+struct eth_p_oui_ctx {
+	struct dl_list list;
+	struct eth_p_oui_iface *iface;
+	/* all data needed to deliver and unregister */
+	u8 oui_suffix; /* last byte of OUI */
+	void (*rx_callback)(void *ctx, const u8 *src_addr,
+			    const u8 *dst_addr, u8 oui_suffix,
+			    const u8 *buf, size_t len);
+	void *rx_callback_ctx;
+};
+
+
+void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		       const u8 *dst_addr, const u8 *buf, size_t len)
+{
+	ctx->rx_callback(ctx->rx_callback_ctx, src_addr, dst_addr,
+			 ctx->oui_suffix, buf, len);
+}
+
+
+static void eth_p_rx(void *ctx, const u8 *src_addr, const u8 *buf, size_t len)
+{
+	struct eth_p_oui_iface *iface = ctx;
+	struct eth_p_oui_ctx *receiver;
+	const struct l2_ethhdr *ethhdr;
+
+	if (len < sizeof(*ethhdr) + sizeof(global_oui) + 1) {
+		/* too short packet */
+		return;
+	}
+
+	ethhdr = (struct l2_ethhdr *) buf;
+	/* trim eth_hdr from buf and len */
+	buf += sizeof(*ethhdr);
+	len -= sizeof(*ethhdr);
+
+	/* verify OUI and vendor-specific subtype match */
+	if (os_memcmp(buf, global_oui, sizeof(global_oui)) != 0)
+		return;
+	buf += sizeof(global_oui);
+	len -= sizeof(global_oui);
+
+	dl_list_for_each(receiver, &iface->receiver,
+			 struct eth_p_oui_ctx, list) {
+		if (buf[0] != receiver->oui_suffix)
+			continue;
+
+		eth_p_oui_deliver(receiver, ethhdr->h_source, ethhdr->h_dest,
+				  buf + 1, len - 1);
+	}
+}
+
+
+struct eth_p_oui_ctx *
+eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix,
+		   void (*rx_callback)(void *ctx, const u8 *src_addr,
+				       const u8 *dst_addr, u8 oui_suffix,
+				       const u8 *buf, size_t len),
+		   void *rx_callback_ctx)
+{
+	struct eth_p_oui_iface *iface;
+	struct eth_p_oui_ctx *receiver;
+	int found = 0;
+	struct hapd_interfaces *interfaces;
+
+	receiver = os_zalloc(sizeof(*receiver));
+	if (!receiver)
+		goto err;
+
+	receiver->oui_suffix = oui_suffix;
+	receiver->rx_callback = rx_callback;
+	receiver->rx_callback_ctx = rx_callback_ctx;
+
+	interfaces = hapd->iface->interfaces;
+
+	dl_list_for_each(iface, &interfaces->eth_p_oui, struct eth_p_oui_iface,
+			 list) {
+		if (os_strcmp(iface->ifname, ifname) != 0)
+			continue;
+		found = 1;
+		break;
+	}
+
+	if (!found) {
+		iface = os_zalloc(sizeof(*iface));
+		if (!iface)
+			goto err;
+
+		os_strlcpy(iface->ifname, ifname, sizeof(iface->ifname));
+		iface->l2 = l2_packet_init(ifname, NULL, ETH_P_OUI, eth_p_rx,
+					   iface, 1);
+		if (!iface->l2) {
+			os_free(iface);
+			goto err;
+		}
+		dl_list_init(&iface->receiver);
+
+		dl_list_add_tail(&interfaces->eth_p_oui, &iface->list);
+	}
+
+	dl_list_add_tail(&iface->receiver, &receiver->list);
+	receiver->iface = iface;
+
+	return receiver;
+err:
+	os_free(receiver);
+	return NULL;
+}
+
+
+void eth_p_oui_unregister(struct eth_p_oui_ctx *ctx)
+{
+	struct eth_p_oui_iface *iface;
+
+	if (!ctx)
+		return;
+
+	iface = ctx->iface;
+
+	dl_list_del(&ctx->list);
+	os_free(ctx);
+
+	if (dl_list_empty(&iface->receiver)) {
+		dl_list_del(&iface->list);
+		l2_packet_deinit(iface->l2);
+		os_free(iface);
+	}
+}
+
+
+int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		   const u8 *dst_addr, const u8 *buf, size_t len)
+{
+	struct eth_p_oui_iface *iface = ctx->iface;
+	u8 *packet, *p;
+	size_t packet_len;
+	int ret;
+	struct l2_ethhdr *ethhdr;
+
+	packet_len = sizeof(*ethhdr) + sizeof(global_oui) + 1 + len;
+	packet = os_zalloc(packet_len);
+	if (!packet)
+		return -1;
+	p = packet;
+
+	ethhdr = (struct l2_ethhdr *) packet;
+	os_memcpy(ethhdr->h_source, src_addr, ETH_ALEN);
+	os_memcpy(ethhdr->h_dest, dst_addr, ETH_ALEN);
+	ethhdr->h_proto = host_to_be16(ETH_P_OUI);
+	p += sizeof(*ethhdr);
+
+	os_memcpy(p, global_oui, sizeof(global_oui));
+	p[sizeof(global_oui)] = ctx->oui_suffix;
+	p += sizeof(global_oui) + 1;
+
+	os_memcpy(p, buf, len);
+
+	ret = l2_packet_send(iface->l2, NULL, 0, packet, packet_len);
+	os_free(packet);
+	return ret;
+}

+ 28 - 0
src/ap/eth_p_oui.h

@@ -0,0 +1,28 @@
+/*
+ * hostapd / IEEE 802 OUI Extended Ethertype
+ * Copyright (c) 2016, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef ETH_P_OUI_H
+#define ETH_P_OUI_H
+
+struct eth_p_oui_ctx;
+struct hostapd_data;
+
+/* rx_callback only gets payload after OUI passed as buf */
+struct eth_p_oui_ctx *
+eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix,
+		   void (*rx_callback)(void *ctx, const u8 *src_addr,
+				       const u8 *dst_addr, u8 oui_suffix,
+				       const u8 *buf, size_t len),
+		   void *rx_callback_ctx);
+void eth_p_oui_unregister(struct eth_p_oui_ctx *eth_p_oui);
+int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		   const u8 *dst_addr, const u8 *buf, size_t len);
+void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		       const u8 *dst_addr, const u8 *buf, size_t len);
+
+#endif /* ETH_P_OUI_H */

+ 641 - 0
src/ap/fils_hlp.c

@@ -0,0 +1,641 @@
+/*
+ * FILS HLP request processing
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/dhcp.h"
+#include "hostapd.h"
+#include "sta_info.h"
+#include "ieee802_11.h"
+#include "fils_hlp.h"
+
+
+static be16 ip_checksum(const void *buf, size_t len)
+{
+	u32 sum = 0;
+	const u16 *pos;
+
+	for (pos = buf; len >= 2; len -= 2)
+		sum += ntohs(*pos++);
+	if (len)
+		sum += ntohs(*pos << 8);
+
+	sum = (sum >> 16) + (sum & 0xffff);
+	sum += sum >> 16;
+	return htons(~sum);
+}
+
+
+static int fils_dhcp_request(struct hostapd_data *hapd, struct sta_info *sta,
+			     struct dhcp_data *dhcpoffer, u8 *dhcpofferend)
+{
+	u8 *pos, *end;
+	struct dhcp_data *dhcp;
+	struct sockaddr_in addr;
+	ssize_t res;
+	const u8 *server_id = NULL;
+
+	if (!sta->hlp_dhcp_discover) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: No pending HLP DHCPDISCOVER available");
+		return -1;
+	}
+
+	/* Convert to DHCPREQUEST, remove rapid commit option, replace requested
+	 * IP address option with yiaddr. */
+	pos = wpabuf_mhead(sta->hlp_dhcp_discover);
+	end = pos + wpabuf_len(sta->hlp_dhcp_discover);
+	dhcp = (struct dhcp_data *) pos;
+	pos = (u8 *) (dhcp + 1);
+	pos += 4; /* skip magic */
+	while (pos < end && *pos != DHCP_OPT_END) {
+		u8 opt, olen;
+
+		opt = *pos++;
+		if (opt == DHCP_OPT_PAD)
+			continue;
+		if (pos >= end)
+			break;
+		olen = *pos++;
+		if (olen > end - pos)
+			break;
+
+		switch (opt) {
+		case DHCP_OPT_MSG_TYPE:
+			if (olen > 0)
+				*pos = DHCPREQUEST;
+			break;
+		case DHCP_OPT_RAPID_COMMIT:
+		case DHCP_OPT_REQUESTED_IP_ADDRESS:
+		case DHCP_OPT_SERVER_ID:
+			/* Remove option */
+			pos -= 2;
+			os_memmove(pos, pos + 2 + olen, end - pos - 2 - olen);
+			end -= 2 + olen;
+			olen = 0;
+			break;
+		}
+		pos += olen;
+	}
+	if (pos >= end || *pos != DHCP_OPT_END) {
+		wpa_printf(MSG_DEBUG, "FILS: Could not update DHCPDISCOVER");
+		return -1;
+	}
+	sta->hlp_dhcp_discover->used = pos - (u8 *) dhcp;
+
+	/* Copy Server ID option from DHCPOFFER to DHCPREQUEST */
+	pos = (u8 *) (dhcpoffer + 1);
+	end = dhcpofferend;
+	pos += 4; /* skip magic */
+	while (pos < end && *pos != DHCP_OPT_END) {
+		u8 opt, olen;
+
+		opt = *pos++;
+		if (opt == DHCP_OPT_PAD)
+			continue;
+		if (pos >= end)
+			break;
+		olen = *pos++;
+		if (olen > end - pos)
+			break;
+
+		switch (opt) {
+		case DHCP_OPT_SERVER_ID:
+			server_id = pos - 2;
+			break;
+		}
+		pos += olen;
+	}
+
+	if (wpabuf_resize(&sta->hlp_dhcp_discover,
+			  6 + 1 + (server_id ? 2 + server_id[1] : 0)))
+		return -1;
+	if (server_id)
+		wpabuf_put_data(sta->hlp_dhcp_discover, server_id,
+				2 + server_id[1]);
+	wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_REQUESTED_IP_ADDRESS);
+	wpabuf_put_u8(sta->hlp_dhcp_discover, 4);
+	wpabuf_put_data(sta->hlp_dhcp_discover, &dhcpoffer->your_ip, 4);
+	wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_END);
+
+	os_memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
+	addr.sin_port = htons(hapd->conf->dhcp_server_port);
+	res = sendto(hapd->dhcp_sock, wpabuf_head(sta->hlp_dhcp_discover),
+		     wpabuf_len(sta->hlp_dhcp_discover), 0,
+		     (const struct sockaddr *) &addr, sizeof(addr));
+	if (res < 0) {
+		wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
+			   strerror(errno));
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG,
+		   "FILS: Acting as DHCP rapid commit proxy for %s:%d",
+		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+	wpabuf_free(sta->hlp_dhcp_discover);
+	sta->hlp_dhcp_discover = NULL;
+	sta->fils_dhcp_rapid_commit_proxy = 1;
+	return 0;
+}
+
+
+static void fils_dhcp_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+	struct hostapd_data *hapd = sock_ctx;
+	struct sta_info *sta;
+	u8 buf[1500], *pos, *end, *end_opt = NULL;
+	struct dhcp_data *dhcp;
+	struct sockaddr_in addr;
+	socklen_t addr_len;
+	ssize_t res;
+	u8 msgtype = 0;
+	int rapid_commit = 0;
+	struct iphdr *iph;
+	struct udphdr *udph;
+	struct wpabuf *resp;
+	const u8 *rpos;
+	size_t left, len;
+
+	addr_len = sizeof(addr);
+	res = recvfrom(sd, buf, sizeof(buf), 0,
+		       (struct sockaddr *) &addr, &addr_len);
+	if (res < 0) {
+		wpa_printf(MSG_DEBUG, "FILS: DHCP read failed: %s",
+			   strerror(errno));
+		return;
+	}
+	wpa_printf(MSG_DEBUG, "FILS: DHCP response from server %s:%d (len=%d)",
+		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), (int) res);
+	wpa_hexdump(MSG_MSGDUMP, "FILS: HLP - DHCP server response", buf, res);
+	if ((size_t) res < sizeof(*dhcp))
+		return;
+	dhcp = (struct dhcp_data *) buf;
+	if (dhcp->op != 2)
+		return; /* Not a BOOTREPLY */
+	if (dhcp->relay_ip != hapd->conf->own_ip_addr.u.v4.s_addr) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: HLP - DHCP response to unknown relay address 0x%x",
+			   dhcp->relay_ip);
+		return;
+	}
+	dhcp->relay_ip = 0;
+	pos = (u8 *) (dhcp + 1);
+	end = &buf[res];
+
+	if (end - pos < 4 || WPA_GET_BE32(pos) != DHCP_MAGIC) {
+		wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic in response");
+		return;
+	}
+	pos += 4;
+
+	wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options in response",
+		    pos, end - pos);
+	while (pos < end && *pos != DHCP_OPT_END) {
+		u8 opt, olen;
+
+		opt = *pos++;
+		if (opt == DHCP_OPT_PAD)
+			continue;
+		if (pos >= end)
+			break;
+		olen = *pos++;
+		if (olen > end - pos)
+			break;
+
+		switch (opt) {
+		case DHCP_OPT_MSG_TYPE:
+			if (olen > 0)
+				msgtype = pos[0];
+			break;
+		case DHCP_OPT_RAPID_COMMIT:
+			rapid_commit = 1;
+			break;
+		}
+		pos += olen;
+	}
+	if (pos < end && *pos == DHCP_OPT_END)
+		end_opt = pos;
+
+	wpa_printf(MSG_DEBUG,
+		   "FILS: HLP - DHCP message type %u (rapid_commit=%d hw_addr="
+		   MACSTR ")",
+		   msgtype, rapid_commit, MAC2STR(dhcp->hw_addr));
+
+	sta = ap_get_sta(hapd, dhcp->hw_addr);
+	if (!sta || !sta->fils_pending_assoc_req) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: No pending HLP DHCP exchange with hw_addr "
+			   MACSTR, MAC2STR(dhcp->hw_addr));
+		return;
+	}
+
+	if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPOFFER &&
+	    !rapid_commit) {
+		/* Use hostapd to take care of 4-message exchange and convert
+		 * the final DHCPACK to rapid commit version. */
+		if (fils_dhcp_request(hapd, sta, dhcp, end) == 0)
+			return;
+		/* failed, so send the server response as-is */
+	} else if (msgtype != DHCPACK) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: No DHCPACK available from the server and cannot do rapid commit proxying");
+	}
+
+	pos = buf;
+	resp = wpabuf_alloc(2 * ETH_ALEN + 6 + 2 +
+			    sizeof(*iph) + sizeof(*udph) + (end - pos) + 2);
+	if (!resp)
+		return;
+	wpabuf_put_data(resp, sta->addr, ETH_ALEN);
+	wpabuf_put_data(resp, hapd->own_addr, ETH_ALEN);
+	wpabuf_put_data(resp, "\xaa\xaa\x03\x00\x00\x00", 6);
+	wpabuf_put_be16(resp, ETH_P_IP);
+	iph = wpabuf_put(resp, sizeof(*iph));
+	iph->version = 4;
+	iph->ihl = sizeof(*iph) / 4;
+	iph->tot_len = htons(sizeof(*iph) + sizeof(*udph) + (end - pos));
+	iph->ttl = 1;
+	iph->protocol = 17; /* UDP */
+	iph->saddr = hapd->conf->dhcp_server.u.v4.s_addr;
+	iph->daddr = dhcp->client_ip;
+	iph->check = ip_checksum(iph, sizeof(*iph));
+	udph = wpabuf_put(resp, sizeof(*udph));
+	udph->uh_sport = htons(DHCP_SERVER_PORT);
+	udph->uh_dport = htons(DHCP_CLIENT_PORT);
+	udph->uh_ulen = htons(sizeof(*udph) + (end - pos));
+	udph->uh_sum = htons(0x0000); /* TODO: calculate checksum */
+	if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPACK &&
+	    !rapid_commit && sta->fils_dhcp_rapid_commit_proxy && end_opt) {
+		/* Add rapid commit option */
+		wpabuf_put_data(resp, pos, end_opt - pos);
+		wpabuf_put_u8(resp, DHCP_OPT_RAPID_COMMIT);
+		wpabuf_put_u8(resp, 0);
+		wpabuf_put_data(resp, end_opt, end - end_opt);
+	} else {
+		wpabuf_put_data(resp, pos, end - pos);
+	}
+	if (wpabuf_resize(&sta->fils_hlp_resp, wpabuf_len(resp) +
+			  2 * wpabuf_len(resp) / 255 + 100)) {
+		wpabuf_free(resp);
+		return;
+	}
+
+	rpos = wpabuf_head(resp);
+	left = wpabuf_len(resp);
+
+	wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXTENSION); /* Element ID */
+	if (left <= 254)
+		len = 1 + left;
+	else
+		len = 255;
+	wpabuf_put_u8(sta->fils_hlp_resp, len); /* Length */
+	/* Element ID Extension */
+	wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXT_FILS_HLP_CONTAINER);
+	/* Destination MAC Address, Source MAC Address, HLP Packet.
+	 * HLP Packet is in MSDU format (i.e., including the LLC/SNAP header
+	 * when LPD is used). */
+	wpabuf_put_data(sta->fils_hlp_resp, rpos, len - 1);
+	rpos += len - 1;
+	left -= len - 1;
+	while (left) {
+		wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_FRAGMENT);
+		len = left > 255 ? 255 : left;
+		wpabuf_put_u8(sta->fils_hlp_resp, len);
+		wpabuf_put_data(sta->fils_hlp_resp, rpos, len);
+		rpos += len;
+		left -= len;
+	}
+	wpabuf_free(resp);
+
+	if (sta->fils_drv_assoc_finish)
+		hostapd_notify_assoc_fils_finish(hapd, sta);
+	else
+		fils_hlp_finish_assoc(hapd, sta);
+}
+
+
+static int fils_process_hlp_dhcp(struct hostapd_data *hapd,
+				 struct sta_info *sta,
+				 const u8 *msg, size_t len)
+{
+	const struct dhcp_data *dhcp;
+	struct wpabuf *dhcp_buf;
+	struct dhcp_data *dhcp_msg;
+	u8 msgtype = 0;
+	int rapid_commit = 0;
+	const u8 *pos = msg, *end;
+	struct sockaddr_in addr;
+	ssize_t res;
+
+	if (len < sizeof(*dhcp))
+		return 0;
+	dhcp = (const struct dhcp_data *) pos;
+	end = pos + len;
+	wpa_printf(MSG_DEBUG,
+		   "FILS: HLP request DHCP: op=%u htype=%u hlen=%u hops=%u xid=0x%x",
+		   dhcp->op, dhcp->htype, dhcp->hlen, dhcp->hops,
+		   ntohl(dhcp->xid));
+	pos += sizeof(*dhcp);
+	if (dhcp->op != 1)
+		return 0; /* Not a BOOTREQUEST */
+
+	if (end - pos < 4)
+		return 0;
+	if (WPA_GET_BE32(pos) != DHCP_MAGIC) {
+		wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic");
+		return 0;
+	}
+	pos += 4;
+
+	wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options", pos, end - pos);
+	while (pos < end && *pos != DHCP_OPT_END) {
+		u8 opt, olen;
+
+		opt = *pos++;
+		if (opt == DHCP_OPT_PAD)
+			continue;
+		if (pos >= end)
+			break;
+		olen = *pos++;
+		if (olen > end - pos)
+			break;
+
+		switch (opt) {
+		case DHCP_OPT_MSG_TYPE:
+			if (olen > 0)
+				msgtype = pos[0];
+			break;
+		case DHCP_OPT_RAPID_COMMIT:
+			rapid_commit = 1;
+			break;
+		}
+		pos += olen;
+	}
+
+	wpa_printf(MSG_DEBUG, "FILS: HLP - DHCP message type %u", msgtype);
+	if (msgtype != DHCPDISCOVER)
+		return 0;
+
+	if (hapd->conf->dhcp_server.af != AF_INET ||
+	    hapd->conf->dhcp_server.u.v4.s_addr == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: HLP - no DHCPv4 server configured - drop request");
+		return 0;
+	}
+
+	if (hapd->conf->own_ip_addr.af != AF_INET ||
+	    hapd->conf->own_ip_addr.u.v4.s_addr == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: HLP - no IPv4 own_ip_addr configured - drop request");
+		return 0;
+	}
+
+	if (hapd->dhcp_sock < 0) {
+		int s;
+
+		s = socket(AF_INET, SOCK_DGRAM, 0);
+		if (s < 0) {
+			wpa_printf(MSG_ERROR,
+				   "FILS: Failed to open DHCP socket: %s",
+				   strerror(errno));
+			return 0;
+		}
+
+		if (hapd->conf->dhcp_relay_port) {
+			os_memset(&addr, 0, sizeof(addr));
+			addr.sin_family = AF_INET;
+			addr.sin_addr.s_addr =
+				hapd->conf->own_ip_addr.u.v4.s_addr;
+			addr.sin_port = htons(hapd->conf->dhcp_relay_port);
+			if (bind(s, (struct sockaddr *) &addr, sizeof(addr))) {
+				wpa_printf(MSG_ERROR,
+					   "FILS: Failed to bind DHCP socket: %s",
+					   strerror(errno));
+				close(s);
+				return 0;
+			}
+		}
+		if (eloop_register_sock(s, EVENT_TYPE_READ,
+					fils_dhcp_handler, NULL, hapd)) {
+			close(s);
+			return 0;
+		}
+
+		hapd->dhcp_sock = s;
+	}
+
+	dhcp_buf = wpabuf_alloc(len);
+	if (!dhcp_buf)
+		return 0;
+	dhcp_msg = wpabuf_put(dhcp_buf, len);
+	os_memcpy(dhcp_msg, msg, len);
+	dhcp_msg->relay_ip = hapd->conf->own_ip_addr.u.v4.s_addr;
+	os_memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
+	addr.sin_port = htons(hapd->conf->dhcp_server_port);
+	res = sendto(hapd->dhcp_sock, dhcp_msg, len, 0,
+		     (const struct sockaddr *) &addr, sizeof(addr));
+	if (res < 0) {
+		wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
+			   strerror(errno));
+		wpabuf_free(dhcp_buf);
+		/* Close the socket to try to recover from error */
+		eloop_unregister_read_sock(hapd->dhcp_sock);
+		close(hapd->dhcp_sock);
+		hapd->dhcp_sock = -1;
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "FILS: HLP relayed DHCP request to server %s:%d (rapid_commit=%d)",
+		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port),
+		   rapid_commit);
+	if (hapd->conf->dhcp_rapid_commit_proxy && rapid_commit) {
+		/* Store a copy of the DHCPDISCOVER for rapid commit proxying
+		 * purposes if the server does not support the rapid commit
+		 * option. */
+		wpa_printf(MSG_DEBUG,
+			   "FILS: Store DHCPDISCOVER for rapid commit proxy");
+		wpabuf_free(sta->hlp_dhcp_discover);
+		sta->hlp_dhcp_discover = dhcp_buf;
+	} else {
+		wpabuf_free(dhcp_buf);
+	}
+
+	return 1;
+}
+
+
+static int fils_process_hlp_udp(struct hostapd_data *hapd,
+				struct sta_info *sta, const u8 *dst,
+				const u8 *pos, size_t len)
+{
+	const struct iphdr *iph;
+	const struct udphdr *udph;
+	u16 sport, dport, ulen;
+
+	if (len < sizeof(*iph) + sizeof(*udph))
+		return 0;
+	iph = (const struct iphdr *) pos;
+	udph = (const struct udphdr *) (iph + 1);
+	sport = ntohs(udph->uh_sport);
+	dport = ntohs(udph->uh_dport);
+	ulen = ntohs(udph->uh_ulen);
+	wpa_printf(MSG_DEBUG,
+		   "FILS: HLP request UDP: sport=%u dport=%u ulen=%u sum=0x%x",
+		   sport, dport, ulen, ntohs(udph->uh_sum));
+	/* TODO: Check UDP checksum */
+	if (ulen < sizeof(*udph) || ulen > len - sizeof(*iph))
+		return 0;
+
+	if (dport == DHCP_SERVER_PORT && sport == DHCP_CLIENT_PORT) {
+		return fils_process_hlp_dhcp(hapd, sta, (const u8 *) (udph + 1),
+					     ulen - sizeof(*udph));
+	}
+
+	return 0;
+}
+
+
+static int fils_process_hlp_ip(struct hostapd_data *hapd,
+			       struct sta_info *sta, const u8 *dst,
+			       const u8 *pos, size_t len)
+{
+	const struct iphdr *iph;
+	u16 tot_len;
+
+	if (len < sizeof(*iph))
+		return 0;
+	iph = (const struct iphdr *) pos;
+	if (ip_checksum(iph, sizeof(*iph)) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: HLP request IPv4 packet had invalid header checksum - dropped");
+		return 0;
+	}
+	tot_len = ntohs(iph->tot_len);
+	if (tot_len > len)
+		return 0;
+	wpa_printf(MSG_DEBUG,
+		   "FILS: HLP request IPv4: saddr=%08x daddr=%08x protocol=%u",
+		   iph->saddr, iph->daddr, iph->protocol);
+	switch (iph->protocol) {
+	case 17:
+		return fils_process_hlp_udp(hapd, sta, dst, pos, len);
+	}
+
+	return 0;
+}
+
+
+static int fils_process_hlp_req(struct hostapd_data *hapd,
+				struct sta_info *sta,
+				const u8 *pos, size_t len)
+{
+	const u8 *pkt, *end;
+
+	wpa_printf(MSG_DEBUG, "FILS: HLP request from " MACSTR " (dst=" MACSTR
+		   " src=" MACSTR " len=%u)",
+		   MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
+		   (unsigned int) len);
+	if (os_memcmp(sta->addr, pos + ETH_ALEN, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: Ignore HLP request with unexpected source address"
+			   MACSTR, MAC2STR(pos + ETH_ALEN));
+		return 0;
+	}
+
+	end = pos + len;
+	pkt = pos + 2 * ETH_ALEN;
+	if (end - pkt >= 6 &&
+	    os_memcmp(pkt, "\xaa\xaa\x03\x00\x00\x00", 6) == 0)
+		pkt += 6; /* Remove SNAP/LLC header */
+	wpa_hexdump(MSG_MSGDUMP, "FILS: HLP request packet", pkt, end - pkt);
+
+	if (end - pkt < 2)
+		return 0;
+
+	switch (WPA_GET_BE16(pkt)) {
+	case ETH_P_IP:
+		return fils_process_hlp_ip(hapd, sta, pos, pkt + 2,
+					   end - pkt - 2);
+	}
+
+	return 0;
+}
+
+
+int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta,
+		     const u8 *pos, int left)
+{
+	const u8 *end = pos + left;
+	u8 *tmp, *tmp_pos;
+	int ret = 0;
+
+	/* Old DHCPDISCOVER is not needed anymore, if it was still pending */
+	wpabuf_free(sta->hlp_dhcp_discover);
+	sta->hlp_dhcp_discover = NULL;
+	sta->fils_dhcp_rapid_commit_proxy = 0;
+
+	/* Check if there are any FILS HLP Container elements */
+	while (end - pos >= 2) {
+		if (2 + pos[1] > end - pos)
+			return 0;
+		if (pos[0] == WLAN_EID_EXTENSION &&
+		    pos[1] >= 1 + 2 * ETH_ALEN &&
+		    pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER)
+			break;
+		pos += 2 + pos[1];
+	}
+	if (end - pos < 2)
+		return 0; /* No FILS HLP Container elements */
+
+	tmp = os_malloc(end - pos);
+	if (!tmp)
+		return 0;
+
+	while (end - pos >= 2) {
+		if (2 + pos[1] > end - pos ||
+		    pos[0] != WLAN_EID_EXTENSION ||
+		    pos[1] < 1 + 2 * ETH_ALEN ||
+		    pos[2] != WLAN_EID_EXT_FILS_HLP_CONTAINER)
+			break;
+		tmp_pos = tmp;
+		os_memcpy(tmp_pos, pos + 3, pos[1] - 1);
+		tmp_pos += pos[1] - 1;
+		pos += 2 + pos[1];
+
+		/* Add possible fragments */
+		while (end - pos >= 2 && pos[0] == WLAN_EID_FRAGMENT &&
+		       2 + pos[1] <= end - pos) {
+			os_memcpy(tmp_pos, pos + 2, pos[1]);
+			tmp_pos += pos[1];
+			pos += 2 + pos[1];
+		}
+
+		if (fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp) > 0)
+			ret = 1;
+	}
+
+	os_free(tmp);
+
+	return ret;
+}
+
+
+void fils_hlp_deinit(struct hostapd_data *hapd)
+{
+	if (hapd->dhcp_sock >= 0) {
+		eloop_unregister_read_sock(hapd->dhcp_sock);
+		close(hapd->dhcp_sock);
+		hapd->dhcp_sock = -1;
+	}
+}

+ 27 - 0
src/ap/fils_hlp.h

@@ -0,0 +1,27 @@
+/*
+ * FILS HLP request processing
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FILS_HLP_H
+#define FILS_HLP_H
+
+int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta,
+		     const u8 *pos, int left);
+
+#ifdef CONFIG_FILS
+
+void fils_hlp_deinit(struct hostapd_data *hapd);
+
+#else /* CONFIG_FILS */
+
+static inline void fils_hlp_deinit(struct hostapd_data *hapd)
+{
+}
+
+#endif /* CONFIG_FILS */
+
+#endif /* FILS_HLP_H */

+ 714 - 0
src/ap/gas_query_ap.c

@@ -0,0 +1,714 @@
+/*
+ * Generic advertisement service (GAS) query (hostapd)
+ * Copyright (c) 2009, Atheros Communications
+ * Copyright (c) 2011-2017, Qualcomm Atheros, Inc.
+ * Copyright (c) 2011-2014, 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 "utils/eloop.h"
+#include "utils/list.h"
+#include "common/ieee802_11_defs.h"
+#include "common/gas.h"
+#include "common/wpa_ctrl.h"
+#include "hostapd.h"
+#include "sta_info.h"
+#include "ap_drv_ops.h"
+#include "gas_query_ap.h"
+
+
+/** GAS query timeout in seconds */
+#define GAS_QUERY_TIMEOUT_PERIOD 2
+
+/* GAS query wait-time / duration in ms */
+#define GAS_QUERY_WAIT_TIME_INITIAL 1000
+#define GAS_QUERY_WAIT_TIME_COMEBACK 150
+
+/**
+ * struct gas_query_pending - Pending GAS query
+ */
+struct gas_query_pending {
+	struct dl_list list;
+	struct gas_query_ap *gas;
+	u8 addr[ETH_ALEN];
+	u8 dialog_token;
+	u8 next_frag_id;
+	unsigned int wait_comeback:1;
+	unsigned int offchannel_tx_started:1;
+	unsigned int retry:1;
+	int freq;
+	u16 status_code;
+	struct wpabuf *req;
+	struct wpabuf *adv_proto;
+	struct wpabuf *resp;
+	struct os_reltime last_oper;
+	void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
+		   enum gas_query_ap_result result,
+		   const struct wpabuf *adv_proto,
+		   const struct wpabuf *resp, u16 status_code);
+	void *ctx;
+	u8 sa[ETH_ALEN];
+};
+
+/**
+ * struct gas_query_ap - Internal GAS query data
+ */
+struct gas_query_ap {
+	struct hostapd_data *hapd;
+	void *msg_ctx;
+	struct dl_list pending; /* struct gas_query_pending */
+	struct gas_query_pending *current;
+};
+
+
+static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx);
+static void gas_query_timeout(void *eloop_data, void *user_ctx);
+static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx);
+static void gas_query_tx_initial_req(struct gas_query_ap *gas,
+				     struct gas_query_pending *query);
+static int gas_query_new_dialog_token(struct gas_query_ap *gas, const u8 *dst);
+
+
+static int ms_from_time(struct os_reltime *last)
+{
+	struct os_reltime now, res;
+
+	os_get_reltime(&now);
+	os_reltime_sub(&now, last, &res);
+	return res.sec * 1000 + res.usec / 1000;
+}
+
+
+/**
+ * gas_query_ap_init - Initialize GAS query component
+ * @hapd: Pointer to hostapd data
+ * Returns: Pointer to GAS query data or %NULL on failure
+ */
+struct gas_query_ap * gas_query_ap_init(struct hostapd_data *hapd,
+					void *msg_ctx)
+{
+	struct gas_query_ap *gas;
+
+	gas = os_zalloc(sizeof(*gas));
+	if (!gas)
+		return NULL;
+
+	gas->hapd = hapd;
+	gas->msg_ctx = msg_ctx;
+	dl_list_init(&gas->pending);
+
+	return gas;
+}
+
+
+static const char * gas_result_txt(enum gas_query_ap_result result)
+{
+	switch (result) {
+	case GAS_QUERY_AP_SUCCESS:
+		return "SUCCESS";
+	case GAS_QUERY_AP_FAILURE:
+		return "FAILURE";
+	case GAS_QUERY_AP_TIMEOUT:
+		return "TIMEOUT";
+	case GAS_QUERY_AP_PEER_ERROR:
+		return "PEER_ERROR";
+	case GAS_QUERY_AP_INTERNAL_ERROR:
+		return "INTERNAL_ERROR";
+	case GAS_QUERY_AP_DELETED_AT_DEINIT:
+		return "DELETED_AT_DEINIT";
+	}
+
+	return "N/A";
+}
+
+
+static void gas_query_free(struct gas_query_pending *query, int del_list)
+{
+	if (del_list)
+		dl_list_del(&query->list);
+
+	wpabuf_free(query->req);
+	wpabuf_free(query->adv_proto);
+	wpabuf_free(query->resp);
+	os_free(query);
+}
+
+
+static void gas_query_done(struct gas_query_ap *gas,
+			   struct gas_query_pending *query,
+			   enum gas_query_ap_result result)
+{
+	wpa_msg(gas->msg_ctx, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR
+		" dialog_token=%u freq=%d status_code=%u result=%s",
+		MAC2STR(query->addr), query->dialog_token, query->freq,
+		query->status_code, gas_result_txt(result));
+	if (gas->current == query)
+		gas->current = NULL;
+	eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
+	eloop_cancel_timeout(gas_query_timeout, gas, query);
+	eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query);
+	dl_list_del(&query->list);
+	query->cb(query->ctx, query->addr, query->dialog_token, result,
+		  query->adv_proto, query->resp, query->status_code);
+	gas_query_free(query, 0);
+}
+
+
+/**
+ * gas_query_ap_deinit - Deinitialize GAS query component
+ * @gas: GAS query data from gas_query_init()
+ */
+void gas_query_ap_deinit(struct gas_query_ap *gas)
+{
+	struct gas_query_pending *query, *next;
+
+	if (gas == NULL)
+		return;
+
+	dl_list_for_each_safe(query, next, &gas->pending,
+			      struct gas_query_pending, list)
+		gas_query_done(gas, query, GAS_QUERY_AP_DELETED_AT_DEINIT);
+
+	os_free(gas);
+}
+
+
+static struct gas_query_pending *
+gas_query_get_pending(struct gas_query_ap *gas, const u8 *addr, u8 dialog_token)
+{
+	struct gas_query_pending *q;
+	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
+		if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
+		    q->dialog_token == dialog_token)
+			return q;
+	}
+	return NULL;
+}
+
+
+static int gas_query_append(struct gas_query_pending *query, const u8 *data,
+			    size_t len)
+{
+	if (wpabuf_resize(&query->resp, len) < 0) {
+		wpa_printf(MSG_DEBUG, "GAS: No memory to store the response");
+		return -1;
+	}
+	wpabuf_put_data(query->resp, data, len);
+	return 0;
+}
+
+
+void gas_query_ap_tx_status(struct gas_query_ap *gas, const u8 *dst,
+			    const u8 *data, size_t data_len, int ok)
+{
+	struct gas_query_pending *query;
+	int dur;
+
+	if (!gas || !gas->current) {
+		wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: dst=" MACSTR
+			   " ok=%d - no query in progress", MAC2STR(dst), ok);
+		return;
+	}
+
+	query = gas->current;
+
+	dur = ms_from_time(&query->last_oper);
+	wpa_printf(MSG_DEBUG, "GAS: TX status: dst=" MACSTR
+		   " ok=%d query=%p dialog_token=%u dur=%d ms",
+		   MAC2STR(dst), ok, query, query->dialog_token, dur);
+	if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination");
+		return;
+	}
+	os_get_reltime(&query->last_oper);
+
+	eloop_cancel_timeout(gas_query_timeout, gas, query);
+	if (!ok) {
+		wpa_printf(MSG_DEBUG, "GAS: No ACK to GAS request");
+		eloop_register_timeout(0, 250000, gas_query_timeout,
+				       gas, query);
+	} else {
+		eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0,
+				       gas_query_timeout, gas, query);
+	}
+	if (query->wait_comeback && !query->retry) {
+		eloop_cancel_timeout(gas_query_rx_comeback_timeout,
+				     gas, query);
+		eloop_register_timeout(
+			0, (GAS_QUERY_WAIT_TIME_COMEBACK + 10) * 1000,
+			gas_query_rx_comeback_timeout, gas, query);
+	}
+}
+
+
+static int pmf_in_use(struct hostapd_data *hapd, const u8 *addr)
+{
+	struct sta_info *sta;
+
+	sta = ap_get_sta(hapd, addr);
+	return sta && (sta->flags & WLAN_STA_MFP);
+}
+
+
+static int gas_query_tx(struct gas_query_ap *gas,
+			struct gas_query_pending *query,
+			struct wpabuf *req, unsigned int wait_time)
+{
+	int res, prot = pmf_in_use(gas->hapd, query->addr);
+
+	wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u "
+		   "freq=%d prot=%d using src addr " MACSTR,
+		   MAC2STR(query->addr), (unsigned int) wpabuf_len(req),
+		   query->freq, prot, MAC2STR(query->sa));
+	if (prot) {
+		u8 *categ = wpabuf_mhead_u8(req);
+		*categ = WLAN_ACTION_PROTECTED_DUAL;
+	}
+	os_get_reltime(&query->last_oper);
+	res = hostapd_drv_send_action(gas->hapd, query->freq, wait_time,
+				      query->addr, wpabuf_head(req),
+				      wpabuf_len(req));
+	return res;
+}
+
+
+static void gas_query_tx_comeback_req(struct gas_query_ap *gas,
+				      struct gas_query_pending *query)
+{
+	struct wpabuf *req;
+	unsigned int wait_time;
+
+	req = gas_build_comeback_req(query->dialog_token);
+	if (req == NULL) {
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+		return;
+	}
+
+	wait_time = (query->retry || !query->offchannel_tx_started) ?
+		GAS_QUERY_WAIT_TIME_INITIAL : GAS_QUERY_WAIT_TIME_COMEBACK;
+
+	if (gas_query_tx(gas, query, req, wait_time) < 0) {
+		wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
+			   MACSTR, MAC2STR(query->addr));
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+	}
+
+	wpabuf_free(req);
+}
+
+
+static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx)
+{
+	struct gas_query_ap *gas = eloop_data;
+	struct gas_query_pending *query = user_ctx;
+	int dialog_token;
+
+	wpa_printf(MSG_DEBUG,
+		   "GAS: No response to comeback request received (retry=%u)",
+		   query->retry);
+	if (gas->current != query || query->retry)
+		return;
+	dialog_token = gas_query_new_dialog_token(gas, query->addr);
+	if (dialog_token < 0)
+		return;
+	wpa_printf(MSG_DEBUG,
+		   "GAS: Retry GAS query due to comeback response timeout");
+	query->retry = 1;
+	query->dialog_token = dialog_token;
+	*(wpabuf_mhead_u8(query->req) + 2) = dialog_token;
+	query->wait_comeback = 0;
+	query->next_frag_id = 0;
+	wpabuf_free(query->adv_proto);
+	query->adv_proto = NULL;
+	eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
+	eloop_cancel_timeout(gas_query_timeout, gas, query);
+	gas_query_tx_initial_req(gas, query);
+}
+
+
+static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx)
+{
+	struct gas_query_ap *gas = eloop_data;
+	struct gas_query_pending *query = user_ctx;
+
+	wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR,
+		   MAC2STR(query->addr));
+	gas_query_tx_comeback_req(gas, query);
+}
+
+
+static void gas_query_tx_comeback_req_delay(struct gas_query_ap *gas,
+					    struct gas_query_pending *query,
+					    u16 comeback_delay)
+{
+	unsigned int secs, usecs;
+
+	secs = (comeback_delay * 1024) / 1000000;
+	usecs = comeback_delay * 1024 - secs * 1000000;
+	wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR
+		   " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs);
+	eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
+	eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout,
+			       gas, query);
+}
+
+
+static void gas_query_rx_initial(struct gas_query_ap *gas,
+				 struct gas_query_pending *query,
+				 const u8 *adv_proto, const u8 *resp,
+				 size_t len, u16 comeback_delay)
+{
+	wpa_printf(MSG_DEBUG, "GAS: Received initial response from "
+		   MACSTR " (dialog_token=%u comeback_delay=%u)",
+		   MAC2STR(query->addr), query->dialog_token, comeback_delay);
+
+	query->adv_proto = wpabuf_alloc_copy(adv_proto, 2 + adv_proto[1]);
+	if (query->adv_proto == NULL) {
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+		return;
+	}
+
+	if (comeback_delay) {
+		eloop_cancel_timeout(gas_query_timeout, gas, query);
+		query->wait_comeback = 1;
+		gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
+		return;
+	}
+
+	/* Query was completed without comeback mechanism */
+	if (gas_query_append(query, resp, len) < 0) {
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+		return;
+	}
+
+	gas_query_done(gas, query, GAS_QUERY_AP_SUCCESS);
+}
+
+
+static void gas_query_rx_comeback(struct gas_query_ap *gas,
+				  struct gas_query_pending *query,
+				  const u8 *adv_proto, const u8 *resp,
+				  size_t len, u8 frag_id, u8 more_frags,
+				  u16 comeback_delay)
+{
+	wpa_printf(MSG_DEBUG, "GAS: Received comeback response from "
+		   MACSTR " (dialog_token=%u frag_id=%u more_frags=%u "
+		   "comeback_delay=%u)",
+		   MAC2STR(query->addr), query->dialog_token, frag_id,
+		   more_frags, comeback_delay);
+	eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query);
+
+	if ((size_t) 2 + adv_proto[1] != wpabuf_len(query->adv_proto) ||
+	    os_memcmp(adv_proto, wpabuf_head(query->adv_proto),
+		      wpabuf_len(query->adv_proto)) != 0) {
+		wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed "
+			   "between initial and comeback response from "
+			   MACSTR, MAC2STR(query->addr));
+		gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR);
+		return;
+	}
+
+	if (comeback_delay) {
+		if (frag_id) {
+			wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response "
+				   "with non-zero frag_id and comeback_delay "
+				   "from " MACSTR, MAC2STR(query->addr));
+			gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR);
+			return;
+		}
+		gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
+		return;
+	}
+
+	if (frag_id != query->next_frag_id) {
+		wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response "
+			   "from " MACSTR, MAC2STR(query->addr));
+		if (frag_id + 1 == query->next_frag_id) {
+			wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible "
+				   "retry of previous fragment");
+			return;
+		}
+		gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR);
+		return;
+	}
+	query->next_frag_id++;
+
+	if (gas_query_append(query, resp, len) < 0) {
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+		return;
+	}
+
+	if (more_frags) {
+		gas_query_tx_comeback_req(gas, query);
+		return;
+	}
+
+	gas_query_done(gas, query, GAS_QUERY_AP_SUCCESS);
+}
+
+
+/**
+ * gas_query_ap_rx - Indicate reception of a Public Action or Protected Dual
+ *	frame
+ * @gas: GAS query data from gas_query_init()
+ * @sa: Source MAC address of the Action frame
+ * @categ: Category of the Action frame
+ * @data: Payload of the Action frame
+ * @len: Length of @data
+ * @freq: Frequency (in MHz) on which the frame was received
+ * Returns: 0 if the Public Action frame was a GAS frame or -1 if not
+ */
+int gas_query_ap_rx(struct gas_query_ap *gas, const u8 *sa, u8 categ,
+		    const u8 *data, size_t len, int freq)
+{
+	struct gas_query_pending *query;
+	u8 action, dialog_token, frag_id = 0, more_frags = 0;
+	u16 comeback_delay, resp_len;
+	const u8 *pos, *adv_proto;
+	int prot, pmf;
+	unsigned int left;
+
+	if (!gas || len < 4)
+		return -1;
+
+	pos = data;
+	action = *pos++;
+	dialog_token = *pos++;
+
+	if (action != WLAN_PA_GAS_INITIAL_RESP &&
+	    action != WLAN_PA_GAS_COMEBACK_RESP)
+		return -1; /* Not a GAS response */
+
+	prot = categ == WLAN_ACTION_PROTECTED_DUAL;
+	pmf = pmf_in_use(gas->hapd, sa);
+	if (prot && !pmf) {
+		wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled");
+		return 0;
+	}
+	if (!prot && pmf) {
+		wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled");
+		return 0;
+	}
+
+	query = gas_query_get_pending(gas, sa, dialog_token);
+	if (query == NULL) {
+		wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR
+			   " dialog token %u", MAC2STR(sa), dialog_token);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR,
+		   ms_from_time(&query->last_oper), MAC2STR(sa));
+
+	if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) {
+		wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from "
+			   MACSTR " dialog token %u when waiting for comeback "
+			   "response", MAC2STR(sa), dialog_token);
+		return 0;
+	}
+
+	if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) {
+		wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from "
+			   MACSTR " dialog token %u when waiting for initial "
+			   "response", MAC2STR(sa), dialog_token);
+		return 0;
+	}
+
+	query->status_code = WPA_GET_LE16(pos);
+	pos += 2;
+
+	if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING &&
+	    action == WLAN_PA_GAS_COMEBACK_RESP) {
+		wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response");
+	} else if (query->status_code != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token "
+			   "%u failed - status code %u",
+			   MAC2STR(sa), dialog_token, query->status_code);
+		gas_query_done(gas, query, GAS_QUERY_AP_FAILURE);
+		return 0;
+	}
+
+	if (action == WLAN_PA_GAS_COMEBACK_RESP) {
+		if (pos + 1 > data + len)
+			return 0;
+		frag_id = *pos & 0x7f;
+		more_frags = (*pos & 0x80) >> 7;
+		pos++;
+	}
+
+	/* Comeback Delay */
+	if (pos + 2 > data + len)
+		return 0;
+	comeback_delay = WPA_GET_LE16(pos);
+	pos += 2;
+
+	/* Advertisement Protocol element */
+	if (pos + 2 > data + len || pos + 2 + pos[1] > data + len) {
+		wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement "
+			   "Protocol element in the response from " MACSTR,
+			   MAC2STR(sa));
+		return 0;
+	}
+
+	if (*pos != WLAN_EID_ADV_PROTO) {
+		wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement "
+			   "Protocol element ID %u in response from " MACSTR,
+			   *pos, MAC2STR(sa));
+		return 0;
+	}
+
+	adv_proto = pos;
+	pos += 2 + pos[1];
+
+	/* Query Response Length */
+	if (pos + 2 > data + len) {
+		wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length");
+		return 0;
+	}
+	resp_len = WPA_GET_LE16(pos);
+	pos += 2;
+
+	left = data + len - pos;
+	if (resp_len > left) {
+		wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in "
+			   "response from " MACSTR, MAC2STR(sa));
+		return 0;
+	}
+
+	if (resp_len < left) {
+		wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data "
+			   "after Query Response from " MACSTR,
+			   left - resp_len, MAC2STR(sa));
+	}
+
+	if (action == WLAN_PA_GAS_COMEBACK_RESP)
+		gas_query_rx_comeback(gas, query, adv_proto, pos, resp_len,
+				      frag_id, more_frags, comeback_delay);
+	else
+		gas_query_rx_initial(gas, query, adv_proto, pos, resp_len,
+				     comeback_delay);
+
+	return 0;
+}
+
+
+static void gas_query_timeout(void *eloop_data, void *user_ctx)
+{
+	struct gas_query_ap *gas = eloop_data;
+	struct gas_query_pending *query = user_ctx;
+
+	wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR
+		   " dialog token %u",
+		   MAC2STR(query->addr), query->dialog_token);
+	gas_query_done(gas, query, GAS_QUERY_AP_TIMEOUT);
+}
+
+
+static int gas_query_dialog_token_available(struct gas_query_ap *gas,
+					    const u8 *dst, u8 dialog_token)
+{
+	struct gas_query_pending *q;
+	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
+		if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
+		    dialog_token == q->dialog_token)
+			return 0;
+	}
+
+	return 1;
+}
+
+
+static void gas_query_tx_initial_req(struct gas_query_ap *gas,
+				     struct gas_query_pending *query)
+{
+	if (gas_query_tx(gas, query, query->req,
+			 GAS_QUERY_WAIT_TIME_INITIAL) < 0) {
+		wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
+			   MACSTR, MAC2STR(query->addr));
+		gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR);
+		return;
+	}
+	gas->current = query;
+
+	wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u",
+		   query->dialog_token);
+	eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0,
+			       gas_query_timeout, gas, query);
+}
+
+
+static int gas_query_new_dialog_token(struct gas_query_ap *gas, const u8 *dst)
+{
+	static int next_start = 0;
+	int dialog_token;
+
+	for (dialog_token = 0; dialog_token < 256; dialog_token++) {
+		if (gas_query_dialog_token_available(
+			    gas, dst, (next_start + dialog_token) % 256))
+			break;
+	}
+	if (dialog_token == 256)
+		return -1; /* Too many pending queries */
+	dialog_token = (next_start + dialog_token) % 256;
+	next_start = (dialog_token + 1) % 256;
+	return dialog_token;
+}
+
+
+/**
+ * gas_query_ap_req - Request a GAS query
+ * @gas: GAS query data from gas_query_init()
+ * @dst: Destination MAC address for the query
+ * @freq: Frequency (in MHz) for the channel on which to send the query
+ * @req: GAS query payload (to be freed by gas_query module in case of success
+ *	return)
+ * @cb: Callback function for reporting GAS query result and response
+ * @ctx: Context pointer to use with the @cb call
+ * Returns: dialog token (>= 0) on success or -1 on failure
+ */
+int gas_query_ap_req(struct gas_query_ap *gas, const u8 *dst, int freq,
+		     struct wpabuf *req,
+		     void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
+				enum gas_query_ap_result result,
+				const struct wpabuf *adv_proto,
+				const struct wpabuf *resp, u16 status_code),
+		     void *ctx)
+{
+	struct gas_query_pending *query;
+	int dialog_token;
+
+	if (!gas || wpabuf_len(req) < 3)
+		return -1;
+
+	dialog_token = gas_query_new_dialog_token(gas, dst);
+	if (dialog_token < 0)
+		return -1;
+
+	query = os_zalloc(sizeof(*query));
+	if (query == NULL)
+		return -1;
+
+	query->gas = gas;
+	os_memcpy(query->addr, dst, ETH_ALEN);
+	query->dialog_token = dialog_token;
+	query->freq = freq;
+	query->cb = cb;
+	query->ctx = ctx;
+	query->req = req;
+	dl_list_add(&gas->pending, &query->list);
+
+	*(wpabuf_mhead_u8(req) + 2) = dialog_token;
+
+	wpa_msg(gas->msg_ctx, MSG_INFO, GAS_QUERY_START "addr=" MACSTR
+		" dialog_token=%u freq=%d",
+		MAC2STR(query->addr), query->dialog_token, query->freq);
+
+	gas_query_tx_initial_req(gas, query);
+
+	return dialog_token;
+}

+ 43 - 0
src/ap/gas_query_ap.h

@@ -0,0 +1,43 @@
+/*
+ * Generic advertisement service (GAS) query
+ * Copyright (c) 2009, Atheros Communications
+ * Copyright (c) 2011-2017, Qualcomm Atheros
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef GAS_QUERY_AP_H
+#define GAS_QUERY_AP_H
+
+struct gas_query_ap;
+
+struct gas_query_ap * gas_query_ap_init(struct hostapd_data *hapd,
+					void *msg_ctx);
+void gas_query_ap_deinit(struct gas_query_ap *gas);
+int gas_query_ap_rx(struct gas_query_ap *gas, const u8 *sa, u8 categ,
+		    const u8 *data, size_t len, int freq);
+
+/**
+ * enum gas_query_ap_result - GAS query result
+ */
+enum gas_query_ap_result {
+	GAS_QUERY_AP_SUCCESS,
+	GAS_QUERY_AP_FAILURE,
+	GAS_QUERY_AP_TIMEOUT,
+	GAS_QUERY_AP_PEER_ERROR,
+	GAS_QUERY_AP_INTERNAL_ERROR,
+	GAS_QUERY_AP_DELETED_AT_DEINIT
+};
+
+int gas_query_ap_req(struct gas_query_ap *gas, const u8 *dst, int freq,
+		     struct wpabuf *req,
+		     void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
+				enum gas_query_ap_result result,
+				const struct wpabuf *adv_proto,
+				const struct wpabuf *resp, u16 status_code),
+		     void *ctx);
+void gas_query_ap_tx_status(struct gas_query_ap *gas, const u8 *dst,
+			    const u8 *data, size_t data_len, int ok);
+
+#endif /* GAS_QUERY_AP_H */

+ 327 - 56
src/ap/gas_serv.c

@@ -11,14 +11,31 @@
 #include "common.h"
 #include "common/ieee802_11_defs.h"
 #include "common/gas.h"
+#include "common/wpa_ctrl.h"
 #include "utils/eloop.h"
 #include "hostapd.h"
 #include "ap_config.h"
 #include "ap_drv_ops.h"
+#include "dpp_hostapd.h"
 #include "sta_info.h"
 #include "gas_serv.h"
 
 
+#ifdef CONFIG_DPP
+static void gas_serv_write_dpp_adv_proto(struct wpabuf *buf)
+{
+	wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO);
+	wpabuf_put_u8(buf, 8); /* Length */
+	wpabuf_put_u8(buf, 0x7f);
+	wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
+	wpabuf_put_u8(buf, 5);
+	wpabuf_put_be24(buf, OUI_WFA);
+	wpabuf_put_u8(buf, DPP_OUI_TYPE);
+	wpabuf_put_u8(buf, 0x01);
+}
+#endif /* CONFIG_DPP */
+
+
 static void convert_to_protected_dual(struct wpabuf *msg)
 {
 	u8 *categ = wpabuf_mhead_u8(msg);
@@ -50,9 +67,12 @@ gas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token)
 		sta->flags |= WLAN_STA_GAS;
 		/*
 		 * The default inactivity is 300 seconds. We don't need
-		 * it to be that long.
+		 * it to be that long. Use five second timeout and increase this
+		 * with the comeback_delay for testing cases.
 		 */
-		ap_sta_session_timeout(hapd, sta, 5);
+		ap_sta_session_timeout(hapd, sta,
+				       hapd->conf->gas_comeback_delay / 1024 +
+				       5);
 	} else {
 		ap_sta_replenish_timeout(hapd, sta, 5);
 	}
@@ -255,20 +275,29 @@ static void anqp_add_capab_list(struct hostapd_data *hapd,
 		wpabuf_put_le16(buf, ANQP_DOMAIN_NAME);
 	if (get_anqp_elem(hapd, ANQP_EMERGENCY_ALERT_URI))
 		wpabuf_put_le16(buf, ANQP_EMERGENCY_ALERT_URI);
+	if (get_anqp_elem(hapd, ANQP_TDLS_CAPABILITY))
+		wpabuf_put_le16(buf, ANQP_TDLS_CAPABILITY);
 	if (get_anqp_elem(hapd, ANQP_EMERGENCY_NAI))
 		wpabuf_put_le16(buf, ANQP_EMERGENCY_NAI);
 	if (get_anqp_elem(hapd, ANQP_NEIGHBOR_REPORT))
 		wpabuf_put_le16(buf, ANQP_NEIGHBOR_REPORT);
-	for (id = 273; id < 277; id++) {
-		if (get_anqp_elem(hapd, id))
-			wpabuf_put_le16(buf, id);
-	}
+#ifdef CONFIG_FILS
+	if (!dl_list_empty(&hapd->conf->fils_realms) ||
+	    get_anqp_elem(hapd, ANQP_FILS_REALM_INFO))
+		wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO);
+#endif /* CONFIG_FILS */
+	if (get_anqp_elem(hapd, ANQP_CAG))
+		wpabuf_put_le16(buf, ANQP_CAG);
 	if (get_anqp_elem(hapd, ANQP_VENUE_URL))
 		wpabuf_put_le16(buf, ANQP_VENUE_URL);
 	if (get_anqp_elem(hapd, ANQP_ADVICE_OF_CHARGE))
 		wpabuf_put_le16(buf, ANQP_ADVICE_OF_CHARGE);
 	if (get_anqp_elem(hapd, ANQP_LOCAL_CONTENT))
 		wpabuf_put_le16(buf, ANQP_LOCAL_CONTENT);
+	for (id = 280; id < 300; id++) {
+		if (get_anqp_elem(hapd, id))
+			wpabuf_put_le16(buf, id);
+	}
 #ifdef CONFIG_HS20
 	anqp_add_hs_capab_list(hapd, buf);
 #endif /* CONFIG_HS20 */
@@ -548,6 +577,36 @@ static void anqp_add_domain_name(struct hostapd_data *hapd, struct wpabuf *buf)
 }
 
 
+#ifdef CONFIG_FILS
+static void anqp_add_fils_realm_info(struct hostapd_data *hapd,
+				     struct wpabuf *buf)
+{
+	size_t count;
+
+	if (anqp_add_override(hapd, buf, ANQP_FILS_REALM_INFO))
+		return;
+
+	count = dl_list_len(&hapd->conf->fils_realms);
+	if (count > 10000)
+		count = 10000;
+	if (count) {
+		struct fils_realm *realm;
+
+		wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO);
+		wpabuf_put_le16(buf, 2 * count);
+
+		dl_list_for_each(realm, &hapd->conf->fils_realms,
+				 struct fils_realm, list) {
+			if (count == 0)
+				break;
+			wpabuf_put_data(buf, realm->hash, 2);
+			count--;
+		}
+	}
+}
+#endif /* CONFIG_FILS */
+
+
 #ifdef CONFIG_HS20
 
 static void anqp_add_operator_friendly_name(struct hostapd_data *hapd,
@@ -649,7 +708,7 @@ static void anqp_add_osu_provider(struct wpabuf *buf,
 
 	/* OSU Method List */
 	count = wpabuf_put(buf, 1);
-	for (i = 0; p->method_list[i] >= 0; i++)
+	for (i = 0; p->method_list && p->method_list[i] >= 0; i++)
 		wpabuf_put_u8(buf, p->method_list[i]);
 	*count = i;
 
@@ -786,6 +845,22 @@ static void anqp_add_icon_binary_file(struct hostapd_data *hapd,
 #endif /* CONFIG_HS20 */
 
 
+#ifdef CONFIG_MBO
+static void anqp_add_mbo_cell_data_conn_pref(struct hostapd_data *hapd,
+					     struct wpabuf *buf)
+{
+	if (hapd->conf->mbo_cell_data_conn_pref >= 0) {
+		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
+		wpabuf_put_be24(buf, OUI_WFA);
+		wpabuf_put_u8(buf, MBO_ANQP_OUI_TYPE);
+		wpabuf_put_u8(buf, MBO_ANQP_SUBTYPE_CELL_CONN_PREF);
+		wpabuf_put_u8(buf, hapd->conf->mbo_cell_data_conn_pref);
+		gas_anqp_set_element_len(buf, len);
+	}
+}
+#endif /* CONFIG_MBO */
+
+
 static size_t anqp_get_required_len(struct hostapd_data *hapd,
 				    const u16 *infoid,
 				    unsigned int num_infoid)
@@ -821,6 +896,10 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd,
 		len += 1000;
 	if (request & ANQP_REQ_ICON_REQUEST)
 		len += 65536;
+#ifdef CONFIG_FILS
+	if (request & ANQP_FILS_REALM_INFO)
+		len += 2 * dl_list_len(&hapd->conf->fils_realms);
+#endif /* CONFIG_FILS */
 	len += anqp_get_required_len(hapd, extra_req, num_extra_req);
 
 	buf = wpabuf_alloc(len);
@@ -860,8 +939,15 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd,
 	if (request & ANQP_REQ_EMERGENCY_NAI)
 		anqp_add_elem(hapd, buf, ANQP_EMERGENCY_NAI);
 
-	for (i = 0; i < num_extra_req; i++)
+	for (i = 0; i < num_extra_req; i++) {
+#ifdef CONFIG_FILS
+		if (extra_req[i] == ANQP_FILS_REALM_INFO) {
+			anqp_add_fils_realm_info(hapd, buf);
+			continue;
+		}
+#endif /* CONFIG_FILS */
 		anqp_add_elem(hapd, buf, extra_req[i]);
+	}
 
 #ifdef CONFIG_HS20
 	if (request & ANQP_REQ_HS_CAPABILITY_LIST)
@@ -880,6 +966,11 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd,
 		anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len);
 #endif /* CONFIG_HS20 */
 
+#ifdef CONFIG_MBO
+	if (request & ANQP_REQ_MBO_CELL_DATA_CONN_PREF)
+		anqp_add_mbo_cell_data_conn_pref(hapd, buf);
+#endif /* CONFIG_MBO */
+
 	return buf;
 }
 
@@ -984,6 +1075,13 @@ static void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id,
 			     get_anqp_elem(hapd, info_id) != NULL, qi);
 		break;
 	default:
+#ifdef CONFIG_FILS
+		if (info_id == ANQP_FILS_REALM_INFO &&
+		    !dl_list_empty(&hapd->conf->fils_realms)) {
+			wpa_printf(MSG_DEBUG,
+				   "ANQP: FILS Realm Information (local)");
+		} else
+#endif /* CONFIG_FILS */
 		if (!get_anqp_elem(hapd, info_id)) {
 			wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u",
 				   info_id);
@@ -1092,49 +1190,12 @@ static void rx_anqp_hs_icon_request(struct hostapd_data *hapd,
 }
 
 
-static void rx_anqp_vendor_specific(struct hostapd_data *hapd,
-				    const u8 *pos, const u8 *end,
-				    struct anqp_query_info *qi)
+static void rx_anqp_vendor_specific_hs20(struct hostapd_data *hapd,
+					 const u8 *pos, const u8 *end,
+					 struct anqp_query_info *qi)
 {
-	u32 oui;
 	u8 subtype;
 
-	if (end - pos < 4) {
-		wpa_printf(MSG_DEBUG, "ANQP: Too short vendor specific ANQP "
-			   "Query element");
-		return;
-	}
-
-	oui = WPA_GET_BE24(pos);
-	pos += 3;
-	if (oui != OUI_WFA) {
-		wpa_printf(MSG_DEBUG, "ANQP: Unsupported vendor OUI %06x",
-			   oui);
-		return;
-	}
-
-#ifdef CONFIG_P2P
-	if (*pos == P2P_OUI_TYPE) {
-		/*
-		 * This is for P2P SD and will be taken care of by the P2P
-		 * implementation. This query needs to be ignored in the generic
-		 * GAS server to avoid duplicated response.
-		 */
-		wpa_printf(MSG_DEBUG,
-			   "ANQP: Ignore WFA vendor type %u (P2P SD) in generic GAS server",
-			   *pos);
-		qi->p2p_sd = 1;
-		return;
-	}
-#endif /* CONFIG_P2P */
-
-	if (*pos != HS20_ANQP_OUI_TYPE) {
-		wpa_printf(MSG_DEBUG, "ANQP: Unsupported WFA vendor type %u",
-			   *pos);
-		return;
-	}
-	pos++;
-
 	if (end - pos <= 1)
 		return;
 
@@ -1164,6 +1225,115 @@ static void rx_anqp_vendor_specific(struct hostapd_data *hapd,
 #endif /* CONFIG_HS20 */
 
 
+#ifdef CONFIG_P2P
+static void rx_anqp_vendor_specific_p2p(struct hostapd_data *hapd,
+					struct anqp_query_info *qi)
+{
+	/*
+	 * This is for P2P SD and will be taken care of by the P2P
+	 * implementation. This query needs to be ignored in the generic
+	 * GAS server to avoid duplicated response.
+	 */
+	wpa_printf(MSG_DEBUG,
+		   "ANQP: Ignore WFA vendor type %u (P2P SD) in generic GAS server",
+		   P2P_OUI_TYPE);
+	qi->p2p_sd = 1;
+	return;
+}
+#endif /* CONFIG_P2P */
+
+
+#ifdef CONFIG_MBO
+
+static void rx_anqp_mbo_query_list(struct hostapd_data *hapd, u8 subtype,
+				  struct anqp_query_info *qi)
+{
+	switch (subtype) {
+	case MBO_ANQP_SUBTYPE_CELL_CONN_PREF:
+		set_anqp_req(ANQP_REQ_MBO_CELL_DATA_CONN_PREF,
+			     "Cellular Data Connection Preference",
+			     hapd->conf->mbo_cell_data_conn_pref >= 0, qi);
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO subtype %u",
+			   subtype);
+		break;
+	}
+}
+
+
+static void rx_anqp_vendor_specific_mbo(struct hostapd_data *hapd,
+					const u8 *pos, const u8 *end,
+					struct anqp_query_info *qi)
+{
+	u8 subtype;
+
+	if (end - pos < 1)
+		return;
+
+	subtype = *pos++;
+	switch (subtype) {
+	case MBO_ANQP_SUBTYPE_QUERY_LIST:
+		wpa_printf(MSG_DEBUG, "ANQP: MBO Query List");
+		while (pos < end) {
+			rx_anqp_mbo_query_list(hapd, *pos, qi);
+			pos++;
+		}
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO query subtype %u",
+			   subtype);
+		break;
+	}
+}
+
+#endif /* CONFIG_MBO */
+
+
+static void rx_anqp_vendor_specific(struct hostapd_data *hapd,
+				    const u8 *pos, const u8 *end,
+				    struct anqp_query_info *qi)
+{
+	u32 oui;
+
+	if (end - pos < 4) {
+		wpa_printf(MSG_DEBUG, "ANQP: Too short vendor specific ANQP "
+			   "Query element");
+		return;
+	}
+
+	oui = WPA_GET_BE24(pos);
+	pos += 3;
+	if (oui != OUI_WFA) {
+		wpa_printf(MSG_DEBUG, "ANQP: Unsupported vendor OUI %06x",
+			   oui);
+		return;
+	}
+
+	switch (*pos) {
+#ifdef CONFIG_P2P
+	case P2P_OUI_TYPE:
+		rx_anqp_vendor_specific_p2p(hapd, qi);
+		break;
+#endif /* CONFIG_P2P */
+#ifdef CONFIG_HS20
+	case HS20_ANQP_OUI_TYPE:
+		rx_anqp_vendor_specific_hs20(hapd, pos + 1, end, qi);
+		break;
+#endif /* CONFIG_HS20 */
+#ifdef CONFIG_MBO
+	case MBO_ANQP_OUI_TYPE:
+		rx_anqp_vendor_specific_mbo(hapd, pos + 1, end, qi);
+		break;
+#endif /* CONFIG_MBO */
+	default:
+		wpa_printf(MSG_DEBUG, "ANQP: Unsupported WFA vendor type %u",
+			   *pos);
+		break;
+	}
+}
+
+
 static void gas_serv_req_local_processing(struct hostapd_data *hapd,
 					  const u8 *sa, u8 dialog_token,
 					  struct anqp_query_info *qi, int prot,
@@ -1189,7 +1359,7 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_P2P */
 
-	if (wpabuf_len(buf) > hapd->gas_frag_limit ||
+	if (wpabuf_len(buf) > hapd->conf->gas_frag_limit ||
 	    hapd->conf->gas_comeback_delay) {
 		struct gas_dialog_info *di;
 		u16 comeback_delay = 1;
@@ -1240,6 +1410,72 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd,
 }
 
 
+#ifdef CONFIG_DPP
+static void gas_serv_req_dpp_processing(struct hostapd_data *hapd,
+					const u8 *sa, u8 dialog_token,
+					int prot, struct wpabuf *buf)
+{
+	struct wpabuf *tx_buf;
+
+	if (wpabuf_len(buf) > hapd->conf->gas_frag_limit ||
+	    hapd->conf->gas_comeback_delay) {
+		struct gas_dialog_info *di;
+		u16 comeback_delay = 1;
+
+		if (hapd->conf->gas_comeback_delay) {
+			/* Testing - allow overriding of the delay value */
+			comeback_delay = hapd->conf->gas_comeback_delay;
+		}
+
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Too long response to fit in initial response - use GAS comeback");
+		di = gas_dialog_create(hapd, sa, dialog_token);
+		if (!di) {
+			wpa_printf(MSG_INFO, "DPP: Could not create dialog for "
+				   MACSTR " (dialog token %u)",
+				   MAC2STR(sa), dialog_token);
+			wpabuf_free(buf);
+			tx_buf = gas_build_initial_resp(
+				dialog_token, WLAN_STATUS_UNSPECIFIED_FAILURE,
+				0, 10);
+			if (tx_buf)
+				gas_serv_write_dpp_adv_proto(tx_buf);
+		} else {
+			di->prot = prot;
+			di->sd_resp = buf;
+			di->sd_resp_pos = 0;
+			tx_buf = gas_build_initial_resp(
+				dialog_token, WLAN_STATUS_SUCCESS,
+				comeback_delay, 10);
+			if (tx_buf)
+				gas_serv_write_dpp_adv_proto(tx_buf);
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: GAS Initial response (no comeback)");
+		tx_buf = gas_build_initial_resp(
+			dialog_token, WLAN_STATUS_SUCCESS, 0,
+			10 + 2 + wpabuf_len(buf));
+		if (tx_buf) {
+			gas_serv_write_dpp_adv_proto(tx_buf);
+			wpabuf_put_le16(tx_buf, wpabuf_len(buf));
+			wpabuf_put_buf(tx_buf, buf);
+			hostapd_dpp_gas_status_handler(hapd, 1);
+		}
+		wpabuf_free(buf);
+	}
+	if (!tx_buf)
+		return;
+	if (prot)
+		convert_to_protected_dual(tx_buf);
+	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
+				wpabuf_head(tx_buf),
+				wpabuf_len(tx_buf));
+	wpabuf_free(tx_buf);
+}
+#endif /* CONFIG_DPP */
+
+
 static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
 					const u8 *sa,
 					const u8 *data, size_t len, int prot,
@@ -1252,6 +1488,9 @@ static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
 	u16 slen;
 	struct anqp_query_info qi;
 	const u8 *adv_proto;
+#ifdef CONFIG_DPP
+	int dpp = 0;
+#endif /* CONFIG_DPP */
 
 	if (len < 1 + 2)
 		return;
@@ -1279,6 +1518,15 @@ static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
 	next = pos + slen;
 	pos++; /* skip QueryRespLenLimit and PAME-BI */
 
+#ifdef CONFIG_DPP
+	if (slen == 8 && *pos == WLAN_EID_VENDOR_SPECIFIC &&
+	    pos[1] == 5 && WPA_GET_BE24(&pos[2]) == OUI_WFA &&
+	    pos[5] == DPP_OUI_TYPE && pos[6] == 0x01) {
+		wpa_printf(MSG_DEBUG, "DPP: Configuration Request");
+		dpp = 1;
+	} else
+#endif /* CONFIG_DPP */
+
 	if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
 		struct wpabuf *buf;
 		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
@@ -1318,6 +1566,18 @@ static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
 		return;
 	end = pos + slen;
 
+#ifdef CONFIG_DPP
+	if (dpp) {
+		struct wpabuf *msg;
+
+		msg = hostapd_dpp_gas_req_handler(hapd, sa, pos, slen);
+		if (!msg)
+			return;
+		gas_serv_req_dpp_processing(hapd, sa, dialog_token, prot, msg);
+		return;
+	}
+#endif /* CONFIG_DPP */
+
 	/* ANQP Query Request */
 	while (pos < end) {
 		u16 info_id, elen;
@@ -1339,11 +1599,9 @@ static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
 		case ANQP_QUERY_LIST:
 			rx_anqp_query_list(hapd, pos, pos + elen, &qi);
 			break;
-#ifdef CONFIG_HS20
 		case ANQP_VENDOR_SPECIFIC:
 			rx_anqp_vendor_specific(hapd, pos, pos + elen, &qi);
 			break;
-#endif /* CONFIG_HS20 */
 		default:
 			wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query "
 				   "Request element %u", info_id);
@@ -1393,8 +1651,8 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd,
 	}
 
 	frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos;
-	if (frag_len > hapd->gas_frag_limit) {
-		frag_len = hapd->gas_frag_limit;
+	if (frag_len > hapd->conf->gas_frag_limit) {
+		frag_len = hapd->conf->gas_frag_limit;
 		more = 1;
 	}
 	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u",
@@ -1407,6 +1665,18 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd,
 		gas_serv_dialog_clear(dialog);
 		return;
 	}
+#ifdef CONFIG_DPP
+	if (dialog->dpp) {
+		tx_buf = gas_build_comeback_resp(dialog_token,
+						 WLAN_STATUS_SUCCESS,
+						 dialog->sd_frag_id, more, 0,
+						 10 + frag_len);
+		if (tx_buf) {
+			gas_serv_write_dpp_adv_proto(tx_buf);
+			wpabuf_put_buf(tx_buf, buf);
+		}
+	} else
+#endif /* CONFIG_DPP */
 	tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token,
 						  WLAN_STATUS_SUCCESS,
 						  dialog->sd_frag_id,
@@ -1430,6 +1700,10 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd,
 	} else {
 		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of "
 			"SD response sent");
+#ifdef CONFIG_DPP
+		if (dialog->dpp)
+			hostapd_dpp_gas_status_handler(hapd, 1);
+#endif /* CONFIG_DPP */
 		gas_serv_dialog_clear(dialog);
 		gas_serv_free_dialogs(hapd, sa);
 	}
@@ -1495,9 +1769,6 @@ int gas_serv_init(struct hostapd_data *hapd)
 {
 	hapd->public_action_cb2 = gas_serv_rx_public_action;
 	hapd->public_action_cb2_ctx = hapd;
-	hapd->gas_frag_limit = 1400;
-	if (hapd->conf->gas_frag_limit > 0)
-		hapd->gas_frag_limit = hapd->conf->gas_frag_limit;
 	return 0;
 }
 

+ 5 - 1
src/ap/gas_serv.h

@@ -41,7 +41,7 @@
 #define ANQP_REQ_EMERGENCY_NAI \
 	(1 << (ANQP_EMERGENCY_NAI - ANQP_QUERY_LIST))
 /*
- * First 16 Hotspot 2.0 vendor specific ANQP-elements can be included in the
+ * First 15 Hotspot 2.0 vendor specific ANQP-elements can be included in the
  * optimized bitmap.
  */
 #define ANQP_REQ_HS_CAPABILITY_LIST \
@@ -60,6 +60,9 @@
 	(0x10000 << HS20_STYPE_OSU_PROVIDERS_LIST)
 #define ANQP_REQ_ICON_REQUEST \
 	(0x10000 << HS20_STYPE_ICON_REQUEST)
+/* The first MBO ANQP-element can be included in the optimized bitmap. */
+#define ANQP_REQ_MBO_CELL_DATA_CONN_PREF \
+	(BIT(29) << MBO_ANQP_SUBTYPE_CELL_CONN_PREF)
 
 struct gas_dialog_info {
 	u8 valid;
@@ -68,6 +71,7 @@ struct gas_dialog_info {
 	size_t sd_resp_pos; /* Offset in sd_resp */
 	u8 sd_frag_id;
 	int prot; /* whether Protected Dual of Public Action frame is used */
+	int dpp; /* whether this is a DPP Config Response */
 };
 
 struct hostapd_data;

+ 181 - 45
src/ap/hostapd.c

@@ -31,6 +31,8 @@
 #include "vlan_init.h"
 #include "wpa_auth.h"
 #include "wps_hostapd.h"
+#include "dpp_hostapd.h"
+#include "gas_query_ap.h"
 #include "hw_features.h"
 #include "wpa_auth_glue.h"
 #include "ap_drv_ops.h"
@@ -45,6 +47,8 @@
 #include "ndisc_snoop.h"
 #include "neighbor_db.h"
 #include "rrm.h"
+#include "fils_hlp.h"
+#include "acs.h"
 
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
@@ -75,6 +79,9 @@ static void hostapd_reload_bss(struct hostapd_data *hapd)
 {
 	struct hostapd_ssid *ssid;
 
+	if (!hapd->started)
+		return;
+
 #ifndef CONFIG_NO_RADIUS
 	radius_client_reconfig(hapd->radius, hapd->conf->radius);
 #endif /* CONFIG_NO_RADIUS */
@@ -210,7 +217,7 @@ static void hostapd_broadcast_key_clear_iface(struct hostapd_data *hapd,
 {
 	int i;
 
-	if (!ifname)
+	if (!ifname || !hapd->drv_priv)
 		return;
 	for (i = 0; i < NUM_WEP_KEYS; i++) {
 		if (hostapd_drv_set_key(ifname, hapd, WPA_ALG_NONE, NULL, i,
@@ -297,6 +304,10 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd)
 #endif /* CONFIG_NO_RADIUS */
 
 	hostapd_deinit_wps(hapd);
+#ifdef CONFIG_DPP
+	hostapd_dpp_deinit(hapd);
+	gas_query_ap_deinit(hapd->gas);
+#endif /* CONFIG_DPP */
 
 	authsrv_deinit(hapd);
 
@@ -341,6 +352,7 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd)
 #endif /* CONFIG_MESH */
 
 	hostapd_clean_rrm(hapd);
+	fils_hlp_deinit(hapd);
 }
 
 
@@ -357,8 +369,10 @@ static void hostapd_cleanup(struct hostapd_data *hapd)
 	wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s))", __func__, hapd,
 		   hapd->conf->iface);
 	if (hapd->iface->interfaces &&
-	    hapd->iface->interfaces->ctrl_iface_deinit)
+	    hapd->iface->interfaces->ctrl_iface_deinit) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO, WPA_EVENT_TERMINATING);
 		hapd->iface->interfaces->ctrl_iface_deinit(hapd);
+	}
 	hostapd_free_hapd_data(hapd);
 }
 
@@ -387,8 +401,11 @@ static void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)
 	hostapd_stop_setup_timers(iface);
 #endif /* NEED_AP_MLME */
 #endif /* CONFIG_IEEE80211N */
+	if (iface->current_mode)
+		acs_cleanup(iface);
 	hostapd_free_hw_features(iface->hw_features, iface->num_hw_features);
 	iface->hw_features = NULL;
+	iface->current_mode = NULL;
 	os_free(iface->current_rates);
 	iface->current_rates = NULL;
 	os_free(iface->basic_rates);
@@ -484,9 +501,12 @@ static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason)
 			ret = -1;
 		}
 	}
-	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "Deauthenticate all stations");
-	os_memset(addr, 0xff, ETH_ALEN);
-	hostapd_drv_sta_deauth(hapd, addr, reason);
+	if (hapd->conf && hapd->conf->broadcast_deauth) {
+		wpa_dbg(hapd->msg_ctx, MSG_DEBUG,
+			"Deauthenticate all stations");
+		os_memset(addr, 0xff, ETH_ALEN);
+		hostapd_drv_sta_deauth(hapd, addr, reason);
+	}
 	hostapd_free_stas(hapd);
 
 	return ret;
@@ -956,13 +976,13 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first)
 	if (conf->wmm_enabled < 0)
 		conf->wmm_enabled = hapd->iconf->ieee80211n;
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (is_zero_ether_addr(conf->r1_key_holder))
 		os_memcpy(conf->r1_key_holder, hapd->own_addr, ETH_ALEN);
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_MESH
-	if (hapd->iface->mconf == NULL)
+	if ((hapd->conf->mesh & MESH_ENABLED) && hapd->iface->mconf == NULL)
 		flush_old_stations = 0;
 #endif /* CONFIG_MESH */
 
@@ -1063,6 +1083,14 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first)
 	if (hostapd_init_wps(hapd, conf))
 		return -1;
 
+#ifdef CONFIG_DPP
+	hapd->gas = gas_query_ap_init(hapd, hapd->msg_ctx);
+	if (!hapd->gas)
+		return -1;
+	if (hostapd_dpp_init(hapd))
+		return -1;
+#endif /* CONFIG_DPP */
+
 	if (authsrv_init(hapd) < 0)
 		return -1;
 
@@ -1150,7 +1178,7 @@ static void hostapd_tx_queue_params(struct hostapd_iface *iface)
 	struct hostapd_tx_queue_params *p;
 
 #ifdef CONFIG_MESH
-	if (iface->mconf == NULL)
+	if ((hapd->conf->mesh & MESH_ENABLED) && iface->mconf == NULL)
 		return;
 #endif /* CONFIG_MESH */
 
@@ -1561,7 +1589,7 @@ static void hostapd_set_own_neighbor_report(struct hostapd_data *hapd)
 	int vht = hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac;
 	struct wpa_ssid_value ssid;
 	u8 channel, op_class;
-	int center_freq1 = 0, center_freq2 = 0;
+	u8 center_freq1_idx = 0, center_freq2_idx = 0;
 	enum nr_chan_width width;
 	u32 bssid_info;
 	struct wpabuf *nr;
@@ -1598,22 +1626,22 @@ static void hostapd_set_own_neighbor_report(struct hostapd_data *hapd)
 
 	/* TODO: Set NEI_REP_BSSID_INFO_MOBILITY_DOMAIN if MDE is set */
 
-	ieee80211_freq_to_channel_ext(hapd->iface->freq,
-				      hapd->iconf->secondary_channel,
-				      hapd->iconf->vht_oper_chwidth,
-				      &op_class, &channel);
+	if (ieee80211_freq_to_channel_ext(hapd->iface->freq,
+					  hapd->iconf->secondary_channel,
+					  hapd->iconf->vht_oper_chwidth,
+					  &op_class, &channel) ==
+	    NUM_HOSTAPD_MODES)
+		return;
 	width = hostapd_get_nr_chan_width(hapd, ht, vht);
 	if (vht) {
-		center_freq1 = ieee80211_chan_to_freq(
-			NULL, op_class,
-			hapd->iconf->vht_oper_centr_freq_seg0_idx);
+		center_freq1_idx = hapd->iconf->vht_oper_centr_freq_seg0_idx;
 		if (width == NR_CHAN_WIDTH_80P80)
-			center_freq2 = ieee80211_chan_to_freq(
-				NULL, op_class,
-				hapd->iconf->vht_oper_centr_freq_seg1_idx);
+			center_freq2_idx =
+				hapd->iconf->vht_oper_centr_freq_seg1_idx;
 	} else if (ht) {
-		center_freq1 = hapd->iface->freq +
-			10 * hapd->iconf->secondary_channel;
+		ieee80211_freq_to_chan(hapd->iface->freq +
+				       10 * hapd->iconf->secondary_channel,
+				       &center_freq1_idx);
 	}
 
 	ssid.ssid_len = hapd->conf->ssid.ssid_len;
@@ -1641,17 +1669,113 @@ static void hostapd_set_own_neighbor_report(struct hostapd_data *hapd)
 	wpabuf_put_u8(nr, WNM_NEIGHBOR_WIDE_BW_CHAN);
 	wpabuf_put_u8(nr, 3);
 	wpabuf_put_u8(nr, width);
-	wpabuf_put_u8(nr, center_freq1);
-	wpabuf_put_u8(nr, center_freq2);
+	wpabuf_put_u8(nr, center_freq1_idx);
+	wpabuf_put_u8(nr, center_freq2_idx);
 
 	hostapd_neighbor_set(hapd, hapd->own_addr, &ssid, nr, hapd->iconf->lci,
-			     hapd->iconf->civic);
+			     hapd->iconf->civic, hapd->iconf->stationary_ap);
 
 	wpabuf_free(nr);
 #endif /* NEED_AP_MLME */
 }
 
 
+#ifdef CONFIG_OWE
+
+static int hostapd_owe_iface_iter(struct hostapd_iface *iface, void *ctx)
+{
+	struct hostapd_data *hapd = ctx;
+	size_t i;
+
+	for (i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *bss = iface->bss[i];
+
+		if (os_strcmp(hapd->conf->owe_transition_ifname,
+			      bss->conf->iface) != 0)
+			continue;
+
+		wpa_printf(MSG_DEBUG,
+			   "OWE: ifname=%s found transition mode ifname=%s BSSID "
+			   MACSTR " SSID %s",
+			   hapd->conf->iface, bss->conf->iface,
+			   MAC2STR(bss->own_addr),
+			   wpa_ssid_txt(bss->conf->ssid.ssid,
+					bss->conf->ssid.ssid_len));
+		if (!bss->conf->ssid.ssid_set || !bss->conf->ssid.ssid_len ||
+		    is_zero_ether_addr(bss->own_addr))
+			continue;
+
+		os_memcpy(hapd->conf->owe_transition_bssid, bss->own_addr,
+			  ETH_ALEN);
+		os_memcpy(hapd->conf->owe_transition_ssid,
+			  bss->conf->ssid.ssid, bss->conf->ssid.ssid_len);
+		hapd->conf->owe_transition_ssid_len = bss->conf->ssid.ssid_len;
+		wpa_printf(MSG_DEBUG,
+			   "OWE: Copied transition mode information");
+		return 1;
+	}
+
+	return 0;
+}
+
+
+int hostapd_owe_trans_get_info(struct hostapd_data *hapd)
+{
+	if (hapd->conf->owe_transition_ssid_len > 0 &&
+	    !is_zero_ether_addr(hapd->conf->owe_transition_bssid))
+		return 0;
+
+	/* Find transition mode SSID/BSSID information from a BSS operated by
+	 * this hostapd instance. */
+	if (!hapd->iface->interfaces ||
+	    !hapd->iface->interfaces->for_each_interface)
+		return hostapd_owe_iface_iter(hapd->iface, hapd);
+	else
+		return hapd->iface->interfaces->for_each_interface(
+			hapd->iface->interfaces, hostapd_owe_iface_iter, hapd);
+}
+
+
+static int hostapd_owe_iface_iter2(struct hostapd_iface *iface, void *ctx)
+{
+	size_t i;
+
+	for (i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *bss = iface->bss[i];
+		int res;
+
+		if (!bss->conf->owe_transition_ifname[0])
+			continue;
+		res = hostapd_owe_trans_get_info(bss);
+		if (res == 0)
+			continue;
+		wpa_printf(MSG_DEBUG,
+			   "OWE: Matching transition mode interface enabled - update beacon data for %s",
+			   bss->conf->iface);
+		ieee802_11_set_beacon(bss);
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_OWE */
+
+
+static void hostapd_owe_update_trans(struct hostapd_iface *iface)
+{
+#ifdef CONFIG_OWE
+	/* Check whether the enabled BSS can complete OWE transition mode
+	 * configuration for any pending interface. */
+	if (!iface->interfaces ||
+	    !iface->interfaces->for_each_interface)
+		hostapd_owe_iface_iter2(iface, NULL);
+	else
+		iface->interfaces->for_each_interface(
+			iface->interfaces, hostapd_owe_iface_iter2, NULL);
+#endif /* CONFIG_OWE */
+}
+
+
 static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,
 						 int err)
 {
@@ -1827,6 +1951,7 @@ dfs_offload:
 #endif /* CONFIG_FST */
 
 	hostapd_set_state(iface, HAPD_IFACE_ENABLED);
+	hostapd_owe_update_trans(iface);
 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_ENABLED);
 	if (hapd->setup_complete_cb)
 		hapd->setup_complete_cb(hapd->setup_complete_cb_ctx);
@@ -1997,10 +2122,16 @@ hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface,
 	hapd->iconf = conf;
 	hapd->conf = bss;
 	hapd->iface = hapd_iface;
-	hapd->driver = hapd->iconf->driver;
+	if (conf)
+		hapd->driver = conf->driver;
 	hapd->ctrl_sock = -1;
 	dl_list_init(&hapd->ctrl_dst);
 	dl_list_init(&hapd->nr_db);
+	hapd->dhcp_sock = -1;
+#ifdef CONFIG_IEEE80211R_AP
+	dl_list_init(&hapd->l2_queue);
+	dl_list_init(&hapd->l2_oui_queue);
+#endif /* CONFIG_IEEE80211R_AP */
 
 	return hapd;
 }
@@ -2612,6 +2743,7 @@ int hostapd_add_iface(struct hapd_interfaces *interfaces, char *buf)
 				return -1;
 			}
 		}
+		hostapd_owe_update_trans(hapd_iface);
 		return 0;
 	}
 
@@ -2829,12 +2961,24 @@ void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
 	ieee802_1x_new_station(hapd, sta);
 	if (reassoc) {
 		if (sta->auth_alg != WLAN_AUTH_FT &&
+		    sta->auth_alg != WLAN_AUTH_FILS_SK &&
+		    sta->auth_alg != WLAN_AUTH_FILS_SK_PFS &&
+		    sta->auth_alg != WLAN_AUTH_FILS_PK &&
 		    !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)))
 			wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH);
 	} else
 		wpa_auth_sta_associated(hapd->wpa_auth, sta->wpa_sm);
 
-	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_INACTIVITY_TIMER)) {
+	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED) {
+		if (eloop_cancel_timeout(ap_handle_timer, hapd, sta) > 0) {
+			wpa_printf(MSG_DEBUG,
+				   "%s: %s: canceled wired ap_handle_timer timeout for "
+				   MACSTR,
+				   hapd->conf->iface, __func__,
+				   MAC2STR(sta->addr));
+		}
+	} else if (!(hapd->iface->drv_flags &
+		     WPA_DRIVER_FLAGS_INACTIVITY_TIMER)) {
 		wpa_printf(MSG_DEBUG,
 			   "%s: %s: reschedule ap_handle_timer timeout for "
 			   MACSTR " (%d seconds - ap_max_inactivity)",
@@ -2928,60 +3072,52 @@ static int hostapd_build_beacon_data(struct hostapd_data *hapd,
 		goto free_ap_params;
 
 	ret = -1;
-	beacon->head = os_malloc(params.head_len);
+	beacon->head = os_memdup(params.head, params.head_len);
 	if (!beacon->head)
 		goto free_ap_extra_ies;
 
-	os_memcpy(beacon->head, params.head, params.head_len);
 	beacon->head_len = params.head_len;
 
-	beacon->tail = os_malloc(params.tail_len);
+	beacon->tail = os_memdup(params.tail, params.tail_len);
 	if (!beacon->tail)
 		goto free_beacon;
 
-	os_memcpy(beacon->tail, params.tail, params.tail_len);
 	beacon->tail_len = params.tail_len;
 
 	if (params.proberesp != NULL) {
-		beacon->probe_resp = os_malloc(params.proberesp_len);
+		beacon->probe_resp = os_memdup(params.proberesp,
+					       params.proberesp_len);
 		if (!beacon->probe_resp)
 			goto free_beacon;
 
-		os_memcpy(beacon->probe_resp, params.proberesp,
-			  params.proberesp_len);
 		beacon->probe_resp_len = params.proberesp_len;
 	}
 
 	/* copy the extra ies */
 	if (beacon_extra) {
-		beacon->beacon_ies = os_malloc(wpabuf_len(beacon_extra));
+		beacon->beacon_ies = os_memdup(beacon_extra->buf,
+					       wpabuf_len(beacon_extra));
 		if (!beacon->beacon_ies)
 			goto free_beacon;
 
-		os_memcpy(beacon->beacon_ies,
-			  beacon_extra->buf, wpabuf_len(beacon_extra));
 		beacon->beacon_ies_len = wpabuf_len(beacon_extra);
 	}
 
 	if (proberesp_extra) {
-		beacon->proberesp_ies =
-			os_malloc(wpabuf_len(proberesp_extra));
+		beacon->proberesp_ies = os_memdup(proberesp_extra->buf,
+						  wpabuf_len(proberesp_extra));
 		if (!beacon->proberesp_ies)
 			goto free_beacon;
 
-		os_memcpy(beacon->proberesp_ies, proberesp_extra->buf,
-			  wpabuf_len(proberesp_extra));
 		beacon->proberesp_ies_len = wpabuf_len(proberesp_extra);
 	}
 
 	if (assocresp_extra) {
-		beacon->assocresp_ies =
-			os_malloc(wpabuf_len(assocresp_extra));
+		beacon->assocresp_ies = os_memdup(assocresp_extra->buf,
+						  wpabuf_len(assocresp_extra));
 		if (!beacon->assocresp_ies)
 			goto free_beacon;
 
-		os_memcpy(beacon->assocresp_ies, assocresp_extra->buf,
-			  wpabuf_len(assocresp_extra));
 		beacon->assocresp_ies_len = wpabuf_len(assocresp_extra);
 	}
 

+ 84 - 6
src/ap/hostapd.h

@@ -53,7 +53,16 @@ struct hapd_interfaces {
 #ifndef CONFIG_NO_VLAN
 	struct dynamic_iface *vlan_priv;
 #endif /* CONFIG_NO_VLAN */
+#ifdef CONFIG_ETH_P_OUI
+	struct dl_list eth_p_oui; /* OUI Extended EtherType handlers */
+#endif /* CONFIG_ETH_P_OUI */
 	int eloop_initialized;
+
+#ifdef CONFIG_DPP
+	int dpp_init_done;
+	struct dl_list dpp_bootstrap; /* struct dpp_bootstrap_info */
+	struct dl_list dpp_configurator; /* struct dpp_configurator */
+#endif /* CONFIG_DPP */
 };
 
 enum hostapd_chan_status {
@@ -76,6 +85,7 @@ struct hostapd_rate_data {
 };
 
 struct hostapd_frame_info {
+	unsigned int freq;
 	u32 channel;
 	u32 datarate;
 	int ssi_signal; /* dBm */
@@ -109,6 +119,7 @@ struct hostapd_neighbor_entry {
 	struct wpabuf *civic;
 	/* LCI update time */
 	struct os_time lci_date;
+	int stationary;
 };
 
 /**
@@ -184,6 +195,17 @@ struct hostapd_data {
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
 	struct l2_packet_data *l2;
+
+#ifdef CONFIG_IEEE80211R_AP
+	struct dl_list l2_queue;
+	struct dl_list l2_oui_queue;
+	struct eth_p_oui_ctx *oui_pull;
+	struct eth_p_oui_ctx *oui_resp;
+	struct eth_p_oui_ctx *oui_push;
+	struct eth_p_oui_ctx *oui_sreq;
+	struct eth_p_oui_ctx *oui_sresp;
+#endif /* CONFIG_IEEE80211R_AP */
+
 	struct wps_context *wps;
 
 	int beacon_set_done;
@@ -242,9 +264,6 @@ struct hostapd_data {
 	unsigned int cs_c_off_ecsa_beacon;
 	unsigned int cs_c_off_ecsa_proberesp;
 
-	/* BSS Load */
-	unsigned int bss_load_update_timeout;
-
 #ifdef CONFIG_P2P
 	struct p2p_data *p2p;
 	struct p2p_group *p2p_group;
@@ -259,9 +278,6 @@ struct hostapd_data {
 	int noa_start;
 	int noa_duration;
 #endif /* CONFIG_P2P */
-#ifdef CONFIG_INTERWORKING
-	size_t gas_frag_limit;
-#endif /* CONFIG_INTERWORKING */
 #ifdef CONFIG_PROXYARP
 	struct l2_packet_data *sock_dhcp;
 	struct l2_packet_data *sock_ndisc;
@@ -292,18 +308,67 @@ struct hostapd_data {
 	unsigned int ext_eapol_frame_io:1;
 
 	struct l2_packet_data *l2_test;
+
+	enum wpa_alg last_gtk_alg;
+	int last_gtk_key_idx;
+	u8 last_gtk[WPA_GTK_MAX_LEN];
+	size_t last_gtk_len;
+
+#ifdef CONFIG_IEEE80211W
+	enum wpa_alg last_igtk_alg;
+	int last_igtk_key_idx;
+	u8 last_igtk[WPA_IGTK_MAX_LEN];
+	size_t last_igtk_len;
+#endif /* CONFIG_IEEE80211W */
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #ifdef CONFIG_MBO
 	unsigned int mbo_assoc_disallow;
+	/**
+	 * enable_oce - Enable OCE if it is enabled by user and device also
+	 *		supports OCE.
+	 */
+	u8 enable_oce;
 #endif /* CONFIG_MBO */
 
 	struct dl_list nr_db;
 
+	u8 beacon_req_token;
 	u8 lci_req_token;
 	u8 range_req_token;
 	unsigned int lci_req_active:1;
 	unsigned int range_req_active:1;
+
+	int dhcp_sock; /* UDP socket used with the DHCP server */
+
+#ifdef CONFIG_DPP
+	int dpp_init_done;
+	struct dpp_authentication *dpp_auth;
+	u8 dpp_allowed_roles;
+	int dpp_qr_mutual;
+	int dpp_auth_ok_on_ack;
+	int dpp_in_response_listen;
+	struct gas_query_ap *gas;
+	struct dpp_pkex *dpp_pkex;
+	struct dpp_bootstrap_info *dpp_pkex_bi;
+	char *dpp_pkex_code;
+	char *dpp_pkex_identifier;
+	char *dpp_pkex_auth_cmd;
+	char *dpp_configurator_params;
+	struct os_reltime dpp_last_init;
+	struct os_reltime dpp_init_iter_start;
+	unsigned int dpp_init_max_tries;
+	unsigned int dpp_init_retry_time;
+	unsigned int dpp_resp_wait_time;
+	unsigned int dpp_resp_max_tries;
+	unsigned int dpp_resp_retry_time;
+#ifdef CONFIG_TESTING_OPTIONS
+	char *dpp_config_obj_override;
+	char *dpp_discovery_override;
+	char *dpp_groups_override;
+	unsigned int dpp_ignore_netaccesskey_mismatch:1;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_DPP */
 };
 
 
@@ -311,6 +376,7 @@ struct hostapd_sta_info {
 	struct dl_list list;
 	u8 addr[ETH_ALEN];
 	struct os_reltime last_seen;
+	int ssi_signal;
 #ifdef CONFIG_TAXONOMY
 	struct wpabuf *probe_ie_taxonomy;
 #endif /* CONFIG_TAXONOMY */
@@ -440,6 +506,10 @@ struct hostapd_iface {
 	u64 last_channel_time_busy;
 	u8 channel_utilization;
 
+	unsigned int chan_util_samples_sum;
+	unsigned int chan_util_num_sample_periods;
+	unsigned int chan_util_average;
+
 	/* eCSA IE will be added only if operating class is specified */
 	u8 cs_oper_class;
 
@@ -459,6 +529,8 @@ struct hostapd_iface {
 
 	struct dl_list sta_seen; /* struct hostapd_sta_info */
 	unsigned int num_sta_seen;
+
+	u8 dfs_domain;
 };
 
 /* hostapd.c */
@@ -499,6 +571,7 @@ hostapd_switch_channel_fallback(struct hostapd_iface *iface,
 				const struct hostapd_freq_params *freq_params);
 void hostapd_cleanup_cs_params(struct hostapd_data *hapd);
 void hostapd_periodic_iface(struct hostapd_iface *iface);
+int hostapd_owe_trans_get_info(struct hostapd_data *hapd);
 
 /* utils.c */
 int hostapd_register_probereq_cb(struct hostapd_data *hapd,
@@ -510,6 +583,8 @@ int hostapd_register_probereq_cb(struct hostapd_data *hapd,
 void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr);
 
 /* drv_callbacks.c (TODO: move to somewhere else?) */
+void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd,
+				      struct sta_info *sta);
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 			const u8 *ie, size_t ielen, int reassoc);
 void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr);
@@ -533,6 +608,9 @@ hostapd_get_eap_user(struct hostapd_data *hapd, const u8 *identity,
 
 struct hostapd_data * hostapd_get_iface(struct hapd_interfaces *interfaces,
 					const char *ifname);
+void hostapd_event_sta_opmode_changed(struct hostapd_data *hapd, const u8 *addr,
+				      enum smps_mode smps_mode,
+				      enum chan_width chan_width, u8 rx_nss);
 
 #ifdef CONFIG_FST
 void fst_hostapd_fill_iface_obj(struct hostapd_data *hapd,

+ 46 - 78
src/ap/hw_features.c

@@ -78,10 +78,12 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
 	int i, j;
 	u16 num_modes, flags;
 	struct hostapd_hw_modes *modes;
+	u8 dfs_domain;
 
 	if (hostapd_drv_none(hapd))
 		return -1;
-	modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags);
+	modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags,
+					    &dfs_domain);
 	if (modes == NULL) {
 		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -91,6 +93,7 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
 	}
 
 	iface->hw_flags = flags;
+	iface->dfs_domain = dfs_domain;
 
 	hostapd_free_hw_features(iface->hw_features, iface->num_hw_features);
 	iface->hw_features = modes;
@@ -329,6 +332,9 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface)
 	res = ieee80211n_allowed_ht40_channel_pair(iface);
 	if (!res) {
 		iface->conf->secondary_channel = 0;
+		iface->conf->vht_oper_centr_freq_seg0_idx = 0;
+		iface->conf->vht_oper_centr_freq_seg1_idx = 0;
+		iface->conf->vht_oper_chwidth = VHT_CHANWIDTH_USE_HT;
 		res = 1;
 		wpa_printf(MSG_INFO, "Fallback to 20 MHz");
 	}
@@ -621,41 +627,6 @@ static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface)
 
 
 #ifdef CONFIG_IEEE80211AC
-
-static int ieee80211ac_cap_check(u32 hw, u32 conf, u32 cap, const char *name)
-{
-	u32 req_cap = conf & cap;
-
-	/*
-	 * Make sure we support all requested capabilities.
-	 * NOTE: We assume that 'cap' represents a capability mask,
-	 * not a discrete value.
-	 */
-	if ((hw & req_cap) != req_cap) {
-		wpa_printf(MSG_ERROR, "Driver does not support configured VHT capability [%s]",
-			   name);
-		return 0;
-	}
-	return 1;
-}
-
-
-static int ieee80211ac_cap_check_max(u32 hw, u32 conf, u32 mask,
-				     unsigned int shift,
-				     const char *name)
-{
-	u32 hw_max = hw & mask;
-	u32 conf_val = conf & mask;
-
-	if (conf_val > hw_max) {
-		wpa_printf(MSG_ERROR, "Configured VHT capability [%s] exceeds max value supported by the driver (%d > %d)",
-			   name, conf_val >> shift, hw_max >> shift);
-		return 0;
-	}
-	return 1;
-}
-
-
 static int ieee80211ac_supported_vht_capab(struct hostapd_iface *iface)
 {
 	struct hostapd_hw_modes *mode = iface->current_mode;
@@ -683,45 +654,7 @@ static int ieee80211ac_supported_vht_capab(struct hostapd_iface *iface)
 		}
 	}
 
-#define VHT_CAP_CHECK(cap) \
-	do { \
-		if (!ieee80211ac_cap_check(hw, conf, cap, #cap)) \
-			return 0; \
-	} while (0)
-
-#define VHT_CAP_CHECK_MAX(cap) \
-	do { \
-		if (!ieee80211ac_cap_check_max(hw, conf, cap, cap ## _SHIFT, \
-					       #cap)) \
-			return 0; \
-	} while (0)
-
-	VHT_CAP_CHECK_MAX(VHT_CAP_MAX_MPDU_LENGTH_MASK);
-	VHT_CAP_CHECK(VHT_CAP_SUPP_CHAN_WIDTH_160MHZ);
-	VHT_CAP_CHECK(VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ);
-	VHT_CAP_CHECK(VHT_CAP_RXLDPC);
-	VHT_CAP_CHECK(VHT_CAP_SHORT_GI_80);
-	VHT_CAP_CHECK(VHT_CAP_SHORT_GI_160);
-	VHT_CAP_CHECK(VHT_CAP_TXSTBC);
-	VHT_CAP_CHECK_MAX(VHT_CAP_RXSTBC_MASK);
-	VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMER_CAPABLE);
-	VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMEE_CAPABLE);
-	VHT_CAP_CHECK_MAX(VHT_CAP_BEAMFORMEE_STS_MAX);
-	VHT_CAP_CHECK_MAX(VHT_CAP_SOUNDING_DIMENSION_MAX);
-	VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMER_CAPABLE);
-	VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMEE_CAPABLE);
-	VHT_CAP_CHECK(VHT_CAP_VHT_TXOP_PS);
-	VHT_CAP_CHECK(VHT_CAP_HTC_VHT);
-	VHT_CAP_CHECK_MAX(VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX);
-	VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB);
-	VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB);
-	VHT_CAP_CHECK(VHT_CAP_RX_ANTENNA_PATTERN);
-	VHT_CAP_CHECK(VHT_CAP_TX_ANTENNA_PATTERN);
-
-#undef VHT_CAP_CHECK
-#undef VHT_CAP_CHECK_MAX
-
-	return 1;
+	return ieee80211ac_cap_check(hw, conf);
 }
 #endif /* CONFIG_IEEE80211AC */
 
@@ -785,20 +718,41 @@ static int hostapd_is_usable_chan(struct hostapd_iface *iface,
 			   chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
 	}
 
+	wpa_printf(MSG_INFO, "Channel %d (%s) not allowed for AP mode",
+		   channel, primary ? "primary" : "secondary");
 	return 0;
 }
 
 
 static int hostapd_is_usable_chans(struct hostapd_iface *iface)
 {
+	int secondary_chan;
+
 	if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1))
 		return 0;
 
 	if (!iface->conf->secondary_channel)
 		return 1;
 
-	return hostapd_is_usable_chan(iface, iface->conf->channel +
-				      iface->conf->secondary_channel * 4, 0);
+	if (!iface->conf->ht40_plus_minus_allowed)
+		return hostapd_is_usable_chan(
+			iface, iface->conf->channel +
+			iface->conf->secondary_channel * 4, 0);
+
+	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
+	secondary_chan = iface->conf->channel + 4;
+	if (hostapd_is_usable_chan(iface, secondary_chan, 0)) {
+		iface->conf->secondary_channel = 1;
+		return 1;
+	}
+
+	secondary_chan = iface->conf->channel - 4;
+	if (hostapd_is_usable_chan(iface, secondary_chan, 0)) {
+		iface->conf->secondary_channel = -1;
+		return 1;
+	}
+
+	return 0;
 }
 
 
@@ -978,5 +932,19 @@ int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan)
 
 int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq)
 {
-	return hw_get_chan(hapd->iface->current_mode, freq);
+	int i, channel;
+	struct hostapd_hw_modes *mode;
+
+	channel = hw_get_chan(hapd->iface->current_mode, freq);
+	if (channel)
+		return channel;
+	/* Check other available modes since the channel list for the current
+	 * mode did not include the specified frequency. */
+	for (i = 0; i < hapd->iface->num_hw_features; i++) {
+		mode = &hapd->iface->hw_features[i];
+		channel = hw_get_chan(mode, freq);
+		if (channel)
+			return channel;
+	}
+	return 0;
 }

File diff suppressed because it is too large
+ 728 - 48
src/ap/ieee802_11.c


+ 31 - 0
src/ap/ieee802_11.h

@@ -16,6 +16,8 @@ struct hostapd_frame_info;
 struct ieee80211_ht_capabilities;
 struct ieee80211_vht_capabilities;
 struct ieee80211_mgmt;
+struct vlan_description;
+struct hostapd_sta_wpa_psk_short;
 
 int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len,
 		    struct hostapd_frame_info *fi);
@@ -55,6 +57,8 @@ u8 * hostapd_eid_vht_operation(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_vendor_vht(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_wb_chsw_wrapper(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_txpower_envelope(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid);
 
 int hostapd_ht_operation_update(struct hostapd_iface *iface);
 void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
@@ -135,4 +139,31 @@ void ap_copy_sta_supp_op_classes(struct sta_info *sta,
 				 const u8 *supp_op_classes,
 				 size_t supp_op_classes_len);
 
+u8 * hostapd_eid_fils_indic(struct hostapd_data *hapd, u8 *eid, int hessid);
+void ieee802_11_finish_fils_auth(struct hostapd_data *hapd,
+				 struct sta_info *sta, int success,
+				 struct wpabuf *erp_resp,
+				 const u8 *msk, size_t msk_len);
+u8 * owe_assoc_req_process(struct hostapd_data *hapd, struct sta_info *sta,
+			   const u8 *owe_dh, u8 owe_dh_len,
+			   u8 *owe_buf, size_t owe_buf_len, u16 *reason);
+void fils_hlp_timeout(void *eloop_ctx, void *eloop_data);
+void fils_hlp_finish_assoc(struct hostapd_data *hapd, struct sta_info *sta);
+void handle_auth_fils(struct hostapd_data *hapd, struct sta_info *sta,
+		      const u8 *pos, size_t len, u16 auth_alg,
+		      u16 auth_transaction, u16 status_code,
+		      void (*cb)(struct hostapd_data *hapd,
+				 struct sta_info *sta,
+				 u16 resp, struct wpabuf *data, int pub));
+
+size_t hostapd_eid_owe_trans_len(struct hostapd_data *hapd);
+u8 * hostapd_eid_owe_trans(struct hostapd_data *hapd, u8 *eid, size_t len);
+int ieee802_11_allowed_address(struct hostapd_data *hapd, const u8 *addr,
+			       const u8 *msg, size_t len, u32 *session_timeout,
+			       u32 *acct_interim_interval,
+			       struct vlan_description *vlan_id,
+			       struct hostapd_sta_wpa_psk_short **psk,
+			       char **identity, char **radius_cui,
+			       int is_probe_req);
+
 #endif /* IEEE802_11_H */

+ 12 - 3
src/ap/ieee802_11_auth.c

@@ -244,6 +244,7 @@ int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr,
  * @psk: Linked list buffer for returning WPA PSK
  * @identity: Buffer for returning identity (from RADIUS)
  * @radius_cui: Buffer for returning CUI (from RADIUS)
+ * @is_probe_req: Whether this query for a Probe Request frame
  * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING
  *
  * The caller is responsible for freeing the returned *identity and *radius_cui
@@ -254,7 +255,8 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 			    u32 *acct_interim_interval,
 			    struct vlan_description *vlan_id,
 			    struct hostapd_sta_wpa_psk_short **psk,
-			    char **identity, char **radius_cui)
+			    char **identity, char **radius_cui,
+			    int is_probe_req)
 {
 	int res;
 
@@ -281,6 +283,12 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 #else /* CONFIG_NO_RADIUS */
 		struct hostapd_acl_query_data *query;
 
+		if (is_probe_req) {
+			/* Skip RADIUS queries for Probe Request frames to avoid
+			 * excessive load on the authentication server. */
+			return HOSTAPD_ACL_ACCEPT;
+		};
+
 		/* Check whether ACL cache has an entry for this station */
 		res = hostapd_acl_cache_get(hapd, addr, session_timeout,
 					    acct_interim_interval, vlan_id, psk,
@@ -327,14 +335,13 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 			return HOSTAPD_ACL_REJECT;
 		}
 
-		query->auth_msg = os_malloc(len);
+		query->auth_msg = os_memdup(msg, len);
 		if (query->auth_msg == NULL) {
 			wpa_printf(MSG_ERROR, "Failed to allocate memory for "
 				   "auth frame.");
 			hostapd_acl_query_free(query);
 			return HOSTAPD_ACL_REJECT;
 		}
-		os_memcpy(query->auth_msg, msg, len);
 		query->auth_msg_len = len;
 		query->next = hapd->acl_queries;
 		hapd->acl_queries = query;
@@ -665,9 +672,11 @@ void hostapd_acl_deinit(struct hostapd_data *hapd)
 
 #ifndef CONFIG_NO_RADIUS
 	hostapd_acl_cache_free(hapd->acl_cache);
+	hapd->acl_cache = NULL;
 #endif /* CONFIG_NO_RADIUS */
 
 	query = hapd->acl_queries;
+	hapd->acl_queries = NULL;
 	while (query) {
 		prev = query;
 		query = query->next;

+ 2 - 1
src/ap/ieee802_11_auth.h

@@ -23,7 +23,8 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 			    u32 *acct_interim_interval,
 			    struct vlan_description *vlan_id,
 			    struct hostapd_sta_wpa_psk_short **psk,
-			    char **identity, char **radius_cui);
+			    char **identity, char **radius_cui,
+			    int is_probe_req);
 int hostapd_acl_init(struct hostapd_data *hapd);
 void hostapd_acl_deinit(struct hostapd_data *hapd);
 void hostapd_free_psk_list(struct hostapd_sta_wpa_psk_short *psk);

+ 88 - 0
src/ap/ieee802_11_he.c

@@ -0,0 +1,88 @@
+/*
+ * hostapd / IEEE 802.11ax HE
+ * Copyright (c) 2016-2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/ieee802_11_defs.h"
+#include "hostapd.h"
+#include "ap_config.h"
+#include "beacon.h"
+#include "ieee802_11.h"
+#include "dfs.h"
+
+u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid)
+{
+	struct ieee80211_he_capabilities *cap;
+	u8 *pos = eid;
+
+	if (!hapd->iface->current_mode)
+		return eid;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 1 + sizeof(struct ieee80211_he_capabilities);
+	*pos++ = WLAN_EID_EXT_HE_CAPABILITIES;
+
+	cap = (struct ieee80211_he_capabilities *) pos;
+	os_memset(cap, 0, sizeof(*cap));
+
+	if (hapd->iface->conf->he_phy_capab.he_su_beamformer)
+		cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMER_CAPAB_IDX] |=
+			HE_PHYCAP_SU_BEAMFORMER_CAPAB;
+
+	if (hapd->iface->conf->he_phy_capab.he_su_beamformee)
+		cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMEE_CAPAB_IDX] |=
+			HE_PHYCAP_SU_BEAMFORMEE_CAPAB;
+
+	if (hapd->iface->conf->he_phy_capab.he_mu_beamformer)
+		cap->he_phy_capab_info[HE_PHYCAP_MU_BEAMFORMER_CAPAB_IDX] |=
+			HE_PHYCAP_MU_BEAMFORMER_CAPAB;
+
+	pos += sizeof(*cap);
+
+	return pos;
+}
+
+
+u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid)
+{
+	struct ieee80211_he_operation *oper;
+	u8 *pos = eid;
+
+	if (!hapd->iface->current_mode)
+		return eid;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 1 + sizeof(struct ieee80211_he_operation);
+	*pos++ = WLAN_EID_EXT_HE_OPERATION;
+
+	oper = (struct ieee80211_he_operation *) pos;
+	os_memset(oper, 0, sizeof(*oper));
+
+	if (hapd->iface->conf->he_op.he_bss_color)
+		oper->he_oper_params |= hapd->iface->conf->he_op.he_bss_color;
+
+	if (hapd->iface->conf->he_op.he_default_pe_duration)
+		oper->he_oper_params |=
+			(hapd->iface->conf->he_op.he_default_pe_duration <<
+			 HE_OPERATION_DFLT_PE_DURATION_OFFSET);
+
+	if (hapd->iface->conf->he_op.he_twt_required)
+		oper->he_oper_params |= HE_OPERATION_TWT_REQUIRED;
+
+	if (hapd->iface->conf->he_op.he_rts_threshold)
+		oper->he_oper_params |=
+			(hapd->iface->conf->he_op.he_rts_threshold <<
+			 HE_OPERATION_RTS_THRESHOLD_OFFSET);
+
+	/* TODO: conditional MaxBSSID Indicator subfield */
+
+	pos += sizeof(*oper);
+
+	return pos;
+}

+ 55 - 11
src/ap/ieee802_11_ht.c

@@ -236,17 +236,29 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 	int i;
 	const u8 *start = (const u8 *) mgmt;
 	const u8 *data = start + IEEE80211_HDRLEN + 2;
+	struct sta_info *sta;
+
+	wpa_printf(MSG_DEBUG,
+		   "HT: Received 20/40 BSS Coexistence Management frame from "
+		   MACSTR, MAC2STR(mgmt->sa));
 
 	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG, "hostapd_public_action - action=%d",
 		       mgmt->u.action.u.public_action.action);
 
-	if (!(iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET))
+	if (!(iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) {
+		wpa_printf(MSG_DEBUG,
+			   "Ignore 20/40 BSS Coexistence Management frame since 40 MHz capability is not enabled");
 		return;
+	}
 
-	if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie))
+	if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie)) {
+		wpa_printf(MSG_DEBUG,
+			   "Ignore too short 20/40 BSS Coexistence Management frame");
 		return;
+	}
 
+	/* 20/40 BSS Coexistence element */
 	bc_ie = (struct ieee80211_2040_bss_coex_ie *) data;
 	if (bc_ie->element_id != WLAN_EID_20_40_BSS_COEXISTENCE ||
 	    bc_ie->length < 1) {
@@ -254,13 +266,35 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 			   bc_ie->element_id, bc_ie->length);
 		return;
 	}
-	if (len < IEEE80211_HDRLEN + 2 + 2 + bc_ie->length)
+	if (len < IEEE80211_HDRLEN + 2 + 2 + bc_ie->length) {
+		wpa_printf(MSG_DEBUG,
+			   "Truncated 20/40 BSS Coexistence element");
 		return;
+	}
 	data += 2 + bc_ie->length;
 
-	wpa_printf(MSG_DEBUG, "20/40 BSS Coexistence Information field: 0x%x",
-		   bc_ie->coex_param);
+	wpa_printf(MSG_DEBUG,
+		   "20/40 BSS Coexistence Information field: 0x%x (%s%s%s%s%s%s)",
+		   bc_ie->coex_param,
+		   (bc_ie->coex_param & BIT(0)) ? "[InfoReq]" : "",
+		   (bc_ie->coex_param & BIT(1)) ? "[40MHzIntolerant]" : "",
+		   (bc_ie->coex_param & BIT(2)) ? "[20MHzBSSWidthReq]" : "",
+		   (bc_ie->coex_param & BIT(3)) ? "[OBSSScanExemptionReq]" : "",
+		   (bc_ie->coex_param & BIT(4)) ?
+		   "[OBSSScanExemptionGrant]" : "",
+		   (bc_ie->coex_param & (BIT(5) | BIT(6) | BIT(7))) ?
+		   "[Reserved]" : "");
+
 	if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ) {
+		/* Intra-BSS communication prohibiting 20/40 MHz BSS operation
+		 */
+		sta = ap_get_sta(hapd, mgmt->sa);
+		if (!sta || !(sta->flags & WLAN_STA_ASSOC)) {
+			wpa_printf(MSG_DEBUG,
+				   "Ignore intra-BSS 20/40 BSS Coexistence Management frame from not-associated STA");
+			return;
+		}
+
 		hostapd_logger(hapd, mgmt->sa,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -269,6 +303,8 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 	}
 
 	if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_40MHZ_INTOL) {
+		/* Inter-BSS communication prohibiting 20/40 MHz BSS operation
+		 */
 		hostapd_logger(hapd, mgmt->sa,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -276,12 +312,16 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 		is_ht40_allowed = 0;
 	}
 
-	if (start + len - data >= 3 &&
-	    data[0] == WLAN_EID_20_40_BSS_INTOLERANT && data[1] >= 1) {
+	/* 20/40 BSS Intolerant Channel Report element (zero or more times) */
+	while (start + len - data >= 3 &&
+	       data[0] == WLAN_EID_20_40_BSS_INTOLERANT && data[1] >= 1) {
 		u8 ielen = data[1];
 
-		if (ielen > start + len - data - 2)
+		if (ielen > start + len - data - 2) {
+			wpa_printf(MSG_DEBUG,
+				   "Truncated 20/40 BSS Intolerant Channel Report element");
 			return;
+		}
 		ic_report = (struct ieee80211_2040_intol_chan_report *) data;
 		wpa_printf(MSG_DEBUG,
 			   "20/40 BSS Intolerant Channel Report: Operating Class %u",
@@ -292,8 +332,10 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 		for (i = 0; i < ielen - 1; i++) {
 			u8 chan = ic_report->variable[i];
 
+			if (chan == iface->conf->channel)
+				continue; /* matching own primary channel */
 			if (is_40_allowed(iface, chan))
-				continue;
+				continue; /* not within affected channels */
 			hostapd_logger(hapd, mgmt->sa,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG,
@@ -301,6 +343,8 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd,
 				       chan);
 			is_ht40_allowed = 0;
 		}
+
+		data += 2 + ielen;
 	}
 	wpa_printf(MSG_DEBUG, "is_ht40_allowed=%d num_sta_ht40_intolerant=%d",
 		   is_ht40_allowed, iface->num_sta_ht40_intolerant);
@@ -340,8 +384,8 @@ u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 	 * that did not specify a valid WMM IE in the (Re)Association Request
 	 * frame.
 	 */
-	if (!ht_capab ||
-	    !(sta->flags & WLAN_STA_WMM) || hapd->conf->disable_11n) {
+	if (!ht_capab || !(sta->flags & WLAN_STA_WMM) ||
+	    !hapd->iconf->ieee80211n || hapd->conf->disable_11n) {
 		sta->flags &= ~WLAN_STA_HT;
 		os_free(sta->ht_capabilities);
 		sta->ht_capabilities = NULL;

+ 178 - 15
src/ap/ieee802_11_shared.c

@@ -186,9 +186,9 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx)
 			*pos |= 0x08; /* Bit 19 - BSS Transition */
 		break;
 	case 3: /* Bits 24-31 */
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 		*pos |= 0x02; /* Bit 25 - SSID List */
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 		if (hapd->conf->time_advertisement == 2)
 			*pos |= 0x08; /* Bit 27 - UTC TSF Offset */
 		if (hapd->conf->interworking)
@@ -218,12 +218,21 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx)
 		if (hapd->conf->ssid.utf8_ssid)
 			*pos |= 0x01; /* Bit 48 - UTF-8 SSID */
 		break;
+	case 7: /* Bits 56-63 */
+		break;
 	case 8: /* Bits 64-71 */
 		if (hapd->conf->ftm_responder)
 			*pos |= 0x40; /* Bit 70 - FTM responder */
 		if (hapd->conf->ftm_initiator)
 			*pos |= 0x80; /* Bit 71 - FTM initiator */
 		break;
+	case 9: /* Bits 72-79 */
+#ifdef CONFIG_FILS
+		if ((hapd->conf->wpa & WPA_PROTO_RSN) &&
+		    wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt))
+			*pos |= 0x01;
+#endif /* CONFIG_FILS */
+		break;
 	}
 }
 
@@ -246,10 +255,10 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid)
 	if (len < 9 &&
 	    (hapd->conf->ftm_initiator || hapd->conf->ftm_responder))
 		len = 9;
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 	if (len < 4)
 		len = 4;
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 #ifdef CONFIG_HS20
 	if (hapd->conf->hs20 && len < 6)
 		len = 6;
@@ -258,6 +267,11 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid)
 	if (hapd->conf->mbo_enabled && len < 6)
 		len = 6;
 #endif /* CONFIG_MBO */
+#ifdef CONFIG_FILS
+	if ((!(hapd->conf->wpa & WPA_PROTO_RSN) ||
+	     !wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt)) && len < 10)
+		len = 10;
+#endif /* CONFIG_FILS */
 	if (len < hapd->iface->extended_capa_len)
 		len = hapd->iface->extended_capa_len;
 	if (len == 0)
@@ -503,7 +517,7 @@ u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid)
 {
 	u8 *pos = eid;
 
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 	if (hapd->conf->ap_max_inactivity > 0) {
 		unsigned int val;
 		*pos++ = WLAN_EID_BSS_MAX_IDLE_PERIOD;
@@ -521,7 +535,7 @@ u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid)
 		pos += 2;
 		*pos++ = 0x00; /* TODO: Protected Keep-Alive Required */
 	}
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 
 	return pos;
 }
@@ -531,23 +545,38 @@ u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid)
 
 u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
 {
-	u8 mbo[6], *mbo_pos = mbo;
+	u8 mbo[9], *mbo_pos = mbo;
 	u8 *pos = eid;
 
-	if (!hapd->conf->mbo_enabled)
+	if (!hapd->conf->mbo_enabled && !hapd->enable_oce)
 		return eid;
 
-	*mbo_pos++ = MBO_ATTR_ID_AP_CAPA_IND;
-	*mbo_pos++ = 1;
-	/* Not Cellular aware */
-	*mbo_pos++ = 0;
+	if (hapd->conf->mbo_enabled) {
+		*mbo_pos++ = MBO_ATTR_ID_AP_CAPA_IND;
+		*mbo_pos++ = 1;
+		/* Not Cellular aware */
+		*mbo_pos++ = 0;
+	}
 
-	if (hapd->mbo_assoc_disallow) {
+	if (hapd->conf->mbo_enabled && hapd->mbo_assoc_disallow) {
 		*mbo_pos++ = MBO_ATTR_ID_ASSOC_DISALLOW;
 		*mbo_pos++ = 1;
 		*mbo_pos++ = hapd->mbo_assoc_disallow;
 	}
 
+	if (hapd->enable_oce & (OCE_AP | OCE_STA_CFON)) {
+		u8 ctrl;
+
+		ctrl = OCE_RELEASE;
+		if ((hapd->enable_oce & (OCE_AP | OCE_STA_CFON)) ==
+		    OCE_STA_CFON)
+			ctrl |= OCE_IS_STA_CFON;
+
+		*mbo_pos++ = OCE_ATTR_ID_CAPA_IND;
+		*mbo_pos++ = 1;
+		*mbo_pos++ = ctrl;
+	}
+
 	pos += mbo_add_ie(pos, len, mbo, mbo_pos - mbo);
 
 	return pos;
@@ -556,19 +585,90 @@ u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
 
 u8 hostapd_mbo_ie_len(struct hostapd_data *hapd)
 {
-	if (!hapd->conf->mbo_enabled)
+	u8 len;
+
+	if (!hapd->conf->mbo_enabled && !hapd->enable_oce)
 		return 0;
 
 	/*
 	 * MBO IE header (6) + Capability Indication attribute (3) +
 	 * Association Disallowed attribute (3) = 12
 	 */
-	return 6 + 3 + (hapd->mbo_assoc_disallow ? 3 : 0);
+	len = 6;
+	if (hapd->conf->mbo_enabled)
+		len += 3 + (hapd->mbo_assoc_disallow ? 3 : 0);
+
+	/* OCE capability indication attribute (3) */
+	if (hapd->enable_oce & (OCE_AP | OCE_STA_CFON))
+		len += 3;
+
+	return len;
 }
 
 #endif /* CONFIG_MBO */
 
 
+#ifdef CONFIG_OWE
+static int hostapd_eid_owe_trans_enabled(struct hostapd_data *hapd)
+{
+	return hapd->conf->owe_transition_ssid_len > 0 &&
+		!is_zero_ether_addr(hapd->conf->owe_transition_bssid);
+}
+#endif /* CONFIG_OWE */
+
+
+size_t hostapd_eid_owe_trans_len(struct hostapd_data *hapd)
+{
+#ifdef CONFIG_OWE
+	if (!hostapd_eid_owe_trans_enabled(hapd))
+		return 0;
+	return 6 + ETH_ALEN + 1 + hapd->conf->owe_transition_ssid_len;
+#else /* CONFIG_OWE */
+	return 0;
+#endif /* CONFIG_OWE */
+}
+
+
+u8 * hostapd_eid_owe_trans(struct hostapd_data *hapd, u8 *eid,
+				  size_t len)
+{
+#ifdef CONFIG_OWE
+	u8 *pos = eid;
+	size_t elen;
+
+	if (hapd->conf->owe_transition_ifname[0] &&
+	    !hostapd_eid_owe_trans_enabled(hapd))
+		hostapd_owe_trans_get_info(hapd);
+
+	if (!hostapd_eid_owe_trans_enabled(hapd))
+		return pos;
+
+	elen = hostapd_eid_owe_trans_len(hapd);
+	if (len < elen) {
+		wpa_printf(MSG_DEBUG,
+			   "OWE: Not enough room in the buffer for OWE IE");
+		return pos;
+	}
+
+	*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+	*pos++ = elen - 2;
+	WPA_PUT_BE24(pos, OUI_WFA);
+	pos += 3;
+	*pos++ = OWE_OUI_TYPE;
+	os_memcpy(pos, hapd->conf->owe_transition_bssid, ETH_ALEN);
+	pos += ETH_ALEN;
+	*pos++ = hapd->conf->owe_transition_ssid_len;
+	os_memcpy(pos, hapd->conf->owe_transition_ssid,
+		  hapd->conf->owe_transition_ssid_len);
+	pos += hapd->conf->owe_transition_ssid_len;
+
+	return pos;
+#else /* CONFIG_OWE */
+	return eid;
+#endif /* CONFIG_OWE */
+}
+
+
 void ap_copy_sta_supp_op_classes(struct sta_info *sta,
 				 const u8 *supp_op_classes,
 				 size_t supp_op_classes_len)
@@ -584,3 +684,66 @@ void ap_copy_sta_supp_op_classes(struct sta_info *sta,
 	os_memcpy(sta->supp_op_classes + 1, supp_op_classes,
 		  supp_op_classes_len);
 }
+
+
+u8 * hostapd_eid_fils_indic(struct hostapd_data *hapd, u8 *eid, int hessid)
+{
+	u8 *pos = eid;
+#ifdef CONFIG_FILS
+	u8 *len;
+	u16 fils_info = 0;
+	size_t realms;
+	struct fils_realm *realm;
+
+	if (!(hapd->conf->wpa & WPA_PROTO_RSN) ||
+	    !wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt))
+		return pos;
+
+	realms = dl_list_len(&hapd->conf->fils_realms);
+	if (realms > 7)
+		realms = 7; /* 3 bit count field limits this to max 7 */
+
+	*pos++ = WLAN_EID_FILS_INDICATION;
+	len = pos++;
+	/* TODO: B0..B2: Number of Public Key Identifiers */
+	if (hapd->conf->erp_domain) {
+		/* B3..B5: Number of Realm Identifiers */
+		fils_info |= realms << 3;
+	}
+	/* TODO: B6: FILS IP Address Configuration */
+	if (hapd->conf->fils_cache_id_set)
+		fils_info |= BIT(7);
+	if (hessid && !is_zero_ether_addr(hapd->conf->hessid))
+		fils_info |= BIT(8); /* HESSID Included */
+	/* FILS Shared Key Authentication without PFS Supported */
+	fils_info |= BIT(9);
+	if (hapd->conf->fils_dh_group) {
+		/* FILS Shared Key Authentication with PFS Supported */
+		fils_info |= BIT(10);
+	}
+	/* TODO: B11: FILS Public Key Authentication Supported */
+	/* B12..B15: Reserved */
+	WPA_PUT_LE16(pos, fils_info);
+	pos += 2;
+	if (hapd->conf->fils_cache_id_set) {
+		os_memcpy(pos, hapd->conf->fils_cache_id, FILS_CACHE_ID_LEN);
+		pos += FILS_CACHE_ID_LEN;
+	}
+	if (hessid && !is_zero_ether_addr(hapd->conf->hessid)) {
+		os_memcpy(pos, hapd->conf->hessid, ETH_ALEN);
+		pos += ETH_ALEN;
+	}
+
+	dl_list_for_each(realm, &hapd->conf->fils_realms, struct fils_realm,
+			 list) {
+		if (realms == 0)
+			break;
+		realms--;
+		os_memcpy(pos, realm->hash, 2);
+		pos += 2;
+	}
+	*len = pos - len - 1;
+#endif /* CONFIG_FILS */
+
+	return pos;
+}

+ 1 - 1
src/ap/ieee802_11_vht.c

@@ -334,7 +334,7 @@ u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 {
 	/* Disable VHT caps for STAs associated to no-VHT BSSes. */
 	if (!vht_capab ||
-	    hapd->conf->disable_11ac ||
+	    !hapd->iconf->ieee80211ac || hapd->conf->disable_11ac ||
 	    !check_valid_vht_mcs(hapd->iface->current_mode, vht_capab)) {
 		sta->flags &= ~WLAN_STA_VHT;
 		os_free(sta->vht_capabilities);

+ 76 - 26
src/ap/ieee802_1x.c

@@ -31,6 +31,8 @@
 #include "ap_drv_ops.h"
 #include "wps_hostapd.h"
 #include "hs20.h"
+/* FIX: Not really a good thing to require ieee802_11.h here.. (FILS) */
+#include "ieee802_11.h"
 #include "ieee802_1x.h"
 
 
@@ -316,6 +318,7 @@ static void ieee802_1x_learn_identity(struct hostapd_data *hapd,
 	     hdr->code != EAP_CODE_INITIATE))
 		return;
 
+	eap_erp_update_identity(sm->eap, eap, len);
 	identity = eap_get_identity(sm->eap, &identity_len);
 	if (identity == NULL)
 		return;
@@ -472,7 +475,7 @@ static int add_common_radius_sta_attr(struct hostapd_data *hapd,
 		}
 	}
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (hapd->conf->wpa && wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt) &&
 	    sta->wpa_sm &&
 	    (wpa_key_mgmt_ft(wpa_auth_sta_key_mgmt(sta->wpa_sm)) ||
@@ -485,7 +488,7 @@ static int add_common_radius_sta_attr(struct hostapd_data *hapd,
 		wpa_printf(MSG_ERROR, "Could not add Mobility-Domain-Id");
 		return -1;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 	if ((hapd->conf->wpa || hapd->conf->osen) && sta->wpa_sm &&
 	    add_common_radius_sta_attr_rsn(hapd, req_attr, sta, msg) < 0)
@@ -588,9 +591,9 @@ int add_common_radius_attr(struct hostapd_data *hapd,
 }
 
 
-static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
-					  struct sta_info *sta,
-					  const u8 *eap, size_t len)
+void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
+				   struct sta_info *sta,
+				   const u8 *eap, size_t len)
 {
 	struct radius_msg *msg;
 	struct eapol_state_machine *sm = sta->eapol_sm;
@@ -845,7 +848,7 @@ static void handle_eap(struct hostapd_data *hapd, struct sta_info *sta,
 }
 
 
-static struct eapol_state_machine *
+struct eapol_state_machine *
 ieee802_1x_alloc_eapol_sm(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	int flags = 0;
@@ -970,7 +973,9 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf,
 	}
 
 	key_mgmt = wpa_auth_sta_key_mgmt(sta->wpa_sm);
-	if (key_mgmt != -1 && wpa_key_mgmt_wpa_psk(key_mgmt)) {
+	if (key_mgmt != -1 &&
+	    (wpa_key_mgmt_wpa_psk(key_mgmt) || key_mgmt == WPA_KEY_MGMT_OWE ||
+	     key_mgmt == WPA_KEY_MGMT_DPP)) {
 		wpa_printf(MSG_DEBUG, "IEEE 802.1X: Ignore EAPOL message - "
 			   "STA is using PSK");
 		return;
@@ -1113,7 +1118,9 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta)
 	}
 
 	key_mgmt = wpa_auth_sta_key_mgmt(sta->wpa_sm);
-	if (key_mgmt != -1 && wpa_key_mgmt_wpa_psk(key_mgmt)) {
+	if (key_mgmt != -1 &&
+	    (wpa_key_mgmt_wpa_psk(key_mgmt) || key_mgmt == WPA_KEY_MGMT_OWE ||
+	     key_mgmt == WPA_KEY_MGMT_DPP)) {
 		wpa_printf(MSG_DEBUG, "IEEE 802.1X: Ignore STA - using PSK");
 		/*
 		 * Clear any possible EAPOL authenticator state to support
@@ -1154,7 +1161,7 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta)
 
 	sta->eapol_sm->eap_if->portEnabled = TRUE;
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (sta->auth_alg == WLAN_AUTH_FT) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -1173,7 +1180,29 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta)
 		/* TODO: get vlan_id from R0KH using RRB message */
 		return;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
+
+#ifdef CONFIG_FILS
+	if (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	    sta->auth_alg == WLAN_AUTH_FILS_PK) {
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "PMK from FILS - skip IEEE 802.1X/EAP");
+		/* Setup EAPOL state machines to already authenticated state
+		 * because of existing FILS information. */
+		sta->eapol_sm->keyRun = TRUE;
+		sta->eapol_sm->eap_if->eapKeyAvailable = TRUE;
+		sta->eapol_sm->auth_pae_state = AUTH_PAE_AUTHENTICATING;
+		sta->eapol_sm->be_auth_state = BE_AUTH_SUCCESS;
+		sta->eapol_sm->authSuccess = TRUE;
+		sta->eapol_sm->authFail = FALSE;
+		sta->eapol_sm->portValid = TRUE;
+		if (sta->eapol_sm->eap)
+			eap_sm_notify_cached(sta->eapol_sm->eap);
+		return;
+	}
+#endif /* CONFIG_FILS */
 
 	pmksa = wpa_auth_sta_get_pmksa(sta->wpa_sm);
 	if (pmksa) {
@@ -1395,11 +1424,10 @@ static void ieee802_1x_store_radius_class(struct hostapd_data *hapd,
 			}
 		} while (class_len < 1);
 
-		nclass[nclass_count].data = os_malloc(class_len);
+		nclass[nclass_count].data = os_memdup(attr_class, class_len);
 		if (nclass[nclass_count].data == NULL)
 			break;
 
-		os_memcpy(nclass[nclass_count].data, attr_class, class_len);
 		nclass[nclass_count].len = class_len;
 		nclass_count++;
 	}
@@ -1663,6 +1691,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 	struct sta_info *sta;
 	u32 session_timeout = 0, termination_action, acct_interim_interval;
 	int session_timeout_set;
+	u32 reason_code;
 	struct eapol_state_machine *sm;
 	int override_eapReq = 0;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
@@ -1811,6 +1840,13 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 	case RADIUS_CODE_ACCESS_REJECT:
 		sm->eap_if->aaaFail = TRUE;
 		override_eapReq = 1;
+		if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_WLAN_REASON_CODE,
+					      &reason_code) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "RADIUS server indicated WLAN-Reason-Code %u in Access-Reject for "
+				   MACSTR, reason_code, MAC2STR(sta->addr));
+			sta->disconnect_reason_code = reason_code;
+		}
 		break;
 	case RADIUS_CODE_ACCESS_CHALLENGE:
 		sm->eap_if->aaaEapReq = TRUE;
@@ -1837,6 +1873,19 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 	if (override_eapReq)
 		sm->eap_if->aaaEapReq = FALSE;
 
+#ifdef CONFIG_FILS
+#ifdef NEED_AP_MLME
+	if (sta->flags & WLAN_STA_PENDING_FILS_ERP) {
+		/* TODO: Add a PMKSA entry on success? */
+		ieee802_11_finish_fils_auth(
+			hapd, sta, hdr->code == RADIUS_CODE_ACCESS_ACCEPT,
+			sm->eap_if->aaaEapReqData,
+			sm->eap_if->aaaEapKeyData,
+			sm->eap_if->aaaEapKeyDataLen);
+	}
+#endif /* NEED_AP_MLME */
+#endif /* CONFIG_FILS */
+
 	eapol_auth_step(sm);
 
 	return RADIUS_RX_QUEUED;
@@ -1924,7 +1973,7 @@ static void ieee802_1x_rekey(void *eloop_ctx, void *timeout_ctx)
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.1X: New default WEP key index %d",
 		   eapol->default_wep_key_idx);
-		      
+
 	if (ieee802_1x_rekey_broadcast(hapd)) {
 		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE8021X,
 			       HOSTAPD_LEVEL_WARNING, "failed to generate a "
@@ -2034,11 +2083,10 @@ static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity,
 	}
 
 	if (eap_user->password) {
-		user->password = os_malloc(eap_user->password_len);
+		user->password = os_memdup(eap_user->password,
+					   eap_user->password_len);
 		if (user->password == NULL)
 			goto out;
-		os_memcpy(user->password, eap_user->password,
-			  eap_user->password_len);
 		user->password_len = eap_user->password_len;
 		user->password_hash = eap_user->password_hash;
 	}
@@ -2190,6 +2238,7 @@ int ieee802_1x_init(struct hostapd_data *hapd)
 	conf.erp_domain = hapd->conf->erp_domain;
 	conf.erp = hapd->conf->eap_server_erp;
 	conf.tls_session_lifetime = hapd->conf->tls_session_lifetime;
+	conf.tls_flags = hapd->conf->tls_flags;
 	conf.pac_opaque_encr_key = hapd->conf->pac_opaque_encr_key;
 	conf.eap_fast_a_id = hapd->conf->eap_fast_a_id;
 	conf.eap_fast_a_id_len = hapd->conf->eap_fast_a_id_len;
@@ -2326,6 +2375,16 @@ int ieee802_1x_eapol_tx_status(struct hostapd_data *hapd, struct sta_info *sta,
 		   MAC2STR(sta->addr), xhdr->version, xhdr->type,
 		   be_to_host16(xhdr->length), ack);
 
+#ifdef CONFIG_WPS
+	if (xhdr->type == IEEE802_1X_TYPE_EAP_PACKET && ack &&
+	    (sta->flags & WLAN_STA_WPS) &&
+	    ap_sta_pending_delayed_1x_auth_fail_disconnect(hapd, sta)) {
+		wpa_printf(MSG_DEBUG,
+			   "WPS: Indicate EAP completion on ACK for EAP-Failure");
+		hostapd_wps_eap_completed(hapd);
+	}
+#endif /* CONFIG_WPS */
+
 	if (xhdr->type != IEEE802_1X_TYPE_EAPOL_KEY)
 		return 0;
 
@@ -2699,15 +2758,6 @@ static void ieee802_1x_finished(struct hostapd_data *hapd,
 		 * EAP-FAST with anonymous provisioning, may require another
 		 * EAPOL authentication to be started to complete connection.
 		 */
-		wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "IEEE 802.1X: Force "
-			"disconnection after EAP-Failure");
-		/* Add a small sleep to increase likelihood of previously
-		 * requested EAP-Failure TX getting out before this should the
-		 * driver reorder operations.
-		 */
-		os_sleep(0, 10000);
-		ap_sta_disconnect(hapd, sta, sta->addr,
-				  WLAN_REASON_IEEE_802_1X_AUTH_FAILED);
-		hostapd_wps_eap_completed(hapd);
+		ap_sta_delayed_1x_auth_fail_disconnect(hapd, sta);
 	}
 }

+ 5 - 0
src/ap/ieee802_1x.h

@@ -57,5 +57,10 @@ int add_common_radius_attr(struct hostapd_data *hapd,
 			   struct hostapd_radius_attr *req_attr,
 			   struct sta_info *sta,
 			   struct radius_msg *msg);
+void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
+				   struct sta_info *sta,
+				   const u8 *eap, size_t len);
+struct eapol_state_machine *
+ieee802_1x_alloc_eapol_sm(struct hostapd_data *hapd, struct sta_info *sta);
 
 #endif /* IEEE802_1X_H */

+ 1 - 0
src/ap/ndisc_snoop.c

@@ -182,4 +182,5 @@ int ndisc_snoop_init(struct hostapd_data *hapd)
 void ndisc_snoop_deinit(struct hostapd_data *hapd)
 {
 	l2_packet_deinit(hapd->sock_ndisc);
+	hapd->sock_ndisc = NULL;
 }

+ 6 - 3
src/ap/neighbor_db.c

@@ -43,6 +43,7 @@ static void hostapd_neighbor_clear_entry(struct hostapd_neighbor_entry *nr)
 	nr->civic = NULL;
 	os_memset(nr->bssid, 0, sizeof(nr->bssid));
 	os_memset(&nr->ssid, 0, sizeof(nr->ssid));
+	nr->stationary = 0;
 }
 
 
@@ -64,7 +65,7 @@ hostapd_neighbor_add(struct hostapd_data *hapd)
 int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid,
 			 const struct wpa_ssid_value *ssid,
 			 const struct wpabuf *nr, const struct wpabuf *lci,
-			 const struct wpabuf *civic)
+			 const struct wpabuf *civic, int stationary)
 {
 	struct hostapd_neighbor_entry *entry;
 
@@ -83,18 +84,20 @@ int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid,
 	if (!entry->nr)
 		goto fail;
 
-	if (lci) {
+	if (lci && wpabuf_len(lci)) {
 		entry->lci = wpabuf_dup(lci);
 		if (!entry->lci || os_get_time(&entry->lci_date))
 			goto fail;
 	}
 
-	if (civic) {
+	if (civic && wpabuf_len(civic)) {
 		entry->civic = wpabuf_dup(civic);
 		if (!entry->civic)
 			goto fail;
 	}
 
+	entry->stationary = stationary;
+
 	return 0;
 
 fail:

+ 1 - 1
src/ap/neighbor_db.h

@@ -16,7 +16,7 @@ hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid,
 int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid,
 			 const struct wpa_ssid_value *ssid,
 			 const struct wpabuf *nr, const struct wpabuf *lci,
-			 const struct wpabuf *civic);
+			 const struct wpabuf *civic, int stationary);
 int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid,
 			    const struct wpa_ssid_value *ssid);
 void hostpad_free_neighbor_db(struct hostapd_data *hapd);

+ 0 - 396
src/ap/peerkey_auth.c

@@ -1,396 +0,0 @@
-/*
- * hostapd - PeerKey for Direct Link Setup (DLS)
- * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
- *
- * This software may be distributed under the terms of the BSD license.
- * See README for more details.
- */
-
-#include "utils/includes.h"
-
-#include "utils/common.h"
-#include "utils/eloop.h"
-#include "crypto/sha1.h"
-#include "crypto/sha256.h"
-#include "crypto/random.h"
-#include "wpa_auth.h"
-#include "wpa_auth_i.h"
-#include "wpa_auth_ie.h"
-
-#ifdef CONFIG_PEERKEY
-
-static void wpa_stsl_step(void *eloop_ctx, void *timeout_ctx)
-{
-#if 0
-	struct wpa_authenticator *wpa_auth = eloop_ctx;
-	struct wpa_stsl_negotiation *neg = timeout_ctx;
-#endif
-
-	/* TODO: ? */
-}
-
-
-struct wpa_stsl_search {
-	const u8 *addr;
-	struct wpa_state_machine *sm;
-};
-
-
-static int wpa_stsl_select_sta(struct wpa_state_machine *sm, void *ctx)
-{
-	struct wpa_stsl_search *search = ctx;
-	if (os_memcmp(search->addr, sm->addr, ETH_ALEN) == 0) {
-		search->sm = sm;
-		return 1;
-	}
-	return 0;
-}
-
-
-static void wpa_smk_send_error(struct wpa_authenticator *wpa_auth,
-			       struct wpa_state_machine *sm, const u8 *peer,
-			       u16 mui, u16 error_type)
-{
-	u8 kde[2 + RSN_SELECTOR_LEN + ETH_ALEN +
-	       2 + RSN_SELECTOR_LEN + sizeof(struct rsn_error_kde)];
-	u8 *pos;
-	struct rsn_error_kde error;
-
-	wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG,
-			"Sending SMK Error");
-
-	pos = kde;
-
-	if (peer) {
-		pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, peer, ETH_ALEN,
-				  NULL, 0);
-	}
-
-	error.mui = host_to_be16(mui);
-	error.error_type = host_to_be16(error_type);
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_ERROR,
-			  (u8 *) &error, sizeof(error), NULL, 0);
-
-	__wpa_send_eapol(wpa_auth, sm,
-			 WPA_KEY_INFO_SECURE | WPA_KEY_INFO_MIC |
-			 WPA_KEY_INFO_SMK_MESSAGE | WPA_KEY_INFO_ERROR,
-			 NULL, NULL, kde, pos - kde, 0, 0, 0);
-}
-
-
-void wpa_smk_m1(struct wpa_authenticator *wpa_auth,
-		struct wpa_state_machine *sm, struct wpa_eapol_key *key,
-		const u8 *key_data, size_t key_data_len)
-{
-	struct wpa_eapol_ie_parse kde;
-	struct wpa_stsl_search search;
-	u8 *buf, *pos;
-	size_t buf_len;
-
-	if (wpa_parse_kde_ies(key_data, key_data_len, &kde) < 0) {
-		wpa_printf(MSG_INFO, "RSN: Failed to parse KDEs in SMK M1");
-		return;
-	}
-
-	if (kde.rsn_ie == NULL || kde.mac_addr == NULL ||
-	    kde.mac_addr_len < ETH_ALEN) {
-		wpa_printf(MSG_INFO, "RSN: No RSN IE or MAC address KDE in "
-			   "SMK M1");
-		return;
-	}
-
-	/* Initiator = sm->addr; Peer = kde.mac_addr */
-
-	search.addr = kde.mac_addr;
-	search.sm = NULL;
-	if (wpa_auth_for_each_sta(wpa_auth, wpa_stsl_select_sta, &search) ==
-	    0 || search.sm == NULL) {
-		wpa_printf(MSG_DEBUG, "RSN: SMK handshake with " MACSTR
-			   " aborted - STA not associated anymore",
-			   MAC2STR(kde.mac_addr));
-		wpa_smk_send_error(wpa_auth, sm, kde.mac_addr, STK_MUI_SMK,
-				   STK_ERR_STA_NR);
-		/* FIX: wpa_stsl_remove(wpa_auth, neg); */
-		return;
-	}
-
-	buf_len = kde.rsn_ie_len + 2 + RSN_SELECTOR_LEN + ETH_ALEN;
-	buf = os_malloc(buf_len);
-	if (buf == NULL)
-		return;
-	/* Initiator RSN IE */
-	os_memcpy(buf, kde.rsn_ie, kde.rsn_ie_len);
-	pos = buf + kde.rsn_ie_len;
-	/* Initiator MAC Address */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, sm->addr, ETH_ALEN,
-			  NULL, 0);
-
-	/* SMK M2:
-	 * EAPOL-Key(S=1, M=1, A=1, I=0, K=0, SM=1, KeyRSC=0, Nonce=INonce,
-	 *           MIC=MIC, DataKDs=(RSNIE_I, MAC_I KDE)
-	 */
-
-	wpa_auth_logger(wpa_auth, search.sm->addr, LOGGER_DEBUG,
-			"Sending SMK M2");
-
-	__wpa_send_eapol(wpa_auth, search.sm,
-			 WPA_KEY_INFO_SECURE | WPA_KEY_INFO_MIC |
-			 WPA_KEY_INFO_ACK | WPA_KEY_INFO_SMK_MESSAGE,
-			 NULL, key->key_nonce, buf, pos - buf, 0, 0, 0);
-
-	os_free(buf);
-}
-
-
-static void wpa_send_smk_m4(struct wpa_authenticator *wpa_auth,
-			    struct wpa_state_machine *sm,
-			    struct wpa_eapol_key *key,
-			    struct wpa_eapol_ie_parse *kde,
-			    const u8 *smk)
-{
-	u8 *buf, *pos;
-	size_t buf_len;
-	u32 lifetime;
-
-	/* SMK M4:
-	 * EAPOL-Key(S=1, M=1, A=0, I=1, K=0, SM=1, KeyRSC=0, Nonce=PNonce,
-	 *           MIC=MIC, DataKDs=(MAC_I KDE, INonce KDE, SMK KDE,
-	 *           Lifetime KDE)
-	 */
-
-	buf_len = 2 + RSN_SELECTOR_LEN + ETH_ALEN +
-		2 + RSN_SELECTOR_LEN + WPA_NONCE_LEN +
-		2 + RSN_SELECTOR_LEN + PMK_LEN + WPA_NONCE_LEN +
-		2 + RSN_SELECTOR_LEN + sizeof(lifetime);
-	pos = buf = os_malloc(buf_len);
-	if (buf == NULL)
-		return;
-
-	/* Initiator MAC Address */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, kde->mac_addr, ETH_ALEN,
-			  NULL, 0);
-
-	/* Initiator Nonce */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_NONCE, kde->nonce, WPA_NONCE_LEN,
-			  NULL, 0);
-
-	/* SMK with PNonce */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_SMK, smk, PMK_LEN,
-			  key->key_nonce, WPA_NONCE_LEN);
-
-	/* Lifetime */
-	lifetime = htonl(43200); /* dot11RSNAConfigSMKLifetime */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_LIFETIME,
-			  (u8 *) &lifetime, sizeof(lifetime), NULL, 0);
-
-	wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
-			"Sending SMK M4");
-
-	__wpa_send_eapol(wpa_auth, sm,
-			 WPA_KEY_INFO_SECURE | WPA_KEY_INFO_MIC |
-			 WPA_KEY_INFO_INSTALL | WPA_KEY_INFO_SMK_MESSAGE,
-			 NULL, key->key_nonce, buf, pos - buf, 0, 1, 0);
-
-	os_free(buf);
-}
-
-
-static void wpa_send_smk_m5(struct wpa_authenticator *wpa_auth,
-			    struct wpa_state_machine *sm,
-			    struct wpa_eapol_key *key,
-			    struct wpa_eapol_ie_parse *kde,
-			    const u8 *smk, const u8 *peer)
-{
-	u8 *buf, *pos;
-	size_t buf_len;
-	u32 lifetime;
-
-	/* SMK M5:
-	 * EAPOL-Key(S=1, M=1, A=0, I=0, K=0, SM=1, KeyRSC=0, Nonce=INonce,
-	 *           MIC=MIC, DataKDs=(RSNIE_P, MAC_P KDE, PNonce, SMK KDE,
-	 *                             Lifetime KDE))
-	 */
-
-	buf_len = kde->rsn_ie_len +
-		2 + RSN_SELECTOR_LEN + ETH_ALEN +
-		2 + RSN_SELECTOR_LEN + WPA_NONCE_LEN +
-		2 + RSN_SELECTOR_LEN + PMK_LEN + WPA_NONCE_LEN +
-		2 + RSN_SELECTOR_LEN + sizeof(lifetime);
-	pos = buf = os_malloc(buf_len);
-	if (buf == NULL)
-		return;
-
-	/* Peer RSN IE */
-	os_memcpy(pos, kde->rsn_ie, kde->rsn_ie_len);
-	pos += kde->rsn_ie_len;
-
-	/* Peer MAC Address */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, peer, ETH_ALEN, NULL, 0);
-
-	/* PNonce */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_NONCE, key->key_nonce,
-			  WPA_NONCE_LEN, NULL, 0);
-
-	/* SMK and INonce */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_SMK, smk, PMK_LEN,
-			  kde->nonce, WPA_NONCE_LEN);
-
-	/* Lifetime */
-	lifetime = htonl(43200); /* dot11RSNAConfigSMKLifetime */
-	pos = wpa_add_kde(pos, RSN_KEY_DATA_LIFETIME,
-			  (u8 *) &lifetime, sizeof(lifetime), NULL, 0);
-
-	wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
-			"Sending SMK M5");
-
-	__wpa_send_eapol(wpa_auth, sm,
-			 WPA_KEY_INFO_SECURE | WPA_KEY_INFO_MIC |
-			 WPA_KEY_INFO_SMK_MESSAGE,
-			 NULL, kde->nonce, buf, pos - buf, 0, 1, 0);
-
-	os_free(buf);
-}
-
-
-void wpa_smk_m3(struct wpa_authenticator *wpa_auth,
-		struct wpa_state_machine *sm, struct wpa_eapol_key *key,
-		const u8 *key_data, size_t key_data_len)
-{
-	struct wpa_eapol_ie_parse kde;
-	struct wpa_stsl_search search;
-	u8 smk[32], buf[ETH_ALEN + 8 + 2 * WPA_NONCE_LEN], *pos;
-
-	if (wpa_parse_kde_ies(key_data, key_data_len, &kde) < 0) {
-		wpa_printf(MSG_INFO, "RSN: Failed to parse KDEs in SMK M3");
-		return;
-	}
-
-	if (kde.rsn_ie == NULL ||
-	    kde.mac_addr == NULL || kde.mac_addr_len < ETH_ALEN ||
-	    kde.nonce == NULL || kde.nonce_len < WPA_NONCE_LEN) {
-		wpa_printf(MSG_INFO, "RSN: No RSN IE, MAC address KDE, or "
-			   "Nonce KDE in SMK M3");
-		return;
-	}
-
-	/* Peer = sm->addr; Initiator = kde.mac_addr;
-	 * Peer Nonce = key->key_nonce; Initiator Nonce = kde.nonce */
-
-	search.addr = kde.mac_addr;
-	search.sm = NULL;
-	if (wpa_auth_for_each_sta(wpa_auth, wpa_stsl_select_sta, &search) ==
-	    0 || search.sm == NULL) {
-		wpa_printf(MSG_DEBUG, "RSN: SMK handshake with " MACSTR
-			   " aborted - STA not associated anymore",
-			   MAC2STR(kde.mac_addr));
-		wpa_smk_send_error(wpa_auth, sm, kde.mac_addr, STK_MUI_SMK,
-				   STK_ERR_STA_NR);
-		/* FIX: wpa_stsl_remove(wpa_auth, neg); */
-		return;
-	}
-
-	if (random_get_bytes(smk, PMK_LEN)) {
-		wpa_printf(MSG_DEBUG, "RSN: Failed to generate SMK");
-		return;
-	}
-
-	/* SMK = PRF-256(Random number, "SMK Derivation",
-	 *               AA || Time || INonce || PNonce)
-	 */
-	os_memcpy(buf, wpa_auth->addr, ETH_ALEN);
-	pos = buf + ETH_ALEN;
-	wpa_get_ntp_timestamp(pos);
-	pos += 8;
-	os_memcpy(pos, kde.nonce, WPA_NONCE_LEN);
-	pos += WPA_NONCE_LEN;
-	os_memcpy(pos, key->key_nonce, WPA_NONCE_LEN);
-#ifdef CONFIG_IEEE80211W
-	sha256_prf(smk, PMK_LEN, "SMK Derivation", buf, sizeof(buf),
-		   smk, PMK_LEN);
-#else /* CONFIG_IEEE80211W */
-	sha1_prf(smk, PMK_LEN, "SMK Derivation", buf, sizeof(buf),
-		 smk, PMK_LEN);
-#endif /* CONFIG_IEEE80211W */
-
-	wpa_hexdump_key(MSG_DEBUG, "RSN: SMK", smk, PMK_LEN);
-
-	wpa_send_smk_m4(wpa_auth, sm, key, &kde, smk);
-	wpa_send_smk_m5(wpa_auth, search.sm, key, &kde, smk, sm->addr);
-
-	/* Authenticator does not need SMK anymore and it is required to forget
-	 * it. */
-	os_memset(smk, 0, sizeof(*smk));
-}
-
-
-void wpa_smk_error(struct wpa_authenticator *wpa_auth,
-		   struct wpa_state_machine *sm,
-		   const u8 *key_data, size_t key_data_len)
-{
-	struct wpa_eapol_ie_parse kde;
-	struct wpa_stsl_search search;
-	struct rsn_error_kde error;
-	u16 mui, error_type;
-
-	if (wpa_parse_kde_ies(key_data, key_data_len, &kde) < 0) {
-		wpa_printf(MSG_INFO, "RSN: Failed to parse KDEs in SMK Error");
-		return;
-	}
-
-	if (kde.mac_addr == NULL || kde.mac_addr_len < ETH_ALEN ||
-	    kde.error == NULL || kde.error_len < sizeof(error)) {
-		wpa_printf(MSG_INFO, "RSN: No MAC address or Error KDE in "
-			   "SMK Error");
-		return;
-	}
-
-	search.addr = kde.mac_addr;
-	search.sm = NULL;
-	if (wpa_auth_for_each_sta(wpa_auth, wpa_stsl_select_sta, &search) ==
-	    0 || search.sm == NULL) {
-		wpa_printf(MSG_DEBUG, "RSN: Peer STA " MACSTR " not "
-			   "associated for SMK Error message from " MACSTR,
-			   MAC2STR(kde.mac_addr), MAC2STR(sm->addr));
-		return;
-	}
-
-	os_memcpy(&error, kde.error, sizeof(error));
-	mui = be_to_host16(error.mui);
-	error_type = be_to_host16(error.error_type);
-	wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
-			 "STA reported SMK Error: Peer " MACSTR
-			 " MUI %d Error Type %d",
-			 MAC2STR(kde.mac_addr), mui, error_type);
-
-	wpa_smk_send_error(wpa_auth, search.sm, sm->addr, mui, error_type);
-}
-
-
-int wpa_stsl_remove(struct wpa_authenticator *wpa_auth,
-		    struct wpa_stsl_negotiation *neg)
-{
-	struct wpa_stsl_negotiation *pos, *prev;
-
-	if (wpa_auth == NULL)
-		return -1;
-	pos = wpa_auth->stsl_negotiations;
-	prev = NULL;
-	while (pos) {
-		if (pos == neg) {
-			if (prev)
-				prev->next = pos->next;
-			else
-				wpa_auth->stsl_negotiations = pos->next;
-
-			eloop_cancel_timeout(wpa_stsl_step, wpa_auth, pos);
-			os_free(pos);
-			return 0;
-		}
-		prev = pos;
-		pos = pos->next;
-	}
-
-	return -1;
-}
-
-#endif /* CONFIG_PEERKEY */

+ 128 - 6
src/ap/pmksa_cache_auth.c

@@ -282,7 +282,42 @@ pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp)
 {
-	struct rsn_pmksa_cache_entry *entry, *pos;
+	struct rsn_pmksa_cache_entry *entry;
+
+	entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, kck, kck_len,
+					      aa, spa, session_timeout, eapol,
+					      akmp);
+
+	if (pmksa_cache_auth_add_entry(pmksa, entry) < 0)
+		return NULL;
+
+	return entry;
+}
+
+
+/**
+ * pmksa_cache_auth_create_entry - Create a PMKSA cache entry
+ * @pmk: The new pairwise master key
+ * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
+ * @pmkid: Calculated PMKID
+ * @kck: Key confirmation key or %NULL if not yet derived
+ * @kck_len: KCK length in bytes
+ * @aa: Authenticator address
+ * @spa: Supplicant address
+ * @session_timeout: Session timeout
+ * @eapol: Pointer to EAPOL state machine data
+ * @akmp: WPA_KEY_MGMT_* used in key derivation
+ * Returns: Pointer to the added PMKSA cache entry or %NULL on error
+ *
+ * This function creates a PMKSA entry.
+ */
+struct rsn_pmksa_cache_entry *
+pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid,
+			      const u8 *kck, size_t kck_len, const u8 *aa,
+			      const u8 *spa, int session_timeout,
+			      struct eapol_state_machine *eapol, int akmp)
+{
+	struct rsn_pmksa_cache_entry *entry;
 	struct os_reltime now;
 
 	if (pmk_len > PMK_LEN_MAX)
@@ -303,8 +338,7 @@ pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
 	else if (wpa_key_mgmt_suite_b(akmp))
 		rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
 	else
-		rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid,
-			  wpa_key_mgmt_sha256(akmp));
+		rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp);
 	os_get_reltime(&now);
 	entry->expiration = now.sec;
 	if (session_timeout > 0)
@@ -315,9 +349,30 @@ pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
 	os_memcpy(entry->spa, spa, ETH_ALEN);
 	pmksa_cache_from_eapol_data(entry, eapol);
 
+	return entry;
+}
+
+
+/**
+ * pmksa_cache_auth_add_entry - Add a PMKSA cache entry
+ * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
+ * @entry: Pointer to PMKSA cache entry
+ *
+ * This function adds PMKSA cache entry to the PMKSA cache. If an old entry is
+ * already in the cache for the same Supplicant, this entry will be replaced
+ * with the new entry. PMKID will be calculated based on the PMK.
+ */
+int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa,
+			       struct rsn_pmksa_cache_entry *entry)
+{
+	struct rsn_pmksa_cache_entry *pos;
+
+	if (entry == NULL)
+		return -1;
+
 	/* Replace an old entry for the same STA (if found) with the new entry
 	 */
-	pos = pmksa_cache_auth_get(pmksa, spa, NULL);
+	pos = pmksa_cache_auth_get(pmksa, entry->spa, NULL);
 	if (pos)
 		pmksa_cache_free_entry(pmksa, pos);
 
@@ -331,7 +386,7 @@ pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
 
 	pmksa_cache_link_entry(pmksa, entry);
 
-	return entry;
+	return 0;
 }
 
 
@@ -462,7 +517,7 @@ struct rsn_pmksa_cache_entry * pmksa_cache_get_okc(
 		if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0)
 			continue;
 		rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid,
-			  wpa_key_mgmt_sha256(entry->akmp));
+			  entry->akmp);
 		if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0)
 			return entry;
 	}
@@ -605,3 +660,70 @@ int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len)
 	}
 	return pos - buf;
 }
+
+
+#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
+#ifdef CONFIG_MESH
+
+/**
+ * pmksa_cache_auth_list_mesh - Dump text list of entries in PMKSA cache
+ * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
+ * @addr: MAC address of the peer (NULL means any)
+ * @buf: Buffer for the list
+ * @len: Length of the buffer
+ * Returns: Number of bytes written to buffer
+ *
+ * This function is used to generate a text format representation of the
+ * current PMKSA cache contents for the ctrl_iface PMKSA_GET command to store
+ * in external storage.
+ */
+int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr,
+			       char *buf, size_t len)
+{
+	int ret;
+	char *pos, *end;
+	struct rsn_pmksa_cache_entry *entry;
+	struct os_reltime now;
+
+	pos = buf;
+	end = buf + len;
+	os_get_reltime(&now);
+
+
+	/*
+	 * Entry format:
+	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
+	 */
+	for (entry = pmksa->pmksa; entry; entry = entry->next) {
+		if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0)
+			continue;
+
+		ret = os_snprintf(pos, end - pos, MACSTR " ",
+				  MAC2STR(entry->spa));
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+
+		pos += wpa_snprintf_hex(pos, end - pos, entry->pmkid,
+					PMKID_LEN);
+
+		ret = os_snprintf(pos, end - pos, " ");
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+
+		pos += wpa_snprintf_hex(pos, end - pos, entry->pmk,
+					entry->pmk_len);
+
+		ret = os_snprintf(pos, end - pos, " %d\n",
+				  (int) (entry->expiration - now.sec));
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+	}
+
+	return pos - buf;
+}
+
+#endif /* CONFIG_MESH */
+#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */

+ 10 - 0
src/ap/pmksa_cache_auth.h

@@ -35,6 +35,7 @@ struct rsn_pmksa_cache_entry {
 };
 
 struct rsn_pmksa_cache;
+struct radius_das_attrs;
 
 struct rsn_pmksa_cache *
 pmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
@@ -53,6 +54,13 @@ pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp);
 struct rsn_pmksa_cache_entry *
+pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid,
+			      const u8 *kck, size_t kck_len, const u8 *aa,
+			      const u8 *spa, int session_timeout,
+			      struct eapol_state_machine *eapol, int akmp);
+int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa,
+			       struct rsn_pmksa_cache_entry *entry);
+struct rsn_pmksa_cache_entry *
 pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
 		    const struct rsn_pmksa_cache_entry *old_entry,
 		    const u8 *aa, const u8 *pmkid);
@@ -65,5 +73,7 @@ int pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa,
 					   struct radius_das_attrs *attr);
 int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len);
 void pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa);
+int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr,
+			       char *buf, size_t len);
 
 #endif /* PMKSA_CACHE_H */

+ 147 - 17
src/ap/rrm.c

@@ -2,6 +2,7 @@
  * hostapd / Radio Measurement (RRM)
  * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH.
  * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved.
+ * Copyright (c) 2016-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -10,6 +11,7 @@
 #include "utils/includes.h"
 
 #include "utils/common.h"
+#include "common/wpa_ctrl.h"
 #include "hostapd.h"
 #include "ap_drv_ops.h"
 #include "sta_info.h"
@@ -69,24 +71,47 @@ static void hostapd_handle_range_report(struct hostapd_data *hapd, u8 token,
 }
 
 
+static void hostapd_handle_beacon_report(struct hostapd_data *hapd,
+					 const u8 *addr, u8 token, u8 rep_mode,
+					 const u8 *pos, size_t len)
+{
+	char report[2 * 255 + 1];
+
+	wpa_printf(MSG_DEBUG, "Beacon report token %u len %zu from " MACSTR,
+		   token, len, MAC2STR(addr));
+	/* Skip to the beginning of the Beacon report */
+	if (len < 3)
+		return;
+	pos += 3;
+	len -= 3;
+	report[0] = '\0';
+	if (wpa_snprintf_hex(report, sizeof(report), pos, len) < 0)
+		return;
+	wpa_msg(hapd->msg_ctx, MSG_INFO, BEACON_RESP_RX MACSTR " %u %02x %s",
+		MAC2STR(addr), token, rep_mode, report);
+}
+
+
 static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
 					     const u8 *buf, size_t len)
 {
 	const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
 	const u8 *pos, *ie, *end;
-	u8 token;
+	u8 token, rep_mode;
 
 	end = buf + len;
 	token = mgmt->u.action.u.rrm.dialog_token;
 	pos = mgmt->u.action.u.rrm.variable;
 
 	while ((ie = get_ie(pos, end - pos, WLAN_EID_MEASURE_REPORT))) {
-		if (ie[1] < 5) {
+		if (ie[1] < 3) {
 			wpa_printf(MSG_DEBUG, "Bad Measurement Report element");
 			break;
 		}
 
-		wpa_printf(MSG_DEBUG, "Measurement report type %u", ie[4]);
+		rep_mode = ie[3];
+		wpa_printf(MSG_DEBUG, "Measurement report mode 0x%x type %u",
+			   rep_mode, ie[4]);
 
 		switch (ie[4]) {
 		case MEASURE_TYPE_LCI:
@@ -95,6 +120,10 @@ static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
 		case MEASURE_TYPE_FTM_RANGE:
 			hostapd_handle_range_report(hapd, token, ie + 2, ie[1]);
 			break;
+		case MEASURE_TYPE_BEACON:
+			hostapd_handle_beacon_report(hapd, mgmt->sa, token,
+						     rep_mode, ie + 2, ie[1]);
+			break;
 		default:
 			wpa_printf(MSG_DEBUG,
 				   "Measurement report type %u is not supported",
@@ -118,7 +147,7 @@ static u16 hostapd_parse_location_lci_req_age(const u8 *buf, size_t len)
 	/* Subelements are arranged as IEs */
 	subelem = get_ie(buf + 4, len - 4, LCI_REQ_SUBELEM_MAX_AGE);
 	if (subelem && subelem[1] == 2)
-		return *(u16 *) (subelem + 2);
+		return WPA_GET_LE16(subelem + 2);
 
 	return 0;
 }
@@ -129,12 +158,12 @@ static int hostapd_check_lci_age(struct hostapd_neighbor_entry *nr, u16 max_age)
 	struct os_time curr, diff;
 	unsigned long diff_l;
 
+	if (nr->stationary || max_age == 0xffff)
+		return 1;
+
 	if (!max_age)
 		return 0;
 
-	if (max_age == 0xffff)
-		return 1;
-
 	if (os_get_time(&curr))
 		return 0;
 
@@ -341,13 +370,7 @@ int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr)
 	struct sta_info *sta = ap_get_sta(hapd, addr);
 	int ret;
 
-	if (!sta) {
-		wpa_printf(MSG_INFO,
-			   "Request LCI: Destination address is not in station list");
-		return -1;
-	}
-
-	if (!(sta->flags & WLAN_STA_AUTHORIZED)) {
+	if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) {
 		wpa_printf(MSG_INFO,
 			   "Request LCI: Destination address is not connected");
 		return -1;
@@ -450,9 +473,8 @@ int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
 		wpa_printf(MSG_DEBUG,
 			   "Request range: Range request is already in process; overriding");
 		hapd->range_req_active = 0;
-		eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
-				       hostapd_range_rep_timeout_handler, hapd,
-				       NULL);
+		eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd,
+				     NULL);
 	}
 
 	/* Action + measurement type + token + reps + EID + len = 7 */
@@ -542,3 +564,111 @@ void hostapd_clean_rrm(struct hostapd_data *hapd)
 	eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
 	hapd->range_req_active = 0;
 }
+
+
+int hostapd_send_beacon_req(struct hostapd_data *hapd, const u8 *addr,
+			    u8 req_mode, const struct wpabuf *req)
+{
+	struct wpabuf *buf;
+	struct sta_info *sta = ap_get_sta(hapd, addr);
+	int ret;
+	enum beacon_report_mode mode;
+	const u8 *pos;
+
+	/* Request data:
+	 * Operating Class (1), Channel Number (1), Randomization Interval (2),
+	 * Measurement Duration (2), Measurement Mode (1), BSSID (6),
+	 * Optional Subelements (variable)
+	 */
+	if (wpabuf_len(req) < 13) {
+		wpa_printf(MSG_INFO, "Beacon request: Too short request data");
+		return -1;
+	}
+	pos = wpabuf_head(req);
+	mode = pos[6];
+
+	if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) {
+		wpa_printf(MSG_INFO,
+			   "Beacon request: " MACSTR " is not connected",
+			   MAC2STR(addr));
+		return -1;
+	}
+
+	switch (mode) {
+	case BEACON_REPORT_MODE_PASSIVE:
+		if (!(sta->rrm_enabled_capa[0] &
+		      WLAN_RRM_CAPS_BEACON_REPORT_PASSIVE)) {
+			wpa_printf(MSG_INFO,
+				   "Beacon request: " MACSTR
+				   " does not support passive beacon report",
+				   MAC2STR(addr));
+			return -1;
+		}
+		break;
+	case BEACON_REPORT_MODE_ACTIVE:
+		if (!(sta->rrm_enabled_capa[0] &
+		      WLAN_RRM_CAPS_BEACON_REPORT_ACTIVE)) {
+			wpa_printf(MSG_INFO,
+				   "Beacon request: " MACSTR
+				   " does not support active beacon report",
+				   MAC2STR(addr));
+			return -1;
+		}
+		break;
+	case BEACON_REPORT_MODE_TABLE:
+		if (!(sta->rrm_enabled_capa[0] &
+		      WLAN_RRM_CAPS_BEACON_REPORT_TABLE)) {
+			wpa_printf(MSG_INFO,
+				   "Beacon request: " MACSTR
+				   " does not support table beacon report",
+				   MAC2STR(addr));
+			return -1;
+		}
+		break;
+	default:
+		wpa_printf(MSG_INFO,
+			   "Beacon request: Unknown measurement mode %d", mode);
+		return -1;
+	}
+
+	buf = wpabuf_alloc(5 + 2 + 3 + wpabuf_len(req));
+	if (!buf)
+		return -1;
+
+	hapd->beacon_req_token++;
+	if (!hapd->beacon_req_token)
+		hapd->beacon_req_token++;
+
+	wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
+	wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST);
+	wpabuf_put_u8(buf, hapd->beacon_req_token);
+	wpabuf_put_le16(buf, 0); /* Number of repetitions */
+
+	/* Measurement Request element */
+	wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
+	wpabuf_put_u8(buf, 3 + wpabuf_len(req));
+	wpabuf_put_u8(buf, 1); /* Measurement Token */
+	wpabuf_put_u8(buf, req_mode); /* Measurement Request Mode */
+	wpabuf_put_u8(buf, MEASURE_TYPE_BEACON); /* Measurement Type */
+	wpabuf_put_buf(buf, req);
+
+	ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
+				      wpabuf_head(buf), wpabuf_len(buf));
+	wpabuf_free(buf);
+	if (ret < 0)
+		return ret;
+
+	return hapd->beacon_req_token;
+}
+
+
+void hostapd_rrm_beacon_req_tx_status(struct hostapd_data *hapd,
+				      const struct ieee80211_mgmt *mgmt,
+				      size_t len, int ok)
+{
+	if (len < 24 + 3)
+		return;
+	wpa_msg(hapd->msg_ctx, MSG_INFO, BEACON_REQ_TX_STATUS MACSTR
+		" %u ack=%d", MAC2STR(mgmt->da),
+		mgmt->u.action.u.rrm.dialog_token, ok);
+}

+ 5 - 0
src/ap/rrm.h

@@ -24,5 +24,10 @@ int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
 			   u16 random_interval, u8 min_ap,
 			   const u8 *responders, unsigned int n_responders);
 void hostapd_clean_rrm(struct hostapd_data *hapd);
+int hostapd_send_beacon_req(struct hostapd_data *hapd, const u8 *addr,
+			    u8 req_mode, const struct wpabuf *req);
+void hostapd_rrm_beacon_req_tx_status(struct hostapd_data *hapd,
+				      const struct ieee80211_mgmt *mgmt,
+				      size_t len, int ok);
 
 #endif /* RRM_H */

+ 121 - 8
src/ap/sta_info.c

@@ -1,6 +1,6 @@
 /*
  * hostapd / Station table
- * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -17,6 +17,7 @@
 #include "radius/radius_client.h"
 #include "p2p/p2p.h"
 #include "fst/fst.h"
+#include "crypto/crypto.h"
 #include "hostapd.h"
 #include "accounting.h"
 #include "ieee802_1x.h"
@@ -36,6 +37,7 @@
 #include "ndisc_snoop.h"
 #include "sta_info.h"
 #include "vlan.h"
+#include "wps_hostapd.h"
 
 static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd,
 				       struct sta_info *sta);
@@ -47,6 +49,7 @@ static void ap_sta_disassoc_cb_timeout(void *eloop_ctx, void *timeout_ctx);
 static void ap_sa_query_timer(void *eloop_ctx, void *timeout_ctx);
 #endif /* CONFIG_IEEE80211W */
 static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta);
+static void ap_sta_delayed_1x_auth_fail_cb(void *eloop_ctx, void *timeout_ctx);
 
 int ap_for_each_sta(struct hostapd_data *hapd,
 		    int (*cb)(struct hostapd_data *hapd, struct sta_info *sta,
@@ -194,7 +197,8 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 	if (sta->no_short_slot_time_set) {
 		sta->no_short_slot_time_set = 0;
 		hapd->iface->num_sta_no_short_slot_time--;
-		if (hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
+		if (hapd->iface->current_mode &&
+		    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
 		    && hapd->iface->num_sta_no_short_slot_time == 0)
 			set_beacon++;
 	}
@@ -202,7 +206,8 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 	if (sta->no_short_preamble_set) {
 		sta->no_short_preamble_set = 0;
 		hapd->iface->num_sta_no_short_preamble--;
-		if (hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
+		if (hapd->iface->current_mode &&
+		    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
 		    && hapd->iface->num_sta_no_short_preamble == 0)
 			set_beacon++;
 	}
@@ -337,6 +342,29 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 	mbo_ap_sta_free(sta);
 	os_free(sta->supp_op_classes);
 
+#ifdef CONFIG_FILS
+	os_free(sta->fils_pending_assoc_req);
+	wpabuf_free(sta->fils_hlp_resp);
+	wpabuf_free(sta->hlp_dhcp_discover);
+	eloop_cancel_timeout(fils_hlp_timeout, hapd, sta);
+#ifdef CONFIG_FILS_SK_PFS
+	crypto_ecdh_deinit(sta->fils_ecdh);
+	wpabuf_clear_free(sta->fils_dh_ss);
+	wpabuf_free(sta->fils_g_sta);
+#endif /* CONFIG_FILS_SK_PFS */
+#endif /* CONFIG_FILS */
+
+#ifdef CONFIG_OWE
+	bin_clear_free(sta->owe_pmk, sta->owe_pmk_len);
+	crypto_ecdh_deinit(sta->owe_ecdh);
+#endif /* CONFIG_OWE */
+
+	os_free(sta->ext_capability);
+
+#ifdef CONFIG_WNM_AP
+	eloop_cancel_timeout(ap_sta_reset_steer_flag_timer, hapd, sta);
+#endif /* CONFIG_WNM_AP */
+
 	os_free(sta);
 }
 
@@ -597,7 +625,7 @@ void ap_sta_no_session_timeout(struct hostapd_data *hapd, struct sta_info *sta)
 
 static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx)
 {
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
 	struct hostapd_data *hapd = eloop_ctx;
 	struct sta_info *sta = timeout_ctx;
 
@@ -608,7 +636,7 @@ static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx)
 
 	wnm_send_ess_disassoc_imminent(hapd, sta, sta->hs20_session_info_url,
 				       sta->hs20_disassoc_timer);
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 }
 
 
@@ -745,9 +773,17 @@ void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
 	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
 		   hapd->conf->iface, MAC2STR(sta->addr));
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
-	sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
+	if (hapd->iface->current_mode &&
+	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
+		/* Skip deauthentication in DMG/IEEE 802.11ad */
+		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
+				WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_REMOVE;
+	} else {
+		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_DEAUTH;
+	}
 	ap_sta_set_authorized(hapd, sta, 0);
-	sta->timeout_next = STA_DEAUTH;
 	wpa_printf(MSG_DEBUG, "%s: reschedule ap_handle_timer timeout "
 		   "for " MACSTR " (%d seconds - "
 		   "AP_MAX_INACTIVITY_AFTER_DISASSOC)",
@@ -783,6 +819,14 @@ static void ap_sta_deauth_cb_timeout(void *eloop_ctx, void *timeout_ctx)
 void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
 			   u16 reason)
 {
+	if (hapd->iface->current_mode &&
+	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
+		/* Deauthentication is not used in DMG/IEEE 802.11ad;
+		 * disassociate the STA instead. */
+		ap_sta_disassociate(hapd, sta, reason);
+		return;
+	}
+
 	wpa_printf(MSG_DEBUG, "%s: deauthenticate STA " MACSTR,
 		   hapd->conf->iface, MAC2STR(sta->addr));
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
@@ -1229,6 +1273,20 @@ void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 			       ap_handle_timer, hapd, sta);
 	sta->timeout_next = STA_REMOVE;
 
+	if (hapd->iface->current_mode &&
+	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
+		/* Deauthentication is not used in DMG/IEEE 802.11ad;
+		 * disassociate the STA instead. */
+		sta->disassoc_reason = reason;
+		sta->flags |= WLAN_STA_PENDING_DISASSOC_CB;
+		eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta);
+		eloop_register_timeout(hapd->iface->drv_flags &
+				       WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS ?
+				       2 : 0, 0, ap_sta_disassoc_cb_timeout,
+				       hapd, sta);
+		return;
+	}
+
 	sta->deauth_reason = reason;
 	sta->flags |= WLAN_STA_PENDING_DEAUTH_CB;
 	eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta);
@@ -1275,6 +1333,15 @@ void ap_sta_clear_disconnect_timeouts(struct hostapd_data *hapd,
 			   "%s: Removed ap_sta_disassoc_cb_timeout timeout for "
 			   MACSTR,
 			   hapd->conf->iface, MAC2STR(sta->addr));
+	if (eloop_cancel_timeout(ap_sta_delayed_1x_auth_fail_cb, hapd, sta) > 0)
+	{
+		wpa_printf(MSG_DEBUG,
+			   "%s: Removed ap_sta_delayed_1x_auth_fail_cb timeout for "
+			   MACSTR,
+			   hapd->conf->iface, MAC2STR(sta->addr));
+		if (sta->flags & WLAN_STA_WPS)
+			hostapd_wps_eap_completed(hapd);
+	}
 }
 
 
@@ -1283,7 +1350,7 @@ int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen)
 	int res;
 
 	buf[0] = '\0';
-	res = os_snprintf(buf, buflen, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+	res = os_snprintf(buf, buflen, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
 			  (flags & WLAN_STA_AUTH ? "[AUTH]" : ""),
 			  (flags & WLAN_STA_ASSOC ? "[ASSOC]" : ""),
 			  (flags & WLAN_STA_AUTHORIZED ? "[AUTHORIZED]" : ""),
@@ -1300,6 +1367,7 @@ int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen)
 			  (flags & WLAN_STA_NONERP ? "[NonERP]" : ""),
 			  (flags & WLAN_STA_WPS2 ? "[WPS2]" : ""),
 			  (flags & WLAN_STA_GAS ? "[GAS]" : ""),
+			  (flags & WLAN_STA_HT ? "[HT]" : ""),
 			  (flags & WLAN_STA_VHT ? "[VHT]" : ""),
 			  (flags & WLAN_STA_VENDOR_VHT ? "[VENDOR_VHT]" : ""),
 			  (flags & WLAN_STA_WNM_SLEEP_MODE ?
@@ -1309,3 +1377,48 @@ int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen)
 
 	return res;
 }
+
+
+static void ap_sta_delayed_1x_auth_fail_cb(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct sta_info *sta = timeout_ctx;
+	u16 reason;
+
+	wpa_dbg(hapd->msg_ctx, MSG_DEBUG,
+		"IEEE 802.1X: Scheduled disconnection of " MACSTR
+		" after EAP-Failure", MAC2STR(sta->addr));
+
+	reason = sta->disconnect_reason_code;
+	if (!reason)
+		reason = WLAN_REASON_IEEE_802_1X_AUTH_FAILED;
+	ap_sta_disconnect(hapd, sta, sta->addr, reason);
+	if (sta->flags & WLAN_STA_WPS)
+		hostapd_wps_eap_completed(hapd);
+}
+
+
+void ap_sta_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd,
+					    struct sta_info *sta)
+{
+	wpa_dbg(hapd->msg_ctx, MSG_DEBUG,
+		"IEEE 802.1X: Force disconnection of " MACSTR
+		" after EAP-Failure in 10 ms", MAC2STR(sta->addr));
+
+	/*
+	 * Add a small sleep to increase likelihood of previously requested
+	 * EAP-Failure TX getting out before this should the driver reorder
+	 * operations.
+	 */
+	eloop_cancel_timeout(ap_sta_delayed_1x_auth_fail_cb, hapd, sta);
+	eloop_register_timeout(0, 10000, ap_sta_delayed_1x_auth_fail_cb,
+			       hapd, sta);
+}
+
+
+int ap_sta_pending_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd,
+						   struct sta_info *sta)
+{
+	return eloop_is_timeout_registered(ap_sta_delayed_1x_auth_fail_cb,
+					   hapd, sta);
+}

+ 55 - 10
src/ap/sta_info.h

@@ -1,6 +1,6 @@
 /*
  * hostapd / Station table
- * Copyright (c) 2002-2011, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -9,14 +9,11 @@
 #ifndef STA_INFO_H
 #define STA_INFO_H
 
-#ifdef CONFIG_MESH
-/* needed for mesh_plink_state enum */
 #include "common/defs.h"
-#include "common/wpa_common.h"
-#endif /* CONFIG_MESH */
-
 #include "list.h"
 #include "vlan.h"
+#include "common/wpa_common.h"
+#include "common/ieee802_11_defs.h"
 
 /* STA flags */
 #define WLAN_STA_AUTH BIT(0)
@@ -38,6 +35,7 @@
 #define WLAN_STA_WNM_SLEEP_MODE BIT(19)
 #define WLAN_STA_VHT_OPMODE_ENABLED BIT(20)
 #define WLAN_STA_VENDOR_VHT BIT(21)
+#define WLAN_STA_PENDING_FILS_ERP BIT(22)
 #define WLAN_STA_PENDING_DISASSOC_CB BIT(29)
 #define WLAN_STA_PENDING_DEAUTH_CB BIT(30)
 #define WLAN_STA_NONERP BIT(31)
@@ -46,6 +44,7 @@
  * Supported Rates IEs). */
 #define WLAN_SUPP_RATES_MAX 32
 
+struct hostapd_data;
 
 struct mbo_non_pref_chan_info {
 	struct mbo_non_pref_chan_info *next;
@@ -68,6 +67,7 @@ struct sta_info {
 	be32 ipaddr;
 	struct dl_list ip6addr; /* list head for struct ip6addr */
 	u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */
+	u16 disconnect_reason_code; /* RADIUS server override */
 	u32 flags; /* Bitfield of WLAN_STA_* */
 	u16 capability;
 	u16 listen_interval; /* or beacon_int for APs */
@@ -113,6 +113,9 @@ struct sta_info {
 	unsigned int radius_das_match:1;
 	unsigned int ecsa_supported:1;
 	unsigned int added_unassoc:1;
+	unsigned int pending_wds_enable:1;
+	unsigned int power_capab:1;
+	unsigned int agreed_to_steer:1;
 
 	u16 auth_alg;
 
@@ -170,11 +173,11 @@ struct sta_info {
 	struct os_reltime sa_query_start;
 #endif /* CONFIG_IEEE80211W */
 
-#ifdef CONFIG_INTERWORKING
+#if defined(CONFIG_INTERWORKING) || defined(CONFIG_DPP)
 #define GAS_DIALOG_MAX 8 /* Max concurrent dialog number */
 	struct gas_dialog_info *gas_dialog;
 	u8 gas_dialog_next;
-#endif /* CONFIG_INTERWORKING */
+#endif /* CONFIG_INTERWORKING || CONFIG_DPP */
 
 	struct wpabuf *wps_ie; /* WPS IE from (Re)Association Request */
 	struct wpabuf *p2p_ie; /* P2P IE from (Re)Association Request */
@@ -214,10 +217,50 @@ struct sta_info {
 
 	u8 rrm_enabled_capa[5];
 
+	s8 min_tx_power;
+	s8 max_tx_power;
+
 #ifdef CONFIG_TAXONOMY
 	struct wpabuf *probe_ie_taxonomy;
 	struct wpabuf *assoc_ie_taxonomy;
 #endif /* CONFIG_TAXONOMY */
+
+#ifdef CONFIG_FILS
+	u8 fils_snonce[FILS_NONCE_LEN];
+	u8 fils_session[FILS_SESSION_LEN];
+	u8 fils_erp_pmkid[PMKID_LEN];
+	u8 *fils_pending_assoc_req;
+	size_t fils_pending_assoc_req_len;
+	unsigned int fils_pending_assoc_is_reassoc:1;
+	unsigned int fils_dhcp_rapid_commit_proxy:1;
+	unsigned int fils_erp_pmkid_set:1;
+	unsigned int fils_drv_assoc_finish:1;
+	struct wpabuf *fils_hlp_resp;
+	struct wpabuf *hlp_dhcp_discover;
+	void (*fils_pending_cb)(struct hostapd_data *hapd, struct sta_info *sta,
+				u16 resp, struct wpabuf *data, int pub);
+#ifdef CONFIG_FILS_SK_PFS
+	struct crypto_ecdh *fils_ecdh;
+#endif /* CONFIG_FILS_SK_PFS */
+	struct wpabuf *fils_dh_ss;
+	struct wpabuf *fils_g_sta;
+#endif /* CONFIG_FILS */
+
+#ifdef CONFIG_OWE
+	u8 *owe_pmk;
+	size_t owe_pmk_len;
+	struct crypto_ecdh *owe_ecdh;
+	u16 owe_group;
+#endif /* CONFIG_OWE */
+
+	u8 *ext_capability;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	enum wpa_alg last_tk_alg;
+	int last_tk_key_idx;
+	u8 last_tk[WPA_TK_MAX_LEN];
+	size_t last_tk_len;
+#endif /* CONFIG_TESTING_OPTIONS */
 };
 
 
@@ -237,8 +280,6 @@ struct sta_info {
 #define AP_MAX_INACTIVITY_AFTER_DEAUTH (1 * 5)
 
 
-struct hostapd_data;
-
 int ap_for_each_sta(struct hostapd_data *hapd,
 		    int (*cb)(struct hostapd_data *hapd, struct sta_info *sta,
 			      void *ctx),
@@ -289,5 +330,9 @@ void ap_sta_clear_disconnect_timeouts(struct hostapd_data *hapd,
 				      struct sta_info *sta);
 
 int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen);
+void ap_sta_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd,
+					    struct sta_info *sta);
+int ap_sta_pending_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd,
+						   struct sta_info *sta);
 
 #endif /* STA_INFO_H */

+ 1 - 0
src/ap/taxonomy.c

@@ -21,6 +21,7 @@
 #include "common/wpa_ctrl.h"
 #include "hostapd.h"
 #include "sta_info.h"
+#include "taxonomy.h"
 
 
 /* Copy a string with no funny schtuff allowed; only alphanumerics. */

+ 6 - 9
src/ap/wmm.c

@@ -21,11 +21,6 @@
 #include "wmm.h"
 
 
-/* TODO: maintain separate sequence and fragment numbers for each AC
- * TODO: IGMP snooping to track which multicasts to forward - and use QOS-DATA
- * if only WMM stations are receiving a certain group */
-
-
 static inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci)
 {
 	u8 ret;
@@ -157,8 +152,9 @@ static void wmm_send_action(struct hostapd_data *hapd, const u8 *addr,
 
 int wmm_process_tspec(struct wmm_tspec_element *tspec)
 {
-	int medium_time, pps, duration;
-	int up, psb, dir, tid;
+	u64 medium_time;
+	unsigned int pps, duration;
+	unsigned int up, psb, dir, tid;
 	u16 val, surplus;
 
 	up = (tspec->ts_info[1] >> 3) & 0x07;
@@ -206,8 +202,9 @@ int wmm_process_tspec(struct wmm_tspec_element *tspec)
 		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
 	}
 
-	medium_time = surplus * pps * duration / 0x2000;
-	wpa_printf(MSG_DEBUG, "WMM: Estimated medium time: %u", medium_time);
+	medium_time = (u64) surplus * pps * duration / 0x2000;
+	wpa_printf(MSG_DEBUG, "WMM: Estimated medium time: %lu",
+		   (unsigned long) medium_time);
 
 	/*
 	 * TODO: store list of granted (and still active) TSPECs and check

+ 76 - 24
src/ap/wnm_ap.c

@@ -95,8 +95,8 @@ static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 	if (mgmt == NULL) {
 		wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for "
 			   "WNM-Sleep Response action frame");
-		os_free(wnmtfs_ie);
-		return -1;
+		res = -1;
+		goto fail;
 	}
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
@@ -109,6 +109,7 @@ static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 	pos = (u8 *)mgmt->u.action.u.wnm_sleep_resp.variable;
 	/* add key data if MFP is enabled */
 	if (!wpa_auth_uses_mfp(sta->wpa_sm) ||
+	    hapd->conf->wnm_sleep_mode_no_keys ||
 	    action_type != WNM_SLEEP_MODE_EXIT) {
 		mgmt->u.action.u.wnm_sleep_resp.keydata_len = 0;
 	} else {
@@ -118,11 +119,8 @@ static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 			   (int) gtk_elem_len);
 #ifdef CONFIG_IEEE80211W
 		res = wpa_wnmsleep_igtk_subelem(sta->wpa_sm, pos);
-		if (res < 0) {
-			os_free(wnmtfs_ie);
-			os_free(mgmt);
-			return -1;
-		}
+		if (res < 0)
+			goto fail;
 		igtk_elem_len = res;
 		pos += igtk_elem_len;
 		wpa_printf(MSG_DEBUG, "Pass 4 igtk_len = %d",
@@ -176,7 +174,8 @@ static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 			wpa_set_wnmsleep(sta->wpa_sm, 0);
 			hostapd_drv_wnm_oper(hapd, WNM_SLEEP_EXIT_CONFIRM,
 					     addr, NULL, NULL);
-			if (!wpa_auth_uses_mfp(sta->wpa_sm))
+			if (!wpa_auth_uses_mfp(sta->wpa_sm) ||
+			    hapd->conf->wnm_sleep_mode_no_keys)
 				wpa_wnmsleep_rekey_gtk(sta->wpa_sm);
 		}
 	} else
@@ -184,6 +183,7 @@ static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 
 #undef MAX_GTK_SUBELEM_LEN
 #undef MAX_IGTK_SUBELEM_LEN
+fail:
 	os_free(wnmtfs_ie);
 	os_free(mgmt);
 	return res;
@@ -202,12 +202,20 @@ static void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd,
 	u8 *tfsreq_ie_end = NULL;
 	u16 tfsreq_ie_len = 0;
 
+	if (!hapd->conf->wnm_sleep_mode) {
+		wpa_printf(MSG_DEBUG, "Ignore WNM-Sleep Mode Request from "
+			   MACSTR " since WNM-Sleep Mode is disabled",
+			   MAC2STR(addr));
+		return;
+	}
+
 	dialog_token = *pos++;
 	while (pos + 1 < frm + len) {
 		u8 ie_len = pos[1];
 		if (pos + 2 + ie_len > frm + len)
 			break;
-		if (*pos == WLAN_EID_WNMSLEEP)
+		if (*pos == WLAN_EID_WNMSLEEP &&
+		    ie_len >= (int) sizeof(*wnmsleep_ie) - 2)
 			wnmsleep_ie = (struct wnm_sleep_element *) pos;
 		else if (*pos == WLAN_EID_TFS_REQ) {
 			if (!tfsreq_ie_start)
@@ -251,20 +259,14 @@ static void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd,
 
 static int ieee802_11_send_bss_trans_mgmt_request(struct hostapd_data *hapd,
 						  const u8 *addr,
-						  u8 dialog_token,
-						  const char *url)
+						  u8 dialog_token)
 {
 	struct ieee80211_mgmt *mgmt;
-	size_t url_len, len;
+	size_t len;
 	u8 *pos;
 	int res;
 
-	if (url)
-		url_len = os_strlen(url);
-	else
-		url_len = 0;
-
-	mgmt = os_zalloc(sizeof(*mgmt) + (url_len ? 1 + url_len : 0));
+	mgmt = os_zalloc(sizeof(*mgmt));
 	if (mgmt == NULL)
 		return -1;
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
@@ -279,11 +281,6 @@ static int ieee802_11_send_bss_trans_mgmt_request(struct hostapd_data *hapd,
 	mgmt->u.action.u.bss_tm_req.disassoc_timer = host_to_le16(0);
 	mgmt->u.action.u.bss_tm_req.validity_interval = 1;
 	pos = mgmt->u.action.u.bss_tm_req.variable;
-	if (url) {
-		*pos++ += url_len;
-		os_memcpy(pos, url, url_len);
-		pos += url_len;
-	}
 
 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to "
 		   MACSTR " dialog_token=%u req_mode=0x%x disassoc_timer=%u "
@@ -307,6 +304,20 @@ static void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd,
 {
 	u8 dialog_token, reason;
 	const u8 *pos, *end;
+	int enabled = hapd->conf->bss_transition;
+
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled)
+		enabled = 1;
+#endif /* CONFIG_MBO */
+	if (!enabled) {
+		wpa_printf(MSG_DEBUG,
+			   "Ignore BSS Transition Management Query from "
+			   MACSTR
+			   " since BSS Transition Management is disabled",
+			   MAC2STR(addr));
+		return;
+	}
 
 	if (len < 2) {
 		wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Query from "
@@ -326,7 +337,20 @@ static void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd,
 	wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries",
 		    pos, end - pos);
 
-	ieee802_11_send_bss_trans_mgmt_request(hapd, addr, dialog_token, NULL);
+	ieee802_11_send_bss_trans_mgmt_request(hapd, addr, dialog_token);
+}
+
+
+void ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct sta_info *sta = timeout_ctx;
+
+	if (sta->agreed_to_steer) {
+		wpa_printf(MSG_DEBUG, "%s: Reset steering flag for STA " MACSTR,
+			   hapd->conf->iface, MAC2STR(sta->addr));
+		sta->agreed_to_steer = 0;
+	}
 }
 
 
@@ -336,6 +360,21 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 {
 	u8 dialog_token, status_code, bss_termination_delay;
 	const u8 *pos, *end;
+	int enabled = hapd->conf->bss_transition;
+	struct sta_info *sta;
+
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled)
+		enabled = 1;
+#endif /* CONFIG_MBO */
+	if (!enabled) {
+		wpa_printf(MSG_DEBUG,
+			   "Ignore BSS Transition Management Response from "
+			   MACSTR
+			   " since BSS Transition Management is disabled",
+			   MAC2STR(addr));
+		return;
+	}
 
 	if (len < 3) {
 		wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Response from "
@@ -354,11 +393,23 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 		   "bss_termination_delay=%u", MAC2STR(addr), dialog_token,
 		   status_code, bss_termination_delay);
 
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "Station " MACSTR
+			   " not found for received BSS TM Response",
+			   MAC2STR(addr));
+		return;
+	}
+
 	if (status_code == WNM_BSS_TM_ACCEPT) {
 		if (end - pos < ETH_ALEN) {
 			wpa_printf(MSG_DEBUG, "WNM: not enough room for Target BSSID field");
 			return;
 		}
+		sta->agreed_to_steer = 1;
+		eloop_cancel_timeout(ap_sta_reset_steer_flag_timer, hapd, sta);
+		eloop_register_timeout(2, 0, ap_sta_reset_steer_flag_timer,
+				       hapd, sta);
 		wpa_printf(MSG_DEBUG, "WNM: Target BSSID: " MACSTR,
 			   MAC2STR(pos));
 		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
@@ -368,6 +419,7 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 			MAC2STR(pos));
 		pos += ETH_ALEN;
 	} else {
+		sta->agreed_to_steer = 0;
 		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
 			" status_code=%u bss_termination_delay=%u",
 			MAC2STR(addr), status_code, bss_termination_delay);

+ 1 - 0
src/ap/wnm_ap.h

@@ -23,5 +23,6 @@ int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta,
 			const u8 *bss_term_dur, const char *url,
 			const u8 *nei_rep, size_t nei_rep_len,
 			const u8 *mbo_attrs, size_t mbo_len);
+void ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx);
 
 #endif /* WNM_AP_H */

File diff suppressed because it is too large
+ 240 - 273
src/ap/wpa_auth.c


+ 175 - 81
src/ap/wpa_auth.h

@@ -1,7 +1,7 @@
 /*
  * hostapd - IEEE 802.11i-2004 / WPA Authenticator
  * Copyright (c) 2017, Mathy Vanhoef <Mathy.Vanhoef@cs.kuleuven.be>
- * Copyright (c) 2004-2015, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2017, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -39,74 +39,90 @@ struct ft_rrb_frame {
 
 #define FT_PACKET_REQUEST 0
 #define FT_PACKET_RESPONSE 1
-/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r */
-#define FT_PACKET_R0KH_R1KH_PULL 200
-#define FT_PACKET_R0KH_R1KH_RESP 201
-#define FT_PACKET_R0KH_R1KH_PUSH 202
-
-#define FT_R0KH_R1KH_PULL_NONCE_LEN 16
-#define FT_R0KH_R1KH_PULL_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
-				    WPA_PMK_NAME_LEN + FT_R1KH_ID_LEN + \
-				    ETH_ALEN)
-#define FT_R0KH_R1KH_PULL_PAD_LEN ((8 - FT_R0KH_R1KH_PULL_DATA_LEN % 8) % 8)
-
-struct ft_r0kh_r1kh_pull_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PULL */
-	le16 data_length; /* little endian length of data (44) */
-	u8 ap_address[ETH_ALEN];
 
-	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN];
-	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
-	u8 r1kh_id[FT_R1KH_ID_LEN];
-	u8 s1kh_id[ETH_ALEN];
-	u8 pad[FT_R0KH_R1KH_PULL_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
-} STRUCT_PACKED;
+/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r. These
+ * use OUI Extended EtherType as the encapsulating format. */
+#define FT_PACKET_R0KH_R1KH_PULL 0x01
+#define FT_PACKET_R0KH_R1KH_RESP 0x02
+#define FT_PACKET_R0KH_R1KH_PUSH 0x03
+#define FT_PACKET_R0KH_R1KH_SEQ_REQ 0x04
+#define FT_PACKET_R0KH_R1KH_SEQ_RESP 0x05
+
+/* packet layout
+ *  IEEE 802 extended OUI ethertype frame header
+ *  u16 authlen (little endian)
+ *  multiple of struct ft_rrb_tlv (authenticated only, length = authlen)
+ *  multiple of struct ft_rrb_tlv (AES-SIV encrypted, AES-SIV needs an extra
+ *                                 blocksize length)
+ *
+ * AES-SIV AAD;
+ *  source MAC address (6)
+ *  authenticated-only TLVs (authlen)
+ *  subtype (1; FT_PACKET_*)
+ */
 
-#define FT_R0KH_R1KH_RESP_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
-				    FT_R1KH_ID_LEN + ETH_ALEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
-#define FT_R0KH_R1KH_RESP_PAD_LEN ((8 - FT_R0KH_R1KH_RESP_DATA_LEN % 8) % 8)
-struct ft_r0kh_r1kh_resp_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_RESP */
-	le16 data_length; /* little endian length of data (78) */
-	u8 ap_address[ETH_ALEN];
+#define FT_RRB_NONCE_LEN 16
 
-	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; /* copied from pull */
-	u8 r1kh_id[FT_R1KH_ID_LEN]; /* copied from pull */
-	u8 s1kh_id[ETH_ALEN]; /* copied from pull */
-	u8 pmk_r1[PMK_LEN];
-	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
-	le16 pairwise;
-	u8 pad[FT_R0KH_R1KH_RESP_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
-} STRUCT_PACKED;
+#define FT_RRB_LAST_EMPTY     0 /* placeholder or padding */
 
-#define FT_R0KH_R1KH_PUSH_DATA_LEN (4 + FT_R1KH_ID_LEN + ETH_ALEN + \
-				    WPA_PMK_NAME_LEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
-#define FT_R0KH_R1KH_PUSH_PAD_LEN ((8 - FT_R0KH_R1KH_PUSH_DATA_LEN % 8) % 8)
-struct ft_r0kh_r1kh_push_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PUSH */
-	le16 data_length; /* little endian length of data (82) */
-	u8 ap_address[ETH_ALEN];
+#define FT_RRB_SEQ            1 /* struct ft_rrb_seq */
+#define FT_RRB_NONCE          2 /* size FT_RRB_NONCE_LEN */
+#define FT_RRB_TIMESTAMP      3 /* le32 unix seconds */
+
+#define FT_RRB_R0KH_ID        4 /* FT_R0KH_ID_MAX_LEN */
+#define FT_RRB_R1KH_ID        5 /* FT_R1KH_ID_LEN */
+#define FT_RRB_S1KH_ID        6 /* ETH_ALEN */
+
+#define FT_RRB_PMK_R0_NAME    7 /* WPA_PMK_NAME_LEN */
+#define FT_RRB_PMK_R0         8 /* PMK_LEN */
+#define FT_RRB_PMK_R1_NAME    9 /* WPA_PMK_NAME_LEN */
+#define FT_RRB_PMK_R1        10 /* PMK_LEN */
+
+#define FT_RRB_PAIRWISE      11 /* le16 */
 
-	/* Encrypted with AES key-wrap */
-	u8 timestamp[4]; /* current time in seconds since unix epoch, little
-			  * endian */
-	u8 r1kh_id[FT_R1KH_ID_LEN];
-	u8 s1kh_id[ETH_ALEN];
-	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
-	u8 pmk_r1[PMK_LEN];
-	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
-	le16 pairwise;
-	u8 pad[FT_R0KH_R1KH_PUSH_PAD_LEN]; /* 8-octet boundary for AES block */
-	u8 key_wrap_extra[8];
+struct ft_rrb_tlv {
+	le16 type;
+	le16 len;
+	/* followed by data of length len */
 } STRUCT_PACKED;
 
+struct ft_rrb_seq {
+	le32 dom;
+	le32 seq;
+	le32 ts;
+} STRUCT_PACKED;
+
+/* session TLVs:
+ *   required: PMK_R1, PMK_R1_NAME, PAIRWISE
+ *
+ * pull frame TLVs:
+ *   auth:
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
+ *   encrypted:
+ *     required: PMK_R0_NAME, S1KH_ID
+ *
+ * response frame TLVs:
+ *   auth:
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
+ *   encrypted:
+ *     required: S1KH_ID
+ *     optional: session TLVs
+ *
+ * push frame TLVs:
+ *   auth:
+ *     required: SEQ, R0KH_ID, R1KH_ID
+ *   encrypted:
+ *     required: S1KH_ID, PMK_R0_NAME, session TLVs
+ *
+ * sequence number request frame TLVs:
+ *   auth:
+ *     required: R0KH_ID, R1KH_ID, NONCE
+ *
+ * sequence number response frame TLVs:
+ *   auth:
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
+ */
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
@@ -118,6 +134,7 @@ struct wpa_authenticator;
 struct wpa_state_machine;
 struct rsn_pmksa_cache_entry;
 struct eapol_state_machine;
+struct ft_remote_seq;
 
 
 struct ft_remote_r0kh {
@@ -125,7 +142,8 @@ struct ft_remote_r0kh {
 	u8 addr[ETH_ALEN];
 	u8 id[FT_R0KH_ID_MAX_LEN];
 	size_t id_len;
-	u8 key[16];
+	u8 key[32];
+	struct ft_remote_seq *seq;
 };
 
 
@@ -133,7 +151,8 @@ struct ft_remote_r1kh {
 	struct ft_remote_r1kh *next;
 	u8 addr[ETH_ALEN];
 	u8 id[FT_R1KH_ID_LEN];
-	u8 key[16];
+	u8 key[32];
+	struct ft_remote_seq *seq;
 };
 
 
@@ -146,10 +165,12 @@ struct wpa_auth_config {
 	int wpa_strict_rekey;
 	int wpa_gmk_rekey;
 	int wpa_ptk_rekey;
+	u32 wpa_group_update_count;
+	u32 wpa_pairwise_update_count;
+	int wpa_disable_eapol_key_retries;
 	int rsn_pairwise;
 	int rsn_preauth;
 	int eapol_version;
-	int peerkey;
 	int wmm_enabled;
 	int wmm_uapsd;
 	int disable_pmksa_caching;
@@ -158,8 +179,9 @@ struct wpa_auth_config {
 #ifdef CONFIG_IEEE80211W
 	enum mfp_options ieee80211w;
 	int group_mgmt_cipher;
+	int sae_require_mfp;
 #endif /* CONFIG_IEEE80211W */
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	u8 ssid[SSID_MAX_LEN];
 	size_t ssid_len;
 	u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN];
@@ -167,12 +189,17 @@ struct wpa_auth_config {
 	size_t r0_key_holder_len;
 	u8 r1_key_holder[FT_R1KH_ID_LEN];
 	u32 r0_key_lifetime;
+	int rkh_pos_timeout;
+	int rkh_neg_timeout;
+	int rkh_pull_timeout; /* ms */
+	int rkh_pull_retries;
 	u32 reassociation_deadline;
-	struct ft_remote_r0kh *r0kh_list;
-	struct ft_remote_r1kh *r1kh_list;
+	struct ft_remote_r0kh **r0kh_list;
+	struct ft_remote_r1kh **r1kh_list;
 	int pmk_r1_push;
 	int ft_over_ds;
-#endif /* CONFIG_IEEE80211R */
+	int ft_psk_generate_local;
+#endif /* CONFIG_IEEE80211R_AP */
 	int disable_gtk;
 	int ap_mlme;
 #ifdef CONFIG_TESTING_OPTIONS
@@ -186,6 +213,10 @@ struct wpa_auth_config {
 	u8 ip_addr_start[4];
 	u8 ip_addr_end[4];
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_FILS
+	unsigned int fils_cache_id_set:1;
+	u8 fils_cache_id[FILS_CACHE_ID_LEN];
+#endif /* CONFIG_FILS */
 };
 
 typedef enum {
@@ -199,7 +230,6 @@ typedef enum {
 } wpa_eapol_variable;
 
 struct wpa_auth_callbacks {
-	void *ctx;
 	void (*logger)(void *ctx, const u8 *addr, logger_level level,
 		       const char *txt);
 	void (*disconnect)(void *ctx, const u8 *addr, u16 reason);
@@ -209,7 +239,7 @@ struct wpa_auth_callbacks {
 			  int value);
 	int (*get_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var);
 	const u8 * (*get_psk)(void *ctx, const u8 *addr, const u8 *p2p_dev_addr,
-			      const u8 *prev_psk);
+			      const u8 *prev_psk, size_t *psk_len);
 	int (*get_msk)(void *ctx, const u8 *addr, u8 *msk, size_t *len);
 	int (*set_key)(void *ctx, int vlan_id, enum wpa_alg alg,
 		       const u8 *addr, int idx, u8 *key, size_t key_len);
@@ -222,13 +252,15 @@ struct wpa_auth_callbacks {
 						  void *ctx), void *cb_ctx);
 	int (*send_ether)(void *ctx, const u8 *dst, u16 proto, const u8 *data,
 			  size_t data_len);
-#ifdef CONFIG_IEEE80211R
+	int (*send_oui)(void *ctx, const u8 *dst, u8 oui_suffix, const u8 *data,
+			size_t data_len);
+#ifdef CONFIG_IEEE80211R_AP
 	struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr);
 	int (*send_ft_action)(void *ctx, const u8 *dst,
 			      const u8 *data, size_t data_len);
 	int (*add_tspec)(void *ctx, const u8 *sta_addr, u8 *tspec_ie,
 			 size_t tspec_ielen);
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_MESH
 	int (*start_ampe)(void *ctx, const u8 *sta_addr);
 #endif /* CONFIG_MESH */
@@ -236,7 +268,8 @@ struct wpa_auth_callbacks {
 
 struct wpa_authenticator * wpa_init(const u8 *addr,
 				    struct wpa_auth_config *conf,
-				    struct wpa_auth_callbacks *cb);
+				    const struct wpa_auth_callbacks *cb,
+				    void *cb_ctx);
 int wpa_init_keys(struct wpa_authenticator *wpa_auth);
 void wpa_deinit(struct wpa_authenticator *wpa_auth);
 int wpa_reconfig(struct wpa_authenticator *wpa_auth,
@@ -246,13 +279,14 @@ enum {
 	WPA_IE_OK, WPA_INVALID_IE, WPA_INVALID_GROUP, WPA_INVALID_PAIRWISE,
 	WPA_INVALID_AKMP, WPA_NOT_ENABLED, WPA_ALLOC_FAIL,
 	WPA_MGMT_FRAME_PROTECTION_VIOLATION, WPA_INVALID_MGMT_GROUP_CIPHER,
-	WPA_INVALID_MDIE, WPA_INVALID_PROTO
+	WPA_INVALID_MDIE, WPA_INVALID_PROTO, WPA_INVALID_PMKID
 };
-	
+
 int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			struct wpa_state_machine *sm,
 			const u8 *wpa_ie, size_t wpa_ie_len,
-			const u8 *mdie, size_t mdie_len);
+			const u8 *mdie, size_t mdie_len,
+			const u8 *owe_dh, size_t owe_dh_len);
 int wpa_validate_osen(struct wpa_authenticator *wpa_auth,
 		      struct wpa_state_machine *sm,
 		      const u8 *osen_ie, size_t osen_ie_len);
@@ -269,7 +303,7 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
 		 u8 *data, size_t data_len);
 enum wpa_event {
 	WPA_AUTH, WPA_ASSOC, WPA_DISASSOC, WPA_DEAUTH, WPA_REAUTH,
-	WPA_REAUTH_EAPOL, WPA_ASSOC_FT
+	WPA_REAUTH_EAPOL, WPA_ASSOC_FT, WPA_ASSOC_FILS, WPA_DRV_STA_REMOVED
 };
 void wpa_remove_ptk(struct wpa_state_machine *sm);
 int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event);
@@ -282,6 +316,8 @@ int wpa_auth_pairwise_set(struct wpa_state_machine *sm);
 int wpa_auth_get_pairwise(struct wpa_state_machine *sm);
 int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm);
 int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm);
+int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm);
+int wpa_auth_sta_fils_tk_already_set(struct wpa_state_machine *sm);
 int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm,
 			     struct rsn_pmksa_cache_entry *entry);
 struct rsn_pmksa_cache_entry *
@@ -298,13 +334,28 @@ int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth,
 			       struct eapol_state_machine *eapol);
 int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr,
 			   const u8 *pmk, const u8 *pmkid);
+void wpa_auth_add_sae_pmkid(struct wpa_state_machine *sm, const u8 *pmkid);
+int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr,
+			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
+			int session_timeout, int akmp);
 void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth,
 			   const u8 *sta_addr);
 int wpa_auth_pmksa_list(struct wpa_authenticator *wpa_auth, char *buf,
 			size_t len);
 void wpa_auth_pmksa_flush(struct wpa_authenticator *wpa_auth);
+int wpa_auth_pmksa_list_mesh(struct wpa_authenticator *wpa_auth, const u8 *addr,
+			     char *buf, size_t len);
+struct rsn_pmksa_cache_entry *
+wpa_auth_pmksa_create_entry(const u8 *aa, const u8 *spa, const u8 *pmk,
+			    const u8 *pmkid, int expiration);
+int wpa_auth_pmksa_add_entry(struct wpa_authenticator *wpa_auth,
+			     struct rsn_pmksa_cache_entry *entry);
+struct rsn_pmksa_cache_entry *
+wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
+		   const u8 *pmkid);
 struct rsn_pmksa_cache_entry *
-wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr);
+wpa_auth_pmksa_get_fils_cache_id(struct wpa_authenticator *wpa_auth,
+				 const u8 *sta_addr, const u8 *pmkid);
 void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa,
 			      struct wpa_state_machine *sm,
 			      struct wpa_authenticator *wpa_auth,
@@ -313,7 +364,7 @@ int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id);
 void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth,
 				  struct wpa_state_machine *sm, int ack);
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos,
 				 size_t max_len, int auth_alg,
 				 const u8 *req_ies, size_t req_ies_len);
@@ -328,8 +379,13 @@ u16 wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies,
 int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len);
 int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 		  const u8 *data, size_t data_len);
+void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
+		       const u8 *dst_addr, u8 oui_suffix, const u8 *data,
+		       size_t data_len);
 void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr);
-#endif /* CONFIG_IEEE80211R */
+void wpa_ft_deinit(struct wpa_authenticator *wpa_auth);
+void wpa_ft_sta_deinit(struct wpa_state_machine *sm);
+#endif /* CONFIG_IEEE80211R_AP */
 
 void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm);
 void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag);
@@ -348,6 +404,44 @@ void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth);
 
 int wpa_auth_ensure_group(struct wpa_authenticator *wpa_auth, int vlan_id);
 int wpa_auth_release_group(struct wpa_authenticator *wpa_auth, int vlan_id);
+int fils_auth_pmk_to_ptk(struct wpa_state_machine *sm, const u8 *pmk,
+			 size_t pmk_len, const u8 *snonce, const u8 *anonce,
+			 const u8 *dhss, size_t dhss_len,
+			 struct wpabuf *g_sta, struct wpabuf *g_ap);
+int fils_decrypt_assoc(struct wpa_state_machine *sm, const u8 *fils_session,
+		       const struct ieee80211_mgmt *mgmt, size_t frame_len,
+		       u8 *pos, size_t left);
+int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf,
+		       size_t current_len, size_t max_len,
+		       const struct wpabuf *hlp);
+int fils_set_tk(struct wpa_state_machine *sm);
+u8 * hostapd_eid_assoc_fils_session(struct wpa_state_machine *sm, u8 *eid,
+				    const u8 *fils_session,
+				    struct wpabuf *fils_hlp_resp);
+const u8 *  wpa_fils_validate_fils_session(struct wpa_state_machine *sm,
+					   const u8 *ies, size_t ies_len,
+					   const u8 *fils_session);
+int wpa_fils_validate_key_confirm(struct wpa_state_machine *sm, const u8 *ies,
+				  size_t ies_len);
+
+int wpa_auth_write_fte(struct wpa_authenticator *wpa_auth, u8 *buf, size_t len);
+void wpa_auth_get_fils_aead_params(struct wpa_state_machine *sm,
+				   u8 *fils_anonce, u8 *fils_snonce,
+				   u8 *fils_kek, size_t *fils_kek_len);
+u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm,
+				   u8 *pos, size_t max_len,
+				   const u8 *req_ies, size_t req_ies_len);
+
+int wpa_auth_resend_m1(struct wpa_state_machine *sm, int change_anonce,
+		       void (*cb)(void *ctx1, void *ctx2),
+		       void *ctx1, void *ctx2);
+int wpa_auth_resend_m3(struct wpa_state_machine *sm,
+		       void (*cb)(void *ctx1, void *ctx2),
+		       void *ctx1, void *ctx2);
+int wpa_auth_resend_group_m1(struct wpa_state_machine *sm,
+			     void (*cb)(void *ctx1, void *ctx2),
+			     void *ctx1, void *ctx2);
+int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth);
 
 #ifdef KRACK_TEST_CLIENT
 #define TEST_TPTK_NONE		0

File diff suppressed because it is too large
+ 1207 - 51
src/ap/wpa_auth_ft.c


+ 409 - 56
src/ap/wpa_auth_glue.c

@@ -9,6 +9,8 @@
 #include "utils/includes.h"
 
 #include "utils/common.h"
+#include "utils/eloop.h"
+#include "utils/list.h"
 #include "common/ieee802_11_defs.h"
 #include "common/sae.h"
 #include "common/wpa_ctrl.h"
@@ -17,6 +19,7 @@
 #include "eapol_auth/eapol_auth_sm_i.h"
 #include "eap_server/eap.h"
 #include "l2_packet/l2_packet.h"
+#include "eth_p_oui.h"
 #include "hostapd.h"
 #include "ieee802_1x.h"
 #include "preauth_auth.h"
@@ -24,6 +27,7 @@
 #include "tkip_countermeasures.h"
 #include "ap_drv_ops.h"
 #include "ap_config.h"
+#include "pmksa_cache_auth.h"
 #include "wpa_auth.h"
 #include "wpa_auth_glue.h"
 
@@ -41,10 +45,13 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 	wconf->wpa_strict_rekey = conf->wpa_strict_rekey;
 	wconf->wpa_gmk_rekey = conf->wpa_gmk_rekey;
 	wconf->wpa_ptk_rekey = conf->wpa_ptk_rekey;
+	wconf->wpa_group_update_count = conf->wpa_group_update_count;
+	wconf->wpa_disable_eapol_key_retries =
+		conf->wpa_disable_eapol_key_retries;
+	wconf->wpa_pairwise_update_count = conf->wpa_pairwise_update_count;
 	wconf->rsn_pairwise = conf->rsn_pairwise;
 	wconf->rsn_preauth = conf->rsn_preauth;
 	wconf->eapol_version = conf->eapol_version;
-	wconf->peerkey = conf->peerkey;
 	wconf->wmm_enabled = conf->wmm_enabled;
 	wconf->wmm_uapsd = conf->wmm_uapsd;
 	wconf->disable_pmksa_caching = conf->disable_pmksa_caching;
@@ -52,8 +59,9 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 #ifdef CONFIG_IEEE80211W
 	wconf->ieee80211w = conf->ieee80211w;
 	wconf->group_mgmt_cipher = conf->group_mgmt_cipher;
+	wconf->sae_require_mfp = conf->sae_require_mfp;
 #endif /* CONFIG_IEEE80211W */
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	wconf->ssid_len = conf->ssid.ssid_len;
 	if (wconf->ssid_len > SSID_MAX_LEN)
 		wconf->ssid_len = SSID_MAX_LEN;
@@ -69,11 +77,16 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 	os_memcpy(wconf->r1_key_holder, conf->r1_key_holder, FT_R1KH_ID_LEN);
 	wconf->r0_key_lifetime = conf->r0_key_lifetime;
 	wconf->reassociation_deadline = conf->reassociation_deadline;
-	wconf->r0kh_list = conf->r0kh_list;
-	wconf->r1kh_list = conf->r1kh_list;
+	wconf->rkh_pos_timeout = conf->rkh_pos_timeout;
+	wconf->rkh_neg_timeout = conf->rkh_neg_timeout;
+	wconf->rkh_pull_timeout = conf->rkh_pull_timeout;
+	wconf->rkh_pull_retries = conf->rkh_pull_retries;
+	wconf->r0kh_list = &conf->r0kh_list;
+	wconf->r1kh_list = &conf->r1kh_list;
 	wconf->pmk_r1_push = conf->pmk_r1_push;
 	wconf->ft_over_ds = conf->ft_over_ds;
-#endif /* CONFIG_IEEE80211R */
+	wconf->ft_psk_generate_local = conf->ft_psk_generate_local;
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_HS20
 	wconf->disable_gtk = conf->disable_dgaf;
 	if (conf->osen) {
@@ -107,6 +120,11 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 	os_memcpy(wconf->ip_addr_start, conf->ip_addr_start, 4);
 	os_memcpy(wconf->ip_addr_end, conf->ip_addr_end, 4);
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_FILS
+	wconf->fils_cache_id_set = conf->fils_cache_id_set;
+	os_memcpy(wconf->fils_cache_id, conf->fils_cache_id,
+		  FILS_CACHE_ID_LEN);
+#endif /* CONFIG_FILS */
 }
 
 
@@ -223,20 +241,47 @@ static int hostapd_wpa_auth_get_eapol(void *ctx, const u8 *addr,
 
 static const u8 * hostapd_wpa_auth_get_psk(void *ctx, const u8 *addr,
 					   const u8 *p2p_dev_addr,
-					   const u8 *prev_psk)
+					   const u8 *prev_psk, size_t *psk_len)
 {
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta = ap_get_sta(hapd, addr);
 	const u8 *psk;
 
+	if (psk_len)
+		*psk_len = PMK_LEN;
+
 #ifdef CONFIG_SAE
 	if (sta && sta->auth_alg == WLAN_AUTH_SAE) {
 		if (!sta->sae || prev_psk)
 			return NULL;
 		return sta->sae->pmk;
 	}
+	if (sta && wpa_auth_uses_sae(sta->wpa_sm)) {
+		wpa_printf(MSG_DEBUG,
+			   "No PSK for STA trying to use SAE with PMKSA caching");
+		return NULL;
+	}
 #endif /* CONFIG_SAE */
 
+#ifdef CONFIG_OWE
+	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) &&
+	    sta && sta->owe_pmk) {
+		if (psk_len)
+			*psk_len = sta->owe_pmk_len;
+		return sta->owe_pmk;
+	}
+	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && sta) {
+		struct rsn_pmksa_cache_entry *sa;
+
+		sa = wpa_auth_sta_get_pmksa(sta->wpa_sm);
+		if (sa && sa->akmp == WPA_KEY_MGMT_OWE) {
+			if (psk_len)
+				*psk_len = sa->pmk_len;
+			return sa->pmk;
+		}
+	}
+#endif /* CONFIG_OWE */
+
 	psk = hostapd_get_psk(hapd->conf, addr, p2p_dev_addr, prev_psk);
 	/*
 	 * This is about to iterate over all psks, prev_psk gives the last
@@ -307,6 +352,37 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
 			return -1;
 	}
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (addr && !is_broadcast_ether_addr(addr)) {
+		struct sta_info *sta;
+
+		sta = ap_get_sta(hapd, addr);
+		if (sta) {
+			sta->last_tk_alg = alg;
+			sta->last_tk_key_idx = idx;
+			if (key)
+				os_memcpy(sta->last_tk, key, key_len);
+			sta->last_tk_len = key_len;
+		}
+#ifdef CONFIG_IEEE80211W
+	} else if (alg == WPA_ALG_IGTK ||
+		   alg == WPA_ALG_BIP_GMAC_128 ||
+		   alg == WPA_ALG_BIP_GMAC_256 ||
+		   alg == WPA_ALG_BIP_CMAC_256) {
+		hapd->last_igtk_alg = alg;
+		hapd->last_igtk_key_idx = idx;
+		if (key)
+			os_memcpy(hapd->last_igtk, key, key_len);
+		hapd->last_igtk_len = key_len;
+#endif /* CONFIG_IEEE80211W */
+	} else {
+		hapd->last_gtk_alg = alg;
+		hapd->last_gtk_key_idx = idx;
+		if (key)
+			os_memcpy(hapd->last_gtk, key, key_len);
+		hapd->last_gtk_len = key_len;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
 	return hostapd_drv_set_key(ifname, hapd, alg, addr, idx, 1, NULL, 0,
 				   key, key_len);
 }
@@ -401,7 +477,32 @@ static int hostapd_wpa_auth_for_each_auth(
 }
 
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
+
+struct wpa_ft_rrb_rx_later_data {
+	struct dl_list list;
+	u8 addr[ETH_ALEN];
+	size_t data_len;
+	/* followed by data_len octets of data */
+};
+
+static void hostapd_wpa_ft_rrb_rx_later(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct wpa_ft_rrb_rx_later_data *data, *n;
+
+	dl_list_for_each_safe(data, n, &hapd->l2_queue,
+			      struct wpa_ft_rrb_rx_later_data, list) {
+		if (hapd->wpa_auth) {
+			wpa_ft_rrb_rx(hapd->wpa_auth, data->addr,
+				      (const u8 *) (data + 1),
+				      data->data_len);
+		}
+		dl_list_del(&data->list);
+		os_free(data);
+	}
+}
+
 
 struct wpa_auth_ft_iface_iter_data {
 	struct hostapd_data *src_hapd;
@@ -414,33 +515,54 @@ struct wpa_auth_ft_iface_iter_data {
 static int hostapd_wpa_auth_ft_iter(struct hostapd_iface *iface, void *ctx)
 {
 	struct wpa_auth_ft_iface_iter_data *idata = ctx;
+	struct wpa_ft_rrb_rx_later_data *data;
 	struct hostapd_data *hapd;
 	size_t j;
 
 	for (j = 0; j < iface->num_bss; j++) {
 		hapd = iface->bss[j];
-		if (hapd == idata->src_hapd)
-			continue;
-		if (!hapd->wpa_auth)
+		if (hapd == idata->src_hapd ||
+		    !hapd->wpa_auth ||
+		    os_memcmp(hapd->own_addr, idata->dst, ETH_ALEN) != 0)
 			continue;
-		if (os_memcmp(hapd->own_addr, idata->dst, ETH_ALEN) == 0) {
-			wpa_printf(MSG_DEBUG, "FT: Send RRB data directly to "
-				   "locally managed BSS " MACSTR "@%s -> "
-				   MACSTR "@%s",
-				   MAC2STR(idata->src_hapd->own_addr),
-				   idata->src_hapd->conf->iface,
-				   MAC2STR(hapd->own_addr), hapd->conf->iface);
-			wpa_ft_rrb_rx(hapd->wpa_auth,
-				      idata->src_hapd->own_addr,
-				      idata->data, idata->data_len);
+
+		wpa_printf(MSG_DEBUG,
+			   "FT: Send RRB data directly to locally managed BSS "
+			   MACSTR "@%s -> " MACSTR "@%s",
+			   MAC2STR(idata->src_hapd->own_addr),
+			   idata->src_hapd->conf->iface,
+			   MAC2STR(hapd->own_addr), hapd->conf->iface);
+
+		/* Defer wpa_ft_rrb_rx() until next eloop step as this is
+		 * when it would be triggered when reading from a socket.
+		 * This avoids
+		 * hapd0:send -> hapd1:recv -> hapd1:send -> hapd0:recv,
+		 * that is calling hapd0:recv handler from within
+		 * hapd0:send directly.
+		 */
+		data = os_zalloc(sizeof(*data) + idata->data_len);
+		if (!data)
 			return 1;
-		}
+
+		os_memcpy(data->addr, idata->src_hapd->own_addr, ETH_ALEN);
+		os_memcpy(data + 1, idata->data, idata->data_len);
+		data->data_len = idata->data_len;
+
+		dl_list_add(&hapd->l2_queue, &data->list);
+
+		if (!eloop_is_timeout_registered(hostapd_wpa_ft_rrb_rx_later,
+						 hapd, NULL))
+			eloop_register_timeout(0, 0,
+					       hostapd_wpa_ft_rrb_rx_later,
+					       hapd, NULL);
+
+		return 1;
 	}
 
 	return 0;
 }
 
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 
 static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto,
@@ -465,7 +587,7 @@ static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto,
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (proto == ETH_P_RRB && hapd->iface->interfaces &&
 	    hapd->iface->interfaces->for_each_interface) {
 		int res;
@@ -480,7 +602,7 @@ static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto,
 		if (res == 1)
 			return data_len;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 	if (hapd->driver && hapd->driver->send_ether)
 		return hapd->driver->send_ether(hapd->drv_priv, dst,
@@ -503,7 +625,157 @@ static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto,
 }
 
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_ETH_P_OUI
+static struct eth_p_oui_ctx * hostapd_wpa_get_oui(struct hostapd_data *hapd,
+						  u8 oui_suffix)
+{
+	switch (oui_suffix) {
+#ifdef CONFIG_IEEE80211R_AP
+	case FT_PACKET_R0KH_R1KH_PULL:
+		return hapd->oui_pull;
+	case FT_PACKET_R0KH_R1KH_RESP:
+		return hapd->oui_resp;
+	case FT_PACKET_R0KH_R1KH_PUSH:
+		return hapd->oui_push;
+	case FT_PACKET_R0KH_R1KH_SEQ_REQ:
+		return hapd->oui_sreq;
+	case FT_PACKET_R0KH_R1KH_SEQ_RESP:
+		return hapd->oui_sresp;
+#endif /* CONFIG_IEEE80211R_AP */
+	default:
+		return NULL;
+	}
+}
+#endif /* CONFIG_ETH_P_OUI */
+
+
+#ifdef CONFIG_IEEE80211R_AP
+
+struct oui_deliver_later_data {
+	struct dl_list list;
+	u8 src_addr[ETH_ALEN];
+	u8 dst_addr[ETH_ALEN];
+	size_t data_len;
+	u8 oui_suffix;
+	/* followed by data_len octets of data */
+};
+
+static void hostapd_oui_deliver_later(void *eloop_ctx, void *timeout_ctx)
+{
+	struct hostapd_data *hapd = eloop_ctx;
+	struct oui_deliver_later_data *data, *n;
+	struct eth_p_oui_ctx *oui_ctx;
+
+	dl_list_for_each_safe(data, n, &hapd->l2_oui_queue,
+			      struct oui_deliver_later_data, list) {
+		oui_ctx = hostapd_wpa_get_oui(hapd, data->oui_suffix);
+		if (hapd->wpa_auth && oui_ctx) {
+			eth_p_oui_deliver(oui_ctx, data->src_addr,
+					  data->dst_addr,
+					  (const u8 *) (data + 1),
+					  data->data_len);
+		}
+		dl_list_del(&data->list);
+		os_free(data);
+	}
+}
+
+
+struct wpa_auth_oui_iface_iter_data {
+	struct hostapd_data *src_hapd;
+	const u8 *dst_addr;
+	const u8 *data;
+	size_t data_len;
+	u8 oui_suffix;
+};
+
+static int hostapd_wpa_auth_oui_iter(struct hostapd_iface *iface, void *ctx)
+{
+	struct wpa_auth_oui_iface_iter_data *idata = ctx;
+	struct oui_deliver_later_data *data;
+	struct hostapd_data *hapd;
+	size_t j;
+
+	for (j = 0; j < iface->num_bss; j++) {
+		hapd = iface->bss[j];
+		if (hapd == idata->src_hapd)
+			continue;
+		if (!is_multicast_ether_addr(idata->dst_addr) &&
+		    os_memcmp(hapd->own_addr, idata->dst_addr, ETH_ALEN) != 0)
+			continue;
+
+		/* defer eth_p_oui_deliver until next eloop step as this is
+		 * when it would be triggerd from reading from sock
+		 * This avoids
+		 * hapd0:send -> hapd1:recv -> hapd1:send -> hapd0:recv,
+		 * that is calling hapd0:recv handler from within
+		 * hapd0:send directly.
+		 */
+		data = os_zalloc(sizeof(*data) + idata->data_len);
+		if (!data)
+			return 1;
+
+		os_memcpy(data->src_addr, idata->src_hapd->own_addr, ETH_ALEN);
+		os_memcpy(data->dst_addr, idata->dst_addr, ETH_ALEN);
+		os_memcpy(data + 1, idata->data, idata->data_len);
+		data->data_len = idata->data_len;
+		data->oui_suffix = idata->oui_suffix;
+
+		dl_list_add(&hapd->l2_oui_queue, &data->list);
+
+		if (!eloop_is_timeout_registered(hostapd_oui_deliver_later,
+						 hapd, NULL))
+			eloop_register_timeout(0, 0,
+					       hostapd_oui_deliver_later,
+					       hapd, NULL);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
+static int hostapd_wpa_auth_send_oui(void *ctx, const u8 *dst, u8 oui_suffix,
+				     const u8 *data, size_t data_len)
+{
+#ifdef CONFIG_ETH_P_OUI
+	struct hostapd_data *hapd = ctx;
+	struct eth_p_oui_ctx *oui_ctx;
+
+#ifdef CONFIG_IEEE80211R_AP
+	if (hapd->iface->interfaces &&
+	    hapd->iface->interfaces->for_each_interface) {
+		struct wpa_auth_oui_iface_iter_data idata;
+		int res;
+
+		idata.src_hapd = hapd;
+		idata.dst_addr = dst;
+		idata.data = data;
+		idata.data_len = data_len;
+		idata.oui_suffix = oui_suffix;
+		res = hapd->iface->interfaces->for_each_interface(
+			hapd->iface->interfaces, hostapd_wpa_auth_oui_iter,
+			&idata);
+		if (res == 1)
+			return data_len;
+	}
+#endif /* CONFIG_IEEE80211R_AP */
+
+	oui_ctx = hostapd_wpa_get_oui(hapd, oui_suffix);
+	if (!oui_ctx)
+		return -1;
+
+	return eth_p_oui_send(oui_ctx, hapd->own_addr, dst, data, data_len);
+#else /* CONFIG_ETH_P_OUI */
+	return -1;
+#endif /* CONFIG_ETH_P_OUI */
+}
+
+
+#ifdef CONFIG_IEEE80211R_AP
 
 static int hostapd_wpa_auth_send_ft_action(void *ctx, const u8 *dst,
 					   const u8 *data, size_t data_len)
@@ -581,6 +853,22 @@ static void hostapd_rrb_receive(void *ctx, const u8 *src_addr, const u8 *buf,
 }
 
 
+static void hostapd_rrb_oui_receive(void *ctx, const u8 *src_addr,
+				    const u8 *dst_addr, u8 oui_suffix,
+				    const u8 *buf, size_t len)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
+		   MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr));
+	if (!is_multicast_ether_addr(dst_addr) &&
+	    os_memcmp(hapd->own_addr, dst_addr, ETH_ALEN) != 0)
+		return;
+	wpa_ft_rrb_oui_rx(hapd->wpa_auth, src_addr, dst_addr, oui_suffix, buf,
+			  len);
+}
+
+
 static int hostapd_wpa_auth_add_tspec(void *ctx, const u8 *sta_addr,
 				      u8 *tspec_ie, size_t tspec_ielen)
 {
@@ -588,13 +876,86 @@ static int hostapd_wpa_auth_add_tspec(void *ctx, const u8 *sta_addr,
 	return hostapd_add_tspec(hapd, sta_addr, tspec_ie, tspec_ielen);
 }
 
-#endif /* CONFIG_IEEE80211R */
+
+
+static int hostapd_wpa_register_ft_oui(struct hostapd_data *hapd,
+				       const char *ft_iface)
+{
+	hapd->oui_pull = eth_p_oui_register(hapd, ft_iface,
+					    FT_PACKET_R0KH_R1KH_PULL,
+					    hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_pull)
+		return -1;
+
+	hapd->oui_resp = eth_p_oui_register(hapd, ft_iface,
+					    FT_PACKET_R0KH_R1KH_RESP,
+					    hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_resp)
+		return -1;
+
+	hapd->oui_push = eth_p_oui_register(hapd, ft_iface,
+					    FT_PACKET_R0KH_R1KH_PUSH,
+					    hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_push)
+		return -1;
+
+	hapd->oui_sreq = eth_p_oui_register(hapd, ft_iface,
+					    FT_PACKET_R0KH_R1KH_SEQ_REQ,
+					    hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_sreq)
+		return -1;
+
+	hapd->oui_sresp = eth_p_oui_register(hapd, ft_iface,
+					     FT_PACKET_R0KH_R1KH_SEQ_RESP,
+					     hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_sresp)
+		return -1;
+
+	return 0;
+}
+
+
+static void hostapd_wpa_unregister_ft_oui(struct hostapd_data *hapd)
+{
+	eth_p_oui_unregister(hapd->oui_pull);
+	hapd->oui_pull = NULL;
+	eth_p_oui_unregister(hapd->oui_resp);
+	hapd->oui_resp = NULL;
+	eth_p_oui_unregister(hapd->oui_push);
+	hapd->oui_push = NULL;
+	eth_p_oui_unregister(hapd->oui_sreq);
+	hapd->oui_sreq = NULL;
+	eth_p_oui_unregister(hapd->oui_sresp);
+	hapd->oui_sresp = NULL;
+}
+#endif /* CONFIG_IEEE80211R_AP */
 
 
 int hostapd_setup_wpa(struct hostapd_data *hapd)
 {
 	struct wpa_auth_config _conf;
-	struct wpa_auth_callbacks cb;
+	static const struct wpa_auth_callbacks cb = {
+		.logger = hostapd_wpa_auth_logger,
+		.disconnect = hostapd_wpa_auth_disconnect,
+		.mic_failure_report = hostapd_wpa_auth_mic_failure_report,
+		.psk_failure_report = hostapd_wpa_auth_psk_failure_report,
+		.set_eapol = hostapd_wpa_auth_set_eapol,
+		.get_eapol = hostapd_wpa_auth_get_eapol,
+		.get_psk = hostapd_wpa_auth_get_psk,
+		.get_msk = hostapd_wpa_auth_get_msk,
+		.set_key = hostapd_wpa_auth_set_key,
+		.get_seqnum = hostapd_wpa_auth_get_seqnum,
+		.send_eapol = hostapd_wpa_auth_send_eapol,
+		.for_each_sta = hostapd_wpa_auth_for_each_sta,
+		.for_each_auth = hostapd_wpa_auth_for_each_auth,
+		.send_ether = hostapd_wpa_auth_send_ether,
+		.send_oui = hostapd_wpa_auth_send_oui,
+#ifdef CONFIG_IEEE80211R_AP
+		.send_ft_action = hostapd_wpa_auth_send_ft_action,
+		.add_sta = hostapd_wpa_auth_add_sta,
+		.add_tspec = hostapd_wpa_auth_add_tspec,
+#endif /* CONFIG_IEEE80211R_AP */
+	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
 
@@ -603,28 +964,7 @@ int hostapd_setup_wpa(struct hostapd_data *hapd)
 		_conf.tx_status = 1;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_MLME)
 		_conf.ap_mlme = 1;
-	os_memset(&cb, 0, sizeof(cb));
-	cb.ctx = hapd;
-	cb.logger = hostapd_wpa_auth_logger;
-	cb.disconnect = hostapd_wpa_auth_disconnect;
-	cb.mic_failure_report = hostapd_wpa_auth_mic_failure_report;
-	cb.psk_failure_report = hostapd_wpa_auth_psk_failure_report;
-	cb.set_eapol = hostapd_wpa_auth_set_eapol;
-	cb.get_eapol = hostapd_wpa_auth_get_eapol;
-	cb.get_psk = hostapd_wpa_auth_get_psk;
-	cb.get_msk = hostapd_wpa_auth_get_msk;
-	cb.set_key = hostapd_wpa_auth_set_key;
-	cb.get_seqnum = hostapd_wpa_auth_get_seqnum;
-	cb.send_eapol = hostapd_wpa_auth_send_eapol;
-	cb.for_each_sta = hostapd_wpa_auth_for_each_sta;
-	cb.for_each_auth = hostapd_wpa_auth_for_each_auth;
-	cb.send_ether = hostapd_wpa_auth_send_ether;
-#ifdef CONFIG_IEEE80211R
-	cb.send_ft_action = hostapd_wpa_auth_send_ft_action;
-	cb.add_sta = hostapd_wpa_auth_add_sta;
-	cb.add_tspec = hostapd_wpa_auth_add_tspec;
-#endif /* CONFIG_IEEE80211R */
-	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb);
+	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb, hapd);
 	if (hapd->wpa_auth == NULL) {
 		wpa_printf(MSG_ERROR, "WPA initialization failed.");
 		return -1;
@@ -649,12 +989,14 @@ int hostapd_setup_wpa(struct hostapd_data *hapd)
 		return -1;
 	}
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (!hostapd_drv_none(hapd) &&
 	    wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt)) {
-		hapd->l2 = l2_packet_init(hapd->conf->bridge[0] ?
-					  hapd->conf->bridge :
-					  hapd->conf->iface, NULL, ETH_P_RRB,
+		const char *ft_iface;
+
+		ft_iface = hapd->conf->bridge[0] ? hapd->conf->bridge :
+			   hapd->conf->iface;
+		hapd->l2 = l2_packet_init(ft_iface, NULL, ETH_P_RRB,
 					  hostapd_rrb_receive, hapd, 1);
 		if (hapd->l2 == NULL &&
 		    (hapd->driver == NULL ||
@@ -663,8 +1005,14 @@ int hostapd_setup_wpa(struct hostapd_data *hapd)
 				   "interface");
 			return -1;
 		}
+
+		if (hostapd_wpa_register_ft_oui(hapd, ft_iface)) {
+			wpa_printf(MSG_ERROR,
+				   "Failed to open ETH_P_OUI interface");
+			return -1;
+		}
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 
 	return 0;
 
@@ -702,8 +1050,13 @@ void hostapd_deinit_wpa(struct hostapd_data *hapd)
 	}
 	ieee802_1x_deinit(hapd);
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
+	eloop_cancel_timeout(hostapd_wpa_ft_rrb_rx_later, hapd, ELOOP_ALL_CTX);
+	hostapd_wpa_ft_rrb_rx_later(hapd, NULL); /* flush without delivering */
+	eloop_cancel_timeout(hostapd_oui_deliver_later, hapd, ELOOP_ALL_CTX);
+	hostapd_oui_deliver_later(hapd, NULL); /* flush without delivering */
 	l2_packet_deinit(hapd->l2);
 	hapd->l2 = NULL;
-#endif /* CONFIG_IEEE80211R */
+	hostapd_wpa_unregister_ft_oui(hapd);
+#endif /* CONFIG_IEEE80211R_AP */
 }

+ 66 - 33
src/ap/wpa_auth_i.h

@@ -9,18 +9,13 @@
 #ifndef WPA_AUTH_I_H
 #define WPA_AUTH_I_H
 
+#include "utils/list.h"
+
 /* max(dot11RSNAConfigGroupUpdateCount,dot11RSNAConfigPairwiseUpdateCount) */
 #define RSNA_MAX_EAPOL_RETRIES 4
 
 struct wpa_group;
 
-struct wpa_stsl_negotiation {
-	struct wpa_stsl_negotiation *next;
-	u8 initiator[ETH_ALEN];
-	u8 peer[ETH_ALEN];
-};
-
-
 struct wpa_state_machine {
 	struct wpa_authenticator *wpa_auth;
 	struct wpa_group *group;
@@ -48,8 +43,9 @@ struct wpa_state_machine {
 	Boolean AuthenticationRequest;
 	Boolean ReAuthenticationRequest;
 	Boolean Disconnect;
-	int TimeoutCtr;
-	int GTimeoutCtr;
+	u16 disconnect_reason; /* specific reason code to use with Disconnect */
+	u32 TimeoutCtr;
+	u32 GTimeoutCtr;
 	Boolean TimeoutEvt;
 	Boolean EAPOLKeyReceived;
 	Boolean EAPOLKeyPairwise;
@@ -62,9 +58,11 @@ struct wpa_state_machine {
 	u8 alt_replay_counter[WPA_REPLAY_COUNTER_LEN];
 	u8 PMK[PMK_LEN_MAX];
 	unsigned int pmk_len;
+	u8 pmkid[PMKID_LEN]; /* valid if pmkid_set == 1 */
 	struct wpa_ptk PTK;
 	Boolean PTK_valid;
 	Boolean pairwise_set;
+	Boolean tk_already_set;
 	int keycount;
 	Boolean Pair;
 	struct wpa_key_replay_counter {
@@ -88,11 +86,12 @@ struct wpa_state_machine {
 	unsigned int rx_eapol_key_secure:1;
 	unsigned int update_snonce:1;
 	unsigned int alt_snonce_valid:1;
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	unsigned int ft_completed:1;
 	unsigned int pmk_r1_name_valid:1;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 	unsigned int is_wnmsleep:1;
+	unsigned int pmkid_set:1;
 
 	u8 req_replay_counter[WPA_REPLAY_COUNTER_LEN];
 	int req_replay_counter_used;
@@ -112,7 +111,7 @@ struct wpa_state_machine {
 	u32 dot11RSNAStatsTKIPLocalMICFailures;
 	u32 dot11RSNAStatsTKIPRemoteMICFailures;
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	u8 xxkey[PMK_LEN]; /* PSK or the second 256 bits of MSK */
 	size_t xxkey_len;
 	u8 pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name derived from FT Auth
@@ -128,16 +127,30 @@ struct wpa_state_machine {
 			      const u8 *ies, size_t ies_len);
 	void *ft_pending_cb_ctx;
 	struct wpabuf *ft_pending_req_ies;
-	u8 ft_pending_pull_nonce[FT_R0KH_R1KH_PULL_NONCE_LEN];
+	u8 ft_pending_pull_nonce[FT_RRB_NONCE_LEN];
 	u8 ft_pending_auth_transaction;
 	u8 ft_pending_current_ap[ETH_ALEN];
-#endif /* CONFIG_IEEE80211R */
+	int ft_pending_pull_left_retries;
+#endif /* CONFIG_IEEE80211R_AP */
 
 	int pending_1_of_4_timeout;
 
 #ifdef CONFIG_P2P
 	u8 ip_addr[4];
 #endif /* CONFIG_P2P */
+
+#ifdef CONFIG_FILS
+	u8 fils_key_auth_sta[FILS_MAX_KEY_AUTH_LEN];
+	u8 fils_key_auth_ap[FILS_MAX_KEY_AUTH_LEN];
+	size_t fils_key_auth_len;
+	unsigned int fils_completed:1;
+#endif /* CONFIG_FILS */
+
+#ifdef CONFIG_TESTING_OPTIONS
+	void (*eapol_status_cb)(void *ctx1, void *ctx2);
+	void *eapol_status_cb_ctx1;
+	void *eapol_status_cb_ctx2;
+#endif /* CONFIG_TESTING_OPTIONS */
 };
 
 
@@ -193,10 +206,9 @@ struct wpa_authenticator {
 	unsigned int dot11RSNATKIPCounterMeasuresInvoked;
 	unsigned int dot11RSNA4WayHandshakeFailures;
 
-	struct wpa_stsl_negotiation *stsl_negotiations;
-
 	struct wpa_auth_config conf;
-	struct wpa_auth_callbacks cb;
+	const struct wpa_auth_callbacks *cb;
+	void *cb_ctx;
 
 	u8 *wpa_ie;
 	size_t wpa_ie_len;
@@ -212,6 +224,38 @@ struct wpa_authenticator {
 };
 
 
+#ifdef CONFIG_IEEE80211R_AP
+
+#define FT_REMOTE_SEQ_BACKLOG 16
+struct ft_remote_seq_rx {
+	u32 dom;
+	struct os_reltime time_offset; /* local time - offset = remote time */
+
+	/* accepted sequence numbers: (offset ... offset + 0x40000000]
+	 *   (except those in last)
+	 * dropped sequence numbers: (offset - 0x40000000 ... offset]
+	 * all others trigger SEQ_REQ message (except first message)
+	 */
+	u32 last[FT_REMOTE_SEQ_BACKLOG];
+	unsigned int num_last;
+	u32 offsetidx;
+
+	struct dl_list queue; /* send nonces + rrb msgs awaiting seq resp */
+};
+
+struct ft_remote_seq_tx {
+	u32 dom; /* non zero if initialized */
+	u32 seq;
+};
+
+struct ft_remote_seq {
+	struct ft_remote_seq_rx rx;
+	struct ft_remote_seq_tx tx;
+};
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		     const u8 *pmkid);
 void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr,
@@ -230,21 +274,7 @@ int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth,
 			   int (*cb)(struct wpa_authenticator *a, void *ctx),
 			   void *cb_ctx);
 
-#ifdef CONFIG_PEERKEY
-int wpa_stsl_remove(struct wpa_authenticator *wpa_auth,
-		    struct wpa_stsl_negotiation *neg);
-void wpa_smk_error(struct wpa_authenticator *wpa_auth,
-		   struct wpa_state_machine *sm,
-		   const u8 *key_data, size_t key_data_len);
-void wpa_smk_m1(struct wpa_authenticator *wpa_auth,
-		struct wpa_state_machine *sm, struct wpa_eapol_key *key,
-		const u8 *key_data, size_t key_data_len);
-void wpa_smk_m3(struct wpa_authenticator *wpa_auth,
-		struct wpa_state_machine *sm, struct wpa_eapol_key *key,
-		const u8 *key_data, size_t key_data_len);
-#endif /* CONFIG_PEERKEY */
-
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len);
 int wpa_write_ftie(struct wpa_auth_config *conf, const u8 *r0kh_id,
 		   size_t r0kh_id_len,
@@ -256,6 +286,9 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void);
 void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache);
 void wpa_ft_install_ptk(struct wpa_state_machine *sm);
-#endif /* CONFIG_IEEE80211R */
+int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
+			const u8 *spa, const u8 *pmk_r0,
+			const u8 *pmk_r0_name, int pairwise);
+#endif /* CONFIG_IEEE80211R_AP */
 
 #endif /* WPA_AUTH_I_H */

+ 171 - 51
src/ap/wpa_auth_ie.c

@@ -164,7 +164,7 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		pos += RSN_SELECTOR_LEN;
 		num_suites++;
 	}
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) {
 		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_802_1X);
 		pos += RSN_SELECTOR_LEN;
@@ -175,7 +175,7 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		pos += RSN_SELECTOR_LEN;
 		num_suites++;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) {
 		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA256);
@@ -210,6 +210,44 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		pos += RSN_SELECTOR_LEN;
 		num_suites++;
 	}
+#ifdef CONFIG_FILS
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FILS_SHA256) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FILS_SHA256);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FILS_SHA384) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FILS_SHA384);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#ifdef CONFIG_IEEE80211R_AP
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_FILS_SHA256);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_FILS_SHA384);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#endif /* CONFIG_IEEE80211R_AP */
+#endif /* CONFIG_FILS */
+#ifdef CONFIG_OWE
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OWE);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#endif /* CONFIG_OWE */
+#ifdef CONFIG_DPP
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_DPP);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#endif /* CONFIG_DPP */
 
 #ifdef CONFIG_RSN_TESTING
 	if (rsn_testing) {
@@ -230,8 +268,6 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 	capab = 0;
 	if (conf->rsn_preauth)
 		capab |= WPA_CAPABILITY_PREAUTH;
-	if (conf->peerkey)
-		capab |= WPA_CAPABILITY_PEERKEY_ENABLED;
 	if (conf->wmm_enabled) {
 		/* 4 PTKSA replay counters when using WMM */
 		capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2);
@@ -407,7 +443,7 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth)
 			return res;
 		pos += res;
 	}
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(wpa_auth->conf.wpa_key_mgmt)) {
 		res = wpa_write_mdie(&wpa_auth->conf, pos,
 				     buf + sizeof(buf) - pos);
@@ -415,7 +451,7 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth)
 			return res;
 		pos += res;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 	if (wpa_auth->conf.wpa & WPA_PROTO_WPA) {
 		res = wpa_write_wpa_ie(&wpa_auth->conf,
 				       pos, buf + sizeof(buf) - pos);
@@ -474,7 +510,8 @@ static int wpa_auth_okc_iter(struct wpa_authenticator *a, void *ctx)
 int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			struct wpa_state_machine *sm,
 			const u8 *wpa_ie, size_t wpa_ie_len,
-			const u8 *mdie, size_t mdie_len)
+			const u8 *mdie, size_t mdie_len,
+			const u8 *owe_dh, size_t owe_dh_len)
 {
 	struct wpa_ie_data data;
 	int ciphers, key_mgmt, res, version;
@@ -509,12 +546,24 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			selector = RSN_AUTH_KEY_MGMT_802_1X_SUITE_B_192;
 		else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B)
 			selector = RSN_AUTH_KEY_MGMT_802_1X_SUITE_B;
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_FILS
+#ifdef CONFIG_IEEE80211R_AP
+		else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384)
+			selector = RSN_AUTH_KEY_MGMT_FT_FILS_SHA384;
+		else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256)
+			selector = RSN_AUTH_KEY_MGMT_FT_FILS_SHA256;
+#endif /* CONFIG_IEEE80211R_AP */
+		else if (data.key_mgmt & WPA_KEY_MGMT_FILS_SHA384)
+			selector = RSN_AUTH_KEY_MGMT_FILS_SHA384;
+		else if (data.key_mgmt & WPA_KEY_MGMT_FILS_SHA256)
+			selector = RSN_AUTH_KEY_MGMT_FILS_SHA256;
+#endif /* CONFIG_FILS */
+#ifdef CONFIG_IEEE80211R_AP
 		else if (data.key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X)
 			selector = RSN_AUTH_KEY_MGMT_FT_802_1X;
 		else if (data.key_mgmt & WPA_KEY_MGMT_FT_PSK)
 			selector = RSN_AUTH_KEY_MGMT_FT_PSK;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 		else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256)
 			selector = RSN_AUTH_KEY_MGMT_802_1X_SHA256;
@@ -531,6 +580,14 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			selector = RSN_AUTH_KEY_MGMT_UNSPEC_802_1X;
 		else if (data.key_mgmt & WPA_KEY_MGMT_PSK)
 			selector = RSN_AUTH_KEY_MGMT_PSK_OVER_802_1X;
+#ifdef CONFIG_OWE
+		else if (data.key_mgmt & WPA_KEY_MGMT_OWE)
+			selector = RSN_AUTH_KEY_MGMT_OWE;
+#endif /* CONFIG_OWE */
+#ifdef CONFIG_DPP
+		else if (data.key_mgmt & WPA_KEY_MGMT_DPP)
+			selector = RSN_AUTH_KEY_MGMT_DPP;
+#endif /* CONFIG_DPP */
 		wpa_auth->dot11RSNAAuthenticationSuiteSelected = selector;
 
 		selector = wpa_cipher_to_suite(WPA_PROTO_RSN,
@@ -591,12 +648,24 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B_192;
 	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B;
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_FILS
+#ifdef CONFIG_IEEE80211R_AP
+	else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA384;
+	else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA256;
+#endif /* CONFIG_IEEE80211R_AP */
+	else if (key_mgmt & WPA_KEY_MGMT_FILS_SHA384)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FILS_SHA384;
+	else if (key_mgmt & WPA_KEY_MGMT_FILS_SHA256)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_FILS_SHA256;
+#endif /* CONFIG_FILS */
+#ifdef CONFIG_IEEE80211R_AP
 	else if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X;
 	else if (key_mgmt & WPA_KEY_MGMT_FT_PSK)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA256;
@@ -611,6 +680,14 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 #endif /* CONFIG_SAE */
 	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X;
+#ifdef CONFIG_OWE
+	else if (key_mgmt & WPA_KEY_MGMT_OWE)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_OWE;
+#endif /* CONFIG_OWE */
+#ifdef CONFIG_DPP
+	else if (key_mgmt & WPA_KEY_MGMT_DPP)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_DPP;
+#endif /* CONFIG_DPP */
 	else
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_PSK;
 
@@ -634,12 +711,6 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			return WPA_MGMT_FRAME_PROTECTION_VIOLATION;
 		}
 
-		if (ciphers & WPA_CIPHER_TKIP) {
-			wpa_printf(MSG_DEBUG, "Management frame protection "
-				   "cannot use TKIP");
-			return WPA_MGMT_FRAME_PROTECTION_VIOLATION;
-		}
-
 		if (data.mgmt_group_cipher != wpa_auth->conf.group_mgmt_cipher)
 		{
 			wpa_printf(MSG_DEBUG, "Unsupported management group "
@@ -648,14 +719,30 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 		}
 	}
 
+#ifdef CONFIG_SAE
+	if (wpa_auth->conf.ieee80211w == MGMT_FRAME_PROTECTION_OPTIONAL &&
+	    wpa_key_mgmt_sae(sm->wpa_key_mgmt) &&
+	    !(data.capabilities & WPA_CAPABILITY_MFPC)) {
+		wpa_printf(MSG_DEBUG,
+			   "Management frame protection required with SAE, but client did not enable it");
+		return WPA_MGMT_FRAME_PROTECTION_VIOLATION;
+	}
+#endif /* CONFIG_SAE */
+
 	if (wpa_auth->conf.ieee80211w == NO_MGMT_FRAME_PROTECTION ||
 	    !(data.capabilities & WPA_CAPABILITY_MFPC))
 		sm->mgmt_frame_prot = 0;
 	else
 		sm->mgmt_frame_prot = 1;
+
+	if (sm->mgmt_frame_prot && (ciphers & WPA_CIPHER_TKIP)) {
+		    wpa_printf(MSG_DEBUG,
+			       "Management frame protection cannot use TKIP");
+		    return WPA_MGMT_FRAME_PROTECTION_VIOLATION;
+	}
 #endif /* CONFIG_IEEE80211W */
 
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
 		if (mdie == NULL || mdie_len < MOBILITY_DOMAIN_ID_LEN + 1) {
 			wpa_printf(MSG_DEBUG, "RSN: Trying to use FT, but "
@@ -668,8 +755,25 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 				    "MDIE", mdie, MOBILITY_DOMAIN_ID_LEN);
 			return WPA_INVALID_MDIE;
 		}
+	} else if (mdie != NULL) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: Trying to use non-FT AKM suite, but MDIE included");
+		return WPA_INVALID_AKMP;
+	}
+#endif /* CONFIG_IEEE80211R_AP */
+
+#ifdef CONFIG_OWE
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE && !owe_dh) {
+		wpa_printf(MSG_DEBUG,
+			   "OWE: No Diffie-Hellman Parameter element");
+		return WPA_INVALID_AKMP;
+	}
+	if (sm->wpa_key_mgmt != WPA_KEY_MGMT_OWE && owe_dh) {
+		wpa_printf(MSG_DEBUG,
+			   "OWE: Unexpected Diffie-Hellman Parameter element with non-OWE AKM");
+		return WPA_INVALID_AKMP;
 	}
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_OWE */
 
 	sm->pairwise = wpa_pick_pairwise_cipher(ciphers, 0);
 	if (sm->pairwise < 0)
@@ -723,6 +827,23 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 		os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN);
 	}
 
+#ifdef CONFIG_SAE
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE && data.num_pmkid &&
+	    !sm->pmksa) {
+		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+				 "No PMKSA cache entry found for SAE");
+		return WPA_INVALID_PMKID;
+	}
+#endif /* CONFIG_SAE */
+
+#ifdef CONFIG_DPP
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && !sm->pmksa) {
+		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+				 "No PMKSA cache entry found for DPP");
+		return WPA_INVALID_PMKID;
+	}
+#endif /* CONFIG_DPP */
+
 	if (sm->wpa_ie == NULL || sm->wpa_ie_len < wpa_ie_len) {
 		os_free(sm->wpa_ie);
 		sm->wpa_ie = os_malloc(wpa_ie_len);
@@ -815,36 +936,6 @@ static int wpa_parse_generic(const u8 *pos, const u8 *end,
 		return 0;
 	}
 
-#ifdef CONFIG_PEERKEY
-	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
-	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_SMK) {
-		ie->smk = pos + 2 + RSN_SELECTOR_LEN;
-		ie->smk_len = pos[1] - RSN_SELECTOR_LEN;
-		return 0;
-	}
-
-	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
-	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_NONCE) {
-		ie->nonce = pos + 2 + RSN_SELECTOR_LEN;
-		ie->nonce_len = pos[1] - RSN_SELECTOR_LEN;
-		return 0;
-	}
-
-	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
-	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_LIFETIME) {
-		ie->lifetime = pos + 2 + RSN_SELECTOR_LEN;
-		ie->lifetime_len = pos[1] - RSN_SELECTOR_LEN;
-		return 0;
-	}
-
-	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
-	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_ERROR) {
-		ie->error = pos + 2 + RSN_SELECTOR_LEN;
-		ie->error_len = pos[1] - RSN_SELECTOR_LEN;
-		return 0;
-	}
-#endif /* CONFIG_PEERKEY */
-
 #ifdef CONFIG_IEEE80211W
 	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
 	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_IGTK) {
@@ -908,14 +999,14 @@ int wpa_parse_kde_ies(const u8 *buf, size_t len, struct wpa_eapol_ie_parse *ie)
 		if (*pos == WLAN_EID_RSN) {
 			ie->rsn_ie = pos;
 			ie->rsn_ie_len = pos[1] + 2;
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 		} else if (*pos == WLAN_EID_MOBILITY_DOMAIN) {
 			ie->mdie = pos;
 			ie->mdie_len = pos[1] + 2;
 		} else if (*pos == WLAN_EID_FAST_BSS_TRANSITION) {
 			ie->ftie = pos;
 			ie->ftie_len = pos[1] + 2;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 		} else if (*pos == WLAN_EID_VENDOR_SPECIFIC) {
 			ret = wpa_parse_generic(pos, end, ie);
 			if (ret < 0)
@@ -938,3 +1029,32 @@ int wpa_auth_uses_mfp(struct wpa_state_machine *sm)
 {
 	return sm ? sm->mgmt_frame_prot : 0;
 }
+
+
+#ifdef CONFIG_OWE
+u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm,
+				   u8 *pos, size_t max_len,
+				   const u8 *req_ies, size_t req_ies_len)
+{
+	int res;
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->own_ie_override_len) {
+		if (max_len < conf->own_ie_override_len)
+			return NULL;
+		wpa_hexdump(MSG_DEBUG, "WPA: Forced own IE(s) for testing",
+			    conf->own_ie_override, conf->own_ie_override_len);
+		os_memcpy(pos, conf->own_ie_override,
+			  conf->own_ie_override_len);
+		return pos + conf->own_ie_override_len;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	res = wpa_write_rsn_ie(&sm->wpa_auth->conf, pos, max_len,
+			       sm->pmksa ? sm->pmksa->pmkid : NULL);
+	if (res < 0)
+		return pos;
+	return pos + res;
+}
+#endif /* CONFIG_OWE */

+ 2 - 12
src/ap/wpa_auth_ie.h

@@ -19,26 +19,16 @@ struct wpa_eapol_ie_parse {
 	size_t gtk_len;
 	const u8 *mac_addr;
 	size_t mac_addr_len;
-#ifdef CONFIG_PEERKEY
-	const u8 *smk;
-	size_t smk_len;
-	const u8 *nonce;
-	size_t nonce_len;
-	const u8 *lifetime;
-	size_t lifetime_len;
-	const u8 *error;
-	size_t error_len;
-#endif /* CONFIG_PEERKEY */
 #ifdef CONFIG_IEEE80211W
 	const u8 *igtk;
 	size_t igtk_len;
 #endif /* CONFIG_IEEE80211W */
-#ifdef CONFIG_IEEE80211R
+#ifdef CONFIG_IEEE80211R_AP
 	const u8 *mdie;
 	size_t mdie_len;
 	const u8 *ftie;
 	size_t ftie_len;
-#endif /* CONFIG_IEEE80211R */
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_P2P
 	const u8 *ip_addr_req;
 	const u8 *ip_addr_alloc;

+ 3 - 1
src/ap/wps_hostapd.c

@@ -1064,7 +1064,9 @@ int hostapd_init_wps(struct hostapd_data *hapd,
 		if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X)
 			wps->auth_types |= WPS_AUTH_WPA2;
 
-		if (conf->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP)) {
+		if (conf->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP |
+					  WPA_CIPHER_CCMP_256 |
+					  WPA_CIPHER_GCMP_256)) {
 			wps->encr_types |= WPS_ENCR_AES;
 			wps->encr_types_rsn |= WPS_ENCR_AES;
 		}

+ 55 - 0
src/common/common_module_tests.c

@@ -53,12 +53,38 @@ static const struct ieee802_11_parse_test_data parse_tests[] = {
 	  18, ParseOK, 9 },
 	{ (u8 *) "\x8b\x00", 2, ParseOK, 1 },
 	{ (u8 *) "\xdd\x04\x00\x90\x4c\x04", 6, ParseUnknown, 1 },
+	{ (u8 *) "\xed\x00", 2, ParseOK, 1 },
+	{ (u8 *) "\xef\x00", 2, ParseOK, 1 },
+	{ (u8 *) "\xef\x01\x11", 3, ParseOK, 1 },
+	{ (u8 *) "\xf0\x00", 2, ParseOK, 1 },
+	{ (u8 *) "\xf1\x00", 2, ParseOK, 1 },
+	{ (u8 *) "\xf1\x02\x11\x22", 4, ParseOK, 1 },
+	{ (u8 *) "\xf2\x00", 2, ParseOK, 1 },
+	{ (u8 *) "\xff\x00", 2, ParseUnknown, 1 },
+	{ (u8 *) "\xff\x01\x00", 3, ParseUnknown, 1 },
+	{ (u8 *) "\xff\x01\x01", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x02\x01\x00", 4, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x02", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x04\x02\x11\x22\x33", 6, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x04", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x05", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x0d\x05\x11\x22\x33\x44\x55\x55\x11\x22\x33\x44\x55\x55",
+	  15, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x06", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x02\x06\x00", 4, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x07", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x09\x07\x11\x22\x33\x44\x55\x66\x77\x88", 11,
+	  ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x0c", 3, ParseOK, 1 },
+	{ (u8 *) "\xff\x02\x0c\x00", 4, ParseOK, 1 },
+	{ (u8 *) "\xff\x01\x0d", 3, ParseOK, 1 },
 	{ NULL, 0, ParseOK, 0 }
 };
 
 static int ieee802_11_parse_tests(void)
 {
 	int i, ret = 0;
+	struct wpabuf *buf;
 
 	wpa_printf(MSG_INFO, "ieee802_11_parse tests");
 
@@ -84,6 +110,35 @@ static int ieee802_11_parse_tests(void)
 		ret = -1;
 	}
 
+	buf = ieee802_11_vendor_ie_concat((const u8 *) "\xdd\x05\x11\x22\x33\x44\x01\xdd\x05\x11\x22\x33\x44\x02\x00\x01",
+					  16, 0x11223344);
+	do {
+		const u8 *pos;
+
+		if (!buf) {
+			wpa_printf(MSG_ERROR,
+				   "ieee802_11_vendor_ie_concat test 2 failed");
+			ret = -1;
+			break;
+		}
+
+		if (wpabuf_len(buf) != 2) {
+			wpa_printf(MSG_ERROR,
+				   "ieee802_11_vendor_ie_concat test 3 failed");
+			ret = -1;
+			break;
+		}
+
+		pos = wpabuf_head(buf);
+		if (pos[0] != 0x01 || pos[1] != 0x02) {
+			wpa_printf(MSG_ERROR,
+				   "ieee802_11_vendor_ie_concat test 3 failed");
+			ret = -1;
+			break;
+		}
+	} while (0);
+	wpabuf_free(buf);
+
 	return ret;
 }
 

+ 37 - 1
src/common/ctrl_iface_common.c

@@ -113,17 +113,53 @@ void sockaddr_print(int level, const char *msg, struct sockaddr_storage *sock,
 }
 
 
+static int ctrl_set_events(struct wpa_ctrl_dst *dst, const char *input)
+{
+	const char *value;
+	int val;
+
+	if (!input)
+		return 0;
+
+	value = os_strchr(input, '=');
+	if (!value)
+		return -1;
+	value++;
+	val = atoi(value);
+	if (val < 0 || val > 1)
+		return -1;
+
+	if (str_starts(input, "probe_rx_events=")) {
+		if (val)
+			dst->events |= WPA_EVENT_RX_PROBE_REQUEST;
+		else
+			dst->events &= ~WPA_EVENT_RX_PROBE_REQUEST;
+	}
+
+	return 0;
+}
+
+
 int ctrl_iface_attach(struct dl_list *ctrl_dst, struct sockaddr_storage *from,
-		      socklen_t fromlen)
+		      socklen_t fromlen, const char *input)
 {
 	struct wpa_ctrl_dst *dst;
 
+	/* Update event registration if already attached */
+	dl_list_for_each(dst, ctrl_dst, struct wpa_ctrl_dst, list) {
+		if (!sockaddr_compare(from, fromlen,
+				      &dst->addr, dst->addrlen))
+			return ctrl_set_events(dst, input);
+	}
+
+	/* New attachment */
 	dst = os_zalloc(sizeof(*dst));
 	if (dst == NULL)
 		return -1;
 	os_memcpy(&dst->addr, from, fromlen);
 	dst->addrlen = fromlen;
 	dst->debug_level = MSG_INFO;
+	ctrl_set_events(dst, input);
 	dl_list_add(ctrl_dst, &dst->list);
 
 	sockaddr_print(MSG_DEBUG, "CTRL_IFACE monitor attached", from, fromlen);

+ 5 - 1
src/common/ctrl_iface_common.h

@@ -11,6 +11,9 @@
 
 #include "utils/list.h"
 
+/* Events enable bits (wpa_ctrl_dst::events) */
+#define WPA_EVENT_RX_PROBE_REQUEST BIT(0)
+
 /**
  * struct wpa_ctrl_dst - Data structure of control interface monitors
  *
@@ -23,13 +26,14 @@ struct wpa_ctrl_dst {
 	socklen_t addrlen;
 	int debug_level;
 	int errors;
+	u32 events; /* WPA_EVENT_* bitmap */
 };
 
 void sockaddr_print(int level, const char *msg, struct sockaddr_storage *sock,
 		    socklen_t socklen);
 
 int ctrl_iface_attach(struct dl_list *ctrl_dst, struct sockaddr_storage *from,
-		       socklen_t fromlen);
+		       socklen_t fromlen, const char *input);
 int ctrl_iface_detach(struct dl_list *ctrl_dst, struct sockaddr_storage *from,
 		      socklen_t fromlen);
 int ctrl_iface_level(struct dl_list *ctrl_dst, struct sockaddr_storage *from,

+ 59 - 5
src/common/defs.h

@@ -51,6 +51,12 @@ typedef enum { FALSE = 0, TRUE = 1 } Boolean;
 #define WPA_KEY_MGMT_OSEN BIT(15)
 #define WPA_KEY_MGMT_IEEE8021X_SUITE_B BIT(16)
 #define WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 BIT(17)
+#define WPA_KEY_MGMT_FILS_SHA256 BIT(18)
+#define WPA_KEY_MGMT_FILS_SHA384 BIT(19)
+#define WPA_KEY_MGMT_FT_FILS_SHA256 BIT(20)
+#define WPA_KEY_MGMT_FT_FILS_SHA384 BIT(21)
+#define WPA_KEY_MGMT_OWE BIT(22)
+#define WPA_KEY_MGMT_DPP BIT(23)
 
 static inline int wpa_key_mgmt_wpa_ieee8021x(int akm)
 {
@@ -60,7 +66,11 @@ static inline int wpa_key_mgmt_wpa_ieee8021x(int akm)
 			 WPA_KEY_MGMT_OSEN |
 			 WPA_KEY_MGMT_IEEE8021X_SHA256 |
 			 WPA_KEY_MGMT_IEEE8021X_SUITE_B |
-			 WPA_KEY_MGMT_IEEE8021X_SUITE_B_192));
+			 WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 |
+			 WPA_KEY_MGMT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FILS_SHA384 |
+			 WPA_KEY_MGMT_FT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FT_FILS_SHA384));
 }
 
 static inline int wpa_key_mgmt_wpa_psk(int akm)
@@ -76,7 +86,14 @@ static inline int wpa_key_mgmt_ft(int akm)
 {
 	return !!(akm & (WPA_KEY_MGMT_FT_PSK |
 			 WPA_KEY_MGMT_FT_IEEE8021X |
-			 WPA_KEY_MGMT_FT_SAE));
+			 WPA_KEY_MGMT_FT_SAE |
+			 WPA_KEY_MGMT_FT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FT_FILS_SHA384));
+}
+
+static inline int wpa_key_mgmt_ft_psk(int akm)
+{
+	return !!(akm & WPA_KEY_MGMT_FT_PSK);
 }
 
 static inline int wpa_key_mgmt_sae(int akm)
@@ -85,17 +102,31 @@ static inline int wpa_key_mgmt_sae(int akm)
 			 WPA_KEY_MGMT_FT_SAE));
 }
 
+static inline int wpa_key_mgmt_fils(int akm)
+{
+	return !!(akm & (WPA_KEY_MGMT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FILS_SHA384 |
+			 WPA_KEY_MGMT_FT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FT_FILS_SHA384));
+}
+
 static inline int wpa_key_mgmt_sha256(int akm)
 {
 	return !!(akm & (WPA_KEY_MGMT_PSK_SHA256 |
 			 WPA_KEY_MGMT_IEEE8021X_SHA256 |
+			 WPA_KEY_MGMT_SAE |
+			 WPA_KEY_MGMT_FT_SAE |
 			 WPA_KEY_MGMT_OSEN |
-			 WPA_KEY_MGMT_IEEE8021X_SUITE_B));
+			 WPA_KEY_MGMT_IEEE8021X_SUITE_B |
+			 WPA_KEY_MGMT_FILS_SHA256 |
+			 WPA_KEY_MGMT_FT_FILS_SHA256));
 }
 
 static inline int wpa_key_mgmt_sha384(int akm)
 {
-	return !!(akm & WPA_KEY_MGMT_IEEE8021X_SUITE_B_192);
+	return !!(akm & (WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 |
+			 WPA_KEY_MGMT_FILS_SHA384 |
+			 WPA_KEY_MGMT_FT_FILS_SHA384));
 }
 
 static inline int wpa_key_mgmt_suite_b(int akm)
@@ -108,7 +139,10 @@ static inline int wpa_key_mgmt_wpa(int akm)
 {
 	return wpa_key_mgmt_wpa_ieee8021x(akm) ||
 		wpa_key_mgmt_wpa_psk(akm) ||
-		wpa_key_mgmt_sae(akm);
+		wpa_key_mgmt_fils(akm) ||
+		wpa_key_mgmt_sae(akm) ||
+		akm == WPA_KEY_MGMT_OWE ||
+		akm == WPA_KEY_MGMT_DPP;
 }
 
 static inline int wpa_key_mgmt_wpa_any(int akm)
@@ -132,7 +166,13 @@ static inline int wpa_key_mgmt_cckm(int akm)
 #define WPA_AUTH_ALG_LEAP BIT(2)
 #define WPA_AUTH_ALG_FT BIT(3)
 #define WPA_AUTH_ALG_SAE BIT(4)
+#define WPA_AUTH_ALG_FILS BIT(5)
+#define WPA_AUTH_ALG_FILS_SK_PFS BIT(6)
 
+static inline int wpa_auth_alg_fils(int alg)
+{
+	return !!(alg & (WPA_AUTH_ALG_FILS | WPA_AUTH_ALG_FILS_SK_PFS));
+}
 
 enum wpa_alg {
 	WPA_ALG_NONE,
@@ -341,4 +381,18 @@ enum wpa_radio_work_band {
 	BAND_60_GHZ = BIT(2),
 };
 
+enum beacon_rate_type {
+	BEACON_RATE_LEGACY,
+	BEACON_RATE_HT,
+	BEACON_RATE_VHT
+};
+
+enum eap_proxy_sim_state {
+	SIM_STATE_ERROR,
+};
+
+#define OCE_STA BIT(0)
+#define OCE_STA_CFON BIT(1)
+#define OCE_AP BIT(2)
+
 #endif /* DEFS_H */

+ 263 - 0
src/common/dhcp.h

@@ -0,0 +1,263 @@
+/*
+ * DHCP definitions
+ * Copyright (c) 2014-2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#include <netinet/ip.h>
+#if __FAVOR_BSD
+#include <netinet/udp.h>
+#else
+#define __FAVOR_BSD 1
+#include <netinet/udp.h>
+#undef __FAVOR_BSD
+#endif
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+struct dhcp_data {
+	u8 op;
+	u8 htype;
+	u8 hlen;
+	u8 hops;
+	be32 xid;
+	be16 secs;
+	be16 flags;
+	be32 client_ip;
+	be32 your_ip;
+	be32 server_ip;
+	be32 relay_ip;
+	u8 hw_addr[16];
+	u8 serv_name[64];
+	u8 boot_file[128];
+} STRUCT_PACKED;
+
+struct bootp_pkt {
+	struct iphdr iph;
+	struct udphdr udph;
+	u8 op;
+	u8 htype;
+	u8 hlen;
+	u8 hops;
+	be32 xid;
+	be16 secs;
+	be16 flags;
+	be32 client_ip;
+	be32 your_ip;
+	be32 server_ip;
+	be32 relay_ip;
+	u8 hw_addr[16];
+	u8 serv_name[64];
+	u8 boot_file[128];
+	u8 exten[312];
+} STRUCT_PACKED;
+
+#define DHCP_MAGIC 0x63825363
+
+/*
+ * IANA DHCP/BOOTP registry
+ * http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
+*/
+enum dhcp_options {
+	DHCP_OPT_PAD = 0,
+	DHCP_OPT_SUBNET_MASK = 1,
+	DHCP_OPT_TIME_OFFSET = 2,
+	DHCP_OPT_ROUTER = 3,
+	DHCP_OPT_TIME_SERVER = 4,
+	DHCP_OPT_NAME_SERVER = 5,
+	DHCP_OPT_DOMAIN_NAME_SERVER = 6,
+	DHCP_OPT_LOG_SERVER = 7,
+	DHCP_OPT_QUOTES_SERVER = 8,
+	DHCP_OPT_LPR_SERVER = 9,
+	DHCP_OPT_IMPRESS_SERVER = 10,
+	DHCP_OPT_RLP_SERVER = 11,
+	DHCP_OPT_HOSTNAME = 12,
+	DHCP_OPT_BOOT_FILE_SIZE = 13,
+	DHCP_OPT_MERIT_DUMP_FILE = 14,
+	DHCP_OPT_DOMAIN_NAME = 15,
+	DHCP_OPT_SWAP_SERVER = 16,
+	DHCP_OPT_ROOT_PATH = 17,
+	DHCP_OPT_EXTENSION_PATH = 18,
+	DHCP_OPT_FORWARD = 19,
+	DHCP_OPT_SRC_RTE = 20,
+	DHCP_OPT_POLICY_FILTER = 21,
+	DHCP_OPT_MAX_DG_ASSEMBLY = 22,
+	DHCP_OPT_DEFAULT_IP_TTL = 23,
+	DHCP_OPT_MTU_TIMEOUT = 24,
+	DHCP_OPT_MTU_PLATEAU = 25,
+	DHCP_OPT_MTU_INTERFACE = 26,
+	DHCP_OPT_ALL_SUBNETS_LOCAL = 27,
+	DHCP_OPT_BROADCAST_ADDRESS = 28,
+	DHCP_OPT_MASK_DISCOVERY = 29,
+	DHCP_OPT_MASK_SUPPLIER = 30,
+	DHCP_OPT_ROUTER_DISCOVERY = 31,
+	DHCP_OPT_ROUTER_SOLICITATION_ADDRESS = 32,
+	DHCP_OPT_STATIC_ROUTE = 33,
+	DHCP_OPT_TRAILERS = 34,
+	DHCP_OPT_ARP_TIMEOUT = 35,
+	DHCP_OPT_ETHERNET = 36,
+	DHCP_OPT_TCP_DEFAULT_TTL = 37,
+	DHCP_OPT_TCP_KEEPALIVE_INTERVAL = 38,
+	DHCP_OPT_TCP_KEEPALIVE_GARBAGE = 39,
+	DHCP_OPT_NIS_DOMAIN = 40,
+	DHCP_OPT_NIS_SERVERS = 41,
+	DHCP_OPT_NTP_SERVERS = 42,
+	DHCP_OPT_VENDOR_SPECIFIC = 43,
+	DHCP_OPT_NETBIOS_NAME_SERVER = 44,
+	DHCP_OPT_NETBIOS_DISTRIBUTION_SERVER = 45,
+	DHCP_OPT_NETBIOS_NODE_TYPE = 46,
+	DHCP_OPT_NETBIOS_SCOPE = 47,
+	DHCP_OPT_FONT_SERVER = 48,
+	DHCP_OPT_DISPLAY_MANAGER = 49,
+	DHCP_OPT_REQUESTED_IP_ADDRESS = 50,
+	DHCP_OPT_IP_ADDRESS_LEASE_TIME = 51,
+	DHCP_OPT_OVERLOAD = 52,
+	DHCP_OPT_MSG_TYPE = 53,
+	DHCP_OPT_SERVER_ID = 54,
+	DHCP_OPT_PARAMETER_REQ_LIST = 55,
+	DHCP_OPT_MESSAGE = 56,
+	DHCP_OPT_MAX_MESSAGE_SIZE = 57,
+	DHCP_OPT_RENEWAL_TIME = 58,
+	DHCP_OPT_REBINDING_TIME = 59,
+	DHCP_OPT_VENDOR_CLASS_ID = 60,
+	DHCP_OPT_CLIENT_ID = 61,
+	DHCP_OPT_NETWARE_IP_DOMAIN = 62,
+	DHCP_OPT_NETWARE_IP_OPTION = 63,
+	DHCP_OPT_NIS_V3_DOMAIN = 64,
+	DHCP_OPT_NIS_V3_SERVERS = 65,
+	DHCP_OPT_TFTP_SERVER_NAME = 66,
+	DHCP_OPT_BOOT_FILE_NAME = 67,
+	DHCP_OPT_HOME_AGENT_ADDRESSES = 68,
+	DHCP_OPT_SMTP_SERVER = 69,
+	DHCP_OPT_POP3_SERVER = 70,
+	DHCP_OPT_NNTP_SERVER = 71,
+	DHCP_OPT_WWW_SERVER = 72,
+	DHCP_OPT_FINGER_SERVER = 73,
+	DHCP_OPT_IRC_SERVER = 74,
+	DHCP_OPT_STREETTALK_SERVER = 75,
+	DHCP_OPT_STDA_SERVER = 76,
+	DHCP_OPT_USER_CLASS = 77,
+	DHCP_OPT_DIRECTORY_AGENT = 78,
+	DHCP_OPT_SERVICE_SCOPE = 79,
+	DHCP_OPT_RAPID_COMMIT = 80,
+	DHCP_OPT_CLIENT_FQDN = 81,
+	DHCP_OPT_RELAY_AGENT_INFO = 82,
+	DHCP_OPT_ISNS = 83,
+	DHCP_OPT_NDS_SERVERS = 85,
+	DHCP_OPT_NDS_TREE_NAME = 86,
+	DHCP_OPT_NDS_CONTEXT = 87,
+	DHCP_OPT_BCMCS_CONTROLLER_DOMAIN_NAME_LIST = 88,
+	DHCP_OPT_BCMCS_CONTROLLER_IPV4_ADDRESS = 89,
+	DHCP_OPT_AUTHENTICATION = 90,
+	DHCP_OPT_CLIENT_LAST_TRANSACTION_TIME = 91,
+	DHCP_OPT_ASSOCIATED_IP = 92,
+	DHCP_OPT_CLIENT_SYSYEM = 93,
+	DHCP_OPT_CLIENT_NDI = 94,
+	DHCP_OPT_LDAP = 95,
+	DHCP_OPT_UUID_GUID = 97,
+	DHCP_OPT_USER_AUTH = 98,
+	DHCP_OPT_GEOCONF_CIVIC = 99,
+	DHCP_OPT_PCODE = 100,
+	DHCP_OPT_TCODE = 101,
+	DHCP_OPT_NETINFO_ADDRESS = 112,
+	DHCP_OPT_NETINFO_TAG = 113,
+	DHCP_OPT_URL = 114,
+	DHCP_OPT_AUTO_CONFIG = 116,
+	DHCP_OPT_NAME_SERVICE_SEARCH = 117,
+	DHCP_OPT_SUBNET_SELECTION = 118,
+	DHCP_OPT_DOMAIN_SEARCH = 119,
+	DHCP_OPT_SIP_SERVERS_DCP = 120,
+	DHCP_OPT_CLASSLESS_STATIC_ROUTE = 121,
+	DHCP_OPT_CCC = 122,
+	DHCP_OPT_GEOCONF = 123,
+	DHCP_OPT_V_I_VENDOR_CLASS = 124,
+	DHCP_OPT_V_I_VENDOR_SPECIFIC_INFO = 125,
+	DHCP_OPT_PANA_AGENT = 136,
+	DHCP_OPT_V4_LOST = 137,
+	DHCP_OPT_CAPWAP_AC_V4 = 138,
+	DHCP_OPT_IPV4_ADDRESS_MOS = 139,
+	DHCP_OPT_IPV4_FQDN_MOS = 140,
+	DHCP_OPT_SIP_UA_CONF = 141,
+	DHCP_OPT_IPV4_ADDRESS_ANDSF = 142,
+	DHCP_OPT_GEOLOC = 144,
+	DHCP_OPT_FORCERENEW_NONCE_CAPABLE = 145,
+	DHCP_OPT_RDNSS_SELECTION = 146,
+	DHCP_OPT_TFTP_SERVER_ADDRESS = 150,
+	DHCP_OPT_STATUS_CODE = 151,
+	DHCP_OPT_BASE_TIME = 152,
+	DHCP_OPT_START_TIME_OF_STATE = 153,
+	DHCP_OPT_QUERY_START_TIME = 154,
+	DHCP_OPT_QUERY_END_TIME = 155,
+	DHCP_OPT_STATE = 156,
+	DHCP_OPT_DATA_SOURCE = 157,
+	DHCP_OPT_V4_PCP_SERVER = 158,
+	DHCP_OPT_V4_PORTPARAMS = 159,
+	DHCP_OPT_CAPTIVE_PORTAL = 160,
+	DHCP_OPT_CONF_FILE = 209,
+	DHCP_OPT_PATH_PREFIX = 210,
+	DHCP_OPT_REBOOT_TIME = 211,
+	DHCP_OPT_6RD = 212,
+	DHCP_OPT_V4_ACCESS_DOMAIN = 213,
+	DHCP_OPT_SUBNET_ALLOCATION = 220,
+	DHCP_OPT_VSS = 221,
+	DHCP_OPT_END = 255
+};
+
+enum dhcp_message_types {
+	DHCPDISCOVER = 1,
+	DHCPOFFER = 2,
+	DHCPREQUEST = 3,
+	DHCPDECLINE = 4,
+	DHCPACK = 5,
+	DHCPNAK = 6,
+	DHCPRELEASE = 7,
+	DHCPINFORM = 8,
+	DHCPFORCERENEW = 9,
+	DHCPLEASEQUERY = 10,
+	DHCPLEASEUNASSIGNED = 11,
+	DHCPLEASEUNKNOWN = 12,
+	DHCPLEASEACTIVE = 13,
+	DHCPBULKLEASEQUERY = 14,
+	DHCPLEASEQUERYDONE = 15,
+	DHCPACTIVELEASEQUERY = 16,
+	DHCPLEASEQUERYSTATUS = 17,
+	DHCPTLS = 18,
+};
+
+enum dhcp_relay_agent_suboptions {
+	DHCP_RELAY_OPT_AGENT_CIRCUIT_ID = 1,
+	DHCP_RELAY_OPT_AGENT_REMOTE_ID = 2,
+	DHCP_RELAY_OPT_DOCSIS_DEVICE_CLASS = 4,
+	DHCP_RELAY_OPT_LINK_SELECTION = 5,
+	DHCP_RELAY_OPT_SUBSCRIBE_ID = 6,
+	DHCP_RELAY_OPT_RADIUS_ATTRIBUTES = 7,
+	DHCP_RELAY_OPT_AUTHENTICATION = 8,
+	DHCP_RELAY_OPT_VEDOR_SPECIFIC = 9,
+	DHCP_RELAY_OPT_RELAY_AGENT_FLAGS = 10,
+	DHCP_RELAY_OPT_SERVER_ID_OVERRIDE = 11,
+	DHCP_RELAY_OPT_RELAY_AGENT_ID = 12,
+	DHCP_RELAY_OPT_ACCESS_TECHNOLOGY_TYPE = 13,
+	DHCP_RELAY_OPT_ACCESS_NETWORK_NAME = 14,
+	DHCP_RELAY_OPT_ACCESS_POINT_NAME = 15,
+	DHCP_RELAY_OPT_ACCESS_POINT_BSSID = 16,
+	DHCP_RELAY_OPT_OPERATOR_ID = 17,
+	DHCP_RELAY_OPT_OPERATOR_REALM = 18,
+	DHCP_RELAY_OPT_DHCPV4_VIRTUAL_SUBNET_SELECTION = 151,
+	DHCP_RELAY_OPT_DHCPV4_VIRTUAL_SUBNET_SELECTION_CONTROL = 152,
+};
+
+enum access_technology_types {
+	ACCESS_TECHNOLOGY_VIRTUAL = 1,
+	ACCESS_TECHNOLOGY_PPP = 2,
+	ACCESS_TECHNOLOGY_ETHERNET = 3,
+	ACCESS_TECHNOLOGY_WLAN = 4,
+	ACCESS_TECHNOLOGY_WIMAX = 5,
+};
+
+#endif /* DHCP_H */

+ 7667 - 0
src/common/dpp.c

@@ -0,0 +1,7667 @@
+/*
+ * DPP functionality shared between hostapd and wpa_supplicant
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include <openssl/opensslv.h>
+#include <openssl/err.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+
+#include "utils/common.h"
+#include "utils/base64.h"
+#include "utils/json.h"
+#include "common/ieee802_11_common.h"
+#include "common/ieee802_11_defs.h"
+#include "common/wpa_ctrl.h"
+#include "crypto/crypto.h"
+#include "crypto/random.h"
+#include "crypto/aes.h"
+#include "crypto/aes_siv.h"
+#include "crypto/sha384.h"
+#include "crypto/sha512.h"
+#include "drivers/driver.h"
+#include "dpp.h"
+
+
+#ifdef CONFIG_TESTING_OPTIONS
+enum dpp_test_behavior dpp_test = DPP_TEST_DISABLED;
+u8 dpp_pkex_own_mac_override[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 };
+u8 dpp_pkex_peer_mac_override[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 };
+u8 dpp_pkex_ephemeral_key_override[600];
+size_t dpp_pkex_ephemeral_key_override_len = 0;
+u8 dpp_protocol_key_override[600];
+size_t dpp_protocol_key_override_len = 0;
+u8 dpp_nonce_override[DPP_MAX_NONCE_LEN];
+size_t dpp_nonce_override_len = 0;
+
+static int dpp_test_gen_invalid_key(struct wpabuf *msg,
+				    const struct dpp_curve_params *curve);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+/* Compatibility wrappers for older versions. */
+
+static int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+	sig->r = r;
+	sig->s = s;
+	return 1;
+}
+
+
+static void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr,
+			   const BIGNUM **ps)
+{
+	if (pr)
+		*pr = sig->r;
+	if (ps)
+		*ps = sig->s;
+}
+
+#endif
+
+
+static const struct dpp_curve_params dpp_curves[] = {
+	/* The mandatory to support and the default NIST P-256 curve needs to
+	 * be the first entry on this list. */
+	{ "prime256v1", 32, 32, 16, 32, "P-256", 19, "ES256" },
+	{ "secp384r1", 48, 48, 24, 48, "P-384", 20, "ES384" },
+	{ "secp521r1", 64, 64, 32, 66, "P-521", 21, "ES512" },
+	{ "brainpoolP256r1", 32, 32, 16, 32, "BP-256", 28, "BS256" },
+	{ "brainpoolP384r1", 48, 48, 24, 48, "BP-384", 29, "BS384" },
+	{ "brainpoolP512r1", 64, 64, 32, 64, "BP-512", 30, "BS512" },
+	{ NULL, 0, 0, 0, 0, NULL, 0, NULL }
+};
+
+
+/* Role-specific elements for PKEX */
+
+/* NIST P-256 */
+static const u8 pkex_init_x_p256[32] = {
+	0x56, 0x26, 0x12, 0xcf, 0x36, 0x48, 0xfe, 0x0b,
+	0x07, 0x04, 0xbb, 0x12, 0x22, 0x50, 0xb2, 0x54,
+	0xb1, 0x94, 0x64, 0x7e, 0x54, 0xce, 0x08, 0x07,
+	0x2e, 0xec, 0xca, 0x74, 0x5b, 0x61, 0x2d, 0x25
+ };
+static const u8 pkex_init_y_p256[32] = {
+	0x3e, 0x44, 0xc7, 0xc9, 0x8c, 0x1c, 0xa1, 0x0b,
+	0x20, 0x09, 0x93, 0xb2, 0xfd, 0xe5, 0x69, 0xdc,
+	0x75, 0xbc, 0xad, 0x33, 0xc1, 0xe7, 0xc6, 0x45,
+	0x4d, 0x10, 0x1e, 0x6a, 0x3d, 0x84, 0x3c, 0xa4
+ };
+static const u8 pkex_resp_x_p256[32] = {
+	0x1e, 0xa4, 0x8a, 0xb1, 0xa4, 0xe8, 0x42, 0x39,
+	0xad, 0x73, 0x07, 0xf2, 0x34, 0xdf, 0x57, 0x4f,
+	0xc0, 0x9d, 0x54, 0xbe, 0x36, 0x1b, 0x31, 0x0f,
+	0x59, 0x91, 0x52, 0x33, 0xac, 0x19, 0x9d, 0x76
+};
+static const u8 pkex_resp_y_p256[32] = {
+	0xd9, 0xfb, 0xf6, 0xb9, 0xf5, 0xfa, 0xdf, 0x19,
+	0x58, 0xd8, 0x3e, 0xc9, 0x89, 0x7a, 0x35, 0xc1,
+	0xbd, 0xe9, 0x0b, 0x77, 0x7a, 0xcb, 0x91, 0x2a,
+	0xe8, 0x21, 0x3f, 0x47, 0x52, 0x02, 0x4d, 0x67
+};
+
+/* NIST P-384 */
+static const u8 pkex_init_x_p384[48] = {
+	0x95, 0x3f, 0x42, 0x9e, 0x50, 0x7f, 0xf9, 0xaa,
+	0xac, 0x1a, 0xf2, 0x85, 0x2e, 0x64, 0x91, 0x68,
+	0x64, 0xc4, 0x3c, 0xb7, 0x5c, 0xf8, 0xc9, 0x53,
+	0x6e, 0x58, 0x4c, 0x7f, 0xc4, 0x64, 0x61, 0xac,
+	0x51, 0x8a, 0x6f, 0xfe, 0xab, 0x74, 0xe6, 0x12,
+	0x81, 0xac, 0x38, 0x5d, 0x41, 0xe6, 0xb9, 0xa3
+};
+static const u8 pkex_init_y_p384[48] = {
+	0x76, 0x2f, 0x68, 0x84, 0xa6, 0xb0, 0x59, 0x29,
+	0x83, 0xa2, 0x6c, 0xa4, 0x6c, 0x3b, 0xf8, 0x56,
+	0x76, 0x11, 0x2a, 0x32, 0x90, 0xbd, 0x07, 0xc7,
+	0x37, 0x39, 0x9d, 0xdb, 0x96, 0xf3, 0x2b, 0xb6,
+	0x27, 0xbb, 0x29, 0x3c, 0x17, 0x33, 0x9d, 0x94,
+	0xc3, 0xda, 0xac, 0x46, 0xb0, 0x8e, 0x07, 0x18
+};
+static const u8 pkex_resp_x_p384[48] = {
+	0xad, 0xbe, 0xd7, 0x1d, 0x3a, 0x71, 0x64, 0x98,
+	0x5f, 0xb4, 0xd6, 0x4b, 0x50, 0xd0, 0x84, 0x97,
+	0x4b, 0x7e, 0x57, 0x70, 0xd2, 0xd9, 0xf4, 0x92,
+	0x2a, 0x3f, 0xce, 0x99, 0xc5, 0x77, 0x33, 0x44,
+	0x14, 0x56, 0x92, 0xcb, 0xae, 0x46, 0x64, 0xdf,
+	0xe0, 0xbb, 0xd7, 0xb1, 0x29, 0x20, 0x72, 0xdf
+};
+static const u8 pkex_resp_y_p384[48] = {
+	0xab, 0xa7, 0xdf, 0x52, 0xaa, 0xe2, 0x35, 0x0c,
+	0xe3, 0x75, 0x32, 0xe6, 0xbf, 0x06, 0xc8, 0x7c,
+	0x38, 0x29, 0x4c, 0xec, 0x82, 0xac, 0xd7, 0xa3,
+	0x09, 0xd2, 0x0e, 0x22, 0x5a, 0x74, 0x52, 0xa1,
+	0x7e, 0x54, 0x4e, 0xfe, 0xc6, 0x29, 0x33, 0x63,
+	0x15, 0xe1, 0x7b, 0xe3, 0x40, 0x1c, 0xca, 0x06
+};
+
+/* NIST P-521 */
+static const u8 pkex_init_x_p521[66] = {
+	0x00, 0x16, 0x20, 0x45, 0x19, 0x50, 0x95, 0x23,
+	0x0d, 0x24, 0xbe, 0x00, 0x87, 0xdc, 0xfa, 0xf0,
+	0x58, 0x9a, 0x01, 0x60, 0x07, 0x7a, 0xca, 0x76,
+	0x01, 0xab, 0x2d, 0x5a, 0x46, 0xcd, 0x2c, 0xb5,
+	0x11, 0x9a, 0xff, 0xaa, 0x48, 0x04, 0x91, 0x38,
+	0xcf, 0x86, 0xfc, 0xa4, 0xa5, 0x0f, 0x47, 0x01,
+	0x80, 0x1b, 0x30, 0xa3, 0xae, 0xe8, 0x1c, 0x2e,
+	0xea, 0xcc, 0xf0, 0x03, 0x9f, 0x77, 0x4c, 0x8d,
+	0x97, 0x76
+};
+static const u8 pkex_init_y_p521[66] = {
+	0x00, 0xb3, 0x8e, 0x02, 0xe4, 0x2a, 0x63, 0x59,
+	0x12, 0xc6, 0x10, 0xba, 0x3a, 0xf9, 0x02, 0x99,
+	0x3f, 0x14, 0xf0, 0x40, 0xde, 0x5c, 0xc9, 0x8b,
+	0x02, 0x55, 0xfa, 0x91, 0xb1, 0xcc, 0x6a, 0xbd,
+	0xe5, 0x62, 0xc0, 0xc5, 0xe3, 0xa1, 0x57, 0x9f,
+	0x08, 0x1a, 0xa6, 0xe2, 0xf8, 0x55, 0x90, 0xbf,
+	0xf5, 0xa6, 0xc3, 0xd8, 0x52, 0x1f, 0xb7, 0x02,
+	0x2e, 0x7c, 0xc8, 0xb3, 0x20, 0x1e, 0x79, 0x8d,
+	0x03, 0xa8
+};
+static const u8 pkex_resp_x_p521[66] = {
+	0x00, 0x79, 0xe4, 0x4d, 0x6b, 0x5e, 0x12, 0x0a,
+	0x18, 0x2c, 0xb3, 0x05, 0x77, 0x0f, 0xc3, 0x44,
+	0x1a, 0xcd, 0x78, 0x46, 0x14, 0xee, 0x46, 0x3f,
+	0xab, 0xc9, 0x59, 0x7c, 0x85, 0xa0, 0xc2, 0xfb,
+	0x02, 0x32, 0x99, 0xde, 0x5d, 0xe1, 0x0d, 0x48,
+	0x2d, 0x71, 0x7d, 0x8d, 0x3f, 0x61, 0x67, 0x9e,
+	0x2b, 0x8b, 0x12, 0xde, 0x10, 0x21, 0x55, 0x0a,
+	0x5b, 0x2d, 0xe8, 0x05, 0x09, 0xf6, 0x20, 0x97,
+	0x84, 0xb4
+};
+static const u8 pkex_resp_y_p521[66] = {
+	0x00, 0x46, 0x63, 0x39, 0xbe, 0xcd, 0xa4, 0x2d,
+	0xca, 0x27, 0x74, 0xd4, 0x1b, 0x91, 0x33, 0x20,
+	0x83, 0xc7, 0x3b, 0xa4, 0x09, 0x8b, 0x8e, 0xa3,
+	0x88, 0xe9, 0x75, 0x7f, 0x56, 0x7b, 0x38, 0x84,
+	0x62, 0x02, 0x7c, 0x90, 0x51, 0x07, 0xdb, 0xe9,
+	0xd0, 0xde, 0xda, 0x9a, 0x5d, 0xe5, 0x94, 0xd2,
+	0xcf, 0x9d, 0x4c, 0x33, 0x91, 0xa6, 0xc3, 0x80,
+	0xa7, 0x6e, 0x7e, 0x8d, 0xf8, 0x73, 0x6e, 0x53,
+	0xce, 0xe1
+};
+
+/* Brainpool P-256r1 */
+static const u8 pkex_init_x_bp_p256r1[32] = {
+	0x46, 0x98, 0x18, 0x6c, 0x27, 0xcd, 0x4b, 0x10,
+	0x7d, 0x55, 0xa3, 0xdd, 0x89, 0x1f, 0x9f, 0xca,
+	0xc7, 0x42, 0x5b, 0x8a, 0x23, 0xed, 0xf8, 0x75,
+	0xac, 0xc7, 0xe9, 0x8d, 0xc2, 0x6f, 0xec, 0xd8
+};
+static const u8 pkex_init_y_bp_p256r1[32] = {
+	0x93, 0xca, 0xef, 0xa9, 0x66, 0x3e, 0x87, 0xcd,
+	0x52, 0x6e, 0x54, 0x13, 0xef, 0x31, 0x67, 0x30,
+	0x15, 0x13, 0x9d, 0x6d, 0xc0, 0x95, 0x32, 0xbe,
+	0x4f, 0xab, 0x5d, 0xf7, 0xbf, 0x5e, 0xaa, 0x0b
+};
+static const u8 pkex_resp_x_bp_p256r1[32] = {
+	0x90, 0x18, 0x84, 0xc9, 0xdc, 0xcc, 0xb5, 0x2f,
+	0x4a, 0x3f, 0x4f, 0x18, 0x0a, 0x22, 0x56, 0x6a,
+	0xa9, 0xef, 0xd4, 0xe6, 0xc3, 0x53, 0xc2, 0x1a,
+	0x23, 0x54, 0xdd, 0x08, 0x7e, 0x10, 0xd8, 0xe3
+};
+static const u8 pkex_resp_y_bp_p256r1[32] = {
+	0x2a, 0xfa, 0x98, 0x9b, 0xe3, 0xda, 0x30, 0xfd,
+	0x32, 0x28, 0xcb, 0x66, 0xfb, 0x40, 0x7f, 0xf2,
+	0xb2, 0x25, 0x80, 0x82, 0x44, 0x85, 0x13, 0x7e,
+	0x4b, 0xb5, 0x06, 0xc0, 0x03, 0x69, 0x23, 0x64
+};
+
+/* Brainpool P-384r1 */
+static const u8 pkex_init_x_bp_p384r1[48] = {
+	0x0a, 0x2c, 0xeb, 0x49, 0x5e, 0xb7, 0x23, 0xbd,
+	0x20, 0x5b, 0xe0, 0x49, 0xdf, 0xcf, 0xcf, 0x19,
+	0x37, 0x36, 0xe1, 0x2f, 0x59, 0xdb, 0x07, 0x06,
+	0xb5, 0xeb, 0x2d, 0xae, 0xc2, 0xb2, 0x38, 0x62,
+	0xa6, 0x73, 0x09, 0xa0, 0x6c, 0x0a, 0xa2, 0x30,
+	0x99, 0xeb, 0xf7, 0x1e, 0x47, 0xb9, 0x5e, 0xbe
+};
+static const u8 pkex_init_y_bp_p384r1[48] = {
+	0x54, 0x76, 0x61, 0x65, 0x75, 0x5a, 0x2f, 0x99,
+	0x39, 0x73, 0xca, 0x6c, 0xf9, 0xf7, 0x12, 0x86,
+	0x54, 0xd5, 0xd4, 0xad, 0x45, 0x7b, 0xbf, 0x32,
+	0xee, 0x62, 0x8b, 0x9f, 0x52, 0xe8, 0xa0, 0xc9,
+	0xb7, 0x9d, 0xd1, 0x09, 0xb4, 0x79, 0x1c, 0x3e,
+	0x1a, 0xbf, 0x21, 0x45, 0x66, 0x6b, 0x02, 0x52
+};
+static const u8 pkex_resp_x_bp_p384r1[48] = {
+	0x03, 0xa2, 0x57, 0xef, 0xe8, 0x51, 0x21, 0xa0,
+	0xc8, 0x9e, 0x21, 0x02, 0xb5, 0x9a, 0x36, 0x25,
+	0x74, 0x22, 0xd1, 0xf2, 0x1b, 0xa8, 0x9a, 0x9b,
+	0x97, 0xbc, 0x5a, 0xeb, 0x26, 0x15, 0x09, 0x71,
+	0x77, 0x59, 0xec, 0x8b, 0xb7, 0xe1, 0xe8, 0xce,
+	0x65, 0xb8, 0xaf, 0xf8, 0x80, 0xae, 0x74, 0x6c
+};
+static const u8 pkex_resp_y_bp_p384r1[48] = {
+	0x2f, 0xd9, 0x6a, 0xc7, 0x3e, 0xec, 0x76, 0x65,
+	0x2d, 0x38, 0x7f, 0xec, 0x63, 0x26, 0x3f, 0x04,
+	0xd8, 0x4e, 0xff, 0xe1, 0x0a, 0x51, 0x74, 0x70,
+	0xe5, 0x46, 0x63, 0x7f, 0x5c, 0xc0, 0xd1, 0x7c,
+	0xfb, 0x2f, 0xea, 0xe2, 0xd8, 0x0f, 0x84, 0xcb,
+	0xe9, 0x39, 0x5c, 0x64, 0xfe, 0xcb, 0x2f, 0xf1
+};
+
+/* Brainpool P-512r1 */
+static const u8 pkex_init_x_bp_p512r1[64] = {
+	0x4c, 0xe9, 0xb6, 0x1c, 0xe2, 0x00, 0x3c, 0x9c,
+	0xa9, 0xc8, 0x56, 0x52, 0xaf, 0x87, 0x3e, 0x51,
+	0x9c, 0xbb, 0x15, 0x31, 0x1e, 0xc1, 0x05, 0xfc,
+	0x7c, 0x77, 0xd7, 0x37, 0x61, 0x27, 0xd0, 0x95,
+	0x98, 0xee, 0x5d, 0xa4, 0x3d, 0x09, 0xdb, 0x3d,
+	0xfa, 0x89, 0x9e, 0x7f, 0xa6, 0xa6, 0x9c, 0xff,
+	0x83, 0x5c, 0x21, 0x6c, 0x3e, 0xf2, 0xfe, 0xdc,
+	0x63, 0xe4, 0xd1, 0x0e, 0x75, 0x45, 0x69, 0x0f
+};
+static const u8 pkex_init_y_bp_p512r1[64] = {
+	0x50, 0xb5, 0x9b, 0xfa, 0x45, 0x67, 0x75, 0x94,
+	0x44, 0xe7, 0x68, 0xb0, 0xeb, 0x3e, 0xb3, 0xb8,
+	0xf9, 0x99, 0x05, 0xef, 0xae, 0x6c, 0xbc, 0xe3,
+	0xe1, 0xd2, 0x51, 0x54, 0xdf, 0x59, 0xd4, 0x45,
+	0x41, 0x3a, 0xa8, 0x0b, 0x76, 0x32, 0x44, 0x0e,
+	0x07, 0x60, 0x3a, 0x6e, 0xbe, 0xfe, 0xe0, 0x58,
+	0x52, 0xa0, 0xaa, 0x8b, 0xd8, 0x5b, 0xf2, 0x71,
+	0x11, 0x9a, 0x9e, 0x8f, 0x1a, 0xd1, 0xc9, 0x99
+};
+static const u8 pkex_resp_x_bp_p512r1[64] = {
+	0x2a, 0x60, 0x32, 0x27, 0xa1, 0xe6, 0x94, 0x72,
+	0x1c, 0x48, 0xbe, 0xc5, 0x77, 0x14, 0x30, 0x76,
+	0xe4, 0xbf, 0xf7, 0x7b, 0xc5, 0xfd, 0xdf, 0x19,
+	0x1e, 0x0f, 0xdf, 0x1c, 0x40, 0xfa, 0x34, 0x9e,
+	0x1f, 0x42, 0x24, 0xa3, 0x2c, 0xd5, 0xc7, 0xc9,
+	0x7b, 0x47, 0x78, 0x96, 0xf1, 0x37, 0x0e, 0x88,
+	0xcb, 0xa6, 0x52, 0x29, 0xd7, 0xa8, 0x38, 0x29,
+	0x8e, 0x6e, 0x23, 0x47, 0xd4, 0x4b, 0x70, 0x3e
+};
+static const u8 pkex_resp_y_bp_p512r1[64] = {
+	0x80, 0x1f, 0x43, 0xd2, 0x17, 0x35, 0xec, 0x81,
+	0xd9, 0x4b, 0xdc, 0x81, 0x19, 0xd9, 0x5f, 0x68,
+	0x16, 0x84, 0xfe, 0x63, 0x4b, 0x8d, 0x5d, 0xaa,
+	0x88, 0x4a, 0x47, 0x48, 0xd4, 0xea, 0xab, 0x7d,
+	0x6a, 0xbf, 0xe1, 0x28, 0x99, 0x6a, 0x87, 0x1c,
+	0x30, 0xb4, 0x44, 0x2d, 0x75, 0xac, 0x35, 0x09,
+	0x73, 0x24, 0x3d, 0xb4, 0x43, 0xb1, 0xc1, 0x56,
+	0x56, 0xad, 0x30, 0x87, 0xf4, 0xc3, 0x00, 0xc7
+};
+
+
+static void dpp_debug_print_point(const char *title, const EC_GROUP *group,
+				  const EC_POINT *point)
+{
+	BIGNUM *x, *y;
+	BN_CTX *ctx;
+	char *x_str = NULL, *y_str = NULL;
+
+	if (!wpa_debug_show_keys)
+		return;
+
+	ctx = BN_CTX_new();
+	x = BN_new();
+	y = BN_new();
+	if (!ctx || !x || !y ||
+	    EC_POINT_get_affine_coordinates_GFp(group, point, x, y, ctx) != 1)
+		goto fail;
+
+	x_str = BN_bn2hex(x);
+	y_str = BN_bn2hex(y);
+	if (!x_str || !y_str)
+		goto fail;
+
+	wpa_printf(MSG_DEBUG, "%s (%s,%s)", title, x_str, y_str);
+
+fail:
+	OPENSSL_free(x_str);
+	OPENSSL_free(y_str);
+	BN_free(x);
+	BN_free(y);
+	BN_CTX_free(ctx);
+}
+
+
+static int dpp_hash_vector(const struct dpp_curve_params *curve,
+			   size_t num_elem, const u8 *addr[], const size_t *len,
+			   u8 *mac)
+{
+	if (curve->hash_len == 32)
+		return sha256_vector(num_elem, addr, len, mac);
+	if (curve->hash_len == 48)
+		return sha384_vector(num_elem, addr, len, mac);
+	if (curve->hash_len == 64)
+		return sha512_vector(num_elem, addr, len, mac);
+	return -1;
+}
+
+
+static int dpp_hkdf_expand(size_t hash_len, const u8 *secret, size_t secret_len,
+			   const char *label, u8 *out, size_t outlen)
+{
+	if (hash_len == 32)
+		return hmac_sha256_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	if (hash_len == 48)
+		return hmac_sha384_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	if (hash_len == 64)
+		return hmac_sha512_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	return -1;
+}
+
+
+static int dpp_hmac_vector(size_t hash_len, const u8 *key, size_t key_len,
+			   size_t num_elem, const u8 *addr[],
+			   const size_t *len, u8 *mac)
+{
+	if (hash_len == 32)
+		return hmac_sha256_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	if (hash_len == 48)
+		return hmac_sha384_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	if (hash_len == 64)
+		return hmac_sha512_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	return -1;
+}
+
+
+static int dpp_hmac(size_t hash_len, const u8 *key, size_t key_len,
+		    const u8 *data, size_t data_len, u8 *mac)
+{
+	if (hash_len == 32)
+		return hmac_sha256(key, key_len, data, data_len, mac);
+	if (hash_len == 48)
+		return hmac_sha384(key, key_len, data, data_len, mac);
+	if (hash_len == 64)
+		return hmac_sha512(key, key_len, data, data_len, mac);
+	return -1;
+}
+
+
+static int dpp_bn2bin_pad(const BIGNUM *bn, u8 *pos, size_t len)
+{
+	int num_bytes, offset;
+
+	num_bytes = BN_num_bytes(bn);
+	if ((size_t) num_bytes > len)
+		return -1;
+	offset = len - num_bytes;
+	os_memset(pos, 0, offset);
+	BN_bn2bin(bn, pos + offset);
+	return 0;
+}
+
+
+static struct wpabuf * dpp_get_pubkey_point(EVP_PKEY *pkey, int prefix)
+{
+	int len, res;
+	EC_KEY *eckey;
+	struct wpabuf *buf;
+	unsigned char *pos;
+
+	eckey = EVP_PKEY_get1_EC_KEY(pkey);
+	if (!eckey)
+		return NULL;
+	EC_KEY_set_conv_form(eckey, POINT_CONVERSION_UNCOMPRESSED);
+	len = i2o_ECPublicKey(eckey, NULL);
+	if (len <= 0) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to determine public key encoding length");
+		EC_KEY_free(eckey);
+		return NULL;
+	}
+
+	buf = wpabuf_alloc(len);
+	if (!buf) {
+		EC_KEY_free(eckey);
+		return NULL;
+	}
+
+	pos = wpabuf_put(buf, len);
+	res = i2o_ECPublicKey(eckey, &pos);
+	EC_KEY_free(eckey);
+	if (res != len) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to encode public key (res=%d/%d)",
+			   res, len);
+		wpabuf_free(buf);
+		return NULL;
+	}
+
+	if (!prefix) {
+		/* Remove 0x04 prefix to match DPP definition */
+		pos = wpabuf_mhead(buf);
+		os_memmove(pos, pos + 1, len - 1);
+		buf->used--;
+	}
+
+	return buf;
+}
+
+
+static EVP_PKEY * dpp_set_pubkey_point_group(const EC_GROUP *group,
+					     const u8 *buf_x, const u8 *buf_y,
+					     size_t len)
+{
+	EC_KEY *eckey = NULL;
+	BN_CTX *ctx;
+	EC_POINT *point = NULL;
+	BIGNUM *x = NULL, *y = NULL;
+	EVP_PKEY *pkey = NULL;
+
+	ctx = BN_CTX_new();
+	if (!ctx) {
+		wpa_printf(MSG_ERROR, "DPP: Out of memory");
+		return NULL;
+	}
+
+	point = EC_POINT_new(group);
+	x = BN_bin2bn(buf_x, len, NULL);
+	y = BN_bin2bn(buf_y, len, NULL);
+	if (!point || !x || !y) {
+		wpa_printf(MSG_ERROR, "DPP: Out of memory");
+		goto fail;
+	}
+
+	if (!EC_POINT_set_affine_coordinates_GFp(group, point, x, y, ctx)) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: OpenSSL: EC_POINT_set_affine_coordinates_GFp failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	if (!EC_POINT_is_on_curve(group, point, ctx) ||
+	    EC_POINT_is_at_infinity(group, point)) {
+		wpa_printf(MSG_ERROR, "DPP: Invalid point");
+		goto fail;
+	}
+	dpp_debug_print_point("DPP: dpp_set_pubkey_point_group", group, point);
+
+	eckey = EC_KEY_new();
+	if (!eckey ||
+	    EC_KEY_set_group(eckey, group) != 1 ||
+	    EC_KEY_set_public_key(eckey, point) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to set EC_KEY: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+
+	pkey = EVP_PKEY_new();
+	if (!pkey || EVP_PKEY_set1_EC_KEY(pkey, eckey) != 1) {
+		wpa_printf(MSG_ERROR, "DPP: Could not create EVP_PKEY");
+		goto fail;
+	}
+
+out:
+	BN_free(x);
+	BN_free(y);
+	EC_KEY_free(eckey);
+	EC_POINT_free(point);
+	BN_CTX_free(ctx);
+	return pkey;
+fail:
+	EVP_PKEY_free(pkey);
+	pkey = NULL;
+	goto out;
+}
+
+
+static EVP_PKEY * dpp_set_pubkey_point(EVP_PKEY *group_key,
+				       const u8 *buf, size_t len)
+{
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	EVP_PKEY *pkey = NULL;
+
+	if (len & 1)
+		return NULL;
+
+	eckey = EVP_PKEY_get1_EC_KEY(group_key);
+	if (!eckey) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Could not get EC_KEY from group_key");
+		return NULL;
+	}
+
+	group = EC_KEY_get0_group(eckey);
+	if (group)
+		pkey = dpp_set_pubkey_point_group(group, buf, buf + len / 2,
+						  len / 2);
+	else
+		wpa_printf(MSG_ERROR, "DPP: Could not get EC group");
+
+	EC_KEY_free(eckey);
+	return pkey;
+}
+
+
+static void dpp_auth_fail(struct dpp_authentication *auth, const char *txt)
+{
+	wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_FAIL "%s", txt);
+}
+
+
+struct wpabuf * dpp_alloc_msg(enum dpp_public_action_frame_type type,
+			      size_t len)
+{
+	struct wpabuf *msg;
+
+	msg = wpabuf_alloc(8 + len);
+	if (!msg)
+		return NULL;
+	wpabuf_put_u8(msg, WLAN_ACTION_PUBLIC);
+	wpabuf_put_u8(msg, WLAN_PA_VENDOR_SPECIFIC);
+	wpabuf_put_be24(msg, OUI_WFA);
+	wpabuf_put_u8(msg, DPP_OUI_TYPE);
+	wpabuf_put_u8(msg, 1); /* Crypto Suite */
+	wpabuf_put_u8(msg, type);
+	return msg;
+}
+
+
+const u8 * dpp_get_attr(const u8 *buf, size_t len, u16 req_id, u16 *ret_len)
+{
+	u16 id, alen;
+	const u8 *pos = buf, *end = buf + len;
+
+	while (end - pos >= 4) {
+		id = WPA_GET_LE16(pos);
+		pos += 2;
+		alen = WPA_GET_LE16(pos);
+		pos += 2;
+		if (alen > end - pos)
+			return NULL;
+		if (id == req_id) {
+			*ret_len = alen;
+			return pos;
+		}
+		pos += alen;
+	}
+
+	return NULL;
+}
+
+
+int dpp_check_attrs(const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+	int wrapped_data = 0;
+
+	pos = buf;
+	end = buf + len;
+	while (end - pos >= 4) {
+		u16 id, alen;
+
+		id = WPA_GET_LE16(pos);
+		pos += 2;
+		alen = WPA_GET_LE16(pos);
+		pos += 2;
+		wpa_printf(MSG_MSGDUMP, "DPP: Attribute ID %04x len %u",
+			   id, alen);
+		if (alen > end - pos) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Truncated message - not enough room for the attribute - dropped");
+			return -1;
+		}
+		if (wrapped_data) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: An unexpected attribute included after the Wrapped Data attribute");
+			return -1;
+		}
+		if (id == DPP_ATTR_WRAPPED_DATA)
+			wrapped_data = 1;
+		pos += alen;
+	}
+
+	if (end != pos) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected octets (%d) after the last attribute",
+			   (int) (end - pos));
+		return -1;
+	}
+
+	return 0;
+}
+
+
+void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info)
+{
+	if (!info)
+		return;
+	os_free(info->uri);
+	os_free(info->info);
+	EVP_PKEY_free(info->pubkey);
+	os_free(info);
+}
+
+
+const char * dpp_bootstrap_type_txt(enum dpp_bootstrap_type type)
+{
+	switch (type) {
+	case DPP_BOOTSTRAP_QR_CODE:
+		return "QRCODE";
+	case DPP_BOOTSTRAP_PKEX:
+		return "PKEX";
+	}
+	return "??";
+}
+
+
+static int dpp_uri_valid_info(const char *info)
+{
+	while (*info) {
+		unsigned char val = *info++;
+
+		if (val < 0x20 || val > 0x7e || val == 0x3b)
+			return 0;
+	}
+
+	return 1;
+}
+
+
+static int dpp_clone_uri(struct dpp_bootstrap_info *bi, const char *uri)
+{
+	bi->uri = os_strdup(uri);
+	return bi->uri ? 0 : -1;
+}
+
+
+int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi,
+			    const char *chan_list)
+{
+	const char *pos = chan_list;
+	int opclass, channel, freq;
+
+	while (pos && *pos && *pos != ';') {
+		opclass = atoi(pos);
+		if (opclass <= 0)
+			goto fail;
+		pos = os_strchr(pos, '/');
+		if (!pos)
+			goto fail;
+		pos++;
+		channel = atoi(pos);
+		if (channel <= 0)
+			goto fail;
+		while (*pos >= '0' && *pos <= '9')
+			pos++;
+		freq = ieee80211_chan_to_freq(NULL, opclass, channel);
+		wpa_printf(MSG_DEBUG,
+			   "DPP: URI channel-list: opclass=%d channel=%d ==> freq=%d",
+			   opclass, channel, freq);
+		if (freq < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Ignore unknown URI channel-list channel (opclass=%d channel=%d)",
+				   opclass, channel);
+		} else if (bi->num_freq == DPP_BOOTSTRAP_MAX_FREQ) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Too many channels in URI channel-list - ignore list");
+			bi->num_freq = 0;
+			break;
+		} else {
+			bi->freq[bi->num_freq++] = freq;
+		}
+
+		if (*pos == ';' || *pos == '\0')
+			break;
+		if (*pos != ',')
+			goto fail;
+		pos++;
+	}
+
+	return 0;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: Invalid URI channel-list");
+	return -1;
+}
+
+
+int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac)
+{
+	if (!mac)
+		return 0;
+
+	if (hwaddr_aton2(mac, bi->mac_addr) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid URI mac");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: URI mac: " MACSTR, MAC2STR(bi->mac_addr));
+
+	return 0;
+}
+
+
+int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info)
+{
+	const char *end;
+
+	if (!info)
+		return 0;
+
+	end = os_strchr(info, ';');
+	if (!end)
+		end = info + os_strlen(info);
+	bi->info = os_malloc(end - info + 1);
+	if (!bi->info)
+		return -1;
+	os_memcpy(bi->info, info, end - info);
+	bi->info[end - info] = '\0';
+	wpa_printf(MSG_DEBUG, "DPP: URI(information): %s", bi->info);
+	if (!dpp_uri_valid_info(bi->info)) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid URI information payload");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_oid(const ASN1_OBJECT *poid)
+{
+	ASN1_OBJECT *oid;
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		oid = OBJ_txt2obj(dpp_curves[i].name, 0);
+		if (oid && OBJ_cmp(poid, oid) == 0)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static const struct dpp_curve_params * dpp_get_curve_nid(int nid)
+{
+	int i, tmp;
+
+	if (!nid)
+		return NULL;
+	for (i = 0; dpp_curves[i].name; i++) {
+		tmp = OBJ_txt2nid(dpp_curves[i].name);
+		if (tmp == nid)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static int dpp_parse_uri_pk(struct dpp_bootstrap_info *bi, const char *info)
+{
+	const char *end;
+	u8 *data;
+	size_t data_len;
+	EVP_PKEY *pkey;
+	const unsigned char *p;
+	int res;
+	X509_PUBKEY *pub = NULL;
+	ASN1_OBJECT *ppkalg;
+	const unsigned char *pk;
+	int ppklen;
+	X509_ALGOR *pa;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+	ASN1_OBJECT *pa_oid;
+#else
+	const ASN1_OBJECT *pa_oid;
+#endif
+	const void *pval;
+	int ptype;
+	const ASN1_OBJECT *poid;
+	char buf[100];
+
+	end = os_strchr(info, ';');
+	if (!end)
+		return -1;
+
+	data = base64_decode((const unsigned char *) info, end - info,
+			     &data_len);
+	if (!data) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid base64 encoding on URI public-key");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Base64 decoded URI public-key",
+		    data, data_len);
+
+	if (sha256_vector(1, (const u8 **) &data, &data_len,
+			  bi->pubkey_hash) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Public key hash",
+		    bi->pubkey_hash, SHA256_MAC_LEN);
+
+	/* DER encoded ASN.1 SubjectPublicKeyInfo
+	 *
+	 * SubjectPublicKeyInfo  ::=  SEQUENCE  {
+	 *      algorithm            AlgorithmIdentifier,
+	 *      subjectPublicKey     BIT STRING  }
+	 *
+	 * AlgorithmIdentifier  ::=  SEQUENCE  {
+	 *      algorithm               OBJECT IDENTIFIER,
+	 *      parameters              ANY DEFINED BY algorithm OPTIONAL  }
+	 *
+	 * subjectPublicKey = compressed format public key per ANSI X9.63
+	 * algorithm = ecPublicKey (1.2.840.10045.2.1)
+	 * parameters = shall be present and shall be OBJECT IDENTIFIER; e.g.,
+	 *       prime256v1 (1.2.840.10045.3.1.7)
+	 */
+
+	p = data;
+	pkey = d2i_PUBKEY(NULL, &p, data_len);
+	os_free(data);
+
+	if (!pkey) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not parse URI public-key SubjectPublicKeyInfo");
+		return -1;
+	}
+
+	if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: SubjectPublicKeyInfo does not describe an EC key");
+		EVP_PKEY_free(pkey);
+		return -1;
+	}
+
+	res = X509_PUBKEY_set(&pub, pkey);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not set pubkey");
+		goto fail;
+	}
+
+	res = X509_PUBKEY_get0_param(&ppkalg, &pk, &ppklen, &pa, pub);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo parameters");
+		goto fail;
+	}
+	res = OBJ_obj2txt(buf, sizeof(buf), ppkalg, 0);
+	if (res < 0 || (size_t) res >= sizeof(buf)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo algorithm");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey algorithm: %s", buf);
+	if (os_strcmp(buf, "id-ecPublicKey") != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported SubjectPublicKeyInfo algorithm");
+		goto fail;
+	}
+
+	X509_ALGOR_get0(&pa_oid, &ptype, (void *) &pval, pa);
+	if (ptype != V_ASN1_OBJECT) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: SubjectPublicKeyInfo parameters did not contain an OID");
+		goto fail;
+	}
+	poid = pval;
+	res = OBJ_obj2txt(buf, sizeof(buf), poid, 0);
+	if (res < 0 || (size_t) res >= sizeof(buf)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo parameters OID");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey parameters: %s", buf);
+	bi->curve = dpp_get_curve_oid(poid);
+	if (!bi->curve) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported SubjectPublicKeyInfo curve: %s",
+			   buf);
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: URI subjectPublicKey", pk, ppklen);
+
+	X509_PUBKEY_free(pub);
+	bi->pubkey = pkey;
+	return 0;
+fail:
+	X509_PUBKEY_free(pub);
+	EVP_PKEY_free(pkey);
+	return -1;
+}
+
+
+static struct dpp_bootstrap_info * dpp_parse_uri(const char *uri)
+{
+	const char *pos = uri;
+	const char *end;
+	const char *chan_list = NULL, *mac = NULL, *info = NULL, *pk = NULL;
+	struct dpp_bootstrap_info *bi;
+
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: URI", uri, os_strlen(uri));
+
+	if (os_strncmp(pos, "DPP:", 4) != 0) {
+		wpa_printf(MSG_INFO, "DPP: Not a DPP URI");
+		return NULL;
+	}
+	pos += 4;
+
+	for (;;) {
+		end = os_strchr(pos, ';');
+		if (!end)
+			break;
+
+		if (end == pos) {
+			/* Handle terminating ";;" and ignore unexpected ";"
+			 * for parsing robustness. */
+			pos++;
+			continue;
+		}
+
+		if (pos[0] == 'C' && pos[1] == ':' && !chan_list)
+			chan_list = pos + 2;
+		else if (pos[0] == 'M' && pos[1] == ':' && !mac)
+			mac = pos + 2;
+		else if (pos[0] == 'I' && pos[1] == ':' && !info)
+			info = pos + 2;
+		else if (pos[0] == 'K' && pos[1] == ':' && !pk)
+			pk = pos + 2;
+		else
+			wpa_hexdump_ascii(MSG_DEBUG,
+					  "DPP: Ignore unrecognized URI parameter",
+					  pos, end - pos);
+		pos = end + 1;
+	}
+
+	if (!pk) {
+		wpa_printf(MSG_INFO, "DPP: URI missing public-key");
+		return NULL;
+	}
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		return NULL;
+
+	if (dpp_clone_uri(bi, uri) < 0 ||
+	    dpp_parse_uri_chan_list(bi, chan_list) < 0 ||
+	    dpp_parse_uri_mac(bi, mac) < 0 ||
+	    dpp_parse_uri_info(bi, info) < 0 ||
+	    dpp_parse_uri_pk(bi, pk) < 0) {
+		dpp_bootstrap_info_free(bi);
+		bi = NULL;
+	}
+
+	return bi;
+}
+
+
+struct dpp_bootstrap_info * dpp_parse_qr_code(const char *uri)
+{
+	struct dpp_bootstrap_info *bi;
+
+	bi = dpp_parse_uri(uri);
+	if (bi)
+		bi->type = DPP_BOOTSTRAP_QR_CODE;
+	return bi;
+}
+
+
+static void dpp_debug_print_key(const char *title, EVP_PKEY *key)
+{
+	EC_KEY *eckey;
+	BIO *out;
+	size_t rlen;
+	char *txt;
+	int res;
+	unsigned char *der = NULL;
+	int der_len;
+	const EC_GROUP *group;
+	const EC_POINT *point;
+
+	out = BIO_new(BIO_s_mem());
+	if (!out)
+		return;
+
+	EVP_PKEY_print_private(out, key, 0, NULL);
+	rlen = BIO_ctrl_pending(out);
+	txt = os_malloc(rlen + 1);
+	if (txt) {
+		res = BIO_read(out, txt, rlen);
+		if (res > 0) {
+			txt[res] = '\0';
+			wpa_printf(MSG_DEBUG, "%s: %s", title, txt);
+		}
+		os_free(txt);
+	}
+	BIO_free(out);
+
+	eckey = EVP_PKEY_get1_EC_KEY(key);
+	if (!eckey)
+		return;
+
+	group = EC_KEY_get0_group(eckey);
+	point = EC_KEY_get0_public_key(eckey);
+	if (group && point)
+		dpp_debug_print_point(title, group, point);
+
+	der_len = i2d_ECPrivateKey(eckey, &der);
+	if (der_len > 0)
+		wpa_hexdump_key(MSG_DEBUG, "DPP: ECPrivateKey", der, der_len);
+	OPENSSL_free(der);
+	if (der_len <= 0) {
+		der = NULL;
+		der_len = i2d_EC_PUBKEY(eckey, &der);
+		if (der_len > 0)
+			wpa_hexdump(MSG_DEBUG, "DPP: EC_PUBKEY", der, der_len);
+		OPENSSL_free(der);
+	}
+
+	EC_KEY_free(eckey);
+}
+
+
+static EVP_PKEY * dpp_gen_keypair(const struct dpp_curve_params *curve)
+{
+	EVP_PKEY_CTX *kctx = NULL;
+	EC_KEY *ec_params;
+	EVP_PKEY *params = NULL, *key = NULL;
+	int nid;
+
+	wpa_printf(MSG_DEBUG, "DPP: Generating a keypair");
+
+	nid = OBJ_txt2nid(curve->name);
+	if (nid == NID_undef) {
+		wpa_printf(MSG_INFO, "DPP: Unsupported curve %s", curve->name);
+		return NULL;
+	}
+
+	ec_params = EC_KEY_new_by_curve_name(nid);
+	if (!ec_params) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to generate EC_KEY parameters");
+		goto fail;
+	}
+	EC_KEY_set_asn1_flag(ec_params, OPENSSL_EC_NAMED_CURVE);
+	params = EVP_PKEY_new();
+	if (!params || EVP_PKEY_set1_EC_KEY(params, ec_params) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to generate EVP_PKEY parameters");
+		goto fail;
+	}
+
+	kctx = EVP_PKEY_CTX_new(params, NULL);
+	if (!kctx ||
+	    EVP_PKEY_keygen_init(kctx) != 1 ||
+	    EVP_PKEY_keygen(kctx, &key) != 1) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate EC key");
+		goto fail;
+	}
+
+	if (wpa_debug_show_keys)
+		dpp_debug_print_key("Own generated key", key);
+
+	EVP_PKEY_free(params);
+	EVP_PKEY_CTX_free(kctx);
+	return key;
+fail:
+	EVP_PKEY_CTX_free(kctx);
+	EVP_PKEY_free(params);
+	return NULL;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_name(const char *name)
+{
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		if (os_strcmp(name, dpp_curves[i].name) == 0 ||
+		    (dpp_curves[i].jwk_crv &&
+		     os_strcmp(name, dpp_curves[i].jwk_crv) == 0))
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_jwk_crv(const char *name)
+{
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		if (dpp_curves[i].jwk_crv &&
+		    os_strcmp(name, dpp_curves[i].jwk_crv) == 0)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static EVP_PKEY * dpp_set_keypair(const struct dpp_curve_params **curve,
+				  const u8 *privkey, size_t privkey_len)
+{
+	EVP_PKEY *pkey;
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	int nid;
+
+	pkey = EVP_PKEY_new();
+	if (!pkey)
+		return NULL;
+	eckey = d2i_ECPrivateKey(NULL, &privkey, privkey_len);
+	if (!eckey) {
+		wpa_printf(MSG_INFO,
+			   "DPP: OpenSSL: d2i_ECPrivateKey() failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	group = EC_KEY_get0_group(eckey);
+	if (!group) {
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	nid = EC_GROUP_get_curve_name(group);
+	*curve = dpp_get_curve_nid(nid);
+	if (!*curve) {
+		wpa_printf(MSG_INFO,
+			   "DPP: Unsupported curve (nid=%d) in pre-assigned key",
+			   nid);
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+
+	if (EVP_PKEY_assign_EC_KEY(pkey, eckey) != 1) {
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	return pkey;
+}
+
+
+typedef struct {
+	/* AlgorithmIdentifier ecPublicKey with optional parameters present
+	 * as an OID identifying the curve */
+	X509_ALGOR *alg;
+	/* Compressed format public key per ANSI X9.63 */
+	ASN1_BIT_STRING *pub_key;
+} DPP_BOOTSTRAPPING_KEY;
+
+ASN1_SEQUENCE(DPP_BOOTSTRAPPING_KEY) = {
+	ASN1_SIMPLE(DPP_BOOTSTRAPPING_KEY, alg, X509_ALGOR),
+	ASN1_SIMPLE(DPP_BOOTSTRAPPING_KEY, pub_key, ASN1_BIT_STRING)
+} ASN1_SEQUENCE_END(DPP_BOOTSTRAPPING_KEY);
+
+IMPLEMENT_ASN1_FUNCTIONS(DPP_BOOTSTRAPPING_KEY);
+
+
+static struct wpabuf * dpp_bootstrap_key_der(EVP_PKEY *key)
+{
+	unsigned char *der = NULL;
+	int der_len;
+	EC_KEY *eckey;
+	struct wpabuf *ret = NULL;
+	size_t len;
+	const EC_GROUP *group;
+	const EC_POINT *point;
+	BN_CTX *ctx;
+	DPP_BOOTSTRAPPING_KEY *bootstrap = NULL;
+	int nid;
+
+	ctx = BN_CTX_new();
+	eckey = EVP_PKEY_get1_EC_KEY(key);
+	if (!ctx || !eckey)
+		goto fail;
+
+	group = EC_KEY_get0_group(eckey);
+	point = EC_KEY_get0_public_key(eckey);
+	if (!group || !point)
+		goto fail;
+	dpp_debug_print_point("DPP: bootstrap public key", group, point);
+	nid = EC_GROUP_get_curve_name(group);
+
+	bootstrap = DPP_BOOTSTRAPPING_KEY_new();
+	if (!bootstrap ||
+	    X509_ALGOR_set0(bootstrap->alg, OBJ_nid2obj(EVP_PKEY_EC),
+			    V_ASN1_OBJECT, (void *) OBJ_nid2obj(nid)) != 1)
+		goto fail;
+
+	len = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED,
+				 NULL, 0, ctx);
+	if (len == 0)
+		goto fail;
+
+	der = OPENSSL_malloc(len);
+	if (!der)
+		goto fail;
+	len = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED,
+				 der, len, ctx);
+
+	OPENSSL_free(bootstrap->pub_key->data);
+	bootstrap->pub_key->data = der;
+	der = NULL;
+	bootstrap->pub_key->length = len;
+	/* No unused bits */
+	bootstrap->pub_key->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
+	bootstrap->pub_key->flags |= ASN1_STRING_FLAG_BITS_LEFT;
+
+	der_len = i2d_DPP_BOOTSTRAPPING_KEY(bootstrap, &der);
+	if (der_len <= 0) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to build DER encoded public key");
+		goto fail;
+	}
+
+	ret = wpabuf_alloc_copy(der, der_len);
+fail:
+	DPP_BOOTSTRAPPING_KEY_free(bootstrap);
+	OPENSSL_free(der);
+	EC_KEY_free(eckey);
+	BN_CTX_free(ctx);
+	return ret;
+}
+
+
+int dpp_bootstrap_key_hash(struct dpp_bootstrap_info *bi)
+{
+	struct wpabuf *der;
+	int res;
+	const u8 *addr[1];
+	size_t len[1];
+
+	der = dpp_bootstrap_key_der(bi->pubkey);
+	if (!der)
+		return -1;
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: Compressed public key (DER)",
+			der);
+
+	addr[0] = wpabuf_head(der);
+	len[0] = wpabuf_len(der);
+	res = sha256_vector(1, addr, len, bi->pubkey_hash);
+	if (res < 0)
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+	else
+		wpa_hexdump(MSG_DEBUG, "DPP: Public key hash", bi->pubkey_hash,
+			    SHA256_MAC_LEN);
+	wpabuf_free(der);
+	return res;
+}
+
+
+char * dpp_keygen(struct dpp_bootstrap_info *bi, const char *curve,
+		  const u8 *privkey, size_t privkey_len)
+{
+	unsigned char *base64 = NULL;
+	char *pos, *end;
+	size_t len;
+	struct wpabuf *der = NULL;
+	const u8 *addr[1];
+	int res;
+
+	if (!curve) {
+		bi->curve = &dpp_curves[0];
+	} else {
+		bi->curve = dpp_get_curve_name(curve);
+		if (!bi->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return NULL;
+		}
+	}
+	if (privkey)
+		bi->pubkey = dpp_set_keypair(&bi->curve, privkey, privkey_len);
+	else
+		bi->pubkey = dpp_gen_keypair(bi->curve);
+	if (!bi->pubkey)
+		goto fail;
+	bi->own = 1;
+
+	der = dpp_bootstrap_key_der(bi->pubkey);
+	if (!der)
+		goto fail;
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: Compressed public key (DER)",
+			der);
+
+	addr[0] = wpabuf_head(der);
+	len = wpabuf_len(der);
+	res = sha256_vector(1, addr, &len, bi->pubkey_hash);
+	if (res < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Public key hash", bi->pubkey_hash,
+		    SHA256_MAC_LEN);
+
+	base64 = base64_encode(wpabuf_head(der), wpabuf_len(der), &len);
+	wpabuf_free(der);
+	der = NULL;
+	if (!base64)
+		goto fail;
+	pos = (char *) base64;
+	end = pos + len;
+	for (;;) {
+		pos = os_strchr(pos, '\n');
+		if (!pos)
+			break;
+		os_memmove(pos, pos + 1, end - pos);
+	}
+	return (char *) base64;
+fail:
+	os_free(base64);
+	wpabuf_free(der);
+	return NULL;
+}
+
+
+static int dpp_derive_k1(const u8 *Mx, size_t Mx_len, u8 *k1,
+			 unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "first intermediate key";
+	int res;
+
+	/* k1 = HKDF(<>, "first intermediate key", M.x) */
+
+	/* HKDF-Extract(<>, M.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Mx, Mx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=M.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, k1, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: k1 = HKDF-Expand(PRK, info, L)",
+			k1, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_k2(const u8 *Nx, size_t Nx_len, u8 *k2,
+			 unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "second intermediate key";
+	int res;
+
+	/* k2 = HKDF(<>, "second intermediate key", N.x) */
+
+	/* HKDF-Extract(<>, N.x) */
+	os_memset(salt, 0, hash_len);
+	res = dpp_hmac(hash_len, salt, hash_len, Nx, Nx_len, prk);
+	if (res < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=N.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, k2, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: k2 = HKDF-Expand(PRK, info, L)",
+			k2, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_ke(struct dpp_authentication *auth, u8 *ke,
+			 unsigned int hash_len)
+{
+	size_t nonce_len;
+	u8 nonces[2 * DPP_MAX_NONCE_LEN];
+	const char *info_ke = "DPP Key";
+	u8 prk[DPP_MAX_HASH_LEN];
+	int res;
+	const u8 *addr[3];
+	size_t len[3];
+	size_t num_elem = 0;
+
+	if (!auth->Mx_len || !auth->Nx_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mx/Nx not available - cannot derive ke");
+		return -1;
+	}
+
+	/* ke = HKDF(I-nonce | R-nonce, "DPP Key", M.x | N.x [| L.x]) */
+
+	/* HKDF-Extract(I-nonce | R-nonce, M.x | N.x [| L.x]) */
+	nonce_len = auth->curve->nonce_len;
+	os_memcpy(nonces, auth->i_nonce, nonce_len);
+	os_memcpy(&nonces[nonce_len], auth->r_nonce, nonce_len);
+	addr[num_elem] = auth->Mx;
+	len[num_elem] = auth->Mx_len;
+	num_elem++;
+	addr[num_elem] = auth->Nx;
+	len[num_elem] = auth->Nx_len;
+	num_elem++;
+	if (auth->peer_bi && auth->own_bi) {
+		if (!auth->Lx_len) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Lx not available - cannot derive ke");
+			return -1;
+		}
+		addr[num_elem] = auth->Lx;
+		len[num_elem] = auth->secret_len;
+		num_elem++;
+	}
+	res = dpp_hmac_vector(hash_len, nonces, 2 * nonce_len,
+			      num_elem, addr, len, prk);
+	if (res < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info_ke, ke, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ke = HKDF-Expand(PRK, info, L)",
+			ke, hash_len);
+	return 0;
+}
+
+
+static void dpp_build_attr_status(struct wpabuf *msg,
+				  enum dpp_status_error status)
+{
+	wpa_printf(MSG_DEBUG, "DPP: Status %d", status);
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, status);
+}
+
+
+static void dpp_build_attr_r_bootstrap_key_hash(struct wpabuf *msg,
+						const u8 *hash)
+{
+	if (hash) {
+		wpa_printf(MSG_DEBUG, "DPP: R-Bootstrap Key Hash");
+		wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH);
+		wpabuf_put_le16(msg, SHA256_MAC_LEN);
+		wpabuf_put_data(msg, hash, SHA256_MAC_LEN);
+	}
+}
+
+
+static void dpp_build_attr_i_bootstrap_key_hash(struct wpabuf *msg,
+						const u8 *hash)
+{
+	if (hash) {
+		wpa_printf(MSG_DEBUG, "DPP: I-Bootstrap Key Hash");
+		wpabuf_put_le16(msg, DPP_ATTR_I_BOOTSTRAP_KEY_HASH);
+		wpabuf_put_le16(msg, SHA256_MAC_LEN);
+		wpabuf_put_data(msg, hash, SHA256_MAC_LEN);
+	}
+}
+
+
+static struct wpabuf * dpp_auth_build_req(struct dpp_authentication *auth,
+					  const struct wpabuf *pi,
+					  size_t nonce_len,
+					  const u8 *r_pubkey_hash,
+					  const u8 *i_pubkey_hash,
+					  unsigned int neg_freq)
+{
+	struct wpabuf *msg;
+	u8 clear[4 + DPP_MAX_NONCE_LEN + 4 + 1];
+	u8 wrapped_data[4 + DPP_MAX_NONCE_LEN + 4 + 1 + AES_BLOCK_SIZE];
+	u8 *pos;
+	const u8 *addr[2];
+	size_t len[2], siv_len, attr_len;
+	u8 *attr_start, *attr_end;
+
+	/* Build DPP Authentication Request frame attributes */
+	attr_len = 2 * (4 + SHA256_MAC_LEN) + 4 + (pi ? wpabuf_len(pi) : 0) +
+		4 + sizeof(wrapped_data);
+	if (neg_freq > 0)
+		attr_len += 4 + 2;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_REQ)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_REQ, attr_len);
+	if (!msg)
+		return NULL;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* Responder Bootstrapping Key Hash */
+	dpp_build_attr_r_bootstrap_key_hash(msg, r_pubkey_hash);
+
+	/* Initiator Bootstrapping Key Hash */
+	dpp_build_attr_i_bootstrap_key_hash(msg, i_pubkey_hash);
+
+	/* Initiator Protocol Key */
+	if (pi) {
+		wpabuf_put_le16(msg, DPP_ATTR_I_PROTOCOL_KEY);
+		wpabuf_put_le16(msg, wpabuf_len(pi));
+		wpabuf_put_buf(msg, pi);
+	}
+
+	/* Channel */
+	if (neg_freq > 0) {
+		u8 op_class, channel;
+
+		if (ieee80211_freq_to_channel_ext(neg_freq, 0, 0, &op_class,
+						  &channel) ==
+		    NUM_HOSTAPD_MODES) {
+			wpa_printf(MSG_INFO,
+				   "DPP: Unsupported negotiation frequency request: %d",
+				   neg_freq);
+			wpabuf_free(msg);
+			return NULL;
+		}
+		wpabuf_put_le16(msg, DPP_ATTR_CHANNEL);
+		wpabuf_put_le16(msg, 2);
+		wpabuf_put_u8(msg, op_class);
+		wpabuf_put_u8(msg, channel);
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Wrapped data ({I-nonce, I-capabilities}k1) */
+	pos = clear;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_I_NONCE_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-nonce");
+		goto skip_i_nonce;
+	}
+	if (dpp_test == DPP_TEST_INVALID_I_NONCE_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid I-nonce");
+		WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+		pos += 2;
+		WPA_PUT_LE16(pos, nonce_len - 1);
+		pos += 2;
+		os_memcpy(pos, auth->i_nonce, nonce_len - 1);
+		pos += nonce_len - 1;
+		goto skip_i_nonce;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* I-nonce */
+	WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+	pos += 2;
+	WPA_PUT_LE16(pos, nonce_len);
+	pos += 2;
+	os_memcpy(pos, auth->i_nonce, nonce_len);
+	pos += nonce_len;
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_i_nonce:
+	if (dpp_test == DPP_TEST_NO_I_CAPAB_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-capab");
+		goto skip_i_capab;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* I-capabilities */
+	WPA_PUT_LE16(pos, DPP_ATTR_I_CAPABILITIES);
+	pos += 2;
+	WPA_PUT_LE16(pos, 1);
+	pos += 2;
+	auth->i_capab = auth->allowed_roles;
+	*pos++ = auth->i_capab;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_ZERO_I_CAPAB) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - zero I-capabilities");
+		pos[-1] = 0;
+	}
+skip_i_capab:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	siv_len = pos - clear;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", clear, siv_len);
+	if (aes_siv_encrypt(auth->k1, auth->curve->hash_len, clear, siv_len,
+			    2, addr, len, wrapped_data) < 0) {
+		wpabuf_free(msg);
+		return NULL;
+	}
+	siv_len += AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, siv_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, siv_len);
+	wpabuf_put_data(msg, wrapped_data, siv_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Request frame attributes", msg);
+
+	return msg;
+}
+
+
+static struct wpabuf * dpp_auth_build_resp(struct dpp_authentication *auth,
+					   enum dpp_status_error status,
+					   const struct wpabuf *pr,
+					   size_t nonce_len,
+					   const u8 *r_pubkey_hash,
+					   const u8 *i_pubkey_hash,
+					   const u8 *r_nonce, const u8 *i_nonce,
+					   const u8 *wrapped_r_auth,
+					   size_t wrapped_r_auth_len,
+					   const u8 *siv_key)
+{
+	struct wpabuf *msg;
+#define DPP_AUTH_RESP_CLEAR_LEN 2 * (4 + DPP_MAX_NONCE_LEN) + 4 + 1 + \
+		4 + 4 + DPP_MAX_HASH_LEN + AES_BLOCK_SIZE
+	u8 clear[DPP_AUTH_RESP_CLEAR_LEN];
+	u8 wrapped_data[DPP_AUTH_RESP_CLEAR_LEN + AES_BLOCK_SIZE];
+	const u8 *addr[2];
+	size_t len[2], siv_len, attr_len;
+	u8 *attr_start, *attr_end, *pos;
+
+	auth->waiting_auth_conf = 1;
+	auth->auth_resp_tries = 0;
+
+	/* Build DPP Authentication Response frame attributes */
+	attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) +
+		4 + (pr ? wpabuf_len(pr) : 0) + 4 + sizeof(wrapped_data);
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_RESP)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_RESP, attr_len);
+	if (!msg)
+		return NULL;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* DPP Status */
+	if (status != 255)
+		dpp_build_attr_status(msg, status);
+
+	/* Responder Bootstrapping Key Hash */
+	dpp_build_attr_r_bootstrap_key_hash(msg, r_pubkey_hash);
+
+	/* Initiator Bootstrapping Key Hash (mutual authentication) */
+	dpp_build_attr_i_bootstrap_key_hash(msg, i_pubkey_hash);
+
+	/* Responder Protocol Key */
+	if (pr) {
+		wpabuf_put_le16(msg, DPP_ATTR_R_PROTOCOL_KEY);
+		wpabuf_put_le16(msg, wpabuf_len(pr));
+		wpabuf_put_buf(msg, pr);
+	}
+
+	attr_end = wpabuf_put(msg, 0);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Wrapped data ({R-nonce, I-nonce, R-capabilities, {R-auth}ke}k2) */
+	pos = clear;
+
+	if (r_nonce) {
+		/* R-nonce */
+		WPA_PUT_LE16(pos, DPP_ATTR_R_NONCE);
+		pos += 2;
+		WPA_PUT_LE16(pos, nonce_len);
+		pos += 2;
+		os_memcpy(pos, r_nonce, nonce_len);
+		pos += nonce_len;
+	}
+
+	if (i_nonce) {
+		/* I-nonce */
+		WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+		pos += 2;
+		WPA_PUT_LE16(pos, nonce_len);
+		pos += 2;
+		os_memcpy(pos, i_nonce, nonce_len);
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_I_NONCE_MISMATCH_AUTH_RESP) {
+			wpa_printf(MSG_INFO, "DPP: TESTING - I-nonce mismatch");
+			pos[nonce_len / 2] ^= 0x01;
+		}
+#endif /* CONFIG_TESTING_OPTIONS */
+		pos += nonce_len;
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_R_CAPAB_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-capab");
+		goto skip_r_capab;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* R-capabilities */
+	WPA_PUT_LE16(pos, DPP_ATTR_R_CAPABILITIES);
+	pos += 2;
+	WPA_PUT_LE16(pos, 1);
+	pos += 2;
+	auth->r_capab = auth->configurator ? DPP_CAPAB_CONFIGURATOR :
+		DPP_CAPAB_ENROLLEE;
+	*pos++ = auth->r_capab;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_ZERO_R_CAPAB) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - zero R-capabilities");
+		pos[-1] = 0;
+	} else if (dpp_test == DPP_TEST_INCOMPATIBLE_R_CAPAB_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - incompatible R-capabilities");
+		if ((auth->i_capab & DPP_CAPAB_ROLE_MASK) ==
+		    (DPP_CAPAB_CONFIGURATOR | DPP_CAPAB_ENROLLEE))
+			pos[-1] = 0;
+		else
+			pos[-1] = auth->configurator ? DPP_CAPAB_ENROLLEE :
+				DPP_CAPAB_CONFIGURATOR;
+	}
+skip_r_capab:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (wrapped_r_auth) {
+		/* {R-auth}ke */
+		WPA_PUT_LE16(pos, DPP_ATTR_WRAPPED_DATA);
+		pos += 2;
+		WPA_PUT_LE16(pos, wrapped_r_auth_len);
+		pos += 2;
+		os_memcpy(pos, wrapped_r_auth, wrapped_r_auth_len);
+		pos += wrapped_r_auth_len;
+	}
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	siv_len = pos - clear;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", clear, siv_len);
+	if (aes_siv_encrypt(siv_key, auth->curve->hash_len, clear, siv_len,
+			    2, addr, len, wrapped_data) < 0) {
+		wpabuf_free(msg);
+		return NULL;
+	}
+	siv_len += AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, siv_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, siv_len);
+	wpabuf_put_data(msg, wrapped_data, siv_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Response frame attributes", msg);
+	return msg;
+}
+
+
+static int dpp_channel_ok_init(struct hostapd_hw_modes *own_modes,
+			       u16 num_modes, unsigned int freq)
+{
+	u16 m;
+	int c, flag;
+
+	if (!own_modes || !num_modes)
+		return 1;
+
+	for (m = 0; m < num_modes; m++) {
+		for (c = 0; c < own_modes[m].num_channels; c++) {
+			if ((unsigned int) own_modes[m].channels[c].freq !=
+			    freq)
+				continue;
+			flag = own_modes[m].channels[c].flag;
+			if (!(flag & (HOSTAPD_CHAN_DISABLED |
+				      HOSTAPD_CHAN_NO_IR |
+				      HOSTAPD_CHAN_RADAR)))
+				return 1;
+		}
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: Peer channel %u MHz not supported", freq);
+	return 0;
+}
+
+
+static int freq_included(const unsigned int freqs[], unsigned int num,
+			 unsigned int freq)
+{
+	while (num > 0) {
+		if (freqs[--num] == freq)
+			return 1;
+	}
+	return 0;
+}
+
+
+static void freq_to_start(unsigned int freqs[], unsigned int num,
+			  unsigned int freq)
+{
+	unsigned int i;
+
+	for (i = 0; i < num; i++) {
+		if (freqs[i] == freq)
+			break;
+	}
+	if (i == 0 || i >= num)
+		return;
+	os_memmove(&freqs[1], &freqs[0], i * sizeof(freqs[0]));
+	freqs[0] = freq;
+}
+
+
+static int dpp_channel_intersect(struct dpp_authentication *auth,
+				 struct hostapd_hw_modes *own_modes,
+				 u16 num_modes)
+{
+	struct dpp_bootstrap_info *peer_bi = auth->peer_bi;
+	unsigned int i, freq;
+
+	for (i = 0; i < peer_bi->num_freq; i++) {
+		freq = peer_bi->freq[i];
+		if (freq_included(auth->freq, auth->num_freq, freq))
+			continue;
+		if (dpp_channel_ok_init(own_modes, num_modes, freq))
+			auth->freq[auth->num_freq++] = freq;
+	}
+	if (!auth->num_freq) {
+		wpa_printf(MSG_INFO,
+			   "DPP: No available channels for initiating DPP Authentication");
+		return -1;
+	}
+	auth->curr_freq = auth->freq[0];
+	return 0;
+}
+
+
+static int dpp_channel_local_list(struct dpp_authentication *auth,
+				  struct hostapd_hw_modes *own_modes,
+				  u16 num_modes)
+{
+	u16 m;
+	int c, flag;
+	unsigned int freq;
+
+	auth->num_freq = 0;
+
+	if (!own_modes || !num_modes) {
+		auth->freq[0] = 2412;
+		auth->freq[1] = 2437;
+		auth->freq[2] = 2462;
+		auth->num_freq = 3;
+		return 0;
+	}
+
+	for (m = 0; m < num_modes; m++) {
+		for (c = 0; c < own_modes[m].num_channels; c++) {
+			freq = own_modes[m].channels[c].freq;
+			flag = own_modes[m].channels[c].flag;
+			if (flag & (HOSTAPD_CHAN_DISABLED |
+				    HOSTAPD_CHAN_NO_IR |
+				    HOSTAPD_CHAN_RADAR))
+				continue;
+			if (freq_included(auth->freq, auth->num_freq, freq))
+				continue;
+			auth->freq[auth->num_freq++] = freq;
+			if (auth->num_freq == DPP_BOOTSTRAP_MAX_FREQ) {
+				m = num_modes;
+				break;
+			}
+		}
+	}
+
+	return auth->num_freq == 0 ? -1 : 0;
+}
+
+
+static int dpp_prepare_channel_list(struct dpp_authentication *auth,
+				    struct hostapd_hw_modes *own_modes,
+				    u16 num_modes)
+{
+	int res;
+	char freqs[DPP_BOOTSTRAP_MAX_FREQ * 6 + 10], *pos, *end;
+	unsigned int i;
+
+	if (auth->peer_bi->num_freq > 0)
+		res = dpp_channel_intersect(auth, own_modes, num_modes);
+	else
+		res = dpp_channel_local_list(auth, own_modes, num_modes);
+	if (res < 0)
+		return res;
+
+	/* Prioritize 2.4 GHz channels 6, 1, 11 (in this order) to hit the most
+	 * likely channels first. */
+	freq_to_start(auth->freq, auth->num_freq, 2462);
+	freq_to_start(auth->freq, auth->num_freq, 2412);
+	freq_to_start(auth->freq, auth->num_freq, 2437);
+
+	auth->freq_idx = 0;
+	auth->curr_freq = auth->freq[0];
+
+	pos = freqs;
+	end = pos + sizeof(freqs);
+	for (i = 0; i < auth->num_freq; i++) {
+		res = os_snprintf(pos, end - pos, " %u", auth->freq[i]);
+		if (os_snprintf_error(end - pos, res))
+			break;
+		pos += res;
+	}
+	*pos = '\0';
+	wpa_printf(MSG_DEBUG, "DPP: Possible frequencies for initiating:%s",
+		   freqs);
+
+	return 0;
+}
+
+
+static int dpp_autogen_bootstrap_key(struct dpp_authentication *auth)
+{
+	struct dpp_bootstrap_info *bi;
+	char *pk = NULL;
+	size_t len;
+
+	if (auth->own_bi)
+		return 0; /* already generated */
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		return -1;
+	bi->type = DPP_BOOTSTRAP_QR_CODE;
+	pk = dpp_keygen(bi, auth->peer_bi->curve->name, NULL, 0);
+	if (!pk)
+		goto fail;
+
+	len = 4; /* "DPP:" */
+	len += 4 + os_strlen(pk);
+	bi->uri = os_malloc(len + 1);
+	if (!bi->uri)
+		goto fail;
+	os_snprintf(bi->uri, len + 1, "DPP:K:%s;;", pk);
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Auto-generated own bootstrapping key info: URI %s",
+		   bi->uri);
+
+	auth->tmp_own_bi = auth->own_bi = bi;
+
+	os_free(pk);
+
+	return 0;
+fail:
+	os_free(pk);
+	dpp_bootstrap_info_free(bi);
+	return -1;
+}
+
+
+struct dpp_authentication * dpp_auth_init(void *msg_ctx,
+					  struct dpp_bootstrap_info *peer_bi,
+					  struct dpp_bootstrap_info *own_bi,
+					  u8 dpp_allowed_roles,
+					  unsigned int neg_freq,
+					  struct hostapd_hw_modes *own_modes,
+					  u16 num_modes)
+{
+	struct dpp_authentication *auth;
+	size_t nonce_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	struct wpabuf *pi = NULL;
+	const u8 *r_pubkey_hash, *i_pubkey_hash;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 test_hash[SHA256_MAC_LEN];
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	auth = os_zalloc(sizeof(*auth));
+	if (!auth)
+		return NULL;
+	auth->msg_ctx = msg_ctx;
+	auth->initiator = 1;
+	auth->waiting_auth_resp = 1;
+	auth->allowed_roles = dpp_allowed_roles;
+	auth->configurator = !!(dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR);
+	auth->peer_bi = peer_bi;
+	auth->own_bi = own_bi;
+	auth->curve = peer_bi->curve;
+
+	if (dpp_autogen_bootstrap_key(auth) < 0 ||
+	    dpp_prepare_channel_list(auth, own_modes, num_modes) < 0)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_nonce_override_len > 0) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - override I-nonce");
+		nonce_len = dpp_nonce_override_len;
+		os_memcpy(auth->i_nonce, dpp_nonce_override, nonce_len);
+	} else {
+		nonce_len = auth->curve->nonce_len;
+		if (random_get_bytes(auth->i_nonce, nonce_len)) {
+			wpa_printf(MSG_ERROR,
+				   "DPP: Failed to generate I-nonce");
+			goto fail;
+		}
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->i_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate I-nonce");
+		goto fail;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", auth->i_nonce, nonce_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_protocol_key_override_len) {
+		const struct dpp_curve_params *tmp_curve;
+
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - override protocol key");
+		auth->own_protocol_key = dpp_set_keypair(
+			&tmp_curve, dpp_protocol_key_override,
+			dpp_protocol_key_override_len);
+	} else {
+		auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (!auth->own_protocol_key)
+		goto fail;
+
+	pi = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+	if (!pi)
+		goto fail;
+
+	/* ECDH: M = pI * BR */
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, auth->peer_bi->pubkey) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Mx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	auth->secret_len = secret_len;
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (M.x)",
+			auth->Mx, auth->secret_len);
+	auth->Mx_len = auth->secret_len;
+
+	if (dpp_derive_k1(auth->Mx, auth->secret_len, auth->k1,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	r_pubkey_hash = auth->peer_bi->pubkey_hash;
+	i_pubkey_hash = auth->own_bi->pubkey_hash;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Bootstrap Key Hash");
+		r_pubkey_hash = NULL;
+	} else if (dpp_test == DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_REQ) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid R-Bootstrap Key Hash");
+		os_memcpy(test_hash, r_pubkey_hash, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		r_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Bootstrap Key Hash");
+		i_pubkey_hash = NULL;
+	} else if (dpp_test == DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_REQ) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid I-Bootstrap Key Hash");
+		os_memcpy(test_hash, i_pubkey_hash, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		i_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_I_PROTO_KEY_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Proto Key");
+		wpabuf_free(pi);
+		pi = NULL;
+	} else if (dpp_test == DPP_TEST_INVALID_I_PROTO_KEY_AUTH_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid I-Proto Key");
+		wpabuf_free(pi);
+		pi = wpabuf_alloc(2 * auth->curve->prime_len);
+		if (!pi || dpp_test_gen_invalid_key(pi, auth->curve) < 0)
+			goto fail;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	auth->req_msg = dpp_auth_build_req(auth, pi, nonce_len, r_pubkey_hash,
+					   i_pubkey_hash, neg_freq);
+	if (!auth->req_msg)
+		goto fail;
+
+out:
+	wpabuf_free(pi);
+	EVP_PKEY_CTX_free(ctx);
+	return auth;
+fail:
+	dpp_auth_deinit(auth);
+	auth = NULL;
+	goto out;
+}
+
+
+struct wpabuf * dpp_build_conf_req(struct dpp_authentication *auth,
+				   const char *json)
+{
+	size_t nonce_len;
+	size_t json_len, clear_len;
+	struct wpabuf *clear = NULL, *msg = NULL;
+	u8 *wrapped;
+	size_t attr_len;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build configuration request");
+
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->e_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate E-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: E-nonce", auth->e_nonce, nonce_len);
+	json_len = os_strlen(json);
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: configAttr JSON", json, json_len);
+
+	/* { E-nonce, configAttrib }ke */
+	clear_len = 4 + nonce_len + 4 + json_len;
+	clear = wpabuf_alloc(clear_len);
+	attr_len = 4 + clear_len + AES_BLOCK_SIZE;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_REQ)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = wpabuf_alloc(attr_len);
+	if (!clear || !msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_E_NONCE_CONF_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no E-nonce");
+		goto skip_e_nonce;
+	}
+	if (dpp_test == DPP_TEST_INVALID_E_NONCE_CONF_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid E-nonce");
+		wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+		wpabuf_put_le16(clear, nonce_len - 1);
+		wpabuf_put_data(clear, auth->e_nonce, nonce_len - 1);
+		goto skip_e_nonce;
+	}
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_CONF_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* E-nonce */
+	wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+	wpabuf_put_le16(clear, nonce_len);
+	wpabuf_put_data(clear, auth->e_nonce, nonce_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_e_nonce:
+	if (dpp_test == DPP_TEST_NO_CONFIG_ATTR_OBJ_CONF_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no configAttrib");
+		goto skip_conf_attr_obj;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* configAttrib */
+	wpabuf_put_le16(clear, DPP_ATTR_CONFIG_ATTR_OBJ);
+	wpabuf_put_le16(clear, json_len);
+	wpabuf_put_data(clear, json, json_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_conf_attr_obj:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	/* No AES-SIV AD */
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    0, NULL, NULL, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Configuration Request frame attributes", msg);
+	wpabuf_free(clear);
+	return msg;
+
+fail:
+	wpabuf_free(clear);
+	wpabuf_free(msg);
+	return NULL;
+}
+
+
+static void dpp_auth_success(struct dpp_authentication *auth)
+{
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Authentication success - clear temporary keys");
+	os_memset(auth->Mx, 0, sizeof(auth->Mx));
+	auth->Mx_len = 0;
+	os_memset(auth->Nx, 0, sizeof(auth->Nx));
+	auth->Nx_len = 0;
+	os_memset(auth->Lx, 0, sizeof(auth->Lx));
+	auth->Lx_len = 0;
+	os_memset(auth->k1, 0, sizeof(auth->k1));
+	os_memset(auth->k2, 0, sizeof(auth->k2));
+
+	auth->auth_success = 1;
+}
+
+
+static int dpp_gen_r_auth(struct dpp_authentication *auth, u8 *r_auth)
+{
+	struct wpabuf *pix, *prx, *bix, *brx;
+	const u8 *addr[7];
+	size_t len[7];
+	size_t i, num_elem = 0;
+	size_t nonce_len;
+	u8 zero = 0;
+	int res = -1;
+
+	/* R-auth = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	nonce_len = auth->curve->nonce_len;
+
+	if (auth->initiator) {
+		pix = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		if (auth->own_bi)
+			bix = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+		else
+			bix = NULL;
+		brx = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+	} else {
+		pix = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		if (auth->peer_bi)
+			bix = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+		else
+			bix = NULL;
+		brx = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+	}
+	if (!pix || !prx || !brx)
+		goto fail;
+
+	addr[num_elem] = auth->i_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = auth->r_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(pix);
+	len[num_elem] = wpabuf_len(pix) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(prx);
+	len[num_elem] = wpabuf_len(prx) / 2;
+	num_elem++;
+
+	if (bix) {
+		addr[num_elem] = wpabuf_head(bix);
+		len[num_elem] = wpabuf_len(bix) / 2;
+		num_elem++;
+	}
+
+	addr[num_elem] = wpabuf_head(brx);
+	len[num_elem] = wpabuf_len(brx) / 2;
+	num_elem++;
+
+	addr[num_elem] = &zero;
+	len[num_elem] = 1;
+	num_elem++;
+
+	wpa_printf(MSG_DEBUG, "DPP: R-auth hash components");
+	for (i = 0; i < num_elem; i++)
+		wpa_hexdump(MSG_DEBUG, "DPP: hash component", addr[i], len[i]);
+	res = dpp_hash_vector(auth->curve, num_elem, addr, len, r_auth);
+	if (res == 0)
+		wpa_hexdump(MSG_DEBUG, "DPP: R-auth", r_auth,
+			    auth->curve->hash_len);
+fail:
+	wpabuf_free(pix);
+	wpabuf_free(prx);
+	wpabuf_free(bix);
+	wpabuf_free(brx);
+	return res;
+}
+
+
+static int dpp_gen_i_auth(struct dpp_authentication *auth, u8 *i_auth)
+{
+	struct wpabuf *pix = NULL, *prx = NULL, *bix = NULL, *brx = NULL;
+	const u8 *addr[7];
+	size_t len[7];
+	size_t i, num_elem = 0;
+	size_t nonce_len;
+	u8 one = 1;
+	int res = -1;
+
+	/* I-auth = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |] 1) */
+	nonce_len = auth->curve->nonce_len;
+
+	if (auth->initiator) {
+		pix = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		if (auth->own_bi)
+			bix = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+		else
+			bix = NULL;
+		if (!auth->peer_bi)
+			goto fail;
+		brx = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+	} else {
+		pix = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		if (auth->peer_bi)
+			bix = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+		else
+			bix = NULL;
+		if (!auth->own_bi)
+			goto fail;
+		brx = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+	}
+	if (!pix || !prx || !brx)
+		goto fail;
+
+	addr[num_elem] = auth->r_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = auth->i_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(prx);
+	len[num_elem] = wpabuf_len(prx) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(pix);
+	len[num_elem] = wpabuf_len(pix) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(brx);
+	len[num_elem] = wpabuf_len(brx) / 2;
+	num_elem++;
+
+	if (bix) {
+		addr[num_elem] = wpabuf_head(bix);
+		len[num_elem] = wpabuf_len(bix) / 2;
+		num_elem++;
+	}
+
+	addr[num_elem] = &one;
+	len[num_elem] = 1;
+	num_elem++;
+
+	wpa_printf(MSG_DEBUG, "DPP: I-auth hash components");
+	for (i = 0; i < num_elem; i++)
+		wpa_hexdump(MSG_DEBUG, "DPP: hash component", addr[i], len[i]);
+	res = dpp_hash_vector(auth->curve, num_elem, addr, len, i_auth);
+	if (res == 0)
+		wpa_hexdump(MSG_DEBUG, "DPP: I-auth", i_auth,
+			    auth->curve->hash_len);
+fail:
+	wpabuf_free(pix);
+	wpabuf_free(prx);
+	wpabuf_free(bix);
+	wpabuf_free(brx);
+	return res;
+}
+
+
+static int dpp_auth_derive_l_responder(struct dpp_authentication *auth)
+{
+	const EC_GROUP *group;
+	EC_POINT *l = NULL;
+	EC_KEY *BI = NULL, *bR = NULL, *pR = NULL;
+	const EC_POINT *BI_point;
+	BN_CTX *bnctx;
+	BIGNUM *lx, *sum, *q;
+	const BIGNUM *bR_bn, *pR_bn;
+	int ret = -1;
+
+	/* L = ((bR + pR) modulo q) * BI */
+
+	bnctx = BN_CTX_new();
+	sum = BN_new();
+	q = BN_new();
+	lx = BN_new();
+	if (!bnctx || !sum || !q || !lx)
+		goto fail;
+	BI = EVP_PKEY_get1_EC_KEY(auth->peer_bi->pubkey);
+	if (!BI)
+		goto fail;
+	BI_point = EC_KEY_get0_public_key(BI);
+	group = EC_KEY_get0_group(BI);
+	if (!group)
+		goto fail;
+
+	bR = EVP_PKEY_get1_EC_KEY(auth->own_bi->pubkey);
+	pR = EVP_PKEY_get1_EC_KEY(auth->own_protocol_key);
+	if (!bR || !pR)
+		goto fail;
+	bR_bn = EC_KEY_get0_private_key(bR);
+	pR_bn = EC_KEY_get0_private_key(pR);
+	if (!bR_bn || !pR_bn)
+		goto fail;
+	if (EC_GROUP_get_order(group, q, bnctx) != 1 ||
+	    BN_mod_add(sum, bR_bn, pR_bn, q, bnctx) != 1)
+		goto fail;
+	l = EC_POINT_new(group);
+	if (!l ||
+	    EC_POINT_mul(group, l, NULL, BI_point, sum, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, l, lx, NULL,
+						bnctx) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "OpenSSL: failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	if (dpp_bn2bin_pad(lx, auth->Lx, auth->secret_len) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: L.x", auth->Lx, auth->secret_len);
+	auth->Lx_len = auth->secret_len;
+	ret = 0;
+fail:
+	EC_POINT_clear_free(l);
+	EC_KEY_free(BI);
+	EC_KEY_free(bR);
+	EC_KEY_free(pR);
+	BN_clear_free(lx);
+	BN_clear_free(sum);
+	BN_free(q);
+	BN_CTX_free(bnctx);
+	return ret;
+}
+
+
+static int dpp_auth_derive_l_initiator(struct dpp_authentication *auth)
+{
+	const EC_GROUP *group;
+	EC_POINT *l = NULL, *sum = NULL;
+	EC_KEY *bI = NULL, *BR = NULL, *PR = NULL;
+	const EC_POINT *BR_point, *PR_point;
+	BN_CTX *bnctx;
+	BIGNUM *lx;
+	const BIGNUM *bI_bn;
+	int ret = -1;
+
+	/* L = bI * (BR + PR) */
+
+	bnctx = BN_CTX_new();
+	lx = BN_new();
+	if (!bnctx || !lx)
+		goto fail;
+	BR = EVP_PKEY_get1_EC_KEY(auth->peer_bi->pubkey);
+	PR = EVP_PKEY_get1_EC_KEY(auth->peer_protocol_key);
+	if (!BR || !PR)
+		goto fail;
+	BR_point = EC_KEY_get0_public_key(BR);
+	PR_point = EC_KEY_get0_public_key(PR);
+
+	bI = EVP_PKEY_get1_EC_KEY(auth->own_bi->pubkey);
+	if (!bI)
+		goto fail;
+	group = EC_KEY_get0_group(bI);
+	bI_bn = EC_KEY_get0_private_key(bI);
+	if (!group || !bI_bn)
+		goto fail;
+	sum = EC_POINT_new(group);
+	l = EC_POINT_new(group);
+	if (!sum || !l ||
+	    EC_POINT_add(group, sum, BR_point, PR_point, bnctx) != 1 ||
+	    EC_POINT_mul(group, l, NULL, sum, bI_bn, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, l, lx, NULL,
+						bnctx) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "OpenSSL: failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	if (dpp_bn2bin_pad(lx, auth->Lx, auth->secret_len) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: L.x", auth->Lx, auth->secret_len);
+	auth->Lx_len = auth->secret_len;
+	ret = 0;
+fail:
+	EC_POINT_clear_free(l);
+	EC_KEY_free(bI);
+	EC_KEY_free(BR);
+	EC_KEY_free(PR);
+	BN_clear_free(lx);
+	BN_CTX_free(bnctx);
+	return ret;
+}
+
+
+static int dpp_auth_build_resp_ok(struct dpp_authentication *auth)
+{
+	size_t nonce_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	struct wpabuf *msg, *pr = NULL;
+	u8 r_auth[4 + DPP_MAX_HASH_LEN];
+	u8 wrapped_r_auth[4 + DPP_MAX_HASH_LEN + AES_BLOCK_SIZE], *w_r_auth;
+	size_t wrapped_r_auth_len;
+	int ret = -1;
+	const u8 *r_pubkey_hash, *i_pubkey_hash, *r_nonce, *i_nonce;
+	enum dpp_status_error status = DPP_STATUS_OK;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 test_hash[SHA256_MAC_LEN];
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Response");
+	if (!auth->own_bi)
+		return -1;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_nonce_override_len > 0) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - override R-nonce");
+		nonce_len = dpp_nonce_override_len;
+		os_memcpy(auth->r_nonce, dpp_nonce_override, nonce_len);
+	} else {
+		nonce_len = auth->curve->nonce_len;
+		if (random_get_bytes(auth->r_nonce, nonce_len)) {
+			wpa_printf(MSG_ERROR,
+				   "DPP: Failed to generate R-nonce");
+			goto fail;
+		}
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->r_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate R-nonce");
+		goto fail;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpa_hexdump(MSG_DEBUG, "DPP: R-nonce", auth->r_nonce, nonce_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_protocol_key_override_len) {
+		const struct dpp_curve_params *tmp_curve;
+
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - override protocol key");
+		auth->own_protocol_key = dpp_set_keypair(
+			&tmp_curve, dpp_protocol_key_override,
+			dpp_protocol_key_override_len);
+	} else {
+		auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (!auth->own_protocol_key)
+		goto fail;
+
+	pr = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+	if (!pr)
+		goto fail;
+
+	/* ECDH: N = pR * PI */
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, auth->peer_protocol_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Nx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			auth->Nx, auth->secret_len);
+	auth->Nx_len = auth->secret_len;
+
+	if (dpp_derive_k2(auth->Nx, auth->secret_len, auth->k2,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	if (auth->own_bi && auth->peer_bi) {
+		/* Mutual authentication */
+		if (dpp_auth_derive_l_responder(auth) < 0)
+			goto fail;
+	}
+
+	if (dpp_derive_ke(auth, auth->ke, auth->curve->hash_len) < 0)
+		goto fail;
+
+	/* R-auth = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	WPA_PUT_LE16(r_auth, DPP_ATTR_R_AUTH_TAG);
+	WPA_PUT_LE16(&r_auth[2], auth->curve->hash_len);
+	if (dpp_gen_r_auth(auth, r_auth + 4) < 0)
+		goto fail;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_R_AUTH_MISMATCH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - R-auth mismatch");
+		r_auth[4 + auth->curve->hash_len / 2] ^= 0x01;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    r_auth, 4 + auth->curve->hash_len,
+			    0, NULL, NULL, wrapped_r_auth) < 0)
+		goto fail;
+	wrapped_r_auth_len = 4 + auth->curve->hash_len + AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: {R-auth}ke",
+		    wrapped_r_auth, wrapped_r_auth_len);
+	w_r_auth = wrapped_r_auth;
+
+	r_pubkey_hash = auth->own_bi->pubkey_hash;
+	if (auth->peer_bi)
+		i_pubkey_hash = auth->peer_bi->pubkey_hash;
+	else
+		i_pubkey_hash = NULL;
+
+	i_nonce = auth->i_nonce;
+	r_nonce = auth->r_nonce;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Bootstrap Key Hash");
+		r_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid R-Bootstrap Key Hash");
+		os_memcpy(test_hash, r_pubkey_hash, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		r_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Bootstrap Key Hash");
+		i_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid I-Bootstrap Key Hash");
+		if (i_pubkey_hash)
+			os_memcpy(test_hash, i_pubkey_hash, SHA256_MAC_LEN);
+		else
+			os_memset(test_hash, 0, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		i_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_R_PROTO_KEY_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Proto Key");
+		wpabuf_free(pr);
+		pr = NULL;
+	} else if (dpp_test == DPP_TEST_INVALID_R_PROTO_KEY_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid R-Proto Key");
+		wpabuf_free(pr);
+		pr = wpabuf_alloc(2 * auth->curve->prime_len);
+		if (!pr || dpp_test_gen_invalid_key(pr, auth->curve) < 0)
+			goto fail;
+	} else if (dpp_test == DPP_TEST_NO_R_AUTH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Auth");
+		w_r_auth = NULL;
+		wrapped_r_auth_len = 0;
+	} else if (dpp_test == DPP_TEST_NO_STATUS_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Status");
+		status = 255;
+	} else if (dpp_test == DPP_TEST_INVALID_STATUS_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status");
+		status = 254;
+	} else if (dpp_test == DPP_TEST_NO_R_NONCE_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-nonce");
+		r_nonce = NULL;
+	} else if (dpp_test == DPP_TEST_NO_I_NONCE_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-nonce");
+		i_nonce = NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	msg = dpp_auth_build_resp(auth, status, pr, nonce_len,
+				  r_pubkey_hash, i_pubkey_hash,
+				  r_nonce, i_nonce,
+				  w_r_auth, wrapped_r_auth_len,
+				  auth->k2);
+	if (!msg)
+		goto fail;
+	wpabuf_free(auth->resp_msg);
+	auth->resp_msg = msg;
+	ret = 0;
+fail:
+	wpabuf_free(pr);
+	return ret;
+}
+
+
+static int dpp_auth_build_resp_status(struct dpp_authentication *auth,
+				      enum dpp_status_error status)
+{
+	struct wpabuf *msg;
+	const u8 *r_pubkey_hash, *i_pubkey_hash, *i_nonce;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 test_hash[SHA256_MAC_LEN];
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (!auth->own_bi)
+		return -1;
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Response");
+
+	r_pubkey_hash = auth->own_bi->pubkey_hash;
+	if (auth->peer_bi)
+		i_pubkey_hash = auth->peer_bi->pubkey_hash;
+	else
+		i_pubkey_hash = NULL;
+
+	i_nonce = auth->i_nonce;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Bootstrap Key Hash");
+		r_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid R-Bootstrap Key Hash");
+		os_memcpy(test_hash, r_pubkey_hash, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		r_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Bootstrap Key Hash");
+		i_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid I-Bootstrap Key Hash");
+		if (i_pubkey_hash)
+			os_memcpy(test_hash, i_pubkey_hash, SHA256_MAC_LEN);
+		else
+			os_memset(test_hash, 0, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		i_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_STATUS_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Status");
+		status = -1;
+	} else if (dpp_test == DPP_TEST_NO_I_NONCE_AUTH_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-nonce");
+		i_nonce = NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	msg = dpp_auth_build_resp(auth, status, NULL, auth->curve->nonce_len,
+				  r_pubkey_hash, i_pubkey_hash,
+				  NULL, i_nonce, NULL, 0, auth->k1);
+	if (!msg)
+		return -1;
+	wpabuf_free(auth->resp_msg);
+	auth->resp_msg = msg;
+	return 0;
+}
+
+
+struct dpp_authentication *
+dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual,
+		struct dpp_bootstrap_info *peer_bi,
+		struct dpp_bootstrap_info *own_bi,
+		unsigned int freq, const u8 *hdr, const u8 *attr_start,
+		size_t attr_len)
+{
+	EVP_PKEY *pi = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const u8 *wrapped_data, *i_proto, *i_nonce, *i_capab, *i_bootstrap,
+		*channel;
+	u16 wrapped_data_len, i_proto_len, i_nonce_len, i_capab_len,
+		i_bootstrap_len, channel_len;
+	struct dpp_authentication *auth = NULL;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_AUTH_REQ) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at Authentication Request");
+		return NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Missing or invalid required Wrapped Data attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Wrapped Data",
+		    wrapped_data, wrapped_data_len);
+	attr_len = wrapped_data - 4 - attr_start;
+
+	auth = os_zalloc(sizeof(*auth));
+	if (!auth)
+		goto fail;
+	auth->msg_ctx = msg_ctx;
+	auth->peer_bi = peer_bi;
+	auth->own_bi = own_bi;
+	auth->curve = own_bi->curve;
+	auth->curr_freq = freq;
+
+	channel = dpp_get_attr(attr_start, attr_len, DPP_ATTR_CHANNEL,
+			       &channel_len);
+	if (channel) {
+		int neg_freq;
+
+		if (channel_len < 2) {
+			dpp_auth_fail(auth, "Too short Channel attribute");
+			goto fail;
+		}
+
+		neg_freq = ieee80211_chan_to_freq(NULL, channel[0], channel[1]);
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Initiator requested different channel for negotiation: op_class=%u channel=%u --> freq=%d",
+			   channel[0], channel[1], neg_freq);
+		if (neg_freq < 0) {
+			dpp_auth_fail(auth,
+				      "Unsupported Channel attribute value");
+			goto fail;
+		}
+
+		if (auth->curr_freq != (unsigned int) neg_freq) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Changing negotiation channel from %u MHz to %u MHz",
+				   freq, neg_freq);
+			auth->curr_freq = neg_freq;
+		}
+	}
+
+	i_proto = dpp_get_attr(attr_start, attr_len, DPP_ATTR_I_PROTOCOL_KEY,
+			       &i_proto_len);
+	if (!i_proto) {
+		dpp_auth_fail(auth,
+			      "Missing required Initiator Protocol Key attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Protocol Key",
+		    i_proto, i_proto_len);
+
+	/* M = bR * PI */
+	pi = dpp_set_pubkey_point(own_bi->pubkey, i_proto, i_proto_len);
+	if (!pi) {
+		dpp_auth_fail(auth, "Invalid Initiator Protocol Key");
+		goto fail;
+	}
+	dpp_debug_print_key("Peer (Initiator) Protocol Key", pi);
+
+	ctx = EVP_PKEY_CTX_new(own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pi) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Mx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		dpp_auth_fail(auth, "Failed to derive ECDH shared secret");
+		goto fail;
+	}
+	auth->secret_len = secret_len;
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (M.x)",
+			auth->Mx, auth->secret_len);
+	auth->Mx_len = auth->secret_len;
+
+	if (dpp_derive_k1(auth->Mx, auth->secret_len, auth->k1,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k1, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth, "Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	os_memcpy(auth->i_nonce, i_nonce, i_nonce_len);
+
+	i_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_I_CAPABILITIES,
+			       &i_capab_len);
+	if (!i_capab || i_capab_len < 1) {
+		dpp_auth_fail(auth, "Missing or invalid I-capabilities");
+		goto fail;
+	}
+	auth->i_capab = i_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: I-capabilities: 0x%02x", auth->i_capab);
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	unwrapped = NULL;
+
+	switch (auth->i_capab & DPP_CAPAB_ROLE_MASK) {
+	case DPP_CAPAB_ENROLLEE:
+		if (!(dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Local policy does not allow Configurator role");
+			goto not_compatible;
+		}
+		wpa_printf(MSG_DEBUG, "DPP: Acting as Configurator");
+		auth->configurator = 1;
+		break;
+	case DPP_CAPAB_CONFIGURATOR:
+		if (!(dpp_allowed_roles & DPP_CAPAB_ENROLLEE)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Local policy does not allow Enrollee role");
+			goto not_compatible;
+		}
+		wpa_printf(MSG_DEBUG, "DPP: Acting as Enrollee");
+		auth->configurator = 0;
+		break;
+	case DPP_CAPAB_CONFIGURATOR | DPP_CAPAB_ENROLLEE:
+		if (dpp_allowed_roles & DPP_CAPAB_ENROLLEE) {
+			wpa_printf(MSG_DEBUG, "DPP: Acting as Enrollee");
+			auth->configurator = 0;
+		} else if (dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR) {
+			wpa_printf(MSG_DEBUG, "DPP: Acting as Configurator");
+			auth->configurator = 1;
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Local policy does not allow Configurator/Enrollee role");
+			goto not_compatible;
+		}
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "DPP: Unexpected role in I-capabilities");
+		wpa_msg(auth->msg_ctx, MSG_INFO,
+			DPP_EVENT_FAIL "Invalid role in I-capabilities 0x%02x",
+			auth->i_capab & DPP_CAPAB_ROLE_MASK);
+		goto fail;
+	}
+
+	auth->peer_protocol_key = pi;
+	pi = NULL;
+	if (qr_mutual && !peer_bi && own_bi->type == DPP_BOOTSTRAP_QR_CODE) {
+		char hex[SHA256_MAC_LEN * 2 + 1];
+
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mutual authentication required with QR Codes, but peer info is not yet available - request more time");
+		if (dpp_auth_build_resp_status(auth,
+					       DPP_STATUS_RESPONSE_PENDING) < 0)
+			goto fail;
+		i_bootstrap = dpp_get_attr(attr_start, attr_len,
+					   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+					   &i_bootstrap_len);
+		if (i_bootstrap && i_bootstrap_len == SHA256_MAC_LEN) {
+			auth->response_pending = 1;
+			os_memcpy(auth->waiting_pubkey_hash,
+				  i_bootstrap, i_bootstrap_len);
+			wpa_snprintf_hex(hex, sizeof(hex), i_bootstrap,
+					 i_bootstrap_len);
+		} else {
+			hex[0] = '\0';
+		}
+
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_SCAN_PEER_QR_CODE
+			"%s", hex);
+		return auth;
+	}
+	if (dpp_auth_build_resp_ok(auth) < 0)
+		goto fail;
+
+	return auth;
+
+not_compatible:
+	wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_NOT_COMPATIBLE
+		"i-capab=0x%02x", auth->i_capab);
+	if (dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR)
+		auth->configurator = 1;
+	else
+		auth->configurator = 0;
+	auth->peer_protocol_key = pi;
+	pi = NULL;
+	if (dpp_auth_build_resp_status(auth, DPP_STATUS_NOT_COMPATIBLE) < 0)
+		goto fail;
+
+	auth->remove_on_tx_status = 1;
+	return auth;
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	EVP_PKEY_free(pi);
+	EVP_PKEY_CTX_free(ctx);
+	dpp_auth_deinit(auth);
+	return NULL;
+}
+
+
+int dpp_notify_new_qr_code(struct dpp_authentication *auth,
+			   struct dpp_bootstrap_info *peer_bi)
+{
+	if (!auth || !auth->response_pending ||
+	    os_memcmp(auth->waiting_pubkey_hash, peer_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0)
+		return 0;
+
+	wpa_printf(MSG_DEBUG,
+		   "DPP: New scanned QR Code has matching public key that was needed to continue DPP Authentication exchange with "
+		   MACSTR, MAC2STR(auth->peer_mac_addr));
+	auth->peer_bi = peer_bi;
+
+	if (dpp_auth_build_resp_ok(auth) < 0)
+		return -1;
+
+	return 1;
+}
+
+
+static struct wpabuf * dpp_auth_build_conf(struct dpp_authentication *auth,
+					   enum dpp_status_error status)
+{
+	struct wpabuf *msg;
+	u8 i_auth[4 + DPP_MAX_HASH_LEN];
+	size_t i_auth_len;
+	u8 r_nonce[4 + DPP_MAX_NONCE_LEN];
+	size_t r_nonce_len;
+	const u8 *addr[2];
+	size_t len[2], attr_len;
+	u8 *wrapped_i_auth;
+	u8 *wrapped_r_nonce;
+	u8 *attr_start, *attr_end;
+	const u8 *r_pubkey_hash, *i_pubkey_hash;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 test_hash[SHA256_MAC_LEN];
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Confirmation");
+
+	i_auth_len = 4 + auth->curve->hash_len;
+	r_nonce_len = 4 + auth->curve->nonce_len;
+	/* Build DPP Authentication Confirmation frame attributes */
+	attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) +
+		4 + i_auth_len + r_nonce_len + AES_BLOCK_SIZE;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_CONF)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_CONF, attr_len);
+	if (!msg)
+		goto fail;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	r_pubkey_hash = auth->peer_bi->pubkey_hash;
+	if (auth->own_bi)
+		i_pubkey_hash = auth->own_bi->pubkey_hash;
+	else
+		i_pubkey_hash = NULL;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_STATUS_AUTH_CONF) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Status");
+		goto skip_status;
+	} else if (dpp_test == DPP_TEST_INVALID_STATUS_AUTH_CONF) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status");
+		status = 254;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* DPP Status */
+	dpp_build_attr_status(msg, status);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_status:
+	if (dpp_test == DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_CONF) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Bootstrap Key Hash");
+		r_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_CONF) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid R-Bootstrap Key Hash");
+		os_memcpy(test_hash, r_pubkey_hash, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		r_pubkey_hash = test_hash;
+	} else if (dpp_test == DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_CONF) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Bootstrap Key Hash");
+		i_pubkey_hash = NULL;
+	} else if (dpp_test ==
+		   DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_CONF) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - invalid I-Bootstrap Key Hash");
+		if (i_pubkey_hash)
+			os_memcpy(test_hash, i_pubkey_hash, SHA256_MAC_LEN);
+		else
+			os_memset(test_hash, 0, SHA256_MAC_LEN);
+		test_hash[SHA256_MAC_LEN - 1] ^= 0x01;
+		i_pubkey_hash = test_hash;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Responder Bootstrapping Key Hash */
+	dpp_build_attr_r_bootstrap_key_hash(msg, r_pubkey_hash);
+
+	/* Initiator Bootstrapping Key Hash (mutual authentication) */
+	dpp_build_attr_i_bootstrap_key_hash(msg, i_pubkey_hash);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_AUTH_CONF)
+		goto skip_wrapped_data;
+	if (dpp_test == DPP_TEST_NO_I_AUTH_AUTH_CONF)
+		i_auth_len = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	if (status == DPP_STATUS_OK) {
+		/* I-auth wrapped with ke */
+		wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+		wpabuf_put_le16(msg, i_auth_len + AES_BLOCK_SIZE);
+		wrapped_i_auth = wpabuf_put(msg, i_auth_len + AES_BLOCK_SIZE);
+
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_NO_I_AUTH_AUTH_CONF)
+			goto skip_i_auth;
+#endif /* CONFIG_TESTING_OPTIONS */
+
+		/* I-auth = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |]
+		 *	      1) */
+		WPA_PUT_LE16(i_auth, DPP_ATTR_I_AUTH_TAG);
+		WPA_PUT_LE16(&i_auth[2], auth->curve->hash_len);
+		if (dpp_gen_i_auth(auth, i_auth + 4) < 0)
+			goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_I_AUTH_MISMATCH_AUTH_CONF) {
+			wpa_printf(MSG_INFO, "DPP: TESTING - I-auth mismatch");
+			i_auth[4 + auth->curve->hash_len / 2] ^= 0x01;
+		}
+skip_i_auth:
+#endif /* CONFIG_TESTING_OPTIONS */
+		if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+				    i_auth, i_auth_len,
+				    2, addr, len, wrapped_i_auth) < 0)
+			goto fail;
+		wpa_hexdump(MSG_DEBUG, "DPP: {I-auth}ke",
+			    wrapped_i_auth, i_auth_len + AES_BLOCK_SIZE);
+	} else {
+		/* R-nonce wrapped with k2 */
+		wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+		wpabuf_put_le16(msg, r_nonce_len + AES_BLOCK_SIZE);
+		wrapped_r_nonce = wpabuf_put(msg, r_nonce_len + AES_BLOCK_SIZE);
+
+		WPA_PUT_LE16(r_nonce, DPP_ATTR_R_NONCE);
+		WPA_PUT_LE16(&r_nonce[2], auth->curve->nonce_len);
+		os_memcpy(r_nonce + 4, auth->r_nonce, auth->curve->nonce_len);
+
+		if (aes_siv_encrypt(auth->k2, auth->curve->hash_len,
+				    r_nonce, r_nonce_len,
+				    2, addr, len, wrapped_r_nonce) < 0)
+			goto fail;
+		wpa_hexdump(MSG_DEBUG, "DPP: {R-nonce}k2",
+			    wrapped_r_nonce, r_nonce_len + AES_BLOCK_SIZE);
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_CONF) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Confirmation frame attributes",
+			msg);
+	if (status == DPP_STATUS_OK)
+		dpp_auth_success(auth);
+
+	return msg;
+
+fail:
+	wpabuf_free(msg);
+	return NULL;
+}
+
+
+static void
+dpp_auth_resp_rx_status(struct dpp_authentication *auth, const u8 *hdr,
+			const u8 *attr_start, size_t attr_len,
+			const u8 *wrapped_data, u16 wrapped_data_len,
+			enum dpp_status_error status)
+{
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const u8 *i_nonce, *r_capab;
+	u16 i_nonce_len, r_capab_len;
+
+	if (status == DPP_STATUS_NOT_COMPATIBLE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported incompatible roles");
+	} else if (status == DPP_STATUS_RESPONSE_PENDING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported more time needed");
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported failure (status %d)",
+			   status);
+		dpp_auth_fail(auth, "Responder reported failure");
+		return;
+	}
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k1, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth, "Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	if (os_memcmp(auth->i_nonce, i_nonce, i_nonce_len) != 0) {
+		dpp_auth_fail(auth, "I-nonce mismatch");
+		goto fail;
+	}
+
+	r_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_R_CAPABILITIES,
+			       &r_capab_len);
+	if (!r_capab || r_capab_len < 1) {
+		dpp_auth_fail(auth, "Missing or invalid R-capabilities");
+		goto fail;
+	}
+	auth->r_capab = r_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: R-capabilities: 0x%02x", auth->r_capab);
+	if (status == DPP_STATUS_NOT_COMPATIBLE) {
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_NOT_COMPATIBLE
+			"r-capab=0x%02x", auth->r_capab);
+	} else if (status == DPP_STATUS_RESPONSE_PENDING) {
+		u8 role = auth->r_capab & DPP_CAPAB_ROLE_MASK;
+
+		if ((auth->configurator && role != DPP_CAPAB_ENROLLEE) ||
+		    (!auth->configurator && role != DPP_CAPAB_CONFIGURATOR)) {
+			wpa_msg(auth->msg_ctx, MSG_INFO,
+				DPP_EVENT_FAIL "Unexpected role in R-capabilities 0x%02x",
+				role);
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Continue waiting for full DPP Authentication Response");
+			wpa_msg(auth->msg_ctx, MSG_INFO,
+				DPP_EVENT_RESPONSE_PENDING "%s",
+				auth->tmp_own_bi ? auth->tmp_own_bi->uri : "");
+		}
+	}
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+}
+
+
+struct wpabuf *
+dpp_auth_resp_rx(struct dpp_authentication *auth, const u8 *hdr,
+		 const u8 *attr_start, size_t attr_len)
+{
+	EVP_PKEY *pr;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL, *unwrapped2 = NULL;
+	size_t unwrapped_len = 0, unwrapped2_len = 0;
+	const u8 *r_bootstrap, *i_bootstrap, *wrapped_data, *status, *r_proto,
+		*r_nonce, *i_nonce, *r_capab, *wrapped2, *r_auth;
+	u16 r_bootstrap_len, i_bootstrap_len, wrapped_data_len, status_len,
+		r_proto_len, r_nonce_len, i_nonce_len, r_capab_len,
+		wrapped2_len, r_auth_len;
+	u8 r_auth2[DPP_MAX_HASH_LEN];
+	u8 role;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_AUTH_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at Authentication Response");
+		return NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (!auth->initiator) {
+		dpp_auth_fail(auth, "Unexpected Authentication Response");
+		return NULL;
+	}
+
+	auth->waiting_auth_resp = 0;
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Wrapped Data attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Wrapped data",
+		    wrapped_data, wrapped_data_len);
+
+	attr_len = wrapped_data - 4 - attr_start;
+
+	r_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
+				   &r_bootstrap_len);
+	if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Responder Bootstrapping Key Hash attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Responder Bootstrapping Key Hash",
+		    r_bootstrap, r_bootstrap_len);
+	if (os_memcmp(r_bootstrap, auth->peer_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0) {
+		dpp_auth_fail(auth,
+			      "Unexpected Responder Bootstrapping Key Hash value");
+		wpa_hexdump(MSG_DEBUG,
+			    "DPP: Expected Responder Bootstrapping Key Hash",
+			    auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+		return NULL;
+	}
+
+	i_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+				   &i_bootstrap_len);
+	if (i_bootstrap) {
+		if (i_bootstrap_len != SHA256_MAC_LEN) {
+			dpp_auth_fail(auth,
+				      "Invalid Initiator Bootstrapping Key Hash attribute");
+			return NULL;
+		}
+		wpa_hexdump(MSG_MSGDUMP,
+			    "DPP: Initiator Bootstrapping Key Hash",
+			    i_bootstrap, i_bootstrap_len);
+		if (!auth->own_bi ||
+		    os_memcmp(i_bootstrap, auth->own_bi->pubkey_hash,
+			      SHA256_MAC_LEN) != 0) {
+			dpp_auth_fail(auth,
+				      "Initiator Bootstrapping Key Hash attribute did not match");
+			return NULL;
+		}
+	} else if (auth->own_bi && auth->own_bi->type == DPP_BOOTSTRAP_PKEX) {
+		/* PKEX bootstrapping mandates use of mutual authentication */
+		dpp_auth_fail(auth,
+			      "Missing Initiator Bootstrapping Key Hash attribute");
+		return NULL;
+	}
+
+	status = dpp_get_attr(attr_start, attr_len, DPP_ATTR_STATUS,
+			      &status_len);
+	if (!status || status_len < 1) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required DPP Status attribute");
+		return NULL;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	auth->auth_resp_status = status[0];
+	if (status[0] != DPP_STATUS_OK) {
+		dpp_auth_resp_rx_status(auth, hdr, attr_start,
+					attr_len, wrapped_data,
+					wrapped_data_len, status[0]);
+		return NULL;
+	}
+
+	if (!i_bootstrap && auth->own_bi) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder decided not to use mutual authentication");
+		auth->own_bi = NULL;
+	}
+
+	wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_AUTH_DIRECTION "mutual=%d",
+		auth->own_bi != NULL);
+
+	r_proto = dpp_get_attr(attr_start, attr_len, DPP_ATTR_R_PROTOCOL_KEY,
+			       &r_proto_len);
+	if (!r_proto) {
+		dpp_auth_fail(auth,
+			      "Missing required Responder Protocol Key attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Protocol Key",
+		    r_proto, r_proto_len);
+
+	/* N = pI * PR */
+	pr = dpp_set_pubkey_point(auth->own_protocol_key, r_proto, r_proto_len);
+	if (!pr) {
+		dpp_auth_fail(auth, "Invalid Responder Protocol Key");
+		return NULL;
+	}
+	dpp_debug_print_key("Peer (Responder) Protocol Key", pr);
+
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pr) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Nx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		dpp_auth_fail(auth, "Failed to derive ECDH shared secret");
+		goto fail;
+	}
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+	auth->peer_protocol_key = pr;
+	pr = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			auth->Nx, auth->secret_len);
+	auth->Nx_len = auth->secret_len;
+
+	if (dpp_derive_k2(auth->Nx, auth->secret_len, auth->k2,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k2, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	r_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_R_NONCE,
+			       &r_nonce_len);
+	if (!r_nonce || r_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth, "DPP: Missing or invalid R-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: R-nonce", r_nonce, r_nonce_len);
+	os_memcpy(auth->r_nonce, r_nonce, r_nonce_len);
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth, "Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	if (os_memcmp(auth->i_nonce, i_nonce, i_nonce_len) != 0) {
+		dpp_auth_fail(auth, "I-nonce mismatch");
+		goto fail;
+	}
+
+	if (auth->own_bi && auth->peer_bi) {
+		/* Mutual authentication */
+		if (dpp_auth_derive_l_initiator(auth) < 0)
+			goto fail;
+	}
+
+	r_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_R_CAPABILITIES,
+			       &r_capab_len);
+	if (!r_capab || r_capab_len < 1) {
+		dpp_auth_fail(auth, "Missing or invalid R-capabilities");
+		goto fail;
+	}
+	auth->r_capab = r_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: R-capabilities: 0x%02x", auth->r_capab);
+	role = auth->r_capab & DPP_CAPAB_ROLE_MASK;
+	if ((auth->allowed_roles ==
+	     (DPP_CAPAB_CONFIGURATOR | DPP_CAPAB_ENROLLEE)) &&
+	    (role == DPP_CAPAB_CONFIGURATOR || role == DPP_CAPAB_ENROLLEE)) {
+		/* Peer selected its role, so move from "either role" to the
+		 * role that is compatible with peer's selection. */
+		auth->configurator = role == DPP_CAPAB_ENROLLEE;
+		wpa_printf(MSG_DEBUG, "DPP: Acting as %s",
+			   auth->configurator ? "Configurator" : "Enrollee");
+	} else if ((auth->configurator && role != DPP_CAPAB_ENROLLEE) ||
+		   (!auth->configurator && role != DPP_CAPAB_CONFIGURATOR)) {
+		wpa_printf(MSG_DEBUG, "DPP: Incompatible role selection");
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Unexpected role in R-capabilities 0x%02x",
+			role);
+		if (role != DPP_CAPAB_ENROLLEE &&
+		    role != DPP_CAPAB_CONFIGURATOR)
+			goto fail;
+		bin_clear_free(unwrapped, unwrapped_len);
+		auth->remove_on_tx_status = 1;
+		return dpp_auth_build_conf(auth, DPP_STATUS_NOT_COMPATIBLE);
+	}
+
+	wrapped2 = dpp_get_attr(unwrapped, unwrapped_len,
+				DPP_ATTR_WRAPPED_DATA, &wrapped2_len);
+	if (!wrapped2 || wrapped2_len < AES_BLOCK_SIZE) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Secondary Wrapped Data");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped2, wrapped2_len);
+
+	if (dpp_derive_ke(auth, auth->ke, auth->curve->hash_len) < 0)
+		goto fail;
+
+	unwrapped2_len = wrapped2_len - AES_BLOCK_SIZE;
+	unwrapped2 = os_malloc(unwrapped2_len);
+	if (!unwrapped2)
+		goto fail;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped2, wrapped2_len,
+			    0, NULL, NULL, unwrapped2) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped2, unwrapped2_len);
+
+	if (dpp_check_attrs(unwrapped2, unwrapped2_len) < 0) {
+		dpp_auth_fail(auth,
+			      "Invalid attribute in secondary unwrapped data");
+		goto fail;
+	}
+
+	r_auth = dpp_get_attr(unwrapped2, unwrapped2_len, DPP_ATTR_R_AUTH_TAG,
+			       &r_auth_len);
+	if (!r_auth || r_auth_len != auth->curve->hash_len) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Responder Authenticating Tag");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Received Responder Authenticating Tag",
+		    r_auth, r_auth_len);
+	/* R-auth' = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	if (dpp_gen_r_auth(auth, r_auth2) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: Calculated Responder Authenticating Tag",
+		    r_auth2, r_auth_len);
+	if (os_memcmp(r_auth, r_auth2, r_auth_len) != 0) {
+		dpp_auth_fail(auth, "Mismatching Responder Authenticating Tag");
+		bin_clear_free(unwrapped, unwrapped_len);
+		bin_clear_free(unwrapped2, unwrapped2_len);
+		auth->remove_on_tx_status = 1;
+		return dpp_auth_build_conf(auth, DPP_STATUS_AUTH_FAILURE);
+	}
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	bin_clear_free(unwrapped2, unwrapped2_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AUTH_RESP_IN_PLACE_OF_CONF) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - Authentication Response in place of Confirm");
+		if (dpp_auth_build_resp_ok(auth) < 0)
+			return NULL;
+		return wpabuf_dup(auth->resp_msg);
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	return dpp_auth_build_conf(auth, DPP_STATUS_OK);
+
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	bin_clear_free(unwrapped2, unwrapped2_len);
+	EVP_PKEY_free(pr);
+	EVP_PKEY_CTX_free(ctx);
+	return NULL;
+}
+
+
+static int dpp_auth_conf_rx_failure(struct dpp_authentication *auth,
+				    const u8 *hdr,
+				    const u8 *attr_start, size_t attr_len,
+				    const u8 *wrapped_data,
+				    u16 wrapped_data_len,
+				    enum dpp_status_error status)
+{
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const u8 *r_nonce;
+	u16 r_nonce_len;
+
+	/* Authentication Confirm failure cases are expected to include
+	 * {R-nonce}k2 in the Wrapped Data attribute. */
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped) {
+		dpp_auth_fail(auth, "Authentication failed");
+		goto fail;
+	}
+	if (aes_siv_decrypt(auth->k2, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	r_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_R_NONCE,
+			       &r_nonce_len);
+	if (!r_nonce || r_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth, "DPP: Missing or invalid R-nonce");
+		goto fail;
+	}
+	if (os_memcmp(r_nonce, auth->r_nonce, r_nonce_len) != 0) {
+		wpa_hexdump(MSG_DEBUG, "DPP: Received R-nonce",
+			    r_nonce, r_nonce_len);
+		wpa_hexdump(MSG_DEBUG, "DPP: Expected R-nonce",
+			    auth->r_nonce, r_nonce_len);
+		dpp_auth_fail(auth, "R-nonce mismatch");
+		goto fail;
+	}
+
+	if (status == DPP_STATUS_NOT_COMPATIBLE)
+		dpp_auth_fail(auth, "Peer reported incompatible R-capab role");
+	else if (status == DPP_STATUS_AUTH_FAILURE)
+		dpp_auth_fail(auth, "Peer reported authentication failure)");
+
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	return -1;
+}
+
+
+int dpp_auth_conf_rx(struct dpp_authentication *auth, const u8 *hdr,
+		     const u8 *attr_start, size_t attr_len)
+{
+	const u8 *r_bootstrap, *i_bootstrap, *wrapped_data, *status, *i_auth;
+	u16 r_bootstrap_len, i_bootstrap_len, wrapped_data_len, status_len,
+		i_auth_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	u8 i_auth2[DPP_MAX_HASH_LEN];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at Authentication Confirm");
+		return -1;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (auth->initiator) {
+		dpp_auth_fail(auth, "Unexpected Authentication Confirm");
+		return -1;
+	}
+
+	auth->waiting_auth_conf = 0;
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Wrapped Data attribute");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Wrapped data",
+		    wrapped_data, wrapped_data_len);
+
+	attr_len = wrapped_data - 4 - attr_start;
+
+	r_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
+				   &r_bootstrap_len);
+	if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Responder Bootstrapping Key Hash attribute");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Responder Bootstrapping Key Hash",
+		    r_bootstrap, r_bootstrap_len);
+	if (os_memcmp(r_bootstrap, auth->own_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0) {
+		wpa_hexdump(MSG_DEBUG,
+			    "DPP: Expected Responder Bootstrapping Key Hash",
+			    auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+		dpp_auth_fail(auth,
+			      "Responder Bootstrapping Key Hash mismatch");
+		return -1;
+	}
+
+	i_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+				   &i_bootstrap_len);
+	if (i_bootstrap) {
+		if (i_bootstrap_len != SHA256_MAC_LEN) {
+			dpp_auth_fail(auth,
+				      "Invalid Initiator Bootstrapping Key Hash attribute");
+			return -1;
+		}
+		wpa_hexdump(MSG_MSGDUMP,
+			    "DPP: Initiator Bootstrapping Key Hash",
+			    i_bootstrap, i_bootstrap_len);
+		if (!auth->peer_bi ||
+		    os_memcmp(i_bootstrap, auth->peer_bi->pubkey_hash,
+			      SHA256_MAC_LEN) != 0) {
+			dpp_auth_fail(auth,
+				      "Initiator Bootstrapping Key Hash mismatch");
+			return -1;
+		}
+	} else if (auth->own_bi && auth->peer_bi) {
+		/* Mutual authentication and peer did not include its
+		 * Bootstrapping Key Hash attribute. */
+		dpp_auth_fail(auth,
+			      "Missing Initiator Bootstrapping Key Hash attribute");
+		return -1;
+	}
+
+	status = dpp_get_attr(attr_start, attr_len, DPP_ATTR_STATUS,
+			      &status_len);
+	if (!status || status_len < 1) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required DPP Status attribute");
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	if (status[0] == DPP_STATUS_NOT_COMPATIBLE ||
+	    status[0] == DPP_STATUS_AUTH_FAILURE)
+		return dpp_auth_conf_rx_failure(auth, hdr, attr_start,
+						attr_len, wrapped_data,
+						wrapped_data_len, status[0]);
+
+	if (status[0] != DPP_STATUS_OK) {
+		dpp_auth_fail(auth, "Authentication failed");
+		return -1;
+	}
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return -1;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_auth = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_AUTH_TAG,
+			      &i_auth_len);
+	if (!i_auth || i_auth_len != auth->curve->hash_len) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Initiator Authenticating Tag");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Received Initiator Authenticating Tag",
+		    i_auth, i_auth_len);
+	/* I-auth' = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |] 1) */
+	if (dpp_gen_i_auth(auth, i_auth2) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: Calculated Initiator Authenticating Tag",
+		    i_auth2, i_auth_len);
+	if (os_memcmp(i_auth, i_auth2, i_auth_len) != 0) {
+		dpp_auth_fail(auth, "Mismatching Initiator Authenticating Tag");
+		goto fail;
+	}
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	dpp_auth_success(auth);
+	return 0;
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	return -1;
+}
+
+
+void dpp_configuration_free(struct dpp_configuration *conf)
+{
+	if (!conf)
+		return;
+	str_clear_free(conf->passphrase);
+	bin_clear_free(conf, sizeof(*conf));
+}
+
+
+void dpp_auth_deinit(struct dpp_authentication *auth)
+{
+	if (!auth)
+		return;
+	dpp_configuration_free(auth->conf_ap);
+	dpp_configuration_free(auth->conf_sta);
+	EVP_PKEY_free(auth->own_protocol_key);
+	EVP_PKEY_free(auth->peer_protocol_key);
+	wpabuf_free(auth->req_msg);
+	wpabuf_free(auth->resp_msg);
+	wpabuf_free(auth->conf_req);
+	os_free(auth->connector);
+	wpabuf_free(auth->net_access_key);
+	wpabuf_free(auth->c_sign_key);
+	dpp_bootstrap_info_free(auth->tmp_own_bi);
+#ifdef CONFIG_TESTING_OPTIONS
+	os_free(auth->config_obj_override);
+	os_free(auth->discovery_override);
+	os_free(auth->groups_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+	bin_clear_free(auth, sizeof(*auth));
+}
+
+
+static struct wpabuf *
+dpp_build_conf_start(struct dpp_authentication *auth,
+		     struct dpp_configuration *conf, size_t tailroom)
+{
+	struct wpabuf *buf;
+	char ssid[6 * sizeof(conf->ssid) + 1];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->discovery_override)
+		tailroom += os_strlen(auth->discovery_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	buf = wpabuf_alloc(200 + tailroom);
+	if (!buf)
+		return NULL;
+	wpabuf_put_str(buf, "{\"wi-fi_tech\":\"infra\",\"discovery\":");
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->discovery_override) {
+		wpa_printf(MSG_DEBUG, "DPP: TESTING - discovery override: '%s'",
+			   auth->discovery_override);
+		wpabuf_put_str(buf, auth->discovery_override);
+		wpabuf_put_u8(buf, ',');
+		return buf;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpabuf_put_str(buf, "{\"ssid\":\"");
+	json_escape_string(ssid, sizeof(ssid),
+			   (const char *) conf->ssid, conf->ssid_len);
+	wpabuf_put_str(buf, ssid);
+	wpabuf_put_str(buf, "\"},");
+
+	return buf;
+}
+
+
+static int dpp_build_jwk(struct wpabuf *buf, const char *name, EVP_PKEY *key,
+			 const char *kid, const struct dpp_curve_params *curve)
+{
+	struct wpabuf *pub;
+	const u8 *pos;
+	char *x = NULL, *y = NULL;
+	int ret = -1;
+
+	pub = dpp_get_pubkey_point(key, 0);
+	if (!pub)
+		goto fail;
+	pos = wpabuf_head(pub);
+	x = (char *) base64_url_encode(pos, curve->prime_len, NULL, 0);
+	pos += curve->prime_len;
+	y = (char *) base64_url_encode(pos, curve->prime_len, NULL, 0);
+	if (!x || !y)
+		goto fail;
+
+	wpabuf_put_str(buf, "\"");
+	wpabuf_put_str(buf, name);
+	wpabuf_put_str(buf, "\":{\"kty\":\"EC\",\"crv\":\"");
+	wpabuf_put_str(buf, curve->jwk_crv);
+	wpabuf_put_str(buf, "\",\"x\":\"");
+	wpabuf_put_str(buf, x);
+	wpabuf_put_str(buf, "\",\"y\":\"");
+	wpabuf_put_str(buf, y);
+	if (kid) {
+		wpabuf_put_str(buf, "\",\"kid\":\"");
+		wpabuf_put_str(buf, kid);
+	}
+	wpabuf_put_str(buf, "\"}");
+	ret = 0;
+fail:
+	wpabuf_free(pub);
+	os_free(x);
+	os_free(y);
+	return ret;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj_dpp(struct dpp_authentication *auth, int ap,
+		       struct dpp_configuration *conf)
+{
+	struct wpabuf *buf = NULL;
+	char *signed1 = NULL, *signed2 = NULL, *signed3 = NULL;
+	size_t tailroom;
+	const struct dpp_curve_params *curve;
+	char jws_prot_hdr[100];
+	size_t signed1_len, signed2_len, signed3_len;
+	struct wpabuf *dppcon = NULL;
+	unsigned char *signature = NULL;
+	const unsigned char *p;
+	size_t signature_len;
+	EVP_MD_CTX *md_ctx = NULL;
+	ECDSA_SIG *sig = NULL;
+	char *dot = ".";
+	const EVP_MD *sign_md;
+	const BIGNUM *r, *s;
+	size_t extra_len = 1000;
+
+	if (!auth->conf) {
+		wpa_printf(MSG_INFO,
+			   "DPP: No configurator specified - cannot generate DPP config object");
+		goto fail;
+	}
+	curve = auth->conf->curve;
+	if (curve->hash_len == SHA256_MAC_LEN) {
+		sign_md = EVP_sha256();
+	} else if (curve->hash_len == SHA384_MAC_LEN) {
+		sign_md = EVP_sha384();
+	} else if (curve->hash_len == SHA512_MAC_LEN) {
+		sign_md = EVP_sha512();
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unknown signature algorithm");
+		goto fail;
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->groups_override)
+		extra_len += os_strlen(auth->groups_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Connector (JSON dppCon object) */
+	dppcon = wpabuf_alloc(extra_len + 2 * auth->curve->prime_len * 4 / 3);
+	if (!dppcon)
+		goto fail;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->groups_override) {
+		wpabuf_put_u8(dppcon, '{');
+		if (auth->groups_override) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: TESTING - groups override: '%s'",
+				   auth->groups_override);
+			wpabuf_put_str(dppcon, "\"groups\":");
+			wpabuf_put_str(dppcon, auth->groups_override);
+			wpabuf_put_u8(dppcon, ',');
+		}
+		goto skip_groups;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpabuf_put_str(dppcon, "{\"groups\":[{\"groupId\":\"*\",");
+	wpabuf_printf(dppcon, "\"netRole\":\"%s\"}],", ap ? "ap" : "sta");
+#ifdef CONFIG_TESTING_OPTIONS
+skip_groups:
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (dpp_build_jwk(dppcon, "netAccessKey", auth->peer_protocol_key, NULL,
+			  auth->curve) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to build netAccessKey JWK");
+		goto fail;
+	}
+	if (conf->netaccesskey_expiry) {
+		struct os_tm tm;
+
+		if (os_gmtime(conf->netaccesskey_expiry, &tm) < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Failed to generate expiry string");
+			goto fail;
+		}
+		wpabuf_printf(dppcon,
+			      ",\"expiry\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\"",
+			      tm.year, tm.month, tm.day,
+			      tm.hour, tm.min, tm.sec);
+	}
+	wpabuf_put_u8(dppcon, '}');
+	wpa_printf(MSG_DEBUG, "DPP: dppCon: %s",
+		   (const char *) wpabuf_head(dppcon));
+
+	os_snprintf(jws_prot_hdr, sizeof(jws_prot_hdr),
+		    "{\"typ\":\"dppCon\",\"kid\":\"%s\",\"alg\":\"%s\"}",
+		    auth->conf->kid, curve->jws_alg);
+	signed1 = (char *) base64_url_encode((unsigned char *) jws_prot_hdr,
+					     os_strlen(jws_prot_hdr),
+					     &signed1_len, 0);
+	signed2 = (char *) base64_url_encode(wpabuf_head(dppcon),
+					     wpabuf_len(dppcon),
+					     &signed2_len, 0);
+	if (!signed1 || !signed2)
+		goto fail;
+
+	md_ctx = EVP_MD_CTX_create();
+	if (!md_ctx)
+		goto fail;
+
+	ERR_clear_error();
+	if (EVP_DigestSignInit(md_ctx, NULL, sign_md, NULL,
+			       auth->conf->csign) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignInit failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestSignUpdate(md_ctx, signed1, signed1_len) != 1 ||
+	    EVP_DigestSignUpdate(md_ctx, dot, 1) != 1 ||
+	    EVP_DigestSignUpdate(md_ctx, signed2, signed2_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignUpdate failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestSignFinal(md_ctx, NULL, &signature_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignFinal failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	signature = os_malloc(signature_len);
+	if (!signature)
+		goto fail;
+	if (EVP_DigestSignFinal(md_ctx, signature, &signature_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignFinal failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector ECDSA signature (DER)",
+		    signature, signature_len);
+	/* Convert to raw coordinates r,s */
+	p = signature;
+	sig = d2i_ECDSA_SIG(NULL, &p, signature_len);
+	if (!sig)
+		goto fail;
+	ECDSA_SIG_get0(sig, &r, &s);
+	if (dpp_bn2bin_pad(r, signature, curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(s, signature + curve->prime_len,
+			   curve->prime_len) < 0)
+		goto fail;
+	signature_len = 2 * curve->prime_len;
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector ECDSA signature (raw r,s)",
+		    signature, signature_len);
+	signed3 = (char *) base64_url_encode(signature, signature_len,
+					     &signed3_len, 0);
+	if (!signed3)
+		goto fail;
+
+	tailroom = 1000;
+	tailroom += 2 * curve->prime_len * 4 / 3 + os_strlen(auth->conf->kid);
+	tailroom += signed1_len + signed2_len + signed3_len;
+	buf = dpp_build_conf_start(auth, conf, tailroom);
+	if (!buf)
+		goto fail;
+
+	wpabuf_put_str(buf, "\"cred\":{\"akm\":\"dpp\",\"signedConnector\":\"");
+	wpabuf_put_str(buf, signed1);
+	wpabuf_put_u8(buf, '.');
+	wpabuf_put_str(buf, signed2);
+	wpabuf_put_u8(buf, '.');
+	wpabuf_put_str(buf, signed3);
+	wpabuf_put_str(buf, "\",");
+	if (dpp_build_jwk(buf, "csign", auth->conf->csign, auth->conf->kid,
+			  curve) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to build csign JWK");
+		goto fail;
+	}
+
+	wpabuf_put_str(buf, "}}");
+
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object",
+			      wpabuf_head(buf), wpabuf_len(buf));
+
+out:
+	EVP_MD_CTX_destroy(md_ctx);
+	ECDSA_SIG_free(sig);
+	os_free(signed1);
+	os_free(signed2);
+	os_free(signed3);
+	os_free(signature);
+	wpabuf_free(dppcon);
+	return buf;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: Failed to build configuration object");
+	wpabuf_free(buf);
+	buf = NULL;
+	goto out;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj_legacy(struct dpp_authentication *auth, int ap,
+			  struct dpp_configuration *conf)
+{
+	struct wpabuf *buf;
+
+	buf = dpp_build_conf_start(auth, conf, 1000);
+	if (!buf)
+		return NULL;
+
+	wpabuf_printf(buf, "\"cred\":{\"akm\":\"%s\",", dpp_akm_str(conf->akm));
+	if (conf->passphrase) {
+		char pass[63 * 6 + 1];
+
+		if (os_strlen(conf->passphrase) > 63) {
+			wpabuf_free(buf);
+			return NULL;
+		}
+
+		json_escape_string(pass, sizeof(pass), conf->passphrase,
+				   os_strlen(conf->passphrase));
+		wpabuf_put_str(buf, "\"pass\":\"");
+		wpabuf_put_str(buf, pass);
+		wpabuf_put_str(buf, "\"");
+	} else {
+		char psk[2 * sizeof(conf->psk) + 1];
+
+		wpa_snprintf_hex(psk, sizeof(psk),
+				 conf->psk, sizeof(conf->psk));
+		wpabuf_put_str(buf, "\"psk_hex\":\"");
+		wpabuf_put_str(buf, psk);
+		wpabuf_put_str(buf, "\"");
+	}
+	wpabuf_put_str(buf, "}}");
+
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object (legacy)",
+			      wpabuf_head(buf), wpabuf_len(buf));
+
+	return buf;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj(struct dpp_authentication *auth, int ap)
+{
+	struct dpp_configuration *conf;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->config_obj_override) {
+		wpa_printf(MSG_DEBUG, "DPP: Testing - Config Object override");
+		return wpabuf_alloc_copy(auth->config_obj_override,
+					 os_strlen(auth->config_obj_override));
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	conf = ap ? auth->conf_ap : auth->conf_sta;
+	if (!conf) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No configuration available for Enrollee(%s) - reject configuration request",
+			   ap ? "ap" : "sta");
+		return NULL;
+	}
+
+	if (conf->akm == DPP_AKM_DPP)
+		return dpp_build_conf_obj_dpp(auth, ap, conf);
+	return dpp_build_conf_obj_legacy(auth, ap, conf);
+}
+
+
+static struct wpabuf *
+dpp_build_conf_resp(struct dpp_authentication *auth, const u8 *e_nonce,
+		    u16 e_nonce_len, int ap)
+{
+	struct wpabuf *conf;
+	size_t clear_len, attr_len;
+	struct wpabuf *clear = NULL, *msg = NULL;
+	u8 *wrapped;
+	const u8 *addr[1];
+	size_t len[1];
+	enum dpp_status_error status;
+
+	conf = dpp_build_conf_obj(auth, ap);
+	if (conf) {
+		wpa_hexdump_ascii(MSG_DEBUG, "DPP: configurationObject JSON",
+				  wpabuf_head(conf), wpabuf_len(conf));
+	}
+	status = conf ? DPP_STATUS_OK : DPP_STATUS_CONFIGURE_FAILURE;
+
+	/* { E-nonce, configurationObject}ke */
+	clear_len = 4 + e_nonce_len;
+	if (conf)
+		clear_len += 4 + wpabuf_len(conf);
+	clear = wpabuf_alloc(clear_len);
+	attr_len = 4 + 1 + 4 + clear_len + AES_BLOCK_SIZE;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_RESP)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = wpabuf_alloc(attr_len);
+	if (!clear || !msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_E_NONCE_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no E-nonce");
+		goto skip_e_nonce;
+	}
+	if (dpp_test == DPP_TEST_E_NONCE_MISMATCH_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - E-nonce mismatch");
+		wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+		wpabuf_put_le16(clear, e_nonce_len);
+		wpabuf_put_data(clear, e_nonce, e_nonce_len - 1);
+		wpabuf_put_u8(clear, e_nonce[e_nonce_len - 1] ^ 0x01);
+		goto skip_e_nonce;
+	}
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* E-nonce */
+	wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+	wpabuf_put_le16(clear, e_nonce_len);
+	wpabuf_put_data(clear, e_nonce, e_nonce_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_e_nonce:
+	if (dpp_test == DPP_TEST_NO_CONFIG_OBJ_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - Config Object");
+		goto skip_config_obj;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (conf) {
+		wpabuf_put_le16(clear, DPP_ATTR_CONFIG_OBJ);
+		wpabuf_put_le16(clear, wpabuf_len(conf));
+		wpabuf_put_buf(clear, conf);
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_config_obj:
+	if (dpp_test == DPP_TEST_NO_STATUS_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - Status");
+		goto skip_status;
+	}
+	if (dpp_test == DPP_TEST_INVALID_STATUS_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status");
+		status = 255;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* DPP Status */
+	dpp_build_attr_status(msg, status);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_status:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	addr[0] = wpabuf_head(msg);
+	len[0] = wpabuf_len(msg);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    1, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Configuration Response attributes", msg);
+out:
+	wpabuf_free(conf);
+	wpabuf_free(clear);
+
+	return msg;
+fail:
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+struct wpabuf *
+dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start,
+		size_t attr_len)
+{
+	const u8 *wrapped_data, *e_nonce, *config_attr;
+	u16 wrapped_data_len, e_nonce_len, config_attr_len;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	struct wpabuf *resp = NULL;
+	struct json_token *root = NULL, *token;
+	int ap;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_CONF_REQ) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at Config Request");
+		return NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (dpp_check_attrs(attr_start, attr_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in config request");
+		return NULL;
+	}
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Wrapped Data attribute");
+		return NULL;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return NULL;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    0, NULL, NULL, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	e_nonce = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_ENROLLEE_NONCE,
+			       &e_nonce_len);
+	if (!e_nonce || e_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Enrollee Nonce attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Enrollee Nonce", e_nonce, e_nonce_len);
+
+	config_attr = dpp_get_attr(unwrapped, unwrapped_len,
+				   DPP_ATTR_CONFIG_ATTR_OBJ,
+				   &config_attr_len);
+	if (!config_attr) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Config Attributes attribute");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: Config Attributes",
+			  config_attr, config_attr_len);
+
+	root = json_parse((const char *) config_attr, config_attr_len);
+	if (!root) {
+		dpp_auth_fail(auth, "Could not parse Config Attributes");
+		goto fail;
+	}
+
+	token = json_get_member(root, "name");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No Config Attributes - name");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Enrollee name = '%s'", token->string);
+
+	token = json_get_member(root, "wi-fi_tech");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No Config Attributes - wi-fi_tech");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: wi-fi_tech = '%s'", token->string);
+	if (os_strcmp(token->string, "infra") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported wi-fi_tech '%s'",
+			   token->string);
+		dpp_auth_fail(auth, "Unsupported wi-fi_tech");
+		goto fail;
+	}
+
+	token = json_get_member(root, "netRole");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No Config Attributes - netRole");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: netRole = '%s'", token->string);
+	if (os_strcmp(token->string, "sta") == 0) {
+		ap = 0;
+	} else if (os_strcmp(token->string, "ap") == 0) {
+		ap = 1;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported netRole '%s'",
+			   token->string);
+		dpp_auth_fail(auth, "Unsupported netRole");
+		goto fail;
+	}
+
+	resp = dpp_build_conf_resp(auth, e_nonce, e_nonce_len, ap);
+
+fail:
+	json_free(root);
+	os_free(unwrapped);
+	return resp;
+}
+
+
+static struct wpabuf *
+dpp_parse_jws_prot_hdr(const struct dpp_curve_params *curve,
+		       const u8 *prot_hdr, u16 prot_hdr_len,
+		       const EVP_MD **ret_md)
+{
+	struct json_token *root, *token;
+	struct wpabuf *kid = NULL;
+
+	root = json_parse((const char *) prot_hdr, prot_hdr_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: JSON parsing failed for JWS Protected Header");
+		goto fail;
+	}
+
+	if (root->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: JWS Protected Header root is not an object");
+		goto fail;
+	}
+
+	token = json_get_member(root, "typ");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No typ string value found");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: JWS Protected Header typ=%s",
+		   token->string);
+	if (os_strcmp(token->string, "dppCon") != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported JWS Protected Header typ=%s",
+			   token->string);
+		goto fail;
+	}
+
+	token = json_get_member(root, "alg");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No alg string value found");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: JWS Protected Header alg=%s",
+		   token->string);
+	if (os_strcmp(token->string, curve->jws_alg) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWS Protected Header alg=%s (expected %s based on C-sign-key)",
+			   token->string, curve->jws_alg);
+		goto fail;
+	}
+	if (os_strcmp(token->string, "ES256") == 0 ||
+	    os_strcmp(token->string, "BS256") == 0)
+		*ret_md = EVP_sha256();
+	else if (os_strcmp(token->string, "ES384") == 0 ||
+		 os_strcmp(token->string, "BS384") == 0)
+		*ret_md = EVP_sha384();
+	else if (os_strcmp(token->string, "ES512") == 0 ||
+		 os_strcmp(token->string, "BS512") == 0)
+		*ret_md = EVP_sha512();
+	else
+		*ret_md = NULL;
+	if (!*ret_md) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported JWS Protected Header alg=%s",
+			   token->string);
+		goto fail;
+	}
+
+	kid = json_get_member_base64url(root, "kid");
+	if (!kid) {
+		wpa_printf(MSG_DEBUG, "DPP: No kid string value found");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWS Protected Header kid (decoded)",
+			kid);
+
+fail:
+	json_free(root);
+	return kid;
+}
+
+
+static int dpp_parse_cred_legacy(struct dpp_authentication *auth,
+				 struct json_token *cred)
+{
+	struct json_token *pass, *psk_hex;
+
+	wpa_printf(MSG_DEBUG, "DPP: Legacy akm=psk credential");
+
+	pass = json_get_member(cred, "pass");
+	psk_hex = json_get_member(cred, "psk_hex");
+
+	if (pass && pass->type == JSON_STRING) {
+		size_t len = os_strlen(pass->string);
+
+		wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Legacy passphrase",
+				      pass->string, len);
+		if (len < 8 || len > 63)
+			return -1;
+		os_strlcpy(auth->passphrase, pass->string,
+			   sizeof(auth->passphrase));
+	} else if (psk_hex && psk_hex->type == JSON_STRING) {
+		if (auth->akm == DPP_AKM_SAE) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Unexpected psk_hex with akm=sae");
+			return -1;
+		}
+		if (os_strlen(psk_hex->string) != PMK_LEN * 2 ||
+		    hexstr2bin(psk_hex->string, auth->psk, PMK_LEN) < 0) {
+			wpa_printf(MSG_DEBUG, "DPP: Invalid psk_hex encoding");
+			return -1;
+		}
+		wpa_hexdump_key(MSG_DEBUG, "DPP: Legacy PSK",
+				auth->psk, PMK_LEN);
+		auth->psk_set = 1;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: No pass or psk_hex strings found");
+		return -1;
+	}
+
+	if ((auth->akm == DPP_AKM_SAE || auth->akm == DPP_AKM_PSK_SAE) &&
+	    !auth->passphrase[0]) {
+		wpa_printf(MSG_DEBUG, "DPP: No pass for sae found");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static EVP_PKEY * dpp_parse_jwk(struct json_token *jwk,
+				const struct dpp_curve_params **key_curve)
+{
+	struct json_token *token;
+	const struct dpp_curve_params *curve;
+	struct wpabuf *x = NULL, *y = NULL;
+	EC_GROUP *group;
+	EVP_PKEY *pkey = NULL;
+
+	token = json_get_member(jwk, "kty");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No kty in JWK");
+		goto fail;
+	}
+	if (os_strcmp(token->string, "EC") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unexpected JWK kty '%s",
+			   token->string);
+		goto fail;
+	}
+
+	token = json_get_member(jwk, "crv");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No crv in JWK");
+		goto fail;
+	}
+	curve = dpp_get_curve_jwk_crv(token->string);
+	if (!curve) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported JWK crv '%s'",
+			   token->string);
+		goto fail;
+	}
+
+	x = json_get_member_base64url(jwk, "x");
+	if (!x) {
+		wpa_printf(MSG_DEBUG, "DPP: No x in JWK");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK x", x);
+	if (wpabuf_len(x) != curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWK x length %u (expected %u for curve %s)",
+			   (unsigned int) wpabuf_len(x),
+			   (unsigned int) curve->prime_len, curve->name);
+		goto fail;
+	}
+
+	y = json_get_member_base64url(jwk, "y");
+	if (!y) {
+		wpa_printf(MSG_DEBUG, "DPP: No y in JWK");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK y", y);
+	if (wpabuf_len(y) != curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWK y length %u (expected %u for curve %s)",
+			   (unsigned int) wpabuf_len(y),
+			   (unsigned int) curve->prime_len, curve->name);
+		goto fail;
+	}
+
+	group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(curve->name));
+	if (!group) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not prepare group for JWK");
+		goto fail;
+	}
+
+	pkey = dpp_set_pubkey_point_group(group, wpabuf_head(x), wpabuf_head(y),
+					  wpabuf_len(x));
+	*key_curve = curve;
+
+fail:
+	wpabuf_free(x);
+	wpabuf_free(y);
+
+	return pkey;
+}
+
+
+int dpp_key_expired(const char *timestamp, os_time_t *expiry)
+{
+	struct os_time now;
+	unsigned int year, month, day, hour, min, sec;
+	os_time_t utime;
+	const char *pos;
+
+	/* ISO 8601 date and time:
+	 * <date>T<time>
+	 * YYYY-MM-DDTHH:MM:SSZ
+	 * YYYY-MM-DDTHH:MM:SS+03:00
+	 */
+	if (os_strlen(timestamp) < 19) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Too short timestamp - assume expired key");
+		return 1;
+	}
+	if (sscanf(timestamp, "%04u-%02u-%02uT%02u:%02u:%02u",
+		   &year, &month, &day, &hour, &min, &sec) != 6) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to parse expiration day - assume expired key");
+		return 1;
+	}
+
+	if (os_mktime(year, month, day, hour, min, sec, &utime) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid date/time information - assume expired key");
+		return 1;
+	}
+
+	pos = timestamp + 19;
+	if (*pos == 'Z' || *pos == '\0') {
+		/* In UTC - no need to adjust */
+	} else if (*pos == '-' || *pos == '+') {
+		int items;
+
+		/* Adjust local time to UTC */
+		items = sscanf(pos + 1, "%02u:%02u", &hour, &min);
+		if (items < 1) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Invalid time zone designator (%s) - assume expired key",
+				   pos);
+			return 1;
+		}
+		if (*pos == '-')
+			utime += 3600 * hour;
+		if (*pos == '+')
+			utime -= 3600 * hour;
+		if (items > 1) {
+			if (*pos == '-')
+				utime += 60 * min;
+			if (*pos == '+')
+				utime -= 60 * min;
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid time zone designator (%s) - assume expired key",
+			   pos);
+		return 1;
+	}
+	if (expiry)
+		*expiry = utime;
+
+	if (os_get_time(&now) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Cannot get current time - assume expired key");
+		return 1;
+	}
+
+	if (now.sec > utime) {
+		wpa_printf(MSG_DEBUG, "DPP: Key has expired (%lu < %lu)",
+			   utime, now.sec);
+		return 1;
+	}
+
+	return 0;
+}
+
+
+static int dpp_parse_connector(struct dpp_authentication *auth,
+			       const unsigned char *payload,
+			       u16 payload_len)
+{
+	struct json_token *root, *groups, *netkey, *token;
+	int ret = -1;
+	EVP_PKEY *key = NULL;
+	const struct dpp_curve_params *curve;
+	unsigned int rules = 0;
+
+	root = json_parse((const char *) payload, payload_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG, "DPP: JSON parsing of connector failed");
+		goto fail;
+	}
+
+	groups = json_get_member(root, "groups");
+	if (!groups || groups->type != JSON_ARRAY) {
+		wpa_printf(MSG_DEBUG, "DPP: No groups array found");
+		goto skip_groups;
+	}
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG, "DPP: Missing groupId string");
+			goto fail;
+		}
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG, "DPP: Missing netRole string");
+			goto fail;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "DPP: connector group: groupId='%s' netRole='%s'",
+			   id->string, role->string);
+		rules++;
+	}
+skip_groups:
+
+	if (!rules) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Connector includes no groups");
+		goto fail;
+	}
+
+	token = json_get_member(root, "expiry");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No expiry string found - connector does not expire");
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: expiry = %s", token->string);
+		if (dpp_key_expired(token->string,
+				    &auth->net_access_key_expiry)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Connector (netAccessKey) has expired");
+			goto fail;
+		}
+	}
+
+	netkey = json_get_member(root, "netAccessKey");
+	if (!netkey || netkey->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found");
+		goto fail;
+	}
+
+	key = dpp_parse_jwk(netkey, &curve);
+	if (!key)
+		goto fail;
+	dpp_debug_print_key("DPP: Received netAccessKey", key);
+
+	if (EVP_PKEY_cmp(key, auth->own_protocol_key) != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: netAccessKey in connector does not match own protocol key");
+#ifdef CONFIG_TESTING_OPTIONS
+		if (auth->ignore_netaccesskey_mismatch) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: TESTING - skip netAccessKey mismatch");
+		} else {
+			goto fail;
+		}
+#else /* CONFIG_TESTING_OPTIONS */
+		goto fail;
+#endif /* CONFIG_TESTING_OPTIONS */
+	}
+
+	ret = 0;
+fail:
+	EVP_PKEY_free(key);
+	json_free(root);
+	return ret;
+}
+
+
+static int dpp_check_pubkey_match(EVP_PKEY *pub, struct wpabuf *r_hash)
+{
+	struct wpabuf *uncomp;
+	int res;
+	u8 hash[SHA256_MAC_LEN];
+	const u8 *addr[1];
+	size_t len[1];
+
+	if (wpabuf_len(r_hash) != SHA256_MAC_LEN)
+		return -1;
+	uncomp = dpp_get_pubkey_point(pub, 1);
+	if (!uncomp)
+		return -1;
+	addr[0] = wpabuf_head(uncomp);
+	len[0] = wpabuf_len(uncomp);
+	wpa_hexdump(MSG_DEBUG, "DPP: Uncompressed public key",
+		    addr[0], len[0]);
+	res = sha256_vector(1, addr, len, hash);
+	wpabuf_free(uncomp);
+	if (res < 0)
+		return -1;
+	if (os_memcmp(hash, wpabuf_head(r_hash), SHA256_MAC_LEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Received hash value does not match calculated public key hash value");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated hash",
+			    hash, SHA256_MAC_LEN);
+		return -1;
+	}
+	return 0;
+}
+
+
+static void dpp_copy_csign(struct dpp_authentication *auth, EVP_PKEY *csign)
+{
+	unsigned char *der = NULL;
+	int der_len;
+
+	der_len = i2d_PUBKEY(csign, &der);
+	if (der_len <= 0)
+		return;
+	wpabuf_free(auth->c_sign_key);
+	auth->c_sign_key = wpabuf_alloc_copy(der, der_len);
+	OPENSSL_free(der);
+}
+
+
+static void dpp_copy_netaccesskey(struct dpp_authentication *auth)
+{
+	unsigned char *der = NULL;
+	int der_len;
+	EC_KEY *eckey;
+
+	eckey = EVP_PKEY_get1_EC_KEY(auth->own_protocol_key);
+	if (!eckey)
+		return;
+
+	der_len = i2d_ECPrivateKey(eckey, &der);
+	if (der_len <= 0) {
+		EC_KEY_free(eckey);
+		return;
+	}
+	wpabuf_free(auth->net_access_key);
+	auth->net_access_key = wpabuf_alloc_copy(der, der_len);
+	OPENSSL_free(der);
+	EC_KEY_free(eckey);
+}
+
+
+struct dpp_signed_connector_info {
+	unsigned char *payload;
+	size_t payload_len;
+};
+
+static enum dpp_status_error
+dpp_process_signed_connector(struct dpp_signed_connector_info *info,
+			     EVP_PKEY *csign_pub, const char *connector)
+{
+	enum dpp_status_error ret = 255;
+	const char *pos, *end, *signed_start, *signed_end;
+	struct wpabuf *kid = NULL;
+	unsigned char *prot_hdr = NULL, *signature = NULL;
+	size_t prot_hdr_len = 0, signature_len = 0;
+	const EVP_MD *sign_md = NULL;
+	unsigned char *der = NULL;
+	int der_len;
+	int res;
+	EVP_MD_CTX *md_ctx = NULL;
+	ECDSA_SIG *sig = NULL;
+	BIGNUM *r = NULL, *s = NULL;
+	const struct dpp_curve_params *curve;
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	int nid;
+
+	eckey = EVP_PKEY_get1_EC_KEY(csign_pub);
+	if (!eckey)
+		goto fail;
+	group = EC_KEY_get0_group(eckey);
+	if (!group)
+		goto fail;
+	nid = EC_GROUP_get_curve_name(group);
+	curve = dpp_get_curve_nid(nid);
+	if (!curve)
+		goto fail;
+	wpa_printf(MSG_DEBUG, "DPP: C-sign-key group: %s", curve->jwk_crv);
+	os_memset(info, 0, sizeof(*info));
+
+	signed_start = pos = connector;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing dot(1) in signedConnector");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	prot_hdr = base64_url_decode((const unsigned char *) pos,
+				     end - pos, &prot_hdr_len);
+	if (!prot_hdr) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector JWS Protected Header");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG,
+			  "DPP: signedConnector - JWS Protected Header",
+			  prot_hdr, prot_hdr_len);
+	kid = dpp_parse_jws_prot_hdr(curve, prot_hdr, prot_hdr_len, &sign_md);
+	if (!kid) {
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	if (wpabuf_len(kid) != SHA256_MAC_LEN) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected signedConnector JWS Protected Header kid length: %u (expected %u)",
+			   (unsigned int) wpabuf_len(kid), SHA256_MAC_LEN);
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	pos = end + 1;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing dot(2) in signedConnector");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	signed_end = end - 1;
+	info->payload = base64_url_decode((const unsigned char *) pos,
+					  end - pos, &info->payload_len);
+	if (!info->payload) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector JWS Payload");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG,
+			  "DPP: signedConnector - JWS Payload",
+			  info->payload, info->payload_len);
+	pos = end + 1;
+	signature = base64_url_decode((const unsigned char *) pos,
+				      os_strlen(pos), &signature_len);
+	if (!signature) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector signature");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+		}
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector - signature",
+		    signature, signature_len);
+
+	if (dpp_check_pubkey_match(csign_pub, kid) < 0) {
+		ret = DPP_STATUS_NO_MATCH;
+		goto fail;
+	}
+
+	if (signature_len & 0x01) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected signedConnector signature length (%d)",
+			   (int) signature_len);
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	/* JWS Signature encodes the signature (r,s) as two octet strings. Need
+	 * to convert that to DER encoded ECDSA_SIG for OpenSSL EVP routines. */
+	r = BN_bin2bn(signature, signature_len / 2, NULL);
+	s = BN_bin2bn(signature + signature_len / 2, signature_len / 2, NULL);
+	sig = ECDSA_SIG_new();
+	if (!r || !s || !sig || ECDSA_SIG_set0(sig, r, s) != 1)
+		goto fail;
+	r = NULL;
+	s = NULL;
+
+	der_len = i2d_ECDSA_SIG(sig, &der);
+	if (der_len <= 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not DER encode signature");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: DER encoded signature", der, der_len);
+	md_ctx = EVP_MD_CTX_create();
+	if (!md_ctx)
+		goto fail;
+
+	ERR_clear_error();
+	if (EVP_DigestVerifyInit(md_ctx, NULL, sign_md, NULL, csign_pub) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestVerifyInit failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestVerifyUpdate(md_ctx, signed_start,
+				   signed_end - signed_start + 1) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestVerifyUpdate failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	res = EVP_DigestVerifyFinal(md_ctx, der, der_len);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: EVP_DigestVerifyFinal failed (res=%d): %s",
+			   res, ERR_error_string(ERR_get_error(), NULL));
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	ret = DPP_STATUS_OK;
+fail:
+	EC_KEY_free(eckey);
+	EVP_MD_CTX_destroy(md_ctx);
+	os_free(prot_hdr);
+	wpabuf_free(kid);
+	os_free(signature);
+	ECDSA_SIG_free(sig);
+	BN_free(r);
+	BN_free(s);
+	OPENSSL_free(der);
+	return ret;
+}
+
+
+static int dpp_parse_cred_dpp(struct dpp_authentication *auth,
+			      struct json_token *cred)
+{
+	struct dpp_signed_connector_info info;
+	struct json_token *token, *csign;
+	int ret = -1;
+	EVP_PKEY *csign_pub = NULL;
+	const struct dpp_curve_params *key_curve = NULL;
+	const char *signed_connector;
+
+	os_memset(&info, 0, sizeof(info));
+
+	wpa_printf(MSG_DEBUG, "DPP: Connector credential");
+
+	csign = json_get_member(cred, "csign");
+	if (!csign || csign->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No csign JWK in JSON");
+		goto fail;
+	}
+
+	csign_pub = dpp_parse_jwk(csign, &key_curve);
+	if (!csign_pub) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse csign JWK");
+		goto fail;
+	}
+	dpp_debug_print_key("DPP: Received C-sign-key", csign_pub);
+
+	token = json_get_member(cred, "signedConnector");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No signedConnector string found");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: signedConnector",
+			  token->string, os_strlen(token->string));
+	signed_connector = token->string;
+
+	if (os_strchr(signed_connector, '"') ||
+	    os_strchr(signed_connector, '\n')) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected character in signedConnector");
+		goto fail;
+	}
+
+	if (dpp_process_signed_connector(&info, csign_pub,
+					 signed_connector) != DPP_STATUS_OK)
+		goto fail;
+
+	if (dpp_parse_connector(auth, info.payload, info.payload_len) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse connector");
+		goto fail;
+	}
+
+	os_free(auth->connector);
+	auth->connector = os_strdup(signed_connector);
+
+	dpp_copy_csign(auth, csign_pub);
+	dpp_copy_netaccesskey(auth);
+
+	ret = 0;
+fail:
+	EVP_PKEY_free(csign_pub);
+	os_free(info.payload);
+	return ret;
+}
+
+
+const char * dpp_akm_str(enum dpp_akm akm)
+{
+	switch (akm) {
+	case DPP_AKM_DPP:
+		return "dpp";
+	case DPP_AKM_PSK:
+		return "psk";
+	case DPP_AKM_SAE:
+		return "sae";
+	case DPP_AKM_PSK_SAE:
+		return "psk+sae";
+	default:
+		return "??";
+	}
+}
+
+
+static enum dpp_akm dpp_akm_from_str(const char *akm)
+{
+	if (os_strcmp(akm, "psk") == 0)
+		return DPP_AKM_PSK;
+	if (os_strcmp(akm, "sae") == 0)
+		return DPP_AKM_SAE;
+	if (os_strcmp(akm, "psk+sae") == 0)
+		return DPP_AKM_PSK_SAE;
+	if (os_strcmp(akm, "dpp") == 0)
+		return DPP_AKM_DPP;
+	return DPP_AKM_UNKNOWN;
+}
+
+
+static int dpp_parse_conf_obj(struct dpp_authentication *auth,
+			      const u8 *conf_obj, u16 conf_obj_len)
+{
+	int ret = -1;
+	struct json_token *root, *token, *discovery, *cred;
+
+	root = json_parse((const char *) conf_obj, conf_obj_len);
+	if (!root)
+		return -1;
+	if (root->type != JSON_OBJECT) {
+		dpp_auth_fail(auth, "JSON root is not an object");
+		goto fail;
+	}
+
+	token = json_get_member(root, "wi-fi_tech");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No wi-fi_tech string value found");
+		goto fail;
+	}
+	if (os_strcmp(token->string, "infra") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported wi-fi_tech value: '%s'",
+			   token->string);
+		dpp_auth_fail(auth, "Unsupported wi-fi_tech value");
+		goto fail;
+	}
+
+	discovery = json_get_member(root, "discovery");
+	if (!discovery || discovery->type != JSON_OBJECT) {
+		dpp_auth_fail(auth, "No discovery object in JSON");
+		goto fail;
+	}
+
+	token = json_get_member(discovery, "ssid");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No discovery::ssid string value found");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: discovery::ssid",
+			  token->string, os_strlen(token->string));
+	if (os_strlen(token->string) > SSID_MAX_LEN) {
+		dpp_auth_fail(auth, "Too long discovery::ssid string value");
+		goto fail;
+	}
+	auth->ssid_len = os_strlen(token->string);
+	os_memcpy(auth->ssid, token->string, auth->ssid_len);
+
+	cred = json_get_member(root, "cred");
+	if (!cred || cred->type != JSON_OBJECT) {
+		dpp_auth_fail(auth, "No cred object in JSON");
+		goto fail;
+	}
+
+	token = json_get_member(cred, "akm");
+	if (!token || token->type != JSON_STRING) {
+		dpp_auth_fail(auth, "No cred::akm string value found");
+		goto fail;
+	}
+	auth->akm = dpp_akm_from_str(token->string);
+
+	if (auth->akm == DPP_AKM_PSK || auth->akm == DPP_AKM_SAE ||
+	    auth->akm == DPP_AKM_PSK_SAE) {
+		if (dpp_parse_cred_legacy(auth, cred) < 0)
+			goto fail;
+	} else if (auth->akm == DPP_AKM_DPP) {
+		if (dpp_parse_cred_dpp(auth, cred) < 0)
+			goto fail;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported akm: %s",
+			   token->string);
+		dpp_auth_fail(auth, "Unsupported akm");
+		goto fail;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: JSON parsing completed successfully");
+	ret = 0;
+fail:
+	json_free(root);
+	return ret;
+}
+
+
+int dpp_conf_resp_rx(struct dpp_authentication *auth,
+		     const struct wpabuf *resp)
+{
+	const u8 *wrapped_data, *e_nonce, *status, *conf_obj;
+	u16 wrapped_data_len, e_nonce_len, status_len, conf_obj_len;
+	const u8 *addr[1];
+	size_t len[1];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	int ret = -1;
+
+	if (dpp_check_attrs(wpabuf_head(resp), wpabuf_len(resp)) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in config response");
+		return -1;
+	}
+
+	wrapped_data = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp),
+				    DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required Wrapped Data attribute");
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return -1;
+
+	addr[0] = wpabuf_head(resp);
+	len[0] = wrapped_data - 4 - (const u8 *) wpabuf_head(resp);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]);
+
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    1, addr, len, unwrapped) < 0) {
+		dpp_auth_fail(auth, "AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_auth_fail(auth, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	e_nonce = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_ENROLLEE_NONCE,
+			       &e_nonce_len);
+	if (!e_nonce || e_nonce_len != auth->curve->nonce_len) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid Enrollee Nonce attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Enrollee Nonce", e_nonce, e_nonce_len);
+	if (os_memcmp(e_nonce, auth->e_nonce, e_nonce_len) != 0) {
+		dpp_auth_fail(auth, "Enrollee Nonce mismatch");
+		goto fail;
+	}
+
+	status = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp),
+			      DPP_ATTR_STATUS, &status_len);
+	if (!status || status_len < 1) {
+		dpp_auth_fail(auth,
+			      "Missing or invalid required DPP Status attribute");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	if (status[0] != DPP_STATUS_OK) {
+		dpp_auth_fail(auth, "Configurator rejected configuration");
+		goto fail;
+	}
+
+	conf_obj = dpp_get_attr(unwrapped, unwrapped_len,
+				DPP_ATTR_CONFIG_OBJ, &conf_obj_len);
+	if (!conf_obj) {
+		dpp_auth_fail(auth,
+			      "Missing required Configuration Object attribute");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: configurationObject JSON",
+			  conf_obj, conf_obj_len);
+	if (dpp_parse_conf_obj(auth, conf_obj, conf_obj_len) < 0)
+		goto fail;
+
+	ret = 0;
+
+fail:
+	os_free(unwrapped);
+	return ret;
+}
+
+
+void dpp_configurator_free(struct dpp_configurator *conf)
+{
+	if (!conf)
+		return;
+	EVP_PKEY_free(conf->csign);
+	os_free(conf->kid);
+	os_free(conf);
+}
+
+
+int dpp_configurator_get_key(const struct dpp_configurator *conf, char *buf,
+			     size_t buflen)
+{
+	EC_KEY *eckey;
+	int key_len, ret = -1;
+	unsigned char *key = NULL;
+
+	if (!conf->csign)
+		return -1;
+
+	eckey = EVP_PKEY_get1_EC_KEY(conf->csign);
+	if (!eckey)
+		return -1;
+
+	key_len = i2d_ECPrivateKey(eckey, &key);
+	if (key_len > 0)
+		ret = wpa_snprintf_hex(buf, buflen, key, key_len);
+
+	EC_KEY_free(eckey);
+	OPENSSL_free(key);
+	return ret;
+}
+
+
+struct dpp_configurator *
+dpp_keygen_configurator(const char *curve, const u8 *privkey,
+			size_t privkey_len)
+{
+	struct dpp_configurator *conf;
+	struct wpabuf *csign_pub = NULL;
+	u8 kid_hash[SHA256_MAC_LEN];
+	const u8 *addr[1];
+	size_t len[1];
+
+	conf = os_zalloc(sizeof(*conf));
+	if (!conf)
+		return NULL;
+
+	if (!curve) {
+		conf->curve = &dpp_curves[0];
+	} else {
+		conf->curve = dpp_get_curve_name(curve);
+		if (!conf->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return NULL;
+		}
+	}
+	if (privkey)
+		conf->csign = dpp_set_keypair(&conf->curve, privkey,
+					      privkey_len);
+	else
+		conf->csign = dpp_gen_keypair(conf->curve);
+	if (!conf->csign)
+		goto fail;
+	conf->own = 1;
+
+	csign_pub = dpp_get_pubkey_point(conf->csign, 1);
+	if (!csign_pub) {
+		wpa_printf(MSG_INFO, "DPP: Failed to extract C-sign-key");
+		goto fail;
+	}
+
+	/* kid = SHA256(ANSI X9.63 uncompressed C-sign-key) */
+	addr[0] = wpabuf_head(csign_pub);
+	len[0] = wpabuf_len(csign_pub);
+	if (sha256_vector(1, addr, len, kid_hash) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to derive kid for C-sign-key");
+		goto fail;
+	}
+
+	conf->kid = (char *) base64_url_encode(kid_hash, sizeof(kid_hash),
+					       NULL, 0);
+	if (!conf->kid)
+		goto fail;
+out:
+	wpabuf_free(csign_pub);
+	return conf;
+fail:
+	dpp_configurator_free(conf);
+	conf = NULL;
+	goto out;
+}
+
+
+int dpp_configurator_own_config(struct dpp_authentication *auth,
+				const char *curve, int ap)
+{
+	struct wpabuf *conf_obj;
+	int ret = -1;
+
+	if (!auth->conf) {
+		wpa_printf(MSG_DEBUG, "DPP: No configurator specified");
+		return -1;
+	}
+
+	if (!curve) {
+		auth->curve = &dpp_curves[0];
+	} else {
+		auth->curve = dpp_get_curve_name(curve);
+		if (!auth->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return -1;
+		}
+	}
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Building own configuration/connector with curve %s",
+		   auth->curve->name);
+
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	if (!auth->own_protocol_key)
+		return -1;
+	dpp_copy_netaccesskey(auth);
+	auth->peer_protocol_key = auth->own_protocol_key;
+	dpp_copy_csign(auth, auth->conf->csign);
+
+	conf_obj = dpp_build_conf_obj(auth, ap);
+	if (!conf_obj)
+		goto fail;
+	ret = dpp_parse_conf_obj(auth, wpabuf_head(conf_obj),
+				 wpabuf_len(conf_obj));
+fail:
+	wpabuf_free(conf_obj);
+	auth->peer_protocol_key = NULL;
+	return ret;
+}
+
+
+static int dpp_compatible_netrole(const char *role1, const char *role2)
+{
+	return (os_strcmp(role1, "sta") == 0 && os_strcmp(role2, "ap") == 0) ||
+		(os_strcmp(role1, "ap") == 0 && os_strcmp(role2, "sta") == 0);
+}
+
+
+static int dpp_connector_compatible_group(struct json_token *root,
+					  const char *group_id,
+					  const char *net_role)
+{
+	struct json_token *groups, *token;
+
+	groups = json_get_member(root, "groups");
+	if (!groups || groups->type != JSON_ARRAY)
+		return 0;
+
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING)
+			continue;
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING)
+			continue;
+
+		if (os_strcmp(id->string, "*") != 0 &&
+		    os_strcmp(group_id, "*") != 0 &&
+		    os_strcmp(id->string, group_id) != 0)
+			continue;
+
+		if (dpp_compatible_netrole(role->string, net_role))
+			return 1;
+	}
+
+	return 0;
+}
+
+
+static int dpp_connector_match_groups(struct json_token *own_root,
+				      struct json_token *peer_root)
+{
+	struct json_token *groups, *token;
+
+	groups = json_get_member(peer_root, "groups");
+	if (!groups || groups->type != JSON_ARRAY) {
+		wpa_printf(MSG_DEBUG, "DPP: No peer groups array found");
+		return 0;
+	}
+
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Missing peer groupId string");
+			continue;
+		}
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Missing peer groups::netRole string");
+			continue;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "DPP: peer connector group: groupId='%s' netRole='%s'",
+			   id->string, role->string);
+		if (dpp_connector_compatible_group(own_root, id->string,
+						   role->string)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Compatible group/netRole in own connector");
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+
+static int dpp_derive_pmk(const u8 *Nx, size_t Nx_len, u8 *pmk,
+			  unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "DPP PMK";
+	int res;
+
+	/* PMK = HKDF(<>, "DPP PMK", N.x) */
+
+	/* HKDF-Extract(<>, N.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Nx, Nx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=N.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, pmk, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PMK = HKDF-Expand(PRK, info, L)",
+			pmk, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_pmkid(const struct dpp_curve_params *curve,
+			    EVP_PKEY *own_key, EVP_PKEY *peer_key, u8 *pmkid)
+{
+	struct wpabuf *nkx, *pkx;
+	int ret = -1, res;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 hash[SHA256_MAC_LEN];
+
+	/* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */
+	nkx = dpp_get_pubkey_point(own_key, 0);
+	pkx = dpp_get_pubkey_point(peer_key, 0);
+	if (!nkx || !pkx)
+		goto fail;
+	addr[0] = wpabuf_head(nkx);
+	len[0] = wpabuf_len(nkx) / 2;
+	addr[1] = wpabuf_head(pkx);
+	len[1] = wpabuf_len(pkx) / 2;
+	if (len[0] != len[1])
+		goto fail;
+	if (os_memcmp(addr[0], addr[1], len[0]) > 0) {
+		addr[0] = wpabuf_head(pkx);
+		addr[1] = wpabuf_head(nkx);
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 1", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 2", addr[1], len[1]);
+	res = sha256_vector(2, addr, len, hash);
+	if (res < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash output", hash, SHA256_MAC_LEN);
+	os_memcpy(pmkid, hash, PMKID_LEN);
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID", pmkid, PMKID_LEN);
+	ret = 0;
+fail:
+	wpabuf_free(nkx);
+	wpabuf_free(pkx);
+	return ret;
+}
+
+
+enum dpp_status_error
+dpp_peer_intro(struct dpp_introduction *intro, const char *own_connector,
+	       const u8 *net_access_key, size_t net_access_key_len,
+	       const u8 *csign_key, size_t csign_key_len,
+	       const u8 *peer_connector, size_t peer_connector_len,
+	       os_time_t *expiry)
+{
+	struct json_token *root = NULL, *netkey, *token;
+	struct json_token *own_root = NULL;
+	enum dpp_status_error ret = 255, res;
+	EVP_PKEY *own_key = NULL, *peer_key = NULL;
+	struct wpabuf *own_key_pub = NULL;
+	const struct dpp_curve_params *curve, *own_curve;
+	struct dpp_signed_connector_info info;
+	const unsigned char *p;
+	EVP_PKEY *csign = NULL;
+	char *signed_connector = NULL;
+	const char *pos, *end;
+	unsigned char *own_conn = NULL;
+	size_t own_conn_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t Nx_len;
+	u8 Nx[DPP_MAX_SHARED_SECRET_LEN];
+
+	os_memset(intro, 0, sizeof(*intro));
+	os_memset(&info, 0, sizeof(info));
+	if (expiry)
+		*expiry = 0;
+
+	p = csign_key;
+	csign = d2i_PUBKEY(NULL, &p, csign_key_len);
+	if (!csign) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to parse local C-sign-key information");
+		goto fail;
+	}
+
+	own_key = dpp_set_keypair(&own_curve, net_access_key,
+				  net_access_key_len);
+	if (!own_key) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to parse own netAccessKey");
+		goto fail;
+	}
+
+	pos = os_strchr(own_connector, '.');
+	if (!pos) {
+		wpa_printf(MSG_DEBUG, "DPP: Own connector is missing the first dot (.)");
+		goto fail;
+	}
+	pos++;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG, "DPP: Own connector is missing the second dot (.)");
+		goto fail;
+	}
+	own_conn = base64_url_decode((const unsigned char *) pos,
+				     end - pos, &own_conn_len);
+	if (!own_conn) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode own signedConnector JWS Payload");
+		goto fail;
+	}
+
+	own_root = json_parse((const char *) own_conn, own_conn_len);
+	if (!own_root) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse local connector");
+		goto fail;
+	}
+
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: Peer signedConnector",
+			  peer_connector, peer_connector_len);
+	signed_connector = os_malloc(peer_connector_len + 1);
+	if (!signed_connector)
+		goto fail;
+	os_memcpy(signed_connector, peer_connector, peer_connector_len);
+	signed_connector[peer_connector_len] = '\0';
+
+	res = dpp_process_signed_connector(&info, csign, signed_connector);
+	if (res != DPP_STATUS_OK) {
+		ret = res;
+		goto fail;
+	}
+
+	root = json_parse((const char *) info.payload, info.payload_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG, "DPP: JSON parsing of connector failed");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	if (!dpp_connector_match_groups(own_root, root)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Peer connector does not include compatible group netrole with own connector");
+		ret = DPP_STATUS_NO_MATCH;
+		goto fail;
+	}
+
+	token = json_get_member(root, "expiry");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No expiry string found - connector does not expire");
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: expiry = %s", token->string);
+		if (dpp_key_expired(token->string, expiry)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Connector (netAccessKey) has expired");
+			ret = DPP_STATUS_INVALID_CONNECTOR;
+			goto fail;
+		}
+	}
+
+	netkey = json_get_member(root, "netAccessKey");
+	if (!netkey || netkey->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found");
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	peer_key = dpp_parse_jwk(netkey, &curve);
+	if (!peer_key) {
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+	dpp_debug_print_key("DPP: Received netAccessKey", peer_key);
+
+	if (own_curve != curve) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mismatching netAccessKey curves (%s != %s)",
+			   own_curve->name, curve->name);
+		ret = DPP_STATUS_INVALID_CONNECTOR;
+		goto fail;
+	}
+
+	/* ECDH: N = nk * PK */
+	ctx = EVP_PKEY_CTX_new(own_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, peer_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Nx_len) != 1 ||
+	    Nx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Nx, &Nx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			Nx, Nx_len);
+
+	/* PMK = HKDF(<>, "DPP PMK", N.x) */
+	if (dpp_derive_pmk(Nx, Nx_len, intro->pmk, curve->hash_len) < 0) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to derive PMK");
+		goto fail;
+	}
+	intro->pmk_len = curve->hash_len;
+
+	/* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */
+	if (dpp_derive_pmkid(curve, own_key, peer_key, intro->pmkid) < 0) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to derive PMKID");
+		goto fail;
+	}
+
+	ret = DPP_STATUS_OK;
+fail:
+	if (ret != DPP_STATUS_OK)
+		os_memset(intro, 0, sizeof(*intro));
+	os_memset(Nx, 0, sizeof(Nx));
+	EVP_PKEY_CTX_free(ctx);
+	os_free(own_conn);
+	os_free(signed_connector);
+	os_free(info.payload);
+	EVP_PKEY_free(own_key);
+	wpabuf_free(own_key_pub);
+	EVP_PKEY_free(peer_key);
+	EVP_PKEY_free(csign);
+	json_free(root);
+	json_free(own_root);
+	return ret;
+}
+
+
+static EVP_PKEY * dpp_pkex_get_role_elem(const struct dpp_curve_params *curve,
+					 int init)
+{
+	EC_GROUP *group;
+	size_t len = curve->prime_len;
+	const u8 *x, *y;
+
+	switch (curve->ike_group) {
+	case 19:
+		x = init ? pkex_init_x_p256 : pkex_resp_x_p256;
+		y = init ? pkex_init_y_p256 : pkex_resp_y_p256;
+		break;
+	case 20:
+		x = init ? pkex_init_x_p384 : pkex_resp_x_p384;
+		y = init ? pkex_init_y_p384 : pkex_resp_y_p384;
+		break;
+	case 21:
+		x = init ? pkex_init_x_p521 : pkex_resp_x_p521;
+		y = init ? pkex_init_y_p521 : pkex_resp_y_p521;
+		break;
+	case 28:
+		x = init ? pkex_init_x_bp_p256r1 : pkex_resp_x_bp_p256r1;
+		y = init ? pkex_init_y_bp_p256r1 : pkex_resp_y_bp_p256r1;
+		break;
+	case 29:
+		x = init ? pkex_init_x_bp_p384r1 : pkex_resp_x_bp_p384r1;
+		y = init ? pkex_init_y_bp_p384r1 : pkex_resp_y_bp_p384r1;
+		break;
+	case 30:
+		x = init ? pkex_init_x_bp_p512r1 : pkex_resp_x_bp_p512r1;
+		y = init ? pkex_init_y_bp_p512r1 : pkex_resp_y_bp_p512r1;
+		break;
+	default:
+		return NULL;
+	}
+
+	group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(curve->name));
+	if (!group)
+		return NULL;
+	return dpp_set_pubkey_point_group(group, x, y, len);
+}
+
+
+static EC_POINT * dpp_pkex_derive_Qi(const struct dpp_curve_params *curve,
+				     const u8 *mac_init, const char *code,
+				     const char *identifier, BN_CTX *bnctx,
+				     const EC_GROUP **ret_group)
+{
+	u8 hash[DPP_MAX_HASH_LEN];
+	const u8 *addr[3];
+	size_t len[3];
+	unsigned int num_elem = 0;
+	EC_POINT *Qi = NULL;
+	EVP_PKEY *Pi = NULL;
+	EC_KEY *Pi_ec = NULL;
+	const EC_POINT *Pi_point;
+	BIGNUM *hash_bn = NULL;
+	const EC_GROUP *group = NULL;
+	EC_GROUP *group2 = NULL;
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+
+	wpa_printf(MSG_DEBUG, "DPP: MAC-Initiator: " MACSTR, MAC2STR(mac_init));
+	addr[num_elem] = mac_init;
+	len[num_elem] = ETH_ALEN;
+	num_elem++;
+	if (identifier) {
+		wpa_printf(MSG_DEBUG, "DPP: code identifier: %s",
+			   identifier);
+		addr[num_elem] = (const u8 *) identifier;
+		len[num_elem] = os_strlen(identifier);
+		num_elem++;
+	}
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: code", code, os_strlen(code));
+	addr[num_elem] = (const u8 *) code;
+	len[num_elem] = os_strlen(code);
+	num_elem++;
+	if (dpp_hash_vector(curve, num_elem, addr, len, hash) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG,
+			"DPP: H(MAC-Initiator | [identifier |] code)",
+			hash, curve->hash_len);
+	Pi = dpp_pkex_get_role_elem(curve, 1);
+	if (!Pi)
+		goto fail;
+	dpp_debug_print_key("DPP: Pi", Pi);
+	Pi_ec = EVP_PKEY_get1_EC_KEY(Pi);
+	if (!Pi_ec)
+		goto fail;
+	Pi_point = EC_KEY_get0_public_key(Pi_ec);
+
+	group = EC_KEY_get0_group(Pi_ec);
+	if (!group)
+		goto fail;
+	group2 = EC_GROUP_dup(group);
+	if (!group2)
+		goto fail;
+	Qi = EC_POINT_new(group2);
+	if (!Qi) {
+		EC_GROUP_free(group2);
+		goto fail;
+	}
+	hash_bn = BN_bin2bn(hash, curve->hash_len, NULL);
+	if (!hash_bn ||
+	    EC_POINT_mul(group2, Qi, NULL, Pi_point, hash_bn, bnctx) != 1)
+		goto fail;
+	if (EC_POINT_is_at_infinity(group, Qi)) {
+		wpa_printf(MSG_INFO, "DPP: Qi is the point-at-infinity");
+		goto fail;
+	}
+	dpp_debug_print_point("DPP: Qi", group, Qi);
+out:
+	EC_KEY_free(Pi_ec);
+	EVP_PKEY_free(Pi);
+	BN_clear_free(hash_bn);
+	if (ret_group)
+		*ret_group = group2;
+	return Qi;
+fail:
+	EC_POINT_free(Qi);
+	Qi = NULL;
+	goto out;
+}
+
+
+static EC_POINT * dpp_pkex_derive_Qr(const struct dpp_curve_params *curve,
+				     const u8 *mac_resp, const char *code,
+				     const char *identifier, BN_CTX *bnctx,
+				     const EC_GROUP **ret_group)
+{
+	u8 hash[DPP_MAX_HASH_LEN];
+	const u8 *addr[3];
+	size_t len[3];
+	unsigned int num_elem = 0;
+	EC_POINT *Qr = NULL;
+	EVP_PKEY *Pr = NULL;
+	EC_KEY *Pr_ec = NULL;
+	const EC_POINT *Pr_point;
+	BIGNUM *hash_bn = NULL;
+	const EC_GROUP *group = NULL;
+	EC_GROUP *group2 = NULL;
+
+	/* Qr = H(MAC-Responder | | [identifier | ] code) * Pr */
+
+	wpa_printf(MSG_DEBUG, "DPP: MAC-Responder: " MACSTR, MAC2STR(mac_resp));
+	addr[num_elem] = mac_resp;
+	len[num_elem] = ETH_ALEN;
+	num_elem++;
+	if (identifier) {
+		wpa_printf(MSG_DEBUG, "DPP: code identifier: %s",
+			   identifier);
+		addr[num_elem] = (const u8 *) identifier;
+		len[num_elem] = os_strlen(identifier);
+		num_elem++;
+	}
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: code", code, os_strlen(code));
+	addr[num_elem] = (const u8 *) code;
+	len[num_elem] = os_strlen(code);
+	num_elem++;
+	if (dpp_hash_vector(curve, num_elem, addr, len, hash) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG,
+			"DPP: H(MAC-Responder | [identifier |] code)",
+			hash, curve->hash_len);
+	Pr = dpp_pkex_get_role_elem(curve, 0);
+	if (!Pr)
+		goto fail;
+	dpp_debug_print_key("DPP: Pr", Pr);
+	Pr_ec = EVP_PKEY_get1_EC_KEY(Pr);
+	if (!Pr_ec)
+		goto fail;
+	Pr_point = EC_KEY_get0_public_key(Pr_ec);
+
+	group = EC_KEY_get0_group(Pr_ec);
+	if (!group)
+		goto fail;
+	group2 = EC_GROUP_dup(group);
+	if (!group2)
+		goto fail;
+	Qr = EC_POINT_new(group2);
+	if (!Qr) {
+		EC_GROUP_free(group2);
+		goto fail;
+	}
+	hash_bn = BN_bin2bn(hash, curve->hash_len, NULL);
+	if (!hash_bn ||
+	    EC_POINT_mul(group2, Qr, NULL, Pr_point, hash_bn, bnctx) != 1)
+		goto fail;
+	if (EC_POINT_is_at_infinity(group, Qr)) {
+		wpa_printf(MSG_INFO, "DPP: Qr is the point-at-infinity");
+		goto fail;
+	}
+	dpp_debug_print_point("DPP: Qr", group, Qr);
+out:
+	EC_KEY_free(Pr_ec);
+	EVP_PKEY_free(Pr);
+	BN_clear_free(hash_bn);
+	if (ret_group)
+		*ret_group = group2;
+	return Qr;
+fail:
+	EC_POINT_free(Qr);
+	Qr = NULL;
+	goto out;
+}
+
+
+#ifdef CONFIG_TESTING_OPTIONS
+static int dpp_test_gen_invalid_key(struct wpabuf *msg,
+				    const struct dpp_curve_params *curve)
+{
+	BN_CTX *ctx;
+	BIGNUM *x, *y;
+	int ret = -1;
+	EC_GROUP *group;
+	EC_POINT *point;
+
+	group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(curve->name));
+	if (!group)
+		return -1;
+
+	ctx = BN_CTX_new();
+	point = EC_POINT_new(group);
+	x = BN_new();
+	y = BN_new();
+	if (!ctx || !point || !x || !y)
+		goto fail;
+
+	if (BN_rand(x, curve->prime_len * 8, 0, 0) != 1)
+		goto fail;
+
+	/* Generate a random y coordinate that results in a point that is not
+	 * on the curve. */
+	for (;;) {
+		if (BN_rand(y, curve->prime_len * 8, 0, 0) != 1)
+			goto fail;
+
+		if (EC_POINT_set_affine_coordinates_GFp(group, point, x, y,
+							ctx) != 1) {
+#ifdef OPENSSL_IS_BORINGSSL
+		/* Unlike OpenSSL, BoringSSL returns an error from
+		 * EC_POINT_set_affine_coordinates_GFp() is not on the curve. */
+			break;
+#else /* OPENSSL_IS_BORINGSSL */
+			goto fail;
+#endif /* OPENSSL_IS_BORINGSSL */
+		}
+
+		if (!EC_POINT_is_on_curve(group, point, ctx))
+			break;
+	}
+
+	if (dpp_bn2bin_pad(x, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(y, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0)
+		goto fail;
+
+	ret = 0;
+fail:
+	if (ret < 0)
+		wpa_printf(MSG_INFO, "DPP: Failed to generate invalid key");
+	BN_free(x);
+	BN_free(y);
+	EC_POINT_free(point);
+	BN_CTX_free(ctx);
+
+	return ret;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+
+static struct wpabuf * dpp_pkex_build_exchange_req(struct dpp_pkex *pkex)
+{
+	EC_KEY *X_ec = NULL;
+	const EC_POINT *X_point;
+	BN_CTX *bnctx = NULL;
+	const EC_GROUP *group;
+	EC_POINT *Qi = NULL, *M = NULL;
+	struct wpabuf *M_buf = NULL;
+	BIGNUM *Mx = NULL, *My = NULL;
+	struct wpabuf *msg = NULL;
+	size_t attr_len;
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build PKEX Exchange Request");
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qi = dpp_pkex_derive_Qi(curve, pkex->own_mac, pkex->code,
+				pkex->identifier, bnctx, &group);
+	if (!Qi)
+		goto fail;
+
+	/* Generate a random ephemeral keypair x/X */
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_pkex_ephemeral_key_override_len) {
+		const struct dpp_curve_params *tmp_curve;
+
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - override ephemeral key x/X");
+		pkex->x = dpp_set_keypair(&tmp_curve,
+					  dpp_pkex_ephemeral_key_override,
+					  dpp_pkex_ephemeral_key_override_len);
+	} else {
+		pkex->x = dpp_gen_keypair(curve);
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	pkex->x = dpp_gen_keypair(curve);
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (!pkex->x)
+		goto fail;
+
+	/* M = X + Qi */
+	X_ec = EVP_PKEY_get1_EC_KEY(pkex->x);
+	if (!X_ec)
+		goto fail;
+	X_point = EC_KEY_get0_public_key(X_ec);
+	if (!X_point)
+		goto fail;
+	dpp_debug_print_point("DPP: X", group, X_point);
+	M = EC_POINT_new(group);
+	Mx = BN_new();
+	My = BN_new();
+	if (!M || !Mx || !My ||
+	    EC_POINT_add(group, M, X_point, Qi, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, M, Mx, My, bnctx) != 1)
+		goto fail;
+	dpp_debug_print_point("DPP: M", group, M);
+
+	/* Initiator -> Responder: group, [identifier,] M */
+	attr_len = 4 + 2;
+	if (pkex->identifier)
+		attr_len += 4 + os_strlen(pkex->identifier);
+	attr_len += 4 + 2 * curve->prime_len;
+	msg = dpp_alloc_msg(DPP_PA_PKEX_EXCHANGE_REQ, attr_len);
+	if (!msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_FINITE_CYCLIC_GROUP_PKEX_EXCHANGE_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Finite Cyclic Group");
+		goto skip_finite_cyclic_group;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Finite Cyclic Group attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_FINITE_CYCLIC_GROUP);
+	wpabuf_put_le16(msg, 2);
+	wpabuf_put_le16(msg, curve->ike_group);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_finite_cyclic_group:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Code Identifier attribute */
+	if (pkex->identifier) {
+		wpabuf_put_le16(msg, DPP_ATTR_CODE_IDENTIFIER);
+		wpabuf_put_le16(msg, os_strlen(pkex->identifier));
+		wpabuf_put_str(msg, pkex->identifier);
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_ENCRYPTED_KEY_PKEX_EXCHANGE_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Encrypted Key");
+		goto out;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* M in Encrypted Key attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_ENCRYPTED_KEY);
+	wpabuf_put_le16(msg, 2 * curve->prime_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_INVALID_ENCRYPTED_KEY_PKEX_EXCHANGE_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Encrypted Key");
+		if (dpp_test_gen_invalid_key(msg, curve) < 0)
+			goto fail;
+		goto out;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (dpp_bn2bin_pad(Mx, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(Mx, pkex->Mx, curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(My, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0)
+		goto fail;
+
+out:
+	wpabuf_free(M_buf);
+	EC_KEY_free(X_ec);
+	EC_POINT_free(M);
+	EC_POINT_free(Qi);
+	BN_clear_free(Mx);
+	BN_clear_free(My);
+	BN_CTX_free(bnctx);
+	return msg;
+fail:
+	wpa_printf(MSG_INFO, "DPP: Failed to build PKEX Exchange Request");
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+static void dpp_pkex_fail(struct dpp_pkex *pkex, const char *txt)
+{
+	wpa_msg(pkex->msg_ctx, MSG_INFO, DPP_EVENT_FAIL "%s", txt);
+}
+
+
+struct dpp_pkex * dpp_pkex_init(void *msg_ctx, struct dpp_bootstrap_info *bi,
+				const u8 *own_mac,
+				const char *identifier,
+				const char *code)
+{
+	struct dpp_pkex *pkex;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (!is_zero_ether_addr(dpp_pkex_own_mac_override)) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - own_mac override " MACSTR,
+			   MAC2STR(dpp_pkex_own_mac_override));
+		own_mac = dpp_pkex_own_mac_override;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	pkex = os_zalloc(sizeof(*pkex));
+	if (!pkex)
+		return NULL;
+	pkex->msg_ctx = msg_ctx;
+	pkex->initiator = 1;
+	pkex->own_bi = bi;
+	os_memcpy(pkex->own_mac, own_mac, ETH_ALEN);
+	if (identifier) {
+		pkex->identifier = os_strdup(identifier);
+		if (!pkex->identifier)
+			goto fail;
+	}
+	pkex->code = os_strdup(code);
+	if (!pkex->code)
+		goto fail;
+	pkex->exchange_req = dpp_pkex_build_exchange_req(pkex);
+	if (!pkex->exchange_req)
+		goto fail;
+	return pkex;
+fail:
+	dpp_pkex_free(pkex);
+	return NULL;
+}
+
+
+static struct wpabuf *
+dpp_pkex_build_exchange_resp(struct dpp_pkex *pkex,
+			     enum dpp_status_error status,
+			     const BIGNUM *Nx, const BIGNUM *Ny)
+{
+	struct wpabuf *msg = NULL;
+	size_t attr_len;
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+
+	/* Initiator -> Responder: DPP Status, [identifier,] N */
+	attr_len = 4 + 1;
+	if (pkex->identifier)
+		attr_len += 4 + os_strlen(pkex->identifier);
+	attr_len += 4 + 2 * curve->prime_len;
+	msg = dpp_alloc_msg(DPP_PA_PKEX_EXCHANGE_RESP, attr_len);
+	if (!msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_STATUS_PKEX_EXCHANGE_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Status");
+		goto skip_status;
+	}
+
+	if (dpp_test == DPP_TEST_INVALID_STATUS_PKEX_EXCHANGE_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status");
+		status = 255;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* DPP Status */
+	dpp_build_attr_status(msg, status);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_status:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Code Identifier attribute */
+	if (pkex->identifier) {
+		wpabuf_put_le16(msg, DPP_ATTR_CODE_IDENTIFIER);
+		wpabuf_put_le16(msg, os_strlen(pkex->identifier));
+		wpabuf_put_str(msg, pkex->identifier);
+	}
+
+	if (status != DPP_STATUS_OK)
+		goto skip_encrypted_key;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_ENCRYPTED_KEY_PKEX_EXCHANGE_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Encrypted Key");
+		goto skip_encrypted_key;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* N in Encrypted Key attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_ENCRYPTED_KEY);
+	wpabuf_put_le16(msg, 2 * curve->prime_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_INVALID_ENCRYPTED_KEY_PKEX_EXCHANGE_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Encrypted Key");
+		if (dpp_test_gen_invalid_key(msg, curve) < 0)
+			goto fail;
+		goto skip_encrypted_key;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (dpp_bn2bin_pad(Nx, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(Nx, pkex->Nx, curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(Ny, wpabuf_put(msg, curve->prime_len),
+			   curve->prime_len) < 0)
+		goto fail;
+
+skip_encrypted_key:
+	if (status == DPP_STATUS_BAD_GROUP) {
+		/* Finite Cyclic Group attribute */
+		wpabuf_put_le16(msg, DPP_ATTR_FINITE_CYCLIC_GROUP);
+		wpabuf_put_le16(msg, 2);
+		wpabuf_put_le16(msg, curve->ike_group);
+	}
+
+	return msg;
+fail:
+	wpabuf_free(msg);
+	return NULL;
+}
+
+
+static int dpp_pkex_derive_z(const u8 *mac_init, const u8 *mac_resp,
+			     const u8 *Mx, size_t Mx_len,
+			     const u8 *Nx, size_t Nx_len,
+			     const char *code,
+			     const u8 *Kx, size_t Kx_len,
+			     u8 *z, unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	int res;
+	u8 *info, *pos;
+	size_t info_len;
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+
+	/* HKDF-Extract(<>, IKM=K.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Kx, Kx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM)",
+			prk, hash_len);
+	info_len = 2 * ETH_ALEN + Mx_len + Nx_len + os_strlen(code);
+	info = os_malloc(info_len);
+	if (!info)
+		return -1;
+	pos = info;
+	os_memcpy(pos, mac_init, ETH_ALEN);
+	pos += ETH_ALEN;
+	os_memcpy(pos, mac_resp, ETH_ALEN);
+	pos += ETH_ALEN;
+	os_memcpy(pos, Mx, Mx_len);
+	pos += Mx_len;
+	os_memcpy(pos, Nx, Nx_len);
+	pos += Nx_len;
+	os_memcpy(pos, code, os_strlen(code));
+
+	/* HKDF-Expand(PRK, info, L) */
+	if (hash_len == 32)
+		res = hmac_sha256_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else if (hash_len == 48)
+		res = hmac_sha384_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else if (hash_len == 64)
+		res = hmac_sha512_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else
+		res = -1;
+	os_free(info);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: z = HKDF-Expand(PRK, info, L)",
+			z, hash_len);
+	return 0;
+}
+
+
+struct dpp_pkex * dpp_pkex_rx_exchange_req(void *msg_ctx,
+					   struct dpp_bootstrap_info *bi,
+					   const u8 *own_mac,
+					   const u8 *peer_mac,
+					   const char *identifier,
+					   const char *code,
+					   const u8 *buf, size_t len)
+{
+	const u8 *attr_group, *attr_id, *attr_key;
+	u16 attr_group_len, attr_id_len, attr_key_len;
+	const struct dpp_curve_params *curve = bi->curve;
+	u16 ike_group;
+	struct dpp_pkex *pkex = NULL;
+	EC_POINT *Qi = NULL, *Qr = NULL, *M = NULL, *X = NULL, *N = NULL;
+	BN_CTX *bnctx = NULL;
+	const EC_GROUP *group;
+	BIGNUM *Mx = NULL, *My = NULL;
+	EC_KEY *Y_ec = NULL, *X_ec = NULL;;
+	const EC_POINT *Y_point;
+	BIGNUM *Nx = NULL, *Ny = NULL;
+	u8 Kx[DPP_MAX_SHARED_SECRET_LEN];
+	size_t Kx_len;
+	int res;
+	EVP_PKEY_CTX *ctx = NULL;
+
+	if (bi->pkex_t >= PKEX_COUNTER_T_LIMIT) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"PKEX counter t limit reached - ignore message");
+		return NULL;
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (!is_zero_ether_addr(dpp_pkex_peer_mac_override)) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - peer_mac override " MACSTR,
+			   MAC2STR(dpp_pkex_peer_mac_override));
+		peer_mac = dpp_pkex_peer_mac_override;
+	}
+	if (!is_zero_ether_addr(dpp_pkex_own_mac_override)) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - own_mac override " MACSTR,
+			   MAC2STR(dpp_pkex_own_mac_override));
+		own_mac = dpp_pkex_own_mac_override;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	attr_id = dpp_get_attr(buf, len, DPP_ATTR_CODE_IDENTIFIER,
+			       &attr_id_len);
+	if (!attr_id && identifier) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code identifier received, but expected one");
+		return NULL;
+	}
+	if (attr_id && identifier &&
+	    (os_strlen(identifier) != attr_id_len ||
+	     os_memcmp(identifier, attr_id, attr_id_len) != 0)) {
+		wpa_printf(MSG_DEBUG, "DPP: PKEX code identifier mismatch");
+		return NULL;
+	}
+
+	attr_group = dpp_get_attr(buf, len, DPP_ATTR_FINITE_CYCLIC_GROUP,
+				  &attr_group_len);
+	if (!attr_group || attr_group_len != 2) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Missing or invalid Finite Cyclic Group attribute");
+		return NULL;
+	}
+	ike_group = WPA_GET_LE16(attr_group);
+	if (ike_group != curve->ike_group) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Mismatching PKEX curve: peer=%u own=%u",
+			ike_group, curve->ike_group);
+		pkex = os_zalloc(sizeof(*pkex));
+		if (!pkex)
+			goto fail;
+		pkex->own_bi = bi;
+		pkex->failed = 1;
+		pkex->exchange_resp = dpp_pkex_build_exchange_resp(
+			pkex, DPP_STATUS_BAD_GROUP, NULL, NULL);
+		if (!pkex->exchange_resp)
+			goto fail;
+		return pkex;
+	}
+
+	/* M in Encrypted Key attribute */
+	attr_key = dpp_get_attr(buf, len, DPP_ATTR_ENCRYPTED_KEY,
+				&attr_key_len);
+	if (!attr_key || attr_key_len & 0x01 || attr_key_len < 2 ||
+	    attr_key_len / 2 > DPP_MAX_SHARED_SECRET_LEN) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Missing Encrypted Key attribute");
+		return NULL;
+	}
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qi = dpp_pkex_derive_Qi(curve, peer_mac, code, identifier, bnctx,
+				&group);
+	if (!Qi)
+		goto fail;
+
+	/* X' = M - Qi */
+	X = EC_POINT_new(group);
+	M = EC_POINT_new(group);
+	Mx = BN_bin2bn(attr_key, attr_key_len / 2, NULL);
+	My = BN_bin2bn(attr_key + attr_key_len / 2, attr_key_len / 2, NULL);
+	if (!X || !M || !Mx || !My ||
+	    EC_POINT_set_affine_coordinates_GFp(group, M, Mx, My, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, M) ||
+	    !EC_POINT_is_on_curve(group, M, bnctx) ||
+	    EC_POINT_invert(group, Qi, bnctx) != 1 ||
+	    EC_POINT_add(group, X, M, Qi, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, X) ||
+	    !EC_POINT_is_on_curve(group, X, bnctx)) {
+		wpa_msg(msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+			"Invalid Encrypted Key value");
+		bi->pkex_t++;
+		goto fail;
+	}
+	dpp_debug_print_point("DPP: M", group, M);
+	dpp_debug_print_point("DPP: X'", group, X);
+
+	pkex = os_zalloc(sizeof(*pkex));
+	if (!pkex)
+		goto fail;
+	pkex->t = bi->pkex_t;
+	pkex->msg_ctx = msg_ctx;
+	pkex->own_bi = bi;
+	os_memcpy(pkex->own_mac, own_mac, ETH_ALEN);
+	os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
+	if (identifier) {
+		pkex->identifier = os_strdup(identifier);
+		if (!pkex->identifier)
+			goto fail;
+	}
+	pkex->code = os_strdup(code);
+	if (!pkex->code)
+		goto fail;
+
+	os_memcpy(pkex->Mx, attr_key, attr_key_len / 2);
+
+	X_ec = EC_KEY_new();
+	if (!X_ec ||
+	    EC_KEY_set_group(X_ec, group) != 1 ||
+	    EC_KEY_set_public_key(X_ec, X) != 1)
+		goto fail;
+	pkex->x = EVP_PKEY_new();
+	if (!pkex->x ||
+	    EVP_PKEY_set1_EC_KEY(pkex->x, X_ec) != 1)
+		goto fail;
+
+	/* Qr = H(MAC-Responder | | [identifier | ] code) * Pr */
+	Qr = dpp_pkex_derive_Qr(curve, own_mac, code, identifier, bnctx, NULL);
+	if (!Qr)
+		goto fail;
+
+	/* Generate a random ephemeral keypair y/Y */
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_pkex_ephemeral_key_override_len) {
+		const struct dpp_curve_params *tmp_curve;
+
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - override ephemeral key y/Y");
+		pkex->y = dpp_set_keypair(&tmp_curve,
+					  dpp_pkex_ephemeral_key_override,
+					  dpp_pkex_ephemeral_key_override_len);
+	} else {
+		pkex->y = dpp_gen_keypair(curve);
+	}
+#else /* CONFIG_TESTING_OPTIONS */
+	pkex->y = dpp_gen_keypair(curve);
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (!pkex->y)
+		goto fail;
+
+	/* N = Y + Qr */
+	Y_ec = EVP_PKEY_get1_EC_KEY(pkex->y);
+	if (!Y_ec)
+		goto fail;
+	Y_point = EC_KEY_get0_public_key(Y_ec);
+	if (!Y_point)
+		goto fail;
+	dpp_debug_print_point("DPP: Y", group, Y_point);
+	N = EC_POINT_new(group);
+	Nx = BN_new();
+	Ny = BN_new();
+	if (!N || !Nx || !Ny ||
+	    EC_POINT_add(group, N, Y_point, Qr, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, N, Nx, Ny, bnctx) != 1)
+		goto fail;
+	dpp_debug_print_point("DPP: N", group, N);
+
+	pkex->exchange_resp = dpp_pkex_build_exchange_resp(pkex, DPP_STATUS_OK,
+							   Nx, Ny);
+	if (!pkex->exchange_resp)
+		goto fail;
+
+	/* K = y * X' */
+	ctx = EVP_PKEY_CTX_new(pkex->y, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->x) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Kx_len) != 1 ||
+	    Kx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Kx, &Kx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (K.x)",
+			Kx, Kx_len);
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+	res = dpp_pkex_derive_z(pkex->peer_mac, pkex->own_mac,
+				pkex->Mx, curve->prime_len,
+				pkex->Nx, curve->prime_len, pkex->code,
+				Kx, Kx_len, pkex->z, curve->hash_len);
+	os_memset(Kx, 0, Kx_len);
+	if (res < 0)
+		goto fail;
+
+	pkex->exchange_done = 1;
+
+out:
+	EVP_PKEY_CTX_free(ctx);
+	BN_CTX_free(bnctx);
+	EC_POINT_free(Qi);
+	EC_POINT_free(Qr);
+	BN_free(Mx);
+	BN_free(My);
+	BN_free(Nx);
+	BN_free(Ny);
+	EC_POINT_free(M);
+	EC_POINT_free(N);
+	EC_POINT_free(X);
+	EC_KEY_free(X_ec);
+	EC_KEY_free(Y_ec);
+	return pkex;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Request processing failed");
+	dpp_pkex_free(pkex);
+	pkex = NULL;
+	goto out;
+}
+
+
+static struct wpabuf *
+dpp_pkex_build_commit_reveal_req(struct dpp_pkex *pkex,
+				 const struct wpabuf *A_pub, const u8 *u)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	struct wpabuf *msg = NULL;
+	size_t clear_len, attr_len;
+	struct wpabuf *clear = NULL;
+	u8 *wrapped;
+	u8 octet;
+	const u8 *addr[2];
+	size_t len[2];
+
+	/* {A, u, [bootstrapping info]}z */
+	clear_len = 4 + 2 * curve->prime_len + 4 + curve->hash_len;
+	clear = wpabuf_alloc(clear_len);
+	attr_len = 4 + clear_len + AES_BLOCK_SIZE;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_REQ)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = dpp_alloc_msg(DPP_PA_PKEX_COMMIT_REVEAL_REQ, attr_len);
+	if (!clear || !msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_BOOTSTRAP_KEY_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Bootstrap Key");
+		goto skip_bootstrap_key;
+	}
+	if (dpp_test == DPP_TEST_INVALID_BOOTSTRAP_KEY_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Bootstrap Key");
+		wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+		wpabuf_put_le16(clear, 2 * curve->prime_len);
+		if (dpp_test_gen_invalid_key(clear, curve) < 0)
+			goto fail;
+		goto skip_bootstrap_key;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* A in Bootstrap Key attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+	wpabuf_put_le16(clear, wpabuf_len(A_pub));
+	wpabuf_put_buf(clear, A_pub);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_bootstrap_key:
+	if (dpp_test == DPP_TEST_NO_I_AUTH_TAG_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no I-Auth tag");
+		goto skip_i_auth_tag;
+	}
+	if (dpp_test == DPP_TEST_I_AUTH_TAG_MISMATCH_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - I-Auth tag mismatch");
+		wpabuf_put_le16(clear, DPP_ATTR_I_AUTH_TAG);
+		wpabuf_put_le16(clear, curve->hash_len);
+		wpabuf_put_data(clear, u, curve->hash_len - 1);
+		wpabuf_put_u8(clear, u[curve->hash_len - 1] ^ 0x01);
+		goto skip_i_auth_tag;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* u in I-Auth tag attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_I_AUTH_TAG);
+	wpabuf_put_le16(clear, curve->hash_len);
+	wpabuf_put_data(clear, u, curve->hash_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_i_auth_tag:
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = DPP_HDR_LEN;
+	octet = 0;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(pkex->z, curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    2, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+out:
+	wpabuf_free(clear);
+	return msg;
+
+fail:
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+struct wpabuf * dpp_pkex_rx_exchange_resp(struct dpp_pkex *pkex,
+					  const u8 *peer_mac,
+					  const u8 *buf, size_t buflen)
+{
+	const u8 *attr_status, *attr_id, *attr_key, *attr_group;
+	u16 attr_status_len, attr_id_len, attr_key_len, attr_group_len;
+	const EC_GROUP *group;
+	BN_CTX *bnctx = NULL;
+	struct wpabuf *msg = NULL, *A_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	EC_POINT *Qr = NULL, *Y = NULL, *N = NULL;
+	BIGNUM *Nx = NULL, *Ny = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	EC_KEY *Y_ec = NULL;
+	size_t Jx_len, Kx_len;
+	u8 Jx[DPP_MAX_SHARED_SECRET_LEN], Kx[DPP_MAX_SHARED_SECRET_LEN];
+	const u8 *addr[4];
+	size_t len[4];
+	u8 u[DPP_MAX_HASH_LEN];
+	int res;
+
+	if (pkex->failed || pkex->t >= PKEX_COUNTER_T_LIMIT || !pkex->initiator)
+		return NULL;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_PKEX_EXCHANGE_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at PKEX Exchange Response");
+		pkex->failed = 1;
+		return NULL;
+	}
+
+	if (!is_zero_ether_addr(dpp_pkex_peer_mac_override)) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - peer_mac override " MACSTR,
+			   MAC2STR(dpp_pkex_peer_mac_override));
+		peer_mac = dpp_pkex_peer_mac_override;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
+
+	attr_status = dpp_get_attr(buf, buflen, DPP_ATTR_STATUS,
+				   &attr_status_len);
+	if (!attr_status || attr_status_len != 1) {
+		dpp_pkex_fail(pkex, "No DPP Status attribute");
+		return NULL;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", attr_status[0]);
+
+	if (attr_status[0] == DPP_STATUS_BAD_GROUP) {
+		attr_group = dpp_get_attr(buf, buflen,
+					  DPP_ATTR_FINITE_CYCLIC_GROUP,
+					  &attr_group_len);
+		if (attr_group && attr_group_len == 2) {
+			wpa_msg(pkex->msg_ctx, MSG_INFO, DPP_EVENT_FAIL
+				"Peer indicated mismatching PKEX group - proposed %u",
+				WPA_GET_LE16(attr_group));
+			return NULL;
+		}
+	}
+
+	if (attr_status[0] != DPP_STATUS_OK) {
+		dpp_pkex_fail(pkex, "PKEX failed (peer indicated failure)");
+		return NULL;
+	}
+
+	attr_id = dpp_get_attr(buf, buflen, DPP_ATTR_CODE_IDENTIFIER,
+			       &attr_id_len);
+	if (!attr_id && pkex->identifier) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code identifier received, but expected one");
+		return NULL;
+	}
+	if (attr_id && pkex->identifier &&
+	    (os_strlen(pkex->identifier) != attr_id_len ||
+	     os_memcmp(pkex->identifier, attr_id, attr_id_len) != 0)) {
+		dpp_pkex_fail(pkex, "PKEX code identifier mismatch");
+		return NULL;
+	}
+
+	/* N in Encrypted Key attribute */
+	attr_key = dpp_get_attr(buf, buflen, DPP_ATTR_ENCRYPTED_KEY,
+				&attr_key_len);
+	if (!attr_key || attr_key_len & 0x01 || attr_key_len < 2) {
+		dpp_pkex_fail(pkex, "Missing Encrypted Key attribute");
+		return NULL;
+	}
+
+	/* Qr = H(MAC-Responder | [identifier |] code) * Pr */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qr = dpp_pkex_derive_Qr(curve, pkex->peer_mac, pkex->code,
+				pkex->identifier, bnctx, &group);
+	if (!Qr)
+		goto fail;
+
+	/* Y' = N - Qr */
+	Y = EC_POINT_new(group);
+	N = EC_POINT_new(group);
+	Nx = BN_bin2bn(attr_key, attr_key_len / 2, NULL);
+	Ny = BN_bin2bn(attr_key + attr_key_len / 2, attr_key_len / 2, NULL);
+	if (!Y || !N || !Nx || !Ny ||
+	    EC_POINT_set_affine_coordinates_GFp(group, N, Nx, Ny, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, N) ||
+	    !EC_POINT_is_on_curve(group, N, bnctx) ||
+	    EC_POINT_invert(group, Qr, bnctx) != 1 ||
+	    EC_POINT_add(group, Y, N, Qr, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, Y) ||
+	    !EC_POINT_is_on_curve(group, Y, bnctx)) {
+		dpp_pkex_fail(pkex, "Invalid Encrypted Key value");
+		pkex->t++;
+		goto fail;
+	}
+	dpp_debug_print_point("DPP: N", group, N);
+	dpp_debug_print_point("DPP: Y'", group, Y);
+
+	pkex->exchange_done = 1;
+
+	/* ECDH: J = a * Y’ */
+	Y_ec = EC_KEY_new();
+	if (!Y_ec ||
+	    EC_KEY_set_group(Y_ec, group) != 1 ||
+	    EC_KEY_set_public_key(Y_ec, Y) != 1)
+		goto fail;
+	pkex->y = EVP_PKEY_new();
+	if (!pkex->y ||
+	    EVP_PKEY_set1_EC_KEY(pkex->y, Y_ec) != 1)
+		goto fail;
+	ctx = EVP_PKEY_CTX_new(pkex->own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->y) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Jx_len) != 1 ||
+	    Jx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Jx, &Jx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (J.x)",
+			Jx, Jx_len);
+
+	/* u = HMAC(J.x,  MAC-Initiator | A.x | Y’.x | X.x ) */
+	A_pub = dpp_get_pubkey_point(pkex->own_bi->pubkey, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	if (!A_pub || !Y_pub || !X_pub)
+		goto fail;
+	addr[0] = pkex->own_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(A_pub);
+	len[1] = wpabuf_len(A_pub) / 2;
+	addr[2] = wpabuf_head(Y_pub);
+	len[2] = wpabuf_len(Y_pub) / 2;
+	addr[3] = wpabuf_head(X_pub);
+	len[3] = wpabuf_len(X_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Jx, Jx_len, 4, addr, len, u) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: u", u, curve->hash_len);
+
+	/* K = x * Y’ */
+	EVP_PKEY_CTX_free(ctx);
+	ctx = EVP_PKEY_CTX_new(pkex->x, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->y) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Kx_len) != 1 ||
+	    Kx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Kx, &Kx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (K.x)",
+			Kx, Kx_len);
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+	res = dpp_pkex_derive_z(pkex->own_mac, pkex->peer_mac,
+				pkex->Mx, curve->prime_len,
+				attr_key /* N.x */, attr_key_len / 2,
+				pkex->code, Kx, Kx_len,
+				pkex->z, curve->hash_len);
+	os_memset(Kx, 0, Kx_len);
+	if (res < 0)
+		goto fail;
+
+	msg = dpp_pkex_build_commit_reveal_req(pkex, A_pub, u);
+	if (!msg)
+		goto fail;
+
+out:
+	wpabuf_free(A_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	EC_POINT_free(Qr);
+	EC_POINT_free(Y);
+	EC_POINT_free(N);
+	BN_free(Nx);
+	BN_free(Ny);
+	EC_KEY_free(Y_ec);
+	EVP_PKEY_CTX_free(ctx);
+	BN_CTX_free(bnctx);
+	return msg;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Response processing failed");
+	goto out;
+}
+
+
+static struct wpabuf *
+dpp_pkex_build_commit_reveal_resp(struct dpp_pkex *pkex,
+				  const struct wpabuf *B_pub, const u8 *v)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	struct wpabuf *msg = NULL;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 octet;
+	u8 *wrapped;
+	struct wpabuf *clear = NULL;
+	size_t clear_len, attr_len;
+
+	/* {B, v [bootstrapping info]}z */
+	clear_len = 4 + 2 * curve->prime_len + 4 + curve->hash_len;
+	clear = wpabuf_alloc(clear_len);
+	attr_len = 4 + clear_len + AES_BLOCK_SIZE;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_RESP)
+		attr_len += 5;
+#endif /* CONFIG_TESTING_OPTIONS */
+	msg = dpp_alloc_msg(DPP_PA_PKEX_COMMIT_REVEAL_RESP, attr_len);
+	if (!clear || !msg)
+		goto fail;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_NO_BOOTSTRAP_KEY_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Bootstrap Key");
+		goto skip_bootstrap_key;
+	}
+	if (dpp_test == DPP_TEST_INVALID_BOOTSTRAP_KEY_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - invalid Bootstrap Key");
+		wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+		wpabuf_put_le16(clear, 2 * curve->prime_len);
+		if (dpp_test_gen_invalid_key(clear, curve) < 0)
+			goto fail;
+		goto skip_bootstrap_key;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* B in Bootstrap Key attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+	wpabuf_put_le16(clear, wpabuf_len(B_pub));
+	wpabuf_put_buf(clear, B_pub);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_bootstrap_key:
+	if (dpp_test == DPP_TEST_NO_R_AUTH_TAG_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no R-Auth tag");
+		goto skip_r_auth_tag;
+	}
+	if (dpp_test == DPP_TEST_R_AUTH_TAG_MISMATCH_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - R-Auth tag mismatch");
+		wpabuf_put_le16(clear, DPP_ATTR_R_AUTH_TAG);
+		wpabuf_put_le16(clear, curve->hash_len);
+		wpabuf_put_data(clear, v, curve->hash_len - 1);
+		wpabuf_put_u8(clear, v[curve->hash_len - 1] ^ 0x01);
+		goto skip_r_auth_tag;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* v in R-Auth tag attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_R_AUTH_TAG);
+	wpabuf_put_le16(clear, curve->hash_len);
+	wpabuf_put_data(clear, v, curve->hash_len);
+
+#ifdef CONFIG_TESTING_OPTIONS
+skip_r_auth_tag:
+	if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data");
+		goto skip_wrapped_data;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = DPP_HDR_LEN;
+	octet = 1;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(pkex->z, curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    2, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data");
+		dpp_build_attr_status(msg, DPP_STATUS_OK);
+	}
+skip_wrapped_data:
+#endif /* CONFIG_TESTING_OPTIONS */
+
+out:
+	wpabuf_free(clear);
+	return msg;
+
+fail:
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+struct wpabuf * dpp_pkex_rx_commit_reveal_req(struct dpp_pkex *pkex,
+					      const u8 *hdr,
+					      const u8 *buf, size_t buflen)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t Jx_len, Lx_len;
+	u8 Jx[DPP_MAX_SHARED_SECRET_LEN];
+	u8 Lx[DPP_MAX_SHARED_SECRET_LEN];
+	const u8 *wrapped_data, *b_key, *peer_u;
+	u16 wrapped_data_len, b_key_len, peer_u_len = 0;
+	const u8 *addr[4];
+	size_t len[4];
+	u8 octet;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	struct wpabuf *msg = NULL, *A_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+	struct wpabuf *B_pub = NULL;
+	u8 u[DPP_MAX_HASH_LEN], v[DPP_MAX_HASH_LEN];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_PKEX_CR_REQ) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at PKEX CR Request");
+		pkex->failed = 1;
+		return NULL;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (!pkex->exchange_done || pkex->failed ||
+	    pkex->t >= PKEX_COUNTER_T_LIMIT || pkex->initiator)
+		goto fail;
+
+	wrapped_data = dpp_get_attr(buf, buflen, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_pkex_fail(pkex,
+			      "Missing or invalid required Wrapped Data attribute");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	octet = 0;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	if (aes_siv_decrypt(pkex->z, curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_pkex_fail(pkex,
+			      "AES-SIV decryption failed - possible PKEX code mismatch");
+		pkex->failed = 1;
+		pkex->t++;
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_pkex_fail(pkex, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	b_key = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_BOOTSTRAP_KEY,
+			     &b_key_len);
+	if (!b_key || b_key_len != 2 * curve->prime_len) {
+		dpp_pkex_fail(pkex, "No valid peer bootstrapping key found");
+		goto fail;
+	}
+	pkex->peer_bootstrap_key = dpp_set_pubkey_point(pkex->x, b_key,
+							b_key_len);
+	if (!pkex->peer_bootstrap_key) {
+		dpp_pkex_fail(pkex, "Peer bootstrapping key is invalid");
+		goto fail;
+	}
+	dpp_debug_print_key("DPP: Peer bootstrap public key",
+			    pkex->peer_bootstrap_key);
+
+	/* ECDH: J' = y * A' */
+	ctx = EVP_PKEY_CTX_new(pkex->y, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->peer_bootstrap_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Jx_len) != 1 ||
+	    Jx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Jx, &Jx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (J.x)",
+			Jx, Jx_len);
+
+	/* u' = HMAC(J'.x, MAC-Initiator | A'.x | Y.x | X'.x) */
+	A_pub = dpp_get_pubkey_point(pkex->peer_bootstrap_key, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	if (!A_pub || !Y_pub || !X_pub)
+		goto fail;
+	addr[0] = pkex->peer_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(A_pub);
+	len[1] = wpabuf_len(A_pub) / 2;
+	addr[2] = wpabuf_head(Y_pub);
+	len[2] = wpabuf_len(Y_pub) / 2;
+	addr[3] = wpabuf_head(X_pub);
+	len[3] = wpabuf_len(X_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Jx, Jx_len, 4, addr, len, u) < 0)
+		goto fail;
+
+	peer_u = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_AUTH_TAG,
+			      &peer_u_len);
+	if (!peer_u || peer_u_len != curve->hash_len ||
+	    os_memcmp(peer_u, u, curve->hash_len) != 0) {
+		dpp_pkex_fail(pkex, "No valid u (I-Auth tag) found");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated u'",
+			    u, curve->hash_len);
+		wpa_hexdump(MSG_DEBUG, "DPP: Received u", peer_u, peer_u_len);
+		pkex->t++;
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Valid u (I-Auth tag) received");
+
+	/* ECDH: L = b * X' */
+	EVP_PKEY_CTX_free(ctx);
+	ctx = EVP_PKEY_CTX_new(pkex->own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->x) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Lx_len) != 1 ||
+	    Lx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Lx, &Lx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (L.x)",
+			Lx, Lx_len);
+
+	/* v = HMAC(L.x, MAC-Responder | B.x | X'.x | Y.x) */
+	B_pub = dpp_get_pubkey_point(pkex->own_bi->pubkey, 0);
+	if (!B_pub)
+		goto fail;
+	addr[0] = pkex->own_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(B_pub);
+	len[1] = wpabuf_len(B_pub) / 2;
+	addr[2] = wpabuf_head(X_pub);
+	len[2] = wpabuf_len(X_pub) / 2;
+	addr[3] = wpabuf_head(Y_pub);
+	len[3] = wpabuf_len(Y_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Lx, Lx_len, 4, addr, len, v) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: v", v, curve->hash_len);
+
+	msg = dpp_pkex_build_commit_reveal_resp(pkex, B_pub, v);
+	if (!msg)
+		goto fail;
+
+out:
+	EVP_PKEY_CTX_free(ctx);
+	os_free(unwrapped);
+	wpabuf_free(A_pub);
+	wpabuf_free(B_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	return msg;
+fail:
+	wpa_printf(MSG_DEBUG,
+		   "DPP: PKEX Commit-Reveal Request processing failed");
+	goto out;
+}
+
+
+int dpp_pkex_rx_commit_reveal_resp(struct dpp_pkex *pkex, const u8 *hdr,
+				   const u8 *buf, size_t buflen)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	const u8 *wrapped_data, *b_key, *peer_v;
+	u16 wrapped_data_len, b_key_len, peer_v_len = 0;
+	const u8 *addr[4];
+	size_t len[4];
+	u8 octet;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	int ret = -1;
+	u8 v[DPP_MAX_HASH_LEN];
+	size_t Lx_len;
+	u8 Lx[DPP_MAX_SHARED_SECRET_LEN];
+	EVP_PKEY_CTX *ctx = NULL;
+	struct wpabuf *B_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (dpp_test == DPP_TEST_STOP_AT_PKEX_CR_RESP) {
+		wpa_printf(MSG_INFO,
+			   "DPP: TESTING - stop at PKEX CR Response");
+		pkex->failed = 1;
+		goto fail;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (!pkex->exchange_done || pkex->failed ||
+	    pkex->t >= PKEX_COUNTER_T_LIMIT || !pkex->initiator)
+		goto fail;
+
+	wrapped_data = dpp_get_attr(buf, buflen, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		dpp_pkex_fail(pkex,
+			      "Missing or invalid required Wrapped Data attribute");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	octet = 1;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	if (aes_siv_decrypt(pkex->z, curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		dpp_pkex_fail(pkex,
+			      "AES-SIV decryption failed - possible PKEX code mismatch");
+		pkex->t++;
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		dpp_pkex_fail(pkex, "Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	b_key = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_BOOTSTRAP_KEY,
+			     &b_key_len);
+	if (!b_key || b_key_len != 2 * curve->prime_len) {
+		dpp_pkex_fail(pkex, "No valid peer bootstrapping key found");
+		goto fail;
+	}
+	pkex->peer_bootstrap_key = dpp_set_pubkey_point(pkex->x, b_key,
+							b_key_len);
+	if (!pkex->peer_bootstrap_key) {
+		dpp_pkex_fail(pkex, "Peer bootstrapping key is invalid");
+		goto fail;
+	}
+	dpp_debug_print_key("DPP: Peer bootstrap public key",
+			    pkex->peer_bootstrap_key);
+
+	/* ECDH: L' = x * B' */
+	ctx = EVP_PKEY_CTX_new(pkex->x, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->peer_bootstrap_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Lx_len) != 1 ||
+	    Lx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Lx, &Lx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (L.x)",
+			Lx, Lx_len);
+
+	/* v' = HMAC(L.x, MAC-Responder | B'.x | X.x | Y'.x) */
+	B_pub = dpp_get_pubkey_point(pkex->peer_bootstrap_key, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	if (!B_pub || !X_pub || !Y_pub)
+		goto fail;
+	addr[0] = pkex->peer_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(B_pub);
+	len[1] = wpabuf_len(B_pub) / 2;
+	addr[2] = wpabuf_head(X_pub);
+	len[2] = wpabuf_len(X_pub) / 2;
+	addr[3] = wpabuf_head(Y_pub);
+	len[3] = wpabuf_len(Y_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Lx, Lx_len, 4, addr, len, v) < 0)
+		goto fail;
+
+	peer_v = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_R_AUTH_TAG,
+			      &peer_v_len);
+	if (!peer_v || peer_v_len != curve->hash_len ||
+	    os_memcmp(peer_v, v, curve->hash_len) != 0) {
+		dpp_pkex_fail(pkex, "No valid v (R-Auth tag) found");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated v'",
+			    v, curve->hash_len);
+		wpa_hexdump(MSG_DEBUG, "DPP: Received v", peer_v, peer_v_len);
+		pkex->t++;
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Valid v (R-Auth tag) received");
+
+	ret = 0;
+out:
+	wpabuf_free(B_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	EVP_PKEY_CTX_free(ctx);
+	os_free(unwrapped);
+	return ret;
+fail:
+	goto out;
+}
+
+
+void dpp_pkex_free(struct dpp_pkex *pkex)
+{
+	if (!pkex)
+		return;
+
+	os_free(pkex->identifier);
+	os_free(pkex->code);
+	EVP_PKEY_free(pkex->x);
+	EVP_PKEY_free(pkex->y);
+	EVP_PKEY_free(pkex->peer_bootstrap_key);
+	wpabuf_free(pkex->exchange_req);
+	wpabuf_free(pkex->exchange_resp);
+	os_free(pkex);
+}
+
+
+#ifdef CONFIG_TESTING_OPTIONS
+char * dpp_corrupt_connector_signature(const char *connector)
+{
+	char *tmp, *pos, *signed3 = NULL;
+	unsigned char *signature = NULL;
+	size_t signature_len = 0, signed3_len;
+
+	tmp = os_zalloc(os_strlen(connector) + 5);
+	if (!tmp)
+		goto fail;
+	os_memcpy(tmp, connector, os_strlen(connector));
+
+	pos = os_strchr(tmp, '.');
+	if (!pos)
+		goto fail;
+
+	pos = os_strchr(pos + 1, '.');
+	if (!pos)
+		goto fail;
+	pos++;
+
+	wpa_printf(MSG_DEBUG, "DPP: Original base64url encoded signature: %s",
+		   pos);
+	signature = base64_url_decode((const unsigned char *) pos,
+				      os_strlen(pos), &signature_len);
+	if (!signature || signature_len == 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: Original Connector signature",
+		    signature, signature_len);
+	signature[signature_len - 1] ^= 0x01;
+	wpa_hexdump(MSG_DEBUG, "DPP: Corrupted Connector signature",
+		    signature, signature_len);
+	signed3 = (char *) base64_url_encode(signature, signature_len,
+					     &signed3_len, 0);
+	if (!signed3)
+		goto fail;
+	os_memcpy(pos, signed3, signed3_len);
+	pos[signed3_len] = '\0';
+	wpa_printf(MSG_DEBUG, "DPP: Corrupted base64url encoded signature: %s",
+		   pos);
+
+out:
+	os_free(signature);
+	os_free(signed3);
+	return tmp;
+fail:
+	os_free(tmp);
+	tmp = NULL;
+	goto out;
+}
+#endif /* CONFIG_TESTING_OPTIONS */

+ 434 - 0
src/common/dpp.h

@@ -0,0 +1,434 @@
+/*
+ * DPP functionality shared between hostapd and wpa_supplicant
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef DPP_H
+#define DPP_H
+
+#include <openssl/x509.h>
+
+#include "utils/list.h"
+#include "common/wpa_common.h"
+#include "crypto/sha256.h"
+
+#define DPP_HDR_LEN (4 + 2) /* OUI, OUI Type, Crypto Suite, DPP frame type */
+
+enum dpp_public_action_frame_type {
+	DPP_PA_AUTHENTICATION_REQ = 0,
+	DPP_PA_AUTHENTICATION_RESP = 1,
+	DPP_PA_AUTHENTICATION_CONF = 2,
+	DPP_PA_PEER_DISCOVERY_REQ = 5,
+	DPP_PA_PEER_DISCOVERY_RESP = 6,
+	DPP_PA_PKEX_EXCHANGE_REQ = 7,
+	DPP_PA_PKEX_EXCHANGE_RESP = 8,
+	DPP_PA_PKEX_COMMIT_REVEAL_REQ = 9,
+	DPP_PA_PKEX_COMMIT_REVEAL_RESP = 10,
+};
+
+enum dpp_attribute_id {
+	DPP_ATTR_STATUS = 0x1000,
+	DPP_ATTR_I_BOOTSTRAP_KEY_HASH = 0x1001,
+	DPP_ATTR_R_BOOTSTRAP_KEY_HASH = 0x1002,
+	DPP_ATTR_I_PROTOCOL_KEY = 0x1003,
+	DPP_ATTR_WRAPPED_DATA = 0x1004,
+	DPP_ATTR_I_NONCE = 0x1005,
+	DPP_ATTR_I_CAPABILITIES = 0x1006,
+	DPP_ATTR_R_NONCE = 0x1007,
+	DPP_ATTR_R_CAPABILITIES = 0x1008,
+	DPP_ATTR_R_PROTOCOL_KEY = 0x1009,
+	DPP_ATTR_I_AUTH_TAG = 0x100A,
+	DPP_ATTR_R_AUTH_TAG = 0x100B,
+	DPP_ATTR_CONFIG_OBJ = 0x100C,
+	DPP_ATTR_CONNECTOR = 0x100D,
+	DPP_ATTR_CONFIG_ATTR_OBJ = 0x100E,
+	DPP_ATTR_BOOTSTRAP_KEY = 0x100F,
+	DPP_ATTR_OWN_NET_NK_HASH = 0x1011,
+	DPP_ATTR_FINITE_CYCLIC_GROUP = 0x1012,
+	DPP_ATTR_ENCRYPTED_KEY = 0x1013,
+	DPP_ATTR_ENROLLEE_NONCE = 0x1014,
+	DPP_ATTR_CODE_IDENTIFIER = 0x1015,
+	DPP_ATTR_TRANSACTION_ID = 0x1016,
+	DPP_ATTR_BOOTSTRAP_INFO = 0x1017,
+	DPP_ATTR_CHANNEL = 0x1018,
+};
+
+enum dpp_status_error {
+	DPP_STATUS_OK = 0,
+	DPP_STATUS_NOT_COMPATIBLE = 1,
+	DPP_STATUS_AUTH_FAILURE = 2,
+	DPP_STATUS_UNWRAP_FAILURE = 3,
+	DPP_STATUS_BAD_GROUP = 4,
+	DPP_STATUS_CONFIGURE_FAILURE = 5,
+	DPP_STATUS_RESPONSE_PENDING = 6,
+	DPP_STATUS_INVALID_CONNECTOR = 7,
+	DPP_STATUS_NO_MATCH = 8,
+};
+
+#define DPP_CAPAB_ENROLLEE BIT(0)
+#define DPP_CAPAB_CONFIGURATOR BIT(1)
+#define DPP_CAPAB_ROLE_MASK (BIT(0) | BIT(1))
+
+#define DPP_BOOTSTRAP_MAX_FREQ 30
+#define DPP_MAX_NONCE_LEN 32
+#define DPP_MAX_HASH_LEN 64
+#define DPP_MAX_SHARED_SECRET_LEN 66
+
+struct dpp_curve_params {
+	const char *name;
+	size_t hash_len;
+	size_t aes_siv_key_len;
+	size_t nonce_len;
+	size_t prime_len;
+	const char *jwk_crv;
+	u16 ike_group;
+	const char *jws_alg;
+};
+
+enum dpp_bootstrap_type {
+	DPP_BOOTSTRAP_QR_CODE,
+	DPP_BOOTSTRAP_PKEX,
+};
+
+struct dpp_bootstrap_info {
+	struct dl_list list;
+	unsigned int id;
+	enum dpp_bootstrap_type type;
+	char *uri;
+	u8 mac_addr[ETH_ALEN];
+	char *info;
+	unsigned int freq[DPP_BOOTSTRAP_MAX_FREQ];
+	unsigned int num_freq;
+	int own;
+	EVP_PKEY *pubkey;
+	u8 pubkey_hash[SHA256_MAC_LEN];
+	const struct dpp_curve_params *curve;
+	unsigned int pkex_t; /* number of failures before dpp_pkex
+			      * instantiation */
+};
+
+#define PKEX_COUNTER_T_LIMIT 5
+
+struct dpp_pkex {
+	void *msg_ctx;
+	unsigned int initiator:1;
+	unsigned int exchange_done:1;
+	unsigned int failed:1;
+	struct dpp_bootstrap_info *own_bi;
+	u8 own_mac[ETH_ALEN];
+	u8 peer_mac[ETH_ALEN];
+	char *identifier;
+	char *code;
+	EVP_PKEY *x;
+	EVP_PKEY *y;
+	u8 Mx[DPP_MAX_SHARED_SECRET_LEN];
+	u8 Nx[DPP_MAX_SHARED_SECRET_LEN];
+	u8 z[DPP_MAX_HASH_LEN];
+	EVP_PKEY *peer_bootstrap_key;
+	struct wpabuf *exchange_req;
+	struct wpabuf *exchange_resp;
+	unsigned int t; /* number of failures on code use */
+	unsigned int exch_req_wait_time;
+	unsigned int exch_req_tries;
+	unsigned int freq;
+};
+
+enum dpp_akm {
+	DPP_AKM_UNKNOWN,
+	DPP_AKM_DPP,
+	DPP_AKM_PSK,
+	DPP_AKM_SAE,
+	DPP_AKM_PSK_SAE
+};
+
+struct dpp_configuration {
+	u8 ssid[32];
+	size_t ssid_len;
+	enum dpp_akm akm;
+
+	/* For DPP configuration (connector) */
+	os_time_t netaccesskey_expiry;
+
+	/* TODO: groups */
+
+	/* For legacy configuration */
+	char *passphrase;
+	u8 psk[32];
+};
+
+struct dpp_authentication {
+	void *msg_ctx;
+	const struct dpp_curve_params *curve;
+	struct dpp_bootstrap_info *peer_bi;
+	struct dpp_bootstrap_info *own_bi;
+	struct dpp_bootstrap_info *tmp_own_bi;
+	u8 waiting_pubkey_hash[SHA256_MAC_LEN];
+	int response_pending;
+	enum dpp_status_error auth_resp_status;
+	u8 peer_mac_addr[ETH_ALEN];
+	u8 i_nonce[DPP_MAX_NONCE_LEN];
+	u8 r_nonce[DPP_MAX_NONCE_LEN];
+	u8 e_nonce[DPP_MAX_NONCE_LEN];
+	u8 i_capab;
+	u8 r_capab;
+	EVP_PKEY *own_protocol_key;
+	EVP_PKEY *peer_protocol_key;
+	struct wpabuf *req_msg;
+	struct wpabuf *resp_msg;
+	/* Intersection of possible frequencies for initiating DPP
+	 * Authentication exchange */
+	unsigned int freq[DPP_BOOTSTRAP_MAX_FREQ];
+	unsigned int num_freq, freq_idx;
+	unsigned int curr_freq;
+	unsigned int neg_freq;
+	unsigned int num_freq_iters;
+	size_t secret_len;
+	u8 Mx[DPP_MAX_SHARED_SECRET_LEN];
+	size_t Mx_len;
+	u8 Nx[DPP_MAX_SHARED_SECRET_LEN];
+	size_t Nx_len;
+	u8 Lx[DPP_MAX_SHARED_SECRET_LEN];
+	size_t Lx_len;
+	u8 k1[DPP_MAX_HASH_LEN];
+	u8 k2[DPP_MAX_HASH_LEN];
+	u8 ke[DPP_MAX_HASH_LEN];
+	int initiator;
+	int waiting_auth_resp;
+	int waiting_auth_conf;
+	int auth_req_ack;
+	unsigned int auth_resp_tries;
+	u8 allowed_roles;
+	int configurator;
+	int remove_on_tx_status;
+	int auth_success;
+	struct wpabuf *conf_req;
+	const struct wpabuf *conf_resp; /* owned by GAS server */
+	struct dpp_configuration *conf_ap;
+	struct dpp_configuration *conf_sta;
+	struct dpp_configurator *conf;
+	char *connector; /* received signedConnector */
+	u8 ssid[SSID_MAX_LEN];
+	u8 ssid_len;
+	char passphrase[64];
+	u8 psk[PMK_LEN];
+	int psk_set;
+	enum dpp_akm akm;
+	struct wpabuf *net_access_key;
+	os_time_t net_access_key_expiry;
+	struct wpabuf *c_sign_key;
+#ifdef CONFIG_TESTING_OPTIONS
+	char *config_obj_override;
+	char *discovery_override;
+	char *groups_override;
+	unsigned int ignore_netaccesskey_mismatch:1;
+#endif /* CONFIG_TESTING_OPTIONS */
+};
+
+struct dpp_configurator {
+	struct dl_list list;
+	unsigned int id;
+	int own;
+	EVP_PKEY *csign;
+	char *kid;
+	const struct dpp_curve_params *curve;
+};
+
+struct dpp_introduction {
+	u8 pmkid[PMKID_LEN];
+	u8 pmk[PMK_LEN_MAX];
+	size_t pmk_len;
+};
+
+#ifdef CONFIG_TESTING_OPTIONS
+enum dpp_test_behavior {
+	DPP_TEST_DISABLED = 0,
+	DPP_TEST_AFTER_WRAPPED_DATA_AUTH_REQ = 1,
+	DPP_TEST_AFTER_WRAPPED_DATA_AUTH_RESP = 2,
+	DPP_TEST_AFTER_WRAPPED_DATA_AUTH_CONF = 3,
+	DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_REQ = 4,
+	DPP_TEST_AFTER_WRAPPED_DATA_PKEX_CR_RESP = 5,
+	DPP_TEST_AFTER_WRAPPED_DATA_CONF_REQ = 6,
+	DPP_TEST_AFTER_WRAPPED_DATA_CONF_RESP = 7,
+	DPP_TEST_ZERO_I_CAPAB = 8,
+	DPP_TEST_ZERO_R_CAPAB = 9,
+	DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_REQ = 10,
+	DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_REQ = 11,
+	DPP_TEST_NO_I_PROTO_KEY_AUTH_REQ = 12,
+	DPP_TEST_NO_I_NONCE_AUTH_REQ = 13,
+	DPP_TEST_NO_I_CAPAB_AUTH_REQ = 14,
+	DPP_TEST_NO_WRAPPED_DATA_AUTH_REQ = 15,
+	DPP_TEST_NO_STATUS_AUTH_RESP = 16,
+	DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_RESP = 17,
+	DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_RESP = 18,
+	DPP_TEST_NO_R_PROTO_KEY_AUTH_RESP = 19,
+	DPP_TEST_NO_R_NONCE_AUTH_RESP = 20,
+	DPP_TEST_NO_I_NONCE_AUTH_RESP = 21,
+	DPP_TEST_NO_R_CAPAB_AUTH_RESP = 22,
+	DPP_TEST_NO_R_AUTH_AUTH_RESP = 23,
+	DPP_TEST_NO_WRAPPED_DATA_AUTH_RESP = 24,
+	DPP_TEST_NO_STATUS_AUTH_CONF = 25,
+	DPP_TEST_NO_R_BOOTSTRAP_KEY_HASH_AUTH_CONF = 26,
+	DPP_TEST_NO_I_BOOTSTRAP_KEY_HASH_AUTH_CONF = 27,
+	DPP_TEST_NO_I_AUTH_AUTH_CONF = 28,
+	DPP_TEST_NO_WRAPPED_DATA_AUTH_CONF = 29,
+	DPP_TEST_I_NONCE_MISMATCH_AUTH_RESP = 30,
+	DPP_TEST_INCOMPATIBLE_R_CAPAB_AUTH_RESP = 31,
+	DPP_TEST_R_AUTH_MISMATCH_AUTH_RESP = 32,
+	DPP_TEST_I_AUTH_MISMATCH_AUTH_CONF = 33,
+	DPP_TEST_NO_FINITE_CYCLIC_GROUP_PKEX_EXCHANGE_REQ = 34,
+	DPP_TEST_NO_ENCRYPTED_KEY_PKEX_EXCHANGE_REQ = 35,
+	DPP_TEST_NO_STATUS_PKEX_EXCHANGE_RESP = 36,
+	DPP_TEST_NO_ENCRYPTED_KEY_PKEX_EXCHANGE_RESP = 37,
+	DPP_TEST_NO_BOOTSTRAP_KEY_PKEX_CR_REQ = 38,
+	DPP_TEST_NO_I_AUTH_TAG_PKEX_CR_REQ = 39,
+	DPP_TEST_NO_WRAPPED_DATA_PKEX_CR_REQ = 40,
+	DPP_TEST_NO_BOOTSTRAP_KEY_PKEX_CR_RESP = 41,
+	DPP_TEST_NO_R_AUTH_TAG_PKEX_CR_RESP = 42,
+	DPP_TEST_NO_WRAPPED_DATA_PKEX_CR_RESP = 43,
+	DPP_TEST_INVALID_ENCRYPTED_KEY_PKEX_EXCHANGE_REQ = 44,
+	DPP_TEST_INVALID_ENCRYPTED_KEY_PKEX_EXCHANGE_RESP = 45,
+	DPP_TEST_INVALID_STATUS_PKEX_EXCHANGE_RESP = 46,
+	DPP_TEST_INVALID_BOOTSTRAP_KEY_PKEX_CR_REQ = 47,
+	DPP_TEST_INVALID_BOOTSTRAP_KEY_PKEX_CR_RESP = 48,
+	DPP_TEST_I_AUTH_TAG_MISMATCH_PKEX_CR_REQ = 49,
+	DPP_TEST_R_AUTH_TAG_MISMATCH_PKEX_CR_RESP = 50,
+	DPP_TEST_NO_E_NONCE_CONF_REQ = 51,
+	DPP_TEST_NO_CONFIG_ATTR_OBJ_CONF_REQ = 52,
+	DPP_TEST_NO_WRAPPED_DATA_CONF_REQ = 53,
+	DPP_TEST_NO_E_NONCE_CONF_RESP = 54,
+	DPP_TEST_NO_CONFIG_OBJ_CONF_RESP = 55,
+	DPP_TEST_NO_STATUS_CONF_RESP = 56,
+	DPP_TEST_NO_WRAPPED_DATA_CONF_RESP = 57,
+	DPP_TEST_INVALID_STATUS_CONF_RESP = 58,
+	DPP_TEST_E_NONCE_MISMATCH_CONF_RESP = 59,
+	DPP_TEST_NO_TRANSACTION_ID_PEER_DISC_REQ = 60,
+	DPP_TEST_NO_CONNECTOR_PEER_DISC_REQ = 61,
+	DPP_TEST_NO_TRANSACTION_ID_PEER_DISC_RESP = 62,
+	DPP_TEST_NO_STATUS_PEER_DISC_RESP = 63,
+	DPP_TEST_NO_CONNECTOR_PEER_DISC_RESP = 64,
+	DPP_TEST_AUTH_RESP_IN_PLACE_OF_CONF = 65,
+	DPP_TEST_INVALID_I_PROTO_KEY_AUTH_REQ = 66,
+	DPP_TEST_INVALID_R_PROTO_KEY_AUTH_RESP = 67,
+	DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_REQ = 68,
+	DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_REQ = 69,
+	DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_RESP = 70,
+	DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_RESP = 71,
+	DPP_TEST_INVALID_R_BOOTSTRAP_KEY_HASH_AUTH_CONF = 72,
+	DPP_TEST_INVALID_I_BOOTSTRAP_KEY_HASH_AUTH_CONF = 73,
+	DPP_TEST_INVALID_STATUS_AUTH_RESP = 74,
+	DPP_TEST_INVALID_STATUS_AUTH_CONF = 75,
+	DPP_TEST_INVALID_CONFIG_ATTR_OBJ_CONF_REQ = 76,
+	DPP_TEST_INVALID_TRANSACTION_ID_PEER_DISC_RESP = 77,
+	DPP_TEST_INVALID_STATUS_PEER_DISC_RESP = 78,
+	DPP_TEST_INVALID_CONNECTOR_PEER_DISC_RESP = 79,
+	DPP_TEST_INVALID_CONNECTOR_PEER_DISC_REQ = 80,
+	DPP_TEST_INVALID_I_NONCE_AUTH_REQ = 81,
+	DPP_TEST_INVALID_TRANSACTION_ID_PEER_DISC_REQ = 82,
+	DPP_TEST_INVALID_E_NONCE_CONF_REQ = 83,
+	DPP_TEST_STOP_AT_PKEX_EXCHANGE_RESP = 84,
+	DPP_TEST_STOP_AT_PKEX_CR_REQ = 85,
+	DPP_TEST_STOP_AT_PKEX_CR_RESP = 86,
+	DPP_TEST_STOP_AT_AUTH_REQ = 87,
+	DPP_TEST_STOP_AT_AUTH_RESP = 88,
+	DPP_TEST_STOP_AT_AUTH_CONF = 89,
+	DPP_TEST_STOP_AT_CONF_REQ = 90,
+};
+
+extern enum dpp_test_behavior dpp_test;
+extern u8 dpp_pkex_own_mac_override[ETH_ALEN];
+extern u8 dpp_pkex_peer_mac_override[ETH_ALEN];
+extern u8 dpp_pkex_ephemeral_key_override[600];
+extern size_t dpp_pkex_ephemeral_key_override_len;
+extern u8 dpp_protocol_key_override[600];
+extern size_t dpp_protocol_key_override_len;
+extern u8 dpp_nonce_override[DPP_MAX_NONCE_LEN];
+extern size_t dpp_nonce_override_len;
+#endif /* CONFIG_TESTING_OPTIONS */
+
+void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info);
+const char * dpp_bootstrap_type_txt(enum dpp_bootstrap_type type);
+int dpp_bootstrap_key_hash(struct dpp_bootstrap_info *bi);
+int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi,
+			    const char *chan_list);
+int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac);
+int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info);
+struct dpp_bootstrap_info * dpp_parse_qr_code(const char *uri);
+char * dpp_keygen(struct dpp_bootstrap_info *bi, const char *curve,
+		  const u8 *privkey, size_t privkey_len);
+struct hostapd_hw_modes;
+struct dpp_authentication * dpp_auth_init(void *msg_ctx,
+					  struct dpp_bootstrap_info *peer_bi,
+					  struct dpp_bootstrap_info *own_bi,
+					  u8 dpp_allowed_roles,
+					  unsigned int neg_freq,
+					  struct hostapd_hw_modes *own_modes,
+					  u16 num_modes);
+struct dpp_authentication *
+dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual,
+		struct dpp_bootstrap_info *peer_bi,
+		struct dpp_bootstrap_info *own_bi,
+		unsigned int freq, const u8 *hdr, const u8 *attr_start,
+		size_t attr_len);
+struct wpabuf *
+dpp_auth_resp_rx(struct dpp_authentication *auth, const u8 *hdr,
+		 const u8 *attr_start, size_t attr_len);
+struct wpabuf * dpp_build_conf_req(struct dpp_authentication *auth,
+				   const char *json);
+int dpp_auth_conf_rx(struct dpp_authentication *auth, const u8 *hdr,
+		     const u8 *attr_start, size_t attr_len);
+int dpp_notify_new_qr_code(struct dpp_authentication *auth,
+			   struct dpp_bootstrap_info *peer_bi);
+void dpp_configuration_free(struct dpp_configuration *conf);
+void dpp_auth_deinit(struct dpp_authentication *auth);
+struct wpabuf *
+dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start,
+		size_t attr_len);
+int dpp_conf_resp_rx(struct dpp_authentication *auth,
+		     const struct wpabuf *resp);
+struct wpabuf * dpp_alloc_msg(enum dpp_public_action_frame_type type,
+			      size_t len);
+const u8 * dpp_get_attr(const u8 *buf, size_t len, u16 req_id, u16 *ret_len);
+int dpp_check_attrs(const u8 *buf, size_t len);
+int dpp_key_expired(const char *timestamp, os_time_t *expiry);
+const char * dpp_akm_str(enum dpp_akm akm);
+int dpp_configurator_get_key(const struct dpp_configurator *conf, char *buf,
+			     size_t buflen);
+void dpp_configurator_free(struct dpp_configurator *conf);
+struct dpp_configurator *
+dpp_keygen_configurator(const char *curve, const u8 *privkey,
+			size_t privkey_len);
+int dpp_configurator_own_config(struct dpp_authentication *auth,
+				const char *curve, int ap);
+enum dpp_status_error
+dpp_peer_intro(struct dpp_introduction *intro, const char *own_connector,
+	       const u8 *net_access_key, size_t net_access_key_len,
+	       const u8 *csign_key, size_t csign_key_len,
+	       const u8 *peer_connector, size_t peer_connector_len,
+	       os_time_t *expiry);
+struct dpp_pkex * dpp_pkex_init(void *msg_ctx, struct dpp_bootstrap_info *bi,
+				const u8 *own_mac,
+				const char *identifier,
+				const char *code);
+struct dpp_pkex * dpp_pkex_rx_exchange_req(void *msg_ctx,
+					   struct dpp_bootstrap_info *bi,
+					   const u8 *own_mac,
+					   const u8 *peer_mac,
+					   const char *identifier,
+					   const char *code,
+					   const u8 *buf, size_t len);
+struct wpabuf * dpp_pkex_rx_exchange_resp(struct dpp_pkex *pkex,
+					  const u8 *peer_mac,
+					  const u8 *buf, size_t len);
+struct wpabuf * dpp_pkex_rx_commit_reveal_req(struct dpp_pkex *pkex,
+					      const u8 *hdr,
+					      const u8 *buf, size_t len);
+int dpp_pkex_rx_commit_reveal_resp(struct dpp_pkex *pkex, const u8 *hdr,
+				   const u8 *buf, size_t len);
+void dpp_pkex_free(struct dpp_pkex *pkex);
+
+char * dpp_corrupt_connector_signature(const char *connector);
+
+#endif /* DPP_H */

+ 1 - 1
src/common/gas.c

@@ -75,7 +75,7 @@ gas_build_initial_resp(u8 dialog_token, u16 status_code, u16 comeback_delay,
 }
 
 
-static struct wpabuf *
+struct wpabuf *
 gas_build_comeback_resp(u8 dialog_token, u16 status_code, u8 frag_id, u8 more,
 			u16 comeback_delay, size_t size)
 {

+ 3 - 0
src/common/gas.h

@@ -14,6 +14,9 @@ struct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size);
 struct wpabuf * gas_build_comeback_req(u8 dialog_token);
 struct wpabuf * gas_build_initial_resp(u8 dialog_token, u16 status_code,
 				       u16 comeback_delay, size_t size);
+struct wpabuf *
+gas_build_comeback_resp(u8 dialog_token, u16 status_code, u8 frag_id, u8 more,
+			u16 comeback_delay, size_t size);
 struct wpabuf * gas_anqp_build_initial_req(u8 dialog_token, size_t size);
 struct wpabuf * gas_anqp_build_initial_resp(u8 dialog_token, u16 status_code,
 					    u16 comeback_delay, size_t size);

+ 483 - 0
src/common/gas_server.c

@@ -0,0 +1,483 @@
+/*
+ * Generic advertisement service (GAS) server
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "utils/common.h"
+#include "utils/list.h"
+#include "utils/eloop.h"
+#include "ieee802_11_defs.h"
+#include "gas.h"
+#include "gas_server.h"
+
+
+#define MAX_ADV_PROTO_ID_LEN 10
+#define GAS_QUERY_TIMEOUT 10
+
+struct gas_server_handler {
+	struct dl_list list;
+	u8 adv_proto_id[MAX_ADV_PROTO_ID_LEN];
+	u8 adv_proto_id_len;
+	struct wpabuf * (*req_cb)(void *ctx, const u8 *sa,
+				  const u8 *query, size_t query_len);
+	void (*status_cb)(void *ctx, struct wpabuf *resp, int ok);
+	void *ctx;
+	struct gas_server *gas;
+};
+
+struct gas_server_response {
+	struct dl_list list;
+	size_t offset;
+	u8 frag_id;
+	struct wpabuf *resp;
+	int freq;
+	u8 dst[ETH_ALEN];
+	u8 dialog_token;
+	struct gas_server_handler *handler;
+};
+
+struct gas_server {
+	struct dl_list handlers; /* struct gas_server_handler::list */
+	struct dl_list responses; /* struct gas_server_response::list */
+	void (*tx)(void *ctx, int freq, const u8 *da, struct wpabuf *resp,
+		   unsigned int wait_time);
+	void *ctx;
+};
+
+static void gas_server_free_response(struct gas_server_response *response);
+
+
+static void gas_server_response_timeout(void *eloop_ctx, void *user_ctx)
+{
+	struct gas_server_response *response = eloop_ctx;
+
+	wpa_printf(MSG_DEBUG, "GAS: Response @%p timeout for " MACSTR
+		   " (dialog_token=%u freq=%d frag_id=%u sent=%lu/%lu) - drop pending data",
+		   response, MAC2STR(response->dst), response->dialog_token,
+		   response->freq, response->frag_id,
+		   (unsigned long) response->offset,
+		   (unsigned long) wpabuf_len(response->resp));
+	response->handler->status_cb(response->handler->ctx,
+				     response->resp, 0);
+	response->resp = NULL;
+	dl_list_del(&response->list);
+	gas_server_free_response(response);
+}
+
+
+static void gas_server_free_response(struct gas_server_response *response)
+{
+	if (!response)
+		return;
+	wpa_printf(MSG_DEBUG, "DPP: Free GAS response @%p", response);
+	eloop_cancel_timeout(gas_server_response_timeout, response, NULL);
+	wpabuf_free(response->resp);
+	os_free(response);
+}
+
+
+static void
+gas_server_send_resp(struct gas_server *gas, struct gas_server_handler *handler,
+		     const u8 *da, int freq, u8 dialog_token,
+		     struct wpabuf *query_resp)
+{
+	size_t max_len = (freq > 56160) ? 928 : 1400;
+	size_t hdr_len = 24 + 2 + 5 + 3 + handler->adv_proto_id_len + 2;
+	size_t resp_frag_len;
+	struct wpabuf *resp;
+	u16 comeback_delay;
+	struct gas_server_response *response;
+
+	if (!query_resp)
+		return;
+
+	response = os_zalloc(sizeof(*response));
+	if (!response)
+		return;
+	wpa_printf(MSG_DEBUG, "DPP: Allocated GAS response @%p", response);
+	response->freq = freq;
+	response->handler = handler;
+	os_memcpy(response->dst, da, ETH_ALEN);
+	response->dialog_token = dialog_token;
+	if (hdr_len + wpabuf_len(query_resp) > max_len) {
+		/* Need to use comeback to initiate fragmentation */
+		comeback_delay = 1;
+		resp_frag_len = 0;
+	} else {
+		/* Full response fits into the initial response */
+		comeback_delay = 0;
+		resp_frag_len = wpabuf_len(query_resp);
+	}
+
+	resp = gas_build_initial_resp(dialog_token, WLAN_STATUS_SUCCESS,
+				      comeback_delay,
+				      handler->adv_proto_id_len +
+				      resp_frag_len);
+	if (!resp) {
+		gas_server_free_response(response);
+		return;
+	}
+
+	/* Advertisement Protocol element */
+	wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
+	wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
+	wpabuf_put_u8(resp, 0x7f);
+	/* Advertisement Protocol ID */
+	wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
+
+	/* Query Response Length */
+	wpabuf_put_le16(resp, resp_frag_len);
+	if (!comeback_delay)
+		wpabuf_put_buf(resp, query_resp);
+
+	if (comeback_delay) {
+		wpa_printf(MSG_DEBUG,
+			   "GAS: Need to fragment query response");
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "GAS: Full query response fits in the GAS Initial Response frame");
+	}
+	response->offset = resp_frag_len;
+	response->resp = query_resp;
+	dl_list_add(&gas->responses, &response->list);
+	gas->tx(gas->ctx, freq, da, resp, comeback_delay ? 2000 : 0);
+	wpabuf_free(resp);
+	eloop_register_timeout(GAS_QUERY_TIMEOUT, 0,
+			       gas_server_response_timeout, response, NULL);
+}
+
+
+static int
+gas_server_rx_initial_req(struct gas_server *gas, const u8 *da, const u8 *sa,
+			  const u8 *bssid, int freq, u8 dialog_token,
+			  const u8 *data, size_t len)
+{
+	const u8 *pos, *end, *adv_proto, *query_req;
+	u8 adv_proto_len;
+	u16 query_req_len;
+	struct gas_server_handler *handler;
+	struct wpabuf *resp;
+
+	wpa_hexdump(MSG_MSGDUMP, "GAS: Received GAS Initial Request frame",
+		    data, len);
+	pos = data;
+	end = data + len;
+
+	if (end - pos < 2 || pos[0] != WLAN_EID_ADV_PROTO) {
+		wpa_printf(MSG_DEBUG,
+			   "GAS: No Advertisement Protocol element found");
+		return -1;
+	}
+	pos++;
+	adv_proto_len = *pos++;
+	if (end - pos < adv_proto_len || adv_proto_len < 2) {
+		wpa_printf(MSG_DEBUG,
+			   "GAS: Truncated Advertisement Protocol element");
+		return -1;
+	}
+
+	adv_proto = pos;
+	pos += adv_proto_len;
+	wpa_hexdump(MSG_MSGDUMP, "GAS: Advertisement Protocol element",
+		    adv_proto, adv_proto_len);
+
+	if (end - pos < 2) {
+		wpa_printf(MSG_DEBUG, "GAS: No Query Request Length field");
+		return -1;
+	}
+	query_req_len = WPA_GET_LE16(pos);
+	pos += 2;
+	if (end - pos < query_req_len) {
+		wpa_printf(MSG_DEBUG, "GAS: Truncated Query Request field");
+		return -1;
+	}
+	query_req = pos;
+	pos += query_req_len;
+	wpa_hexdump(MSG_MSGDUMP, "GAS: Query Request",
+		    query_req, query_req_len);
+
+	if (pos < end) {
+		wpa_hexdump(MSG_MSGDUMP,
+			    "GAS: Ignored extra data after Query Request field",
+			    pos, end - pos);
+	}
+
+	dl_list_for_each(handler, &gas->handlers, struct gas_server_handler,
+			 list) {
+		if (adv_proto_len < 1 + handler->adv_proto_id_len ||
+		    os_memcmp(adv_proto + 1, handler->adv_proto_id,
+			      handler->adv_proto_id_len) != 0)
+			continue;
+
+		wpa_printf(MSG_DEBUG,
+			   "GAS: Calling handler for the requested Advertisement Protocol ID");
+		resp = handler->req_cb(handler->ctx, sa, query_req,
+				       query_req_len);
+		wpa_hexdump_buf(MSG_MSGDUMP, "GAS: Response from the handler",
+				resp);
+		gas_server_send_resp(gas, handler, sa, freq, dialog_token,
+				     resp);
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "GAS: No registered handler for the requested Advertisement Protocol ID");
+	return -1;
+}
+
+
+static void
+gas_server_handle_rx_comeback_req(struct gas_server_response *response)
+{
+	struct gas_server_handler *handler = response->handler;
+	struct gas_server *gas = handler->gas;
+	size_t max_len = (response->freq > 56160) ? 928 : 1400;
+	size_t hdr_len = 24 + 2 + 6 + 3 + handler->adv_proto_id_len + 2;
+	size_t remaining, resp_frag_len;
+	struct wpabuf *resp;
+
+	remaining = wpabuf_len(response->resp) - response->offset;
+	if (hdr_len + remaining > max_len)
+		resp_frag_len = max_len - hdr_len;
+	else
+		resp_frag_len = remaining;
+	wpa_printf(MSG_DEBUG,
+		   "GAS: Sending out %u/%u remaining Query Response octets",
+		   (unsigned int) resp_frag_len, (unsigned int) remaining);
+
+	resp = gas_build_comeback_resp(response->dialog_token,
+				       WLAN_STATUS_SUCCESS,
+				       response->frag_id++,
+				       resp_frag_len < remaining, 0,
+				       handler->adv_proto_id_len +
+				       resp_frag_len);
+	if (!resp) {
+		gas_server_free_response(response);
+		return;
+	}
+
+	/* Advertisement Protocol element */
+	wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
+	wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
+	wpabuf_put_u8(resp, 0x7f);
+	/* Advertisement Protocol ID */
+	wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
+
+	/* Query Response Length */
+	wpabuf_put_le16(resp, resp_frag_len);
+	wpabuf_put_data(resp, wpabuf_head_u8(response->resp) + response->offset,
+			resp_frag_len);
+
+	response->offset += resp_frag_len;
+
+	gas->tx(gas->ctx, response->freq, response->dst, resp,
+		remaining > resp_frag_len ? 2000 : 0);
+	wpabuf_free(resp);
+}
+
+
+static int
+gas_server_rx_comeback_req(struct gas_server *gas, const u8 *da, const u8 *sa,
+			   const u8 *bssid, int freq, u8 dialog_token)
+{
+	struct gas_server_response *response;
+
+	dl_list_for_each(response, &gas->responses, struct gas_server_response,
+			 list) {
+		if (response->dialog_token != dialog_token ||
+		    os_memcmp(sa, response->dst, ETH_ALEN) != 0)
+			continue;
+		gas_server_handle_rx_comeback_req(response);
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG, "GAS: No pending GAS response for " MACSTR
+		   " (dialog token %u)", MAC2STR(sa), dialog_token);
+	return -1;
+}
+
+
+/**
+ * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame
+ * @gas: GAS query data from gas_server_init()
+ * @da: Destination MAC address of the Action frame
+ * @sa: Source MAC address of the Action frame
+ * @bssid: BSSID of the Action frame
+ * @categ: Category of the Action frame
+ * @data: Payload of the Action frame
+ * @len: Length of @data
+ * @freq: Frequency (in MHz) on which the frame was received
+ * Returns: 0 if the Public Action frame was a GAS request frame or -1 if not
+ */
+int gas_server_rx(struct gas_server *gas, const u8 *da, const u8 *sa,
+		  const u8 *bssid, u8 categ, const u8 *data, size_t len,
+		  int freq)
+{
+	u8 action, dialog_token;
+	const u8 *pos, *end;
+
+	if (!gas || len < 2)
+		return -1;
+
+	if (categ == WLAN_ACTION_PROTECTED_DUAL)
+		return -1; /* Not supported for now */
+
+	pos = data;
+	end = data + len;
+	action = *pos++;
+	dialog_token = *pos++;
+
+	if (action != WLAN_PA_GAS_INITIAL_REQ &&
+	    action != WLAN_PA_GAS_COMEBACK_REQ)
+		return -1; /* Not a GAS request */
+
+	wpa_printf(MSG_DEBUG, "GAS: Received GAS %s Request frame DA=" MACSTR
+		   " SA=" MACSTR " BSSID=" MACSTR
+		   " freq=%d dialog_token=%u len=%u",
+		   action == WLAN_PA_GAS_INITIAL_REQ ? "Initial" : "Comeback",
+		   MAC2STR(da), MAC2STR(sa), MAC2STR(bssid), freq, dialog_token,
+		   (unsigned int) len);
+
+	if (action == WLAN_PA_GAS_INITIAL_REQ)
+		return gas_server_rx_initial_req(gas, da, sa, bssid,
+						 freq, dialog_token,
+						 pos, end - pos);
+	return gas_server_rx_comeback_req(gas, da, sa, bssid,
+					  freq, dialog_token);
+}
+
+
+static void gas_server_handle_tx_status(struct gas_server_response *response,
+					int ack)
+{
+	if (ack && response->offset < wpabuf_len(response->resp)) {
+		wpa_printf(MSG_DEBUG,
+			   "GAS: More fragments remaining - keep pending entry");
+		return;
+	}
+
+	if (!ack)
+		wpa_printf(MSG_DEBUG,
+			   "GAS: No ACK received - drop pending entry");
+	else
+		wpa_printf(MSG_DEBUG,
+			   "GAS: Last fragment of the response sent out - drop pending entry");
+
+	response->handler->status_cb(response->handler->ctx,
+				     response->resp, ack);
+	response->resp = NULL;
+	dl_list_del(&response->list);
+	gas_server_free_response(response);
+}
+
+
+void gas_server_tx_status(struct gas_server *gas, const u8 *dst, const u8 *data,
+			  size_t data_len, int ack)
+{
+	const u8 *pos;
+	u8 action, code, dialog_token;
+	struct gas_server_response *response;
+
+	if (data_len < 24 + 3)
+		return;
+	pos = data + 24;
+	action = *pos++;
+	code = *pos++;
+	dialog_token = *pos++;
+	if (action != WLAN_ACTION_PUBLIC ||
+	    (code != WLAN_PA_GAS_INITIAL_RESP &&
+	     code != WLAN_PA_GAS_COMEBACK_RESP))
+		return;
+	wpa_printf(MSG_DEBUG, "GAS: TX status dst=" MACSTR
+		   " ack=%d %s dialog_token=%u",
+		   MAC2STR(dst), ack,
+		   code == WLAN_PA_GAS_INITIAL_RESP ? "initial" : "comeback",
+		   dialog_token);
+	dl_list_for_each(response, &gas->responses, struct gas_server_response,
+			 list) {
+		if (response->dialog_token != dialog_token ||
+		    os_memcmp(dst, response->dst, ETH_ALEN) != 0)
+			continue;
+		gas_server_handle_tx_status(response, ack);
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "GAS: No pending response matches TX status");
+}
+
+
+struct gas_server * gas_server_init(void *ctx,
+				    void (*tx)(void *ctx, int freq,
+					       const u8 *da,
+					       struct wpabuf *buf,
+					       unsigned int wait_time))
+{
+	struct gas_server *gas;
+
+	gas = os_zalloc(sizeof(*gas));
+	if (!gas)
+		return NULL;
+	gas->ctx = ctx;
+	gas->tx = tx;
+	dl_list_init(&gas->handlers);
+	dl_list_init(&gas->responses);
+	return gas;
+}
+
+
+void gas_server_deinit(struct gas_server *gas)
+{
+	struct gas_server_handler *handler, *tmp;
+	struct gas_server_response *response, *tmp_r;
+
+	if (!gas)
+		return;
+
+	dl_list_for_each_safe(handler, tmp, &gas->handlers,
+			      struct gas_server_handler, list) {
+		dl_list_del(&handler->list);
+		os_free(handler);
+	}
+
+	dl_list_for_each_safe(response, tmp_r, &gas->responses,
+			      struct gas_server_response, list) {
+		dl_list_del(&response->list);
+		gas_server_free_response(response);
+	}
+
+	os_free(gas);
+}
+
+
+int gas_server_register(struct gas_server *gas,
+			const u8 *adv_proto_id, u8 adv_proto_id_len,
+			struct wpabuf *
+			(*req_cb)(void *ctx, const u8 *sa,
+				  const u8 *query, size_t query_len),
+			void (*status_cb)(void *ctx, struct wpabuf *resp,
+					  int ok),
+			void *ctx)
+{
+	struct gas_server_handler *handler;
+
+	if (!gas || adv_proto_id_len > MAX_ADV_PROTO_ID_LEN)
+		return -1;
+	handler = os_zalloc(sizeof(*handler));
+	if (!handler)
+		return -1;
+
+	os_memcpy(handler->adv_proto_id, adv_proto_id, adv_proto_id_len);
+	handler->adv_proto_id_len = adv_proto_id_len;
+	handler->req_cb = req_cb;
+	handler->status_cb = status_cb;
+	handler->ctx = ctx;
+	handler->gas = gas;
+	dl_list_add(&gas->handlers, &handler->list);
+
+	return 0;
+}

+ 44 - 0
src/common/gas_server.h

@@ -0,0 +1,44 @@
+/*
+ * Generic advertisement service (GAS) server
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef GAS_SERVER_H
+#define GAS_SERVER_H
+
+#ifdef CONFIG_GAS_SERVER
+
+struct gas_server;
+
+struct gas_server * gas_server_init(void *ctx,
+				    void (*tx)(void *ctx, int freq,
+					       const u8 *da,
+					       struct wpabuf *buf,
+					       unsigned int wait_time));
+void gas_server_deinit(struct gas_server *gas);
+int gas_server_register(struct gas_server *gas,
+			const u8 *adv_proto_id, u8 adv_proto_id_len,
+			struct wpabuf *
+			(*req_cb)(void *ctx, const u8 *sa,
+				  const u8 *query, size_t query_len),
+			void (*status_cb)(void *ctx, struct wpabuf *resp,
+					  int ok),
+			void *ctx);
+int gas_server_rx(struct gas_server *gas, const u8 *da, const u8 *sa,
+		  const u8 *bssid, u8 categ, const u8 *data, size_t len,
+		  int freq);
+void gas_server_tx_status(struct gas_server *gas, const u8 *dst, const u8 *data,
+			  size_t data_len, int ack);
+
+#else /* CONFIG_GAS_SERVER */
+
+static inline void gas_server_deinit(struct gas_server *gas)
+{
+}
+
+#endif /* CONFIG_GAS_SERVER */
+
+#endif /* GAS_SERVER_H */

+ 103 - 3
src/common/hw_features_common.c

@@ -89,7 +89,7 @@ int allowed_ht40_channel_pair(struct hostapd_hw_modes *mode, int pri_chan,
 {
 	int ok, j, first;
 	int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 140,
-			  149, 157, 184, 192 };
+			  149, 157, 165, 184, 192 };
 	size_t k;
 
 	if (pri_chan == sec_chan || !sec_chan)
@@ -388,8 +388,10 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 		/* fall through */
 	case VHT_CHANWIDTH_80MHZ:
 		data->bandwidth = 80;
-		if ((vht_oper_chwidth == 1 && center_segment1) ||
-		    (vht_oper_chwidth == 3 && !center_segment1) ||
+		if ((vht_oper_chwidth == VHT_CHANWIDTH_80MHZ &&
+		     center_segment1) ||
+		    (vht_oper_chwidth == VHT_CHANWIDTH_80P80MHZ &&
+		     !center_segment1) ||
 		    !sec_channel_offset)
 			return -1;
 		if (!center_segment0) {
@@ -453,3 +455,101 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 
 	return 0;
 }
+
+
+void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps,
+		      int disabled)
+{
+	/* Masking these out disables HT40 */
+	le16 msk = host_to_le16(HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET |
+				HT_CAP_INFO_SHORT_GI40MHZ);
+
+	if (disabled)
+		htcaps->ht_capabilities_info &= ~msk;
+	else
+		htcaps->ht_capabilities_info |= msk;
+}
+
+
+#ifdef CONFIG_IEEE80211AC
+
+static int _ieee80211ac_cap_check(u32 hw, u32 conf, u32 cap,
+				  const char *name)
+{
+	u32 req_cap = conf & cap;
+
+	/*
+	 * Make sure we support all requested capabilities.
+	 * NOTE: We assume that 'cap' represents a capability mask,
+	 * not a discrete value.
+	 */
+	if ((hw & req_cap) != req_cap) {
+		wpa_printf(MSG_ERROR,
+			   "Driver does not support configured VHT capability [%s]",
+			   name);
+		return 0;
+	}
+	return 1;
+}
+
+
+static int ieee80211ac_cap_check_max(u32 hw, u32 conf, u32 mask,
+				     unsigned int shift,
+				     const char *name)
+{
+	u32 hw_max = hw & mask;
+	u32 conf_val = conf & mask;
+
+	if (conf_val > hw_max) {
+		wpa_printf(MSG_ERROR,
+			   "Configured VHT capability [%s] exceeds max value supported by the driver (%d > %d)",
+			   name, conf_val >> shift, hw_max >> shift);
+		return 0;
+	}
+	return 1;
+}
+
+
+int ieee80211ac_cap_check(u32 hw, u32 conf)
+{
+#define VHT_CAP_CHECK(cap) \
+	do { \
+		if (!_ieee80211ac_cap_check(hw, conf, cap, #cap)) \
+			return 0; \
+	} while (0)
+
+#define VHT_CAP_CHECK_MAX(cap) \
+	do { \
+		if (!ieee80211ac_cap_check_max(hw, conf, cap, cap ## _SHIFT, \
+					       #cap)) \
+			return 0; \
+	} while (0)
+
+	VHT_CAP_CHECK_MAX(VHT_CAP_MAX_MPDU_LENGTH_MASK);
+	VHT_CAP_CHECK_MAX(VHT_CAP_SUPP_CHAN_WIDTH_MASK);
+	VHT_CAP_CHECK(VHT_CAP_RXLDPC);
+	VHT_CAP_CHECK(VHT_CAP_SHORT_GI_80);
+	VHT_CAP_CHECK(VHT_CAP_SHORT_GI_160);
+	VHT_CAP_CHECK(VHT_CAP_TXSTBC);
+	VHT_CAP_CHECK_MAX(VHT_CAP_RXSTBC_MASK);
+	VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMER_CAPABLE);
+	VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMEE_CAPABLE);
+	VHT_CAP_CHECK_MAX(VHT_CAP_BEAMFORMEE_STS_MAX);
+	VHT_CAP_CHECK_MAX(VHT_CAP_SOUNDING_DIMENSION_MAX);
+	VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMER_CAPABLE);
+	VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMEE_CAPABLE);
+	VHT_CAP_CHECK(VHT_CAP_VHT_TXOP_PS);
+	VHT_CAP_CHECK(VHT_CAP_HTC_VHT);
+	VHT_CAP_CHECK_MAX(VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX);
+	VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB);
+	VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB);
+	VHT_CAP_CHECK(VHT_CAP_RX_ANTENNA_PATTERN);
+	VHT_CAP_CHECK(VHT_CAP_TX_ANTENNA_PATTERN);
+
+#undef VHT_CAP_CHECK
+#undef VHT_CAP_CHECK_MAX
+
+	return 1;
+}
+
+#endif /* CONFIG_IEEE80211AC */

+ 3 - 0
src/common/hw_features_common.h

@@ -35,5 +35,8 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 			    int vht_enabled, int sec_channel_offset,
 			    int vht_oper_chwidth, int center_segment0,
 			    int center_segment1, u32 vht_caps);
+void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps,
+		      int disabled);
+int ieee80211ac_cap_check(u32 hw, u32 conf);
 
 #endif /* HW_FEATURES_COMMON_H */

+ 424 - 1
src/common/ieee802_11_common.c

@@ -179,6 +179,96 @@ static int ieee802_11_parse_vendor_specific(const u8 *pos, size_t elen,
 }
 
 
+static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
+				      struct ieee802_11_elems *elems,
+				      int show_errors)
+{
+	u8 ext_id;
+
+	if (elen < 1) {
+		if (show_errors) {
+			wpa_printf(MSG_MSGDUMP,
+				   "short information element (Ext)");
+		}
+		return -1;
+	}
+
+	ext_id = *pos++;
+	elen--;
+
+	switch (ext_id) {
+	case WLAN_EID_EXT_ASSOC_DELAY_INFO:
+		if (elen != 1)
+			break;
+		elems->assoc_delay_info = pos;
+		break;
+	case WLAN_EID_EXT_FILS_REQ_PARAMS:
+		if (elen < 3)
+			break;
+		elems->fils_req_params = pos;
+		elems->fils_req_params_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_KEY_CONFIRM:
+		elems->fils_key_confirm = pos;
+		elems->fils_key_confirm_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_SESSION:
+		if (elen != FILS_SESSION_LEN)
+			break;
+		elems->fils_session = pos;
+		break;
+	case WLAN_EID_EXT_FILS_HLP_CONTAINER:
+		if (elen < 2 * ETH_ALEN)
+			break;
+		elems->fils_hlp = pos;
+		elems->fils_hlp_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN:
+		if (elen < 1)
+			break;
+		elems->fils_ip_addr_assign = pos;
+		elems->fils_ip_addr_assign_len = elen;
+		break;
+	case WLAN_EID_EXT_KEY_DELIVERY:
+		if (elen < WPA_KEY_RSC_LEN)
+			break;
+		elems->key_delivery = pos;
+		elems->key_delivery_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_WRAPPED_DATA:
+		elems->fils_wrapped_data = pos;
+		elems->fils_wrapped_data_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_PUBLIC_KEY:
+		if (elen < 1)
+			break;
+		elems->fils_pk = pos;
+		elems->fils_pk_len = elen;
+		break;
+	case WLAN_EID_EXT_FILS_NONCE:
+		if (elen != FILS_NONCE_LEN)
+			break;
+		elems->fils_nonce = pos;
+		break;
+	case WLAN_EID_EXT_OWE_DH_PARAM:
+		if (elen < 2)
+			break;
+		elems->owe_dh = pos;
+		elems->owe_dh_len = elen;
+		break;
+	default:
+		if (show_errors) {
+			wpa_printf(MSG_MSGDUMP,
+				   "IEEE 802.11 element parsing ignored unknown element extension (ext_id=%u elen=%u)",
+				   ext_id, (unsigned int) elen);
+		}
+		return -1;
+	}
+
+	return 0;
+}
+
+
 /**
  * ieee802_11_parse_elems - Parse information elements in management frames
  * @start: Pointer to the start of IEs
@@ -262,6 +352,10 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
 			elems->rsn_ie_len = elen;
 			break;
 		case WLAN_EID_PWR_CAPABILITY:
+			if (elen < 2)
+				break;
+			elems->power_capab = pos;
+			elems->power_capab_len = elen;
 			break;
 		case WLAN_EID_SUPPORTED_CHANNELS:
 			elems->supp_channels = pos;
@@ -379,6 +473,35 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
 			elems->rrm_enabled = pos;
 			elems->rrm_enabled_len = elen;
 			break;
+		case WLAN_EID_CAG_NUMBER:
+			elems->cag_number = pos;
+			elems->cag_number_len = elen;
+			break;
+		case WLAN_EID_AP_CSN:
+			if (elen < 1)
+				break;
+			elems->ap_csn = pos;
+			break;
+		case WLAN_EID_FILS_INDICATION:
+			if (elen < 2)
+				break;
+			elems->fils_indic = pos;
+			elems->fils_indic_len = elen;
+			break;
+		case WLAN_EID_DILS:
+			if (elen < 2)
+				break;
+			elems->dils = pos;
+			elems->dils_len = elen;
+			break;
+		case WLAN_EID_FRAGMENT:
+			/* TODO */
+			break;
+		case WLAN_EID_EXTENSION:
+			if (ieee802_11_parse_extension(pos, elen, elems,
+						       show_errors))
+				unknown++;
+			break;
 		default:
 			unknown++;
 			if (!show_errors)
@@ -681,6 +804,25 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
 		return HOSTAPD_MODE_IEEE80211A;
 	}
 
+	/* 5 GHz, channels 52..64 */
+	if (freq >= 5260 && freq <= 5320) {
+		if ((freq - 5000) % 5)
+			return NUM_HOSTAPD_MODES;
+
+		if (vht_opclass)
+			*op_class = vht_opclass;
+		else if (sec_channel == 1)
+			*op_class = 119;
+		else if (sec_channel == -1)
+			*op_class = 120;
+		else
+			*op_class = 118;
+
+		*channel = (freq - 5000) / 5;
+
+		return HOSTAPD_MODE_IEEE80211A;
+	}
+
 	/* 5 GHz, channels 149..169 */
 	if (freq >= 5745 && freq <= 5845) {
 		if ((freq - 5000) % 5)
@@ -981,7 +1123,7 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan)
 			return -1;
 		return 5000 + 5 * chan;
 	case 129: /* center freqs 50, 114; 160 MHz */
-		if (chan < 50 || chan > 114)
+		if (chan < 36 || chan > 128)
 			return -1;
 		return 5000 + 5 * chan;
 	case 180: /* 60 GHz band, channels 1..4 */
@@ -1295,6 +1437,40 @@ const u8 * get_ie(const u8 *ies, size_t len, u8 eid)
 }
 
 
+/**
+ * get_ie_ext - Fetch a specified extended information element from IEs buffer
+ * @ies: Information elements buffer
+ * @len: Information elements buffer length
+ * @ext: Information element extension identifier (WLAN_EID_EXT_*)
+ * Returns: Pointer to the information element (id field) or %NULL if not found
+ *
+ * This function returns the first matching information element in the IEs
+ * buffer or %NULL in case the element is not found.
+ */
+const u8 * get_ie_ext(const u8 *ies, size_t len, u8 ext)
+{
+	const u8 *end;
+
+	if (!ies)
+		return NULL;
+
+	end = ies + len;
+
+	while (end - ies > 1) {
+		if (2 + ies[1] > end - ies)
+			break;
+
+		if (ies[0] == WLAN_EID_EXTENSION && ies[1] >= 1 &&
+		    ies[2] == ext)
+			return ies;
+
+		ies += 2 + ies[1];
+	}
+
+	return NULL;
+}
+
+
 size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len)
 {
 	/*
@@ -1317,3 +1493,250 @@ size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len)
 
 	return 6 + attr_len;
 }
+
+
+static const struct country_op_class us_op_class[] = {
+	{ 1, 115 },
+	{ 2, 118 },
+	{ 3, 124 },
+	{ 4, 121 },
+	{ 5, 125 },
+	{ 12, 81 },
+	{ 22, 116 },
+	{ 23, 119 },
+	{ 24, 122 },
+	{ 25, 126 },
+	{ 26, 126 },
+	{ 27, 117 },
+	{ 28, 120 },
+	{ 29, 123 },
+	{ 30, 127 },
+	{ 31, 127 },
+	{ 32, 83 },
+	{ 33, 84 },
+	{ 34, 180 },
+};
+
+static const struct country_op_class eu_op_class[] = {
+	{ 1, 115 },
+	{ 2, 118 },
+	{ 3, 121 },
+	{ 4, 81 },
+	{ 5, 116 },
+	{ 6, 119 },
+	{ 7, 122 },
+	{ 8, 117 },
+	{ 9, 120 },
+	{ 10, 123 },
+	{ 11, 83 },
+	{ 12, 84 },
+	{ 17, 125 },
+	{ 18, 180 },
+};
+
+static const struct country_op_class jp_op_class[] = {
+	{ 1, 115 },
+	{ 30, 81 },
+	{ 31, 82 },
+	{ 32, 118 },
+	{ 33, 118 },
+	{ 34, 121 },
+	{ 35, 121 },
+	{ 36, 116 },
+	{ 37, 119 },
+	{ 38, 119 },
+	{ 39, 122 },
+	{ 40, 122 },
+	{ 41, 117 },
+	{ 42, 120 },
+	{ 43, 120 },
+	{ 44, 123 },
+	{ 45, 123 },
+	{ 56, 83 },
+	{ 57, 84 },
+	{ 58, 121 },
+	{ 59, 180 },
+};
+
+static const struct country_op_class cn_op_class[] = {
+	{ 1, 115 },
+	{ 2, 118 },
+	{ 3, 125 },
+	{ 4, 116 },
+	{ 5, 119 },
+	{ 6, 126 },
+	{ 7, 81 },
+	{ 8, 83 },
+	{ 9, 84 },
+};
+
+static u8
+global_op_class_from_country_array(u8 op_class, size_t array_size,
+				   const struct country_op_class *country_array)
+{
+	size_t i;
+
+	for (i = 0; i < array_size; i++) {
+		if (country_array[i].country_op_class == op_class)
+			return country_array[i].global_op_class;
+	}
+
+	return 0;
+}
+
+
+u8 country_to_global_op_class(const char *country, u8 op_class)
+{
+	const struct country_op_class *country_array;
+	size_t size;
+	u8 g_op_class;
+
+	if (country_match(us_op_class_cc, country)) {
+		country_array = us_op_class;
+		size = ARRAY_SIZE(us_op_class);
+	} else if (country_match(eu_op_class_cc, country)) {
+		country_array = eu_op_class;
+		size = ARRAY_SIZE(eu_op_class);
+	} else if (country_match(jp_op_class_cc, country)) {
+		country_array = jp_op_class;
+		size = ARRAY_SIZE(jp_op_class);
+	} else if (country_match(cn_op_class_cc, country)) {
+		country_array = cn_op_class;
+		size = ARRAY_SIZE(cn_op_class);
+	} else {
+		/*
+		 * Countries that do not match any of the above countries use
+		 * global operating classes
+		 */
+		return op_class;
+	}
+
+	g_op_class = global_op_class_from_country_array(op_class, size,
+							country_array);
+
+	/*
+	 * If the given operating class did not match any of the country's
+	 * operating classes, assume that global operating class is used.
+	 */
+	return g_op_class ? g_op_class : op_class;
+}
+
+
+const struct oper_class_map * get_oper_class(const char *country, u8 op_class)
+{
+	const struct oper_class_map *op;
+
+	if (country)
+		op_class = country_to_global_op_class(country, op_class);
+
+	op = &global_op_class[0];
+	while (op->op_class && op->op_class != op_class)
+		op++;
+
+	if (!op->op_class)
+		return NULL;
+
+	return op;
+}
+
+
+int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep,
+				    size_t nei_rep_len)
+{
+	u8 *nei_pos = nei_rep;
+	const char *end;
+
+	/*
+	 * BSS Transition Candidate List Entries - Neighbor Report elements
+	 * neighbor=<BSSID>,<BSSID Information>,<Operating Class>,
+	 * <Channel Number>,<PHY Type>[,<hexdump of Optional Subelements>]
+	 */
+	while (pos) {
+		u8 *nei_start;
+		long int val;
+		char *endptr, *tmp;
+
+		pos = os_strstr(pos, " neighbor=");
+		if (!pos)
+			break;
+		if (nei_pos + 15 > nei_rep + nei_rep_len) {
+			wpa_printf(MSG_DEBUG,
+				   "Not enough room for additional neighbor");
+			return -1;
+		}
+		pos += 10;
+
+		nei_start = nei_pos;
+		*nei_pos++ = WLAN_EID_NEIGHBOR_REPORT;
+		nei_pos++; /* length to be filled in */
+
+		if (hwaddr_aton(pos, nei_pos)) {
+			wpa_printf(MSG_DEBUG, "Invalid BSSID");
+			return -1;
+		}
+		nei_pos += ETH_ALEN;
+		pos += 17;
+		if (*pos != ',') {
+			wpa_printf(MSG_DEBUG, "Missing BSSID Information");
+			return -1;
+		}
+		pos++;
+
+		val = strtol(pos, &endptr, 0);
+		WPA_PUT_LE32(nei_pos, val);
+		nei_pos += 4;
+		if (*endptr != ',') {
+			wpa_printf(MSG_DEBUG, "Missing Operating Class");
+			return -1;
+		}
+		pos = endptr + 1;
+
+		*nei_pos++ = atoi(pos); /* Operating Class */
+		pos = os_strchr(pos, ',');
+		if (pos == NULL) {
+			wpa_printf(MSG_DEBUG, "Missing Channel Number");
+			return -1;
+		}
+		pos++;
+
+		*nei_pos++ = atoi(pos); /* Channel Number */
+		pos = os_strchr(pos, ',');
+		if (pos == NULL) {
+			wpa_printf(MSG_DEBUG, "Missing PHY Type");
+			return -1;
+		}
+		pos++;
+
+		*nei_pos++ = atoi(pos); /* PHY Type */
+		end = os_strchr(pos, ' ');
+		tmp = os_strchr(pos, ',');
+		if (tmp && (!end || tmp < end)) {
+			/* Optional Subelements (hexdump) */
+			size_t len;
+
+			pos = tmp + 1;
+			end = os_strchr(pos, ' ');
+			if (end)
+				len = end - pos;
+			else
+				len = os_strlen(pos);
+			if (nei_pos + len / 2 > nei_rep + nei_rep_len) {
+				wpa_printf(MSG_DEBUG,
+					   "Not enough room for neighbor subelements");
+				return -1;
+			}
+			if (len & 0x01 ||
+			    hexstr2bin(pos, nei_pos, len / 2) < 0) {
+				wpa_printf(MSG_DEBUG,
+					   "Invalid neighbor subelement info");
+				return -1;
+			}
+			nei_pos += len / 2;
+			pos = end;
+		}
+
+		nei_start[1] = nei_pos - nei_start - 2;
+	}
+
+	return nei_pos - nei_rep;
+}

Some files were not shown because too many files changed in this diff